Merge pull request #9612 from GustavoStingelin/tests/multimutex

multimutex: add unit tests
This commit is contained in:
Oliver Gugger 2025-03-24 10:14:58 -06:00 committed by GitHub
commit 6a9ec1065b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -0,0 +1,159 @@
package multimutex_test
import (
"sync"
"testing"
"time"
"github.com/lightningnetwork/lnd/multimutex"
"github.com/stretchr/testify/require"
)
// newTestMutex initializes a new mutex.
func newTestMutex[T comparable]() *multimutex.Mutex[T] {
return multimutex.NewMutex[T]()
}
// TestMultiMutexLockUnlock verifies basic locking/unlocking functionality.
func TestMultiMutexLockUnlock(t *testing.T) {
t.Parallel()
mtx := newTestMutex[string]()
// Lock and unlock the mutex with a single ID.
mtx.Lock("id1")
mtx.Unlock("id1")
// Lock and unlock with multiple IDs.
mtx.Lock("id1")
mtx.Lock("id2")
mtx.Unlock("id1")
mtx.Unlock("id2")
}
// TestMultiMutexConcurrency validates proper handling of concurrent access.
func TestMultiMutexConcurrency(t *testing.T) {
t.Parallel()
mtx := newTestMutex[string]()
var counter int32
var wg sync.WaitGroup
const numOps = 100
for i := 0; i < numOps; i++ {
wg.Add(1)
go func() {
defer wg.Done()
mtx.Lock("shared")
current := counter
// Simulate some work to check atomicity.
time.Sleep(time.Millisecond)
counter = current + 1
mtx.Unlock("shared")
}()
}
wg.Wait()
require.Equal(t, int32(numOps), counter)
}
// TestMultiMutexMultipleIDs validates independent locking per ID.
func TestMultiMutexMultipleIDs(t *testing.T) {
t.Parallel()
mtx := newTestMutex[string]()
counters := map[string]*int32{
"id1": new(int32),
"id2": new(int32),
}
var wg sync.WaitGroup
const numOps = 50
for i := 0; i < numOps; i++ {
wg.Add(2)
go func(id string) {
defer wg.Done()
mtx.Lock(id)
defer mtx.Unlock(id)
current := *counters[id]
// Simulate some work to check atomicity.
time.Sleep(time.Millisecond)
*counters[id] = current + 1
}("id1")
go func(id string) {
defer wg.Done()
mtx.Lock(id)
defer mtx.Unlock(id)
current := *counters[id]
// Simulate some work to check atomicity.
time.Sleep(time.Millisecond)
*counters[id] = current + 1
}("id2")
}
wg.Wait()
require.Equal(t, int32(numOps), *counters["id1"])
require.Equal(t, int32(numOps), *counters["id2"])
}
// TestMultiMutexReuse validates mutex reuse after unlocking.
func TestMultiMutexReuse(t *testing.T) {
t.Parallel()
mtx := newTestMutex[int]()
for i := 0; i < 5; i++ {
mtx.Lock(1)
mtx.Unlock(1)
}
for i := 1; i <= 5; i++ {
mtx.Lock(i)
}
for i := 1; i <= 5; i++ {
mtx.Unlock(i)
}
}
// TestMultiMutexPanic validates proper panic behavior on invalid unlocks.
func TestMultiMutexPanic(t *testing.T) {
t.Parallel()
t.Run("unlock never-locked ID", func(t *testing.T) {
mtx := newTestMutex[int]()
require.Panics(t, func() { mtx.Unlock(1) })
})
t.Run("double unlock", func(t *testing.T) {
mtx := newTestMutex[int]()
mtx.Lock(1)
mtx.Unlock(1)
require.Panics(t, func() { mtx.Unlock(1) })
})
}
// TestMultiMutexCustomType validates compatibility with custom types.
func TestMultiMutexCustomType(t *testing.T) {
t.Parallel()
type CustomID struct {
ID string
Type int
}
mtx := newTestMutex[CustomID]()
id1, id2 := CustomID{"test", 1}, CustomID{"test", 2}
mtx.Lock(id1)
mtx.Lock(id2)
mtx.Unlock(id1)
mtx.Unlock(id2)
}