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 Jul 6, 2022
1 parent af73bbc commit e263e44
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 9 deletions.
1 change: 0 additions & 1 deletion testscript/cmd.go
Expand Up @@ -24,7 +24,6 @@ import (
// Keep list and the implementations below sorted by name.
//
// NOTE: If you make changes here, update doc.go.
//
var scriptCmds = map[string]func(*TestScript, bool, []string){
"cd": (*TestScript).cmdCd,
"chmod": (*TestScript).cmdChmod,
Expand Down
15 changes: 10 additions & 5 deletions testscript/exe.go
Expand Up @@ -42,11 +42,13 @@ 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 are installed as regular commands in the shell
// path, so can be invoked with "exec" or via any other command (for example a shell script).
//
// 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 Expand Up @@ -119,6 +121,9 @@ func RunMain(m TestingM, commands map[string]func() int) (exitCode int) {
return 2
}
scriptCmds[name] = func(ts *TestScript, neg bool, args []string) {
if ts.params.RequireExplicitExec {
ts.Fatalf("use 'exec %s' rather than '%s' (because RequireExplicitExec is enabled)", args[0], args[0])
}
ts.cmdExec(neg, append([]string{name}, args...))
}
}
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
6 changes: 6 additions & 0 deletions testscript/testscript.go
Expand Up @@ -155,6 +155,12 @@ type Params struct {
// a manual change will be needed if it is not unquoted in the
// script.
UpdateScripts bool

// RequireExplicitExec requires that commands passed to RunMain must be used
// in test scripts via `exec cmd` and not simply `cmd`. This can help keep
// consistency across test scripts as well as keep separate process
// executions explicit.
RequireExplicitExec bool
}

// RunDir runs the tests in the given directory. All files in dir with a ".txt"
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 e263e44

Please sign in to comment.