summaryrefslogtreecommitdiffstats
path: root/src/cmd/go/scriptreadme_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/go/scriptreadme_test.go')
-rw-r--r--src/cmd/go/scriptreadme_test.go267
1 files changed, 267 insertions, 0 deletions
diff --git a/src/cmd/go/scriptreadme_test.go b/src/cmd/go/scriptreadme_test.go
new file mode 100644
index 0000000..2a842fb
--- /dev/null
+++ b/src/cmd/go/scriptreadme_test.go
@@ -0,0 +1,267 @@
+// Copyright 2022 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 main_test
+
+import (
+ "bytes"
+ "cmd/go/internal/script"
+ "flag"
+ "internal/diff"
+ "internal/testenv"
+ "os"
+ "strings"
+ "testing"
+ "text/template"
+)
+
+var fixReadme = flag.Bool("fixreadme", false, "if true, update ../testdata/script/README")
+
+func checkScriptReadme(t *testing.T, engine *script.Engine, env []string) {
+ var args struct {
+ Language string
+ Commands string
+ Conditions string
+ }
+
+ cmds := new(strings.Builder)
+ if err := engine.ListCmds(cmds, true); err != nil {
+ t.Fatal(err)
+ }
+ args.Commands = cmds.String()
+
+ conds := new(strings.Builder)
+ if err := engine.ListConds(conds, nil); err != nil {
+ t.Fatal(err)
+ }
+ args.Conditions = conds.String()
+
+ doc := new(strings.Builder)
+ cmd := testenv.Command(t, testGo, "doc", "cmd/go/internal/script")
+ cmd.Env = env
+ cmd.Stdout = doc
+ if err := cmd.Run(); err != nil {
+ t.Fatal(cmd, ":", err)
+ }
+ _, lang, ok := strings.Cut(doc.String(), "# Script Language\n\n")
+ if !ok {
+ t.Fatalf("%q did not include Script Language section", cmd)
+ }
+ lang, _, ok = strings.Cut(lang, "\n\nvar ")
+ if !ok {
+ t.Fatalf("%q did not include vars after Script Language section", cmd)
+ }
+ args.Language = lang
+
+ tmpl := template.Must(template.New("README").Parse(readmeTmpl[1:]))
+ buf := new(bytes.Buffer)
+ if err := tmpl.Execute(buf, args); err != nil {
+ t.Fatal(err)
+ }
+
+ const readmePath = "testdata/script/README"
+ old, err := os.ReadFile(readmePath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ diff := diff.Diff(readmePath, old, "readmeTmpl", buf.Bytes())
+ if diff == nil {
+ t.Logf("%s is up to date.", readmePath)
+ return
+ }
+
+ if *fixReadme {
+ if err := os.WriteFile(readmePath, buf.Bytes(), 0666); err != nil {
+ t.Fatal(err)
+ }
+ t.Logf("wrote %d bytes to %s", buf.Len(), readmePath)
+ } else {
+ t.Logf("\n%s", diff)
+ t.Errorf("%s is stale. To update, run 'go generate cmd/go'.", readmePath)
+ }
+}
+
+const readmeTmpl = `
+This file is generated by 'go generate cmd/go'. DO NOT EDIT.
+
+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 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 other environment variables, including:
+
+ GOARCH=<target GOARCH>
+ GOCACHE=<actual GOCACHE being used outside the test>
+ GOEXE=<executable file suffix: .exe on Windows, empty on other systems>
+ GOOS=<target GOOS>
+ GOPATH=$WORK/gopath
+ GOPROXY=<local module proxy serving from cmd/go/testdata/mod>
+ GOROOT=<actual GOROOT>
+ GOROOT_FINAL=<actual GOROOT_FINAL>
+ TESTGO_GOROOT=<GOROOT used to build cmd/go, for use in tests that may change GOROOT>
+ HOME=/no-home
+ PATH=<actual PATH>
+ TMPDIR=$WORK/tmp
+ GODEBUG=<actual GODEBUG>
+ devnull=<value of os.DevNull>
+ goversion=<current Go version; for example, 1.12>
+
+On Plan 9, the variables $path and $home are set instead of $PATH and $HOME.
+On Windows, the variables $USERPROFILE and $TMP are set instead of
+$HOME and $TMPDIR.
+
+The lines at the top of the script are a sequence of commands to be executed by
+a small script engine configured in ../../script_test.go (not the system shell).
+
+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.
+
+{{.Language}}
+
+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() }
+ $
+
+The available commands are:
+{{.Commands}}
+
+The available conditions are:
+{{.Conditions}}
+`