1
2
3
4
5
6 package scripttest
7
8 import (
9 "bytes"
10 "cmd/internal/script"
11 "context"
12 "fmt"
13 "internal/testenv"
14 "internal/txtar"
15 "os"
16 "os/exec"
17 "path/filepath"
18 "runtime"
19 "strings"
20 "testing"
21 "time"
22 )
23
24
25
26 type ToolReplacement struct {
27 ToolName string
28 ReplacementPath string
29 EnvVar string
30 }
31
32
33
34 func NewEngine(t *testing.T, repls []ToolReplacement) (*script.Engine, []string) {
35
36
37 testenv.MustHaveGoBuild(t)
38
39
40
41 if runtime.GOOS == "plan9" {
42 t.Skipf("no symlinks on plan9")
43 }
44
45
46 gotool, err := testenv.GoTool()
47 if err != nil {
48 t.Fatalf("locating go tool: %v", err)
49 }
50
51 goEnv := func(name string) string {
52 out, err := exec.Command(gotool, "env", name).CombinedOutput()
53 if err != nil {
54 t.Fatalf("go env %s: %v\n%s", name, err, out)
55 }
56 return strings.TrimSpace(string(out))
57 }
58
59
60
61 cmds := DefaultCmds()
62 conds := DefaultConds()
63
64 addcmd := func(name string, cmd script.Cmd) {
65 if _, ok := cmds[name]; ok {
66 panic(fmt.Sprintf("command %q is already registered", name))
67 }
68 cmds[name] = cmd
69 }
70
71 prependToPath := func(env []string, dir string) {
72 found := false
73 for k := range env {
74 ev := env[k]
75 if !strings.HasPrefix(ev, "PATH=") {
76 continue
77 }
78 oldpath := ev[5:]
79 env[k] = "PATH=" + dir + string(filepath.ListSeparator) + oldpath
80 found = true
81 break
82 }
83 if !found {
84 t.Fatalf("could not update PATH")
85 }
86 }
87
88 setenv := func(env []string, varname, val string) []string {
89 pref := varname + "="
90 found := false
91 for k := range env {
92 if !strings.HasPrefix(env[k], pref) {
93 continue
94 }
95 env[k] = pref + val
96 found = true
97 break
98 }
99 if !found {
100 env = append(env, varname+"="+val)
101 }
102 return env
103 }
104
105 interrupt := func(cmd *exec.Cmd) error {
106 return cmd.Process.Signal(os.Interrupt)
107 }
108 gracePeriod := 60 * time.Second
109
110
111
112
113 goroot := goEnv("GOROOT")
114 tmpdir := t.TempDir()
115 tgr := SetupTestGoRoot(t, tmpdir, goroot)
116
117
118 for _, repl := range repls {
119 ReplaceGoToolInTestGoRoot(t, tgr, repl.ToolName, repl.ReplacementPath)
120 }
121
122
123 testgo := filepath.Join(tgr, "bin", "go")
124 gocmd := script.Program(testgo, interrupt, gracePeriod)
125 addcmd("go", gocmd)
126 cmdExec := cmds["exec"]
127 addcmd("cc", scriptCC(cmdExec, goEnv("CC")))
128
129
130 goHostOS, goHostArch := goEnv("GOHOSTOS"), goEnv("GOHOSTARCH")
131 AddToolChainScriptConditions(t, conds, goHostOS, goHostArch)
132
133
134 env := os.Environ()
135 prependToPath(env, filepath.Join(tgr, "bin"))
136 env = setenv(env, "GOROOT", tgr)
137
138 env = setenv(env, "GOOS", runtime.GOOS)
139 env = setenv(env, "GOARCH", runtime.GOARCH)
140 for _, repl := range repls {
141
142 chunks := strings.Split(repl.EnvVar, "=")
143 if len(chunks) != 2 {
144 t.Fatalf("malformed env var setting: %s", repl.EnvVar)
145 }
146 env = append(env, repl.EnvVar)
147 }
148
149
150 engine := &script.Engine{
151 Conds: conds,
152 Cmds: cmds,
153 Quiet: !testing.Verbose(),
154 }
155
156 return engine, env
157 }
158
159
160
161
162
163
164 func RunToolScriptTest(t *testing.T, repls []ToolReplacement, scriptsdir string, fixReadme bool) {
165
166 gotool, err := testenv.GoTool()
167 if err != nil {
168 t.Fatalf("locating go tool: %v", err)
169 }
170
171 engine, env := NewEngine(t, repls)
172
173 t.Run("README", func(t *testing.T) {
174 checkScriptReadme(t, engine, env, scriptsdir, gotool, fixReadme)
175 })
176
177
178 ctx := context.Background()
179 pattern := filepath.Join(scriptsdir, "*.txt")
180 RunTests(t, ctx, engine, env, pattern)
181 }
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196 func ScriptTestContext(t *testing.T, ctx context.Context) context.Context {
197 deadline, ok := t.Deadline()
198 if !ok {
199 return ctx
200 }
201
202 gracePeriod := 100 * time.Millisecond
203 timeout := time.Until(deadline)
204
205
206
207 gracePeriod = max(gracePeriod, timeout/20)
208
209
210 timeout -= 2 * gracePeriod
211
212 ctx, cancel := context.WithTimeout(ctx, timeout)
213 t.Cleanup(cancel)
214 return ctx
215 }
216
217
218
219
220
221 func RunTests(t *testing.T, ctx context.Context, engine *script.Engine, env []string, pattern string) {
222 ctx = ScriptTestContext(t, ctx)
223
224 files, _ := filepath.Glob(pattern)
225 if len(files) == 0 {
226 t.Fatal("no testdata")
227 }
228 for _, file := range files {
229 file := file
230 name := strings.TrimSuffix(filepath.Base(file), ".txt")
231 t.Run(name, func(t *testing.T) {
232 t.Parallel()
233
234 workdir := t.TempDir()
235 s, err := script.NewState(ctx, workdir, env)
236 if err != nil {
237 t.Fatal(err)
238 }
239
240
241 a, err := txtar.ParseFile(file)
242 if err != nil {
243 t.Fatal(err)
244 }
245 InitScriptDirs(t, s)
246 if err := s.ExtractFiles(a); err != nil {
247 t.Fatal(err)
248 }
249
250 t.Log(time.Now().UTC().Format(time.RFC3339))
251 work, _ := s.LookupEnv("WORK")
252 t.Logf("$WORK=%s", work)
253
254
255
256
257
258
259 Run(t, engine, s, file, bytes.NewReader(a.Comment))
260 })
261 }
262 }
263
264
265
266
267
268
269 func InitScriptDirs(t testing.TB, s *script.State) {
270 must := func(err error) {
271 if err != nil {
272 t.Helper()
273 t.Fatal(err)
274 }
275 }
276
277 work := s.Getwd()
278 must(s.Setenv("WORK", work))
279 must(os.MkdirAll(filepath.Join(work, "tmp"), 0777))
280 must(s.Setenv(tempEnvName(), filepath.Join(work, "tmp")))
281 }
282
283 func tempEnvName() string {
284 switch runtime.GOOS {
285 case "windows":
286 return "TMP"
287 case "plan9":
288 return "TMPDIR"
289 default:
290 return "TMPDIR"
291 }
292 }
293
294
295 func scriptCC(cmdExec script.Cmd, ccexe string) script.Cmd {
296 return script.Command(
297 script.CmdUsage{
298 Summary: "run the platform C compiler",
299 Args: "args...",
300 },
301 func(s *script.State, args ...string) (script.WaitFunc, error) {
302 return cmdExec.Run(s, append([]string{ccexe}, args...)...)
303 })
304 }
305
View as plain text