Commit 54a966e3 authored by Russ Cox's avatar Russ Cox

go/build: make TestDependencies check all systems at once

We used to use build.Import to get the dependencies, but that meant
we had to repeat the check for every possible GOOS/GOARCH/cgo
combination, which took too long. So we made the test in short mode
only check the current GOOS/GOARCH/cgo combination.
But some combinations can't run the test at all. For example darwin/arm64
does not run tests with a full source file systems, so it cannot test itself,
so nothing was testing darwin/arm64. This led to bugs like #10455
not being caught.

Rewrite the test to read the imports out of the source files ourselves,
so that we can look at all source files in a directory in one pass,
regardless of which GOOS/GOARCH/cgo/etc they require.
This one complete pass runs in the same amount of time as the
old single combination check ran, so we can now test all systems,
even in short mode.

Change-Id: Ie216303c2515bbf1b6fb717d530a0636e271cb6d
Reviewed-on: https://go-review.googlesource.com/12576Reviewed-by: 's avatarIan Lance Taylor <iant@golang.org>
parent 0cf48b4d
...@@ -968,7 +968,7 @@ func (ctxt *Context) matchFile(dir, name string, returnImports bool, allTags map ...@@ -968,7 +968,7 @@ func (ctxt *Context) matchFile(dir, name string, returnImports bool, allTags map
} }
if strings.HasSuffix(filename, ".go") { if strings.HasSuffix(filename, ".go") {
data, err = readImports(f, false) data, err = readImports(f, false, nil)
} else { } else {
data, err = readComments(f) data, err = readComments(f)
} }
......
...@@ -8,10 +8,14 @@ ...@@ -8,10 +8,14 @@
package build package build
import ( import (
"bytes"
"fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"sort" "sort"
"strconv"
"strings" "strings"
"testing" "testing"
) )
...@@ -259,6 +263,9 @@ var pkgDeps = map[string][]string{ ...@@ -259,6 +263,9 @@ var pkgDeps = map[string][]string{
// that shows up in programs that use cgo. // that shows up in programs that use cgo.
"C": {}, "C": {},
// Race detector uses cgo.
"runtime/race": {"C"},
// Plan 9 alone needs io/ioutil and os. // Plan 9 alone needs io/ioutil and os.
"os/user": {"L4", "CGO", "io/ioutil", "os", "syscall"}, "os/user": {"L4", "CGO", "io/ioutil", "os", "syscall"},
...@@ -449,53 +456,65 @@ func TestDependencies(t *testing.T) { ...@@ -449,53 +456,65 @@ func TestDependencies(t *testing.T) {
test := func(mustImport bool) { test := func(mustImport bool) {
for _, pkg := range all { for _, pkg := range all {
if pkg == "runtime/cgo" && !ctxt.CgoEnabled { imports, err := findImports(pkg)
continue
}
p, err := ctxt.Import(pkg, "", 0)
if err != nil { if err != nil {
if _, ok := err.(*NoGoError); ok { t.Error(err)
continue
}
if allowedErrors[osPkg{ctxt.GOOS, pkg}] {
continue
}
if !ctxt.CgoEnabled && pkg == "runtime/cgo" {
continue
}
// Some of the combinations we try might not
// be reasonable (like arm,plan9,cgo), so ignore
// errors for the auto-generated combinations.
if !mustImport {
continue
}
t.Errorf("%s/%s/cgo=%v %v", ctxt.GOOS, ctxt.GOARCH, ctxt.CgoEnabled, err)
continue continue
} }
ok := allowed(pkg) ok := allowed(pkg)
var bad []string var bad []string
for _, imp := range p.Imports { for _, imp := range imports {
if !ok[imp] { if !ok[imp] {
bad = append(bad, imp) bad = append(bad, imp)
} }
} }
if bad != nil { if bad != nil {
t.Errorf("%s/%s/cgo=%v unexpected dependency: %s imports %v", ctxt.GOOS, ctxt.GOARCH, ctxt.CgoEnabled, pkg, bad) t.Errorf("unexpected dependency: %s imports %v", pkg, bad)
} }
} }
} }
test(true) test(true)
}
if testing.Short() { var buildIgnore = []byte("\n// +build ignore")
t.Logf("skipping other systems")
return
}
for _, ctxt.GOOS = range geese { func findImports(pkg string) ([]string, error) {
for _, ctxt.GOARCH = range goarches { dir := filepath.Join(Default.GOROOT, "src", pkg)
for _, ctxt.CgoEnabled = range bools { files, err := ioutil.ReadDir(dir)
test(false) if err != nil {
return nil, err
}
var imports []string
var haveImport = map[string]bool{}
for _, file := range files {
name := file.Name()
if !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") {
continue
}
f, err := os.Open(filepath.Join(dir, name))
if err != nil {
return nil, err
}
var imp []string
data, err := readImports(f, false, &imp)
f.Close()
if err != nil {
return nil, fmt.Errorf("reading %v: %v", name, err)
}
if bytes.Contains(data, buildIgnore) {
continue
}
for _, quoted := range imp {
path, err := strconv.Unquote(quoted)
if err != nil {
continue
}
if !haveImport[path] {
haveImport[path] = true
imports = append(imports, path)
} }
} }
} }
sort.Strings(imports)
return imports, nil
} }
...@@ -146,11 +146,15 @@ func (r *importReader) readIdent() { ...@@ -146,11 +146,15 @@ func (r *importReader) readIdent() {
// readString reads a quoted string literal from the input. // readString reads a quoted string literal from the input.
// If an identifier is not present, readString records a syntax error. // If an identifier is not present, readString records a syntax error.
func (r *importReader) readString() { func (r *importReader) readString(save *[]string) {
switch r.nextByte(true) { switch r.nextByte(true) {
case '`': case '`':
start := len(r.buf) - 1
for r.err == nil { for r.err == nil {
if r.nextByte(false) == '`' { if r.nextByte(false) == '`' {
if save != nil {
*save = append(*save, string(r.buf[start:]))
}
break break
} }
if r.eof { if r.eof {
...@@ -158,9 +162,13 @@ func (r *importReader) readString() { ...@@ -158,9 +162,13 @@ func (r *importReader) readString() {
} }
} }
case '"': case '"':
start := len(r.buf) - 1
for r.err == nil { for r.err == nil {
c := r.nextByte(false) c := r.nextByte(false)
if c == '"' { if c == '"' {
if save != nil {
*save = append(*save, string(r.buf[start:]))
}
break break
} }
if r.eof || c == '\n' { if r.eof || c == '\n' {
...@@ -177,14 +185,14 @@ func (r *importReader) readString() { ...@@ -177,14 +185,14 @@ func (r *importReader) readString() {
// readImport reads an import clause - optional identifier followed by quoted string - // readImport reads an import clause - optional identifier followed by quoted string -
// from the input. // from the input.
func (r *importReader) readImport() { func (r *importReader) readImport(imports *[]string) {
c := r.peekByte(true) c := r.peekByte(true)
if c == '.' { if c == '.' {
r.peek = 0 r.peek = 0
} else if isIdent(c) { } else if isIdent(c) {
r.readIdent() r.readIdent()
} }
r.readString() r.readString(imports)
} }
// readComments is like ioutil.ReadAll, except that it only reads the leading // readComments is like ioutil.ReadAll, except that it only reads the leading
...@@ -201,7 +209,7 @@ func readComments(f io.Reader) ([]byte, error) { ...@@ -201,7 +209,7 @@ func readComments(f io.Reader) ([]byte, error) {
// readImports is like ioutil.ReadAll, except that it expects a Go file as input // readImports is like ioutil.ReadAll, except that it expects a Go file as input
// and stops reading the input once the imports have completed. // and stops reading the input once the imports have completed.
func readImports(f io.Reader, reportSyntaxError bool) ([]byte, error) { func readImports(f io.Reader, reportSyntaxError bool, imports *[]string) ([]byte, error) {
r := &importReader{b: bufio.NewReader(f)} r := &importReader{b: bufio.NewReader(f)}
r.readKeyword("package") r.readKeyword("package")
...@@ -211,11 +219,11 @@ func readImports(f io.Reader, reportSyntaxError bool) ([]byte, error) { ...@@ -211,11 +219,11 @@ func readImports(f io.Reader, reportSyntaxError bool) ([]byte, error) {
if r.peekByte(true) == '(' { if r.peekByte(true) == '(' {
r.nextByte(false) r.nextByte(false)
for r.peekByte(true) != ')' && r.err == nil { for r.peekByte(true) != ')' && r.err == nil {
r.readImport() r.readImport(imports)
} }
r.nextByte(false) r.nextByte(false)
} else { } else {
r.readImport() r.readImport(imports)
} }
} }
......
...@@ -131,7 +131,7 @@ func testRead(t *testing.T, tests []readTest, read func(io.Reader) ([]byte, erro ...@@ -131,7 +131,7 @@ func testRead(t *testing.T, tests []readTest, read func(io.Reader) ([]byte, erro
} }
func TestReadImports(t *testing.T) { func TestReadImports(t *testing.T) {
testRead(t, readImportsTests, func(r io.Reader) ([]byte, error) { return readImports(r, true) }) testRead(t, readImportsTests, func(r io.Reader) ([]byte, error) { return readImports(r, true, nil) })
} }
func TestReadComments(t *testing.T) { func TestReadComments(t *testing.T) {
...@@ -207,7 +207,7 @@ var readFailuresTests = []readTest{ ...@@ -207,7 +207,7 @@ var readFailuresTests = []readTest{
func TestReadFailures(t *testing.T) { func TestReadFailures(t *testing.T) {
// Errors should be reported (true arg to readImports). // Errors should be reported (true arg to readImports).
testRead(t, readFailuresTests, func(r io.Reader) ([]byte, error) { return readImports(r, true) }) testRead(t, readFailuresTests, func(r io.Reader) ([]byte, error) { return readImports(r, true, nil) })
} }
func TestReadFailuresIgnored(t *testing.T) { func TestReadFailuresIgnored(t *testing.T) {
...@@ -222,5 +222,5 @@ func TestReadFailuresIgnored(t *testing.T) { ...@@ -222,5 +222,5 @@ func TestReadFailuresIgnored(t *testing.T) {
tt.err = "" tt.err = ""
} }
} }
testRead(t, tests, func(r io.Reader) ([]byte, error) { return readImports(r, false) }) testRead(t, tests, func(r io.Reader) ([]byte, error) { return readImports(r, false, nil) })
} }
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