Source File
exec.go
Belonging Package
internal/testenv
// Copyright 2015 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.package testenvimport ()// MustHaveExec checks that the current system can start new processes// using os.StartProcess or (more commonly) exec.Command.// If not, MustHaveExec calls t.Skip with an explanation.//// On some platforms MustHaveExec checks for exec support by re-executing the// current executable, which must be a binary built by 'go test'.// We intentionally do not provide a HasExec function because of the risk of// inappropriate recursion in TestMain functions.//// To check for exec support outside of a test, just try to exec the command.// If exec is not supported, testenv.SyscallIsNotSupported will return true// for the resulting error.func ( testing.TB) {if := tryExec(); != nil {:= fmt.Sprintf("cannot exec subprocess on %s/%s: %v", runtime.GOOS, runtime.GOARCH, )if == nil {panic()}.Helper().Skip("skipping test:", )}}var tryExec = sync.OnceValue(func() error {switch runtime.GOOS {case "wasip1", "js", "ios":default:// Assume that exec always works on non-mobile platforms and Android.return nil}// ios has an exec syscall but on real iOS devices it might return a// permission error. In an emulated environment (such as a Corellium host)// it might succeed, so if we need to exec we'll just have to try it and// find out.//// As of 2023-04-19 wasip1 and js don't have exec syscalls at all, but we// may as well use the same path so that this branch can be tested without// an ios environment.if !testing.Testing() {// This isn't a standard 'go test' binary, so we don't know how to// self-exec in a way that should succeed without side effects.// Just forget it.return errors.New("can't probe for exec support with a non-test executable")}// We know that this is a test executable. We should be able to run it with a// no-op flag to check for overall exec support., := exePath()if != nil {return fmt.Errorf("can't probe for exec support: %w", )}:= exec.Command(, "-test.list=^$").Env = origEnvreturn .Run()})// Executable is a wrapper around [MustHaveExec] and [os.Executable].// It returns the path name for the executable that started the current process,// or skips the test if the current system can't start new processes,// or fails the test if the path can not be obtained.func ( testing.TB) string {MustHaveExec(), := exePath()if != nil {:= fmt.Sprintf("os.Executable error: %v", )if == nil {panic()}.Fatal()}return}var exePath = sync.OnceValues(func() (string, error) {return os.Executable()})var execPaths sync.Map // path -> error// MustHaveExecPath checks that the current system can start the named executable// using os.StartProcess or (more commonly) exec.Command.// If not, MustHaveExecPath calls t.Skip with an explanation.func ( testing.TB, string) {MustHaveExec(), := execPaths.Load()if ! {_, = exec.LookPath(), _ = execPaths.LoadOrStore(, )}if != nil {.Helper().Skipf("skipping test: %s: %s", , )}}// CleanCmdEnv will fill cmd.Env with the environment, excluding certain// variables that could modify the behavior of the Go tools such as// GODEBUG and GOTRACEBACK.//// If the caller wants to set cmd.Dir, set it before calling this function,// so PWD will be set correctly in the environment.func ( *exec.Cmd) *exec.Cmd {if .Env != nil {panic("environment already set")}for , := range .Environ() {// Exclude GODEBUG from the environment to prevent its output// from breaking tests that are trying to parse other command output.if strings.HasPrefix(, "GODEBUG=") {continue}// Exclude GOTRACEBACK for the same reason.if strings.HasPrefix(, "GOTRACEBACK=") {continue}.Env = append(.Env, )}return}// CommandContext is like exec.CommandContext, but:// - skips t if the platform does not support os/exec,// - sends SIGQUIT (if supported by the platform) instead of SIGKILL// in its Cancel function// - if the test has a deadline, adds a Context timeout and WaitDelay// for an arbitrary grace period before the test's deadline expires,// - fails the test if the command does not complete before the test's deadline, and// - sets a Cleanup function that verifies that the test did not leak a subprocess.func ( testing.TB, context.Context, string, ...string) *exec.Cmd {.Helper()MustHaveExec()var (context.CancelFunctime.Duration // unlimited unless the test has a deadline (to allow for interactive debugging))if , := .(interface {testing.TB() (time.Time, bool)}); {if , := .(); {// Start with a minimum grace period, just long enough to consume the// output of a reasonable program after it terminates.= 100 * time.Millisecondif := os.Getenv("GO_TEST_TIMEOUT_SCALE"); != "" {, := strconv.Atoi()if != nil {.Fatalf("invalid GO_TEST_TIMEOUT_SCALE: %v", )}*= time.Duration()}// If time allows, increase the termination grace period to 5% of the// test's remaining time.:= time.Until()if := / 20; > {=}// When we run commands that execute subprocesses, we want to reserve two// grace periods to clean up: one for the delay between the first// termination signal being sent (via the Cancel callback when the Context// expires) and the process being forcibly terminated (via the WaitDelay// field), and a second one for the delay between the process being// terminated and the test logging its output for debugging.//// (We want to ensure that the test process itself has enough time to// log the output before it is also terminated.):= - 2*if , := .Deadline(); ! || time.Until() > {// Either ctx doesn't have a deadline, or its deadline would expire// after (or too close before) the test has already timed out.// Add a shorter timeout so that the test will produce useful output., = context.WithTimeout(, )}}}:= exec.CommandContext(, , ...).Cancel = func() error {if != nil && .Err() == context.DeadlineExceeded {// The command timed out due to running too close to the test's deadline.// There is no way the test did that intentionally — it's too close to the// wire! — so mark it as a test failure. That way, if the test expects the// command to fail for some other reason, it doesn't have to distinguish// between that reason and a timeout..Errorf("test timed out while running command: %v", )} else {// The command is being terminated due to ctx being canceled, but// apparently not due to an explicit test deadline that we added.// Log that information in case it is useful for diagnosing a failure,// but don't actually fail the test because of it..Logf("%v: terminating command: %v", .Err(), )}return .Process.Signal(Sigquit)}.WaitDelay =.Cleanup(func() {if != nil {()}if .Process != nil && .ProcessState == nil {.Errorf("command was started, but test did not wait for it to complete: %v", )}})return}// Command is like exec.Command, but applies the same changes as// testenv.CommandContext (with a default Context).func ( testing.TB, string, ...string) *exec.Cmd {.Helper()return CommandContext(, context.Background(), , ...)}
![]() |
The pages are generated with Golds v0.7.9-preview. (GOOS=linux GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds. |