94 lines
4.4 KiB
Go
94 lines
4.4 KiB
Go
package runs
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"textmachine/platform/internal/pgstore"
|
|
)
|
|
|
|
// ⛔ AN OPERATOR'S TERMINAL VERDICT IS RECORDED AS ONE, and until this pack nothing recorded it.
|
|
//
|
|
// `run abandon` writes the run `failed` with `service_error`. On the WIRE that is right and stays:
|
|
// the client's question there is «is a retry worth offering», and it is not — whatever wedged the run
|
|
// is still whatever it was. But nothing anywhere said that a PERSON had decided, so the run wore a
|
|
// fault it never had, which is the class of unified backlog row 394 one level up: our own policy read
|
|
// as a broken deployment. The column that exists for «what this platform concluded» now carries it.
|
|
//
|
|
// ⚠ The wire word is asserted TOO, and deliberately: a fix that had moved it would have traded one
|
|
// wrong answer for another, since `abandoned` says nothing about whether a retry helps.
|
|
func TestAnOperatorsVerdictIsRecordedAsHisAndNotAsAFault(t *testing.T) {
|
|
f := newFixture(t, "10", 500)
|
|
run, err := f.svc.Start(f.ctx, StartRequest{UserID: "u1", BookID: f.bookID(t), Chapters: order(10)})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := f.svc.Spawn(f.ctx, run.ID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// The floor the command waits for: seeing a run go wrong and being allowed to end it are
|
|
// different permissions, so the row has to be stalled before a verdict is accepted at all.
|
|
if _, err := f.store.Pool().Exec(f.ctx,
|
|
`update run_attempts set reconcile_failures = $2 where run_id = $1`, run.ID, StalledAfter); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := f.store.AbandonRun(f.ctx, proven(t, f, run.ID, "systemd says the unit is gone")); err != nil {
|
|
t.Fatalf("a proven stalled run could not be given up on: %v", err)
|
|
}
|
|
var result, reason, status string
|
|
if err := f.store.Pool().QueryRow(f.ctx, `
|
|
select coalesce(a.exit_result, ''), coalesce(r.failure_reason, ''), r.status
|
|
from runs r join run_attempts a on a.run_id = r.id
|
|
where r.id = $1 order by a.attempt_no desc limit 1`, run.ID).
|
|
Scan(&result, &reason, &status); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// ⛔ THE LITERAL, NOT THE CONSTANT, and that is the PD-1 class this zone names: a test that reads
|
|
// the same constant the code writes passes at ANY value, the empty string included. Proved rather
|
|
// than argued — this pack's mutation campaign emptied the constant and the first edition of this
|
|
// assertion stayed green.
|
|
if result != "abandoned" {
|
|
t.Errorf("the abandoned attempt's exit_result is %q, want %q: whose decision ended this run is "+
|
|
"not recorded anywhere", result, "abandoned")
|
|
}
|
|
// The constant and the literal are asserted to be the same thing ONCE, here, so the value the code
|
|
// writes cannot drift from the value this test demands without one of the two lines going red.
|
|
if pgstore.AbandonedResult != "abandoned" {
|
|
t.Errorf("pgstore.AbandonedResult is %q: the word an operator's verdict is recorded under has "+
|
|
"moved, and every reader of the column now reads a different history", pgstore.AbandonedResult)
|
|
}
|
|
if status != "failed" || reason != "service_error" {
|
|
t.Errorf("the abandoned run reads %s/%s, want failed/service_error — the client's retry answer "+
|
|
"must not move with this", status, reason)
|
|
}
|
|
}
|
|
|
|
// ⚠ AND A MACHINE VERDICT ALREADY ON THE ROW IS NEVER OVERWRITTEN: an attempt whose unit had ended
|
|
// carries what the machine said, and an operator's word about the RUN must not erase it. The
|
|
// settlement branch of the same command is the one that reaches such a row.
|
|
func TestAnOperatorsWordDoesNotOverwriteWhatTheMachineSaid(t *testing.T) {
|
|
f := newFixture(t, "10", 500)
|
|
run, err := f.svc.Start(f.ctx, StartRequest{UserID: "u1", BookID: f.bookID(t), Chapters: order(10)})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := f.svc.Spawn(f.ctx, run.ID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// The machine's own word, as the ordinary ending writes it.
|
|
if _, err := f.store.Pool().Exec(f.ctx,
|
|
`update run_attempts set exit_result = 'oom-kill', reconcile_failures = $2 where run_id = $1`,
|
|
run.ID, StalledAfter); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := f.store.AbandonRun(f.ctx, proven(t, f, run.ID, "the host took it for its memory")); err != nil {
|
|
t.Fatalf("abandon: %v", err)
|
|
}
|
|
var result string
|
|
if err := f.store.Pool().QueryRow(f.ctx,
|
|
`select coalesce(exit_result, '') from run_attempts where run_id = $1`, run.ID).Scan(&result); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result != "oom-kill" {
|
|
t.Errorf("exit_result is %q: an operator's verdict overwrote what the machine said about the unit", result)
|
|
}
|
|
}
|