2023-03-28 22:48:58 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
2020-12-04 23:56:03 +00:00
|
|
|
package mutex
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestMutex(t *testing.T) {
|
|
|
|
t.Run("starts unlocked", func(t *testing.T) {
|
|
|
|
m := New()
|
|
|
|
canLock(t, m)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Lock blocks when locked", func(t *testing.T) {
|
|
|
|
m := New()
|
|
|
|
m.Lock()
|
|
|
|
lockIsBlocked(t, m)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Unlock unblocks Lock", func(t *testing.T) {
|
|
|
|
m := New()
|
|
|
|
m.Lock()
|
|
|
|
m.Unlock() // nolint:staticcheck // SA2001 is not relevant here
|
|
|
|
canLock(t, m)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("TryLock acquires lock", func(t *testing.T) {
|
|
|
|
m := New()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
|
|
t.Cleanup(cancel)
|
|
|
|
require.NoError(t, m.TryLock(ctx))
|
|
|
|
lockIsBlocked(t, m)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("TryLock blocks until timeout when locked", func(t *testing.T) {
|
|
|
|
m := New()
|
|
|
|
m.Lock()
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
|
|
|
|
t.Cleanup(cancel)
|
|
|
|
err := m.TryLock(ctx)
|
|
|
|
require.Equal(t, err, context.DeadlineExceeded)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("TryLock acquires lock before timeout", func(t *testing.T) {
|
|
|
|
m := New()
|
|
|
|
m.Lock()
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
time.Sleep(20 * time.Millisecond)
|
|
|
|
m.Unlock()
|
|
|
|
}()
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
|
|
t.Cleanup(cancel)
|
|
|
|
err := m.TryLock(ctx)
|
|
|
|
require.NoError(t, err)
|
|
|
|
})
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func canLock(t *testing.T, m *Mutex) {
|
|
|
|
t.Helper()
|
|
|
|
chDone := make(chan struct{})
|
|
|
|
go func() {
|
|
|
|
m.Lock()
|
|
|
|
close(chDone)
|
|
|
|
}()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-chDone:
|
|
|
|
case <-time.After(20 * time.Millisecond):
|
|
|
|
t.Fatal("failed to acquire lock before timeout")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func lockIsBlocked(t *testing.T, m *Mutex) {
|
|
|
|
t.Helper()
|
|
|
|
chDone := make(chan struct{})
|
|
|
|
go func() {
|
|
|
|
m.Lock()
|
|
|
|
close(chDone)
|
|
|
|
}()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-chDone:
|
|
|
|
t.Fatal("expected Lock to block")
|
|
|
|
case <-time.After(20 * time.Millisecond):
|
|
|
|
}
|
|
|
|
}
|