1
2
3
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 "strings"
16 "sync/atomic"
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
26
27
28 goModProxyDir = ".gomodproxy"
29 )
30
31 type envVarsFlag struct {
32 vals []string
33 }
34
35 func (e *envVarsFlag) String() string {
36 return fmt.Sprintf("%v", e.vals)
37 }
38
39 func (e *envVarsFlag) Set(v string) error {
40 e.vals = append(e.vals, v)
41 return nil
42 }
43
44 func main() {
45 os.Exit(main1())
46 }
47
48 func main1() int {
49 switch err := mainerr(); err {
50 case nil:
51 return 0
52 case flag.ErrHelp:
53 return 2
54 default:
55 fmt.Fprintln(os.Stderr, err)
56 return 1
57 }
58 }
59
60 func mainerr() (retErr error) {
61 fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
62 fs.Usage = func() {
63 mainUsage(os.Stderr)
64 }
65 var envVars envVarsFlag
66 fUpdate := fs.Bool("u", false, "update archive file if a cmp fails")
67 fWork := fs.Bool("work", false, "print temporary work directory and do not remove when done")
68 fContinue := fs.Bool("continue", false, "continue running the script if an error occurs")
69 fVerbose := fs.Bool("v", false, "run tests verbosely")
70 fs.Var(&envVars, "e", "pass through environment variable to script (can appear multiple times)")
71 if err := fs.Parse(os.Args[1:]); err != nil {
72 return err
73 }
74
75 td, err := ioutil.TempDir("", "testscript")
76 if err != nil {
77 return fmt.Errorf("unable to create temp dir: %v", err)
78 }
79 if *fWork {
80 fmt.Fprintf(os.Stderr, "temporary work directory: %v\n", td)
81 } else {
82 defer os.RemoveAll(td)
83 }
84
85 files := fs.Args()
86 if len(files) == 0 {
87 files = []string{"-"}
88 }
89
90
91
92
93
94 onlyReadFromStdin := true
95 for _, f := range files {
96 if f != "-" {
97 onlyReadFromStdin = false
98 }
99 }
100 if onlyReadFromStdin && *fUpdate {
101 return fmt.Errorf("cannot use -u when reading from stdin")
102 }
103
104 tr := testRunner{
105 update: *fUpdate,
106 continueOnError: *fContinue,
107 verbose: *fVerbose,
108 env: envVars.vals,
109 testWork: *fWork,
110 }
111
112 dirNames := make(map[string]int)
113 for _, filename := range files {
114
115
116
117
118
119
120 dirName := filepath.Base(filename)
121 count := dirNames[dirName]
122 dirNames[dirName] = count + 1
123 if count != 0 {
124 dirName = fmt.Sprintf("%s%d", dirName, count)
125 }
126
127 runDir := filepath.Join(td, dirName)
128 if err := os.Mkdir(runDir, 0o777); err != nil {
129 return fmt.Errorf("failed to create a run directory within %v for %v: %v", td, renderFilename(filename), err)
130 }
131 if err := tr.run(runDir, filename); err != nil {
132 return err
133 }
134 }
135
136 return nil
137 }
138
139 type testRunner struct {
140
141
142 update bool
143
144
145
146 continueOnError bool
147
148
149 verbose bool
150
151
152
153 env []string
154
155
156
157
158 testWork bool
159 }
160
161
162
163 func (tr *testRunner) run(runDir, filename string) error {
164 var ar *txtar.Archive
165 var err error
166
167 mods := filepath.Join(runDir, goModProxyDir)
168
169 if err := os.MkdirAll(mods, 0o777); err != nil {
170 return fmt.Errorf("failed to create goModProxy dir: %v", err)
171 }
172
173 if filename == "-" {
174 byts, err := ioutil.ReadAll(os.Stdin)
175 if err != nil {
176 return fmt.Errorf("failed to read from stdin: %v", err)
177 }
178 ar = txtar.Parse(byts)
179 } else {
180 ar, err = txtar.ParseFile(filename)
181 }
182
183 if err != nil {
184 return fmt.Errorf("failed to txtar parse %v: %v", renderFilename(filename), err)
185 }
186
187 var script, gomodProxy txtar.Archive
188 script.Comment = ar.Comment
189
190 for _, f := range ar.Files {
191 fp := filepath.Clean(filepath.FromSlash(f.Name))
192 parts := strings.Split(fp, string(os.PathSeparator))
193
194 if len(parts) > 1 && parts[0] == goModProxyDir {
195 gomodProxy.Files = append(gomodProxy.Files, f)
196 } else {
197 script.Files = append(script.Files, f)
198 }
199 }
200
201 if txtar.Write(&gomodProxy, runDir); err != nil {
202 return fmt.Errorf("failed to write .gomodproxy files: %v", err)
203 }
204
205 scriptFile := filepath.Join(runDir, "script.txtar")
206
207 if err := ioutil.WriteFile(scriptFile, txtar.Format(&script), 0o666); err != nil {
208 return fmt.Errorf("failed to write script for %v: %v", renderFilename(filename), err)
209 }
210
211 p := testscript.Params{
212 Dir: runDir,
213 UpdateScripts: tr.update,
214 ContinueOnError: tr.continueOnError,
215 }
216
217 if _, err := exec.LookPath("go"); err == nil {
218 if err := gotooltest.Setup(&p); err != nil {
219 return fmt.Errorf("failed to setup go tool for %v run: %v", renderFilename(filename), err)
220 }
221 }
222
223 addSetup := func(f func(env *testscript.Env) error) {
224 origSetup := p.Setup
225 p.Setup = func(env *testscript.Env) error {
226 if origSetup != nil {
227 if err := origSetup(env); err != nil {
228 return err
229 }
230 }
231 return f(env)
232 }
233 }
234
235 if tr.testWork {
236 addSetup(func(env *testscript.Env) error {
237 fmt.Fprintf(os.Stderr, "temporary work directory for %s: %s\n", renderFilename(filename), env.WorkDir)
238 return nil
239 })
240 }
241
242 if len(gomodProxy.Files) > 0 {
243 srv, err := goproxytest.NewServer(mods, "")
244 if err != nil {
245 return fmt.Errorf("cannot start proxy for %v: %v", renderFilename(filename), err)
246 }
247 defer srv.Close()
248
249 addSetup(func(env *testscript.Env) error {
250
251
252 env.Vars = append(env.Vars,
253 "GOPROXY="+srv.URL,
254 "GONOSUMDB=*",
255 )
256 return nil
257 })
258 }
259
260 if len(tr.env) > 0 {
261 addSetup(func(env *testscript.Env) error {
262 for _, v := range tr.env {
263 varName := v
264 if i := strings.Index(v, "="); i >= 0 {
265 varName = v[:i]
266 } else {
267 v = fmt.Sprintf("%s=%s", v, os.Getenv(v))
268 }
269 switch varName {
270 case "":
271 return fmt.Errorf("invalid variable name %q", varName)
272 case "WORK":
273 return fmt.Errorf("cannot override WORK variable")
274 }
275 env.Vars = append(env.Vars, v)
276 }
277 return nil
278 })
279 }
280
281 r := &runT{
282 verbose: tr.verbose,
283 }
284
285 func() {
286 defer func() {
287 switch recover() {
288 case nil, skipRun:
289 case failedRun:
290 err = failedRun
291 default:
292 panic(fmt.Errorf("unexpected panic: %v [%T]", err, err))
293 }
294 }()
295 testscript.RunT(r, p)
296
297
298
299 if r.Failed() {
300 err = failedRun
301 }
302 }()
303
304 if err != nil {
305 return fmt.Errorf("error running %v in %v\n", renderFilename(filename), runDir)
306 }
307
308 if tr.update && filename != "-" {
309
310
311
312 source, err := ioutil.ReadFile(scriptFile)
313 if err != nil {
314 return fmt.Errorf("failed to read from script file %v for -update: %v", scriptFile, err)
315 }
316 updatedAr := txtar.Parse(source)
317 updatedFiles := make(map[string]txtar.File)
318 for _, f := range updatedAr.Files {
319 updatedFiles[f.Name] = f
320 }
321 for i, f := range ar.Files {
322 if newF, ok := updatedFiles[f.Name]; ok {
323 ar.Files[i] = newF
324 }
325 }
326 if err := ioutil.WriteFile(filename, txtar.Format(ar), 0o666); err != nil {
327 return fmt.Errorf("failed to write script back to %v for -update: %v", renderFilename(filename), err)
328 }
329 }
330
331 return nil
332 }
333
334 var (
335 failedRun = errors.New("failed run")
336 skipRun = errors.New("skip")
337 )
338
339
340
341 func renderFilename(filename string) string {
342 if filename == "-" {
343 return "<stdin>"
344 }
345 return filename
346 }
347
348
349 type runT struct {
350 verbose bool
351 failed int32
352 }
353
354 func (r *runT) Skip(is ...interface{}) {
355 panic(skipRun)
356 }
357
358 func (r *runT) Fatal(is ...interface{}) {
359 r.Log(is...)
360 r.FailNow()
361 }
362
363 func (r *runT) Parallel() {
364
365
366 }
367
368 func (r *runT) Log(is ...interface{}) {
369 fmt.Print(is...)
370 }
371
372 func (r *runT) FailNow() {
373 atomic.StoreInt32(&r.failed, 1)
374 panic(failedRun)
375 }
376
377 func (r *runT) Failed() bool {
378 return atomic.LoadInt32(&r.failed) != 0
379 }
380
381 func (r *runT) Run(n string, f func(t testscript.T)) {
382
383
384
385 f(r)
386 }
387
388 func (r *runT) Verbose() bool {
389 return r.verbose
390 }
391
View as plain text