Commit 2497c430 authored by Russ Cox's avatar Russ Cox

cmd/go: detect import cycle caused by test code

The runtime was detecting the cycle already,
but we can give a better error without even
building the binary.

Fixes #7789.

LGTM=iant
R=golang-codereviews, iant
CC=golang-codereviews
https://golang.org/cl/96290043
parent 02cc45ad
......@@ -144,7 +144,7 @@ type PackageError struct {
func (p *PackageError) Error() string {
// Import cycles deserve special treatment.
if p.isImportCycle {
return fmt.Sprintf("%s: %s\npackage %s\n", p.Pos, p.Err, strings.Join(p.ImportStack, "\n\timports "))
return fmt.Sprintf("%s\npackage %s\n", p.Err, strings.Join(p.ImportStack, "\n\timports "))
}
if p.Pos != "" {
// Omit import stack. The full path to the file where the error
......
......@@ -770,6 +770,19 @@ elif ! grep 'no buildable Go' testdata/err.out >/dev/null; then
fi
rm -f testdata/err.out
TEST 'go test detects test-only import cycles'
export GOPATH=$(pwd)/testdata
if ./testgo test -c testcycle/p3 2>testdata/err.out; then
echo "go test testcycle/p3 succeeded, should have failed"
ok=false
elif ! grep 'import cycle not allowed in test' testdata/err.out >/dev/null; then
echo "go test testcycle/p3 produced unexpected error:"
cat testdata/err.out
ok=false
fi
rm -f testdata/err.out
unset GOPATH
# clean up
if $started; then stop; fi
rm -rf testdata/bin testdata/bin1
......
......@@ -538,14 +538,27 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
var imports, ximports []*Package
var stk importStack
stk.push(p.ImportPath + "_test")
stk.push(p.ImportPath + " (test)")
for _, path := range p.TestImports {
p1 := loadImport(path, p.Dir, &stk, p.build.TestImportPos[path])
if p1.Error != nil {
return nil, nil, nil, p1.Error
}
if contains(p1.Deps, p.ImportPath) {
// Same error that loadPackage returns (via reusePackage) in pkg.go.
// Can't change that code, because that code is only for loading the
// non-test copy of a package.
err := &PackageError{
ImportStack: testImportStack(stk[0], p1, p.ImportPath),
Err: "import cycle not allowed in test",
isImportCycle: true,
}
return nil, nil, nil, err
}
imports = append(imports, p1)
}
stk.pop()
stk.push(p.ImportPath + "_test")
for _, path := range p.XTestImports {
if path == p.ImportPath {
continue
......@@ -777,6 +790,24 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
return pmainAction, runAction, printAction, nil
}
func testImportStack(top string, p *Package, target string) []string {
stk := []string{top, p.ImportPath}
Search:
for p.ImportPath != target {
for _, p1 := range p.imports {
if p1.ImportPath == target || contains(p1.Deps, target) {
stk = append(stk, p1.ImportPath)
p = p1
continue Search
}
}
// Can't happen, but in case it does...
stk = append(stk, "<lost path to cycle>")
break
}
return stk
}
func recompileForTest(pmain, preal, ptest *Package, testDir string) {
// The "test copy" of preal is ptest.
// For each package that depends on preal, make a "test copy"
......
package p1
import _ "testcycle/p2"
func init() {
println("p1 init")
}
package p1
import "testing"
func Test(t *testing.T) {
}
package p2
import _ "testcycle/p3"
func init() {
println("p2 init")
}
package p3
func init() {
println("p3 init")
}
package p3
import (
"testing"
_ "testcycle/p1"
)
func Test(t *testing.T) {
}
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