package locking import ( "testing" "time" "github.com/stretchr/testify/assert" ) func TestLocalLockBackend(t *testing.T) { backend := NewLocalLockBackend() token1, err := backend.Acquire("test-lock", 30*time.Second) assert.NoError(t, err) assert.NotEmpty(t, token1) // Try to acquire again (should fail) _, err = backend.Acquire("test-lock", 30*time.Second) assert.Error(t, err) // Release err = backend.Release("test-lock", token1) assert.NoError(t, err) // Acquire again (should succeed) _, err = backend.Acquire("test-lock", 30*time.Second) assert.NoError(t, err) } func TestLocalLockReleaseWrongToken(t *testing.T) { backend := NewLocalLockBackend() _, _ = backend.Acquire("test-lock", 30*time.Second) err := backend.Release("test-lock", "wrong-token") assert.Error(t, err) // Lock should still be held locked, _ := backend.IsLocked("test-lock") assert.True(t, locked) } func TestLocalLockIsLocked(t *testing.T) { backend := NewLocalLockBackend() locked, _ := backend.IsLocked("test-lock") assert.False(t, locked) backend.Acquire("test-lock", 30*time.Second) locked, _ = backend.IsLocked("test-lock") assert.True(t, locked) } func TestDistributedLockAcquire(t *testing.T) { backend := NewLocalLockBackend() lock := NewDistributedLock("test-lock", backend, 30*time.Second) err := lock.Acquire(5 * time.Second) assert.NoError(t, err) assert.True(t, lock.IsAcquired()) } func TestDistributedLockRelease(t *testing.T) { backend := NewLocalLockBackend() lock := NewDistributedLock("test-lock", backend, 30*time.Second) lock.Acquire(5 * time.Second) err := lock.Release() assert.NoError(t, err) assert.False(t, lock.IsAcquired()) } func TestDistributedLockTimeout(t *testing.T) { backend := NewLocalLockBackend() lock1 := NewDistributedLock("test-lock", backend, 30*time.Second) lock2 := NewDistributedLock("test-lock", backend, 30*time.Second) lock1.Acquire(5 * time.Second) // Try to acquire with very short timeout start := time.Now() err := lock2.Acquire(100 * time.Millisecond) elapsed := time.Since(start) assert.Error(t, err) assert.Greater(t, elapsed, 50*time.Millisecond) } func TestDistributedLockGetAcquiredAt(t *testing.T) { backend := NewLocalLockBackend() lock := NewDistributedLock("test-lock", backend, 30*time.Second) lock.Acquire(5 * time.Second) acquiredAt := lock.GetAcquiredAt() assert.NotZero(t, acquiredAt) assert.True(t, acquiredAt.Before(time.Now())) } func TestDistributedLockGetHoldDuration(t *testing.T) { backend := NewLocalLockBackend() lock := NewDistributedLock("test-lock", backend, 30*time.Second) lock.Acquire(5 * time.Second) time.Sleep(100 * time.Millisecond) duration := lock.GetHoldDuration() assert.Greater(t, duration, 50*time.Millisecond) assert.Less(t, duration, 200*time.Millisecond) } func TestDistributedLockRenew(t *testing.T) { backend := NewLocalLockBackend() lock := NewDistributedLock("test-lock", backend, 30*time.Second) lock.Acquire(5 * time.Second) err := lock.Renew() assert.NoError(t, err) assert.True(t, lock.IsAcquired()) } func TestLockManagerAcquire(t *testing.T) { backend := NewLocalLockBackend() manager := NewLockManager(backend, 30*time.Second) err := manager.AcquireLock("lock-1", 5*time.Second) assert.NoError(t, err) stats := manager.GetLockStats() assert.Equal(t, 1, stats.TotalAcquisitions) assert.Equal(t, 1, stats.ActiveLocks) } func TestLockManagerRelease(t *testing.T) { backend := NewLocalLockBackend() manager := NewLockManager(backend, 30*time.Second) manager.AcquireLock("lock-1", 5*time.Second) err := manager.ReleaseLock("lock-1") assert.NoError(t, err) stats := manager.GetLockStats() assert.Equal(t, 1, stats.TotalReleases) assert.Equal(t, 0, stats.ActiveLocks) } func TestLockManagerMultipleLocks(t *testing.T) { backend := NewLocalLockBackend() manager := NewLockManager(backend, 30*time.Second) for i := 0; i < 5; i++ { key := "lock-" + string(rune(48+i)) err := manager.AcquireLock(key, 5*time.Second) assert.NoError(t, err) } stats := manager.GetLockStats() assert.Equal(t, 5, stats.ActiveLocks) activeLocks := manager.GetActiveLocks() assert.Equal(t, 5, len(activeLocks)) } func TestLockManagerReleaseAll(t *testing.T) { backend := NewLocalLockBackend() manager := NewLockManager(backend, 30*time.Second) for i := 0; i < 5; i++ { key := "lock-" + string(rune(48+i)) manager.AcquireLock(key, 5*time.Second) } assert.Equal(t, 5, manager.GetLockStats().ActiveLocks) manager.ReleaseAll() assert.Equal(t, 0, manager.GetLockStats().ActiveLocks) assert.Equal(t, 0, len(manager.GetActiveLocks())) } func TestLockManagerRenew(t *testing.T) { backend := NewLocalLockBackend() manager := NewLockManager(backend, 30*time.Second) manager.AcquireLock("lock-1", 5*time.Second) err := manager.RenewLock("lock-1") assert.NoError(t, err) } func TestLockManagerGetStats(t *testing.T) { backend := NewLocalLockBackend() manager := NewLockManager(backend, 30*time.Second) manager.AcquireLock("lock-1", 5*time.Second) manager.AcquireLock("lock-2", 5*time.Second) manager.ReleaseLock("lock-1") stats := manager.GetLockStats() assert.Equal(t, 2, stats.TotalAcquisitions) assert.Equal(t, 1, stats.TotalReleases) assert.Equal(t, 1, stats.ActiveLocks) } func TestLockManagerFailedAcquisition(t *testing.T) { backend := NewLocalLockBackend() manager := NewLockManager(backend, 30*time.Second) lock1 := NewDistributedLock("lock-1", backend, 30*time.Second) // Acquire from outside manager lock1.Acquire(5 * time.Second) // Try to acquire from manager err := manager.AcquireLock("lock-1", 100*time.Millisecond) assert.Error(t, err) stats := manager.GetLockStats() assert.Equal(t, 1, stats.FailedAcquisitions) } func TestDistributedLockDifferentKeys(t *testing.T) { backend := NewLocalLockBackend() lock1 := NewDistributedLock("lock-1", backend, 30*time.Second) lock2 := NewDistributedLock("lock-2", backend, 30*time.Second) lock1.Acquire(5 * time.Second) // lock2 should acquire without blocking err := lock2.Acquire(100 * time.Millisecond) assert.NoError(t, err) assert.True(t, lock1.IsAcquired()) assert.True(t, lock2.IsAcquired()) } func TestLockManagerDuplicateAcquisition(t *testing.T) { backend := NewLocalLockBackend() manager := NewLockManager(backend, 30*time.Second) manager.AcquireLock("lock-1", 5*time.Second) err := manager.AcquireLock("lock-1", 5*time.Second) assert.Error(t, err) } func TestLockManagerReleaseMissing(t *testing.T) { backend := NewLocalLockBackend() manager := NewLockManager(backend, 30*time.Second) err := manager.ReleaseLock("nonexistent") assert.Error(t, err) } func TestDistributedLockReleaseNotAcquired(t *testing.T) { backend := NewLocalLockBackend() lock := NewDistributedLock("test-lock", backend, 30*time.Second) err := lock.Release() assert.Error(t, err) } func TestConcurrentLockAcquisition(t *testing.T) { backend := NewLocalLockBackend() lock := NewDistributedLock("shared-lock", backend, 30*time.Second) acquired := false lock.Acquire(5 * time.Second) // Simulate another goroutine trying to acquire go func() { lock2 := NewDistributedLock("shared-lock", backend, 30*time.Second) err := lock2.Acquire(100 * time.Millisecond) if err == nil { acquired = true } }() time.Sleep(200 * time.Millisecond) assert.False(t, acquired) } func TestDefaultLockTTL(t *testing.T) { backend := NewLocalLockBackend() lock := NewDistributedLock("test-lock", backend, 0) assert.Equal(t, 30*time.Second, lock.ttl) } func TestDefaultLockManagerTTL(t *testing.T) { backend := NewLocalLockBackend() manager := NewLockManager(backend, 0) assert.Equal(t, 30*time.Second, manager.lockTTL) } func BenchmarkLockAcquisition(b *testing.B) { backend := NewLocalLockBackend() for i := 0; i < b.N; i++ { lock := NewDistributedLock("test-lock", backend, 30*time.Second) lock.Acquire(5 * time.Second) lock.Release() } } func BenchmarkLockManagerAcquisition(b *testing.B) { backend := NewLocalLockBackend() manager := NewLockManager(backend, 30*time.Second) for i := 0; i < b.N; i++ { key := "lock-" + string(rune(48+i%100)) manager.AcquireLock(key, 5*time.Second) manager.ReleaseLock(key) } }