From c0bda009acf0ce65b9a9ebe9c671867b283529f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Tue, 28 Jun 2022 16:40:03 +0100 Subject: [PATCH] testscript: add Params.RequireExplicitExec 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. --- testscript/cmd.go | 9 ++++++ testscript/exe.go | 14 +++++--- .../testdata/testscript_explicit_exec.txt | 32 +++++++++++++++++++ testscript/testscript.go | 5 +++ testscript/testscript_test.go | 12 +++++-- 5 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 testscript/testdata/testscript_explicit_exec.txt diff --git a/testscript/cmd.go b/testscript/cmd.go index 5ec4d7ec..56bbf972 100644 --- a/testscript/cmd.go +++ b/testscript/cmd.go @@ -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 { diff --git a/testscript/exe.go b/testscript/exe.go index 26ce4972..462b72a3 100644 --- a/testscript/exe.go +++ b/testscript/exe.go @@ -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) { diff --git a/testscript/testdata/testscript_explicit_exec.txt b/testscript/testdata/testscript_explicit_exec.txt new file mode 100644 index 00000000..c2fc0a6a --- /dev/null +++ b/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 diff --git a/testscript/testscript.go b/testscript/testscript.go index 54f3ccf9..7e0f4913 100644 --- a/testscript/testscript.go +++ b/testscript/testscript.go @@ -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" @@ -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]] } diff --git a/testscript/testscript_test.go b/testscript/testscript_test.go index 401c86bb..e8faee04 100644 --- a/testscript/testscript_test.go +++ b/testscript/testscript_test.go @@ -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] ") + ts.Fatalf("testscript [-verbose] [-update] [-explicit-exec] ") } dir := fset.Arg(0) t := &fakeT{ts: ts, verbose: *fVerbose} @@ -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)