Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd/testscript: support -update flag #121

Merged
merged 1 commit into from
Jan 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion cmd/testscript/README.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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...
myitcv marked this conversation as resolved.
Show resolved Hide resolved

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
Original file line number Diff line number Diff line change
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)
myitcv marked this conversation as resolved.
Show resolved Hide resolved
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
Original file line number Diff line number Diff line change
@@ -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"