blob: 28ee39c368165c8cdff6c45e443ccd1359c8c69f [file] [log] [blame]
package emulators
import (
"fmt"
"os"
"os/exec"
"syscall"
"time"
"go.skia.org/infra/bazel/go/bazel"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/sktest"
)
type StartEmulator func() (bool, error)
// RequireEmulator fails the test when running outside of RBE if the corresponding *_EMULATOR_HOST
// environment variable wasn't set, or starts a new emulator instance under RBE if necessary.
// This should not be called by unit tests directly, but rather by the subpackages of this module.
func RequireEmulator(t sktest.TestingT, emulator Emulator, fn StartEmulator) {
if bazel.InBazelTestOnRBE() {
setUpEmulatorBazelRBEOnly(t, emulator, fn)
return
}
// When running locally, the developer is responsible for running any necessary emulators.
host := GetEmulatorHostEnvVar(emulator)
if host == "" {
t.Fatalf(`This test requires the %s emulator, which you can start with
$ ./scripts/run_emulators/run_emulators start
and then set the environment variables it prints out.`, emulator)
}
}
// setUpEmulatorBazelRBEOnly starts an instance of the given emulator, or reuses an existing
// instance if one was started by an earlier test case. It then sets the appropriate environment
// variable with the port number where the emulator is listening.
//
// If a test case-specific instance is requested, it will always start a new emulator instance,
// regardless of whether one was started by an earlier test case. The test case-specific instance
// will only be visible to the current test case (i.e. it will not be visible to any subsequent test
// cases).
//
// Test case-specific instances should only be used in the presence of hard-to-diagnose bugs that
// only occur under RBE.
func setUpEmulatorBazelRBEOnly(t sktest.TestingT, emulator Emulator, fn StartEmulator) {
started, err := fn()
if err != nil {
t.Fatalf("Could not start %s Emulator: %s", emulator, err)
return
}
if err = setEmulatorHostEnvVar(emulator); err != nil {
t.Fatalf("Could not set env var for %s Emulator: %s", emulator, err)
return
}
// If the above code actually started the emulator, give the emulator time to boot.
//
// Empirically chosen: A delay of 5 seconds seems OK for all emulators; shorter delays tend to
// cause flakes.
if started {
// TODO(kjlubick) use emulator health checks instead of just sleeping
time.Sleep(5 * time.Second)
fmt.Println("Finished sleeping waiting for emulator to boot")
}
t.Cleanup(func() {
// By unsetting any *_EMULATOR_HOST environment variables set by the current test case, we
// ensure that any subsequent test cases only "see" the emulators they request via any of the
// unittest.Requires*Emulator functions. This makes dependencies on emulators explicit at the
// test case level, and makes individual test cases more self-documenting.
if err := unsetAllEmulatorHostEnvVars(); err != nil {
t.Fatalf("Error while unsetting the emulator host environment variables: %v", err)
}
})
}
// StartForRBE performs some shared-setup to starting emulators. It should not be called by anything
// other than subpackages of this package.
func StartForRBE(cmd *exec.Cmd) error {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if bazel.InBazelTestOnRBE() {
// Force emulator child processes to die as soon as the parent process (e.g. the Go test runner)
// dies. If we don't do this, the emulators will continue running indefinitely after the parent
// process dies, eventually timing out.
//
// Note that this is only possible under Linux. The below function call will panic under
// non-Linux operating systems. Running emulator tests under RBE on non-Linux OSes is therefore
// not supported. This is OK because our RBE instance is currently Linux-only. See the comments
// in the function body for alternative approaches if we ever decide to run emulator tests under
// RBE on other operating systems.
cmd.SysProcAttr = makeSysProcAttrWithPdeathsigSIGKILL()
}
// Start the emulator.
fmt.Printf("Starting emulator: %s %s\n", cmd.Path, cmd.Args)
if err := cmd.Start(); err != nil {
return skerr.Wrap(err)
}
// Log the emulator's exit status.
go func() {
err := cmd.Wait()
if err != nil {
fmt.Printf("Emulator %s finished with error: %v\n", cmd.Path, err)
return
}
fmt.Printf("Emulator %s finished with exit status: %d\n", cmd.Path, cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus())
}()
return nil
}