blob: fd1f91b37672c7b627ff8bdb03436db7f61dd49c [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
Serge Bazanskiac6b6442020-05-06 19:13:43 +02002// SPDX-License-Identifier: Apache-2.0
Serge Bazanskiac6b6442020-05-06 19:13:43 +02003
4package supervisor
Serge Bazanskif8a8e652021-07-06 16:23:43 +02005
6import (
7 "context"
8 "errors"
Serge Bazanskiec19b602022-03-09 20:41:31 +01009 "log"
10 "sort"
Serge Bazanskif8a8e652021-07-06 16:23:43 +020011 "testing"
Serge Bazanskiec19b602022-03-09 20:41:31 +010012 "time"
Serge Bazanskif8a8e652021-07-06 16:23:43 +020013
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020014 "source.monogon.dev/osbase/logtree"
Serge Bazanskif8a8e652021-07-06 16:23:43 +020015)
16
17// TestHarness runs a supervisor in a harness designed for unit testing
18// runnables and runnable trees.
19//
20// The given runnable will be run in a new supervisor, and the logs from this
21// supervisor will be streamed to stderr. If the runnable returns a non-context
22// error, the harness will throw a test error, but will not abort the test.
23//
24// The harness also returns a context cancel function that can be used to
Serge Bazanskiec19b602022-03-09 20:41:31 +010025// terminate the started supervisor early. Regardless of manual cancellation,
Serge Bazanskif8a8e652021-07-06 16:23:43 +020026// the supervisor will always be terminated up at the end of the test/benchmark
Serge Bazanskiec19b602022-03-09 20:41:31 +010027// it's running in. The supervision tree will also be cleaned up and the test
28// will block until all runnables have exited.
Serge Bazanskif8a8e652021-07-06 16:23:43 +020029//
30// The second returned value is the logtree used by this supervisor. It can be
31// used to assert some log messages are emitted in tests that exercise some
32// log-related functionality.
Serge Bazanskie25b3a42023-03-17 00:07:53 +010033func TestHarness(t testing.TB, r func(ctx context.Context) error) (context.CancelFunc, *logtree.LogTree) {
Serge Bazanskif8a8e652021-07-06 16:23:43 +020034 t.Helper()
35
36 ctx, ctxC := context.WithCancel(context.Background())
Serge Bazanskif8a8e652021-07-06 16:23:43 +020037
38 lt := logtree.New()
Serge Bazanskie25b3a42023-03-17 00:07:53 +010039
40 // Only log to stderr when we're running in a test, not in a fuzz harness or a
41 // benchmark - otherwise we just waste CPU cycles.
42 verbose := false
43 if _, ok := t.(*testing.T); ok {
44 verbose = true
45 }
46 if verbose {
Serge Bazanski29974f32023-04-05 12:29:09 +020047 logtree.PipeAllToTest(t, lt)
Serge Bazanskie25b3a42023-03-17 00:07:53 +010048 }
Serge Bazanskif8a8e652021-07-06 16:23:43 +020049
Serge Bazanskiec19b602022-03-09 20:41:31 +010050 sup := New(ctx, func(ctx context.Context) error {
Serge Bazanski3379a5d2021-09-09 12:56:40 +020051 Logger(ctx).Infof("Starting test %s...", t.Name())
Serge Bazanskif8a8e652021-07-06 16:23:43 +020052 if err := r(ctx); err != nil && !errors.Is(err, ctx.Err()) {
53 t.Errorf("Supervised runnable in harness returned error: %v", err)
Serge Bazanski826a9e92021-10-05 21:23:48 +020054 return err
Serge Bazanskif8a8e652021-07-06 16:23:43 +020055 }
56 return nil
Jan Schära8cfb562024-04-08 14:54:15 +020057 }, WithExistingLogtree(lt), WithPropagatePanic)
Serge Bazanskiec19b602022-03-09 20:41:31 +010058
59 t.Cleanup(func() {
Serge Bazanskiec19b602022-03-09 20:41:31 +010060 ctxC()
Serge Bazanskie25b3a42023-03-17 00:07:53 +010061 if verbose {
62 log.Printf("supervisor.TestHarness: Waiting for supervisor runnables to die...")
63 }
Serge Bazanskiec19b602022-03-09 20:41:31 +010064 timeoutNag := time.Now().Add(5 * time.Second)
65
66 for {
67 live := sup.liveRunnables()
68 if len(live) == 0 {
Serge Bazanskie25b3a42023-03-17 00:07:53 +010069 if verbose {
70 log.Printf("supervisor.TestHarness: All done.")
71 }
Serge Bazanskiec19b602022-03-09 20:41:31 +010072 return
73 }
74
75 if time.Now().After(timeoutNag) {
76 timeoutNag = time.Now().Add(5 * time.Second)
77 sort.Strings(live)
Serge Bazanskie25b3a42023-03-17 00:07:53 +010078 if verbose {
79 log.Printf("supervisor.TestHarness: Still live:")
80 for _, l := range live {
81 log.Printf("supervisor.TestHarness: - %s", l)
82 }
Serge Bazanskiec19b602022-03-09 20:41:31 +010083 }
84 }
85
Jan Schär75ea9f42024-07-29 17:01:41 +020086 time.Sleep(10 * time.Millisecond)
Serge Bazanskiec19b602022-03-09 20:41:31 +010087 }
88 })
Serge Bazanskif8a8e652021-07-06 16:23:43 +020089 return ctxC, lt
90}