Skip to content

Commit 8b0b133

Browse files
authored
cmd/testscript: support -update flag (#121)
This is exactly equivalent to setting testscript.Params.UpdateScripts
1 parent f55fd4a commit 8b0b133

File tree

4 files changed

+133
-7
lines changed

4 files changed

+133
-7
lines changed

cmd/testscript/README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ The testscript command runs github.com/rogpeppe/go-internal/testscript scripts
33
in a fresh temporary work directory tree.
44
55
Usage:
6-
testscript [-v] files...
6+
testscript [-v] [-e VAR]... [-u] files...
77
88
The testscript command is designed to make it easy to create self-contained
99
reproductions of command sequences.
@@ -20,6 +20,17 @@ proxy server. See the documentation for
2020
github.com/rogpeppe/go-internal/goproxytest for details on the format of these
2121
files/directories.
2222
23+
Environment variables can be passed through to each script with the -e flag,
24+
where VAR is the name of the variable. Variables override testscript-defined
25+
values, with the exception of WORK which cannot be overridden. The -e flag can
26+
appear multiple times to specify multiple variables.
27+
28+
The -u flag specifies that if a cmp command within a testscript fails and its
29+
second argument refers to a file inside the testscript file, the command will
30+
succeed and the testscript file will be updated to reflect the actual content.
31+
As such, this is the cmd/testcript equivalent of
32+
testscript.Params.UpdateScripts.
33+
2334
Examples
2435
========
2536

cmd/testscript/help.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ The testscript command runs github.com/rogpeppe/go-internal/testscript scripts
1414
in a fresh temporary work directory tree.
1515
1616
Usage:
17-
testscript [-v] [-e VAR]... files...
17+
testscript [-v] [-e VAR]... [-u] files...
1818
1919
The testscript command is designed to make it easy to create self-contained
2020
reproductions of command sequences.
@@ -36,6 +36,12 @@ where VAR is the name of the variable. Variables override testscript-defined
3636
values, with the exception of WORK which cannot be overridden. The -e flag can
3737
appear multiple times to specify multiple variables.
3838
39+
The -u flag specifies that if a cmp command within a testscript fails and its
40+
second argument refers to a file inside the testscript file, the command will
41+
succeed and the testscript file will be updated to reflect the actual content.
42+
As such, this is the cmd/testcript equivalent of
43+
testscript.Params.UpdateScripts.
44+
3945
Examples
4046
========
4147

cmd/testscript/main.go

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ func mainerr() (retErr error) {
6262
mainUsage(os.Stderr)
6363
}
6464
var envVars envVarsFlag
65+
fUpdate := fs.Bool("u", false, "update archive file if a cmp fails")
6566
fWork := fs.Bool("work", false, "print temporary work directory and do not remove when done")
6667
fVerbose := fs.Bool("v", false, "run tests verbosely")
6768
fs.Var(&envVars, "e", "pass through environment variable to script (can appear multiple times)")
@@ -84,6 +85,20 @@ func mainerr() (retErr error) {
8485
files = []string{"-"}
8586
}
8687

88+
// If we are only reading from stdin, -u cannot be specified. It seems a bit
89+
// bizarre to invoke testscript with '-' and a regular file, but hey. In
90+
// that case the -u flag will only apply to the regular file and we assume
91+
// the user knows it.
92+
onlyReadFromStdin := true
93+
for _, f := range files {
94+
if f != "-" {
95+
onlyReadFromStdin = false
96+
}
97+
}
98+
if onlyReadFromStdin && *fUpdate {
99+
return fmt.Errorf("cannot use -u when reading from stdin")
100+
}
101+
87102
dirNames := make(map[string]int)
88103
for _, filename := range files {
89104
// TODO make running files concurrent by default? If we do, note we'll need to do
@@ -103,7 +118,7 @@ func mainerr() (retErr error) {
103118
if err := os.Mkdir(runDir, 0777); err != nil {
104119
return fmt.Errorf("failed to create a run directory within %v for %v: %v", td, renderFilename(filename), err)
105120
}
106-
if err := run(runDir, filename, *fVerbose, envVars.vals); err != nil {
121+
if err := run(runDir, filename, *fUpdate, *fVerbose, envVars.vals); err != nil {
107122
return err
108123
}
109124
}
@@ -162,7 +177,11 @@ func renderFilename(filename string) string {
162177
return filename
163178
}
164179

165-
func run(runDir, filename string, verbose bool, envVars []string) error {
180+
// run runs the testscript archive in filename within the temporary runDir.
181+
// verbose causes the output to be verbose (akin to go test -v) and update
182+
// sets the UpdateScripts parameter passed to testscript.Run such that any
183+
// updates to the archive get written back to filename
184+
func run(runDir, filename string, update bool, verbose bool, envVars []string) error {
166185
var ar *txtar.Archive
167186
var err error
168187

@@ -204,12 +223,15 @@ func run(runDir, filename string, verbose bool, envVars []string) error {
204223
return fmt.Errorf("failed to write .gomodproxy files: %v", err)
205224
}
206225

207-
if err := ioutil.WriteFile(filepath.Join(runDir, "script.txt"), txtar.Format(&script), 0666); err != nil {
208-
return fmt.Errorf("failed to write script for %v: %v", filename, err)
226+
scriptFile := filepath.Join(runDir, "script.txt")
227+
228+
if err := ioutil.WriteFile(scriptFile, txtar.Format(&script), 0666); err != nil {
229+
return fmt.Errorf("failed to write script for %v: %v", renderFilename(filename), err)
209230
}
210231

211232
p := testscript.Params{
212-
Dir: runDir,
233+
Dir: runDir,
234+
UpdateScripts: update,
213235
}
214236

215237
if _, err := exec.LookPath("go"); err == nil {
@@ -282,5 +304,28 @@ func run(runDir, filename string, verbose bool, envVars []string) error {
282304
return fmt.Errorf("error running %v in %v\n", renderFilename(filename), runDir)
283305
}
284306

307+
if update && filename != "-" {
308+
// Parse the (potentially) updated scriptFile as an archive, then merge
309+
// with the original archive, retaining order. Then write the archive
310+
// back to the source file
311+
source, err := ioutil.ReadFile(scriptFile)
312+
if err != nil {
313+
return fmt.Errorf("failed to read from script file %v for -update: %v", scriptFile, err)
314+
}
315+
updatedAr := txtar.Parse(source)
316+
updatedFiles := make(map[string]txtar.File)
317+
for _, f := range updatedAr.Files {
318+
updatedFiles[f.Name] = f
319+
}
320+
for i, f := range ar.Files {
321+
if newF, ok := updatedFiles[f.Name]; ok {
322+
ar.Files[i] = newF
323+
}
324+
}
325+
if err := ioutil.WriteFile(filename, txtar.Format(ar), 0666); err != nil {
326+
return fmt.Errorf("failed to write script back to %v for -update: %v", renderFilename(filename), err)
327+
}
328+
}
329+
285330
return nil
286331
}

cmd/testscript/testdata/update.txt

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# should support the -update flag
2+
3+
unquote in.txt res.txt
4+
5+
# Should be an error to use -u with only stdin
6+
stdin in.txt
7+
! testscript -u
8+
stderr 'cannot use -u when reading from stdin'
9+
10+
# It is ok to use -u when reading from stdin and
11+
# a regular file
12+
testscript -u - in.txt
13+
cmp in.txt res.txt
14+
15+
-- in.txt --
16+
>exec printf 'hello\n'
17+
>cmp stdout stdout.txt
18+
>
19+
>-- .gomodproxy/fruit.com_v1.0.0/.mod --
20+
>module fruit.com
21+
>
22+
>-- .gomodproxy/fruit.com_v1.0.0/.info --
23+
>{"Version":"v1.0.0","Time":"2018-10-22T18:45:39Z"}
24+
>
25+
>-- .gomodproxy/fruit.com_v1.0.0/go.mod --
26+
>module fruit.com
27+
>
28+
>-- stdout.txt --
29+
>goodbye
30+
>-- .gomodproxy/fruit.com_v1.0.0/fruit/fruit.go --
31+
>package fruit
32+
>
33+
>const Apple = "apple"
34+
>-- .gomodproxy/fruit.com_v1.0.0/coretest/coretest.go --
35+
>// package coretest becomes a candidate for the missing
36+
>// core import in main above
37+
>package coretest
38+
>
39+
>const Mandarin = "mandarin"
40+
-- res.txt --
41+
>exec printf 'hello\n'
42+
>cmp stdout stdout.txt
43+
>
44+
>-- .gomodproxy/fruit.com_v1.0.0/.mod --
45+
>module fruit.com
46+
>
47+
>-- .gomodproxy/fruit.com_v1.0.0/.info --
48+
>{"Version":"v1.0.0","Time":"2018-10-22T18:45:39Z"}
49+
>
50+
>-- .gomodproxy/fruit.com_v1.0.0/go.mod --
51+
>module fruit.com
52+
>
53+
>-- stdout.txt --
54+
>hello
55+
>-- .gomodproxy/fruit.com_v1.0.0/fruit/fruit.go --
56+
>package fruit
57+
>
58+
>const Apple = "apple"
59+
>-- .gomodproxy/fruit.com_v1.0.0/coretest/coretest.go --
60+
>// package coretest becomes a candidate for the missing
61+
>// core import in main above
62+
>package coretest
63+
>
64+
>const Mandarin = "mandarin"

0 commit comments

Comments
 (0)