blob: 253db2d7fc0922a55b5d2201d47e1b7af5786b52 [file] [log] [blame]
package main
import (
"context"
"flag"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
"time"
)
func main() {
var (
testBin = flag.String("test_bin", "", "Path to the test binary")
envBin = flag.String("env_bin", "", "Path to the environment binary")
readyCheck = flag.Duration("ready_check", 5*time.Second, "Wait up to this long for the environment to be ready")
)
flag.Parse()
if *testBin == "" || *envBin == "" {
fatalf("Must provide --test_bin and --env_bin")
}
// For some unknown reason, Go tests fail with "fork/exec [...]: no such file or directory" when
// invoked from this script via $TEST_BIN, which holds the path to a symlink created by Bazel.
// Invoking the test binary via its real path, as opposed to a symlink, prevents this error.
// We need to be sure to use filepath.EvalSymlinks and not os.ReadLink because the latter will
// only resolve the first symlink.
var err error
*testBin, err = filepath.EvalSymlinks(*testBin)
if err != nil {
fatalf("Could not resolve symlinks to %s: %s", *testBin, err)
}
*envBin, err = filepath.EvalSymlinks(*envBin)
if err != nil {
fatalf("Could not resolve symlinks to %s: %s", *envBin, err)
}
fmt.Printf("TestBin %s\n", *testBin)
fmt.Printf("EnvBin %s\n", *envBin)
envDir := filepath.Join(os.Getenv("TEST_TMPDIR"), "envdir")
if err := os.MkdirAll(envDir, 0755); err != nil {
fatalf("Could not create %s: %s", envDir, err)
}
envReadyFile := filepath.Join(envDir, "ready")
// Run both of the binaries with a copy of the visible environment variables plus these two.
env := os.Environ()
env = append(env, "ENV_DIR="+envDir, "ENV_READY_FILE="+envReadyFile)
fmt.Printf("TEST_TMPDIR: %s\n", os.Getenv("TEST_TMPDIR"))
fmt.Printf("ENV_DIR: %s\n", envDir)
fmt.Printf("ENV_READY_FILE: %s\n", envReadyFile)
// We do not want any child processes to outlive this one.
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
envCmd := exec.CommandContext(ctx, *envBin)
envCmd.Env = env
envCmd.Stdout = wrap(os.Stdout, "[env binary] ")
envCmd.Stderr = wrap(os.Stderr, "[env binary] ")
if err := envCmd.Start(); err != nil {
fatalf("Could not run env binary %s: %s", *envBin, err)
}
fmt.Printf("Environment started with PID %d\n", envCmd.Process.Pid)
fmt.Printf("Waiting up to %s for environment to be ready\n", *readyCheck)
startTime := time.Now()
tck := time.NewTicker(100 * time.Millisecond)
ready := false
for time.Now().Sub(startTime) < *readyCheck {
<-tck.C
if _, err := os.Stat(envReadyFile); err == nil {
ready = true
break // The file exists, we are done
}
}
tck.Stop()
if !ready {
fatalf("Timed out while waiting for environment to be ready")
}
// The environment is set up (and probably still running), we can finally run the tests
// If *testBin refers to a bash script, but does not have a shebang as the very first line,
// there will be a mysterious "fork/exec ... exec format error"
testCmd := exec.CommandContext(ctx, *testBin)
testCmd.Env = env
testCmd.Stdout = os.Stdout
testCmd.Stderr = os.Stderr
if err := testCmd.Start(); err != nil {
fatalf("Could not run test binary %s: %s", *testBin, err)
}
err = testCmd.Wait()
fmt.Printf("Test finished: %v\n", err)
// Tear down the environment. Send SIGTERM and give it a moment to gracefully shutdown before
// this process exits.
err = envCmd.Process.Signal(syscall.SIGTERM)
fmt.Printf("Shutting down environment: %v\n", err)
time.Sleep(100 * time.Millisecond)
// forward the test exit code to Bazel
os.Exit(testCmd.ProcessState.ExitCode())
}
type wrappedWriter struct {
writer io.Writer
prefix []byte
}
// Write writes the configured prefix and then the given bytes. If the prefix fails, the main
// payload is not written.
func (w wrappedWriter) Write(p []byte) (n int, err error) {
if _, err := w.writer.Write(w.prefix); err != nil {
return 0, err
}
return w.writer.Write(p)
}
// wrap will put the given prefix before any calls to Write on the provided io.Writer.
func wrap(w io.Writer, prefix string) io.Writer {
return wrappedWriter{writer: w, prefix: []byte(prefix)}
}
func fatalf(format string, args ...interface{}) {
// Ensure there is a newline at the end of the fatal message.
format = strings.TrimSuffix(format, "\n") + "\n"
fmt.Printf(format, args...)
os.Exit(1)
}