package stacks import ( "io" "log" "strings" "testing" ) // R-514 — a worker OOM-killed inside a RUNNING container must be seen. BIGNIGHT: paperless-webserver // `oomkilled=true restarts=0`, app „Fut", no event. // // RED-PROOF (run 2026-09-15, recorded in REPORT.md): with the `f[1] != "true"` filter inverted to // skip every "true" line, the scan returned nothing and this failed at "OOM-killed worker not seen". func TestScanOOMKilled_SeesKilledWorkerInRunningContainer(t *testing.T) { var gotArgs []string m := &Manager{ logger: log.New(io.Discard, "", 0), stacks: map[string]*Stack{ "paperless-ngx": {Name: "paperless-ngx", Deployed: true, Containers: []ContainerInfo{ {Name: "paperless-webserver", State: StateRunning}, {Name: "paperless-redis", State: StateRunning}, }}, "bookstack": {Name: "bookstack", Deployed: true, Containers: []ContainerInfo{{Name: "bookstack", State: StateRunning}}}, "stopped": {Name: "stopped", Deployed: true, Containers: []ContainerInfo{{Name: "stopped-c", State: StateExited}}}, "undeployed": {Name: "undeployed", Deployed: false, Containers: []ContainerInfo{{Name: "u", State: StateRunning}}}, }, } m.execFn = func(name string, args ...string) (string, error) { gotArgs = args return "/bookstack|false|2026-09-14T18:00:00Z\n/paperless-redis|false|2026-09-14T18:00:00Z\n/paperless-webserver|true|2026-09-14T18:00:01Z\n", nil } ooms, err := m.ScanOOMKilled() if err != nil { t.Fatal(err) } if len(ooms) != 1 || ooms[0].Stack != "paperless-ngx" || ooms[0].Container != "paperless-webserver" || ooms[0].StartedAt == "" { t.Fatalf("OOM-killed worker not seen: %+v", ooms) } joined := strings.Join(gotArgs, " ") if strings.Contains(joined, "stopped-c") || strings.Contains(joined, " u") { t.Fatalf("inspected a stopped or undeployed container: %v", gotArgs) } if got := m.OOMKilledStacks(); len(got["paperless-ngx"]) != 1 || len(got) != 1 { t.Fatalf("dashboard cache wrong: %v", got) } // Next scan clean → cache cleared (a restarted container resets OOMKilled). m.execFn = func(string, ...string) (string, error) { return "/bookstack|false|x\n/paperless-redis|false|x\n/paperless-webserver|false|y\n", nil } _, _ = m.ScanOOMKilled() if got := m.OOMKilledStacks(); len(got) != 0 { t.Fatalf("cache not cleared after a clean scan: %v", got) } } // R-636 — the kernel's kill counter, the limit and the peak are read from `cat memory.events // memory.max memory.peak` inside the container; an unreadable one is -1, never 0. func TestR636_ParseCgroupMemory(t *testing.T) { out := "low 0\nhigh 0\nmax 4521\noom 4530\noom_kill 4530\noom_group_kill 0\n1342177280\n1341128704\n" k, lim, peak := parseCgroupMemory(out) if k != 4530 || lim != "1280M" || peak != "1279M" { t.Fatalf("got kills=%d limit=%q peak=%q", k, lim, peak) } if k, lim, _ := parseCgroupMemory("oom_kill 3\nmax\n"); k != 3 || lim != "max" { t.Fatalf("no memory.peak (older kernel), unlimited: got %d %q", k, lim) } if k, _, _ := parseCgroupMemory("OCI runtime exec failed: exec: \"cat\": executable file not found"); k != -1 { t.Fatalf("an image without cat must read -1 (unknown), got %d", k) } } // R-636 — the scan asks the kill counter of the FLAGGED containers only (one exec each; none on a // healthy box). COMPANION RED-PROOF (REPORT.md): drop the exec loop — Kills stays -1 and this fails. func TestR636_ScanReadsTheCounterOfFlaggedContainersOnly(t *testing.T) { var execd []string m := &Manager{logger: log.New(io.Discard, "", 0), stacks: map[string]*Stack{ "romm": {Name: "romm", Deployed: true, Containers: []ContainerInfo{{Name: "romm", State: StateRunning}}}, "bookstack": {Name: "bookstack", Deployed: true, Containers: []ContainerInfo{{Name: "bookstack", State: StateRunning}}}, }} m.execFn = func(name string, args ...string) (string, error) { if args[0] == "exec" { execd = append(execd, args[1]) return "oom_kill 377\n1342177280\n1341128704\n", nil } return "/bookstack|false|t0\n/romm|true|2026-09-22T09:08:00Z\n", nil } ooms, err := m.ScanOOMKilled() if err != nil || len(ooms) != 1 { t.Fatalf("ooms=%+v err=%v", ooms, err) } if ooms[0].Kills != 377 || ooms[0].MemLimit != "1280M" || ooms[0].Peak != "1279M" { t.Errorf("the counter must be read for the flagged container: %+v", ooms[0]) } if len(execd) != 1 || execd[0] != "romm" { t.Errorf("exec must reach the flagged container only, got %v", execd) } }