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