Repository: opentofu/opentofu
File: internal/flock/filesystem_lock_test.go (Go)

Code before:

	// Should fail due to timeout or cancellation
	if err == nil {
		t.Fatal("Expected blocking lock to be cancelled, but it succeeded")
	}

	// Check cancellation behavior
	if err == context.DeadlineExceeded || err == context.Canceled {
		t.Logf("Lock was properly cancelled: %v (took %v)", err, elapsed)
		
		// Should have been cancelled within reasonable time
		if elapsed > 200*time.Millisecond {
			t.Fatalf("Cancellation took too long: %v", elapsed)
		}
	} else {
		t.Fatalf("Expected timeout/cancellation error, got: %v", err)
	}
}

func TestLockBlocking_EventualSuccess(t *testing.T) {
	// Tests eventual success of LockBlocking on a single file while a Lock is 
	// already in place which is then released
	// Doesn't really test anything in POSIX systems since the same process can hold multiple locks
	// on the same file

	tmpDir := t.TempDir()
	lockFile := filepath.Join(tmpDir, "eventual.lock")

	// Create test file
	f1, err := os.OpenFile(lockFile, os.O_CREATE|os.O_RDWR, 0644)
	if err != nil {
		t.Fatalf("Failed to create test file: %v", err)
	}
	defer f1.Close()

	f2, err := os.OpenFile(lockFile, os.O_RDWR, 0644)
	if err != nil {
		t.Fatalf("Failed to open test file: %v", err)
	}
	defer f2.Close()

	// Acquire lock with first handle
	err = Lock(f1)
	if err != nil {
		t.Fatalf("Failed to acquire first lock: %v", err)
	}

	var wg sync.WaitGroup
	var lockErr error

	// Start blocking lock in goroutine

Review comment (performance):
This test is panicking on Windows:

```
panic: test timed out after 10m0s
	running tests:
		TestLockBlocking_EventualSuccess (10m0s)

goroutine 16 [running]:
testing.(*M).startAlarm.func1()
	C:/hostedtoolcache/windows/go/1.25.2/x64/src/testing/testing.go:2682 +0x345
created by time.goFunc
	C:/hostedtoolcache/windows/go/1.25.2/x64/src/time/sleep.go:215 +0x2d

goroutine 1 [chan receive]:
testing.(*T).Run(0xc000003500, {0x7ff618a4756f?, 0x7ffac65a3fb0?}, 0x7ff618a4e568)
	C:/hostedtoolcache/windows/go/1.25.2/x64/src/testing/testing.go:2005 +0x469
testing.runTests.func1(0xc000003500)
	C:/hostedtoolcache/windows/go/1.25.2/x64/src/testing/testing.go:2477 +0x37
testing.tRunner(0xc000003500, 0xc000049c70)
	C:/hostedtoolcache/windows/go/1.25.2/x64/src/testing/testing.go:1934 +0xc3
testing.runTests(0xc0000080f0, {0x7ff618b8b700, 0x6, 0x6}, {0x7?, 0xc00005c8c0?, 0x7ff618b90720?})
	C:/hostedtoolcache/windows/go/1.25.2/x64/src/testing/testing.go:2475 +0x4b4
testing.(*M).Run(0xc000060140)
	C:/hostedtoolcache/windows/go/1.25.2/x64/src/testing/testing.go:2337 +0x63a
main.main()
	_testmain.go:55 +0x9b

goroutine 13 [sync.WaitGroup.Wait]:
sync.runtime_SemacquireWaitGroup(0xc000036800?, 0xa0?)
	C:/hostedtoolcache/windows/go/1.25.2/x64/src/runtime/sema.go:114 +0x2e
sync.(*WaitGroup).Wait(0xc00000a460)
	C:/hostedtoolcache/windows/go/1.25.2/x64/src/sync/waitgroup.go:206 +0x85
github.com/opentofu/opentofu/internal/flock.TestLockBlocking_EventualSuccess(0xc0001421c0)
	D:/a/opentofu/opentofu/internal/flock/filesystem_lock_test.go:279 +0x2c5
testing.tRunner(0xc0001421c0, 0x7ff618a4e568)
	C:/hostedtoolcache/windows/go/1.25.2/x64/src/testing/testing.go:1934 +0xc3
created by testing.(*T).Run in goroutine 1
	C:/hostedtoolcache/windows/go/1.25.2/x64/src/testing/testing.go:1997 +0x44b

goroutine 14 [chan receive]:
github.com/opentofu/opentofu/internal/flock.LockBlocking({0x7ff618a7eed8, 0x7ff618bd79e0}, 0xc00004c148)
	D:/a/opentofu/opentofu/internal/flock/filesystem_lock_windows.go:91 +0xba
github.com/opentofu/opentofu/internal/flock.TestLockBlocking_EventualSuccess.func1()
	D:/a/opentofu/opentofu/internal/flock/filesystem_lock_test.go:268 +0x65
created by github.com/opentofu/opentofu/internal/flock.TestLockBlocking_EventualSuccess in goroutine 13
	D:/a/opentofu/opentofu/internal/flock/filesystem_lock_test.go:265 +0x2ab

goroutine 15 [sleep]:
time.Sleep(0x5f5e100)
	C:/hostedtoolcache/windows/go/1.25.2/x64/src/runtime/time.go:363 +0x167
github.com/opentofu/opentofu/internal/flock.LockBlocking.func1()
	D:/a/opentofu/opentofu/internal/flock/filesystem_lock_windows.go:82 +0xbe
created by github.com/opentofu/opentofu/internal/flock.LockBlocking in goroutine 14
	D:/a/opentofu/opentofu/internal/flock/filesystem_lock_windows.go:62 +0xa5
```

Code after:
	// Clean up first lock
	_ = Unlock(f1)

	// Should fail due to timeout or cancellation
	if err == nil {
		t.Fatal("Expected blocking lock to be cancelled, but it succeeded")
	}

	// Check cancellation behavior
	if err == context.DeadlineExceeded || err == context.Canceled {
		t.Logf("Lock was properly cancelled: %v (took %v)", err, elapsed)
		
		// Should have been cancelled within reasonable time
		if elapsed > 200*time.Millisecond {
			t.Fatalf("Cancellation took too long: %v", elapsed)
		}
	} else {
		t.Fatalf("Expected timeout/cancellation error, got: %v", err)
	}
}

func TestLockBlocking_EventualSuccess(t *testing.T) {
	// Tests eventual success of LockBlocking on a single file while a Lock is 
	// already in place which is then released
	// Doesn't really test anything in POSIX systems since the same process can hold multiple locks
	// on the same file

	tmpDir := t.TempDir()
	lockFile := filepath.Join(tmpDir, "eventual.lock")

	// Create test file
	f1, err := os.OpenFile(lockFile, os.O_CREATE|os.O_RDWR, 0644)
	if err != nil {
		t.Fatalf("Failed to create test file: %v", err)
	}
	defer f1.Close()

	f2, err := os.OpenFile(lockFile, os.O_RDWR, 0644)
	if err != nil {
		t.Fatalf("Failed to open test file with a second handler: %v", err)
	}
	defer f2.Close()

	// Acquire lock with first handle
	err = Lock(f1)
	if err != nil {
		t.Fatalf("Failed to acquire first lock: %v", err)
	}

	var wg sync.WaitGroup
	var lockErr error