-
Notifications
You must be signed in to change notification settings - Fork 77
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
cmd/testscript: initial commit (#41)
- Loading branch information
Showing
16 changed files
with
629 additions
and
86 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
) | ||
|
||
func mainUsage(f io.Writer) { | ||
fmt.Fprint(f, mainHelp) | ||
} | ||
|
||
var mainHelp = ` | ||
The testscript command runs github.com/rogpeppe/go-internal/testscript scripts | ||
in a fresh temporary work directory tree. | ||
Usage: | ||
testscript [-v] files... | ||
The testscript command is designed to make it easy to create self-contained | ||
reproductions of command sequences. | ||
Each file is opened as a script and run as described in the documentation for | ||
github.com/rogpeppe/go-internal/testscript. The special filename "-" is | ||
interpreted as the standard input. | ||
As a special case, supporting files/directories in the .gomodproxy subdirectory | ||
will be served via a github.com/rogpeppe/go-internal/goproxytest server which | ||
is available to each script via the GOPROXY environment variable. The contents | ||
of the .gomodproxy subdirectory are not available to the script except via the | ||
proxy server. See the documentation for | ||
github.com/rogpeppe/go-internal/goproxytest for details on the format of these | ||
files/directories. | ||
Examples | ||
======== | ||
The following example, fruit.txt, shows a simple reproduction that includes | ||
.gomodproxy supporting files: | ||
go get -m fruit.com | ||
go list fruit.com/... | ||
stdout 'fruit.com/fruit' | ||
-- go.mod -- | ||
module mod | ||
-- .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/fruit/fruit.go -- | ||
package fruit | ||
const Name = "Apple" | ||
Running testscript -v fruit.txt we get: | ||
... | ||
> go get -m fruit.com | ||
[stderr] | ||
go: finding fruit.com v1.0.0 | ||
> go list fruit.com/... | ||
[stdout] | ||
fruit.com/fruit | ||
[stderr] | ||
go: downloading fruit.com v1.0.0 | ||
> stdout 'fruit.com/fruit' | ||
PASS | ||
The following example, goimports.txt, shows a simple reproduction involving | ||
goimports: | ||
go install golang.org/x/tools/cmd/goimports | ||
# check goimports help information | ||
exec goimports -d main.go | ||
stdout 'import "math"' | ||
-- go.mod -- | ||
module mod | ||
require golang.org/x/tools v0.0.0-20181221235234-d00ac6d27372 | ||
-- main.go -- | ||
package mod | ||
const Pi = math.Pi | ||
Running testscript -v goimports.txt we get: | ||
... | ||
> go install golang.org/x/tools/cmd/goimports | ||
[stderr] | ||
go: finding golang.org/x/tools v0.0.0-20181221235234-d00ac6d27372 | ||
go: downloading golang.org/x/tools v0.0.0-20181221235234-d00ac6d27372 | ||
# check goimports help information (0.015s) | ||
> exec goimports -d main.go | ||
[stdout] | ||
diff -u main.go.orig main.go | ||
--- main.go.orig 2019-01-08 16:03:35.861907738 +0000 | ||
+++ main.go 2019-01-08 16:03:35.861907738 +0000 | ||
@@ -1,3 +1,5 @@ | ||
package mod | ||
+import "math" | ||
+ | ||
const Pi = math.Pi | ||
> stdout 'import "math"' | ||
PASS | ||
`[1:] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,222 @@ | ||
// Copyright 2018 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package main | ||
|
||
import ( | ||
"errors" | ||
"flag" | ||
"fmt" | ||
"io/ioutil" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"strconv" | ||
"strings" | ||
|
||
"github.com/rogpeppe/go-internal/goproxytest" | ||
"github.com/rogpeppe/go-internal/gotooltest" | ||
"github.com/rogpeppe/go-internal/testscript" | ||
"github.com/rogpeppe/go-internal/txtar" | ||
) | ||
|
||
const ( | ||
// goModProxyDir is the special subdirectory in a txtar script's supporting files | ||
// within which we expect to find github.com/rogpeppe/go-internal/goproxytest | ||
// directories. | ||
goModProxyDir = ".gomodproxy" | ||
) | ||
|
||
func main() { | ||
os.Exit(main1()) | ||
} | ||
|
||
func main1() int { | ||
if err := mainerr(); err != nil { | ||
fmt.Fprintln(os.Stderr, err) | ||
return 1 | ||
} | ||
return 0 | ||
} | ||
|
||
func mainerr() (retErr error) { | ||
fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError) | ||
fs.Usage = func() { | ||
mainUsage(os.Stderr) | ||
} | ||
fWork := fs.Bool("work", false, "print temporary work directory and do not remove when done") | ||
fVerbose := fs.Bool("v", false, "run tests verbosely") | ||
if err := fs.Parse(os.Args[1:]); err != nil { | ||
return err | ||
} | ||
|
||
td, err := ioutil.TempDir("", "testscript") | ||
if err != nil { | ||
return fmt.Errorf("unable to create temp dir: %v", err) | ||
} | ||
fmt.Printf("temporary work directory: %v\n", td) | ||
if !*fWork { | ||
defer os.RemoveAll(td) | ||
} | ||
|
||
files := fs.Args() | ||
if len(files) == 0 { | ||
files = []string{"-"} | ||
} | ||
|
||
for i, fileName := range files { | ||
// TODO make running files concurrent by default? If we do, note we'll need to do | ||
// something smarter with the runner stdout and stderr below | ||
runDir := filepath.Join(td, strconv.Itoa(i)) | ||
if err := os.Mkdir(runDir, 0777); err != nil { | ||
return fmt.Errorf("failed to create a run directory within %v for %v: %v", td, fileName, err) | ||
} | ||
if err := run(runDir, fileName, *fVerbose); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
var ( | ||
failedRun = errors.New("failed run") | ||
skipRun = errors.New("skip") | ||
) | ||
|
||
type runner struct { | ||
verbose bool | ||
} | ||
|
||
func (r runner) Skip(is ...interface{}) { | ||
panic(skipRun) | ||
} | ||
|
||
func (r runner) Fatal(is ...interface{}) { | ||
r.Log(is...) | ||
r.FailNow() | ||
} | ||
|
||
func (r runner) Parallel() { | ||
// No-op for now; we are currently only running a single script in a | ||
// testscript instance. | ||
} | ||
|
||
func (r runner) Log(is ...interface{}) { | ||
fmt.Print(is...) | ||
} | ||
|
||
func (r runner) FailNow() { | ||
panic(failedRun) | ||
} | ||
|
||
func (r runner) Run(n string, f func(t testscript.T)) { | ||
// For now we we don't top/tail the run of a subtest. We are currently only | ||
// running a single script in a testscript instance, which means that we | ||
// will only have a single subtest. | ||
f(r) | ||
} | ||
|
||
func (r runner) Verbose() bool { | ||
return r.verbose | ||
} | ||
|
||
func run(runDir, fileName string, verbose bool) error { | ||
var ar *txtar.Archive | ||
var err error | ||
|
||
mods := filepath.Join(runDir, goModProxyDir) | ||
|
||
if err := os.MkdirAll(mods, 0777); err != nil { | ||
return fmt.Errorf("failed to create goModProxy dir: %v", err) | ||
} | ||
|
||
if fileName == "-" { | ||
fileName = "<stdin>" | ||
byts, err := ioutil.ReadAll(os.Stdin) | ||
if err != nil { | ||
return fmt.Errorf("failed to read from stdin: %v", err) | ||
} | ||
ar = txtar.Parse(byts) | ||
} else { | ||
ar, err = txtar.ParseFile(fileName) | ||
} | ||
|
||
if err != nil { | ||
return fmt.Errorf("failed to txtar parse %v: %v", fileName, err) | ||
} | ||
|
||
var script, gomodProxy txtar.Archive | ||
script.Comment = ar.Comment | ||
|
||
for _, f := range ar.Files { | ||
fp := filepath.Clean(filepath.FromSlash(f.Name)) | ||
parts := strings.Split(fp, string(os.PathSeparator)) | ||
|
||
if len(parts) > 1 && parts[0] == goModProxyDir { | ||
gomodProxy.Files = append(gomodProxy.Files, f) | ||
} else { | ||
script.Files = append(script.Files, f) | ||
} | ||
} | ||
|
||
if txtar.Write(&gomodProxy, runDir); err != nil { | ||
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) | ||
} | ||
|
||
p := testscript.Params{ | ||
Dir: runDir, | ||
} | ||
|
||
if len(gomodProxy.Files) > 0 { | ||
srv, err := goproxytest.NewServer(mods, "") | ||
if err != nil { | ||
return fmt.Errorf("cannot start proxy for %v: %v", fileName, err) | ||
} | ||
defer srv.Close() | ||
|
||
currSetup := p.Setup | ||
|
||
p.Setup = func(env *testscript.Env) error { | ||
env.Vars = append(env.Vars, "GOPROXY="+srv.URL) | ||
if currSetup != nil { | ||
return currSetup(env) | ||
} | ||
return nil | ||
} | ||
} | ||
|
||
if _, err := exec.LookPath("go"); err == nil { | ||
if err := gotooltest.Setup(&p); err != nil { | ||
return fmt.Errorf("failed to setup go tool for %v run: %v", fileName, err) | ||
} | ||
} | ||
|
||
r := runner{ | ||
verbose: verbose, | ||
} | ||
|
||
func() { | ||
defer func() { | ||
switch recover() { | ||
case nil, skipRun: | ||
case failedRun: | ||
err = failedRun | ||
default: | ||
panic(fmt.Errorf("unexpected panic: %v [%T]", err, err)) | ||
} | ||
}() | ||
testscript.RunT(r, p) | ||
}() | ||
|
||
if err != nil { | ||
return fmt.Errorf("error running %v in %v\n", fileName, runDir) | ||
} | ||
|
||
return nil | ||
} |
Oops, something went wrong.