313 lines
8.0 KiB
Go
313 lines
8.0 KiB
Go
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)
|
||
|
|
}
|
||
|
|
}
|