Skip to content

Commit

Permalink
cmd/testscript: support -update flag (#121)
Browse files Browse the repository at this point in the history
This is exactly equivalent to setting testscript.Params.UpdateScripts
  • Loading branch information
myitcv committed Jan 19, 2021
1 parent f55fd4a commit 8b0b133
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 7 deletions.
13 changes: 12 additions & 1 deletion cmd/testscript/README.md
Expand Up @@ -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.
Expand All @@ -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
========
Expand Down
8 changes: 7 additions & 1 deletion cmd/testscript/help.go
Expand Up @@ -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.
Expand All @@ -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
========
Expand Down
55 changes: 50 additions & 5 deletions cmd/testscript/main.go
Expand Up @@ -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)")
Expand All @@ -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
Expand All @@ -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
}
}
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
64 changes: 64 additions & 0 deletions 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"

0 comments on commit 8b0b133

Please sign in to comment.