package pi import ( "errors" "os" "os/exec" "path/filepath" "testing" ) func TestAdvisoryLockRejectsAConcurrentOwner(t *testing.T) { statePath := filepath.Join(t.TempDir(), "update-state.json") first, err := acquireLock(statePath) if err != nil { t.Fatal(err) } defer first.Release() second, err := acquireLock(statePath) if second != nil { second.Release() } if !errors.Is(err, ErrLockHeld) { t.Fatalf("second acquireLock() error = %v, want ErrLockHeld", err) } } func TestAdvisoryLockCrashReleasesAndReacquires(t *testing.T) { statePath := filepath.Join(t.TempDir(), "update-state.json") if os.Getenv("THOTHCTL_LOCK_CRASH_HELPER") == "1" { lock, err := acquireLock(os.Getenv("THOTHCTL_LOCK_STATE")) if err != nil || lock == nil { os.Exit(23) } os.Exit(0) // Deliberately bypass Release: the OS must release ownership. } command := exec.Command(os.Args[0], "-test.run=^TestAdvisoryLockCrashReleasesAndReacquires$") command.Env = append(os.Environ(), "THOTHCTL_LOCK_CRASH_HELPER=1", "THOTHCTL_LOCK_STATE="+statePath) if output, err := command.CombinedOutput(); err != nil { t.Fatalf("crash helper failed: %v: %s", err, output) } lock, err := acquireLock(statePath) if err != nil { t.Fatalf("acquireLock() after owner crash = %v", err) } lock.Release() } func TestAdvisoryLockIgnoresPartialDiagnosticMetadata(t *testing.T) { statePath := filepath.Join(t.TempDir(), "update-state.json") if err := os.WriteFile(statePath+".lock", nil, 0o600); err != nil { t.Fatal(err) } if err := os.WriteFile(statePath+".lock.owner.json", []byte("{partial"), 0o600); err != nil { t.Fatal(err) } lock, err := acquireLock(statePath) if err != nil { t.Fatalf("acquireLock() with partial diagnostics = %v", err) } lock.Release() }