Commit 269a9382 authored by Russ Cox's avatar Russ Cox

cmd/go: convert final module tests to scripts

Change-Id: Iba68b3aaf4a132bd4ca44edf4912a46549d2ef8f
Reviewed-on: https://go-review.googlesource.com/124700Reviewed-by: default avatarBryan C. Mills <bcmills@google.com>
parent 9430c1a6
This diff is collapsed.
......@@ -231,6 +231,8 @@ func findHash(m module.Version) string {
var archiveCache par.Cache
var cmdGoDir, _ = os.Getwd()
func readArchive(path, vers string) *txtar.Archive {
enc, err := module.EncodePath(path)
if err != nil {
......
......@@ -294,20 +294,37 @@ Script:
// NOTE: If you make changes here, update testdata/script/README too!
//
var scriptCmds = map[string]func(*testScript, bool, []string){
"cd": (*testScript).cmdCd,
"cp": (*testScript).cmdCp,
"env": (*testScript).cmdEnv,
"exec": (*testScript).cmdExec,
"exists": (*testScript).cmdExists,
"go": (*testScript).cmdGo,
"grep": (*testScript).cmdGrep,
"mkdir": (*testScript).cmdMkdir,
"rm": (*testScript).cmdRm,
"skip": (*testScript).cmdSkip,
"stale": (*testScript).cmdStale,
"stderr": (*testScript).cmdStderr,
"stdout": (*testScript).cmdStdout,
"stop": (*testScript).cmdStop,
"addcrlf": (*testScript).cmdAddcrlf,
"cd": (*testScript).cmdCd,
"cmp": (*testScript).cmdCmp,
"cp": (*testScript).cmdCp,
"env": (*testScript).cmdEnv,
"exec": (*testScript).cmdExec,
"exists": (*testScript).cmdExists,
"go": (*testScript).cmdGo,
"grep": (*testScript).cmdGrep,
"mkdir": (*testScript).cmdMkdir,
"rm": (*testScript).cmdRm,
"skip": (*testScript).cmdSkip,
"stale": (*testScript).cmdStale,
"stderr": (*testScript).cmdStderr,
"stdout": (*testScript).cmdStdout,
"stop": (*testScript).cmdStop,
"symlink": (*testScript).cmdSymlink,
}
// addcrlf adds CRLF line endings to the named files.
func (ts *testScript) cmdAddcrlf(neg bool, args []string) {
if len(args) == 0 {
ts.fatalf("usage: addcrlf file...")
}
for _, file := range args {
file = ts.mkabs(file)
data, err := ioutil.ReadFile(file)
ts.check(err)
ts.check(ioutil.WriteFile(file, bytes.Replace(data, []byte("\n"), []byte("\r\n"), -1), 0666))
}
}
// cd changes to a different directory.
......@@ -335,6 +352,40 @@ func (ts *testScript) cmdCd(neg bool, args []string) {
fmt.Fprintf(&ts.log, "%s\n", ts.cd)
}
// cmp compares two files.
func (ts *testScript) cmdCmp(neg bool, args []string) {
if neg {
// It would be strange to say "this file can have any content except this precise byte sequence".
ts.fatalf("unsupported: ! cmp")
}
if len(args) != 2 {
ts.fatalf("usage: cmp file1 file2")
}
name1, name2 := args[0], args[1]
var text1, text2 string
if name1 == "stdout" {
text1 = ts.stdout
} else if name1 == "stderr" {
text1 = ts.stderr
} else {
data, err := ioutil.ReadFile(ts.mkabs(name1))
ts.check(err)
text1 = string(data)
}
data, err := ioutil.ReadFile(ts.mkabs(name2))
ts.check(err)
text2 = string(data)
if text1 == text2 {
return
}
fmt.Fprintf(&ts.log, "[diff -%s +%s]\n%s\n", name1, name2, diff(text1, text2))
ts.fatalf("%s and %s differ", name1, name2)
}
// cp copies files, maybe eventually directories.
func (ts *testScript) cmdCp(neg bool, args []string) {
if neg {
......@@ -516,22 +567,6 @@ func (ts *testScript) cmdStale(neg bool, args []string) {
}
}
// stop stops execution of the test (marking it passed).
func (ts *testScript) cmdStop(neg bool, args []string) {
if neg {
ts.fatalf("unsupported: ! stop")
}
if len(args) > 1 {
ts.fatalf("usage: stop [msg]")
}
if len(args) == 1 {
fmt.Fprintf(&ts.log, "stop: %s\n", args[0])
} else {
fmt.Fprintf(&ts.log, "stop\n")
}
ts.stopped = true
}
// stdout checks that the last go command standard output matches a regexp.
func (ts *testScript) cmdStdout(neg bool, args []string) {
scriptMatch(ts, neg, args, ts.stdout, "stdout")
......@@ -614,6 +649,35 @@ func scriptMatch(ts *testScript, neg bool, args []string, text, name string) {
}
}
// stop stops execution of the test (marking it passed).
func (ts *testScript) cmdStop(neg bool, args []string) {
if neg {
ts.fatalf("unsupported: ! stop")
}
if len(args) > 1 {
ts.fatalf("usage: stop [msg]")
}
if len(args) == 1 {
fmt.Fprintf(&ts.log, "stop: %s\n", args[0])
} else {
fmt.Fprintf(&ts.log, "stop\n")
}
ts.stopped = true
}
// symlink creates a symbolic link.
func (ts *testScript) cmdSymlink(neg bool, args []string) {
if neg {
ts.fatalf("unsupported: ! symlink")
}
if len(args) != 3 || args[1] != "->" {
ts.fatalf("usage: symlink file -> target")
}
// Note that the link target args[2] is not interpreted with mkabs:
// it will be interpreted relative to the directory file is in.
ts.check(os.Symlink(args[2], ts.mkabs(args[0])))
}
// Helpers for command implementations.
// abbrev abbreviates the actual work directory in the string s to the literal string "$WORK".
......@@ -727,3 +791,104 @@ func (ts *testScript) parse(line string) []string {
}
return args
}
// diff returns a formatted diff of the two texts,
// showing the entire text and the minimum line-level
// additions and removals to turn text1 into text2.
// (That is, lines only in text1 appear with a leading -,
// and lines only in text2 appear with a leading +.)
func diff(text1, text2 string) string {
if text1 != "" && !strings.HasSuffix(text1, "\n") {
text1 += "(missing final newline)"
}
lines1 := strings.Split(text1, "\n")
lines1 = lines1[:len(lines1)-1] // remove empty string after final line
if text2 != "" && !strings.HasSuffix(text2, "\n") {
text2 += "(missing final newline)"
}
lines2 := strings.Split(text2, "\n")
lines2 = lines2[:len(lines2)-1] // remove empty string after final line
// Naive dynamic programming algorithm for edit distance.
// https://en.wikipedia.org/wiki/Wagner–Fischer_algorithm
// dist[i][j] = edit distance between lines1[:len(lines1)-i] and lines2[:len(lines2)-j]
// (The reversed indices make following the minimum cost path
// visit lines in the same order as in the text.)
dist := make([][]int, len(lines1)+1)
for i := range dist {
dist[i] = make([]int, len(lines2)+1)
if i == 0 {
for j := range dist[0] {
dist[0][j] = j
}
continue
}
for j := range dist[i] {
if j == 0 {
dist[i][0] = i
continue
}
cost := dist[i][j-1] + 1
if cost > dist[i-1][j]+1 {
cost = dist[i-1][j] + 1
}
if lines1[len(lines1)-i] == lines2[len(lines2)-j] {
if cost > dist[i-1][j-1] {
cost = dist[i-1][j-1]
}
}
dist[i][j] = cost
}
}
var buf strings.Builder
i, j := len(lines1), len(lines2)
for i > 0 || j > 0 {
cost := dist[i][j]
if i > 0 && j > 0 && cost == dist[i-1][j-1] && lines1[len(lines1)-i] == lines2[len(lines2)-j] {
fmt.Fprintf(&buf, " %s\n", lines1[len(lines1)-i])
i--
j--
} else if i > 0 && cost == dist[i-1][j]+1 {
fmt.Fprintf(&buf, "-%s\n", lines1[len(lines1)-i])
i--
} else {
fmt.Fprintf(&buf, "+%s\n", lines2[len(lines2)-j])
j--
}
}
return buf.String()
}
var diffTests = []struct {
text1 string
text2 string
diff string
}{
{"a b c", "a b d e f", "a b -c +d +e +f"},
{"", "a b c", "+a +b +c"},
{"a b c", "", "-a -b -c"},
{"a b c", "d e f", "-a -b -c +d +e +f"},
{"a b c d e f", "a b d e f", "a b -c d e f"},
{"a b c e f", "a b c d e f", "a b c +d e f"},
}
func TestDiff(t *testing.T) {
for _, tt := range diffTests {
// Turn spaces into \n.
text1 := strings.Replace(tt.text1, " ", "\n", -1)
if text1 != "" {
text1 += "\n"
}
text2 := strings.Replace(tt.text2, " ", "\n", -1)
if text2 != "" {
text2 += "\n"
}
out := diff(text1, text2)
// Cut final \n, cut spaces, turn remaining \n into spaces.
out = strings.Replace(strings.Replace(strings.TrimSuffix(out, "\n"), " ", "", -1), "\n", " ", -1)
if out != tt.diff {
t.Errorf("diff(%q, %q) = %q, want %q", text1, text2, out, tt.diff)
}
}
}
env GO111MODULE=on
# sync removes unused y, but everything else is used
go mod -sync -v
stderr '^unused y.1'
! stderr '^unused [^y]'
go list -m all
! stdout '^y'
stdout '^w.1 v1.2.0'
stdout '^z.1 v1.2.0'
-- go.mod --
module m
require (
x.1 v1.0.0
y.1 v1.0.0
w.1 v1.2.0
)
replace x.1 v1.0.0 => ../x
replace y.1 v1.0.0 => ../y
replace z.1 v1.1.0 => ../z
replace z.1 v1.2.0 => ../z
replace w.1 => ../w
-- m.go --
package m
import _ "x.1"
import _ "z.1/sub"
-- w/go.mod --
module w
-- w/w.go --
package w
-- x/go.mod --
module x
require w.1 v1.1.0
require z.1 v1.1.0
-- x/x.go --
package x
import _ "w.1"
-- y/go.mod --
module y
require z.1 v1.2.0
-- z/go.mod --
module z
-- z/sub/sub.go --
package sub
......@@ -85,6 +85,13 @@ The commands are:
- cd dir
Change to the given directory for future commands.
- cmp file1 file2
Check that the named files have the same content.
By convention, file1 is the actual data and file2 the expected data.
File1 can be "stdout" or "stderr" to use the standard output or standard error
from the most recent exec or go command.
(If the files have differing content, the failure prints a diff.)
- cp src... dst
Copy the listed files to the target file or existing directory.
......@@ -133,6 +140,9 @@ The commands are:
- stop [message]
Stop the test early (marking it as passing), including the message if given.
- symlink file -> target
Create file as a symlink to target. The -> (like in ls -l output) is required.
When TestScript runs a script and the script fails, by default TestScript shows
the execution of the most recent phase of the script (since the last # comment)
and only shows the # comments for earlier phases. For example, here is a
......
env GO111MODULE=on
cd $WORK/test/x
go list -m all
stdout '^m$'
-- $WORK/test/Gopkg.lock --
-- $WORK/test/x/x.go --
package x // import "m/x"
env GO111MODULE=on
# detect root of module tree as root of enclosing git repo
cd $WORK/test/x
go list -m all
stdout '^m$'
-- $WORK/test/.git/config --
-- $WORK/test/x/x.go --
package x // import "m/x"
env GO111MODULE=on
cd $WORK/test/x
go list -m all
stdout '^m$'
-- $WORK/test/glide.lock --
-- $WORK/test/x/x.go --
package x // import "m/x"
env GO111MODULE=on
cd $WORK/test/x
go list -m all
stdout '^m$'
-- $WORK/test/GLOCKFILE --
-- $WORK/test/x/x.go --
package x // import "m/x"
env GO111MODULE=on
cd $WORK/test/x
go list -m all
stdout '^m$'
-- $WORK/test/Godeps/Godeps.json --
{}
-- $WORK/test/x/x.go --
package x // import "m/x"
env GO111MODULE=on
cd $WORK/test/x
go list -m all
stdout '^m$'
-- $WORK/test/dependencies.tsv --
-- $WORK/test/x/x.go --
package x // import "m/x"
env GO111MODULE=on
cd $WORK/test/x
go list -m all
stdout '^m$'
-- $WORK/test/vendor.conf --
-- $WORK/test/x/x.go --
package x // import "m/x"
env GO111MODULE=on
cd $WORK/test/x
go list -m all
stdout '^m$'
-- $WORK/test/vendor/vendor.json --
{}
-- $WORK/test/x/x.go --
package x // import "m/x"
env GO111MODULE=on
cd $WORK/test/x
go list -m all
stdout '^m$'
-- $WORK/test/vendor/manifest --
{}
-- $WORK/test/x/x.go --
package x // import "m/x"
env GO111MODULE=on
cd $WORK/test/x
go list -m all
stdout '^m$'
-- $WORK/test/vendor.yml --
-- $WORK/test/x/x.go --
package x // import "m/x"
env GO111MODULE=on
# Test that go mod edits and related mod flags work.
# Also test that they can use a dummy name that isn't resolvable. golang.org/issue/24100
# go mod -init
! go mod -init
stderr 'cannot determine module path'
! exists go.mod
go mod -init -module x.x/y/z
stderr 'creating new go.mod: module x.x/y/z'
cmp go.mod $WORK/go.mod.init
! go mod -init
cmp go.mod $WORK/go.mod.init
# go mod edits
go mod -droprequire=x.1 -require=x.1@v1.0.0 -require=x.2@v1.1.0 -droprequire=x.2 -exclude='x.1 @ v1.2.0' -exclude=x.1@v1.2.1 -replace=x.1@v1.3.0=y.1@v1.4.0 -replace='x.1@v1.4.0 = ../z'
cmp go.mod $WORK/go.mod.edit1
go mod -droprequire=x.1 -dropexclude=x.1@v1.2.1 -dropreplace=x.1@v1.3.0 -require=x.3@v1.99.0
cmp go.mod $WORK/go.mod.edit2
# go mod -json
go mod -json
cmp stdout $WORK/go.mod.json
# go mod -replace
go mod -replace=x.1@v1.3.0=y.1/v2@v2.3.5 -replace=x.1@v1.4.0=y.1/v2@v2.3.5
cmp go.mod $WORK/go.mod.edit3
go mod -replace=x.1=y.1/v2@v2.3.6
cmp go.mod $WORK/go.mod.edit4
# go mod -packages
go mod -packages
cmp stdout $WORK/go.mod.packages
# go mod -fmt
cp $WORK/go.mod.badfmt go.mod
go mod -fmt
cmp go.mod $WORK/go.mod.edit4
-- x.go --
package x
-- w/w.go --
package w
-- $WORK/go.mod.init --
module x.x/y/z
-- $WORK/go.mod.edit1 --
module x.x/y/z
require x.1 v1.0.0
exclude (
x.1 v1.2.0
x.1 v1.2.1
)
replace (
x.1 v1.3.0 => y.1 v1.4.0
x.1 v1.4.0 => ../z
)
-- $WORK/go.mod.edit2 --
module x.x/y/z
exclude x.1 v1.2.0
replace x.1 v1.4.0 => ../z
require x.3 v1.99.0
-- $WORK/go.mod.json --
{
"Module": {
"Path": "x.x/y/z"
},
"Require": [
{
"Path": "x.3",
"Version": "v1.99.0"
}
],
"Exclude": [
{
"Path": "x.1",
"Version": "v1.2.0"
}
],
"Replace": [
{
"Old": {
"Path": "x.1",
"Version": "v1.4.0"
},
"New": {
"Path": "../z"
}
}
]
}
-- $WORK/go.mod.edit3 --
module x.x/y/z
exclude x.1 v1.2.0
replace (
x.1 v1.3.0 => y.1/v2 v2.3.5
x.1 v1.4.0 => y.1/v2 v2.3.5
)
require x.3 v1.99.0
-- $WORK/go.mod.edit4 --
module x.x/y/z
exclude x.1 v1.2.0
replace x.1 => y.1/v2 v2.3.6
require x.3 v1.99.0
-- $WORK/go.mod.packages --
x.x/y/z
x.x/y/z/w
-- $WORK/go.mod.badfmt --
module x.x/y/z
exclude x.1 v1.2.0
replace x.1 => y.1/v2 v2.3.6
require x.3 v1.99.0
env GO111MODULE=on
# Derive module path from import comment.
# TODO SHOULD NOT NEED ENV VAR YET
cd $WORK/x
exists x.go
go mod -init
stderr 'module x'
# Import comment works even with CRLF line endings.
rm go.mod
addcrlf x.go
go mod -init
stderr 'module x'
# Derive module path from location inside GOPATH.
cd $GOPATH/src/example.com/x/y
go mod -init
stderr 'module example.com/x/y$'
rm go.mod
# Module path from Godeps/Godeps.json overrides GOPATH.
cd $GOPATH/src/example.com/x/y/z
go mod -init
stderr 'unexpected.com/z'
rm go.mod
# Empty directory outside GOPATH fails.
mkdir $WORK/empty
cd $WORK/empty
! go mod -init
stderr 'cannot determine module path for source directory'
rm go.mod
# Empty directory inside GOPATH/src uses location inside GOPATH.
mkdir $GOPATH/src/empty
cd $GOPATH/src/empty
go mod -init
stderr 'empty'
rm go.mod
[!symlink] stop
# gplink1/src/empty where gopathlink -> GOPATH
symlink $WORK/gopathlink -> gopath
cd $WORK/gopathlink/src/empty
go mod -init
rm go.mod
# GOPATH/src/link where link -> out of GOPATH
symlink $GOPATH/src/link -> $WORK/empty
cd $WORK/empty
! go mod -init
cd $GOPATH/src/link
go mod -init
stderr link
rm go.mod
# GOPATH/src/empty where GOPATH itself is a symlink
env GOPATH=$WORK/gopathlink
cd $GOPATH/src/empty
go mod -init
rm go.mod
cd $WORK/gopath/src/empty
go mod -init
rm go.mod
# GOPATH/src/link where GOPATH and link are both symlinks
cd $GOPATH/src/link
go mod -init
stderr link
rm go.mod
# Too hard: doesn't match unevaluated nor completely evaluated. (Only partially evaluated.)
# Whether this works depends on which OS we are running on.
# cd $WORK/gopath/src/link
# ! go mod -init
-- $WORK/x/x.go --
package x // import "x"
-- $GOPATH/src/example.com/x/y/y.go --
package y
-- $GOPATH/src/example.com/x/y/z/z.go --
package z
-- $GOPATH/src/example.com/x/y/z/Godeps/Godeps.json --
{"ImportPath": "unexpected.com/z"}
env GO111MODULE=on
go list -m all
stdout '^x v1.0.0 => ./x'
stdout '^w'
[!short] go build
[!short] ! go build -getmode=vendor
go list -f {{.Dir}} x
stdout 'src[\\/]x'
go mod -vendor -v
stderr '^# x v1.0.0 => ./x'
stderr '^x'
stderr '^# y v1.0.0 => ./y'
stderr '^y'
stderr '^# z v1.0.0 => ./z'
stderr '^z'
! stderr '^w'
go list -f {{.Dir}} x
stdout 'src[\\/]x'
go list -f {{.Dir}} -m x
stdout 'src[\\/]x'
go list -getmode=vendor -f {{.Dir}} x
stdout 'src[\\/]vendor[\\/]x'
go list -getmode=vendor -f {{.Dir}} -m x
stdout 'src[\\/]vendor[\\/]x'
go list -f {{.Dir}} -m w
stdout 'src[\\/]w'
! go list -getmode=vendor -f {{.Dir}} w
stderr 'src[\\/]vendor[\\/]w'
! exists vendor/x/testdata
! exists vendor/a/foo/bar/b/main_test.go
exists vendor/a/foo/AUTHORS.txt
exists vendor/a/foo/CONTRIBUTORS
exists vendor/a/foo/LICENSE
exists vendor/a/foo/PATENTS
exists vendor/a/foo/COPYING
exists vendor/a/foo/COPYLEFT
exists vendor/x/NOTICE!
exists vendor/mysite/myname/mypkg/LICENSE.txt
! exists vendor/a/foo/licensed-to-kill
! exists vendor/w
! exists vendor/w/LICENSE
! exists vendor/x/x2
! exists vendor/x/x2/LICENSE
[short] stop
go build
go build -getmode=vendor
go test -getmode=vendor . ./subdir
go test -getmode=vendor ./...
-- go.mod --
module m
require (
a v1.0.0
mysite/myname/mypkg v1.0.0
w v1.0.0 // indirect
x v1.0.0
y v1.0.0
z v1.0.0
)
replace (
a v1.0.0 => ./a
mysite/myname/mypkg v1.0.0 => ./mypkg
w v1.0.0 => ./w
x v1.0.0 => ./x
y v1.0.0 => ./y
z v1.0.0 => ./z
)
-- a/foo/AUTHORS.txt --
-- a/foo/CONTRIBUTORS --
-- a/foo/LICENSE --
-- a/foo/PATENTS --
-- a/foo/COPYING --
-- a/foo/COPYLEFT --
-- a/foo/licensed-to-kill --
-- w/LICENSE --
-- x/NOTICE! --
-- x/x2/LICENSE --
-- mypkg/LICENSE.txt --
-- a/foo/bar/b/main.go --
package b
-- a/foo/bar/b/main_test.go --
package b
import (
"os"
"testing"
)
func TestDir(t *testing.T) {
if _, err := os.Stat("../testdata/1"); err != nil {
t.Fatalf("testdata: %v", err)
}
}
-- a/foo/bar/c/main.go --
package c
-- a/foo/bar/c/main_test.go --
package c
import (
"os"
"testing"
)
func TestDir(t *testing.T) {
if _, err := os.Stat("../../../testdata/1"); err != nil {
t.Fatalf("testdata: %v", err)
}
if _, err := os.Stat("./testdata/1"); err != nil {
t.Fatalf("testdata: %v", err)
}
}
-- a/foo/bar/c/testdata/1 --
-- a/foo/bar/testdata/1 --
-- a/go.mod --
module a
-- a/main.go --
package a
-- a/main_test.go --
package a
import (
"os"
"testing"
)
func TestDir(t *testing.T) {
if _, err := os.Stat("./testdata/1"); err != nil {
t.Fatalf("testdata: %v", err)
}
}
-- a/testdata/1 --
-- appengine.go --
// +build appengine
package m
import _ "appengine"
import _ "appengine/datastore"
-- mypkg/go.mod --
module me
-- mypkg/mydir/d.go --
package mydir
-- subdir/v1_test.go --
package m
import _ "mysite/myname/mypkg/mydir"
-- testdata1.go --
package m
import _ "a"
-- testdata2.go --
package m
import _ "a/foo/bar/b"
import _ "a/foo/bar/c"
-- v1.go --
package m
import _ "x"
-- v2.go --
// +build abc
package mMmMmMm
import _ "y"
-- v3.go --
// +build !abc
package m
import _ "z"
-- v4.go --
// +build notmytag
package m
import _ "x/x1"
-- w/go.mod --
module w
-- w/w.go --
package w
-- x/go.mod --
module x
-- x/testdata/x.txt --
placeholder - want directory with no go files
-- x/x.go --
package x
-- x/x1/x1.go --
// +build notmytag
package x1
-- x/x2/dummy.txt --
dummy
-- x/x_test.go --
package x
import _ "w"
-- y/go.mod --
module y
-- y/y.go --
package y
-- z/go.mod --
module z
-- z/z.go --
package z
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment