m/t/installer: resolve deps with bazel.Runfile

Change-Id: Ie69b8b6ef12028264d1c396dacb3c00be795ad44
Reviewed-on: https://review.monogon.dev/c/monogon/+/455
Reviewed-by: Sergiusz Bazanski <serge@monogon.tech>
diff --git a/metropolis/test/installer/main.go b/metropolis/test/installer/main.go
index ea2c20a..0f2f40e 100644
--- a/metropolis/test/installer/main.go
+++ b/metropolis/test/installer/main.go
@@ -27,23 +27,35 @@
 	"log"
 	"os"
 	"os/exec"
+	"path/filepath"
 	"syscall"
 	"testing"
 
+	"github.com/bazelbuild/rules_go/go/tools/bazel"
 	diskfs "github.com/diskfs/go-diskfs"
 	"github.com/diskfs/go-diskfs/disk"
 	"github.com/diskfs/go-diskfs/partition/gpt"
-	"source.monogon.dev/metropolis/proto/api"
 
 	mctl "source.monogon.dev/metropolis/cli/metroctl/core"
-	osimage "source.monogon.dev/metropolis/node/build/mkimage/osimage"
+	"source.monogon.dev/metropolis/node/build/mkimage/osimage"
+	"source.monogon.dev/metropolis/proto/api"
 )
 
-const (
-	InstallerEFIPayload = "metropolis/node/installer/kernel.efi"
-	TestOSBundle        = "metropolis/test/installer/testos/testos_bundle.zip"
-	InstallerImage      = "metropolis/test/installer/installer.img"
-	NodeStorage         = "metropolis/test/installer/stor.img"
+// Each variable in this block points to either a test dependency or a side
+// effect. These variables are initialized in TestMain using Bazel.
+var (
+	// installerEFIPayload is a filesystem path pointing at the unified kernel
+	// image dependency.
+	installerEFIPayload string
+	// testOSBundle is a filesystem path pointing at the Metropolis installation
+	// bundle.
+	testOSBundle string
+	// installerImage is a filesystem path pointing at the installer image that
+	// is generated during the test, and is removed afterwards.
+	installerImage string
+	// nodeStorage is a filesystem path pointing at the VM block device image
+	// Metropolis is installed to during the test. The file is removed afterwards.
+	nodeStorage string
 )
 
 // runQemu starts a QEMU process and waits for it to finish. args is
@@ -89,22 +101,22 @@
 // concatenated to the list of predefined default arguments. It returns true if
 // expectedOutput is found in the serial port output. It may return an error.
 func runQemuWithInstaller(args []string, expectedOutput string) (bool, error) {
-	args = append(args, "-drive", "if=virtio,format=raw,snapshot=on,cache=unsafe,file="+InstallerImage)
+	args = append(args, "-drive", "if=virtio,format=raw,snapshot=on,cache=unsafe,file="+installerImage)
 	return runQemu(args, expectedOutput)
 }
 
 // getStorage creates a sparse file, given a size expressed in mebibytes, and
 // returns a path to that file. It may return an error.
 func getStorage(size int64) (string, error) {
-	image, err := os.Create(NodeStorage)
+	image, err := os.Create(nodeStorage)
 	if err != nil {
-		return "", fmt.Errorf("couldn't create the block device image at %q: %w", NodeStorage, err)
+		return "", fmt.Errorf("couldn't create the block device image at %q: %w", nodeStorage, err)
 	}
 	if err := syscall.Ftruncate(int(image.Fd()), size*1024*1024); err != nil {
-		return "", fmt.Errorf("couldn't resize the block device image at %q: %w", NodeStorage, err)
+		return "", fmt.Errorf("couldn't resize the block device image at %q: %w", nodeStorage, err)
 	}
 	image.Close()
-	return NodeStorage, nil
+	return nodeStorage, nil
 }
 
 // qemuDriveParam returns QEMU parameters required to run it with a
@@ -131,18 +143,50 @@
 }
 
 func TestMain(m *testing.M) {
+	// Initialize global variables holding filesystem paths pointing to runtime
+	// dependencies and side effects.
+	paths := []struct {
+		// res is a pointer to the global variable initialized.
+		res *string
+		// dep states whether the path should be resolved as a dependency, rather
+		// than a side effect.
+		dep bool
+		// src is a source path, based on which res is resolved. In case of
+		// dependencies it must be a path relative to the repository root. For
+		// side effects, it must be just a filename.
+		src string
+	}{
+		{&installerEFIPayload, true, "metropolis/node/installer/kernel.efi"},
+		{&testOSBundle, true, "metropolis/test/installer/testos/testos_bundle.zip"},
+		{&installerImage, false, "installer.img"},
+		{&nodeStorage, false, "stor.img"},
+	}
+	for _, p := range paths {
+		if p.dep {
+			res, err := bazel.Runfile(p.src)
+			if err != nil {
+				log.Fatal(err)
+			}
+			*p.res = res
+		} else {
+			od := os.Getenv("TEST_TMPDIR")
+			// If od is empty, just use the working directory, which is set according
+			// to the rundir attribute of go_test.
+			*p.res = filepath.Join(od, p.src)
+		}
+	}
+
 	// Build the installer image with metroctl, given the EFI executable
-	// generated by Metropolis buildsystem. This mimics standard usage of
-	// metroctl CLI.
-	installer, err := os.Open(InstallerEFIPayload)
+	// generated by Metropolis buildsystem.
+	installer, err := os.Open(installerEFIPayload)
 	if err != nil {
-		log.Fatalf("Couldn't open the installer EFI executable at %q: %s", InstallerEFIPayload, err.Error())
+		log.Fatalf("Couldn't open the installer EFI executable at %q: %s", installerEFIPayload, err.Error())
 	}
 	info, err := installer.Stat()
 	if err != nil {
 		log.Fatalf("Couldn't stat the installer EFI executable: %s", err.Error())
 	}
-	bundle, err := os.Open(TestOSBundle)
+	bundle, err := os.Open(testOSBundle)
 	if err != nil {
 		log.Fatalf("failed to open TestOS bundle: %v", err)
 	}
@@ -153,27 +197,27 @@
 	iargs := mctl.MakeInstallerImageArgs{
 		Installer:     installer,
 		InstallerSize: uint64(info.Size()),
-		TargetPath:    InstallerImage,
+		TargetPath:    installerImage,
 		NodeParams:    &api.NodeParameters{},
 		Bundle:        bundle,
 		BundleSize:    uint64(bundleStat.Size()),
 	}
 	if err := mctl.MakeInstallerImage(iargs); err != nil {
-		log.Fatalf("Couldn't create the installer image at %q: %s", InstallerImage, err.Error())
+		log.Fatalf("Couldn't create the installer image at %q: %s", installerImage, err.Error())
 	}
 	// With common dependencies set up, run the tests.
 	code := m.Run()
 	// Clean up.
-	os.Remove(InstallerImage)
+	os.Remove(installerImage)
 	os.Exit(code)
 }
 
 func TestInstallerImage(t *testing.T) {
 	// This test examines the installer image, making sure that the GPT and the
 	// ESP contents are in order.
-	image, err := diskfs.OpenWithMode(InstallerImage, diskfs.ReadOnly)
+	image, err := diskfs.OpenWithMode(installerImage, diskfs.ReadOnly)
 	if err != nil {
-		t.Errorf("Couldn't open the installer image at %q: %s", InstallerImage, err.Error())
+		t.Errorf("Couldn't open the installer image at %q: %s", installerImage, err.Error())
 	}
 	// Verify that GPT exists.
 	ti, err := image.GetPartitionTable()