Skip to content

Commit dc3eec0

Browse files
authored
cmd/testscript: initial commit (#41)
1 parent 4bbc89b commit dc3eec0

File tree

16 files changed

+629
-86
lines changed

16 files changed

+629
-86
lines changed

cmd/testscript/help.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"io"
6+
)
7+
8+
func mainUsage(f io.Writer) {
9+
fmt.Fprint(f, mainHelp)
10+
}
11+
12+
var mainHelp = `
13+
The testscript command runs github.com/rogpeppe/go-internal/testscript scripts
14+
in a fresh temporary work directory tree.
15+
16+
Usage:
17+
testscript [-v] files...
18+
19+
The testscript command is designed to make it easy to create self-contained
20+
reproductions of command sequences.
21+
22+
Each file is opened as a script and run as described in the documentation for
23+
github.com/rogpeppe/go-internal/testscript. The special filename "-" is
24+
interpreted as the standard input.
25+
26+
As a special case, supporting files/directories in the .gomodproxy subdirectory
27+
will be served via a github.com/rogpeppe/go-internal/goproxytest server which
28+
is available to each script via the GOPROXY environment variable. The contents
29+
of the .gomodproxy subdirectory are not available to the script except via the
30+
proxy server. See the documentation for
31+
github.com/rogpeppe/go-internal/goproxytest for details on the format of these
32+
files/directories.
33+
34+
Examples
35+
========
36+
37+
The following example, fruit.txt, shows a simple reproduction that includes
38+
.gomodproxy supporting files:
39+
40+
go get -m fruit.com
41+
go list fruit.com/...
42+
stdout 'fruit.com/fruit'
43+
44+
-- go.mod --
45+
module mod
46+
47+
-- .gomodproxy/fruit.com_v1.0.0/.mod --
48+
module fruit.com
49+
50+
-- .gomodproxy/fruit.com_v1.0.0/.info --
51+
{"Version":"v1.0.0","Time":"2018-10-22T18:45:39Z"}
52+
53+
-- .gomodproxy/fruit.com_v1.0.0/fruit/fruit.go --
54+
package fruit
55+
56+
const Name = "Apple"
57+
58+
Running testscript -v fruit.txt we get:
59+
60+
...
61+
> go get -m fruit.com
62+
[stderr]
63+
go: finding fruit.com v1.0.0
64+
65+
> go list fruit.com/...
66+
[stdout]
67+
fruit.com/fruit
68+
69+
[stderr]
70+
go: downloading fruit.com v1.0.0
71+
72+
> stdout 'fruit.com/fruit'
73+
PASS
74+
75+
76+
The following example, goimports.txt, shows a simple reproduction involving
77+
goimports:
78+
79+
go install golang.org/x/tools/cmd/goimports
80+
81+
# check goimports help information
82+
exec goimports -d main.go
83+
stdout 'import "math"'
84+
85+
-- go.mod --
86+
module mod
87+
88+
require golang.org/x/tools v0.0.0-20181221235234-d00ac6d27372
89+
90+
-- main.go --
91+
package mod
92+
93+
const Pi = math.Pi
94+
95+
Running testscript -v goimports.txt we get:
96+
97+
...
98+
> go install golang.org/x/tools/cmd/goimports
99+
[stderr]
100+
go: finding golang.org/x/tools v0.0.0-20181221235234-d00ac6d27372
101+
go: downloading golang.org/x/tools v0.0.0-20181221235234-d00ac6d27372
102+
103+
# check goimports help information (0.015s)
104+
> exec goimports -d main.go
105+
[stdout]
106+
diff -u main.go.orig main.go
107+
--- main.go.orig 2019-01-08 16:03:35.861907738 +0000
108+
+++ main.go 2019-01-08 16:03:35.861907738 +0000
109+
@@ -1,3 +1,5 @@
110+
package mod
111+
112+
+import "math"
113+
+
114+
const Pi = math.Pi
115+
> stdout 'import "math"'
116+
PASS
117+
`[1:]

