|  | // Package executil provides a mostly transparent way to make os/exec testable. It is inspired by | 
|  | // https://npf.io/2015/06/testing-exec-command/ (which was inspired by the standard library's tests | 
|  | // of os/exec). Basically, the helpers in this package replace a call to an arbitrary executable | 
|  | // (and arguments) with a call to the underlying test binary, with a flag to run exactly one test. | 
|  | // This test can then be a fake implementation of the binary, do assertions on the arguments, etc. | 
|  | // | 
|  | // See executil_test.go for example usages. | 
|  | package executil | 
|  |  | 
|  | import ( | 
|  | "context" | 
|  | "os" | 
|  | "os/exec" | 
|  | "sync" | 
|  | ) | 
|  |  | 
|  | const ( | 
|  | // OverrideEnvironmentVariable is the environment variable that will be set if a test has been | 
|  | // invoked via CommandContext below and it should behave as if it is faking a call to an | 
|  | // executable. The value it is set to should be considered arbitrary and not relied upon. | 
|  | OverrideEnvironmentVariable = "SKIA_INFRA_OVERRIDE_TEST" | 
|  |  | 
|  | // This is the key used in context.Value to correspond to a *fakeTestTracker object. | 
|  | overrideKey = "skia_infra_override_cmd" | 
|  | ) | 
|  |  | 
|  | // WithFakeTests returns a context.Context loaded with a special Value containing the given test | 
|  | // names. When this Context is passed into this package's CommandContext, faked *exec.Cmd objects | 
|  | // will be returned using the given test names. The first call to CommandContext will be faked | 
|  | // with the first value of fakeTestNames, the second call to CommandContext will use the | 
|  | // second value of fakeTestNames and so on. This panics if the provided context was one that | 
|  | // already has fake tests associated with it. | 
|  | func WithFakeTests(parent context.Context, fakeTestNames ...string) context.Context { | 
|  | if _, ok := parent.Value(overrideKey).(*fakeTestTracker); ok { | 
|  | panic("parent context already has fake tests associated with it") | 
|  | } | 
|  | return context.WithValue(parent, overrideKey, &fakeTestTracker{ | 
|  | index:         0, | 
|  | fakeTestNames: fakeTestNames, | 
|  | }) | 
|  | } | 
|  |  | 
|  | // FakeTestsContext is a convenient wrapper around WithFakeTests using context.Background(). | 
|  | func FakeTestsContext(fakeTestNames ...string) context.Context { | 
|  | return WithFakeTests(context.Background(), fakeTestNames...) | 
|  | } | 
|  |  | 
|  | // fakeTestTracker keeps track of which test we should fake out next. We have this be a struct and | 
|  | // store the pointer to this struct in the ctx.Value so we can mutate the value without having | 
|  | // to return a new context or something more complex. Contexts are meant to be thread safe, so this | 
|  | // object has a mutex to avoid problems when being used synchronously, although in practice using | 
|  | // this package in a multi-threaded fashion would likely lead to flaky tests. | 
|  | type fakeTestTracker struct { | 
|  | index         int | 
|  | fakeTestNames []string | 
|  | mutex         sync.Mutex | 
|  | } | 
|  |  | 
|  | // CommandContext looks for a special value on the provided context.Context (see WithFakeTests). | 
|  | // If that value exists, it will use the next fake test value and return a faked *exec.Cmd. It | 
|  | // panics if there are not enough fake tests that were provided to the original context. If the | 
|  | // special value does not exist, it is a passthrough to os/exec.CommandContext. | 
|  | func CommandContext(ctx context.Context, cmd string, args ...string) *exec.Cmd { | 
|  | if override, ok := ctx.Value(overrideKey).(*fakeTestTracker); ok { | 
|  | override.mutex.Lock() | 
|  | defer override.mutex.Unlock() | 
|  | // We are going to shell out to the current test executable... | 
|  | testBinary := os.Args[0] | 
|  | // ...and tell it to run the next faked test. | 
|  | if override.index >= len(override.fakeTestNames) { | 
|  | panic("Not enough fake tests provided") | 
|  | } | 
|  | fakeTest := override.fakeTestNames[override.index] | 
|  | override.index++ | 
|  | // fakeTest is where the client has put their fake implementation of the given command. | 
|  | argsWithOverride := []string{"-test.run=" + fakeTest, "--", cmd} | 
|  | argsWithOverride = append(argsWithOverride, args...) | 
|  | fakedCmd := exec.CommandContext(ctx, testBinary, argsWithOverride...) | 
|  | fakedCmd.Env = []string{OverrideEnvironmentVariable + "=1"} | 
|  | return fakedCmd | 
|  | } | 
|  | // Did not find special Context value, so fall back to default impl | 
|  | return exec.CommandContext(ctx, cmd, args...) | 
|  | } | 
|  |  | 
|  | // FakeCommandsReturned returns the count of how many times CommandContext was called using the | 
|  | // given context. This is a proxy for the number of fake commands run. | 
|  | func FakeCommandsReturned(ctx context.Context) int { | 
|  | if override, ok := ctx.Value(overrideKey).(*fakeTestTracker); ok { | 
|  | override.mutex.Lock() | 
|  | defer override.mutex.Unlock() | 
|  | return override.index | 
|  | } | 
|  | panic("A Context was passed in that was not produced by the executil package.") | 
|  | } | 
|  |  | 
|  | // OriginalArgs returns the original arguments passed into a test function. Concretely, it looks | 
|  | // at the osArgs and strips off the first 3 (the test binary, the test to run, and "--") | 
|  | func OriginalArgs() []string { | 
|  | return os.Args[3:] | 
|  | } | 
|  |  | 
|  | // IsCallingFakeCommand returns whether the current process is a test process that's running a | 
|  | // mocked-out CLI invocation. This should be called at the beginning of each Test_FakeExe_... test | 
|  | // and trigger an early return if false. | 
|  | func IsCallingFakeCommand() bool { | 
|  | return os.Getenv(OverrideEnvironmentVariable) != "" | 
|  | } |