mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-15 04:53:00 +08:00
157 lines
4.3 KiB
Go
157 lines
4.3 KiB
Go
//go:build unix
|
|
|
|
package daemon
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/matthewlu070111/smart-srun/core/internal/domain"
|
|
)
|
|
|
|
func lockPath(t *testing.T) string {
|
|
t.Helper()
|
|
return filepath.Join(t.TempDir(), "daemon.lock")
|
|
}
|
|
|
|
func codeOf(t *testing.T, err error) domain.ErrorCode {
|
|
t.Helper()
|
|
code, ok := domain.CodeOf(err)
|
|
if !ok {
|
|
t.Fatalf("error carries no code: %v", err)
|
|
}
|
|
return code
|
|
}
|
|
|
|
// T25 -- a second copy of the daemon does not start.
|
|
//
|
|
// Two daemons on one router would each believe they owned the account state,
|
|
// each write the snapshot, and each authenticate; the gateway would see two
|
|
// logins for one account and knock one of them off.
|
|
func TestASecondDaemonCannotTakeTheLock(t *testing.T) {
|
|
path := lockPath(t)
|
|
|
|
first, err := Acquire(path)
|
|
if err != nil {
|
|
t.Fatalf("first acquire: %v", err)
|
|
}
|
|
defer first.Release()
|
|
|
|
_, err = Acquire(path)
|
|
if err == nil {
|
|
t.Fatal("a second daemon took the lock")
|
|
}
|
|
if code := codeOf(t, err); code != domain.CodeConflict {
|
|
t.Errorf("code = %s, want Conflict", code)
|
|
}
|
|
// The message names who to look at. A refusal that just said "already
|
|
// running" leaves the user with nothing to check.
|
|
if holder, ok := Holder(path); !ok || holder != os.Getpid() {
|
|
t.Errorf("holder = %d/%v, want this process %d", holder, ok, os.Getpid())
|
|
}
|
|
}
|
|
|
|
// The lock is released when it is released, so restarting works.
|
|
func TestReleasingTheLockLetsTheNextDaemonIn(t *testing.T) {
|
|
path := lockPath(t)
|
|
|
|
first, err := Acquire(path)
|
|
if err != nil {
|
|
t.Fatalf("first acquire: %v", err)
|
|
}
|
|
if err := first.Release(); err != nil {
|
|
t.Fatalf("release: %v", err)
|
|
}
|
|
|
|
second, err := Acquire(path)
|
|
if err != nil {
|
|
t.Fatalf("second acquire after release: %v", err)
|
|
}
|
|
defer second.Release()
|
|
|
|
// Releasing twice is not an error: a deferred Release after an explicit one
|
|
// is the ordinary shape of this code.
|
|
if err := first.Release(); err != nil {
|
|
t.Errorf("second release: %v", err)
|
|
}
|
|
}
|
|
|
|
// T25 -- a recycled pid does not look like a running daemon.
|
|
//
|
|
// This is why the lock is a flock and not a pidfile. A router that has just
|
|
// rebooted hands out low pids quickly, so the number a crashed daemon left
|
|
// behind will belong to something else within seconds. A pidfile has to guess
|
|
// whether that process is "really" the daemon; the kernel simply knows nobody
|
|
// holds this descriptor.
|
|
func TestAStalePIDDoesNotBlockTheLock(t *testing.T) {
|
|
path := lockPath(t)
|
|
|
|
// A file left by a daemon that died, naming a pid that is now somebody
|
|
// else's -- or nobody's.
|
|
if err := os.WriteFile(path, []byte("1\n"), RuntimeFileMode); err != nil {
|
|
t.Fatalf("write stale lock: %v", err)
|
|
}
|
|
|
|
lock, err := Acquire(path)
|
|
if err != nil {
|
|
t.Fatalf("a stale pid blocked the lock: %v", err)
|
|
}
|
|
defer lock.Release()
|
|
|
|
if holder, _ := Holder(path); holder != os.Getpid() {
|
|
t.Errorf("holder = %d, want the new owner %d", holder, os.Getpid())
|
|
}
|
|
}
|
|
|
|
// Garbage in the lock file is a missing diagnostic, not a reason to refuse to
|
|
// start: the file is a note about who to look at, and nothing decides from it.
|
|
func TestUnreadableLockContentsDoNotPreventStarting(t *testing.T) {
|
|
for name, contents := range map[string]string{
|
|
"empty": "",
|
|
"not a number": "hello\n",
|
|
"negative": "-7\n",
|
|
"zero": "0\n",
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
path := lockPath(t)
|
|
if err := os.WriteFile(path, []byte(contents), RuntimeFileMode); err != nil {
|
|
t.Fatalf("write: %v", err)
|
|
}
|
|
if _, ok := Holder(path); ok {
|
|
t.Error("garbage was reported as a holder")
|
|
}
|
|
lock, err := Acquire(path)
|
|
if err != nil {
|
|
t.Fatalf("acquire: %v", err)
|
|
}
|
|
lock.Release()
|
|
})
|
|
}
|
|
if _, ok := Holder(filepath.Join(t.TempDir(), "missing")); ok {
|
|
t.Error("a missing lock file reported a holder")
|
|
}
|
|
}
|
|
|
|
// The lock file is not world-readable: it lives in the runtime directory with
|
|
// everything else the daemon owns.
|
|
func TestTheLockFileIsPrivate(t *testing.T) {
|
|
path := lockPath(t)
|
|
lock, err := Acquire(path)
|
|
if err != nil {
|
|
t.Fatalf("acquire: %v", err)
|
|
}
|
|
defer lock.Release()
|
|
|
|
info, err := os.Stat(path)
|
|
if err != nil {
|
|
t.Fatalf("stat: %v", err)
|
|
}
|
|
if got := info.Mode().Perm(); got != RuntimeFileMode.Perm() {
|
|
t.Errorf("mode = %#o, want %#o", got, RuntimeFileMode.Perm())
|
|
}
|
|
if got := lock.Path(); got != path {
|
|
t.Errorf("Path() = %q, want %q", got, path)
|
|
}
|
|
}
|