Skip to content

Commit ffb517c

Browse files
committed
cmd/testscript: do not create an extra temporary directory
It's bugged me for a long time that the error messages printed by the `testscript` command do not refer to the actual files passed to the command, but instead to a temporary file created for the purposes of the command. This change alters the testscript command so that it avoids creating an extra copy of the script file, instead using the new ability of the testscript package to interpret explicitly provided files instead. Gratifyingly, this also simplifies the logic quite a bit. Note: this is dependent on #258, so should not be reviewed until that lands.
1 parent b143f3f commit ffb517c

File tree

5 files changed

+103
-192
lines changed

5 files changed

+103
-192
lines changed

cmd/testscript/main.go

Lines changed: 78 additions & 184 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ import (
1313
"os/exec"
1414
"path/filepath"
1515
"strings"
16+
"sync/atomic"
1617

1718
"github.com/rogpeppe/go-internal/goproxytest"
1819
"github.com/rogpeppe/go-internal/gotooltest"
1920
"github.com/rogpeppe/go-internal/testscript"
20-
"github.com/rogpeppe/go-internal/txtar"
2121
)
2222

2323
const (
@@ -71,16 +71,6 @@ func mainerr() (retErr error) {
7171
return err
7272
}
7373

74-
td, err := os.MkdirTemp("", "testscript")
75-
if err != nil {
76-
return fmt.Errorf("unable to create temp dir: %v", err)
77-
}
78-
if *fWork {
79-
fmt.Fprintf(os.Stderr, "temporary work directory: %v\n", td)
80-
} else {
81-
defer os.RemoveAll(td)
82-
}
83-
8474
files := fs.Args()
8575
if len(files) == 0 {
8676
files = []string{"-"}
@@ -99,123 +89,43 @@ func mainerr() (retErr error) {
9989
if onlyReadFromStdin && *fUpdate {
10090
return fmt.Errorf("cannot use -u when reading from stdin")
10191
}
102-
103-
tr := testRunner{
104-
update: *fUpdate,
105-
continueOnError: *fContinue,
106-
verbose: *fVerbose,
107-
env: envVars.vals,
108-
testWork: *fWork,
109-
}
110-
111-
dirNames := make(map[string]int)
112-
for _, filename := range files {
113-
// TODO make running files concurrent by default? If we do, note we'll need to do
114-
// something smarter with the runner stdout and stderr below
115-
116-
// Derive a name for the directory from the basename of file, making
117-
// uniq by adding a numeric suffix in the case we otherwise end
118-
// up with multiple files with the same basename
119-
dirName := filepath.Base(filename)
120-
count := dirNames[dirName]
121-
dirNames[dirName] = count + 1
122-
if count != 0 {
123-
dirName = fmt.Sprintf("%s%d", dirName, count)
92+
var stdinTempFile string
93+
for i, f := range files {
94+
if f != "-" {
95+
continue
12496
}
125-
126-
runDir := filepath.Join(td, dirName)
127-
if err := os.Mkdir(runDir, 0o777); err != nil {
128-
return fmt.Errorf("failed to create a run directory within %v for %v: %v", td, renderFilename(filename), err)
97+
if stdinTempFile != "" {
98+
return fmt.Errorf("cannot read stdin twice")
12999
}
130-
if err := tr.run(runDir, filename); err != nil {
131-
return err
100+
data, err := io.ReadAll(os.Stdin)
101+
if err != nil {
102+
return fmt.Errorf("error reading stdin: %v", err)
132103
}
133-
}
134-
135-
return nil
136-
}
137-
138-
type testRunner struct {
139-
// update denotes that the source testscript archive filename should be
140-
// updated in the case of any cmp failures.
141-
update bool
142-
143-
// continueOnError indicates that T.FailNow should not panic, allowing the
144-
// test script to continue running. Note that T is still marked as failed.
145-
continueOnError bool
146-
147-
// verbose indicates the running of the script should be noisy.
148-
verbose bool
149-
150-
// env is the environment that should be set on top of the base
151-
// testscript-defined minimal environment.
152-
env []string
153-
154-
// testWork indicates whether or not temporary working directory trees
155-
// should be left behind. Corresponds exactly to the
156-
// testscript.Params.TestWork field.
157-
testWork bool
158-
}
159-
160-
// run runs the testscript archive located at the path filename, within the
161-
// working directory runDir. filename could be "-" in the case of stdin
162-
func (tr *testRunner) run(runDir, filename string) error {
163-
var ar *txtar.Archive
164-
var err error
165-
166-
mods := filepath.Join(runDir, goModProxyDir)
167-
168-
if err := os.MkdirAll(mods, 0o777); err != nil {
169-
return fmt.Errorf("failed to create goModProxy dir: %v", err)
170-
}
171-
172-
if filename == "-" {
173-
byts, err := io.ReadAll(os.Stdin)
104+
f, err := os.CreateTemp("", "stdin*.txtar")
174105
if err != nil {
175-
return fmt.Errorf("failed to read from stdin: %v", err)
106+
return err
176107
}
177-
ar = txtar.Parse(byts)
178-
} else {
179-
ar, err = txtar.ParseFile(filename)
180-
}
181-
182-
if err != nil {
183-
return fmt.Errorf("failed to txtar parse %v: %v", renderFilename(filename), err)
184-
}
185-
186-
var script, gomodProxy txtar.Archive
187-
script.Comment = ar.Comment
188-
189-
for _, f := range ar.Files {
190-
fp := filepath.Clean(filepath.FromSlash(f.Name))
191-
parts := strings.Split(fp, string(os.PathSeparator))
192-
193-
if len(parts) > 1 && parts[0] == goModProxyDir {
194-
gomodProxy.Files = append(gomodProxy.Files, f)
195-
} else {
196-
script.Files = append(script.Files, f)
108+
if _, err := f.Write(data); err != nil {
109+
return err
197110
}
198-
}
199-
200-
if txtar.Write(&gomodProxy, runDir); err != nil {
201-
return fmt.Errorf("failed to write .gomodproxy files: %v", err)
202-
}
203-
204-
scriptFile := filepath.Join(runDir, "script.txtar")
205-
206-
if err := os.WriteFile(scriptFile, txtar.Format(&script), 0o666); err != nil {
207-
return fmt.Errorf("failed to write script for %v: %v", renderFilename(filename), err)
111+
if err := f.Close(); err != nil {
112+
return err
113+
}
114+
stdinTempFile = f.Name()
115+
files[i] = stdinTempFile
116+
defer os.Remove(stdinTempFile)
208117
}
209118

210119
p := testscript.Params{
211-
Dir: runDir,
212-
UpdateScripts: tr.update,
213-
ContinueOnError: tr.continueOnError,
120+
Files: files,
121+
UpdateScripts: *fUpdate,
122+
ContinueOnError: *fContinue,
123+
TestWork: *fWork,
214124
}
215125

216126
if _, err := exec.LookPath("go"); err == nil {
217127
if err := gotooltest.Setup(&p); err != nil {
218-
return fmt.Errorf("failed to setup go tool for %v run: %v", renderFilename(filename), err)
128+
return fmt.Errorf("failed to setup go tool: %v", err)
219129
}
220130
}
221131

@@ -231,34 +141,36 @@ func (tr *testRunner) run(runDir, filename string) error {
231141
}
232142
}
233143

234-
if tr.testWork {
144+
if *fWork {
235145
addSetup(func(env *testscript.Env) error {
236-
fmt.Fprintf(os.Stderr, "temporary work directory for %s: %s\n", renderFilename(filename), env.WorkDir)
146+
env.T().Log("temporary work directory: ", env.WorkDir)
237147
return nil
238148
})
239149
}
240150

241-
if len(gomodProxy.Files) > 0 {
242-
srv, err := goproxytest.NewServer(mods, "")
151+
addSetup(func(env *testscript.Env) error {
152+
proxyDir := filepath.Join(env.WorkDir, goModProxyDir)
153+
if info, err := os.Stat(proxyDir); err != nil || !info.IsDir() {
154+
return nil
155+
}
156+
srv, err := goproxytest.NewServer(proxyDir, "")
243157
if err != nil {
244-
return fmt.Errorf("cannot start proxy for %v: %v", renderFilename(filename), err)
158+
return fmt.Errorf("cannot start Go proxy: %v", err)
245159
}
246-
defer srv.Close()
247-
248-
addSetup(func(env *testscript.Env) error {
249-
// Add GOPROXY after calling the original setup
250-
// so that it overrides any GOPROXY set there.
251-
env.Vars = append(env.Vars,
252-
"GOPROXY="+srv.URL,
253-
"GONOSUMDB=*",
254-
)
255-
return nil
256-
})
257-
}
258-
259-
if len(tr.env) > 0 {
160+
env.Defer(srv.Close)
161+
162+
// Add GOPROXY after calling the original setup
163+
// so that it overrides any GOPROXY set there.
164+
env.Vars = append(env.Vars,
165+
"GOPROXY="+srv.URL,
166+
"GONOSUMDB=*",
167+
)
168+
return nil
169+
})
170+
171+
if len(envVars.vals) > 0 {
260172
addSetup(func(env *testscript.Env) error {
261-
for _, v := range tr.env {
173+
for _, v := range envVars.vals {
262174
varName := v
263175
if i := strings.Index(v, "="); i >= 0 {
264176
varName = v[:i]
@@ -278,49 +190,15 @@ func (tr *testRunner) run(runDir, filename string) error {
278190
}
279191

280192
r := &runT{
281-
verbose: tr.verbose,
193+
verbose: *fVerbose,
194+
stdinTempFile: stdinTempFile,
282195
}
283-
284-
func() {
285-
defer func() {
286-
switch recover() {
287-
case nil, skipRun:
288-
case failedRun:
289-
err = failedRun
290-
default:
291-
panic(fmt.Errorf("unexpected panic: %v [%T]", err, err))
292-
}
293-
}()
294-
testscript.RunT(r, p)
295-
}()
296-
297-
if err != nil {
298-
return fmt.Errorf("error running %v in %v\n", renderFilename(filename), runDir)
196+
r.Run("", func(t testscript.T) {
197+
testscript.RunT(t, p)
198+
})
199+
if r.failed.Load() {
200+
return failedRun
299201
}
300-
301-
if tr.update && filename != "-" {
302-
// Parse the (potentially) updated scriptFile as an archive, then merge
303-
// with the original archive, retaining order. Then write the archive
304-
// back to the source file
305-
source, err := os.ReadFile(scriptFile)
306-
if err != nil {
307-
return fmt.Errorf("failed to read from script file %v for -update: %v", scriptFile, err)
308-
}
309-
updatedAr := txtar.Parse(source)
310-
updatedFiles := make(map[string]txtar.File)
311-
for _, f := range updatedAr.Files {
312-
updatedFiles[f.Name] = f
313-
}
314-
for i, f := range ar.Files {
315-
if newF, ok := updatedFiles[f.Name]; ok {
316-
ar.Files[i] = newF
317-
}
318-
}
319-
if err := os.WriteFile(filename, txtar.Format(ar), 0o666); err != nil {
320-
return fmt.Errorf("failed to write script back to %v for -update: %v", renderFilename(filename), err)
321-
}
322-
}
323-
324202
return nil
325203
}
326204

@@ -340,7 +218,9 @@ func renderFilename(filename string) string {
340218

341219
// runT implements testscript.T and is used in the call to testscript.Run
342220
type runT struct {
343-
verbose bool
221+
verbose bool
222+
stdinTempFile string
223+
failed atomic.Bool
344224
}
345225

346226
func (r *runT) Skip(is ...interface{}) {
@@ -353,22 +233,36 @@ func (r *runT) Fatal(is ...interface{}) {
353233
}
354234

355235
func (r *runT) Parallel() {
356-
// No-op for now; we are currently only running a single script in a
357-
// testscript instance.
236+
// TODO run tests in parallel.
358237
}
359238

360239
func (r *runT) Log(is ...interface{}) {
361-
fmt.Print(is...)
240+
msg := fmt.Sprint(is...)
241+
if r.stdinTempFile != "" {
242+
msg = strings.ReplaceAll(msg, r.stdinTempFile, "<stdin>")
243+
}
244+
if !strings.HasSuffix(msg, "\n") {
245+
msg += "\n"
246+
}
247+
fmt.Print(msg)
362248
}
363249

364250
func (r *runT) FailNow() {
365251
panic(failedRun)
366252
}
367253

368-
func (r *runT) Run(n string, f func(t testscript.T)) {
369-
// For now we we don't top/tail the run of a subtest. We are currently only
370-
// running a single script in a testscript instance, which means that we
371-
// will only have a single subtest.
254+
func (r *runT) Run(name string, f func(t testscript.T)) {
255+
// TODO: perhaps log the test name when there's more
256+
// than one test file?
257+
defer func() {
258+
switch err := recover(); err {
259+
case nil, skipRun:
260+
case failedRun:
261+
r.failed.Store(true)
262+
default:
263+
panic(fmt.Errorf("unexpected panic: %v [%T]", err, err))
264+
}
265+
}()
372266
f(r)
373267
}
374268

cmd/testscript/testdata/error.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ unquote file.txt
44
# stdin
55
stdin file.txt
66
! testscript -v
7-
stderr 'error running <stdin> in'
7+
stdout 'FAIL: <stdin>:1: unexpected command failure'
88

99
# file-based
1010
! testscript -v file.txt
11-
stderr 'error running file.txt in'
11+
stdout 'FAIL: file.txt:1: unexpected command failure'
1212

1313
-- file.txt --
1414
>exec false

cmd/testscript/testdata/multi.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Check that all scripts are executed even when one fails.
2+
3+
! testscript a.txt b.txt
4+
cmp stdout want-stdout
5+
-- want-stdout --
6+
> exec false
7+
[exit status 1]
8+
FAIL: a.txt:1: unexpected command failure
9+
> exec false
10+
[exit status 1]
11+
FAIL: b.txt:2: unexpected command failure
12+
-- a.txt --
13+
exec false
14+
-- b.txt --
15+
16+
exec false

cmd/testscript/testdata/nogo.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ dropgofrompath
77

88
! testscript -v file.txt
99
stdout 'unknown command "go"'
10-
stderr 'error running file.txt in'
1110

1211
-- file.txt --
1312
>go env

0 commit comments

Comments
 (0)