blob: 1bfb6c0f186b9762233e94b73214767e776b700e [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.
46func TestHarness(t *testing.T, r func(ctx context.Context) error) (context.CancelFunc, *logtree.LogTree) {
47 t.Helper()
48
49 ctx, ctxC := context.WithCancel(context.Background())
Serge Bazanskif8a8e652021-07-06 16:23:43 +020050
51 lt := logtree.New()
52 logtree.PipeAllToStderr(t, lt)
53
Serge Bazanskiec19b602022-03-09 20:41:31 +010054 sup := New(ctx, func(ctx context.Context) error {
Serge Bazanski3379a5d2021-09-09 12:56:40 +020055 Logger(ctx).Infof("Starting test %s...", t.Name())
Serge Bazanskif8a8e652021-07-06 16:23:43 +020056 if err := r(ctx); err != nil && !errors.Is(err, ctx.Err()) {
57 t.Errorf("Supervised runnable in harness returned error: %v", err)
Serge Bazanski826a9e92021-10-05 21:23:48 +020058 return err
Serge Bazanskif8a8e652021-07-06 16:23:43 +020059 }
60 return nil
61 }, WithExistingLogtree(lt))
Serge Bazanskiec19b602022-03-09 20:41:31 +010062
63 t.Cleanup(func() {
64 log.Printf("supervisor.TestHarness: Canceling context...")
65 ctxC()
66 log.Printf("supervisor.TestHarness: Waiting for supervisor runnables to die...")
67 timeoutNag := time.Now().Add(5 * time.Second)
68
69 for {
70 live := sup.liveRunnables()
71 if len(live) == 0 {
72 log.Printf("supervisor.TestHarness: All done.")
73 return
74 }
75
76 if time.Now().After(timeoutNag) {
77 timeoutNag = time.Now().Add(5 * time.Second)
78 sort.Strings(live)
79 log.Printf("supervisor.TestHarness: Still live:")
80 for _, l := range live {
81 log.Printf("supervisor.TestHarness: - %s", l)
82 }
83 }
84
85 time.Sleep(time.Second)
86 }
87 })
Serge Bazanskif8a8e652021-07-06 16:23:43 +020088 return ctxC, lt
89}