Skip to content

Commit

Permalink
testscript: add Params.RequireExplicitExec
Browse files Browse the repository at this point in the history
We also document how top-level commands fed to RunMain work with and
without "exec" the same way, and how RequireExplicitExec can drop
backwards compatibility for greater consistency.

Fixes #163.
  • Loading branch information
mvdan committed Jun 28, 2022
1 parent af73bbc commit c0bda00
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 8 deletions.
9 changes: 9 additions & 0 deletions testscript/cmd.go
Expand Up @@ -48,6 +48,15 @@ var scriptCmds = map[string]func(*TestScript, bool, []string){
"wait": (*TestScript).cmdWait,
}

var builtinCmds map[string]bool

func init() {
builtinCmds = make(map[string]bool, len(scriptCmds))
for name := range scriptCmds {
builtinCmds[name] = true
}
}

// cd changes to a different directory.
func (ts *TestScript) cmdCd(neg bool, args []string) {
if neg {
Expand Down
14 changes: 9 additions & 5 deletions testscript/exe.go
Expand Up @@ -42,11 +42,15 @@ func IgnoreMissedCoverage() {
// code to pass to os.Exit. It's OK for a command function to
// exit itself, but this may result in loss of coverage information.
//
// When Run is called, these commands will be available as
// testscript commands; note that these commands behave like
// commands run with the "exec" command: they set stdout
// and stderr, and can be run in the background by passing "&"
// as a final argument.
// When Run is called, these commands will work when run via "exec".
// For example, a map entry with key "foo" will be available as "exec foo".
// Just like any other "exec" command, they set stdout and stderr, and can be
// run in the background by passing "&" as a final argument.
//
// For backwards compatibility, the commands declared in the map can be run
// without "exec" - that is, "foo" will behave like "exec foo".
// This can be disabled with Params.RequireExplicitExec to keep consistency
// across test scripts, and to keep separate process executions explicit.
//
// This function returns an exit code to pass to os.Exit, after calling m.Run.
func RunMain(m TestingM, commands map[string]func() int) (exitCode int) {
Expand Down
32 changes: 32 additions & 0 deletions testscript/testdata/testscript_explicit_exec.txt
@@ -0,0 +1,32 @@
# Check that RequireExplicitExec works;
# it should reject `fprintargs` in favor of `exec fprintargs`,
# but it shouldn't complain about `some-param-cmd`,
# as that Params.Cmds entry won't work via `exec some-param-cmd`.

unquote scripts-implicit/testscript.txt
unquote scripts-explicit/testscript.txt

testscript scripts-implicit
testscript scripts-explicit

! testscript -verbose -explicit-exec scripts-implicit
testscript -verbose -explicit-exec scripts-explicit

-- scripts-implicit/testscript.txt --
>fprintargs stdout right
>cmp stdout expect
>
>some-param-cmd
>! exec some-param-cmd
>
>-- expect --
>right
-- scripts-explicit/testscript.txt --
>exec fprintargs stdout right
>cmp stdout expect
>
>some-param-cmd
>! exec some-param-cmd
>
>-- expect --
>right
5 changes: 5 additions & 0 deletions testscript/testscript.go
Expand Up @@ -155,6 +155,8 @@ type Params struct {
// a manual change will be needed if it is not unquoted in the
// script.
UpdateScripts bool

RequireExplicitExec bool
}

// RunDir runs the tests in the given directory. All files in dir with a ".txt"
Expand Down Expand Up @@ -512,6 +514,9 @@ Script:

// Run command.
cmd := scriptCmds[args[0]]
if cmd != nil && !builtinCmds[args[0]] && ts.params.RequireExplicitExec {
ts.Fatalf("use 'exec %s' rather than '%s' (as per RequireExplicitExec)", args[0], args[0])
}
if cmd == nil {
cmd = ts.params.Cmds[args[0]]
}
Expand Down
12 changes: 9 additions & 3 deletions testscript/testscript_test.go
Expand Up @@ -182,12 +182,13 @@ func TestScripts(t *testing.T) {
// Run testscript in testscript. Oooh! Meta!
fset := flag.NewFlagSet("testscript", flag.ContinueOnError)
fUpdate := fset.Bool("update", false, "update scripts when cmp fails")
fExplicitExec := fset.Bool("explicit-exec", false, "require explicit use of exec for commands")
fVerbose := fset.Bool("verbose", false, "be verbose with output")
if err := fset.Parse(args); err != nil {
ts.Fatalf("failed to parse args for testscript: %v", err)
}
if fset.NArg() != 1 {
ts.Fatalf("testscript [-verbose] [-update] <dir>")
ts.Fatalf("testscript [-verbose] [-update] [-explicit-exec] <dir>")
}
dir := fset.Arg(0)
t := &fakeT{ts: ts, verbose: *fVerbose}
Expand All @@ -200,8 +201,13 @@ func TestScripts(t *testing.T) {
}
}()
RunT(t, Params{
Dir: ts.MkAbs(dir),
UpdateScripts: *fUpdate,
Dir: ts.MkAbs(dir),
UpdateScripts: *fUpdate,
RequireExplicitExec: *fExplicitExec,
Cmds: map[string]func(ts *TestScript, neg bool, args []string){
"some-param-cmd": func(ts *TestScript, neg bool, args []string) {
},
},
})
}()
ts.stdout = strings.Replace(t.log.String(), ts.workdir, "$WORK", -1)
Expand Down

0 comments on commit c0bda00

Please sign in to comment.