blob: 017c9f93046b601dfba0461f6e952b3cb63030fa [file] [log] [blame]
Serge Bazanskiac6b6442020-05-06 19:13:43 +02001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17package supervisor
Serge Bazanskif8a8e652021-07-06 16:23:43 +020018
19import (
20 "context"
21 "errors"
Serge Bazanskiec19b602022-03-09 20:41:31 +010022 "log"
23 "sort"
Serge Bazanskif8a8e652021-07-06 16:23:43 +020024 "testing"
Serge Bazanskiec19b602022-03-09 20:41:31 +010025 "time"
Serge Bazanskif8a8e652021-07-06 16:23:43 +020026
27 "source.monogon.dev/metropolis/pkg/logtree"
28)
29
30// TestHarness runs a supervisor in a harness designed for unit testing
31// runnables and runnable trees.
32//
33// The given runnable will be run in a new supervisor, and the logs from this
34// supervisor will be streamed to stderr. If the runnable returns a non-context
35// error, the harness will throw a test error, but will not abort the test.
36//
37// The harness also returns a context cancel function that can be used to
Serge Bazanskiec19b602022-03-09 20:41:31 +010038// terminate the started supervisor early. Regardless of manual cancellation,
Serge Bazanskif8a8e652021-07-06 16:23:43 +020039// the supervisor will always be terminated up at the end of the test/benchmark
Serge Bazanskiec19b602022-03-09 20:41:31 +010040// it's running in. The supervision tree will also be cleaned up and the test
41// will block until all runnables have exited.
Serge Bazanskif8a8e652021-07-06 16:23:43 +020042//
43// The second returned value is the logtree used by this supervisor. It can be
44// used to assert some log messages are emitted in tests that exercise some
45// log-related functionality.
Serge Bazanskie25b3a42023-03-17 00:07:53 +010046func TestHarness(t testing.TB, r func(ctx context.Context) error) (context.CancelFunc, *logtree.LogTree) {
Serge Bazanskif8a8e652021-07-06 16:23:43 +020047 t.Helper()
48
49 ctx, ctxC := context.WithCancel(context.Background())
Serge Bazanskif8a8e652021-07-06 16:23:43 +020050
51 lt := logtree.New()
Serge Bazanskie25b3a42023-03-17 00:07:53 +010052
53 // Only log to stderr when we're running in a test, not in a fuzz harness or a
54 // benchmark - otherwise we just waste CPU cycles.
55 verbose := false
56 if _, ok := t.(*testing.T); ok {
57 verbose = true
58 }
59 if verbose {
60 logtree.PipeAllToStderr(t, lt)
61 }
Serge Bazanskif8a8e652021-07-06 16:23:43 +020062
Serge Bazanskiec19b602022-03-09 20:41:31 +010063 sup := New(ctx, func(ctx context.Context) error {
Serge Bazanski3379a5d2021-09-09 12:56:40 +020064 Logger(ctx).Infof("Starting test %s...", t.Name())
Serge Bazanskif8a8e652021-07-06 16:23:43 +020065 if err := r(ctx); err != nil && !errors.Is(err, ctx.Err()) {
66 t.Errorf("Supervised runnable in harness returned error: %v", err)
Serge Bazanski826a9e92021-10-05 21:23:48 +020067 return err
Serge Bazanskif8a8e652021-07-06 16:23:43 +020068 }
69 return nil
70 }, WithExistingLogtree(lt))
Serge Bazanskiec19b602022-03-09 20:41:31 +010071
72 t.Cleanup(func() {
Serge Bazanskiec19b602022-03-09 20:41:31 +010073 ctxC()
Serge Bazanskie25b3a42023-03-17 00:07:53 +010074 if verbose {
75 log.Printf("supervisor.TestHarness: Waiting for supervisor runnables to die...")
76 }
Serge Bazanskiec19b602022-03-09 20:41:31 +010077 timeoutNag := time.Now().Add(5 * time.Second)
78
79 for {
80 live := sup.liveRunnables()
81 if len(live) == 0 {
Serge Bazanskie25b3a42023-03-17 00:07:53 +010082 if verbose {
83 log.Printf("supervisor.TestHarness: All done.")
84 }
Serge Bazanskiec19b602022-03-09 20:41:31 +010085 return
86 }
87
88 if time.Now().After(timeoutNag) {
89 timeoutNag = time.Now().Add(5 * time.Second)
90 sort.Strings(live)
Serge Bazanskie25b3a42023-03-17 00:07:53 +010091 if verbose {
92 log.Printf("supervisor.TestHarness: Still live:")
93 for _, l := range live {
94 log.Printf("supervisor.TestHarness: - %s", l)
95 }
Serge Bazanskiec19b602022-03-09 20:41:31 +010096 }
97 }
98
99 time.Sleep(time.Second)
100 }
101 })
Serge Bazanskif8a8e652021-07-06 16:23:43 +0200102 return ctxC, lt
103}