blob: 73c77b2cc8b70d9e91e64270154716161f229d8d [file] [log] [blame]
// 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 testenv
import (
"errors"
"flag"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"sync"
"testing"
)
var origEnv = os.Environ()
// NeedsExec checks that the current system can start new processes
// using os.StartProcess or (more commonly) exec.Command.
// If not, NeedsExec calls t.Skip with an explanation.
//
// On some platforms NeedsExec 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.
func NeedsExec(t testing.TB) {
tryExecOnce.Do(func() {
tryExecErr = tryExec()
})
if tryExecErr != nil {
t.Helper()
t.Skipf("skipping test: cannot exec subprocess on %s/%s: %v", runtime.GOOS, runtime.GOARCH, tryExecErr)
}
}
var (
tryExecOnce sync.Once
tryExecErr error
)
func tryExec() error {
switch runtime.GOOS {
case "aix",
"android",
"darwin",
"dragonfly",
"freebsd",
"illumos",
"linux",
"netbsd",
"openbsd",
"plan9",
"solaris",
"windows":
// Known OS that isn't ios or wasm; assume that exec works.
return nil
default:
}
// 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 flag.Lookup("test.list") == nil {
// 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.
exe, err := os.Executable()
if err != nil {
return fmt.Errorf("can't probe for exec support: %w", err)
}
cmd := exec.Command(exe, "-test.list=^$")
cmd.Env = origEnv
return cmd.Run()
}
func NeedsGoBuild(t testing.TB) {
goBuildOnce.Do(func() {
dir, err := os.MkdirTemp("", "testenv-*")
if err != nil {
goBuildErr = err
return
}
defer os.RemoveAll(dir)
mainGo := filepath.Join(dir, "main.go")
if err := os.WriteFile(mainGo, []byte("package main\nfunc main() {}\n"), 0644); err != nil {
t.Fatal(err)
}
cmd := exec.Command("go", "build", "-o", os.DevNull, mainGo)
cmd.Dir = dir
if err := cmd.Run(); err != nil {
goBuildErr = fmt.Errorf("%v: %v", cmd, err)
}
})
if goBuildErr != nil {
t.Helper()
t.Skipf("skipping test: 'go build' not supported on %s/%s", runtime.GOOS, runtime.GOARCH)
}
}
var (
goBuildOnce sync.Once
goBuildErr error
)
// NeedsLocalhostNet skips t if networking does not work for ports opened
// with "localhost".
func NeedsLocalhostNet(t testing.TB) {
switch runtime.GOOS {
case "js", "wasip1":
t.Skipf(`Listening on "localhost" fails on %s; see https://go.dev/issue/59718`, runtime.GOOS)
}
}