blob: 99017b1e0830f67bc19cee9a416895b8fc901fa4 [file] [log] [blame]
package unittest
import (
"flag"
"fmt"
"runtime"
"strings"
"time"
"go.skia.org/infra/bazel/go/bazel"
"go.skia.org/infra/go/emulators"
"go.skia.org/infra/go/sktest"
)
const (
SMALL_TEST = "small"
MEDIUM_TEST = "medium"
LARGE_TEST = "large"
MANUAL_TEST = "manual"
)
var (
small = flag.Bool(SMALL_TEST, false, "Whether or not to run small tests.")
medium = flag.Bool(MEDIUM_TEST, false, "Whether or not to run medium tests.")
large = flag.Bool(LARGE_TEST, false, "Whether or not to run large tests.")
manual = flag.Bool(MANUAL_TEST, false, "Whether or not to run manual tests.")
uncategorized = flag.Bool("uncategorized", false, "Only run uncategorized tests.")
// DEFAULT_RUN indicates whether the given test type runs by default
// when no filter flag is specified.
DEFAULT_RUN = map[string]bool{
SMALL_TEST: true,
MEDIUM_TEST: true,
LARGE_TEST: true,
MANUAL_TEST: true,
}
TIMEOUT_SMALL = "4s"
TIMEOUT_MEDIUM = "15s"
TIMEOUT_LARGE = "4m"
TIMEOUT_MANUAL = TIMEOUT_LARGE
TIMEOUT_RACE = "5m"
// TEST_TYPES lists all of the types of tests.
TEST_TYPES = []string{
SMALL_TEST,
MEDIUM_TEST,
LARGE_TEST,
MANUAL_TEST,
}
)
// ShouldRun determines whether the test should run based on the provided flags.
func ShouldRun(testType string) bool {
if *uncategorized {
return false
}
// Fallback if no test filter is specified.
if !*small && !*medium && !*large && !*manual {
return DEFAULT_RUN[testType]
}
switch testType {
case SMALL_TEST:
return *small
case MEDIUM_TEST:
return *medium
case LARGE_TEST:
return *large
case MANUAL_TEST:
return *manual
}
return false
}
// SmallTest is a function which should be called at the beginning of a small
// test: A test (under 2 seconds) with no dependencies on external databases,
// networks, etc.
func SmallTest(t sktest.TestingT) {
if !ShouldRun(SMALL_TEST) {
t.Skip("Not running small tests.")
}
}
// MediumTest is a function which should be called at the beginning of an
// medium-sized test: a test (2-15 seconds) which has dependencies on external
// databases, networks, etc.
func MediumTest(t sktest.TestingT) {
if !ShouldRun(MEDIUM_TEST) {
t.Skip("Not running medium tests.")
}
}
// LargeTest is a function which should be called at the beginning of a large
// test: a test (> 15 seconds) with significant reliance on external
// dependencies which makes it too slow or flaky to run as part of the normal
// test suite.
func LargeTest(t sktest.TestingT) {
if !ShouldRun(LARGE_TEST) {
t.Skip("Not running large tests.")
}
}
// ManualTest is a function which should be called at the beginning of tests
// which shouldn't run on the bots due to excessive running time, external
// requirements, etc. These only run when the --manual flag is set.
func ManualTest(t sktest.TestingT) {
// Find the caller's file name.
_, thisFile, _, ok := runtime.Caller(0)
if !ok {
t.Fatal("runtime.Caller(0) failed")
}
file := thisFile
for skip := 0; file == thisFile; skip++ {
var ok bool
_, file, _, ok = runtime.Caller(skip)
if !ok {
t.Fatalf("runtime.Caller(%d) failed", skip)
}
}
// Force the naming convention expected by our custom go_test Bazel macro.
if !strings.HasSuffix(file, "_manual_test.go") {
t.Fatalf(`Manual tests must be placed in files ending with "_manual_test.go", was: "%s"`, file)
}
if !ShouldRun(MANUAL_TEST) {
t.Skip("Not running manual tests.")
}
}
// FakeExeTest masks a test from the uncategorized tests check. See executil.go for
// more on what FakeTests are used for.
func FakeExeTest(t sktest.TestingT) {
if *uncategorized {
t.Skip(`This is to appease the "uncategorized tests" check`)
}
}
// BazelOnlyTest is a function which should be called at the beginning of tests
// which should only run under Bazel (e.g. via "bazel test ...").
func BazelOnlyTest(t sktest.TestingT) {
if !bazel.InBazelTest() {
t.Skip("Not running Bazel tests from outside Bazel.")
}
}
// LinuxOnlyTest is a function which should be called at the beginning of a test
// which should only run on Linux.
func LinuxOnlyTest(t sktest.TestingT) {
if runtime.GOOS != "linux" {
t.Skip("Not running Linux-only tests.")
}
}
// RequiresBigTableEmulator should be called by any test case that requires the BigTable emulator.
//
// When running locally, the test case will fail if the corresponding environment variable is unset.
// When running under RBE, the first invocation of this function will start the emulator and set the
// appropriate environment variable, and any subsequent calls will reuse the emulator instance.
func RequiresBigTableEmulator(t sktest.TestingT) {
requiresEmulator(t, emulators.BigTable, useTestSuiteSharedEmulatorInstanceUnderRBE)
}
// RequiresCockroachDB should be called by any test case that requires the CockroachDB emulator.
//
// When running locally, the test case will fail if the corresponding environment variable is unset.
// When running under RBE, the first invocation of this function will start the emulator and set the
// appropriate environment variable, and any subsequent calls will reuse the emulator instance.
//
// Note: The CockroachDB emulator is just a test-only, real CockroachDB instance. We refer to it as
// an emulator for consistency with the Google Cloud emulators.
func RequiresCockroachDB(t sktest.TestingT) {
requiresEmulator(t, emulators.CockroachDB, useTestSuiteSharedEmulatorInstanceUnderRBE)
}
// RequiresDatastoreEmulator should be called by any test case that requires the Datastore emulator.
//
// When running locally, the test case will fail if the corresponding environment variable is unset.
// When running under RBE, the first invocation of this function will start the emulator and set the
// appropriate environment variable, and any subsequent calls will reuse the emulator instance.
func RequiresDatastoreEmulator(t sktest.TestingT) {
requiresEmulator(t, emulators.Datastore, useTestSuiteSharedEmulatorInstanceUnderRBE)
}
// RequiresFirestoreEmulator should be called by any test case that requires the Firestore emulator.
//
// When running locally, the test case will fail if the corresponding environment variable is unset.
// When running under RBE, the first invocation of this function will start the emulator and set the
// appropriate environment variable, and any subsequent calls will reuse the emulator instance.
func RequiresFirestoreEmulator(t sktest.TestingT) {
requiresEmulator(t, emulators.Firestore, useTestSuiteSharedEmulatorInstanceUnderRBE)
}
// RequiresFirestoreEmulatorWithTestCaseSpecificInstanceUnderRBE is equivalent to
// RequiresFirestoreEmulator, except that under Bazel and RBE, it will always launch a new emulator
// instance that will only be visible to the current test case. After the test case finishes, the
// emulator instance will *not* be reused by any subsequent test cases, and will eventually be
// killed after the test suite finishes running.
//
// It is safe for some test cases in a test suite to call RequiresFirestoreEmulator, and for some
// others to call RequiresFirestoreEmulatorWithTestCaseSpecificInstanceUnderRBE.
//
// This should only be used in the presence of hard-to-diagnose bugs that only occur under RBE.
func RequiresFirestoreEmulatorWithTestCaseSpecificInstanceUnderRBE(t sktest.TestingT) {
requiresEmulator(t, emulators.Firestore, useTestCaseSpecificEmulatorInstanceUnderRBE)
}
// RequiresPubSubEmulator should be called by any test case that requires the PubSub emulator.
//
// When running locally, the test case will fail if the corresponding environment variable is unset.
// When running under RBE, the first invocation of this function will start the emulator and set the
// appropriate environment variable, and any subsequent calls will reuse the emulator instance.
func RequiresPubSubEmulator(t sktest.TestingT) {
requiresEmulator(t, emulators.PubSub, useTestSuiteSharedEmulatorInstanceUnderRBE)
}
// emulatorScopeUnderRBE indicates whether the test case requesting an emulator can reuse an
// emulator instance started by an earlier test case, or whether it requires a test case-specific
// instance not visible to any other test cases.
//
// Note that this is ignored outside of Bazel and RBE, in which case, tests will always use the
// emulator instance pointed to by the corresponding *_EMULATOR_HOST environment variable.
type emulatorScopeUnderRBE string
const (
useTestSuiteSharedEmulatorInstanceUnderRBE = emulatorScopeUnderRBE("useTestSuiteSharedEmulatorInstanceUnderRBE")
useTestCaseSpecificEmulatorInstanceUnderRBE = emulatorScopeUnderRBE("useTestCaseSpecificEmulatorInstanceUnderRBE")
)
// requiresEmulator 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.
func requiresEmulator(t sktest.TestingT, emulator emulators.Emulator, scope emulatorScopeUnderRBE) {
if bazel.InBazelTestOnRBE() {
setUpEmulatorBazelRBEOnly(t, emulator, scope)
return
}
// When running locally, the developer is responsible for running any necessary emulators.
host := emulators.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)
}
}
// testCaseSpecificEmulatorInstancesBazelRBEOnly keeps track of the test case-specific emulator
// instances started by the current test case. We allow at most one test case-specific instance of
// each emulator.
var testCaseSpecificEmulatorInstancesBazelRBEOnly = map[emulators.Emulator]bool{}
// setUpEmulatorBazelRBEOnly starts an instance of the given emulator, or reuses an existing
// instance if one was started by an earlier test case.
//
// 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 emulators.Emulator, scope emulatorScopeUnderRBE) {
if !bazel.InBazelTestOnRBE() {
panic("This function must only be called when running under Bazel and RBE.")
}
var wasEmulatorStarted bool
switch scope {
case useTestCaseSpecificEmulatorInstanceUnderRBE:
// If the current test case requests a test case-specific instance of the same emulator more
// than once, it's almost surely a bug, so we fail loudly.
if testCaseSpecificEmulatorInstancesBazelRBEOnly[emulator] {
t.Fatalf("A test-case specific instance of the %s emulator was already started.", emulator)
}
if err := emulators.StartAdHocEmulatorInstanceAndSetEmulatorHostEnvVarBazelRBEOnly(emulator); err != nil {
t.Fatalf("Error starting a test case-specific instance of the %s emulator: %v", emulator, err)
}
wasEmulatorStarted = true
testCaseSpecificEmulatorInstancesBazelRBEOnly[emulator] = true
case useTestSuiteSharedEmulatorInstanceUnderRBE:
// Start an emulator instance shared among all test cases. If the emulator was already started
// by an earlier test case, then we'll reuse the emulator instance that's already running.
var err error
wasEmulatorStarted, err = emulators.StartEmulatorIfNotRunning(emulator)
if err != nil {
t.Fatalf("Error starting emulator: %v", err)
}
// Setting the corresponding *_EMULATOR_HOST environment variable is what effectively makes the
// emulator visible to the test case.
if err := emulators.SetEmulatorHostEnvVar(emulator); err != nil {
t.Fatalf("Error setting emulator host environment variables: %v", err)
}
default:
panic(fmt.Sprintf("Unknown emulatorScopeUnderRBE value: %s", scope))
}
// 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 wasEmulatorStarted {
// 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 := emulators.UnsetAllEmulatorHostEnvVars(); err != nil {
t.Fatalf("Error while unsetting the emulator host environment variables: %v", err)
}
// Allow subsequent test cases to start their own test case-specific instances of the emulator.
if scope == useTestCaseSpecificEmulatorInstanceUnderRBE {
testCaseSpecificEmulatorInstancesBazelRBEOnly[emulator] = false
}
})
}