• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2022 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
5package main_test
6
7import (
8	"bytes"
9	"cmd/go/internal/script"
10	"flag"
11	"internal/diff"
12	"internal/testenv"
13	"os"
14	"strings"
15	"testing"
16	"text/template"
17)
18
19var fixReadme = flag.Bool("fixreadme", false, "if true, update ../testdata/script/README")
20
21func checkScriptReadme(t *testing.T, engine *script.Engine, env []string) {
22	var args struct {
23		Language   string
24		Commands   string
25		Conditions string
26	}
27
28	cmds := new(strings.Builder)
29	if err := engine.ListCmds(cmds, true); err != nil {
30		t.Fatal(err)
31	}
32	args.Commands = cmds.String()
33
34	conds := new(strings.Builder)
35	if err := engine.ListConds(conds, nil); err != nil {
36		t.Fatal(err)
37	}
38	args.Conditions = conds.String()
39
40	doc := new(strings.Builder)
41	cmd := testenv.Command(t, testGo, "doc", "cmd/go/internal/script")
42	cmd.Env = env
43	cmd.Stdout = doc
44	if err := cmd.Run(); err != nil {
45		t.Fatal(cmd, ":", err)
46	}
47	_, lang, ok := strings.Cut(doc.String(), "# Script Language\n\n")
48	if !ok {
49		t.Fatalf("%q did not include Script Language section", cmd)
50	}
51	lang, _, ok = strings.Cut(lang, "\n\nvar ")
52	if !ok {
53		t.Fatalf("%q did not include vars after Script Language section", cmd)
54	}
55	args.Language = lang
56
57	tmpl := template.Must(template.New("README").Parse(readmeTmpl[1:]))
58	buf := new(bytes.Buffer)
59	if err := tmpl.Execute(buf, args); err != nil {
60		t.Fatal(err)
61	}
62
63	const readmePath = "testdata/script/README"
64	old, err := os.ReadFile(readmePath)
65	if err != nil {
66		t.Fatal(err)
67	}
68	diff := diff.Diff(readmePath, old, "readmeTmpl", buf.Bytes())
69	if diff == nil {
70		t.Logf("%s is up to date.", readmePath)
71		return
72	}
73
74	if *fixReadme {
75		if err := os.WriteFile(readmePath, buf.Bytes(), 0666); err != nil {
76			t.Fatal(err)
77		}
78		t.Logf("wrote %d bytes to %s", buf.Len(), readmePath)
79	} else {
80		t.Logf("\n%s", diff)
81		t.Errorf("%s is stale. To update, run 'go generate cmd/go'.", readmePath)
82	}
83}
84
85const readmeTmpl = `
86This file is generated by 'go generate cmd/go'. DO NOT EDIT.
87
88This directory holds test scripts *.txt run during 'go test cmd/go'.
89To run a specific script foo.txt
90
91	go test cmd/go -run=Script/^foo$
92
93In general script files should have short names: a few words, not whole sentences.
94The first word should be the general category of behavior being tested,
95often the name of a go subcommand (list, build, test, ...) or concept (vendor, pattern).
96
97Each script is a text archive (go doc internal/txtar).
98The script begins with an actual command script to run
99followed by the content of zero or more supporting files to
100create in the script's temporary file system before it starts executing.
101
102As an example, run_hello.txt says:
103
104	# hello world
105	go run hello.go
106	stderr 'hello world'
107	! stdout .
108
109	-- hello.go --
110	package main
111	func main() { println("hello world") }
112
113Each script runs in a fresh temporary work directory tree, available to scripts as $WORK.
114Scripts also have access to other environment variables, including:
115
116	GOARCH=<target GOARCH>
117	GOCACHE=<actual GOCACHE being used outside the test>
118	GOEXE=<executable file suffix: .exe on Windows, empty on other systems>
119	GOOS=<target GOOS>
120	GOPATH=$WORK/gopath
121	GOPROXY=<local module proxy serving from cmd/go/testdata/mod>
122	GOROOT=<actual GOROOT>
123	TESTGO_GOROOT=<GOROOT used to build cmd/go, for use in tests that may change GOROOT>
124	HOME=/no-home
125	PATH=<actual PATH>
126	TMPDIR=$WORK/tmp
127	GODEBUG=<actual GODEBUG>
128	devnull=<value of os.DevNull>
129	goversion=<current Go version; for example, 1.12>
130
131On Plan 9, the variables $path and $home are set instead of $PATH and $HOME.
132On Windows, the variables $USERPROFILE and $TMP are set instead of
133$HOME and $TMPDIR.
134
135The lines at the top of the script are a sequence of commands to be executed by
136a small script engine configured in ../../script_test.go (not the system shell).
137
138The scripts' supporting files are unpacked relative to $GOPATH/src
139(aka $WORK/gopath/src) and then the script begins execution in that directory as
140well. Thus the example above runs in $WORK/gopath/src with GOPATH=$WORK/gopath
141and $WORK/gopath/src/hello.go containing the listed contents.
142
143{{.Language}}
144
145When TestScript runs a script and the script fails, by default TestScript shows
146the execution of the most recent phase of the script (since the last # comment)
147and only shows the # comments for earlier phases. For example, here is a
148multi-phase script with a bug in it:
149
150	# GOPATH with p1 in d2, p2 in d2
151	env GOPATH=$WORK${/}d1${:}$WORK${/}d2
152
153	# build & install p1
154	env
155	go install -i p1
156	! stale p1
157	! stale p2
158
159	# modify p2 - p1 should appear stale
160	cp $WORK/p2x.go $WORK/d2/src/p2/p2.go
161	stale p1 p2
162
163	# build & install p1 again
164	go install -i p11
165	! stale p1
166	! stale p2
167
168	-- $WORK/d1/src/p1/p1.go --
169	package p1
170	import "p2"
171	func F() { p2.F() }
172	-- $WORK/d2/src/p2/p2.go --
173	package p2
174	func F() {}
175	-- $WORK/p2x.go --
176	package p2
177	func F() {}
178	func G() {}
179
180The bug is that the final phase installs p11 instead of p1. The test failure looks like:
181
182	$ go test -run=Script
183	--- FAIL: TestScript (3.75s)
184	    --- FAIL: TestScript/install_rebuild_gopath (0.16s)
185	        script_test.go:223:
186	            # GOPATH with p1 in d2, p2 in d2 (0.000s)
187	            # build & install p1 (0.087s)
188	            # modify p2 - p1 should appear stale (0.029s)
189	            # build & install p1 again (0.022s)
190	            > go install -i p11
191	            [stderr]
192	            can't load package: package p11: cannot find package "p11" in any of:
193	            	/Users/rsc/go/src/p11 (from $GOROOT)
194	            	$WORK/d1/src/p11 (from $GOPATH)
195	            	$WORK/d2/src/p11
196	            [exit status 1]
197	            FAIL: unexpected go command failure
198
199	        script_test.go:73: failed at testdata/script/install_rebuild_gopath.txt:15 in $WORK/gopath/src
200
201	FAIL
202	exit status 1
203	FAIL	cmd/go	4.875s
204	$
205
206Note that the commands in earlier phases have been hidden, so that the relevant
207commands are more easily found, and the elapsed time for a completed phase
208is shown next to the phase heading. To see the entire execution, use "go test -v",
209which also adds an initial environment dump to the beginning of the log.
210
211Note also that in reported output, the actual name of the per-script temporary directory
212has been consistently replaced with the literal string $WORK.
213
214The cmd/go test flag -testwork (which must appear on the "go test" command line after
215standard test flags) causes each test to log the name of its $WORK directory and other
216environment variable settings and also to leave that directory behind when it exits,
217for manual debugging of failing tests:
218
219	$ go test -run=Script -work
220	--- FAIL: TestScript (3.75s)
221	    --- FAIL: TestScript/install_rebuild_gopath (0.16s)
222	        script_test.go:223:
223	            WORK=/tmp/cmd-go-test-745953508/script-install_rebuild_gopath
224	            GOARCH=
225	            GOCACHE=/Users/rsc/Library/Caches/go-build
226	            GOOS=
227	            GOPATH=$WORK/gopath
228	            GOROOT=/Users/rsc/go
229	            HOME=/no-home
230	            TMPDIR=$WORK/tmp
231	            exe=
232
233	            # GOPATH with p1 in d2, p2 in d2 (0.000s)
234	            # build & install p1 (0.085s)
235	            # modify p2 - p1 should appear stale (0.030s)
236	            # build & install p1 again (0.019s)
237	            > go install -i p11
238	            [stderr]
239	            can't load package: package p11: cannot find package "p11" in any of:
240	            	/Users/rsc/go/src/p11 (from $GOROOT)
241	            	$WORK/d1/src/p11 (from $GOPATH)
242	            	$WORK/d2/src/p11
243	            [exit status 1]
244	            FAIL: unexpected go command failure
245
246	        script_test.go:73: failed at testdata/script/install_rebuild_gopath.txt:15 in $WORK/gopath/src
247
248	FAIL
249	exit status 1
250	FAIL	cmd/go	4.875s
251	$
252
253	$ WORK=/tmp/cmd-go-test-745953508/script-install_rebuild_gopath
254	$ cd $WORK/d1/src/p1
255	$ cat p1.go
256	package p1
257	import "p2"
258	func F() { p2.F() }
259	$
260
261The available commands are:
262{{.Commands}}
263
264The available conditions are:
265{{.Conditions}}
266`
267