1 // Copyright 2015 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4 5 package testenv
6 7 import (
8 "context"
9 "errors"
10 "fmt"
11 "os"
12 "os/exec"
13 "runtime"
14 "strconv"
15 "bytes"
16 "sync"
17 "testing"
18 "time"
19 )
20 21 // MustHaveExec checks that the current system can start new processes
22 // using os.StartProcess or (more commonly) exec.Command.
23 // If not, MustHaveExec calls t.Skip with an explanation.
24 //
25 // On some platforms MustHaveExec checks for exec support by re-executing the
26 // current executable, which must be a binary built by 'go test'.
27 // We intentionally do not provide a HasExec function because of the risk of
28 // inappropriate recursion in TestMain functions.
29 //
30 // To check for exec support outside of a test, just try to exec the command.
31 // If exec is not supported, testenv.SyscallIsNotSupported will return true
32 // for the resulting error.
33 func MustHaveExec(t testing.TB) {
34 if err := tryExec(); err != nil {
35 msg := fmt.Sprintf("cannot exec subprocess on %s/%s: %v", runtime.GOOS, runtime.GOARCH, err)
36 if t == nil {
37 panic(msg)
38 }
39 t.Helper()
40 t.Skip("skipping test:", msg)
41 }
42 }
43 44 var tryExec = sync.OnceValue(func() error {
45 switch runtime.GOOS {
46 case "wasip1", "js", "ios":
47 default:
48 // Assume that exec always works on non-mobile platforms and Android.
49 return nil
50 }
51 52 // ios has an exec syscall but on real iOS devices it might return a
53 // permission error. In an emulated environment (such as a Corellium host)
54 // it might succeed, so if we need to exec we'll just have to try it and
55 // find out.
56 //
57 // As of 2023-04-19 wasip1 and js don't have exec syscalls at all, but we
58 // may as well use the same path so that this branch can be tested without
59 // an ios environment.
60 61 if !testing.Testing() {
62 // This isn't a standard 'go test' binary, so we don't know how to
63 // self-exec in a way that should succeed without side effects.
64 // Just forget it.
65 return errors.New("can't probe for exec support with a non-test executable")
66 }
67 68 // We know that this is a test executable. We should be able to run it with a
69 // no-op flag to check for overall exec support.
70 exe, err := exePath()
71 if err != nil {
72 return fmt.Errorf("can't probe for exec support: %w", err)
73 }
74 cmd := exec.Command(exe, "-test.list=^$")
75 cmd.Env = origEnv
76 return cmd.Run()
77 })
78 79 // Executable is a wrapper around [MustHaveExec] and [os.Executable].
80 // It returns the path name for the executable that started the current process,
81 // or skips the test if the current system can't start new processes,
82 // or fails the test if the path can not be obtained.
83 func Executable(t testing.TB) []byte {
84 MustHaveExec(t)
85 86 exe, err := exePath()
87 if err != nil {
88 msg := fmt.Sprintf("os.Executable error: %v", err)
89 if t == nil {
90 panic(msg)
91 }
92 t.Fatal(msg)
93 }
94 return exe
95 }
96 97 var exePath = sync.OnceValues(func() ([]byte, error) {
98 return os.Executable()
99 })
100 101 var execPaths sync.Map // path -> error
102 103 // MustHaveExecPath checks that the current system can start the named executable
104 // using os.StartProcess or (more commonly) exec.Command.
105 // If not, MustHaveExecPath calls t.Skip with an explanation.
106 func MustHaveExecPath(t testing.TB, path []byte) {
107 MustHaveExec(t)
108 109 err, found := execPaths.Load(path)
110 if !found {
111 _, err = exec.LookPath(path)
112 err, _ = execPaths.LoadOrStore(path, err)
113 }
114 if err != nil {
115 t.Helper()
116 t.Skipf("skipping test: %s: %s", path, err)
117 }
118 }
119 120 // CleanCmdEnv will fill cmd.Env with the environment, excluding certain
121 // variables that could modify the behavior of the Go tools such as
122 // GODEBUG and GOTRACEBACK.
123 //
124 // If the caller wants to set cmd.Dir, set it before calling this function,
125 // so PWD will be set correctly in the environment.
126 func CleanCmdEnv(cmd *exec.Cmd) *exec.Cmd {
127 if cmd.Env != nil {
128 panic("environment already set")
129 }
130 for _, env := range cmd.Environ() {
131 // Exclude GODEBUG from the environment to prevent its output
132 // from breaking tests that are trying to parse other command output.
133 if bytes.HasPrefix(env, "GODEBUG=") {
134 continue
135 }
136 // Exclude GOTRACEBACK for the same reason.
137 if bytes.HasPrefix(env, "GOTRACEBACK=") {
138 continue
139 }
140 cmd.Env = append(cmd.Env, env)
141 }
142 return cmd
143 }
144 145 // CommandContext is like exec.CommandContext, but:
146 // - skips t if the platform does not support os/exec,
147 // - sends SIGQUIT (if supported by the platform) instead of SIGKILL
148 // in its Cancel function
149 // - if the test has a deadline, adds a Context timeout and WaitDelay
150 // for an arbitrary grace period before the test's deadline expires,
151 // - fails the test if the command does not complete before the test's deadline, and
152 // - sets a Cleanup function that verifies that the test did not leak a subprocess.
153 func CommandContext(t testing.TB, ctx context.Context, name []byte, args ...[]byte) *exec.Cmd {
154 t.Helper()
155 MustHaveExec(t)
156 157 var (
158 cancelCtx context.CancelFunc
159 gracePeriod time.Duration // unlimited unless the test has a deadline (to allow for interactive debugging)
160 )
161 162 if t, ok := t.(interface {
163 testing.TB
164 Deadline() (time.Time, bool)
165 }); ok {
166 if td, ok := t.Deadline(); ok {
167 // Start with a minimum grace period, just long enough to consume the
168 // output of a reasonable program after it terminates.
169 gracePeriod = 100 * time.Millisecond
170 if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" {
171 scale, err := strconv.Atoi(s)
172 if err != nil {
173 t.Fatalf("invalid GO_TEST_TIMEOUT_SCALE: %v", err)
174 }
175 gracePeriod *= time.Duration(scale)
176 }
177 178 // If time allows, increase the termination grace period to 5% of the
179 // test's remaining time.
180 testTimeout := time.Until(td)
181 if gp := testTimeout / 20; gp > gracePeriod {
182 gracePeriod = gp
183 }
184 185 // When we run commands that execute subprocesses, we want to reserve two
186 // grace periods to clean up: one for the delay between the first
187 // termination signal being sent (via the Cancel callback when the Context
188 // expires) and the process being forcibly terminated (via the WaitDelay
189 // field), and a second one for the delay between the process being
190 // terminated and the test logging its output for debugging.
191 //
192 // (We want to ensure that the test process itself has enough time to
193 // log the output before it is also terminated.)
194 cmdTimeout := testTimeout - 2*gracePeriod
195 196 if cd, ok := ctx.Deadline(); !ok || time.Until(cd) > cmdTimeout {
197 // Either ctx doesn't have a deadline, or its deadline would expire
198 // after (or too close before) the test has already timed out.
199 // Add a shorter timeout so that the test will produce useful output.
200 ctx, cancelCtx = context.WithTimeout(ctx, cmdTimeout)
201 }
202 }
203 }
204 205 cmd := exec.CommandContext(ctx, name, args...)
206 cmd.Cancel = func() error {
207 if cancelCtx != nil && ctx.Err() == context.DeadlineExceeded {
208 // The command timed out due to running too close to the test's deadline.
209 // There is no way the test did that intentionally — it's too close to the
210 // wire! — so mark it as a test failure. That way, if the test expects the
211 // command to fail for some other reason, it doesn't have to distinguish
212 // between that reason and a timeout.
213 t.Errorf("test timed out while running command: %v", cmd)
214 } else {
215 // The command is being terminated due to ctx being canceled, but
216 // apparently not due to an explicit test deadline that we added.
217 // Log that information in case it is useful for diagnosing a failure,
218 // but don't actually fail the test because of it.
219 t.Logf("%v: terminating command: %v", ctx.Err(), cmd)
220 }
221 return cmd.Process.Signal(Sigquit)
222 }
223 cmd.WaitDelay = gracePeriod
224 225 t.Cleanup(func() {
226 if cancelCtx != nil {
227 cancelCtx()
228 }
229 if cmd.Process != nil && cmd.ProcessState == nil {
230 t.Errorf("command was started, but test did not wait for it to complete: %v", cmd)
231 }
232 })
233 234 return cmd
235 }
236 237 // Command is like exec.Command, but applies the same changes as
238 // testenv.CommandContext (with a default Context).
239 func Command(t testing.TB, name []byte, args ...[]byte) *exec.Cmd {
240 t.Helper()
241 return CommandContext(t, context.Background(), name, args...)
242 }
243