add unit test to ClosableMutex

This commit is contained in:
georgehao 2025-02-26 00:47:46 +08:00
parent f485e213b6
commit 8cd23e6cfc
No known key found for this signature in database
1 changed files with 124 additions and 0 deletions

View File

@ -0,0 +1,124 @@
package syncx
import (
"sync"
"sync/atomic"
"testing"
"time"
)
func TestClosableMutex(t *testing.T) {
t.Run("basic lock/unlock", func(t *testing.T) {
cm := NewClosableMutex()
if !cm.TryLock() {
t.Error("TryLock should succeed on new mutex")
}
cm.Unlock()
})
t.Run("multiple lock/unlock", func(t *testing.T) {
cm := NewClosableMutex()
for i := 0; i < 3; i++ {
if !cm.TryLock() {
t.Error("TryLock should succeed")
}
cm.Unlock()
}
})
t.Run("concurrent lock/unlock", func(t *testing.T) {
cm := NewClosableMutex()
var wg sync.WaitGroup
// lockCount is used to count how many goroutines have acquired the lock
var lockCount atomic.Int32
// Start a goroutine that holds the lock for a short time
wg.Add(2)
go func() {
defer wg.Done()
if !cm.TryLock() {
t.Error("First TryLock should succeed")
return
}
time.Sleep(3 * time.Second)
lockCount.Add(1)
cm.Unlock()
}()
go func() {
defer wg.Done()
// if main goroutine acquires the lock, it will increment lockCount
// so check the lockCount at the second to see if the main goroutine has acquired the lock
time.Sleep(2 * time.Second)
if lockCount.Load() != 0 {
t.Error("Second TryLock should not succeed while the first goroutine holds the lock")
}
}()
// Try to acquire the lock while it's held
time.Sleep(time.Second) // Wait for the first goroutine to acquire the lock
// NOTE: will block here until the first goroutine releases the lock
if !cm.TryLock() {
t.Error("Second TryLock should block here unitl the first goroutine releases the lock, but it didn't")
cm.Unlock()
}
lockCount.Add(1)
// the main goroutine will increment lockCount once the first gourotine releases the lock,
// so lockCount should be 2
if lockCount.Load() != 2 {
t.Error("main gourotine should have acquired the lock")
}
wg.Wait()
})
t.Run("must lock", func(t *testing.T) {
cm := NewClosableMutex()
cm.MustLock()
cm.Unlock()
})
t.Run("close mutex", func(t *testing.T) {
cm := NewClosableMutex()
cm.Close()
if cm.TryLock() {
t.Error("TryLock should fail on closed mutex")
}
})
t.Run("panic on unlock of unlocked mutex", func(t *testing.T) {
cm := NewClosableMutex()
defer func() {
if r := recover(); r == nil {
t.Error("Expected panic on Unlock of unlocked mutex")
}
}()
cm.Unlock() // Should panic
})
t.Run("panic on must lock of closed mutex", func(t *testing.T) {
cm := NewClosableMutex()
cm.Close()
defer func() {
if r := recover(); r == nil {
t.Error("Expected panic on MustLock of closed mutex")
}
}()
cm.MustLock() // Should panic
})
t.Run("panic on close of closed mutex", func(t *testing.T) {
cm := NewClosableMutex()
cm.Close()
defer func() {
if r := recover(); r == nil {
t.Error("Expected panic on Close of closed mutex")
}
}()
cm.Close() // Should panic
})
}