Skip to content

Commit f98815c

Browse files
rogpeppemvdan
authored andcommitted
testscript: fix error handling in setup
The new FailNow logic introduced in #192 had a flaw: it did not correctly handle failures in the setup code, as observed in #185. This PR fixes that omission.
1 parent 9957a52 commit f98815c

File tree

2 files changed

+70
-44
lines changed

2 files changed

+70
-44
lines changed

testscript/testscript.go

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,11 @@ type backgroundCmd struct {
375375
// setup sets up the test execution temporary directory and environment.
376376
// It returns the comment section of the txtar archive.
377377
func (ts *TestScript) setup() string {
378+
defer catchFailNow(func() {
379+
// There's been a failure in setup; fail immediately regardless
380+
// of the ContinueOnError flag.
381+
ts.t.FailNow()
382+
})
378383
ts.workdir = filepath.Join(ts.testTempDir, "script-"+ts.name)
379384

380385
// Establish a temporary directory in workdir, but use a prefix that ensures
@@ -575,19 +580,11 @@ func (ts *TestScript) run() {
575580
}
576581
}
577582

578-
var failNow = errors.New("fail now!")
579-
580583
func (ts *TestScript) runLine(line string) (runOK bool) {
581-
defer func() {
582-
e := recover()
583-
if e == nil {
584-
return
585-
}
586-
if e != failNow {
587-
panic(e)
588-
}
584+
defer catchFailNow(func() {
589585
runOK = false
590-
}()
586+
})
587+
591588
// Parse input line. Ignore blanks entirely.
592589
args := ts.parse(line)
593590
if len(args) == 0 {
@@ -678,6 +675,21 @@ func (ts *TestScript) applyScriptUpdates() {
678675
ts.Logf("%s updated", ts.file)
679676
}
680677

678+
var failNow = errors.New("fail now!")
679+
680+
// catchFailNow catches any panic from Fatalf and calls
681+
// f if it did so. It must be called in a defer.
682+
func catchFailNow(f func()) {
683+
e := recover()
684+
if e == nil {
685+
return
686+
}
687+
if e != failNow {
688+
panic(e)
689+
}
690+
f()
691+
}
692+
681693
// condition reports whether the given condition is satisfied.
682694
func (ts *TestScript) condition(cond string) (bool, error) {
683695
switch {

testscript/testscript_test.go

Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,31 @@ func TestEnv(t *testing.T) {
135135
}
136136
}
137137

138+
func TestSetupFailure(t *testing.T) {
139+
dir := t.TempDir()
140+
if err := os.WriteFile(filepath.Join(dir, "foo.txt"), nil, 0o666); err != nil {
141+
t.Fatal(err)
142+
}
143+
ft := &fakeT{}
144+
func() {
145+
defer catchAbort()
146+
RunT(ft, Params{
147+
Dir: dir,
148+
Setup: func(*Env) error {
149+
return fmt.Errorf("some failure")
150+
},
151+
})
152+
}()
153+
if !ft.failed {
154+
t.Fatal("test should have failed because of setup failure")
155+
}
156+
157+
want := regexp.MustCompile(`^FAIL: .*: some failure\n$`)
158+
if got := ft.log.String(); !want.MatchString(got) {
159+
t.Fatalf("expected msg to match `%v`; got:\n%q", want, got)
160+
}
161+
}
162+
138163
func TestScripts(t *testing.T) {
139164
// TODO set temp directory.
140165
testDeferCount := 0
@@ -197,15 +222,9 @@ func TestScripts(t *testing.T) {
197222
ts.Fatalf("testscript [-v] [-continue] [-update] [-explicit-exec] <dir>")
198223
}
199224
dir := fset.Arg(0)
200-
t := &fakeT{ts: ts, verbose: *fVerbose}
225+
t := &fakeT{verbose: *fVerbose}
201226
func() {
202-
defer func() {
203-
if err := recover(); err != nil {
204-
if err != errAbort {
205-
panic(err)
206-
}
207-
}
208-
}()
227+
defer catchAbort()
209228
RunT(t, Params{
210229
Dir: ts.MkAbs(dir),
211230
UpdateScripts: *fUpdate,
@@ -219,13 +238,13 @@ func TestScripts(t *testing.T) {
219238
}()
220239
ts.stdout = strings.Replace(t.log.String(), ts.workdir, "$WORK", -1)
221240
if neg {
222-
if len(t.failMsgs) == 0 {
241+
if !t.failed {
223242
ts.Fatalf("testscript unexpectedly succeeded")
224243
}
225244
return
226245
}
227-
if len(t.failMsgs) > 0 {
228-
ts.Fatalf("testscript unexpectedly failed with errors: %q", t.failMsgs)
246+
if t.failed {
247+
ts.Fatalf("testscript unexpectedly failed with errors: %q", &t.log)
229248
}
230249
},
231250
},
@@ -310,24 +329,21 @@ func TestWorkdirRoot(t *testing.T) {
310329
func TestBadDir(t *testing.T) {
311330
ft := new(fakeT)
312331
func() {
313-
defer func() {
314-
if err := recover(); err != nil {
315-
if err != errAbort {
316-
panic(err)
317-
}
318-
}
319-
}()
332+
defer catchAbort()
320333
RunT(ft, Params{
321334
Dir: "thiswillnevermatch",
322335
})
323336
}()
324-
wantCount := 1
325-
if got := len(ft.failMsgs); got != wantCount {
326-
t.Fatalf("expected %v fail message; got %v", wantCount, got)
337+
want := regexp.MustCompile(`no txtar nor txt scripts found in dir thiswillnevermatch`)
338+
if got := ft.log.String(); !want.MatchString(got) {
339+
t.Fatalf("expected msg to match `%v`; got:\n%v", want, got)
327340
}
328-
wantMsg := regexp.MustCompile(`no txtar nor txt scripts found in dir thiswillnevermatch`)
329-
if got := ft.failMsgs[0]; !wantMsg.MatchString(got) {
330-
t.Fatalf("expected msg to match `%v`; got:\n%v", wantMsg, got)
341+
}
342+
343+
// catchAbort catches the panic raised by fakeT.FailNow.
344+
func catchAbort() {
345+
if err := recover(); err != nil && err != errAbort {
346+
panic(err)
331347
}
332348
}
333349

@@ -398,11 +414,9 @@ func waitFile(ts *TestScript, neg bool, args []string) {
398414
}
399415

400416
type fakeT struct {
401-
ts *TestScript
402-
log bytes.Buffer
403-
failMsgs []string
404-
verbose bool
405-
failed bool
417+
log strings.Builder
418+
verbose bool
419+
failed bool
406420
}
407421

408422
var errAbort = errors.New("abort test")
@@ -412,9 +426,8 @@ func (t *fakeT) Skip(args ...interface{}) {
412426
}
413427

414428
func (t *fakeT) Fatal(args ...interface{}) {
415-
t.failed = true
416-
t.failMsgs = append(t.failMsgs, fmt.Sprint(args...))
417-
panic(errAbort)
429+
t.Log(args...)
430+
t.FailNow()
418431
}
419432

420433
func (t *fakeT) Parallel() {}
@@ -424,7 +437,8 @@ func (t *fakeT) Log(args ...interface{}) {
424437
}
425438

426439
func (t *fakeT) FailNow() {
427-
t.Fatal("failed")
440+
t.failed = true
441+
panic(errAbort)
428442
}
429443

430444
func (t *fakeT) Run(name string, f func(T)) {

0 commit comments

Comments
 (0)