m/t/e2e: move testEventual to common test util pkg

testEventual, among other implementation, will be reused in metroctl
tests.

Change-Id: I24df31a72034b707e3906889e7a569c8e97669ad
Reviewed-on: https://review.monogon.dev/c/monogon/+/788
Tested-by: Jenkins CI
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
diff --git a/metropolis/test/e2e/BUILD.bazel b/metropolis/test/e2e/BUILD.bazel
index 4d04857..6cc0ab7 100644
--- a/metropolis/test/e2e/BUILD.bazel
+++ b/metropolis/test/e2e/BUILD.bazel
@@ -2,10 +2,7 @@
 
 go_library(
     name = "e2e",
-    srcs = [
-        "kubernetes_helpers.go",
-        "utils.go",
-    ],
+    srcs = ["kubernetes_helpers.go"],
     importpath = "source.monogon.dev/metropolis/test/e2e",
     visibility = ["//metropolis/test:__subpackages__"],
     deps = [
@@ -37,6 +34,7 @@
         "//metropolis/node/core/rpc",
         "//metropolis/proto/api",
         "//metropolis/test/launch/cluster",
+        "//metropolis/test/util",
         "@io_k8s_api//core/v1:core",
         "@io_k8s_apimachinery//pkg/api/resource",
         "@io_k8s_apimachinery//pkg/apis/meta/v1:meta",
diff --git a/metropolis/test/e2e/main_test.go b/metropolis/test/e2e/main_test.go
index 89df286..32cf2bd 100644
--- a/metropolis/test/e2e/main_test.go
+++ b/metropolis/test/e2e/main_test.go
@@ -42,6 +42,7 @@
 	"source.monogon.dev/metropolis/node/core/rpc"
 	apb "source.monogon.dev/metropolis/proto/api"
 	"source.monogon.dev/metropolis/test/launch/cluster"
+	"source.monogon.dev/metropolis/test/util"
 )
 
 const (
@@ -114,7 +115,7 @@
 	// Deployments and StatefulSets
 	t.Run("RunGroup", func(t *testing.T) {
 		t.Run("Cluster", func(t *testing.T) {
-			testEventual(t, "Retrieving cluster directory sucessful", ctx, 60*time.Second, func(ctx context.Context) error {
+			util.TestEventual(t, "Retrieving cluster directory sucessful", ctx, 60*time.Second, func(ctx context.Context) error {
 				res, err := mgmt.GetClusterInfo(ctx, &apb.GetClusterInfoRequest{})
 				if err != nil {
 					return fmt.Errorf("GetClusterInfo: %w", err)
@@ -143,14 +144,14 @@
 				}
 				return nil
 			})
-			testEventual(t, "Node rejoin successful", ctx, 60*time.Second, func(ctx context.Context) error {
+			util.TestEventual(t, "Node rejoin successful", ctx, 60*time.Second, func(ctx context.Context) error {
 				// Ensure nodes rejoin the cluster after a reboot by reboting the 1st node.
 				if err := cluster.RebootNode(ctx, 1); err != nil {
 					return fmt.Errorf("while rebooting a node: %w", err)
 				}
 				return nil
 			})
-			testEventual(t, "Heartbeat test successful", ctx, 60*time.Second, func(ctx context.Context) error {
+			util.TestEventual(t, "Heartbeat test successful", ctx, 60*time.Second, func(ctx context.Context) error {
 				// Ensure all cluster nodes are capable of sending heartbeat updates.
 				// This test assumes the expected count of nodes is already present in
 				// the cluster.
@@ -192,7 +193,7 @@
 			if err != nil {
 				t.Fatal(err)
 			}
-			testEventual(t, "Nodes are registered and ready", ctx, largeTestTimeout, func(ctx context.Context) error {
+			util.TestEventual(t, "Nodes are registered and ready", ctx, largeTestTimeout, func(ctx context.Context) error {
 				nodes, err := clientSet.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
 				if err != nil {
 					return err
@@ -211,11 +212,11 @@
 				}
 				return nil
 			})
-			testEventual(t, "Simple deployment", ctx, largeTestTimeout, func(ctx context.Context) error {
+			util.TestEventual(t, "Simple deployment", ctx, largeTestTimeout, func(ctx context.Context) error {
 				_, err := clientSet.AppsV1().Deployments("default").Create(ctx, makeTestDeploymentSpec("test-deploy-1"), metav1.CreateOptions{})
 				return err
 			})
-			testEventual(t, "Simple deployment is running", ctx, largeTestTimeout, func(ctx context.Context) error {
+			util.TestEventual(t, "Simple deployment is running", ctx, largeTestTimeout, func(ctx context.Context) error {
 				res, err := clientSet.CoreV1().Pods("default").List(ctx, metav1.ListOptions{LabelSelector: "name=test-deploy-1"})
 				if err != nil {
 					return err
@@ -234,14 +235,14 @@
 					return fmt.Errorf("pod is not ready: %v", events.Items[0].Message)
 				}
 			})
-			testEventual(t, "Simple deployment with runc", ctx, largeTestTimeout, func(ctx context.Context) error {
+			util.TestEventual(t, "Simple deployment with runc", ctx, largeTestTimeout, func(ctx context.Context) error {
 				deployment := makeTestDeploymentSpec("test-deploy-2")
 				var runcStr = "runc"
 				deployment.Spec.Template.Spec.RuntimeClassName = &runcStr
 				_, err := clientSet.AppsV1().Deployments("default").Create(ctx, deployment, metav1.CreateOptions{})
 				return err
 			})
-			testEventual(t, "Simple deployment is running on runc", ctx, largeTestTimeout, func(ctx context.Context) error {
+			util.TestEventual(t, "Simple deployment is running on runc", ctx, largeTestTimeout, func(ctx context.Context) error {
 				res, err := clientSet.CoreV1().Pods("default").List(ctx, metav1.ListOptions{LabelSelector: "name=test-deploy-2"})
 				if err != nil {
 					return err
@@ -265,11 +266,11 @@
 					return fmt.Errorf("pod is not ready: %v", errorMsg.String())
 				}
 			})
-			testEventual(t, "Simple StatefulSet with PVC", ctx, largeTestTimeout, func(ctx context.Context) error {
+			util.TestEventual(t, "Simple StatefulSet with PVC", ctx, largeTestTimeout, func(ctx context.Context) error {
 				_, err := clientSet.AppsV1().StatefulSets("default").Create(ctx, makeTestStatefulSet("test-statefulset-1", corev1.PersistentVolumeFilesystem), metav1.CreateOptions{})
 				return err
 			})
-			testEventual(t, "Simple StatefulSet with PVC is running", ctx, largeTestTimeout, func(ctx context.Context) error {
+			util.TestEventual(t, "Simple StatefulSet with PVC is running", ctx, largeTestTimeout, func(ctx context.Context) error {
 				res, err := clientSet.CoreV1().Pods("default").List(ctx, metav1.ListOptions{LabelSelector: "name=test-statefulset-1"})
 				if err != nil {
 					return err
@@ -288,11 +289,11 @@
 					return fmt.Errorf("pod is not ready: %v", events.Items[0].Message)
 				}
 			})
-			testEventual(t, "Simple StatefulSet with Block PVC", ctx, largeTestTimeout, func(ctx context.Context) error {
+			util.TestEventual(t, "Simple StatefulSet with Block PVC", ctx, largeTestTimeout, func(ctx context.Context) error {
 				_, err := clientSet.AppsV1().StatefulSets("default").Create(ctx, makeTestStatefulSet("test-statefulset-2", corev1.PersistentVolumeBlock), metav1.CreateOptions{})
 				return err
 			})
-			testEventual(t, "Simple StatefulSet with Block PVC is running", ctx, largeTestTimeout, func(ctx context.Context) error {
+			util.TestEventual(t, "Simple StatefulSet with Block PVC is running", ctx, largeTestTimeout, func(ctx context.Context) error {
 				res, err := clientSet.CoreV1().Pods("default").List(ctx, metav1.ListOptions{LabelSelector: "name=test-statefulset-2"})
 				if err != nil {
 					return err
@@ -311,7 +312,7 @@
 					return fmt.Errorf("pod is not ready: %v", events.Items[0].Message)
 				}
 			})
-			testEventual(t, "Pod with preseeded image", ctx, smallTestTimeout, func(ctx context.Context) error {
+			util.TestEventual(t, "Pod with preseeded image", ctx, smallTestTimeout, func(ctx context.Context) error {
 				_, err := clientSet.CoreV1().Pods("default").Create(ctx, &corev1.Pod{
 					ObjectMeta: metav1.ObjectMeta{
 						Name: "preseed-test-1",
@@ -327,7 +328,7 @@
 				}, metav1.CreateOptions{})
 				return err
 			})
-			testEventual(t, "Pod with preseeded image is completed", ctx, largeTestTimeout, func(ctx context.Context) error {
+			util.TestEventual(t, "Pod with preseeded image is completed", ctx, largeTestTimeout, func(ctx context.Context) error {
 				pod, err := clientSet.CoreV1().Pods("default").Get(ctx, "preseed-test-1", metav1.GetOptions{})
 				if err != nil {
 					return fmt.Errorf("failed to get pod: %w", err)
@@ -343,7 +344,7 @@
 				}
 			})
 			if os.Getenv("HAVE_NESTED_KVM") != "" {
-				testEventual(t, "Pod for KVM/QEMU smoke test", ctx, smallTestTimeout, func(ctx context.Context) error {
+				util.TestEventual(t, "Pod for KVM/QEMU smoke test", ctx, smallTestTimeout, func(ctx context.Context) error {
 					runcRuntimeClass := "runc"
 					_, err := clientSet.CoreV1().Pods("default").Create(ctx, &corev1.Pod{
 						ObjectMeta: metav1.ObjectMeta{
@@ -366,7 +367,7 @@
 					}, metav1.CreateOptions{})
 					return err
 				})
-				testEventual(t, "KVM/QEMU smoke test completion", ctx, smallTestTimeout, func(ctx context.Context) error {
+				util.TestEventual(t, "KVM/QEMU smoke test completion", ctx, smallTestTimeout, func(ctx context.Context) error {
 					pod, err := clientSet.CoreV1().Pods("default").Get(ctx, "vm-smoketest", metav1.GetOptions{})
 					if err != nil {
 						return fmt.Errorf("failed to get pod: %w", err)
diff --git a/metropolis/test/e2e/utils.go b/metropolis/test/e2e/utils.go
deleted file mode 100644
index dcc9eac..0000000
--- a/metropolis/test/e2e/utils.go
+++ /dev/null
@@ -1,52 +0,0 @@
-// 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 e2e
-
-import (
-	"context"
-	"errors"
-	"testing"
-	"time"
-)
-
-// testEventual creates a new subtest looping the given function until it
-// either doesn't return an error anymore or the timeout is exceeded. The last
-// returned non-context-related error is being used as the test error.
-func testEventual(t *testing.T, name string, ctx context.Context, timeout time.Duration, f func(context.Context) error) {
-	ctx, cancel := context.WithTimeout(ctx, timeout)
-	t.Helper()
-	t.Run(name, func(t *testing.T) {
-		defer cancel()
-		var lastErr = errors.New("test didn't run to completion at least once")
-		t.Parallel()
-		for {
-			err := f(ctx)
-			if err == nil {
-				return
-			}
-			if err == ctx.Err() {
-				t.Fatal(lastErr)
-			}
-			lastErr = err
-			select {
-			case <-ctx.Done():
-				t.Fatal(lastErr)
-			case <-time.After(1 * time.Second):
-			}
-		}
-	})
-}
diff --git a/metropolis/test/util/BUILD.bazel b/metropolis/test/util/BUILD.bazel
new file mode 100644
index 0000000..7caf53b
--- /dev/null
+++ b/metropolis/test/util/BUILD.bazel
@@ -0,0 +1,8 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+    name = "util",
+    srcs = ["runners.go"],
+    importpath = "source.monogon.dev/metropolis/test/util",
+    visibility = ["//metropolis:__subpackages__"],
+)
diff --git a/metropolis/test/util/runners.go b/metropolis/test/util/runners.go
new file mode 100644
index 0000000..47bf59f
--- /dev/null
+++ b/metropolis/test/util/runners.go
@@ -0,0 +1,38 @@
+// This file implements test helper functions that augment the way any given
+// test is run.
+package util
+
+import (
+	"context"
+	"errors"
+	"testing"
+	"time"
+)
+
+// TestEventual creates a new subtest looping the given function until it
+// either doesn't return an error anymore or the timeout is exceeded. The last
+// returned non-context-related error is being used as the test error.
+func TestEventual(t *testing.T, name string, ctx context.Context, timeout time.Duration, f func(context.Context) error) {
+	ctx, cancel := context.WithTimeout(ctx, timeout)
+	t.Helper()
+	t.Run(name, func(t *testing.T) {
+		defer cancel()
+		var lastErr = errors.New("test didn't run to completion at least once")
+		t.Parallel()
+		for {
+			err := f(ctx)
+			if err == nil {
+				return
+			}
+			if err == ctx.Err() {
+				t.Fatal(lastErr)
+			}
+			lastErr = err
+			select {
+			case <-ctx.Done():
+				t.Fatal(lastErr)
+			case <-time.After(1 * time.Second):
+			}
+		}
+	})
+}