Files
mesh-delivery/modules/mesh-delivery-teller/cmd/mesh-delivery-teller/runner_test.go
T
jschoubben 8e6a31bc6a
mesh/delivery-group group feat/delivery-parts checking: 1 of 2 member(s) ready
mesh/repo-check head 8e6a31bc6ab4 merged into trunk 7f618177d677 beside mesh-catalog#374@75957fc3 — pass: THE CHANGE ALTERS ITS OWN CHECK (merge-check.sh):…
mesh/merge-gate head 8e6a31bc6ab4 merged into trunk 7f618177d677 — pass: group feat/delivery-parts composed: every machine composes with the change as it d…
Fix the teller's integration test taking its result twice: it hung the merge check (issue 1240, review round 1)
2026-10-19 17:39:44 +02:00

76 lines
2.4 KiB
Go

package main
import (
"context"
"errors"
"testing"
"time"
)
// runner runs the program's run function in a goroutine and stops it, bounded: a program that does not return when its
// context ends fails the test after a deadline with a message, and the result can be taken more than once (the
// integration test takes it in its body and again in its cleanup; a plain buffered channel of one value gave the second
// taker nothing, and the cleanup waited for ever: the merge check's 10-minute timeout).
type runner struct {
stop context.CancelFunc
done chan error
}
func startRunner(parent context.Context, run func(context.Context) error) *runner {
ctx, stop := context.WithCancel(parent)
r := &runner{stop: stop, done: make(chan error, 1)}
go func() {
r.done <- run(ctx)
close(r.done) // a second taker reads the closed channel at once
}()
return r
}
// finish stops the program and waits at most d for it to return. Called again, it returns at once.
func (r *runner) finish(t testing.TB, d time.Duration) error {
t.Helper()
r.stop()
select {
case err := <-r.done:
return err
case <-time.After(d):
t.Errorf("the program did not return within %s of its context ending", d)
return errors.New("did not return")
}
}
func TestRunnerCanBeFinishedTwice(t *testing.T) { // 1240 (review round 1: body and cleanup both take the result)
r := startRunner(context.Background(), func(ctx context.Context) error { <-ctx.Done(); return nil })
if err := r.finish(t, 2*time.Second); err != nil {
t.Fatal(err)
}
second := make(chan struct{})
go func() { r.finish(t, 2*time.Second); close(second) }()
select {
case <-second:
case <-time.After(5 * time.Second):
t.Fatal("the second finish waits for ever: the result was taken once")
}
}
func TestRunnerReportsAProgramThatDoesNotReturn(t *testing.T) { // 1240: a hang fails fast with a message
block := make(chan struct{})
t.Cleanup(func() { close(block) })
r := startRunner(context.Background(), func(context.Context) error { <-block; return nil })
probe := &recorder{TB: t}
start := time.Now()
if err := r.finish(probe, 100*time.Millisecond); err == nil || !probe.failed {
t.Fatalf("a program that never returns was not reported (err %v)", err)
}
if time.Since(start) > 5*time.Second {
t.Fatal("the report was not prompt")
}
}
type recorder struct {
testing.TB
failed bool
}
func (r *recorder) Errorf(string, ...any) { r.failed = true }