Skip to content

Commit

Permalink
testscript: add Config.Files (#258)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
rogpeppe authored Jun 11, 2024
1 parent 8300480 commit b143f3f
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 22 deletions.
2 changes: 1 addition & 1 deletion cmd/testscript/testdata/work.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ stderr '^temporary work directory: \Q'$WORK'\E[/\\]\.tmp[/\\]'
stderr '^temporary work directory for file.txt: \Q'$WORK'\E[/\\]\.tmp[/\\]'
stderr '^temporary work directory for dir[/\\]file.txt: \Q'$WORK'\E[/\\]\.tmp[/\\]'
expandone $WORK/.tmp/testscript*/file.txt/script.txtar
expandone $WORK/.tmp/testscript*/file.txt1/script.txtar
expandone $WORK/.tmp/testscript*/file.txt#1/script.txtar

-- file.txt --
>exec true
Expand Down
1 change: 1 addition & 0 deletions testscript/testdata/big_diff.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ env
cmpenv stdout stdout.golden

-- stdout.golden --
** RUN script **
> cmp a b
diff a b
--- a
Expand Down
1 change: 1 addition & 0 deletions testscript/testdata/long_diff.txt
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ cmpenv stdout stdout.golden
>a
>a
-- stdout.golden --
** RUN script **
> cmp a b
diff a b
--- a
Expand Down
26 changes: 26 additions & 0 deletions testscript/testdata/testscript_explicit_files.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Check that we can pass an explicit set of files to be tested.
! testscript -files foo.txtar x/bar.txtar y/bar.txtar 'y/bar#1.txtar'
cmpenv stdout expect-stdout
-- expect-stdout --
** RUN foo **
PASS
** RUN bar **
PASS
** RUN bar#1 **
> echoandexit 1 '' 'bar#1 failure'
[stderr]
bar#1 failure
FAIL: $$WORK${/}y${/}bar.txtar:1: told to exit with code 1
** RUN bar#1#1 **
> echoandexit 1 '' 'bar#1#1 failure'
[stderr]
bar#1#1 failure
FAIL: $$WORK${/}y${/}bar#1.txtar:1: told to exit with code 1
-- foo.txtar --
echoandexit 0 '' 'foo failure'
-- x/bar.txtar --
echoandexit 0 '' 'bar failure'
-- y/bar.txtar --
echoandexit 1 '' 'bar#1 failure'
-- y/bar#1.txtar --
echoandexit 1 '' 'bar#1#1 failure'
4 changes: 4 additions & 0 deletions testscript/testdata/testscript_logging.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ printargs section5
status 1

-- expect-stdout.txt --
** RUN testscript **
# comment 1 (0.000s)
# comment 2 (0.000s)
# comment 3 (0.000s)
Expand All @@ -43,6 +44,7 @@ status 1
[exit status 1]
FAIL: $$WORK${/}scripts${/}testscript.txt:9: unexpected command failure
-- expect-stdout-v.txt --
** RUN testscript **
# comment 1 (0.000s)
> printargs section1
[stdout]
Expand All @@ -59,6 +61,7 @@ FAIL: $$WORK${/}scripts${/}testscript.txt:9: unexpected command failure
[exit status 1]
FAIL: $$WORK${/}scripts${/}testscript.txt:9: unexpected command failure
-- expect-stdout-c.txt --
** RUN testscript **
# comment 1 (0.000s)
# comment 2 (0.000s)
# comment 3 (0.000s)
Expand All @@ -80,6 +83,7 @@ FAIL: $$WORK${/}scripts${/}testscript.txt:9: unexpected command failure
[exit status 1]
FAIL: $$WORK${/}scripts${/}testscript.txt:16: unexpected command failure
-- expect-stdout-vc.txt --
** RUN testscript **
# comment 1 (0.000s)
> printargs section1
[stdout]
Expand Down
1 change: 1 addition & 0 deletions testscript/testdata/testscript_stdout_stderr_error.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ cmpenv stdout stdout.golden
> printargs hello world
> echoandexit 1 'this is stdout' 'this is stderr'
-- stdout.golden --
** RUN testscript **
> printargs hello world
[stdout]
["printargs" "hello" "world"]
Expand Down
58 changes: 43 additions & 15 deletions testscript/testscript.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"regexp"
"runtime"
"slices"
"strconv"
"strings"
"sync/atomic"
"syscall"
Expand Down Expand Up @@ -136,6 +137,11 @@ type Params struct {
// Dir is interpreted relative to the current test directory.
Dir string

// Files holds a set of script filenames. If Dir is empty and this
// is non-nil, these files will be used instead of reading
// a directory.
Files []string

// Setup is called, if not nil, to complete any setup required
// for a test. The WorkDir and Vars fields will have already
// been initialized and all the files extracted into WorkDir,
Expand Down Expand Up @@ -241,24 +247,29 @@ func (t tshim) Verbose() bool {
// RunT is like Run but uses an interface type instead of the concrete *testing.T
// type to make it possible to use testscript functionality outside of go test.
func RunT(t T, p Params) {
entries, err := os.ReadDir(p.Dir)
if os.IsNotExist(err) {
// Continue so we give a helpful error on len(files)==0 below.
} else if err != nil {
t.Fatal(err)
}
var files []string
for _, entry := range entries {
name := entry.Name()
if strings.HasSuffix(name, ".txtar") || strings.HasSuffix(name, ".txt") {
files = append(files, filepath.Join(p.Dir, name))
if p.Dir == "" && p.Files != nil {
files = p.Files
} else {
entries, err := os.ReadDir(p.Dir)
if os.IsNotExist(err) {
// Continue so we give a helpful error on len(files)==0 below.
} else if err != nil {
t.Fatal(err)
}
for _, entry := range entries {
name := entry.Name()
if strings.HasSuffix(name, ".txtar") || strings.HasSuffix(name, ".txt") {
files = append(files, filepath.Join(p.Dir, name))
}
}
}

if len(files) == 0 {
t.Fatal(fmt.Sprintf("no txtar nor txt scripts found in dir %s", p.Dir))
if len(files) == 0 {
t.Fatal(fmt.Sprintf("no txtar nor txt scripts found in dir %s", p.Dir))
}
}
testTempDir := p.WorkdirRoot
var err error
if testTempDir == "" {
testTempDir, err = os.MkdirTemp(os.Getenv("GOTMPDIR"), "go-test-script")
if err != nil {
Expand Down Expand Up @@ -307,10 +318,27 @@ func RunT(t T, p Params) {
}

refCount := int32(len(files))
names := make(map[string]bool)
for _, file := range files {
file := file
name := strings.TrimSuffix(filepath.Base(file), ".txt")
name = strings.TrimSuffix(name, ".txtar")
name := filepath.Base(file)
if name1, ok := strings.CutSuffix(name, ".txt"); ok {
name = name1
} else if name1, ok := strings.CutSuffix(name, ".txtar"); ok {
name = name1
}
// We can have duplicate names when files are passed explicitly,
// so disambiguate by adding a counter.
// Take care to handle the situation where a name with a counter-like
// suffix already exists, for example:
// a/foo.txt
// b/foo.txtar
// c/foo#1.txt
prefix := name
for i := 1; names[name]; i++ {
name = prefix + "#" + strconv.Itoa(i)
}
names[name] = true
t.Run(name, func(t T) {
t.Parallel()
ts := &TestScript{
Expand Down
40 changes: 34 additions & 6 deletions testscript/testscript_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ func TestSetupFailure(t *testing.T) {
t.Fatal("test should have failed because of setup failure")
}

want := regexp.MustCompile(`^FAIL: .*: some failure\n$`)
want := regexp.MustCompile(`\nFAIL: .*: some failure\n$`)
if got := ft.log.String(); !want.MatchString(got) {
t.Fatalf("expected msg to match `%v`; got:\n%q", want, got)
}
Expand Down Expand Up @@ -226,18 +226,28 @@ func TestScripts(t *testing.T) {
fUniqueNames := fset.Bool("unique-names", false, "require unique names in txtar archive")
fVerbose := fset.Bool("v", false, "be verbose with output")
fContinue := fset.Bool("continue", false, "continue on error")
fFiles := fset.Bool("files", false, "specify files rather than a directory")
if err := fset.Parse(args); err != nil {
ts.Fatalf("failed to parse args for testscript: %v", err)
}
if fset.NArg() != 1 {
ts.Fatalf("testscript [-v] [-continue] [-update] [-explicit-exec] <dir>")
if fset.NArg() != 1 && !*fFiles {
ts.Fatalf("testscript [-v] [-continue] [-update] [-explicit-exec] [-files] <dir>|<file>...")
}
var files []string
var dir string
if *fFiles {
for _, f := range fset.Args() {
files = append(files, ts.MkAbs(f))
}
} else {
dir = ts.MkAbs(fset.Arg(0))
}
dir := fset.Arg(0)
t := &fakeT{verbose: *fVerbose}
func() {
defer catchAbort()
RunT(t, Params{
Dir: ts.MkAbs(dir),
Dir: dir,
Files: files,
UpdateScripts: *fUpdate,
RequireExplicitExec: *fExplicitExec,
RequireUniqueNames: *fUniqueNames,
Expand Down Expand Up @@ -502,9 +512,27 @@ func (t *fakeT) FailNow() {
}

func (t *fakeT) Run(name string, f func(T)) {
f(t)
fmt.Fprintf(&t.log, "** RUN %s **\n", name)
defer catchAbort()
f(&subT{
fakeT: t,
})
}

func (t *fakeT) Verbose() bool {
return t.verbose
}

type subT struct {
*fakeT
failed bool
}

func (t *subT) Run(name string, f func(T)) {
panic("multiple test levels not supported")
}

func (t *subT) FailNow() {
t.failed = true
t.fakeT.FailNow()
}

0 comments on commit b143f3f

Please sign in to comment.