Newer
Older
// Copyright 2012 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 testdir_test runs tests in the GOROOT/test directory.
package testdir_test
import (
"bytes"
"encoding/json"
"errors"
"flag"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"sort"
"strconv"
"strings"
)
var (
allCodegen = flag.Bool("all_codegen", defaultAllCodeGen(), "run all goos/goarch for codegen")
runSkips = flag.Bool("run_skips", false, "run skipped tests (ignore skip and build tags)")
linkshared = flag.Bool("linkshared", false, "")
updateErrors = flag.Bool("update_errors", false, "update error messages in test file based on compiler output")
runoutputLimit = flag.Int("l", defaultRunOutputLimit(), "number of parallel runoutput tests to run")
force = flag.Bool("f", false, "ignore expected-failure test lists")
target = flag.String("target", "", "cross-compile tests for `goos/goarch`")
shard = flag.Int("shard", 0, "shard index to run. Only applicable if -shards is non-zero.")
shards = flag.Int("shards", 0, "number of shards. If 0, all tests are run. This is used by the continuous build.")
// defaultAllCodeGen returns the default value of the -all_codegen
// flag. By default, we prefer to be fast (returning false), except on
// the linux-amd64 builder that's already very fast, so we get more
// test coverage on trybots. See https://go.dev/issue/34297.
func defaultAllCodeGen() bool {
return os.Getenv("GO_BUILDER_NAME") == "linux-amd64"
}
// Package-scoped variables that are initialized at the start of Test.
goTool string
goos string // Target GOOS
goarch string // Target GOARCH
cgoEnabled bool
goExperiment string
// dirs are the directories to look for *.go files in.
// TODO(bradfitz): just use all directories?
dirs = []string{".", "ken", "chan", "interface", "syntax", "dwarf", "fixedbugs", "codegen", "runtime", "abi", "typeparam", "typeparam/mdempsky", "arenas"}
// Test is the main entrypoint that runs tests in the GOROOT/test directory.
// Each .go file test case in GOROOT/test is registered as a subtest with
// a full name like "Test/fixedbugs/bug000.go" ('/'-separated relative path).
func Test(t *testing.T) {
if *target != "" {
// When -target is set, propagate it to GOOS/GOARCH in our environment
// so that all commands run with the target GOOS/GOARCH.
//
// We do this before even calling "go env", because GOOS/GOARCH can
// affect other settings we get from go env (notably CGO_ENABLED).
goos, goarch, ok := strings.Cut(*target, "/")
if !ok {
t.Fatalf("bad -target flag %q, expected goos/goarch", *target)
}
t.Setenv("GOOS", goos)
t.Setenv("GOARCH", goarch)
}
goTool = testenv.GoToolPath(t)
cmd := exec.Command(goTool, "env", "-json")
stdout, err := cmd.StdoutPipe()
if err != nil {
t.Fatal("StdoutPipe:", err)
}
if err := cmd.Start(); err != nil {
t.Fatal("Start:", err)
}
var env struct {
GOOS string
GOARCH string
GOEXPERIMENT string
CGO_ENABLED string
}
if err := json.NewDecoder(stdout).Decode(&env); err != nil {
t.Fatal("Decode:", err)
}
if err := cmd.Wait(); err != nil {
t.Fatal("Wait:", err)
goos = env.GOOS
goarch = env.GOARCH
cgoEnabled, _ = strconv.ParseBool(env.CGO_ENABLED)
goExperiment = env.GOEXPERIMENT
common := testCommon{
gorootTestDir: filepath.Join(testenv.GOROOT(t), "test"),
runoutputGate: make(chan bool, *runoutputLimit),
}
// cmd/distpack deletes GOROOT/test, so skip the test if it isn't present.
// cmd/distpack also requires GOROOT/VERSION to exist, so use that to
// suppress false-positive skips.
if _, err := os.Stat(common.gorootTestDir); os.IsNotExist(err) {
if _, err := os.Stat(filepath.Join(testenv.GOROOT(t), "VERSION")); err == nil {
t.Skipf("skipping: GOROOT/test not present")
}
}
for _, dir := range dirs {
for _, goFile := range goFiles(t, dir) {
test := test{testCommon: common, dir: dir, goFile: goFile}
t.Run(path.Join(dir, goFile), func(t *testing.T) {
t.Parallel()
test.T = t
testError := test.run()
wantError := test.expectFail() && !*force
if testError != nil {
if wantError {
t.Log(testError.Error() + " (expected)")
t.Fatal("unexpected success")
}
})
}
}
}
func shardMatch(name string) bool {
if *shards <= 1 {
return true
}
h := fnv.New32()
io.WriteString(h, name)
return int(h.Sum32()%uint32(*shards)) == *shard
}
func goFiles(t *testing.T, dir string) []string {
f, err := os.Open(filepath.Join(testenv.GOROOT(t), "test", dir))
dirnames, err := f.Readdirnames(-1)
names := []string{}
for _, name := range dirnames {
if !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") && shardMatch(name) {
names = append(names, name)
}
}
sort.Strings(names)
return names
}
Daniel Morsing
committed
type runCmd func(...string) ([]byte, error)
func compileFile(runcmd runCmd, longname string, flags []string) (out []byte, err error) {
cmd := []string{goTool, "tool", "compile", "-e", "-p=p", "-importcfg=" + stdlibImportcfgFile()}
cmd = append(cmd, flags...)
if *linkshared {
cmd = append(cmd, "-dynlink", "-installsuffix=dynlink")
}
cmd = append(cmd, longname)
return runcmd(cmd...)
Daniel Morsing
committed
}
func compileInDir(runcmd runCmd, dir string, flags []string, importcfg string, pkgname string, names ...string) (out []byte, err error) {
if importcfg == "" {
importcfg = stdlibImportcfgFile()
}
cmd := []string{goTool, "tool", "compile", "-e", "-D", "test", "-importcfg=" + importcfg}
if pkgname == "main" {
cmd = append(cmd, "-p=main")
} else {
pkgname = path.Join("test", strings.TrimSuffix(names[0], ".go"))
cmd = append(cmd, "-o", pkgname+".a", "-p", pkgname)
cmd = append(cmd, flags...)
if *linkshared {
cmd = append(cmd, "-dynlink", "-installsuffix=dynlink")
}
for _, name := range names {
cmd = append(cmd, filepath.Join(dir, name))
}
return runcmd(cmd...)
Daniel Morsing
committed
}
var stdlibImportcfgStringOnce sync.Once // TODO(#56102): Use sync.OnceValue once available. Also below.
var stdlibImportcfgString string
func stdlibImportcfg() string {
stdlibImportcfgStringOnce.Do(func() {
khr@golang.org
committed
cmd := exec.Command(goTool, "list", "-export", "-f", "{{if .Export}}packagefile {{.ImportPath}}={{.Export}}{{end}}", "std")
cmd.Env = append(os.Environ(), "GOENV=off", "GOFLAGS=")
output, err := cmd.Output()
if err != nil {
log.Fatal(err)
}
stdlibImportcfgString = string(output)
})
return stdlibImportcfgString
}
var stdlibImportcfgFilenameOnce sync.Once
var stdlibImportcfgFilename string
func stdlibImportcfgFile() string {
stdlibImportcfgFilenameOnce.Do(func() {
tmpdir, err := os.MkdirTemp("", "importcfg")
if err != nil {
log.Fatal(err)
}
filename := filepath.Join(tmpdir, "importcfg")
err = os.WriteFile(filename, []byte(stdlibImportcfg()), 0644)
if err != nil {
log.Fatal(err)
}
stdlibImportcfgFilename = filename
})
return stdlibImportcfgFilename
}
func linkFile(runcmd runCmd, goname string, importcfg string, ldflags []string) (err error) {
if importcfg == "" {
importcfg = stdlibImportcfgFile()
}
pfile := strings.Replace(goname, ".go", ".o", -1)
cmd := []string{goTool, "tool", "link", "-w", "-o", "a.exe", "-importcfg=" + importcfg}
if *linkshared {
cmd = append(cmd, "-linkshared", "-installsuffix=dynlink")
}
if ldflags != nil {
cmd = append(cmd, ldflags...)
}
cmd = append(cmd, pfile)
_, err = runcmd(cmd...)
Daniel Morsing
committed
return
}
type testCommon struct {
// gorootTestDir is the GOROOT/test directory path.
gorootTestDir string
// runoutputGate controls the max number of runoutput tests
// executed in parallel as they can each consume a lot of memory.
runoutputGate chan bool
}
// test is a single test case in the GOROOT/test directory.
type test struct {
*testing.T
// dir and goFile identify the test case.
// For example, "fixedbugs", "bug000.go".
dir, goFile string
// expectFail reports whether the (overall) test recipe is
// expected to fail under the current build+test configuration.
func (t test) expectFail() bool {
failureSets := []map[string]bool{types2Failures}
// Note: gccgo supports more 32-bit architectures than this, but
// hopefully the 32-bit failures are fixed before this matters.
switch goarch {
case "386", "arm", "mips", "mipsle":
failureSets = append(failureSets, types2Failures32Bit)
}
testName := path.Join(t.dir, t.goFile) // Test name is '/'-separated.
for _, set := range failureSets {
if set[testName] {
return true
func (t test) goFileName() string {
return filepath.Join(t.dir, t.goFile)
func (t test) goDirName() string {
return filepath.Join(t.dir, strings.Replace(t.goFile, ".go", ".dir", -1))
// goDirFiles returns .go files in dir.
func goDirFiles(dir string) (filter []fs.DirEntry, _ error) {
files, err := os.ReadDir(dir)
if err != nil {
return nil, err
Daniel Morsing
committed
}
for _, goFile := range files {
if filepath.Ext(goFile.Name()) == ".go" {
filter = append(filter, goFile)
Daniel Morsing
committed
}
}
Daniel Morsing
committed
}
var packageRE = regexp.MustCompile(`(?m)^package ([\p{Lu}\p{Ll}\w]+)`)
func getPackageNameFromSource(fn string) (string, error) {
if err != nil {
return "", err
}
pkgname := packageRE.FindStringSubmatch(string(data))
if pkgname == nil {
return "", fmt.Errorf("cannot find package name in %s", fn)
}
return pkgname[1], nil
}
// goDirPkg represents a Go package in some directory.
type goDirPkg struct {
name string
files []string
}
// goDirPackages returns distinct Go packages in dir.
// If singlefilepkgs is set, each file is considered a separate package
// even if the package names are the same.
func goDirPackages(t *testing.T, dir string, singlefilepkgs bool) []*goDirPkg {
files, err := goDirFiles(dir)
if err != nil {
var pkgs []*goDirPkg
m := make(map[string]*goDirPkg)
for _, file := range files {
name := file.Name()
pkgname, err := getPackageNameFromSource(filepath.Join(dir, name))
if singlefilepkgs || !ok {
p = &goDirPkg{name: pkgname}
pkgs = append(pkgs, p)
m[pkgname] = p
GOOS string
GOARCH string
cgoEnabled bool
noOptEnv bool
// shouldTest looks for build tags in a source file and returns
// whether the file should be used according to the tags.
func shouldTest(src string, goos, goarch string) (ok bool, whyNot string) {
if *runSkips {
return true, ""
}
for _, line := range strings.Split(src, "\n") {
if strings.HasPrefix(line, "package ") {
break
if expr, err := constraint.Parse(line); err == nil {
gcFlags := os.Getenv("GO_GCFLAGS")
ctxt := &context{
GOOS: goos,
GOARCH: goarch,
cgoEnabled: cgoEnabled,
noOptEnv: strings.Contains(gcFlags, "-N") || strings.Contains(gcFlags, "-l"),
if !expr.Eval(ctxt.match) {
}
}
return true, ""
}
func (ctxt *context) match(name string) bool {
if name == "" {
return false
}
// Tags must be letters, digits, underscores or dots.
// Unlike in Go identifiers, all digits are fine (e.g., "386").
for _, c := range name {
if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
return false
}
}
if slices.Contains(build.Default.ReleaseTags, name) {
return true
}
Austin Clements
committed
if strings.HasPrefix(name, "goexperiment.") {
return slices.Contains(build.Default.ToolTags, name)
if name == "cgo" && ctxt.cgoEnabled {
return true
}
if name == ctxt.GOOS || name == ctxt.GOARCH || name == "gc" {
if ctxt.noOptEnv && name == "gcflags_noopt" {
return true
}
if name == "test_run" {
return true
}
// goGcflags returns the -gcflags argument to use with go build / go run.
// This must match the flags used for building the standard library,
// or else the commands will rebuild any needed packages (like runtime)
// over and over.
func (test) goGcflags() string {
return "-gcflags=all=" + os.Getenv("GO_GCFLAGS")
func (test) goGcflagsIsEmpty() bool {
return "" == os.Getenv("GO_GCFLAGS")
var errTimeout = errors.New("command exceeded time limit")
// run runs the test case.
//
// When there is a problem, run uses t.Fatal to signify that it's an unskippable
// infrastructure error (such as failing to read an input file or the test recipe
// being malformed), or it returns a non-nil error to signify a test case error.
//
// t.Error isn't used here to give the caller the opportunity to decide whether
// the test case failing is expected before promoting it to a real test failure.
// See expectFail and -f flag.
func (t test) run() error {
srcBytes, err := os.ReadFile(filepath.Join(t.gorootTestDir, t.goFileName()))
if err != nil {
t.Fatal("reading test case .go file:", err)
} else if bytes.HasPrefix(srcBytes, []byte{'\n'}) {
t.Fatal(".go file source starts with a newline")
src := string(srcBytes)
// Execution recipe is contained in a comment in
// the first non-empty line that is not a build constraint.
var action string
for actionSrc := src; action == "" && actionSrc != ""; {
var line string
line, actionSrc, _ = strings.Cut(actionSrc, "\n")
if constraint.IsGoBuild(line) || constraint.IsPlusBuild(line) {
continue
}
action = strings.TrimSpace(strings.TrimPrefix(line, "//"))
if action == "" {
t.Fatalf("execution recipe not found in GOROOT/test/%s", t.goFileName())
// Check for build constraints only up to the actual code.
header, _, ok := strings.Cut(src, "\npackage")
if !ok {
header = action // some files are intentionally malformed
if ok, why := shouldTest(header, goos, goarch); !ok {
t.Skip(why)
var args, flags, runenv []string
wantError := false
singlefilepkgs := false
f, err := splitQuoted(action)
if err != nil {
t.Fatal("invalid test recipe:", err)
if len(f) > 0 {
action = f[0]
args = f[1:]
}
// TODO: Clean up/simplify this switch statement.
switch action {
case "compile", "compiledir", "build", "builddir", "buildrundir", "run", "buildrun", "runoutput", "rundir", "runindir", "asmcheck":
case "errorcheckandrundir":
wantError = false // should be no error if also will run
case "errorcheckwithauto":
action = "errorcheck"
wantAuto = true
wantError = true
case "errorcheck", "errorcheckdir", "errorcheckoutput":
if *runSkips {
break
}
t.Skip("skip")
t.Fatalf("unknown pattern: %q", action)
// collect flags
for len(args) > 0 && strings.HasPrefix(args[0], "-") {
switch args[0] {
case "-1":
wantError = true
case "-0":
wantError = false
case "-s":
singlefilepkgs = true
case "-t": // timeout in seconds
args = args[1:]
var err error
tim, err = strconv.Atoi(args[0])
if err != nil {
t.Fatalf("need number of seconds for -t timeout, got %s instead", args[0])
if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" {
timeoutScale, err := strconv.Atoi(s)
if err != nil {
t.Fatalf("failed to parse $GO_TEST_TIMEOUT_SCALE = %q as integer: %v", s, err)
}
tim *= timeoutScale
}
case "-goexperiment": // set GOEXPERIMENT environment
args = args[1:]
if goexp != "" {
goexp += ","
}
goexp += args[0]
runenv = append(runenv, "GOEXPERIMENT="+goexp)
case "-godebug": // set GODEBUG environment
args = args[1:]
if godebug != "" {
godebug += ","
}
godebug += args[0]
runenv = append(runenv, "GODEBUG="+godebug)
case "-gomodversion": // set the GoVersion in generated go.mod files (just runindir ATM)
args = args[1:]
gomodvers = args[0]
default:
flags = append(flags, args[0])
}
args = args[1:]
}
if action == "errorcheck" {
found := false
for i, f := range flags {
if strings.HasPrefix(f, "-d=") {
flags[i] = f + ",ssa/check/on"
found = true
break
}
}
if !found {
flags = append(flags, "-d=ssa/check/on")
}
}
tempDir := t.TempDir()
err = os.Mkdir(filepath.Join(tempDir, "test"), 0755)
if err != nil {
t.Fatal(err)
err = os.WriteFile(filepath.Join(tempDir, t.goFile), srcBytes, 0644)
var (
tempDirIsGOPATH = false
)
runcmd := func(args ...string) ([]byte, error) {
cmd := exec.Command(args[0], args[1:]...)
var buf bytes.Buffer
cmd.Stdout = &buf
cmd.Stderr = &buf
cmd.Env = append(os.Environ(), "GOENV=off", "GOFLAGS=")
if runInDir != "" {
cmd.Dir = runInDir
// Set PWD to match Dir to speed up os.Getwd in the child process.
cmd.Env = append(cmd.Env, "PWD="+cmd.Dir)
} else {
// Default to running in the GOROOT/test directory.
cmd.Dir = t.gorootTestDir
// Set PWD to match Dir to speed up os.Getwd in the child process.
cmd.Env = append(cmd.Env, "PWD="+cmd.Dir)
if tempDirIsGOPATH {
cmd.Env = append(cmd.Env, "GOPATH="+tempDir)
cmd.Env = append(cmd.Env, "STDLIB_IMPORTCFG="+stdlibImportcfgFile())
cmd.Env = append(cmd.Env, runenv...)
var err error
if tim != 0 {
err = cmd.Start()
// This command-timeout code adapted from cmd/go/test.go
// Note: the Go command uses a more sophisticated timeout
// strategy, first sending SIGQUIT (if appropriate for the
// OS in question) to try to trigger a stack trace, then
// finally much later SIGKILL. If timeouts prove to be a
// common problem here, it would be worth porting over
// that code as well. See https://do.dev/issue/50973
// for more discussion.
if err == nil {
tick := time.NewTimer(time.Duration(tim) * time.Second)
done := make(chan error)
go func() {
done <- cmd.Wait()
}()
select {
case err = <-done:
// ok
case <-tick.C:
cmd.Process.Signal(os.Interrupt)
time.Sleep(1 * time.Second)
cmd.Process.Kill()
}
tick.Stop()
}
} else {
err = cmd.Run()
}
if err != nil && err != errTimeout {
Daniel Morsing
committed
err = fmt.Errorf("%s\n%s", err, buf.Bytes())
}
importcfg := func(pkgs []*goDirPkg) string {
cfg := stdlibImportcfg()
for _, pkg := range pkgs {
pkgpath := path.Join("test", strings.TrimSuffix(pkg.files[0], ".go"))
cfg += "\npackagefile " + pkgpath + "=" + filepath.Join(tempDir, pkgpath+".a")
}
filename := filepath.Join(tempDir, "importcfg")
err := os.WriteFile(filename, []byte(cfg), 0644)
if err != nil {
t.Fatal(err)
}
return filename
}
long := filepath.Join(t.gorootTestDir, t.goFileName())
t.Fatalf("unimplemented action %q", action)
panic("unreachable")
// Compile Go file and match the generated assembly
// against a set of regexps in comments.
ops := t.wantedAsmOpcodes(long)
self := runtime.GOOS + "/" + runtime.GOARCH
for _, env := range ops.Envs() {
// Only run checks relevant to the current GOOS/GOARCH,
// to avoid triggering a cross-compile of the runtime.
if string(env) != self && !strings.HasPrefix(string(env), self+"/") && !*allCodegen {
continue
}
// -S=2 forces outermost line numbers when disassembling inlined code.
cmdline := []string{"build", "-gcflags", "-S=2"}
// Append flags, but don't override -gcflags=-S=2; add to it instead.
for i := 0; i < len(flags); i++ {
flag := flags[i]
switch {
case strings.HasPrefix(flag, "-gcflags="):
cmdline[2] += " " + strings.TrimPrefix(flag, "-gcflags=")
case strings.HasPrefix(flag, "--gcflags="):
cmdline[2] += " " + strings.TrimPrefix(flag, "--gcflags=")
case flag == "-gcflags", flag == "--gcflags":
i++
if i < len(flags) {
cmdline[2] += " " + flags[i]
}
default:
cmdline = append(cmdline, flag)
}
}
cmdline = append(cmdline, long)
cmd := exec.Command(goTool, cmdline...)
cmd.Env = append(os.Environ(), env.Environ()...)
if len(flags) > 0 && flags[0] == "-race" {
cmd.Env = append(cmd.Env, "CGO_ENABLED=1")
}
var buf bytes.Buffer
cmd.Stdout, cmd.Stderr = &buf, &buf
if err := cmd.Run(); err != nil {
t.Log(env, "\n", cmd.Stderr)
return err
err := t.asmCheck(buf.String(), long, env, ops[env])
if err != nil {
return err
// Compile Go file.
// Fail if wantError is true and compilation was successful and vice versa.
// Match errors produced by gc against errors in comments.
// TODO(gri) remove need for -C (disable printing of columns in error messages)
cmdline := []string{goTool, "tool", "compile", "-p=p", "-d=panic", "-C", "-e", "-importcfg=" + stdlibImportcfgFile(), "-o", "a.o"}
// No need to add -dynlink even if linkshared if we're just checking for errors...
cmdline = append(cmdline, flags...)
cmdline = append(cmdline, long)
out, err := runcmd(cmdline...)
if wantError {
if err == nil {
return fmt.Errorf("compilation succeeded unexpectedly\n%s", out)
return fmt.Errorf("compilation timed out")
} else {
if err != nil {
if *updateErrors {
t.updateErrors(string(out), long)
}
return t.errorCheck(string(out), wantAuto, long, t.goFile)
Robert Griesemer
committed
_, err := compileFile(runcmd, long, flags)
return err
// Compile all files in the directory as packages in lexicographic order.
longdir := filepath.Join(t.gorootTestDir, t.goDirName())
pkgs := goDirPackages(t.T, longdir, singlefilepkgs)
importcfgfile := importcfg(pkgs)
_, err := compileInDir(runcmd, longdir, flags, importcfgfile, pkg.name, pkg.files...)
if err != nil {
return err
Daniel Morsing
committed
}
Daniel Morsing
committed
case "errorcheckdir", "errorcheckandrundir":
flags = append(flags, "-d=panic")
// Compile and errorCheck all files in the directory as packages in lexicographic order.
// If errorcheckdir and wantError, compilation of the last package must fail.
// If errorcheckandrundir and wantError, compilation of the package prior the last must fail.
longdir := filepath.Join(t.gorootTestDir, t.goDirName())
pkgs := goDirPackages(t.T, longdir, singlefilepkgs)
errPkg := len(pkgs) - 1
if wantError && action == "errorcheckandrundir" {
// The last pkg should compiled successfully and will be run in next case.
// Preceding pkg must return an error from compileInDir.
errPkg--
}
importcfgfile := importcfg(pkgs)
out, err := compileInDir(runcmd, longdir, flags, importcfgfile, pkg.name, pkg.files...)
Daniel Morsing
committed
if wantError && err == nil {
return fmt.Errorf("compilation succeeded unexpectedly\n%s", out)
Daniel Morsing
committed
} else if !wantError && err != nil {
Daniel Morsing
committed
}
} else if err != nil {
Daniel Morsing
committed
}
var fullshort []string
fullshort = append(fullshort, filepath.Join(longdir, name), name)
}
err = t.errorCheck(string(out), wantAuto, fullshort...)
if err != nil {
return err
if action == "errorcheckdir" {
Daniel Morsing
committed
case "rundir":
// Compile all files in the directory as packages in lexicographic order.
// In case of errorcheckandrundir, ignore failed compilation of the package before the last.
// Link as if the last file is the main package, run it.
// Verify the expected output.
longdir := filepath.Join(t.gorootTestDir, t.goDirName())
pkgs := goDirPackages(t.T, longdir, singlefilepkgs)
// Split flags into gcflags and ldflags
ldflags := []string{}
for i, fl := range flags {
if fl == "-ldflags" {
ldflags = flags[i+1:]
flags = flags[0:i]
break
}
}
importcfgfile := importcfg(pkgs)
_, err := compileInDir(runcmd, longdir, flags, importcfgfile, pkg.name, pkg.files...)
// Allow this package compilation fail based on conditions below;
// its errors were checked in previous case.
if err != nil && !(wantError && action == "errorcheckandrundir" && i == len(pkgs)-2) {
Daniel Morsing
committed
}
if i == len(pkgs)-1 {
err = linkFile(runcmd, pkg.files[0], importcfgfile, ldflags)
var cmd []string
cmd = append(cmd, findExecCmd()...)
cmd = append(cmd, filepath.Join(tempDir, "a.exe"))
cmd = append(cmd, args...)
out, err := runcmd(cmd...)
t.checkExpectedOutput(out)
Daniel Morsing
committed
}
Daniel Morsing
committed
// Make a shallow copy of t.goDirName() in its own module and GOPATH, and
// run "go run ." in it. The module path (and hence import path prefix) of
// the copy is equal to the basename of the source directory.
//
// It's used when test a requires a full 'go build' in order to compile
// the sources, such as when importing multiple packages (issue29612.dir)
// or compiling a package containing assembly files (see issue15609.dir),
// but still needs to be run to verify the expected output.
tempDirIsGOPATH = true
srcDir := filepath.Join(t.gorootTestDir, t.goDirName())
modName := filepath.Base(srcDir)
gopathSrcDir := filepath.Join(tempDir, "src", modName)
runInDir = gopathSrcDir
if err := overlayDir(gopathSrcDir, srcDir); err != nil {
}
modVersion := gomodvers
if modVersion == "" {
modVersion = "1.14"
}
modFile := fmt.Sprintf("module %s\ngo %s\n", modName, modVersion)
if err := os.WriteFile(filepath.Join(gopathSrcDir, "go.mod"), []byte(modFile), 0666); err != nil {
t.Fatal(err)
}
cmd := []string{goTool, "run", t.goGcflags()}
if *linkshared {
cmd = append(cmd, "-linkshared")
}
cmd = append(cmd, flags...)
cmd = append(cmd, ".")
out, err := runcmd(cmd...)
if err != nil {
return t.checkExpectedOutput(out)
cmd := []string{goTool, "build", t.goGcflags()}
cmd = append(cmd, flags...)
cmd = append(cmd, "-o", "a.exe", long)
_, err := runcmd(cmd...)
// Build an executable from all the .go and .s files in a subdirectory.
// Run it and verify its output in the buildrundir case.
longdir := filepath.Join(t.gorootTestDir, t.goDirName())
files, err := os.ReadDir(longdir)
if err != nil {
t.Fatal(err)
var gos []string
var asms []string
for _, file := range files {
switch filepath.Ext(file.Name()) {
case ".go":
gos = append(gos, filepath.Join(longdir, file.Name()))
asms = append(asms, filepath.Join(longdir, file.Name()))
if len(asms) > 0 {
emptyHdrFile := filepath.Join(tempDir, "go_asm.h")
if err := os.WriteFile(emptyHdrFile, nil, 0666); err != nil {
t.Fatalf("write empty go_asm.h: %v", err)
cmd := []string{goTool, "tool", "asm", "-p=main", "-gensymabis", "-o", "symabis"}
cmd = append(cmd, asms...)
_, err = runcmd(cmd...)
if err != nil {
cmd := []string{goTool, "tool", "compile", "-p=main", "-e", "-D", ".", "-importcfg=" + stdlibImportcfgFile(), "-o", "go.o"}
cmd = append(cmd, "-asmhdr", "go_asm.h", "-symabis", "symabis")
}
objs = append(objs, "go.o")
if len(asms) > 0 {
cmd = []string{goTool, "tool", "asm", "-p=main", "-e", "-I", ".", "-o", "asm.o"}
_, err = runcmd(cmd...)
if err != nil {
}
objs = append(objs, "asm.o")
}
cmd = []string{goTool, "tool", "pack", "c", "all.a"}
cmd = append(cmd, objs...)
_, err = runcmd(cmd...)
if err != nil {
cmd = []string{goTool, "tool", "link", "-importcfg=" + stdlibImportcfgFile(), "-o", "a.exe", "all.a"}
_, err = runcmd(cmd...)
if err != nil {
if action == "builddir" {