From e263e44e42dc0b51e32464bb85f0a3eca090c94f 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 | 1 - testscript/exe.go | 15 ++++++--- .../testdata/testscript_explicit_exec.txt | 32 +++++++++++++++++++ testscript/testscript.go | 6 ++++ testscript/testscript_test.go | 12 +++++-- 5 files changed, 57 insertions(+), 9 deletions(-) create mode 100644 testscript/testdata/testscript_explicit_exec.txt diff --git a/testscript/cmd.go b/testscript/cmd.go index 5ec4d7ec..59f12e63 100644 --- a/testscript/cmd.go +++ b/testscript/cmd.go @@ -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, diff --git a/testscript/exe.go b/testscript/exe.go index 26ce4972..3c3579e7 100644 --- a/testscript/exe.go +++ b/testscript/exe.go @@ -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) { @@ -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...)) } } 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..bd05977a 100644 --- a/testscript/testscript.go +++ b/testscript/testscript.go @@ -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" 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)