mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-05-19 16:20:02 +02:00
160 lines
3.1 KiB
Go
160 lines
3.1 KiB
Go
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)
|
|
}
|