package rollout import ( "os" "testing" "time" ) // I9's deterministic witnesses (fix wave 1, item 3; Astra final review 1, // finding 3). Each scripts the model's events around a grant and then // delivers the grant through the real Manager and book. The code is not // told of the restriction the model adds after the grant's issue, which is // exactly what a mutant that moves no generation does (M14's renewal): the // grant's generation still matches, the code accepts it and clears the // marker, and I9 must report it. The control shows that a restriction that // landed before the issue, in the same step, is not inside the check. // newI9WitnessSim is a seed whose book is on disk (seed%4 == 3), with h's // hold-ran marker naming x (h's installed build) in the model and, saved, // in the book, so the box publishes a generation for it and a grant for // (x, h) is accepted. func newI9WitnessSim(t *testing.T) (*sim, *simHost, string) { t.Helper() if os.Geteuid() == 0 { t.Skip("as root a seed keeps its book in memory, and I9 is not checked") } s := newSim(3, t.TempDir(), t.TempDir(), map[string]bool{}) t.Cleanup(func() { s.cancel() close(s.dead) os.RemoveAll(s.dir) }) h := s.hosts[0] x := s.installed[h.ch] s.holdRanOn(h, x, s.now) if err := s.book.MarkHoldRan(h.name, HoldRan{Account: "bab", App: "blog", Build: x, Exec: "w-witness-0", At: s.now}); err != nil { t.Fatalf("MarkHoldRan: %v", err) } if s.published(x, h.name) == 0 { t.Fatal("no published generation for the marker") } witnessStep(s) return s, h, x } // witnessStep moves the model one step on. func witnessStep(s *sim) { s.step++ s.now = s.now.Add(time.Second) } // lastGrant is the grant issueGrant just queued. func lastGrant(t *testing.T, s *sim) simGrant { t.Helper() if len(s.grants) == 0 { t.Fatal("no grant was issued") } return s.grants[len(s.grants)-1] } // wantI9 runs fn, a delivery of a grant for h, and requires it to report an // I9 violation. Without one it says whether the code accepted the grant // (h's marker cleared), which is what makes a missed violation a miss. func wantI9(t *testing.T, s *sim, h *simHost, fn func()) { t.Helper() defer func() { r := recover() v, ok := r.(simViolation) if !ok || v.inv != "I9" { _, still := s.book.HoldRanOn(h.name) t.Fatalf("got %v, want an I9 violation (the code accepted the grant: %v)", r, !still) } t.Logf("caught: %s", v.msg) }() fn() } // Astra's counterexample: issue G0; another hold execution of x begins on // h; issue G1; that execution's run renews the marker (with M14's missing // generation move); deliver G0 first. G1's issue must not answer the // renewal: only an applied clear discharges a fact. func TestI9WitnessAGrantsIssueMasksNothing(t *testing.T) { s, h, x := newI9WitnessSim(t) s.issueGrant(h, x) g0 := lastGrant(t, s) witnessStep(s) start := s.now // another hold execution of x begins on h witnessStep(s) s.issueGrant(h, x) // G1: nothing has moved yet, so the same generation witnessStep(s) s.holdRanOn(h, x, start) // its run renews the marker; the code moves nothing witnessStep(s) wantI9(t, s, h, func() { s.deliver(g0) }) } // A late outcome: an execution of x on h begins before the grant is issued, // and its failure lands after the issue, with the grant not yet applied (so // not PreRetry). It is inside the check, though its execution began first. func TestI9WitnessALateOutcomeIsInsideTheCheck(t *testing.T) { s, h, x := newI9WitnessSim(t) start := s.now // the execution begins witnessStep(s) s.issueGrant(h, x) g := lastGrant(t, s) witnessStep(s) // The failure lands. The code is not told (its generation does not // move), so I8, which reads the disk for it, is not this witness's. dir := s.dir s.dir = "" s.failure(x, h.name, time.Time{}, false, start) s.dir = dir witnessStep(s) wantI9(t, s, h, func() { s.deliver(g) }) } // A change in the grant's own step, after its issue: the step's own action // runs a hold execution of x on h that renews the marker. The model's times // cannot order the two, its transition numbers can. func TestI9WitnessASameStepChangeIsInsideTheCheck(t *testing.T) { s, h, x := newI9WitnessSim(t) s.issueGrant(h, x) g := lastGrant(t, s) s.holdRanOn(h, x, s.now) // same step, after the issue witnessStep(s) wantI9(t, s, h, func() { s.deliver(g) }) } // The control: a renewal that landed before the issue, in the same step, // and that the code saw (its generation moved, and the grant names the new // one), is what the grant was issued for. The grant clears it, and I9 holds. func TestI9WitnessASameStepChangeBeforeTheIssueIsNotInsideTheCheck(t *testing.T) { s, h, x := newI9WitnessSim(t) s.holdRanOn(h, x, s.now) if err := s.book.MarkHoldRan(h.name, HoldRan{Account: "bab", App: "blog", Build: x, Exec: "w-witness-1", At: s.now}); err != nil { t.Fatalf("MarkHoldRan: %v", err) } s.issueGrant(h, x) g := lastGrant(t, s) witnessStep(s) s.deliver(g) if _, still := s.book.HoldRanOn(h.name); still { t.Fatal("the grant was not accepted: the control proves nothing") } }