Source file
src/cmd/go/scriptreadme_test.go
Documentation: cmd/go
1
2
3
4
5 package main_test
6
7 import (
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
19 var fixReadme = flag.Bool("fixreadme", false, "if true, update ../testdata/script/README")
20
21 func 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
85 const readmeTmpl = `
86 This file is generated by 'go generate cmd/go'. DO NOT EDIT.
87
88 This directory holds test scripts *.txt run during 'go test cmd/go'.
89 To run a specific script foo.txt
90
91 go test cmd/go -run=Script/^foo$
92
93 In general script files should have short names: a few words, not whole sentences.
94 The first word should be the general category of behavior being tested,
95 often the name of a go subcommand (list, build, test, ...) or concept (vendor, pattern).
96
97 Each script is a text archive (go doc internal/txtar).
98 The script begins with an actual command script to run
99 followed by the content of zero or more supporting files to
100 create in the script's temporary file system before it starts executing.
101
102 As 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
113 Each script runs in a fresh temporary work directory tree, available to scripts as $WORK.
114 Scripts 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 GOROOT_FINAL=<actual GOROOT_FINAL>
124 TESTGO_GOROOT=<GOROOT used to build cmd/go, for use in tests that may change GOROOT>
125 HOME=/no-home
126 PATH=<actual PATH>
127 TMPDIR=$WORK/tmp
128 GODEBUG=<actual GODEBUG>
129 devnull=<value of os.DevNull>
130 goversion=<current Go version; for example, 1.12>
131
132 On Plan 9, the variables $path and $home are set instead of $PATH and $HOME.
133 On Windows, the variables $USERPROFILE and $TMP are set instead of
134 $HOME and $TMPDIR.
135
136 The lines at the top of the script are a sequence of commands to be executed by
137 a small script engine configured in ../../script_test.go (not the system shell).
138
139 The scripts' supporting files are unpacked relative to $GOPATH/src
140 (aka $WORK/gopath/src) and then the script begins execution in that directory as
141 well. Thus the example above runs in $WORK/gopath/src with GOPATH=$WORK/gopath
142 and $WORK/gopath/src/hello.go containing the listed contents.
143
144 {{.Language}}
145
146 When TestScript runs a script and the script fails, by default TestScript shows
147 the execution of the most recent phase of the script (since the last # comment)
148 and only shows the # comments for earlier phases. For example, here is a
149 multi-phase script with a bug in it:
150
151 # GOPATH with p1 in d2, p2 in d2
152 env GOPATH=$WORK${/}d1${:}$WORK${/}d2
153
154 # build & install p1
155 env
156 go install -i p1
157 ! stale p1
158 ! stale p2
159
160 # modify p2 - p1 should appear stale
161 cp $WORK/p2x.go $WORK/d2/src/p2/p2.go
162 stale p1 p2
163
164 # build & install p1 again
165 go install -i p11
166 ! stale p1
167 ! stale p2
168
169 -- $WORK/d1/src/p1/p1.go --
170 package p1
171 import "p2"
172 func F() { p2.F() }
173 -- $WORK/d2/src/p2/p2.go --
174 package p2
175 func F() {}
176 -- $WORK/p2x.go --
177 package p2
178 func F() {}
179 func G() {}
180
181 The bug is that the final phase installs p11 instead of p1. The test failure looks like:
182
183 $ go test -run=Script
184 --- FAIL: TestScript (3.75s)
185 --- FAIL: TestScript/install_rebuild_gopath (0.16s)
186 script_test.go:223:
187 # GOPATH with p1 in d2, p2 in d2 (0.000s)
188 # build & install p1 (0.087s)
189 # modify p2 - p1 should appear stale (0.029s)
190 # build & install p1 again (0.022s)
191 > go install -i p11
192 [stderr]
193 can't load package: package p11: cannot find package "p11" in any of:
194 /Users/rsc/go/src/p11 (from $GOROOT)
195 $WORK/d1/src/p11 (from $GOPATH)
196 $WORK/d2/src/p11
197 [exit status 1]
198 FAIL: unexpected go command failure
199
200 script_test.go:73: failed at testdata/script/install_rebuild_gopath.txt:15 in $WORK/gopath/src
201
202 FAIL
203 exit status 1
204 FAIL cmd/go 4.875s
205 $
206
207 Note that the commands in earlier phases have been hidden, so that the relevant
208 commands are more easily found, and the elapsed time for a completed phase
209 is shown next to the phase heading. To see the entire execution, use "go test -v",
210 which also adds an initial environment dump to the beginning of the log.
211
212 Note also that in reported output, the actual name of the per-script temporary directory
213 has been consistently replaced with the literal string $WORK.
214
215 The cmd/go test flag -testwork (which must appear on the "go test" command line after
216 standard test flags) causes each test to log the name of its $WORK directory and other
217 environment variable settings and also to leave that directory behind when it exits,
218 for manual debugging of failing tests:
219
220 $ go test -run=Script -work
221 --- FAIL: TestScript (3.75s)
222 --- FAIL: TestScript/install_rebuild_gopath (0.16s)
223 script_test.go:223:
224 WORK=/tmp/cmd-go-test-745953508/script-install_rebuild_gopath
225 GOARCH=
226 GOCACHE=/Users/rsc/Library/Caches/go-build
227 GOOS=
228 GOPATH=$WORK/gopath
229 GOROOT=/Users/rsc/go
230 HOME=/no-home
231 TMPDIR=$WORK/tmp
232 exe=
233
234 # GOPATH with p1 in d2, p2 in d2 (0.000s)
235 # build & install p1 (0.085s)
236 # modify p2 - p1 should appear stale (0.030s)
237 # build & install p1 again (0.019s)
238 > go install -i p11
239 [stderr]
240 can't load package: package p11: cannot find package "p11" in any of:
241 /Users/rsc/go/src/p11 (from $GOROOT)
242 $WORK/d1/src/p11 (from $GOPATH)
243 $WORK/d2/src/p11
244 [exit status 1]
245 FAIL: unexpected go command failure
246
247 script_test.go:73: failed at testdata/script/install_rebuild_gopath.txt:15 in $WORK/gopath/src
248
249 FAIL
250 exit status 1
251 FAIL cmd/go 4.875s
252 $
253
254 $ WORK=/tmp/cmd-go-test-745953508/script-install_rebuild_gopath
255 $ cd $WORK/d1/src/p1
256 $ cat p1.go
257 package p1
258 import "p2"
259 func F() { p2.F() }
260 $
261
262 The available commands are:
263 {{.Commands}}
264
265 The available conditions are:
266 {{.Conditions}}
267 `
268
View as plain text