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 cfg.Runner.KeysFile = "/etc/tm/keys.env" 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.KeysFile != cfg.Runner.KeysFile: // The exact regression class this test exists for: dropped here, the engine silently starves // for keys again (row 211) at the price of a whole paid run. t.Errorf("KeysFile is %q, want the operator's %q: TM_PLATFORM_ENGINE_KEYS_PATH does nothing", got.KeysFile, cfg.Runner.KeysFile) 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) } }