1
2
3
4
5 package testscript
6
7 import (
8 "bytes"
9 "errors"
10 "flag"
11 "fmt"
12 "io/ioutil"
13 "os"
14 "os/exec"
15 "os/signal"
16 "path/filepath"
17 "reflect"
18 "regexp"
19 "strconv"
20 "strings"
21 "testing"
22 "time"
23 )
24
25 func printArgs() int {
26 fmt.Printf("%q\n", os.Args)
27 return 0
28 }
29
30 func fprintArgs() int {
31 s := strings.Join(os.Args[2:], " ")
32 switch os.Args[1] {
33 case "stdout":
34 fmt.Println(s)
35 case "stderr":
36 fmt.Fprintln(os.Stderr, s)
37 }
38 return 0
39 }
40
41 func exitWithStatus() int {
42 n, _ := strconv.Atoi(os.Args[1])
43 return n
44 }
45
46 func signalCatcher() int {
47
48 c := make(chan os.Signal, 1)
49 signal.Notify(c, os.Interrupt)
50
51
52 if err := ioutil.WriteFile("catchsignal", nil, 0o666); err != nil {
53 fmt.Println(err)
54 return 1
55 }
56 <-c
57 fmt.Println("caught interrupt")
58 return 0
59 }
60
61 func terminalPrompt() int {
62 tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
63 if err != nil {
64 fmt.Println(err)
65 return 1
66 }
67 tty.WriteString("The magic words are: ")
68 var words string
69 fmt.Fscanln(tty, &words)
70 if words != "SQUEAMISHOSSIFRAGE" {
71 fmt.Println(words)
72 return 42
73 }
74 return 0
75 }
76
77 func TestMain(m *testing.M) {
78 timeSince = func(t time.Time) time.Duration {
79 return 0
80 }
81
82 showVerboseEnv = false
83 os.Exit(RunMain(m, map[string]func() int{
84 "printargs": printArgs,
85 "fprintargs": fprintArgs,
86 "status": exitWithStatus,
87 "signalcatcher": signalCatcher,
88 "terminalprompt": terminalPrompt,
89 }))
90 }
91
92 func TestCRLFInput(t *testing.T) {
93 td, err := ioutil.TempDir("", "")
94 if err != nil {
95 t.Fatalf("failed to create TempDir: %v", err)
96 }
97 defer func() {
98 os.RemoveAll(td)
99 }()
100 tf := filepath.Join(td, "script.txt")
101 contents := []byte("exists output.txt\r\n-- output.txt --\r\noutput contents")
102 if err := ioutil.WriteFile(tf, contents, 0o644); err != nil {
103 t.Fatalf("failed to write to %v: %v", tf, err)
104 }
105 t.Run("_", func(t *testing.T) {
106 Run(t, Params{Dir: td})
107 })
108 }
109
110 func TestEnv(t *testing.T) {
111 e := &Env{
112 Vars: []string{
113 "HOME=/no-home",
114 "PATH=/usr/bin",
115 "PATH=/usr/bin:/usr/local/bin",
116 "INVALID",
117 },
118 }
119
120 if got, want := e.Getenv("HOME"), "/no-home"; got != want {
121 t.Errorf("e.Getenv(\"HOME\") == %q, want %q", got, want)
122 }
123
124 e.Setenv("HOME", "/home/user")
125 if got, want := e.Getenv("HOME"), "/home/user"; got != want {
126 t.Errorf(`e.Getenv("HOME") == %q, want %q`, got, want)
127 }
128
129 if got, want := e.Getenv("PATH"), "/usr/bin:/usr/local/bin"; got != want {
130 t.Errorf(`e.Getenv("PATH") == %q, want %q`, got, want)
131 }
132
133 if got, want := e.Getenv("INVALID"), ""; got != want {
134 t.Errorf(`e.Getenv("INVALID") == %q, want %q`, got, want)
135 }
136
137 for _, key := range []string{
138 "",
139 "=",
140 "key=invalid",
141 } {
142 var panicValue interface{}
143 func() {
144 defer func() {
145 panicValue = recover()
146 }()
147 e.Setenv(key, "")
148 }()
149 if panicValue == nil {
150 t.Errorf("e.Setenv(%q) did not panic, want panic", key)
151 }
152 }
153 }
154
155 func TestSetupFailure(t *testing.T) {
156 dir := t.TempDir()
157 if err := os.WriteFile(filepath.Join(dir, "foo.txt"), nil, 0o666); err != nil {
158 t.Fatal(err)
159 }
160 ft := &fakeT{}
161 func() {
162 defer catchAbort()
163 RunT(ft, Params{
164 Dir: dir,
165 Setup: func(*Env) error {
166 return fmt.Errorf("some failure")
167 },
168 })
169 }()
170 if !ft.failed {
171 t.Fatal("test should have failed because of setup failure")
172 }
173
174 want := regexp.MustCompile(`^FAIL: .*: some failure\n$`)
175 if got := ft.log.String(); !want.MatchString(got) {
176 t.Fatalf("expected msg to match `%v`; got:\n%q", want, got)
177 }
178 }
179
180 func TestScripts(t *testing.T) {
181
182 testDeferCount := 0
183 Run(t, Params{
184 UpdateScripts: os.Getenv("TESTSCRIPT_UPDATE") != "",
185 Dir: "testdata",
186 Cmds: map[string]func(ts *TestScript, neg bool, args []string){
187 "setSpecialVal": setSpecialVal,
188 "ensureSpecialVal": ensureSpecialVal,
189 "interrupt": interrupt,
190 "waitfile": waitFile,
191 "testdefer": func(ts *TestScript, neg bool, args []string) {
192 testDeferCount++
193 n := testDeferCount
194 ts.Defer(func() {
195 if testDeferCount != n {
196 t.Errorf("defers not run in reverse order; got %d want %d", testDeferCount, n)
197 }
198 testDeferCount--
199 })
200 },
201 "setup-filenames": func(ts *TestScript, neg bool, want []string) {
202 got := ts.Value("setupFilenames")
203 if !reflect.DeepEqual(want, got) {
204 ts.Fatalf("setup did not see expected files; got %q want %q", got, want)
205 }
206 },
207 "test-values": func(ts *TestScript, neg bool, args []string) {
208 if ts.Value("somekey") != 1234 {
209 ts.Fatalf("test-values did not see expected value")
210 }
211 if ts.Value("t").(T) != ts.t {
212 ts.Fatalf("test-values did not see expected t")
213 }
214 if _, ok := ts.Value("t").(testing.TB); !ok {
215 ts.Fatalf("test-values t does not implement testing.TB")
216 }
217 },
218 "testreadfile": func(ts *TestScript, neg bool, args []string) {
219 if len(args) != 1 {
220 ts.Fatalf("testreadfile <filename>")
221 }
222 got := ts.ReadFile(args[0])
223 want := args[0] + "\n"
224 if got != want {
225 ts.Fatalf("reading %q; got %q want %q", args[0], got, want)
226 }
227 },
228 "testscript": func(ts *TestScript, neg bool, args []string) {
229
230 fset := flag.NewFlagSet("testscript", flag.ContinueOnError)
231 fUpdate := fset.Bool("update", false, "update scripts when cmp fails")
232 fExplicitExec := fset.Bool("explicit-exec", false, "require explicit use of exec for commands")
233 fUniqueNames := fset.Bool("unique-names", false, "require unique names in txtar archive")
234 fVerbose := fset.Bool("v", false, "be verbose with output")
235 fContinue := fset.Bool("continue", false, "continue on error")
236 if err := fset.Parse(args); err != nil {
237 ts.Fatalf("failed to parse args for testscript: %v", err)
238 }
239 if fset.NArg() != 1 {
240 ts.Fatalf("testscript [-v] [-continue] [-update] [-explicit-exec] <dir>")
241 }
242 dir := fset.Arg(0)
243 t := &fakeT{verbose: *fVerbose}
244 func() {
245 defer catchAbort()
246 RunT(t, Params{
247 Dir: ts.MkAbs(dir),
248 UpdateScripts: *fUpdate,
249 RequireExplicitExec: *fExplicitExec,
250 RequireUniqueNames: *fUniqueNames,
251 Cmds: map[string]func(ts *TestScript, neg bool, args []string){
252 "some-param-cmd": func(ts *TestScript, neg bool, args []string) {
253 },
254 "echoandexit": echoandexit,
255 },
256 ContinueOnError: *fContinue,
257 })
258 }()
259 stdout := t.log.String()
260 stdout = strings.ReplaceAll(stdout, ts.workdir, "$WORK")
261 fmt.Fprint(ts.Stdout(), stdout)
262 if neg {
263 if !t.failed {
264 ts.Fatalf("testscript unexpectedly succeeded")
265 }
266 return
267 }
268 if t.failed {
269 ts.Fatalf("testscript unexpectedly failed with errors: %q", &t.log)
270 }
271 },
272 "echoandexit": echoandexit,
273 },
274 Setup: func(env *Env) error {
275 infos, err := ioutil.ReadDir(env.WorkDir)
276 if err != nil {
277 return fmt.Errorf("cannot read workdir: %v", err)
278 }
279 var setupFilenames []string
280 for _, info := range infos {
281 setupFilenames = append(setupFilenames, info.Name())
282 }
283 env.Values["setupFilenames"] = setupFilenames
284 env.Values["somekey"] = 1234
285 env.Values["t"] = env.T()
286 env.Vars = append(env.Vars,
287 "GONOSUMDB=*",
288 )
289 return nil
290 },
291 })
292 if testDeferCount != 0 {
293 t.Fatalf("defer mismatch; got %d want 0", testDeferCount)
294 }
295
296 }
297
298 func echoandexit(ts *TestScript, neg bool, args []string) {
299
300
301
302
303
304 if len(args) == 0 || len(args) > 3 {
305 ts.Fatalf("echoandexit takes at least one and at most three arguments")
306 }
307 if neg {
308 ts.Fatalf("neg means nothing for echoandexit")
309 }
310 exitCode, err := strconv.ParseInt(args[0], 10, 64)
311 if err != nil {
312 ts.Fatalf("failed to parse exit code from %q: %v", args[0], err)
313 }
314 if len(args) > 1 && args[1] != "" {
315 fmt.Fprint(ts.Stdout(), args[1])
316 }
317 if len(args) > 2 && args[2] != "" {
318 fmt.Fprint(ts.Stderr(), args[2])
319 }
320 if exitCode != 0 {
321 ts.Fatalf("told to exit with code %d", exitCode)
322 }
323 }
324
325
326
327 func TestTestwork(t *testing.T) {
328 out, err := exec.Command("go", "test", ".", "-testwork", "-v", "-run", "TestScripts/^nothing$").CombinedOutput()
329 if err != nil {
330 t.Fatal(err)
331 }
332
333 re := regexp.MustCompile(`\s+WORK=(\S+)`)
334 match := re.FindAllStringSubmatch(string(out), -1)
335
336
337 if len(match) != 1 || len(match[0]) != 2 {
338 t.Fatalf("failed to extract WORK directory")
339 }
340
341 var fi os.FileInfo
342 if fi, err = os.Stat(match[0][1]); err != nil {
343 t.Fatalf("failed to stat expected work directory %v: %v", match[0][1], err)
344 }
345
346 if !fi.IsDir() {
347 t.Fatalf("expected persisted workdir is not a directory: %v", match[0][1])
348 }
349 }
350
351
352 func TestWorkdirRoot(t *testing.T) {
353 td, err := ioutil.TempDir("", "")
354 if err != nil {
355 t.Fatalf("failed to create temp dir: %v", err)
356 }
357 defer os.RemoveAll(td)
358 params := Params{
359 Dir: filepath.Join("testdata", "nothing"),
360 WorkdirRoot: td,
361 }
362
363
364 t.Run("run tests", func(t *testing.T) {
365 Run(t, params)
366 })
367
368 files, err := filepath.Glob(filepath.Join(td, "script-nothing", "README.md"))
369 if err != nil {
370 t.Fatal(err)
371 }
372 if len(files) != 1 {
373 t.Fatalf("unexpected files found for kept files; got %q", files)
374 }
375 }
376
377
378
379 func TestBadDir(t *testing.T) {
380 ft := new(fakeT)
381 func() {
382 defer catchAbort()
383 RunT(ft, Params{
384 Dir: "thiswillnevermatch",
385 })
386 }()
387 want := regexp.MustCompile(`no txtar nor txt scripts found in dir thiswillnevermatch`)
388 if got := ft.log.String(); !want.MatchString(got) {
389 t.Fatalf("expected msg to match `%v`; got:\n%v", want, got)
390 }
391 }
392
393
394 func catchAbort() {
395 if err := recover(); err != nil && err != errAbort {
396 panic(err)
397 }
398 }
399
400 func TestUNIX2DOS(t *testing.T) {
401 for data, want := range map[string]string{
402 "": "",
403 "\n": "\r\n",
404 "\r\n": "\r\n",
405 "a": "a\r\n",
406 "a\n": "a\r\n",
407 "a\r\n": "a\r\n",
408 "a\nb\n": "a\r\nb\r\n",
409 "a\r\nb\n": "a\r\nb\r\n",
410 "a\nb\r\n": "a\r\nb\r\n",
411 } {
412 if got, err := unix2DOS([]byte(data)); err != nil || !bytes.Equal(got, []byte(want)) {
413 t.Errorf("unix2DOS(%q) == %q, %v, want %q, nil", data, got, err, want)
414 }
415 }
416 }
417
418 func setSpecialVal(ts *TestScript, neg bool, args []string) {
419 ts.Setenv("SPECIALVAL", "42")
420 }
421
422 func ensureSpecialVal(ts *TestScript, neg bool, args []string) {
423 want := "42"
424 if got := ts.Getenv("SPECIALVAL"); got != want {
425 ts.Fatalf("expected SPECIALVAL to be %q; got %q", want, got)
426 }
427 }
428
429
430
431 func interrupt(ts *TestScript, neg bool, args []string) {
432 if neg {
433 ts.Fatalf("interrupt does not support neg")
434 }
435 if len(args) > 0 {
436 ts.Fatalf("unexpected args found")
437 }
438 bg := ts.BackgroundCmds()
439 if got, want := len(bg), 1; got != want {
440 ts.Fatalf("unexpected background cmd count; got %d want %d", got, want)
441 }
442 bg[0].Process.Signal(os.Interrupt)
443 }
444
445 func waitFile(ts *TestScript, neg bool, args []string) {
446 if neg {
447 ts.Fatalf("waitfile does not support neg")
448 }
449 if len(args) != 1 {
450 ts.Fatalf("usage: waitfile file")
451 }
452 path := ts.MkAbs(args[0])
453 for i := 0; i < 100; i++ {
454 _, err := os.Stat(path)
455 if err == nil {
456 return
457 }
458 if !os.IsNotExist(err) {
459 ts.Fatalf("unexpected stat error: %v", err)
460 }
461 time.Sleep(10 * time.Millisecond)
462 }
463 ts.Fatalf("timed out waiting for %q to be created", path)
464 }
465
466 type fakeT struct {
467 log strings.Builder
468 verbose bool
469 failed bool
470 }
471
472 var errAbort = errors.New("abort test")
473
474 func (t *fakeT) Skip(args ...interface{}) {
475 panic(errAbort)
476 }
477
478 func (t *fakeT) Fatal(args ...interface{}) {
479 t.Log(args...)
480 t.FailNow()
481 }
482
483 func (t *fakeT) Parallel() {}
484
485 func (t *fakeT) Log(args ...interface{}) {
486 fmt.Fprint(&t.log, args...)
487 }
488
489 func (t *fakeT) FailNow() {
490 t.failed = true
491 panic(errAbort)
492 }
493
494 func (t *fakeT) Run(name string, f func(T)) {
495 f(t)
496 }
497
498 func (t *fakeT) Verbose() bool {
499 return t.verbose
500 }
501
502 func (t *fakeT) Failed() bool {
503 return t.failed
504 }
505
View as plain text