progresstest captures the events of a test, draws them as a tree that
reads the same however the concurrent work was scheduled, and checks that
they keep the promises of progress to every sink.
_ = func(t *testing.T) {
ctx, w := progresstest.Watch(t.Context(), t)
_ = reset(ctx, []string{"exe1", "exe2", "exe3"})
want := `step reset the machines total=3 [fold]: failed (target): exe3: no answer
target exe3: failed (target): {}: no answer
target exe[1-2]: ok
`
if got := w.Finish(); got != want {
t.Errorf("the work reported\n%s\nwant\n%s", got, want)
}
}progress/progresstest/example_test.go. CI compiles it.The main names#
| Name | What it does |
|---|---|
progresstest.Watch, progresstest.Watcher | a context with a Bus of its own; Finish checks and returns the tree |
progresstest.Capture | a sink that keeps the events; Tree draws them |
progresstest.Check | the rules, on events that you have |
progresstest.Screen | a terminal in memory, for a test of what a display draws |
What Check holds an emitter to#
Three rules shape how parallel work is reported. A pool written by hand breaks them most often:
- Every target of a folded step or a batch is announced queued before the
first of them runs. A pool that starts each target in its own goroutine,
as
errgroup.Godoes, breaks this. - When a folded step or a batch ends, the targets that ended under it are
its
Total. A count never passes itsTotal, and aTotalnever shrinks. - No more targets run at once below a span than its
Limit.
The work of a span has rules too: two advances of a span come at least 100 ms apart, an amount is never negative, and the unit of a span never changes.
The rules are an interface (decision 15). A new or stricter rule is a breaking change.
screen := &progresstest.Screen{Styles: true}
fmt.Fprint(screen, "\x1b[1;31m✗\x1b[0m exe0007\x1b[38;5;244m · \x1b[0mtransport\n")
fmt.Print(screen.String())
fmt.Print(screen.Styled())✗ exe0007 · transport
«1;31»✗«» exe0007«38;5;244» · «»transportprogress/progresstest/example_test.go. CI runs it and checks its output.