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