76 lines
3.2 KiB
Go
76 lines
3.2 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"textmachine/platform/internal/config"
|
|
)
|
|
|
|
// A relative exit-marker command passes os.Stat and is then refused by systemd when the FIRST run
|
|
// tries to start — measured on systemd 259: "neither a valid executable name nor an absolute path",
|
|
// which fails the whole unit. The platform then claims the attempt, fails to spawn, gives the claim
|
|
// back and repeats on every sweep, so the only symptom is that nothing ever runs (register row
|
|
// PD-165). Refused at boot instead, where an operator is watching, exactly as StateDir is.
|
|
//
|
|
// Mutation caught: dropping the filepath.IsAbs guard and relying on os.Stat.
|
|
func TestARelativeExitMarkerCommandIsRefusedAtBoot(t *testing.T) {
|
|
dir := t.TempDir()
|
|
bin := filepath.Join(dir, "tmplatformctl")
|
|
if err := os.WriteFile(bin, []byte("#!/bin/sh\n"), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// The same file, named relatively from a working directory where it really is present: os.Stat
|
|
// says yes and systemd says no, which is the whole of the defect.
|
|
t.Chdir(dir)
|
|
if _, err := markerArgv("./tmplatformctl"); err == nil {
|
|
t.Fatal("a relative exit-marker command was accepted; systemd refuses it and every run would fail to start")
|
|
} else if !strings.Contains(err.Error(), "absolute") {
|
|
t.Errorf("the refusal does not name the cause: %v", err)
|
|
}
|
|
argv, err := markerArgv(bin)
|
|
if err != nil {
|
|
t.Fatalf("an absolute command was refused: %v", err)
|
|
}
|
|
if len(argv) != 2 || argv[0] != bin || argv[1] != "exit-marker" {
|
|
t.Errorf("argv = %v", argv)
|
|
}
|
|
}
|
|
|
|
// The operator's knobs reach the code that reads them.
|
|
//
|
|
// Nothing witnessed this: `startRunner` needs a database, a systemd bus and a queue, so its config
|
|
// literal was observable only by running the whole daemon — and `RunBudget` was declared,
|
|
// documented and left at its zero value through a whole pack because of it.
|
|
//
|
|
// Mutation caught: dropping any assignment in runsConfig.
|
|
func TestTheOperatorsRunnerKnobsReachTheReconciler(t *testing.T) {
|
|
cfg := config.Config{}
|
|
cfg.Runner.StateDir = "/var/lib/tm"
|
|
cfg.Runner.EngineBinary = "/opt/tm/tmctl"
|
|
cfg.Runner.MemoryMax = "2G"
|
|
cfg.Runner.TasksMax = 64
|
|
cfg.Runner.AllowEngineVersionChange = true
|
|
cfg.Runner.ResyncEvery = 5 * time.Minute
|
|
cfg.Runner.RunBudget = 90 * time.Second
|
|
|
|
got := runsConfig(cfg, []string{"/usr/bin/tmplatformctl", "exit-marker"}, []string{"--max-cost"})
|
|
switch {
|
|
case got.RunBudget != cfg.Runner.RunBudget:
|
|
t.Errorf("RunBudget is %v, want the operator's %v: TM_PLATFORM_RUN_BUDGET does nothing",
|
|
got.RunBudget, cfg.Runner.RunBudget)
|
|
case got.ResyncEvery != cfg.Runner.ResyncEvery:
|
|
t.Errorf("ResyncEvery is %v, want %v", got.ResyncEvery, cfg.Runner.ResyncEvery)
|
|
case got.StateDir != cfg.Runner.StateDir || got.EngineBinary != cfg.Runner.EngineBinary:
|
|
t.Errorf("the paths did not arrive: %+v", got)
|
|
case got.MemoryMax != cfg.Runner.MemoryMax || got.TasksMax != cfg.Runner.TasksMax:
|
|
t.Errorf("the unit's limits did not arrive: %+v", got)
|
|
case !got.AllowEngineVersionChange:
|
|
t.Error("the engine-version switch did not arrive")
|
|
case len(got.MarkerArgv) != 2 || len(got.Ceiling) != 1:
|
|
t.Errorf("the argv the caller assembled did not arrive: %+v", got)
|
|
}
|
|
}
|