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
+}