package pvegate import ( "context" "testing" "time" ) // A write that starts while a step runs waits until the step ends. // // COMPANION RED-PROOF (observed): make Write ignore `stepping` → this fails with "the write went through while the // Proxmox step held the gate". Restored. (audits/day-2026-10-07/B/red-pvegate.txt) func TestWrite_WaitsWhileAStepRuns(t *testing.T) { end, err := Step(context.Background()) if err != nil { t.Fatal(err) } got := make(chan time.Time, 1) go func() { rel, _, err := Write(context.Background()) if err == nil { rel() } got <- time.Now() }() select { case <-got: end() t.Fatal("the write went through while the Proxmox step held the gate") case <-time.After(150 * time.Millisecond): } ended := time.Now() end() select { case at := <-got: if at.Before(ended) { t.Fatal("the write finished before the step ended") } case <-time.After(2 * time.Second): t.Fatal("the write never went through after the step ended") } } // A step waits for a write already in flight before it starts. func TestStep_WaitsForAWriteInFlight(t *testing.T) { rel, _, err := Write(context.Background()) if err != nil { t.Fatal(err) } started := make(chan struct{}) go func() { end, err := Step(context.Background()) if err == nil { close(started) end() } }() select { case <-started: rel() t.Fatal("the step started while a write was in flight") case <-time.After(150 * time.Millisecond): } rel() select { case <-started: case <-time.After(2 * time.Second): t.Fatal("the step never started after the write finished") } } // A step that cannot drain the writes in time gives up and releases the gate (it never waits forever). func TestStep_GivesUpAndReleases(t *testing.T) { rel, _, _ := Write(context.Background()) ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() if _, err := Step(ctx); err == nil { t.Fatal("the step must give up while a write is in flight past its deadline") } if Stepping() { t.Fatal("a step that gave up must release the gate") } rel() } // A write held back past its own deadline returns the context's error. func TestWrite_HonoursItsContext(t *testing.T) { end, _ := Step(context.Background()) defer end() ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) defer cancel() if _, _, err := Write(ctx); err == nil { t.Fatal("a write held back past its deadline must fail") } } // One step at a time. func TestStep_OneAtATime(t *testing.T) { end, _ := Step(context.Background()) defer end() if _, err := Step(context.Background()); err != ErrStepRunning { t.Fatalf("a second step must be refused, got %v", err) } }