treewide: introduce osbase package and move things around
All except localregistry moved from metropolis/pkg to osbase,
localregistry moved to metropolis/test as its only used there anyway.
Change-Id: If1a4bf377364bef0ac23169e1b90379c71b06d72
Reviewed-on: https://review.monogon.dev/c/monogon/+/3079
Tested-by: Jenkins CI
Reviewed-by: Serge Bazanski <serge@monogon.tech>
diff --git a/osbase/supervisor/supervisor_testhelpers.go b/osbase/supervisor/supervisor_testhelpers.go
new file mode 100644
index 0000000..ba015a2
--- /dev/null
+++ b/osbase/supervisor/supervisor_testhelpers.go
@@ -0,0 +1,103 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package supervisor
+
+import (
+ "context"
+ "errors"
+ "log"
+ "sort"
+ "testing"
+ "time"
+
+ "source.monogon.dev/osbase/logtree"
+)
+
+// TestHarness runs a supervisor in a harness designed for unit testing
+// runnables and runnable trees.
+//
+// The given runnable will be run in a new supervisor, and the logs from this
+// supervisor will be streamed to stderr. If the runnable returns a non-context
+// error, the harness will throw a test error, but will not abort the test.
+//
+// The harness also returns a context cancel function that can be used to
+// terminate the started supervisor early. Regardless of manual cancellation,
+// the supervisor will always be terminated up at the end of the test/benchmark
+// it's running in. The supervision tree will also be cleaned up and the test
+// will block until all runnables have exited.
+//
+// The second returned value is the logtree used by this supervisor. It can be
+// used to assert some log messages are emitted in tests that exercise some
+// log-related functionality.
+func TestHarness(t testing.TB, r func(ctx context.Context) error) (context.CancelFunc, *logtree.LogTree) {
+ t.Helper()
+
+ ctx, ctxC := context.WithCancel(context.Background())
+
+ lt := logtree.New()
+
+ // Only log to stderr when we're running in a test, not in a fuzz harness or a
+ // benchmark - otherwise we just waste CPU cycles.
+ verbose := false
+ if _, ok := t.(*testing.T); ok {
+ verbose = true
+ }
+ if verbose {
+ logtree.PipeAllToTest(t, lt)
+ }
+
+ sup := New(ctx, func(ctx context.Context) error {
+ Logger(ctx).Infof("Starting test %s...", t.Name())
+ if err := r(ctx); err != nil && !errors.Is(err, ctx.Err()) {
+ t.Errorf("Supervised runnable in harness returned error: %v", err)
+ return err
+ }
+ return nil
+ }, WithExistingLogtree(lt), WithPropagatePanic)
+
+ t.Cleanup(func() {
+ ctxC()
+ if verbose {
+ log.Printf("supervisor.TestHarness: Waiting for supervisor runnables to die...")
+ }
+ timeoutNag := time.Now().Add(5 * time.Second)
+
+ for {
+ live := sup.liveRunnables()
+ if len(live) == 0 {
+ if verbose {
+ log.Printf("supervisor.TestHarness: All done.")
+ }
+ return
+ }
+
+ if time.Now().After(timeoutNag) {
+ timeoutNag = time.Now().Add(5 * time.Second)
+ sort.Strings(live)
+ if verbose {
+ log.Printf("supervisor.TestHarness: Still live:")
+ for _, l := range live {
+ log.Printf("supervisor.TestHarness: - %s", l)
+ }
+ }
+ }
+
+ time.Sleep(time.Second)
+ }
+ })
+ return ctxC, lt
+}