diff --git a/cmd/testscript/README.md b/cmd/testscript/README.md index 5207068b..41cc3890 100644 --- a/cmd/testscript/README.md +++ b/cmd/testscript/README.md @@ -3,7 +3,7 @@ The testscript command runs github.com/rogpeppe/go-internal/testscript scripts in a fresh temporary work directory tree. Usage: - testscript [-v] files... + testscript [-v] [-e VAR]... [-u] files... The testscript command is designed to make it easy to create self-contained reproductions of command sequences. @@ -20,6 +20,17 @@ proxy server. See the documentation for github.com/rogpeppe/go-internal/goproxytest for details on the format of these files/directories. +Environment variables can be passed through to each script with the -e flag, +where VAR is the name of the variable. Variables override testscript-defined +values, with the exception of WORK which cannot be overridden. The -e flag can +appear multiple times to specify multiple variables. + +The -u flag specifies that if a cmp command within a testscript fails and its +second argument refers to a file inside the testscript file, the command will +succeed and the testscript file will be updated to reflect the actual content. +As such, this is the cmd/testcript equivalent of +testscript.Params.UpdateScripts. + Examples ======== diff --git a/cmd/testscript/help.go b/cmd/testscript/help.go index 968e522b..2340668b 100644 --- a/cmd/testscript/help.go +++ b/cmd/testscript/help.go @@ -14,7 +14,7 @@ The testscript command runs github.com/rogpeppe/go-internal/testscript scripts in a fresh temporary work directory tree. Usage: - testscript [-v] [-e VAR]... files... + testscript [-v] [-e VAR]... [-u] files... The testscript command is designed to make it easy to create self-contained reproductions of command sequences. @@ -36,6 +36,12 @@ where VAR is the name of the variable. Variables override testscript-defined values, with the exception of WORK which cannot be overridden. The -e flag can appear multiple times to specify multiple variables. +The -u flag specifies that if a cmp command within a testscript fails and its +second argument refers to a file inside the testscript file, the command will +succeed and the testscript file will be updated to reflect the actual content. +As such, this is the cmd/testcript equivalent of +testscript.Params.UpdateScripts. + Examples ======== diff --git a/cmd/testscript/main.go b/cmd/testscript/main.go index 8c3872f4..7d73d484 100644 --- a/cmd/testscript/main.go +++ b/cmd/testscript/main.go @@ -62,6 +62,7 @@ func mainerr() (retErr error) { mainUsage(os.Stderr) } var envVars envVarsFlag + fUpdate := fs.Bool("u", false, "update archive file if a cmp fails") fWork := fs.Bool("work", false, "print temporary work directory and do not remove when done") fVerbose := fs.Bool("v", false, "run tests verbosely") fs.Var(&envVars, "e", "pass through environment variable to script (can appear multiple times)") @@ -84,6 +85,20 @@ func mainerr() (retErr error) { files = []string{"-"} } + // If we are only reading from stdin, -u cannot be specified. It seems a bit + // bizarre to invoke testscript with '-' and a regular file, but hey. In + // that case the -u flag will only apply to the regular file and we assume + // the user knows it. + onlyReadFromStdin := true + for _, f := range files { + if f != "-" { + onlyReadFromStdin = false + } + } + if onlyReadFromStdin && *fUpdate { + return fmt.Errorf("cannot use -u when reading from stdin") + } + dirNames := make(map[string]int) for _, filename := range files { // TODO make running files concurrent by default? If we do, note we'll need to do @@ -103,7 +118,7 @@ func mainerr() (retErr error) { if err := os.Mkdir(runDir, 0777); err != nil { return fmt.Errorf("failed to create a run directory within %v for %v: %v", td, renderFilename(filename), err) } - if err := run(runDir, filename, *fVerbose, envVars.vals); err != nil { + if err := run(runDir, filename, *fUpdate, *fVerbose, envVars.vals); err != nil { return err } } @@ -162,7 +177,11 @@ func renderFilename(filename string) string { return filename } -func run(runDir, filename string, verbose bool, envVars []string) error { +// run runs the testscript archive in filename within the temporary runDir. +// verbose causes the output to be verbose (akin to go test -v) and update +// sets the UpdateScripts parameter passed to testscript.Run such that any +// updates to the archive get written back to filename +func run(runDir, filename string, update bool, verbose bool, envVars []string) error { var ar *txtar.Archive var err error @@ -204,12 +223,15 @@ func run(runDir, filename string, verbose bool, envVars []string) error { return fmt.Errorf("failed to write .gomodproxy files: %v", err) } - if err := ioutil.WriteFile(filepath.Join(runDir, "script.txt"), txtar.Format(&script), 0666); err != nil { - return fmt.Errorf("failed to write script for %v: %v", filename, err) + scriptFile := filepath.Join(runDir, "script.txt") + + if err := ioutil.WriteFile(scriptFile, txtar.Format(&script), 0666); err != nil { + return fmt.Errorf("failed to write script for %v: %v", renderFilename(filename), err) } p := testscript.Params{ - Dir: runDir, + Dir: runDir, + UpdateScripts: update, } if _, err := exec.LookPath("go"); err == nil { @@ -282,5 +304,28 @@ func run(runDir, filename string, verbose bool, envVars []string) error { return fmt.Errorf("error running %v in %v\n", renderFilename(filename), runDir) } + if update && filename != "-" { + // Parse the (potentially) updated scriptFile as an archive, then merge + // with the original archive, retaining order. Then write the archive + // back to the source file + source, err := ioutil.ReadFile(scriptFile) + if err != nil { + return fmt.Errorf("failed to read from script file %v for -update: %v", scriptFile, err) + } + updatedAr := txtar.Parse(source) + updatedFiles := make(map[string]txtar.File) + for _, f := range updatedAr.Files { + updatedFiles[f.Name] = f + } + for i, f := range ar.Files { + if newF, ok := updatedFiles[f.Name]; ok { + ar.Files[i] = newF + } + } + if err := ioutil.WriteFile(filename, txtar.Format(ar), 0666); err != nil { + return fmt.Errorf("failed to write script back to %v for -update: %v", renderFilename(filename), err) + } + } + return nil } diff --git a/cmd/testscript/testdata/update.txt b/cmd/testscript/testdata/update.txt new file mode 100644 index 00000000..e1c15bd1 --- /dev/null +++ b/cmd/testscript/testdata/update.txt @@ -0,0 +1,64 @@ +# should support the -update flag + +unquote in.txt res.txt + +# Should be an error to use -u with only stdin +stdin in.txt +! testscript -u +stderr 'cannot use -u when reading from stdin' + +# It is ok to use -u when reading from stdin and +# a regular file +testscript -u - in.txt +cmp in.txt res.txt + +-- in.txt -- +>exec printf 'hello\n' +>cmp stdout stdout.txt +> +>-- .gomodproxy/fruit.com_v1.0.0/.mod -- +>module fruit.com +> +>-- .gomodproxy/fruit.com_v1.0.0/.info -- +>{"Version":"v1.0.0","Time":"2018-10-22T18:45:39Z"} +> +>-- .gomodproxy/fruit.com_v1.0.0/go.mod -- +>module fruit.com +> +>-- stdout.txt -- +>goodbye +>-- .gomodproxy/fruit.com_v1.0.0/fruit/fruit.go -- +>package fruit +> +>const Apple = "apple" +>-- .gomodproxy/fruit.com_v1.0.0/coretest/coretest.go -- +>// package coretest becomes a candidate for the missing +>// core import in main above +>package coretest +> +>const Mandarin = "mandarin" +-- res.txt -- +>exec printf 'hello\n' +>cmp stdout stdout.txt +> +>-- .gomodproxy/fruit.com_v1.0.0/.mod -- +>module fruit.com +> +>-- .gomodproxy/fruit.com_v1.0.0/.info -- +>{"Version":"v1.0.0","Time":"2018-10-22T18:45:39Z"} +> +>-- .gomodproxy/fruit.com_v1.0.0/go.mod -- +>module fruit.com +> +>-- stdout.txt -- +>hello +>-- .gomodproxy/fruit.com_v1.0.0/fruit/fruit.go -- +>package fruit +> +>const Apple = "apple" +>-- .gomodproxy/fruit.com_v1.0.0/coretest/coretest.go -- +>// package coretest becomes a candidate for the missing +>// core import in main above +>package coretest +> +>const Mandarin = "mandarin"