cmd/testscript/main.go

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
// Copyright 2018 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package main
6+
7+
import (
8+
"errors"
9+
"flag"
10+
"fmt"
11+
"io/ioutil"
12+
"os"
13+
"os/exec"
14+
"path/filepath"
15+
"strconv"
16+
"strings"
17+
18+
"github.com/rogpeppe/go-internal/goproxytest"
19+
"github.com/rogpeppe/go-internal/gotooltest"
20+
"github.com/rogpeppe/go-internal/testscript"
21+
"github.com/rogpeppe/go-internal/txtar"
22+
)
23+
24+
const (
25+
// goModProxyDir is the special subdirectory in a txtar script's supporting files
26+
// within which we expect to find github.com/rogpeppe/go-internal/goproxytest
27+
// directories.
28+
goModProxyDir = ".gomodproxy"
29+
)
30+
31+
func main() {
32+
os.Exit(main1())
33+
}
34+
35+
func main1() int {
36+
if err := mainerr(); err != nil {
37+
fmt.Fprintln(os.Stderr, err)
38+
return 1
39+
}
40+
return 0
41+
}
42+
43+
func mainerr() (retErr error) {
44+
fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
45+
fs.Usage = func() {
46+
mainUsage(os.Stderr)
47+
}
48+
fWork := fs.Bool("work", false, "print temporary work directory and do not remove when done")
49+
fVerbose := fs.Bool("v", false, "run tests verbosely")
50+
if err := fs.Parse(os.Args[1:]); err != nil {
51+
return err
52+
}
53+
54+
td, err := ioutil.TempDir("", "testscript")
55+
if err != nil {
56+
return fmt.Errorf("unable to create temp dir: %v", err)
57+
}
58+
fmt.Printf("temporary work directory: %v\n", td)
59+
if !*fWork {
60+
defer os.RemoveAll(td)
61+
}
62+
63+
files := fs.Args()
64+
if len(files) == 0 {
65+
files = []string{"-"}
66+
}
67+
68+
for i, fileName := range files {
69+
// TODO make running files concurrent by default? If we do, note we'll need to do
70+
// something smarter with the runner stdout and stderr below
71+
runDir := filepath.Join(td, strconv.Itoa(i))
72+
if err := os.Mkdir(runDir, 0777); err != nil {
73+
return fmt.Errorf("failed to create a run directory within %v for %v: %v", td, fileName, err)
74+
}
75+
if err := run(runDir, fileName, *fVerbose); err != nil {
76+
return err
77+
}
78+
}
79+
80+
return nil
81+
}
82+
83+
var (
84+
failedRun = errors.New("failed run")
85+
skipRun = errors.New("skip")
86+
)
87+
88+
type runner struct {
89+
verbose bool
90+
}
91+
92+
func (r runner) Skip(is ...interface{}) {
93+
panic(skipRun)
94+
}
95+
96+
func (r runner) Fatal(is ...interface{}) {
97+
r.Log(is...)
98+
r.FailNow()
99+
}
100+
101+
func (r runner) Parallel() {
102+
// No-op for now; we are currently only running a single script in a
103+
// testscript instance.
104+
}
105+
106+
func (r runner) Log(is ...interface{}) {
107+
fmt.Print(is...)
108+
}
109+
110+
func (r runner) FailNow() {
111+
panic(failedRun)
112+
}
113+
114+
func (r runner) Run(n string, f func(t testscript.T)) {
115+
// For now we we don't top/tail the run of a subtest. We are currently only
116+
// running a single script in a testscript instance, which means that we
117+
// will only have a single subtest.
118+
f(r)
119+
}
120+
121+
func (r runner) Verbose() bool {
122+
return r.verbose
123+
}
124+
125+
func run(runDir, fileName string, verbose bool) error {
126+
var ar *txtar.Archive
127+
var err error
128+
129+
mods := filepath.Join(runDir, goModProxyDir)
130+
131+
if err := os.MkdirAll(mods, 0777); err != nil {
132+
return fmt.Errorf("failed to create goModProxy dir: %v", err)
133+
}
134+
135+
if fileName == "-" {
136+
fileName = "<stdin>"
137+
byts, err := ioutil.ReadAll(os.Stdin)
138+
if err != nil {
139+
return fmt.Errorf("failed to read from stdin: %v", err)
140+
}
141+
ar = txtar.Parse(byts)
142+
} else {
143+
ar, err = txtar.ParseFile(fileName)
144+
}
145+
146+
if err != nil {
147+
return fmt.Errorf("failed to txtar parse %v: %v", fileName, err)
148+
}
149+
150+
var script, gomodProxy txtar.Archive
151+
script.Comment = ar.Comment
152+
153+
for _, f := range ar.Files {
154+
fp := filepath.Clean(filepath.FromSlash(f.Name))
155+
parts := strings.Split(fp, string(os.PathSeparator))
156+
157+
if len(parts) > 1 && parts[0] == goModProxyDir {
158+
gomodProxy.Files = append(gomodProxy.Files, f)
159+
} else {
160+
script.Files = append(script.Files, f)
161+
}
162+
}
163+
164+
if txtar.Write(&gomodProxy, runDir); err != nil {
165+
return fmt.Errorf("failed to write .gomodproxy files: %v", err)
166+
}
167+
168+
if err := ioutil.WriteFile(filepath.Join(runDir, "script.txt"), txtar.Format(&script), 0666); err != nil {
169+
return fmt.Errorf("failed to write script for %v: %v", fileName, err)
170+
}
171+
172+
p := testscript.Params{
173+
Dir: runDir,
174+
}
175+
176+
if len(gomodProxy.Files) > 0 {
177+
srv, err := goproxytest.NewServer(mods, "")
178+
if err != nil {
179+
return fmt.Errorf("cannot start proxy for %v: %v", fileName, err)
180+
}
181+
defer srv.Close()
182+
183+
currSetup := p.Setup
184+
185+
p.Setup = func(env *testscript.Env) error {
186+
env.Vars = append(env.Vars, "GOPROXY="+srv.URL)
187+
if currSetup != nil {
188+
return currSetup(env)
189+
}
190+
return nil
191+
}
192+
}
193+
194+
if _, err := exec.LookPath("go"); err == nil {
195+
if err := gotooltest.Setup(&p); err != nil {
196+
return fmt.Errorf("failed to setup go tool for %v run: %v", fileName, err)
197+
}
198+
}
199+
200+
r := runner{
201+
verbose: verbose,
202+
}
203+
204+
func() {
205+
defer func() {
206+
switch recover() {
207+
case nil, skipRun:
208+
case failedRun:
209+
err = failedRun
210+
default:
211+
panic(fmt.Errorf("unexpected panic: %v [%T]", err, err))
212+
}
213+
}()
214+
testscript.RunT(r, p)
215+
}()
216+
217+
if err != nil {
218+
return fmt.Errorf("error running %v in %v\n", fileName, runDir)
219+
}
220+
221+
return nil
222+
}

0 commit comments

Comments
 (0)