| index f909ba937..08c06537e 100644 |
| --- a/internal/sim/engines/sim_test.go |
| +++ b/internal/sim/engines/sim_test.go |
| @@ -1,7 +1,9 @@ |
| package engines |
| |
| import ( |
| + "fmt" |
| "os" |
| + "os/exec" |
| "reflect" |
| "sort" |
| "strconv" |
| @@ -98,14 +100,51 @@ func TestSeedDeterministic(t *testing.T) { |
| } |
| } |
| |
| -// TestRulesListed prints the rules a run would check, one per line, and runs |
| -// nothing. Without ENGINESIM_LIST it stands aside. |
| -func TestRulesListed(t *testing.T) { |
| - if os.Getenv("ENGINESIM_LIST") != "1" { |
| - t.Skip("ENGINESIM_LIST=1 prints the rules") |
| +// TestMain handles ENGINESIM_LIST=1 before any test runs: it prints the rules |
| +// a run would check, one per line, to stdout and exits 0 without running a |
| +// test (the mutation harness reads this list). |
| +func TestMain(m *testing.M) { |
| + if os.Getenv("ENGINESIM_LIST") == "1" { |
| + for _, r := range allRules { |
| + fmt.Println(r) |
| + } |
| + os.Exit(0) |
| } |
| + os.Exit(m.Run()) |
| +} |
| + |
| +// TestListPrintsRulesAndRunsNothing runs this test binary as a subprocess |
| +// with ENGINESIM_LIST=1 and requires that its output is exactly the rule |
| +// list and that no test ran. A control run without the variable shows the |
| +// "=== RUN" marker is what a test run prints. |
| +func TestListPrintsRulesAndRunsNothing(t *testing.T) { |
| + run := func(list bool) string { |
| + cmd := exec.Command(os.Args[0], "-test.v", "-test.run=TestOnMutation") |
| + cmd.Env = os.Environ() |
| + for i := 0; i < len(cmd.Env); i++ { |
| + if strings.HasPrefix(cmd.Env[i], "ENGINESIM_LIST=") { |
| + cmd.Env = append(cmd.Env[:i], cmd.Env[i+1:]...) |
| + i-- |
| + } |
| + } |
| + if list { |
| + cmd.Env = append(cmd.Env, "ENGINESIM_LIST=1") |
| + } |
| + out, err := cmd.Output() |
| + if err != nil { |
| + t.Fatalf("list=%v: %v", list, err) |
| + } |
| + return string(out) |
| + } |
| + if control := run(false); !strings.Contains(control, "=== RUN") { |
| + t.Fatalf("control run printed no test markers: %q", control) |
| + } |
| + want := "" |
| for _, r := range allRules { |
| - t.Log(r) |
| + want += r + "\n" |
| + } |
| + if got := run(true); got != want { |
| + t.Fatalf("ENGINESIM_LIST=1 output = %q, want exactly the rule list %q", got, want) |
| } |
| } |
| |
| @@ -175,16 +214,22 @@ func TestStepLoop(t *testing.T) { |
| |
| calls := map[string]int{} |
| var chaosCalls, calmCalls int |
| + phase := func(w *world) { |
| + if w.calm() { |
| + calmCalls++ |
| + } else { |
| + chaosCalls++ |
| + } |
| + } |
| actions = []action{ |
| {name: "alpha", weight: 3, run: func(w *world) { |
| calls["alpha"]++ |
| - if w.calm() { |
| - calmCalls++ |
| - } else { |
| - chaosCalls++ |
| - } |
| + phase(w) |
| + }}, |
| + {name: "beta", weight: 1, run: func(w *world) { |
| + calls["beta"]++ |
| + phase(w) |
| }}, |
| - {name: "beta", weight: 1, run: func(w *world) { calls["beta"]++ }}, |
| {name: "never", weight: 0, run: func(w *world) { t.Error("a zero-weight action ran") }}, |
| } |
| w = runSeed(3, false) |
| @@ -197,8 +242,8 @@ func TestStepLoop(t *testing.T) { |
| if calls["alpha"] < 2*calls["beta"] { |
| t.Fatalf("weights ignored: %v", calls) |
| } |
| - if chaosCalls == 0 || calmCalls == 0 { |
| - t.Fatalf("the walk must reach both phases: chaos %d calm %d", chaosCalls, calmCalls) |
| + if chaosCalls != chaosSteps || calmCalls != calmSteps || chaosSteps != 400 || calmSteps != 200 { |
| + t.Fatalf("phase split: chaos %d calm %d, want 400 and 200", chaosCalls, calmCalls) |
| } |
| if _, ok := w.stats["never"]; ok { |
| t.Fatal("a zero-weight action was counted") |