Files

91 lines
2.2 KiB
Go

package operationlock_test
import (
"context"
"os"
"path/filepath"
"testing"
"time"
"proxyctl/internal/operationlock"
)
type recordingLocker struct {
name string
events *[]string
err error
}
func (l recordingLocker) Lock(context.Context) (func(), error) {
*l.events = append(*l.events, "lock "+l.name)
if l.err != nil {
return nil, l.err
}
return func() { *l.events = append(*l.events, "unlock "+l.name) }, nil
}
func TestGroupAcquiresInOrderAndReleasesInReverse(t *testing.T) {
var events []string
group := operationlock.NewGroup(
recordingLocker{name: "system", events: &events},
recordingLocker{name: "user", events: &events},
)
unlock, err := group.Lock(context.Background())
if err != nil {
t.Fatal(err)
}
unlock()
want := []string{"lock system", "lock user", "unlock user", "unlock system"}
if len(events) != len(want) {
t.Fatalf("events = %v, want %v", events, want)
}
for i := range want {
if events[i] != want[i] {
t.Fatalf("events = %v, want %v", events, want)
}
}
}
func TestLockRejectsSymlinkedStateAncestor(t *testing.T) {
root := t.TempDir()
victim := filepath.Join(root, "victim")
if err := os.Mkdir(victim, 0o700); err != nil {
t.Fatal(err)
}
if err := os.Symlink(victim, filepath.Join(root, "state")); err != nil {
t.Fatal(err)
}
lock := operationlock.New(filepath.Join(root, "state", "proxyctl", "operation.lock"))
if _, err := lock.Lock(context.Background()); err == nil {
t.Fatal("lock followed a symlinked state ancestor")
}
entries, err := os.ReadDir(victim)
if err != nil {
t.Fatal(err)
}
if len(entries) != 0 {
t.Fatalf("victim changed: %v", entries)
}
}
func TestLockSerializesIndependentInstancesAndHonorsContext(t *testing.T) {
path := t.TempDir() + "/proxyctl.lock"
first := operationlock.New(path)
second := operationlock.New(path)
unlock, err := first.Lock(context.Background())
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Millisecond)
defer cancel()
if _, err := second.Lock(ctx); err == nil {
t.Fatal("second lock acquired while first held")
}
unlock()
unlockSecond, err := second.Lock(context.Background())
if err != nil {
t.Fatal(err)
}
unlockSecond()
}