"internal/testenv"
"io"
"io/ioutil"
+ "log"
"os"
"os/exec"
"path/filepath"
var testCC string
var testGOCACHE string
+var testGo string
+var testTmpDir string
+
// The TestMain function creates a go command for testing purposes and
// deletes it after the tests have been run.
func TestMain(m *testing.M) {
select {}
}
+ dir, err := ioutil.TempDir(os.Getenv("GOTMPDIR"), "cmd-go-test-")
+ if err != nil {
+ log.Fatal(err)
+ }
+ testTmpDir = dir
+ if !*testWork {
+ defer removeAll(testTmpDir)
+ }
+
if canRun {
- args := []string{"build", "-tags", "testgo", "-o", "testgo" + exeSuffix}
+ testGo = filepath.Join(testTmpDir, "testgo"+exeSuffix)
+ args := []string{"build", "-tags", "testgo", "-o", testGo}
if race.Enabled {
args = append(args, "-race")
}
}
testCC = strings.TrimSpace(string(out))
- if out, err := exec.Command("./testgo"+exeSuffix, "env", "CGO_ENABLED").Output(); err != nil {
+ if out, err := exec.Command(testGo, "env", "CGO_ENABLED").Output(); err != nil {
fmt.Fprintf(os.Stderr, "running testgo failed: %v\n", err)
canRun = false
} else {
}
r := m.Run()
-
- if canRun {
- os.Remove("testgo" + exeSuffix)
+ if !*testWork {
+ removeAll(testTmpDir) // os.Exit won't run defer
}
os.Exit(r)
ran bool
inParallel bool
stdout, stderr bytes.Buffer
+ execDir string // dir for tg.run
}
// skipIfGccgo skips the test if using gccgo.
}
func (tg *testgoData) goTool() string {
- if tg.wd == "" {
- return "./testgo" + exeSuffix
- }
- return filepath.Join(tg.wd, "testgo"+exeSuffix)
+ return testGo
}
// doRun runs the test go command, recording stdout and stderr and
cmd := exec.Command(prog, args...)
tg.stdout.Reset()
tg.stderr.Reset()
+ cmd.Dir = tg.execDir
cmd.Stdout = &tg.stdout
cmd.Stderr = &tg.stderr
cmd.Env = tg.env
tg.setenv("PATH", fmt.Sprintf("%v%c%v", fail, filepath.ListSeparator, os.Getenv("PATH")))
}
-func TestBuildComplex(t *testing.T) {
- // Simple smoke test for build configuration.
- tg := testgo(t)
- defer tg.cleanup()
- tg.parallel()
- tg.setenv("GOPATH", filepath.Join(tg.pwd(), "testdata"))
- tg.run("build", "-x", "-o", os.DevNull, "complex")
-
- if _, err := exec.LookPath("gccgo"); err == nil {
- tg.run("build", "-x", "-o", os.DevNull, "-compiler=gccgo", "complex")
- }
-}
-
-func TestFileLineInErrorMessages(t *testing.T) {
- tg := testgo(t)
- defer tg.cleanup()
- tg.parallel()
- tg.tempFile("err.go", `package main; import "bar"`)
- path := tg.path("err.go")
- tg.runFail("run", path)
- shortPath := path
- if rel, err := filepath.Rel(tg.pwd(), path); err == nil && len(rel) < len(path) {
- shortPath = rel
- }
- tg.grepStderr("^"+regexp.QuoteMeta(shortPath)+":", "missing file:line in error message")
-}
-
-func TestProgramNameInCrashMessages(t *testing.T) {
- skipIfGccgo(t, "gccgo does not use cmd/link")
- tg := testgo(t)
- defer tg.cleanup()
- tg.parallel()
- tg.tempFile("triv.go", `package main; func main() {}`)
- tg.runFail("build", "-ldflags", "-crash_for_testing", tg.path("triv.go"))
- tg.grepStderr(`[/\\]tool[/\\].*[/\\]link`, "missing linker name in error message")
-}
-
-func TestBrokenTestsWithoutTestFunctionsAllFail(t *testing.T) {
- tg := testgo(t)
- defer tg.cleanup()
- // TODO: tg.parallel()
- tg.runFail("test", "./testdata/src/badtest/...")
- tg.grepBothNot("^ok", "test passed unexpectedly")
- tg.grepBoth("FAIL.*badtest/badexec", "test did not run everything")
- tg.grepBoth("FAIL.*badtest/badsyntax", "test did not run everything")
- tg.grepBoth("FAIL.*badtest/badvar", "test did not run everything")
-}
-
func TestNewReleaseRebuildsStalePackagesInGOPATH(t *testing.T) {
if testing.Short() {
t.Skip("don't rebuild the standard library in short mode")
tg.run("install", "std")
}
-func TestGoListStandard(t *testing.T) {
- skipIfGccgo(t, "gccgo does not have GOROOT")
- tooSlow(t)
- tg := testgo(t)
- defer tg.cleanup()
- // TODO: tg.parallel()
- tg.cd(runtime.GOROOT() + "/src")
- tg.run("list", "-f", "{{if not .Standard}}{{.ImportPath}}{{end}}", "./...")
- stdout := tg.getStdout()
- for _, line := range strings.Split(stdout, "\n") {
- if strings.HasPrefix(line, "_/") && strings.HasSuffix(line, "/src") {
- // $GOROOT/src shows up if there are any .go files there.
- // We don't care.
- continue
- }
- if line == "" {
- continue
- }
- t.Errorf("package in GOROOT not listed as standard: %v", line)
- }
-
- // Similarly, expanding std should include some of our vendored code.
- tg.run("list", "std", "cmd")
- tg.grepStdout("golang.org/x/net/http2/hpack", "list std cmd did not mention vendored hpack")
- tg.grepStdout("golang.org/x/arch/x86/x86asm", "list std cmd did not mention vendored x86asm")
-}
-
-func TestGoInstallCleansUpAfterGoBuild(t *testing.T) {
- tooSlow(t)
- tg := testgo(t)
- defer tg.cleanup()
- // TODO: tg.parallel()
- tg.tempFile("src/mycmd/main.go", `package main; func main(){}`)
- tg.setenv("GOPATH", tg.path("."))
- tg.cd(tg.path("src/mycmd"))
-
- doesNotExist := func(file, msg string) {
- if _, err := os.Stat(file); err == nil {
- t.Fatal(msg)
- } else if !os.IsNotExist(err) {
- t.Fatal(msg, "error:", err)
- }
- }
-
- tg.run("build")
- tg.wantExecutable("mycmd"+exeSuffix, "testgo build did not write command binary")
- tg.run("install")
- doesNotExist("mycmd"+exeSuffix, "testgo install did not remove command binary")
- tg.run("build")
- tg.wantExecutable("mycmd"+exeSuffix, "testgo build did not write command binary (second time)")
- // Running install with arguments does not remove the target,
- // even in the same directory.
- tg.run("install", "mycmd")
- tg.wantExecutable("mycmd"+exeSuffix, "testgo install mycmd removed command binary when run in mycmd")
- tg.run("build")
- tg.wantExecutable("mycmd"+exeSuffix, "testgo build did not write command binary (third time)")
- // And especially not outside the directory.
- tg.cd(tg.path("."))
- if data, err := ioutil.ReadFile("src/mycmd/mycmd" + exeSuffix); err != nil {
- t.Fatal("could not read file:", err)
- } else {
- if err := ioutil.WriteFile("mycmd"+exeSuffix, data, 0555); err != nil {
- t.Fatal("could not write file:", err)
- }
- }
- tg.run("install", "mycmd")
- tg.wantExecutable("src/mycmd/mycmd"+exeSuffix, "testgo install mycmd removed command binary from its source dir when run outside mycmd")
- tg.wantExecutable("mycmd"+exeSuffix, "testgo install mycmd removed command binary from current dir when run outside mycmd")
-}
-
-func TestGoInstallRebuildsStalePackagesInOtherGOPATH(t *testing.T) {
- tooSlow(t)
- tg := testgo(t)
- defer tg.cleanup()
- tg.parallel()
- tg.tempFile("d1/src/p1/p1.go", `package p1
- import "p2"
- func F() { p2.F() }`)
- tg.tempFile("d2/src/p2/p2.go", `package p2
- func F() {}`)
- sep := string(filepath.ListSeparator)
- tg.setenv("GOPATH", tg.path("d1")+sep+tg.path("d2"))
- tg.run("install", "-i", "p1")
- tg.wantNotStale("p1", "", "./testgo list claims p1 is stale, incorrectly")
- tg.wantNotStale("p2", "", "./testgo list claims p2 is stale, incorrectly")
- tg.sleep()
- if f, err := os.OpenFile(tg.path("d2/src/p2/p2.go"), os.O_WRONLY|os.O_APPEND, 0); err != nil {
- t.Fatal(err)
- } else if _, err = f.WriteString(`func G() {}`); err != nil {
- t.Fatal(err)
- } else {
- tg.must(f.Close())
- }
- tg.wantStale("p2", "build ID mismatch", "./testgo list claims p2 is NOT stale, incorrectly")
- tg.wantStale("p1", "stale dependency: p2", "./testgo list claims p1 is NOT stale, incorrectly")
-
- tg.run("install", "-i", "p1")
- tg.wantNotStale("p2", "", "./testgo list claims p2 is stale after reinstall, incorrectly")
- tg.wantNotStale("p1", "", "./testgo list claims p1 is stale after reinstall, incorrectly")
-}
-
-func TestGoInstallDetectsRemovedFiles(t *testing.T) {
- tg := testgo(t)
- defer tg.cleanup()
- tg.parallel()
- tg.tempFile("src/mypkg/x.go", `package mypkg`)
- tg.tempFile("src/mypkg/y.go", `package mypkg`)
- tg.tempFile("src/mypkg/z.go", `// +build missingtag
-
- package mypkg`)
- tg.setenv("GOPATH", tg.path("."))
- tg.run("install", "mypkg")
- tg.wantNotStale("mypkg", "", "./testgo list mypkg claims mypkg is stale, incorrectly")
- // z.go was not part of the build; removing it is okay.
- tg.must(os.Remove(tg.path("src/mypkg/z.go")))
- tg.wantNotStale("mypkg", "", "./testgo list mypkg claims mypkg is stale after removing z.go; should not be stale")
- // y.go was part of the package; removing it should be detected.
- tg.must(os.Remove(tg.path("src/mypkg/y.go")))
- tg.wantStale("mypkg", "build ID mismatch", "./testgo list mypkg claims mypkg is NOT stale after removing y.go; should be stale")
-}
-
-func TestWildcardMatchesSyntaxErrorDirs(t *testing.T) {
- tg := testgo(t)
- defer tg.cleanup()
- // TODO: tg.parallel()
- tg.tempFile("src/mypkg/x.go", `package mypkg`)
- tg.tempFile("src/mypkg/y.go", `pkg mypackage`)
- tg.setenv("GOPATH", tg.path("."))
- tg.cd(tg.path("src/mypkg"))
- tg.runFail("list", "./...")
- tg.runFail("build", "./...")
- tg.runFail("install", "./...")
-}
-
-func TestGoListWithTags(t *testing.T) {
- tg := testgo(t)
- defer tg.cleanup()
- tg.tempFile("src/mypkg/x.go", "// +build thetag\n\npackage mypkg\n")
- tg.setenv("GOPATH", tg.path("."))
- tg.cd(tg.path("./src"))
- tg.run("list", "-tags=thetag", "./my...")
- tg.grepStdout("mypkg", "did not find mypkg")
-}
-
-func TestGoInstallErrorOnCrossCompileToBin(t *testing.T) {
- if testing.Short() {
- t.Skip("don't install into GOROOT in short mode")
- }
-
- tg := testgo(t)
- defer tg.cleanup()
- tg.tempFile("src/mycmd/x.go", `package main
- func main() {}`)
- tg.setenv("GOPATH", tg.path("."))
- tg.cd(tg.path("src/mycmd"))
-
- tg.run("build", "mycmd")
-
- goarch := "386"
- if runtime.GOARCH == "386" {
- goarch = "amd64"
- }
- tg.setenv("GOOS", "linux")
- tg.setenv("GOARCH", goarch)
- tg.run("install", "mycmd")
- tg.setenv("GOBIN", tg.path("."))
- tg.runFail("install", "mycmd")
- tg.run("install", "cmd/pack")
-}
-
-func TestGoInstallDetectsRemovedFilesInPackageMain(t *testing.T) {
- tooSlow(t)
- tg := testgo(t)
- defer tg.cleanup()
- tg.parallel()
- tg.tempFile("src/mycmd/x.go", `package main
- func main() {}`)
- tg.tempFile("src/mycmd/y.go", `package main`)
- tg.tempFile("src/mycmd/z.go", `// +build missingtag
-
- package main`)
- tg.setenv("GOPATH", tg.path("."))
- tg.run("install", "mycmd")
- tg.wantNotStale("mycmd", "", "./testgo list mypkg claims mycmd is stale, incorrectly")
- // z.go was not part of the build; removing it is okay.
- tg.must(os.Remove(tg.path("src/mycmd/z.go")))
- tg.wantNotStale("mycmd", "", "./testgo list mycmd claims mycmd is stale after removing z.go; should not be stale")
- // y.go was part of the package; removing it should be detected.
- tg.must(os.Remove(tg.path("src/mycmd/y.go")))
- tg.wantStale("mycmd", "build ID mismatch", "./testgo list mycmd claims mycmd is NOT stale after removing y.go; should be stale")
-}
-
func testLocalRun(tg *testgoData, exepath, local, match string) {
tg.t.Helper()
out, err := exec.Command(exepath).Output()
defer tg.cleanup()
tg.setenv("GOPATH", filepath.Join(tg.pwd(), "testdata"))
tg.runFail("test", "syntaxerror")
- tg.grepStderr("FAIL", "go test did not say FAIL")
+ tg.grepStderr("x_test.go:", "did not diagnose error")
+ tg.grepStdout("FAIL", "go test did not say FAIL")
}
func TestWildcardsDoNotLookInUselessDirectories(t *testing.T) {
}
}
+func tempEnvName() string {
+ switch runtime.GOOS {
+ case "windows":
+ return "TMP"
+ case "plan9":
+ return "TMPDIR" // actually plan 9 doesn't have one at all but this is fine
+ default:
+ return "TMPDIR"
+ }
+}
+
func TestDefaultGOPATH(t *testing.T) {
tg := testgo(t)
defer tg.cleanup()
}
-func TestGOTMPDIR(t *testing.T) {
- tg := testgo(t)
- defer tg.cleanup()
- tg.parallel()
- tg.setenv("GOPATH", filepath.Join(tg.pwd(), "testdata"))
- tg.makeTempdir()
- tg.setenv("GOTMPDIR", tg.tempdir)
- tg.setenv("GOCACHE", "off")
-
- // complex/x is a trivial non-main package.
- tg.run("build", "-work", "-x", "complex/w")
- tg.grepStderr("WORK="+regexp.QuoteMeta(tg.tempdir), "did not work in $GOTMPDIR")
-}
-
-func TestBuildCache(t *testing.T) {
- tooSlow(t)
- if strings.Contains(os.Getenv("GODEBUG"), "gocacheverify") {
- t.Skip("GODEBUG gocacheverify")
- }
- tg := testgo(t)
- defer tg.cleanup()
- tg.parallel()
- tg.setenv("GOPATH", filepath.Join(tg.pwd(), "testdata"))
- tg.makeTempdir()
- tg.setenv("GOCACHE", tg.tempdir)
-
- // complex/w is a trivial non-main package.
- // It imports nothing, so there should be no Deps.
- tg.run("list", "-f={{join .Deps \" \"}}", "complex/w")
- tg.grepStdoutNot(".+", "complex/w depends on unexpected packages")
-
- tg.run("build", "-x", "complex/w")
- tg.grepStderr(`[\\/]compile|gccgo`, "did not run compiler")
-
- tg.run("build", "-x", "complex/w")
- tg.grepStderrNot(`[\\/]compile|gccgo`, "ran compiler incorrectly")
-
- tg.run("build", "-a", "-x", "complex/w")
- tg.grepStderr(`[\\/]compile|gccgo`, "did not run compiler with -a")
-
- // complex is a non-trivial main package.
- // the link step should not be cached.
- tg.run("build", "-o", os.DevNull, "-x", "complex")
- tg.grepStderr(`[\\/]link|gccgo`, "did not run linker")
-
- tg.run("build", "-o", os.DevNull, "-x", "complex")
- tg.grepStderr(`[\\/]link|gccgo`, "did not run linker")
-}
-
-func TestCacheOutput(t *testing.T) {
- // Test that command output is cached and replayed too.
- if strings.Contains(os.Getenv("GODEBUG"), "gocacheverify") {
- t.Skip("GODEBUG gocacheverify")
- }
- tg := testgo(t)
- defer tg.cleanup()
- tg.parallel()
- tg.setenv("GOPATH", filepath.Join(tg.pwd(), "testdata"))
- tg.makeTempdir()
- tg.setenv("GOCACHE", tg.tempdir)
-
- tg.run("build", "-gcflags=-m", "errors")
- stdout1 := tg.getStdout()
- stderr1 := tg.getStderr()
-
- tg.run("build", "-gcflags=-m", "errors")
- stdout2 := tg.getStdout()
- stderr2 := tg.getStderr()
-
- if stdout2 != stdout1 || stderr2 != stderr1 {
- t.Errorf("cache did not reproduce output:\n\nstdout1:\n%s\n\nstdout2:\n%s\n\nstderr1:\n%s\n\nstderr2:\n%s",
- stdout1, stdout2, stderr1, stderr2)
- }
-}
-
func TestCacheListStale(t *testing.T) {
tooSlow(t)
if strings.Contains(os.Getenv("GODEBUG"), "gocacheverify") {
l = l[:n-1]
}
n := len(l)
- if n >= 2 && knownOS[l[n-2]] && knownArch[l[n-1]] {
+ if n >= 2 && KnownOS[l[n-2]] && KnownArch[l[n-1]] {
return tags[l[n-2]] && tags[l[n-1]]
}
- if n >= 1 && knownOS[l[n-1]] {
+ if n >= 1 && KnownOS[l[n-1]] {
return tags[l[n-1]]
}
- if n >= 1 && knownArch[l[n-1]] {
+ if n >= 1 && KnownArch[l[n-1]] {
return tags[l[n-1]]
}
return true
}
-var knownOS = make(map[string]bool)
-var knownArch = make(map[string]bool)
+var KnownOS = make(map[string]bool)
+var KnownArch = make(map[string]bool)
func init() {
for _, v := range strings.Fields(goosList) {
- knownOS[v] = true
+ KnownOS[v] = true
}
for _, v := range strings.Fields(goarchList) {
- knownArch[v] = true
+ KnownArch[v] = true
}
}
if err != nil {
str := err.Error()
str = strings.TrimPrefix(str, "\n")
- failed := fmt.Sprintf("FAIL\t%s [setup failed]\n", p.ImportPath)
-
if p.ImportPath != "" {
- base.Errorf("# %s\n%s\n%s", p.ImportPath, str, failed)
+ base.Errorf("# %s\n%s", p.ImportPath, str)
} else {
- base.Errorf("%s\n%s", str, failed)
+ base.Errorf("%s", str)
}
+ fmt.Printf("FAIL\t%s [setup failed]\n", p.ImportPath)
continue
}
builds = append(builds, buildTest)
} else {
tmp, err := ioutil.TempDir(os.Getenv("GOTMPDIR"), "go-build")
if err != nil {
- base.Fatalf("%s", err)
+ base.Fatalf("go: creating work dir: %v", err)
}
if !filepath.IsAbs(tmp) {
abs, err := filepath.Abs(tmp)
--- /dev/null
+// Copyright 2018 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.
+
+// Script-driven tests.
+// See testdata/script/README for an overview.
+
+package main_test
+
+import (
+ "bytes"
+ "fmt"
+ "internal/testenv"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "regexp"
+ "runtime"
+ "strings"
+ "testing"
+ "time"
+
+ "cmd/go/internal/imports"
+ "cmd/go/internal/par"
+ "cmd/go/internal/txtar"
+)
+
+// TestScript runs the tests in testdata/script/*.txt.
+func TestScript(t *testing.T) {
+ testenv.MustHaveGoBuild(t)
+ if skipExternal {
+ t.Skipf("skipping external tests on %s/%s", runtime.GOOS, runtime.GOARCH)
+ }
+
+ files, err := filepath.Glob("testdata/script/*.txt")
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, file := range files {
+ file := file
+ name := strings.TrimSuffix(filepath.Base(file), ".txt")
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+ ts := &testScript{t: t, name: name, file: file}
+ ts.setup()
+ if !*testWork {
+ defer removeAll(ts.workdir)
+ }
+ ts.run()
+ })
+ }
+}
+
+// A testScript holds execution state for a single test script.
+type testScript struct {
+ t *testing.T
+ workdir string // temporary work dir ($WORK)
+ log bytes.Buffer // test execution log (printed at end of test)
+ mark int // offset of next log truncation
+ cd string // current directory during test execution; initially $WORK/gopath/src
+ name string // short name of test ("foo")
+ file string // full file name ("testdata/script/foo.txt")
+ lineno int // line number currently executing
+ line string // line currently executing
+ env []string // environment list (for os/exec)
+ envMap map[string]string // environment mapping (matches env)
+ stdout string // standard output from last 'go' command; for 'stdout' command
+ stderr string // standard error from last 'go' command; for 'stderr' command
+ stopped bool // test wants to stop early
+ start time.Time // time phase started
+}
+
+// setup sets up the test execution temporary directory and environment.
+func (ts *testScript) setup() {
+ ts.workdir = filepath.Join(testTmpDir, "script-"+ts.name)
+ ts.check(os.MkdirAll(filepath.Join(ts.workdir, "tmp"), 0777))
+ ts.check(os.MkdirAll(filepath.Join(ts.workdir, "gopath/src"), 0777))
+ ts.cd = filepath.Join(ts.workdir, "gopath/src")
+ ts.env = []string{
+ "WORK=" + ts.workdir, // must be first for ts.abbrev
+ "PATH=" + os.Getenv("PATH"),
+ homeEnvName() + "=/no-home",
+ "GOARCH=" + runtime.GOARCH,
+ "GOCACHE=" + testGOCACHE,
+ "GOOS=" + runtime.GOOS,
+ "GOPATH=" + filepath.Join(ts.workdir, "gopath"),
+ "GOROOT=" + testGOROOT,
+ tempEnvName() + "=" + filepath.Join(ts.workdir, "tmp"),
+ "devnull=" + os.DevNull,
+ }
+ if runtime.GOOS == "windows" {
+ ts.env = append(ts.env, "exe=.exe")
+ } else {
+ ts.env = append(ts.env, "exe=")
+ }
+ ts.envMap = make(map[string]string)
+ for _, kv := range ts.env {
+ if i := strings.Index(kv, "="); i >= 0 {
+ ts.envMap[kv[:i]] = kv[i+1:]
+ }
+ }
+}
+
+var execCache par.Cache
+
+// run runs the test script.
+func (ts *testScript) run() {
+ // Truncate log at end of last phase marker,
+ // discarding details of successful phase.
+ rewind := func() {
+ if !testing.Verbose() {
+ ts.log.Truncate(ts.mark)
+ }
+ }
+
+ // Insert elapsed time for phase at end of phase marker
+ markTime := func() {
+ if ts.mark > 0 && !ts.start.IsZero() {
+ afterMark := append([]byte{}, ts.log.Bytes()[ts.mark:]...)
+ ts.log.Truncate(ts.mark - 1) // cut \n and afterMark
+ fmt.Fprintf(&ts.log, " (%.3fs)\n", time.Since(ts.start).Seconds())
+ ts.log.Write(afterMark)
+ }
+ ts.start = time.Time{}
+ }
+
+ defer func() {
+ markTime()
+ // Flush testScript log to testing.T log.
+ ts.t.Log("\n" + ts.abbrev(ts.log.String()))
+ }()
+
+ // Unpack archive.
+ a, err := txtar.ParseFile(ts.file)
+ ts.check(err)
+ for _, f := range a.Files {
+ name := ts.mkabs(ts.expand(f.Name))
+ ts.check(os.MkdirAll(filepath.Dir(name), 0777))
+ ts.check(ioutil.WriteFile(name, f.Data, 0666))
+ }
+
+ // With -v or -testwork, start log with full environment.
+ if *testWork || testing.Verbose() {
+ // Display environment.
+ ts.cmdEnv(false, nil)
+ fmt.Fprintf(&ts.log, "\n")
+ ts.mark = ts.log.Len()
+ }
+
+ // Run script.
+ // See testdata/script/README for documentation of script form.
+ script := string(a.Comment)
+Script:
+ for script != "" {
+ // Extract next line.
+ ts.lineno++
+ var line string
+ if i := strings.Index(script, "\n"); i >= 0 {
+ line, script = script[:i], script[i+1:]
+ } else {
+ line, script = script, ""
+ }
+
+ // # is a comment indicating the start of new phase.
+ if strings.HasPrefix(line, "#") {
+ // If there was a previous phase, it succeeded,
+ // so rewind the log to delete its details (unless -v is in use).
+ // If nothing has happened at all since the mark,
+ // rewinding is a no-op and adding elapsed time
+ // for doing nothing is meaningless, so don't.
+ if ts.log.Len() > ts.mark {
+ rewind()
+ markTime()
+ }
+ // Print phase heading and mark start of phase output.
+ fmt.Fprintf(&ts.log, "%s\n", line)
+ ts.mark = ts.log.Len()
+ ts.start = time.Now()
+ continue
+ }
+
+ // Parse input line. Ignore blanks entirely.
+ args := ts.parse(line)
+ if len(args) == 0 {
+ continue
+ }
+
+ // Echo command to log.
+ fmt.Fprintf(&ts.log, "> %s\n", line)
+
+ // Command prefix [cond] means only run this command if cond is satisfied.
+ for strings.HasPrefix(args[0], "[") && strings.HasSuffix(args[0], "]") {
+ cond := args[0]
+ cond = cond[1 : len(cond)-1]
+ cond = strings.TrimSpace(cond)
+ args = args[1:]
+ if len(args) == 0 {
+ ts.fatalf("missing command after condition")
+ }
+ want := true
+ if strings.HasPrefix(cond, "!") {
+ want = false
+ cond = strings.TrimSpace(cond[1:])
+ }
+ // Known conds are: $GOOS, $GOARCH, runtime.Compiler, and 'short' (for testing.Short).
+ //
+ // NOTE: If you make changes here, update testdata/script/README too!
+ //
+ ok := false
+ switch cond {
+ case runtime.GOOS, runtime.GOARCH, runtime.Compiler:
+ ok = true
+ case "short":
+ ok = testing.Short()
+ case "cgo":
+ ok = canCgo
+ case "msan":
+ ok = canMSan
+ case "race":
+ ok = canRace
+ case "net":
+ ok = testenv.HasExternalNetwork()
+ case "link":
+ ok = testenv.HasLink()
+ case "symlink":
+ ok = testenv.HasSymlink()
+ default:
+ if strings.HasPrefix(cond, "exec:") {
+ prog := cond[len("exec:"):]
+ ok = execCache.Do(prog, func() interface{} {
+ _, err := exec.LookPath(prog)
+ return err == nil
+ }).(bool)
+ break
+ }
+ if !imports.KnownArch[cond] && !imports.KnownOS[cond] && cond != "gc" && cond != "gccgo" {
+ ts.fatalf("unknown condition %q", cond)
+ }
+ }
+ if ok != want {
+ // Don't run rest of line.
+ continue Script
+ }
+ }
+
+ // Command prefix ! means negate the expectations about this command:
+ // go command should fail, match should not be found, etc.
+ neg := false
+ if args[0] == "!" {
+ neg = true
+ args = args[1:]
+ if len(args) == 0 {
+ ts.fatalf("! on line by itself")
+ }
+ }
+
+ // Run command.
+ cmd := scriptCmds[args[0]]
+ if cmd == nil {
+ ts.fatalf("unknown command %q", args[0])
+ }
+ cmd(ts, neg, args[1:])
+
+ // Command can ask script to stop early.
+ if ts.stopped {
+ return
+ }
+ }
+
+ // Final phase ended.
+ rewind()
+ markTime()
+ fmt.Fprintf(&ts.log, "PASS\n")
+}
+
+// scriptCmds are the script command implementations.
+// Keep list and the implementations below sorted by name.
+//
+// NOTE: If you make changes here, update testdata/script/README too!
+//
+var scriptCmds = map[string]func(*testScript, bool, []string){
+ "cd": (*testScript).cmdCd,
+ "cp": (*testScript).cmdCp,
+ "env": (*testScript).cmdEnv,
+ "exec": (*testScript).cmdExec,
+ "exists": (*testScript).cmdExists,
+ "go": (*testScript).cmdGo,
+ "mkdir": (*testScript).cmdMkdir,
+ "rm": (*testScript).cmdRm,
+ "skip": (*testScript).cmdSkip,
+ "stale": (*testScript).cmdStale,
+ "stderr": (*testScript).cmdStderr,
+ "stdout": (*testScript).cmdStdout,
+ "stop": (*testScript).cmdStop,
+}
+
+// cd changes to a different directory.
+func (ts *testScript) cmdCd(neg bool, args []string) {
+ if neg {
+ ts.fatalf("unsupported: ! cd")
+ }
+ if len(args) != 1 {
+ ts.fatalf("usage: cd dir")
+ }
+
+ dir := args[0]
+ if !filepath.IsAbs(dir) {
+ dir = filepath.Join(ts.cd, dir)
+ }
+ info, err := os.Stat(dir)
+ if os.IsNotExist(err) {
+ ts.fatalf("directory %s does not exist", dir)
+ }
+ ts.check(err)
+ if !info.IsDir() {
+ ts.fatalf("%s is not a directory", dir)
+ }
+ ts.cd = dir
+ fmt.Fprintf(&ts.log, "%s\n", ts.cd)
+}
+
+// cp copies files, maybe eventually directories.
+func (ts *testScript) cmdCp(neg bool, args []string) {
+ if neg {
+ ts.fatalf("unsupported: ! cp")
+ }
+ if len(args) < 2 {
+ ts.fatalf("usage: cp src... dst")
+ }
+
+ dst := ts.mkabs(args[len(args)-1])
+ info, err := os.Stat(dst)
+ dstDir := err == nil && info.IsDir()
+ if len(args) > 2 && !dstDir {
+ ts.fatalf("cp: destination %s is not a directory", dst)
+ }
+
+ for _, arg := range args[:len(args)-1] {
+ src := ts.mkabs(arg)
+ info, err := os.Stat(src)
+ ts.check(err)
+ data, err := ioutil.ReadFile(src)
+ ts.check(err)
+ targ := dst
+ if dstDir {
+ targ = filepath.Join(dst, filepath.Base(src))
+ }
+ ts.check(ioutil.WriteFile(targ, data, info.Mode()&0777))
+ }
+}
+
+// env displays or adds to the environment.
+func (ts *testScript) cmdEnv(neg bool, args []string) {
+ if neg {
+ ts.fatalf("unsupported: ! env")
+ }
+ if len(args) == 0 {
+ printed := make(map[string]bool) // env list can have duplicates; only print effective value (from envMap) once
+ for _, kv := range ts.env {
+ k := kv[:strings.Index(kv, "=")]
+ if !printed[k] {
+ fmt.Fprintf(&ts.log, "%s=%s\n", k, ts.envMap[k])
+ }
+ }
+ return
+ }
+ for _, env := range args {
+ i := strings.Index(env, "=")
+ if i < 0 {
+ // Display value instead of setting it.
+ fmt.Fprintf(&ts.log, "%s=%s\n", env, ts.envMap[env])
+ continue
+ }
+ ts.env = append(ts.env, env)
+ ts.envMap[env[:i]] = env[i+1:]
+ }
+}
+
+// exec runs the given command.
+func (ts *testScript) cmdExec(neg bool, args []string) {
+ if len(args) < 1 {
+ ts.fatalf("usage: exec program [args...]")
+ }
+ var err error
+ ts.stdout, ts.stderr, err = ts.exec(args[0], args[1:]...)
+ if ts.stdout != "" {
+ fmt.Fprintf(&ts.log, "[stdout]\n%s", ts.stdout)
+ }
+ if ts.stderr != "" {
+ fmt.Fprintf(&ts.log, "[stderr]\n%s", ts.stderr)
+ }
+ if err != nil {
+ fmt.Fprintf(&ts.log, "[%v]\n", err)
+ if !neg {
+ ts.fatalf("unexpected command failure")
+ }
+ } else {
+ if neg {
+ ts.fatalf("unexpected command success")
+ }
+ }
+}
+
+// exists checks that the list of files exists.
+func (ts *testScript) cmdExists(neg bool, args []string) {
+ if len(args) == 0 {
+ ts.fatalf("usage: exists file...")
+ }
+
+ for _, file := range args {
+ file = ts.mkabs(file)
+ info, err := os.Stat(file)
+ if err == nil && neg {
+ what := "file"
+ if info.IsDir() {
+ what = "directory"
+ }
+ ts.fatalf("%s %s unexpectedly exists", what, file)
+ }
+ if err != nil && !neg {
+ ts.fatalf("%s does not exist", file)
+ }
+ }
+}
+
+// go runs the go command.
+func (ts *testScript) cmdGo(neg bool, args []string) {
+ ts.cmdExec(neg, append([]string{testGo}, args...))
+}
+
+// mkdir creates directories.
+func (ts *testScript) cmdMkdir(neg bool, args []string) {
+ if neg {
+ ts.fatalf("unsupported: ! mkdir")
+ }
+ if len(args) < 1 {
+ ts.fatalf("usage: mkdir dir...")
+ }
+ for _, arg := range args {
+ ts.check(os.MkdirAll(ts.mkabs(arg), 0777))
+ }
+}
+
+// rm removes files or directories.
+func (ts *testScript) cmdRm(neg bool, args []string) {
+ if neg {
+ ts.fatalf("unsupported: ! rm")
+ }
+ if len(args) < 1 {
+ ts.fatalf("usage: rm file...")
+ }
+ for _, arg := range args {
+ file := ts.mkabs(arg)
+ removeAll(file) // does chmod and then attempts rm
+ ts.check(os.RemoveAll(file)) // report error
+ }
+}
+
+// skip marks the test skipped.
+func (ts *testScript) cmdSkip(neg bool, args []string) {
+ if len(args) > 1 {
+ ts.fatalf("usage: skip [msg]")
+ }
+ if neg {
+ ts.fatalf("unsupported: ! skip")
+ }
+ if len(args) == 1 {
+ ts.t.Skip(args[0])
+ }
+ ts.t.Skip()
+}
+
+// stale checks that the named build targets are stale.
+func (ts *testScript) cmdStale(neg bool, args []string) {
+ if len(args) == 0 {
+ ts.fatalf("usage: stale target...")
+ }
+ tmpl := "{{if .Error}}{{.ImportPath}}: {{.Error.Err}}{else}}"
+ if neg {
+ tmpl += "{{if .Stale}}{{.ImportPath}} is unexpectedly stale{{end}}"
+ } else {
+ tmpl += "{{if not .Stale}}{{.ImportPath}} is unexpectedly NOT stale{{end}}"
+ }
+ tmpl += "{{end}}"
+ goArgs := append([]string{"list", "-e", "-f=" + tmpl}, args...)
+ stdout, stderr, err := ts.exec(testGo, goArgs...)
+ if err != nil {
+ ts.fatalf("go list: %v\n%s%s", err, stdout, stderr)
+ }
+ if stdout != "" {
+ ts.fatalf("%s", stdout)
+ }
+}
+
+// stop stops execution of the test (marking it passed).
+func (ts *testScript) cmdStop(neg bool, args []string) {
+ if neg {
+ ts.fatalf("unsupported: ! stop")
+ }
+ if len(args) > 1 {
+ ts.fatalf("usage: stop [msg]")
+ }
+ if len(args) == 1 {
+ fmt.Fprintf(&ts.log, "stop: %s\n", args[0])
+ } else {
+ fmt.Fprintf(&ts.log, "stop\n")
+ }
+ ts.stopped = true
+}
+
+// stdout checks that the last go command standard output matches a regexp.
+func (ts *testScript) cmdStdout(neg bool, args []string) {
+ scriptMatch(ts, neg, args, ts.stdout, "stdout")
+}
+
+// stderr checks that the last go command standard output matches a regexp.
+func (ts *testScript) cmdStderr(neg bool, args []string) {
+ scriptMatch(ts, neg, args, ts.stderr, "stderr")
+}
+
+// scriptMatch implements both stdout and stderr.
+func scriptMatch(ts *testScript, neg bool, args []string, text, name string) {
+ if len(args) != 1 {
+ ts.fatalf("usage: %s 'pattern' (%q)", name, args)
+ }
+ re, err := regexp.Compile(`(?m)` + args[0])
+ ts.check(err)
+ if neg {
+ if re.MatchString(text) {
+ ts.fatalf("unexpected match for %#q found in %s: %s %q", args[0], name, text, re.FindString(text))
+ }
+ } else {
+ if !re.MatchString(text) {
+ ts.fatalf("no match for %#q found in %s", args[0], name)
+ }
+ }
+}
+
+// Helpers for command implementations.
+
+// abbrev abbreviates the actual work directory in the string s to the literal string "$WORK".
+func (ts *testScript) abbrev(s string) string {
+ s = strings.Replace(s, ts.workdir, "$WORK", -1)
+ if *testWork {
+ // Expose actual $WORK value in environment dump on first line of work script,
+ // so that the user can find out what directory -testwork left behind.
+ s = "WORK=" + ts.workdir + "\n" + strings.TrimPrefix(s, "WORK=$WORK\n")
+ }
+ return s
+}
+
+// check calls ts.fatalf if err != nil.
+func (ts *testScript) check(err error) {
+ if err != nil {
+ ts.fatalf("%v", err)
+ }
+}
+
+// exec runs the given command line (an actual subprocess, not simulated)
+// in ts.cd with environment ts.env and then returns collected standard output and standard error.
+func (ts *testScript) exec(command string, args ...string) (stdout, stderr string, err error) {
+ cmd := exec.Command(testGo, args...)
+ cmd.Dir = ts.cd
+ cmd.Env = append(ts.env, "PWD="+ts.cd)
+ var stdoutBuf, stderrBuf strings.Builder
+ cmd.Stdout = &stdoutBuf
+ cmd.Stderr = &stderrBuf
+ err = cmd.Run()
+ return stdoutBuf.String(), stderrBuf.String(), err
+}
+
+// expand applies environment variable expansion to the string s.
+func (ts *testScript) expand(s string) string {
+ return os.Expand(s, func(key string) string { return ts.envMap[key] })
+}
+
+// fatalf aborts the test with the given failure message.
+func (ts *testScript) fatalf(format string, args ...interface{}) {
+ fmt.Fprintf(&ts.log, "FAIL: %s:%d: %s\n", ts.file, ts.lineno, fmt.Sprintf(format, args...))
+ ts.t.FailNow()
+}
+
+// mkabs interprets file relative to the test script's current directory
+// and returns the corresponding absolute path.
+func (ts *testScript) mkabs(file string) string {
+ if filepath.IsAbs(file) {
+ return file
+ }
+ return filepath.Join(ts.cd, file)
+}
+
+// parse parses a single line as a list of space-separated arguments
+// subject to environment variable expansion (but not resplitting).
+// Single quotes around text disable splitting and expansion.
+// To embed a single quote, double it: 'Don''t communicate by sharing memory.'
+func (ts *testScript) parse(line string) []string {
+ ts.line = line
+
+ var (
+ args []string
+ arg string // text of current arg so far (need to add line[start:i])
+ start = -1 // if >= 0, position where current arg text chunk starts
+ quoted = false // currently processing quoted text
+ )
+ for i := 0; ; i++ {
+ if !quoted && (i >= len(line) || line[i] == ' ' || line[i] == '\t' || line[i] == '\r') {
+ // Found arg-separating space.
+ if start >= 0 {
+ arg += ts.expand(line[start:i])
+ args = append(args, arg)
+ start = -1
+ arg = ""
+ }
+ if i >= len(line) {
+ break
+ }
+ continue
+ }
+ if i >= len(line) {
+ ts.fatalf("unterminated quoted argument")
+ }
+ if line[i] == '\'' {
+ if !quoted {
+ // starting a quoted chunk
+ if start >= 0 {
+ arg += ts.expand(line[start:i])
+ }
+ start = i + 1
+ quoted = true
+ continue
+ }
+ // 'foo''bar' means foo'bar, like in rc shell and Pascal.
+ if i+1 < len(line) && line[i+1] == '\'' {
+ arg += line[start:i]
+ start = i + 1
+ i++ // skip over second ' before next iteration
+ continue
+ }
+ // ending a quoted chunk
+ arg += line[start:i]
+ start = i + 1
+ quoted = false
+ continue
+ }
+ // found character worth saving; make sure we're saving
+ if start < 0 {
+ start = i
+ }
+ }
+ return args
+}
--- /dev/null
+This directory holds test scripts *.txt run during 'go test cmd/go'.
+To run a specific script foo.txt
+
+ go test cmd/go -run=Script/^foo$
+
+In general script files should have short names: a few words, not whole sentences.
+The first word should be the general category of behavior being tested,
+often the name of a go subcommand (list, build, test, ...) or concept (vendor, pattern).
+
+Each script is a text archive (go doc cmd/go/internal/txtar).
+The script begins with an actual command script to run
+followed by the content of zero or more supporting files to
+create in the script's temporary file system before it starts executing.
+
+As an example, run_hello.txt says:
+
+ # hello world
+ go run hello.go
+ stderr 'hello world'
+ ! stdout .
+
+ -- hello.go --
+ package main
+ func main() { println("hello world") }
+
+Each script runs in a fresh temporary work directory tree, available to scripts as $WORK.
+Scripts also have access to these other environment variables:
+
+ GOARCH=<target GOARCH>
+ GOCACHE=<actual GOCACHE being used outside the test>
+ GOOS=<target GOOS>
+ GOPATH=$WORK/gopath
+ GOROOT=<actual GOROOT>
+ HOME=/no-home
+ PATH=<actual PATH>
+ TMPDIR=$WORK/tmp
+ devnull=<value of os.DevNull>
+
+The environment variable $exe (lowercase) is an empty string on most systems, ".exe" on Windows.
+
+The scripts supporting files are unpacked relative to $GOPATH/src (aka $WORK/gopath/src)
+and then the script begins execution in that directory as well. Thus the example above runs
+in $WORK/gopath/src with GOPATH=$WORK/gopath and $WORK/gopath/src/hello.go
+containing the listed contents.
+
+The lines at the top of the script are a sequence of commands to be executed
+by a tiny script engine in ../../script_test.go (not the system shell).
+The script stops and the overall test fails if any particular command fails.
+
+Each line is parsed into a sequence of space-separated command words,
+with environment variable expansion. Adding single quotes around text
+keeps spaces in that text from being treated as word separators and also
+disables environment variable expansion. Inside a single-quoted block of
+text, a repeated single quote indicates a literal single quote, as in:
+
+ 'Don''t communicate by sharing memory.'
+
+A line beginning with # is a comment and conventionally explains what is
+being done or tested at the start of a new phase in the script.
+
+The command prefix ! indicates that the command on the rest of the line
+(typically go or a matching predicate) must fail, not succeed. Only certain
+commands support this prefix. They are indicated below by [!] in the synopsis.
+
+The command prefix [cond] indicates that the command on the rest of the line
+should only run when the condition is satisfied. The available conditions are:
+
+ - GOOS and GOARCH values, like [386], [windows], and so on.
+ - Compiler names, like [gccgo], [gc].
+ - Test environment details:
+ - [short] for testing.Short()
+ - [cgo], [msan], [race] for whether cgo, msan, and the race detector can be used
+ - [net] for whether the external network can be used
+ - [link] for testenv.HasLink()
+ - [symlink] for testenv.HasSymlink()
+ - [exec:prog] for whether prog is available for execution (found by exec.LookPath)
+
+A condition can be negated: [!short] means to run the rest of the line
+when testing.Short() is false.
+
+The commands are:
+
+- cd dir
+ Change to the given directory for future commands.
+
+- cp src... dst
+ Copy the listed files to the target file or existing directory.
+
+- env [key=value...]
+ With no arguments, print the environment (useful for debugging).
+ Otherwise add the listed key=value pairs to the environment.
+
+- [!] exec program [args...]
+ Run the given executable program with the arguments.
+ It must (or must not) succeed.
+ Note that 'exec' does not terminate the script (unlike in Unix shells).
+
+- [!] exists file...
+ Each of the listed files must (or must not) exist. (Directories are allowed.)
+
+- [!] go args...
+ Run the (test copy of the) go command with the given arguments.
+ It must (or must not) succeed.
+
+- mkdir path...
+ Create the listed directories, if they do not already exists.
+
+- rm file...
+ Remove the listed files or directories.
+
+- skip [message]
+ Mark the test skipped, including the message if given.
+
+- [!] stale path...
+ The packages named by the path arguments must (or must not)
+ be reported as "stale" by the go command.
+
+- [!] stderr pattern
+ Standard error from the most recent exec or go command
+ must (or must not) match the regular expression pattern.
+
+- [!] stdout pattern
+ Standard output from the most recent exec or go command
+ must (or must not) match the regular expression pattern.
+
+- stop [message]
+ Stop the test early (marking it as passing), including the message if given.
+
+When TestScript runs a script and the script fails, by default TestScript shows
+the execution of the most recent phase of the script (since the last # comment)
+and only shows the # comments for earlier phases. For example, here is a
+multi-phase script with a bug in it:
+
+ # GOPATH with p1 in d2, p2 in d2
+ env GOPATH=$WORK/d1:$WORK/d2
+
+ # build & install p1
+ env
+ go install -i p1
+ ! stale p1
+ ! stale p2
+
+ # modify p2 - p1 should appear stale
+ cp $WORK/p2x.go $WORK/d2/src/p2/p2.go
+ stale p1 p2
+
+ # build & install p1 again
+ go install -i p11
+ ! stale p1
+ ! stale p2
+
+ -- $WORK/d1/src/p1/p1.go --
+ package p1
+ import "p2"
+ func F() { p2.F() }
+ -- $WORK/d2/src/p2/p2.go --
+ package p2
+ func F() {}
+ -- $WORK/p2x.go --
+ package p2
+ func F() {}
+ func G() {}
+
+The bug is that the final phase installs p11 instead of p1. The test failure looks like:
+
+ $ go test -run=Script
+ --- FAIL: TestScript (3.75s)
+ --- FAIL: TestScript/install_rebuild_gopath (0.16s)
+ script_test.go:223:
+ # GOPATH with p1 in d2, p2 in d2 (0.000s)
+ # build & install p1 (0.087s)
+ # modify p2 - p1 should appear stale (0.029s)
+ # build & install p1 again (0.022s)
+ > go install -i p11
+ [stderr]
+ can't load package: package p11: cannot find package "p11" in any of:
+ /Users/rsc/go/src/p11 (from $GOROOT)
+ $WORK/d1/src/p11 (from $GOPATH)
+ $WORK/d2/src/p11
+ [exit status 1]
+ FAIL: unexpected go command failure
+
+ script_test.go:73: failed at testdata/script/install_rebuild_gopath.txt:15 in $WORK/gopath/src
+
+ FAIL
+ exit status 1
+ FAIL cmd/go 4.875s
+ $
+
+Note that the commands in earlier phases have been hidden, so that the relevant
+commands are more easily found, and the elapsed time for a completed phase
+is shown next to the phase heading. To see the entire execution, use "go test -v",
+which also adds an initial environment dump to the beginning of the log.
+
+Note also that in reported output, the actual name of the per-script temporary directory
+has been consistently replaced with the literal string $WORK.
+
+The cmd/go test flag -testwork (which must appear on the "go test" command line after
+standard test flags) causes each test to log the name of its $WORK directory and other
+environment variable settings and also to leave that directory behind when it exits,
+for manual debugging of failing tests:
+
+ $ go test -run=Script -work
+ --- FAIL: TestScript (3.75s)
+ --- FAIL: TestScript/install_rebuild_gopath (0.16s)
+ script_test.go:223:
+ WORK=/tmp/cmd-go-test-745953508/script-install_rebuild_gopath
+ GOARCH=
+ GOCACHE=/Users/rsc/Library/Caches/go-build
+ GOOS=
+ GOPATH=$WORK/gopath
+ GOROOT=/Users/rsc/go
+ HOME=/no-home
+ TMPDIR=$WORK/tmp
+ exe=
+
+ # GOPATH with p1 in d2, p2 in d2 (0.000s)
+ # build & install p1 (0.085s)
+ # modify p2 - p1 should appear stale (0.030s)
+ # build & install p1 again (0.019s)
+ > go install -i p11
+ [stderr]
+ can't load package: package p11: cannot find package "p11" in any of:
+ /Users/rsc/go/src/p11 (from $GOROOT)
+ $WORK/d1/src/p11 (from $GOPATH)
+ $WORK/d2/src/p11
+ [exit status 1]
+ FAIL: unexpected go command failure
+
+ script_test.go:73: failed at testdata/script/install_rebuild_gopath.txt:15 in $WORK/gopath/src
+
+ FAIL
+ exit status 1
+ FAIL cmd/go 4.875s
+ $
+
+ $ WORK=/tmp/cmd-go-test-745953508/script-install_rebuild_gopath
+ $ cd $WORK/d1/src/p1
+ $ cat p1.go
+ package p1
+ import "p2"
+ func F() { p2.F() }
+ $
+
--- /dev/null
+# Build should use GOTMPDIR if set.
+env GOTMPDIR=$WORK/my-favorite-tmpdir
+env GOCACHE=off
+mkdir $GOTMPDIR
+go build -work hello.go
+stderr ^WORK=.*my-favorite-tmpdir
+
+-- hello.go --
+package main
+func main() { println("hello") }
+
--- /dev/null
+# Set up fresh GOCACHE.
+env GOCACHE=$WORK/gocache
+mkdir $GOCACHE
+
+# Building trivial non-main package should run compiler the first time.
+go build -x lib.go
+stderr '(compile|gccgo)( |\.exe).*lib\.go'
+
+# ... but not again ...
+go build -x lib.go
+! stderr '(compile|gccgo)( |\.exe).*lib\.go'
+
+# ... unless we use -a.
+go build -a -x lib.go
+stderr '(compile|gccgo)( |\.exe)'
+
+-- lib.go --
+package lib
--- /dev/null
+# Set up fresh GOCACHE.
+env GOCACHE=$WORK/gocache
+mkdir $GOCACHE
+
+# Building a main package should run the compiler and linker ...
+go build -o $devnull -x main.go
+stderr '(compile|gccgo)( |\.exe).*main\.go'
+stderr '(link|gccgo)( |\.exe)'
+
+# ... and then the linker again ...
+go build -o $devnull -x main.go
+! stderr '(compile|gccgo)( |\.exe).*main\.go'
+stderr '(link|gccgo)( |\.exe)'
+
+# ... but the output binary can serve as a cache.
+go build -o main$exe -x main.go
+stderr '(link|gccgo)( |\.exe)'
+go build -o main$exe -x main.go
+! stderr '(link|gccgo)( |\.exe)'
+
+-- main.go --
+package main
+func main() {}
--- /dev/null
+[!gc] skip
+
+# Set up fresh GOCACHE.
+env GOCACHE=$WORK/gocache
+mkdir $GOCACHE
+
+# Building a trivial non-main package should run compiler the first time.
+go build -x -gcflags=-m lib.go
+stderr 'compile( |\.exe)'
+stderr 'lib.go:2.* can inline f'
+
+# ... but not the second, even though it still prints the compiler output.
+go build -x -gcflags=-m lib.go
+! stderr 'compile( |\.exe)'
+stderr 'lib.go:2.* can inline f'
+
+-- lib.go --
+package p
+func f(x *int) *int { return x }
--- /dev/null
+# look for short, relative file:line in error message
+! go run ../../gopath/x/y/z/err.go
+stderr ^..[\\/]x[\\/]y[\\/]z[\\/]err.go:
+
+-- ../x/y/z/err.go --
+package main; import "bar"
--- /dev/null
+# 'go install' with no arguments should clean up after go build
+cd mycmd
+go build
+exists mycmd$exe
+go install
+! exists mycmd$exe
+
+# 'go install mycmd' does not clean up, even in the mycmd directory
+go build
+exists mycmd$exe
+go install mycmd
+exists mycmd$exe
+
+# 'go install mycmd' should not clean up in an unrelated current directory either
+cd ..
+cp mycmd/mycmd$exe mycmd$exe
+go install mycmd
+exists mycmd$exe
+
+-- mycmd/main.go --
+package main
+func main() {}
--- /dev/null
+cd mycmd
+go build mycmd
+
+# cross-compile install with implicit GOBIN=$GOPATH/bin can make subdirectory
+env GOARCH=386
+[386] env GOARCH=amd64
+env GOOS=linux
+go install mycmd
+exists $GOPATH/bin/linux_$GOARCH/mycmd
+
+# cross-compile install with explicit GOBIN cannot make subdirectory
+env GOBIN=$WORK/bin
+! go install mycmd
+! exists $GOBIN/linux_$GOARCH
+
+# installing standard command should still work
+# (should also be mtime update only if cmd/pack is up-to-date).
+! stale cmd/pack
+[!short] go install cmd/pack
+
+-- mycmd/x.go --
+package main
+func main() {}
--- /dev/null
+# GOPATH with p1 in d1, p2 in d2
+[!windows] env GOPATH=$WORK/d1:$WORK/d2
+[windows] env GOPATH=$WORK/d1;$WORK/d2
+
+# build & install p1
+go install -i p1
+! stale p1 p2
+
+# modify p2 - p1 should appear stale
+cp $WORK/p2x.go $WORK/d2/src/p2/p2.go
+stale p1 p2
+
+# build & install p1 again
+go install -i p1
+! stale p1 p2
+
+-- $WORK/d1/src/p1/p1.go --
+package p1
+import "p2"
+func F() { p2.F() }
+
+-- $WORK/d2/src/p2/p2.go --
+package p2
+func F() {}
+
+-- $WORK/p2x.go --
+package p2
+func F() {}
+func G() {}
--- /dev/null
+# go command should detect package staleness as source file set changes
+go install mypkg
+! stale mypkg
+
+# z.go was not compiled; removing it should NOT make mypkg stale
+rm mypkg/z.go
+! stale mypkg
+
+# y.go was compiled; removing it should make mypkg stale
+rm mypkg/y.go
+stale mypkg
+
+# go command should detect executable staleness too
+go install mycmd
+! stale mycmd
+rm mycmd/z.go
+! stale mycmd
+rm mycmd/y.go
+stale mycmd
+
+-- mypkg/x.go --
+package mypkg
+
+-- mypkg/y.go --
+package mypkg
+
+-- mypkg/z.go --
+// +build missingtag
+
+package mypkg
+
+-- mycmd/x.go --
+package main
+func main() {}
+
+-- mycmd/y.go --
+package main
+
+-- mycmd/z.go --
+// +build missingtag
+
+package main
--- /dev/null
+# check for linker name in error message about linker crash
+[!gc] skip
+! go build -ldflags=-crash_for_testing x.go
+stderr [\\/]tool[\\/].*[\\/]link
+
+-- x.go --
+package main; func main() {}
--- /dev/null
+[!gc] skip
+
+# listing GOROOT should only find standard packages
+cd $GOROOT/src
+go list -f '{{if not .Standard}}{{.ImportPath}}{{end}}' ./...
+! stdout .
+# TODO: ignore _/blah/go/src in output
+
+# our vendored packages should be reported as standard
+go list std cmd
+stdout golang_org/x/net/http2/hpack
+stdout cmd/vendor/golang\.org/x/arch/x86/x86asm
--- /dev/null
+# go list supports -tags
+go list -tags=thetag ./my...
+stdout mypkg
+
+-- mypkg/x.go --
+// +build thetag
+
+package mypkg
--- /dev/null
+# patterns match directories with syntax errors
+! go list ./...
+! go build ./...
+! go install ./...
+
+-- mypkg/x.go --
+package mypkg
+
+-- mypkg/y.go --
+pkg mypackage
--- /dev/null
+# hello world
+go run hello.go
+stderr 'hello world'
+
+-- hello.go --
+package main
+func main() { println("hello world") }
--- /dev/null
+! go test badtest/...
+! stdout ^ok
+stdout ^FAIL\tbadtest/badexec
+stdout ^FAIL\tbadtest/badsyntax
+stdout ^FAIL\tbadtest/badvar
+
+-- badtest/badexec/x_test.go --
+package badexec
+
+func init() {
+ panic("badexec")
+}
+
+-- badtest/badsyntax/x.go --
+package badsyntax
+
+-- badtest/badsyntax/x_test.go --
+package badsyntax
+
+func func func func func!
+
+-- badtest/badvar/x.go --
+package badvar
+
+-- badtest/badvar/x_test.go --
+package badvar_test
+
+func f() {
+ _ = notdefined
+}
--- /dev/null
+# smoke test for complex build configuration
+go build -o complex.exe complex
+[exec:gccgo] go build -compiler=gccgo -o complex.exe complex
+
+-- complex/main.go --
+package main
+
+import (
+ _ "complex/nest/sub/test12"
+ _ "complex/nest/sub/test23"
+ "complex/w"
+ "v"
+)
+
+func main() {
+ println(v.Hello + " " + w.World)
+}
+
+-- complex/nest/sub/test12/p.go --
+package test12
+
+// Check that vendor/v1 is used but vendor/v2 is NOT used (sub/vendor/v2 wins).
+
+import (
+ "v1"
+ "v2"
+)
+
+const x = v1.ComplexNestVendorV1
+const y = v2.ComplexNestSubVendorV2
+
+-- complex/nest/sub/test23/p.go --
+package test23
+
+// Check that vendor/v3 is used but vendor/v2 is NOT used (sub/vendor/v2 wins).
+
+import (
+ "v2"
+ "v3"
+)
+
+const x = v3.ComplexNestVendorV3
+const y = v2.ComplexNestSubVendorV2
+
+-- complex/nest/sub/vendor/v2/v2.go --
+package v2
+
+const ComplexNestSubVendorV2 = true
+
+-- complex/nest/vendor/v1/v1.go --
+package v1
+
+const ComplexNestVendorV1 = true
+
+-- complex/nest/vendor/v2/v2.go --
+package v2
+
+const ComplexNestVendorV2 = true
+
+-- complex/nest/vendor/v3/v3.go --
+package v3
+
+const ComplexNestVendorV3 = true
+
+-- complex/vendor/v/v.go --
+package v
+
+const Hello = "hello"
+
+-- complex/w/w.go --
+package w
+
+const World = "world"
+++ /dev/null
-package badexec
-
-func init() {
- panic("badexec")
-}
+++ /dev/null
-package badsyntax
+++ /dev/null
-package badsyntax
-
-func func func func func!
+++ /dev/null
-package badvar
+++ /dev/null
-package badvar_test
-
-func f() {
- _ = notdefined
-}
+++ /dev/null
-package main
-
-import (
- _ "complex/nest/sub/test12"
- _ "complex/nest/sub/test23"
- "complex/w"
- "v"
-)
-
-func main() {
- println(v.Hello + " " + w.World)
-}
+++ /dev/null
-package test12
-
-// Check that vendor/v1 is used but vendor/v2 is NOT used (sub/vendor/v2 wins).
-
-import (
- "v1"
- "v2"
-)
-
-const x = v1.ComplexNestVendorV1
-const y = v2.ComplexNestSubVendorV2
+++ /dev/null
-package test23
-
-// Check that vendor/v3 is used but vendor/v2 is NOT used (sub/vendor/v2 wins).
-
-import (
- "v2"
- "v3"
-)
-
-const x = v3.ComplexNestVendorV3
-const y = v2.ComplexNestSubVendorV2
+++ /dev/null
-package v2
-
-const ComplexNestSubVendorV2 = true
+++ /dev/null
-package v1
-
-const ComplexNestVendorV1 = true
+++ /dev/null
-package v2
-
-const ComplexNestVendorV2 = true
+++ /dev/null
-package v3
-
-const ComplexNestVendorV3 = true
+++ /dev/null
-package v
-
-const Hello = "hello"
+++ /dev/null
-package w
-
-const World = "world"