Skip to content

Commit b143f3f

Browse files
authored
testscript: add Config.Files (#258)
This makes it possible to pass an arbitrary set of testscript files to be run instead of just a directory, making it possible for the testscript command to pass its command line arguments directly. In order to check that all the files are actually tested, we need to make the test harness implement independent subtest failure, and it's useful to see the name of the test too so that we can see the name disambiguation logic at work, which makes for changes to some of the other tests too. Note that the name deduping logic is somewhat improved from similar logic in cmd/testscript, in that it is always guaranteed to produce unique names even in the presence of filenames that look like deduped names.
1 parent 8300480 commit b143f3f

8 files changed

+111
-22
lines changed

Diff for: cmd/testscript/testdata/work.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ stderr '^temporary work directory: \Q'$WORK'\E[/\\]\.tmp[/\\]'
1313
stderr '^temporary work directory for file.txt: \Q'$WORK'\E[/\\]\.tmp[/\\]'
1414
stderr '^temporary work directory for dir[/\\]file.txt: \Q'$WORK'\E[/\\]\.tmp[/\\]'
1515
expandone $WORK/.tmp/testscript*/file.txt/script.txtar
16-
expandone $WORK/.tmp/testscript*/file.txt1/script.txtar
16+
expandone $WORK/.tmp/testscript*/file.txt#1/script.txtar
1717

1818
-- file.txt --
1919
>exec true

Diff for: testscript/testdata/big_diff.txt

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ env
77
cmpenv stdout stdout.golden
88

99
-- stdout.golden --
10+
** RUN script **
1011
> cmp a b
1112
diff a b
1213
--- a

Diff for: testscript/testdata/long_diff.txt

+1
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ cmpenv stdout stdout.golden
110110
>a
111111
>a
112112
-- stdout.golden --
113+
** RUN script **
113114
> cmp a b
114115
diff a b
115116
--- a

Diff for: testscript/testdata/testscript_explicit_files.txt

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Check that we can pass an explicit set of files to be tested.
2+
! testscript -files foo.txtar x/bar.txtar y/bar.txtar 'y/bar#1.txtar'
3+
cmpenv stdout expect-stdout
4+
-- expect-stdout --
5+
** RUN foo **
6+
PASS
7+
** RUN bar **
8+
PASS
9+
** RUN bar#1 **
10+
> echoandexit 1 '' 'bar#1 failure'
11+
[stderr]
12+
bar#1 failure
13+
FAIL: $$WORK${/}y${/}bar.txtar:1: told to exit with code 1
14+
** RUN bar#1#1 **
15+
> echoandexit 1 '' 'bar#1#1 failure'
16+
[stderr]
17+
bar#1#1 failure
18+
FAIL: $$WORK${/}y${/}bar#1.txtar:1: told to exit with code 1
19+
-- foo.txtar --
20+
echoandexit 0 '' 'foo failure'
21+
-- x/bar.txtar --
22+
echoandexit 0 '' 'bar failure'
23+
-- y/bar.txtar --
24+
echoandexit 1 '' 'bar#1 failure'
25+
-- y/bar#1.txtar --
26+
echoandexit 1 '' 'bar#1#1 failure'

Diff for: testscript/testdata/testscript_logging.txt

+4
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ printargs section5
3333
status 1
3434

3535
-- expect-stdout.txt --
36+
** RUN testscript **
3637
# comment 1 (0.000s)
3738
# comment 2 (0.000s)
3839
# comment 3 (0.000s)
@@ -43,6 +44,7 @@ status 1
4344
[exit status 1]
4445
FAIL: $$WORK${/}scripts${/}testscript.txt:9: unexpected command failure
4546
-- expect-stdout-v.txt --
47+
** RUN testscript **
4648
# comment 1 (0.000s)
4749
> printargs section1
4850
[stdout]
@@ -59,6 +61,7 @@ FAIL: $$WORK${/}scripts${/}testscript.txt:9: unexpected command failure
5961
[exit status 1]
6062
FAIL: $$WORK${/}scripts${/}testscript.txt:9: unexpected command failure
6163
-- expect-stdout-c.txt --
64+
** RUN testscript **
6265
# comment 1 (0.000s)
6366
# comment 2 (0.000s)
6467
# comment 3 (0.000s)
@@ -80,6 +83,7 @@ FAIL: $$WORK${/}scripts${/}testscript.txt:9: unexpected command failure
8083
[exit status 1]
8184
FAIL: $$WORK${/}scripts${/}testscript.txt:16: unexpected command failure
8285
-- expect-stdout-vc.txt --
86+
** RUN testscript **
8387
# comment 1 (0.000s)
8488
> printargs section1
8589
[stdout]

Diff for: testscript/testdata/testscript_stdout_stderr_error.txt

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ cmpenv stdout stdout.golden
99
> printargs hello world
1010
> echoandexit 1 'this is stdout' 'this is stderr'
1111
-- stdout.golden --
12+
** RUN testscript **
1213
> printargs hello world
1314
[stdout]
1415
["printargs" "hello" "world"]

Diff for: testscript/testscript.go

+43-15
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"regexp"
2323
"runtime"
2424
"slices"
25+
"strconv"
2526
"strings"
2627
"sync/atomic"
2728
"syscall"
@@ -136,6 +137,11 @@ type Params struct {
136137
// Dir is interpreted relative to the current test directory.
137138
Dir string
138139

140+
// Files holds a set of script filenames. If Dir is empty and this
141+
// is non-nil, these files will be used instead of reading
142+
// a directory.
143+
Files []string
144+
139145
// Setup is called, if not nil, to complete any setup required
140146
// for a test. The WorkDir and Vars fields will have already
141147
// been initialized and all the files extracted into WorkDir,
@@ -241,24 +247,29 @@ func (t tshim) Verbose() bool {
241247
// RunT is like Run but uses an interface type instead of the concrete *testing.T
242248
// type to make it possible to use testscript functionality outside of go test.
243249
func RunT(t T, p Params) {
244-
entries, err := os.ReadDir(p.Dir)
245-
if os.IsNotExist(err) {
246-
// Continue so we give a helpful error on len(files)==0 below.
247-
} else if err != nil {
248-
t.Fatal(err)
249-
}
250250
var files []string
251-
for _, entry := range entries {
252-
name := entry.Name()
253-
if strings.HasSuffix(name, ".txtar") || strings.HasSuffix(name, ".txt") {
254-
files = append(files, filepath.Join(p.Dir, name))
251+
if p.Dir == "" && p.Files != nil {
252+
files = p.Files
253+
} else {
254+
entries, err := os.ReadDir(p.Dir)
255+
if os.IsNotExist(err) {
256+
// Continue so we give a helpful error on len(files)==0 below.
257+
} else if err != nil {
258+
t.Fatal(err)
259+
}
260+
for _, entry := range entries {
261+
name := entry.Name()
262+
if strings.HasSuffix(name, ".txtar") || strings.HasSuffix(name, ".txt") {
263+
files = append(files, filepath.Join(p.Dir, name))
264+
}
255265
}
256-
}
257266

258-
if len(files) == 0 {
259-
t.Fatal(fmt.Sprintf("no txtar nor txt scripts found in dir %s", p.Dir))
267+
if len(files) == 0 {
268+
t.Fatal(fmt.Sprintf("no txtar nor txt scripts found in dir %s", p.Dir))
269+
}
260270
}
261271
testTempDir := p.WorkdirRoot
272+
var err error
262273
if testTempDir == "" {
263274
testTempDir, err = os.MkdirTemp(os.Getenv("GOTMPDIR"), "go-test-script")
264275
if err != nil {
@@ -307,10 +318,27 @@ func RunT(t T, p Params) {
307318
}
308319

309320
refCount := int32(len(files))
321+
names := make(map[string]bool)
310322
for _, file := range files {
311323
file := file
312-
name := strings.TrimSuffix(filepath.Base(file), ".txt")
313-
name = strings.TrimSuffix(name, ".txtar")
324+
name := filepath.Base(file)
325+
if name1, ok := strings.CutSuffix(name, ".txt"); ok {
326+
name = name1
327+
} else if name1, ok := strings.CutSuffix(name, ".txtar"); ok {
328+
name = name1
329+
}
330+
// We can have duplicate names when files are passed explicitly,
331+
// so disambiguate by adding a counter.
332+
// Take care to handle the situation where a name with a counter-like
333+
// suffix already exists, for example:
334+
// a/foo.txt
335+
// b/foo.txtar
336+
// c/foo#1.txt
337+
prefix := name
338+
for i := 1; names[name]; i++ {
339+
name = prefix + "#" + strconv.Itoa(i)
340+
}
341+
names[name] = true
314342
t.Run(name, func(t T) {
315343
t.Parallel()
316344
ts := &TestScript{

Diff for: testscript/testscript_test.go

+34-6
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ func TestSetupFailure(t *testing.T) {
164164
t.Fatal("test should have failed because of setup failure")
165165
}
166166

167-
want := regexp.MustCompile(`^FAIL: .*: some failure\n$`)
167+
want := regexp.MustCompile(`\nFAIL: .*: some failure\n$`)
168168
if got := ft.log.String(); !want.MatchString(got) {
169169
t.Fatalf("expected msg to match `%v`; got:\n%q", want, got)
170170
}
@@ -226,18 +226,28 @@ func TestScripts(t *testing.T) {
226226
fUniqueNames := fset.Bool("unique-names", false, "require unique names in txtar archive")
227227
fVerbose := fset.Bool("v", false, "be verbose with output")
228228
fContinue := fset.Bool("continue", false, "continue on error")
229+
fFiles := fset.Bool("files", false, "specify files rather than a directory")
229230
if err := fset.Parse(args); err != nil {
230231
ts.Fatalf("failed to parse args for testscript: %v", err)
231232
}
232-
if fset.NArg() != 1 {
233-
ts.Fatalf("testscript [-v] [-continue] [-update] [-explicit-exec] <dir>")
233+
if fset.NArg() != 1 && !*fFiles {
234+
ts.Fatalf("testscript [-v] [-continue] [-update] [-explicit-exec] [-files] <dir>|<file>...")
235+
}
236+
var files []string
237+
var dir string
238+
if *fFiles {
239+
for _, f := range fset.Args() {
240+
files = append(files, ts.MkAbs(f))
241+
}
242+
} else {
243+
dir = ts.MkAbs(fset.Arg(0))
234244
}
235-
dir := fset.Arg(0)
236245
t := &fakeT{verbose: *fVerbose}
237246
func() {
238247
defer catchAbort()
239248
RunT(t, Params{
240-
Dir: ts.MkAbs(dir),
249+
Dir: dir,
250+
Files: files,
241251
UpdateScripts: *fUpdate,
242252
RequireExplicitExec: *fExplicitExec,
243253
RequireUniqueNames: *fUniqueNames,
@@ -502,9 +512,27 @@ func (t *fakeT) FailNow() {
502512
}
503513

504514
func (t *fakeT) Run(name string, f func(T)) {
505-
f(t)
515+
fmt.Fprintf(&t.log, "** RUN %s **\n", name)
516+
defer catchAbort()
517+
f(&subT{
518+
fakeT: t,
519+
})
506520
}
507521

508522
func (t *fakeT) Verbose() bool {
509523
return t.verbose
510524
}
525+
526+
type subT struct {
527+
*fakeT
528+
failed bool
529+
}
530+
531+
func (t *subT) Run(name string, f func(T)) {
532+
panic("multiple test levels not supported")
533+
}
534+
535+
func (t *subT) FailNow() {
536+
t.failed = true
537+
t.fakeT.FailNow()
538+
}

0 commit comments

Comments
 (0)