You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
200 lines
7.1 KiB
200 lines
7.1 KiB
package build
|
|
|
|
import (
|
|
"fmt"
|
|
gobuild "go/build"
|
|
"go/token"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/kisielk/gotool"
|
|
"github.com/shurcooL/go/importgraphutil"
|
|
)
|
|
|
|
// Natives augment the standard library with GopherJS-specific changes.
|
|
// This test ensures that none of the standard library packages are modified
|
|
// in a way that adds imports which the original upstream standard library package
|
|
// does not already import. Doing that can increase generated output size or cause
|
|
// other unexpected issues (since the cmd/go tool does not know about these extra imports),
|
|
// so it's best to avoid it.
|
|
//
|
|
// It checks all standard library packages. Each package is considered as a normal
|
|
// package, as a test package, and as an external test package.
|
|
func TestNativesDontImportExtraPackages(t *testing.T) {
|
|
// Calculate the forward import graph for all standard library packages.
|
|
// It's needed for populateImportSet.
|
|
stdOnly := gobuild.Default
|
|
stdOnly.GOPATH = "" // We only care about standard library, so skip all GOPATH packages.
|
|
forward, _, err := importgraphutil.BuildNoTests(&stdOnly)
|
|
if err != nil {
|
|
t.Fatalf("importgraphutil.BuildNoTests: %v", err)
|
|
}
|
|
|
|
// populateImportSet takes a slice of imports, and populates set with those
|
|
// imports, as well as their transitive dependencies. That way, the set can
|
|
// be quickly queried to check if a package is in the import graph of imports.
|
|
//
|
|
// Note, this does not include transitive imports of test/xtest packages,
|
|
// which could cause some false positives. It currently doesn't, but if it does,
|
|
// then support for that should be added here.
|
|
populateImportSet := func(imports []string, set *stringSet) {
|
|
for _, p := range imports {
|
|
(*set)[p] = struct{}{}
|
|
switch p {
|
|
case "sync":
|
|
(*set)["github.com/gopherjs/gopherjs/nosync"] = struct{}{}
|
|
}
|
|
transitiveImports := forward.Search(p)
|
|
for p := range transitiveImports {
|
|
(*set)[p] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check all standard library packages.
|
|
//
|
|
// The general strategy is to first import each standard library package using the
|
|
// normal build.Import, which returns a *build.Package. That contains Imports, TestImports,
|
|
// and XTestImports values that are considered the "real imports".
|
|
//
|
|
// That list of direct imports is then expanded to the transitive closure by populateImportSet,
|
|
// meaning all packages that are indirectly imported are also added to the set.
|
|
//
|
|
// Then, github.com/gopherjs/gopherjs/build.parseAndAugment(*build.Package) returns []*ast.File.
|
|
// Those augmented parsed Go files of the package are checked, one file at at time, one import
|
|
// at a time. Each import is verified to belong in the set of allowed real imports.
|
|
for _, pkg := range gotool.ImportPaths([]string{"std"}) {
|
|
// Normal package.
|
|
{
|
|
// Import the real normal package, and populate its real import set.
|
|
bpkg, err := gobuild.Import(pkg, "", gobuild.ImportComment)
|
|
if err != nil {
|
|
t.Fatalf("gobuild.Import: %v", err)
|
|
}
|
|
realImports := make(stringSet)
|
|
populateImportSet(bpkg.Imports, &realImports)
|
|
|
|
// Use parseAndAugment to get a list of augmented AST files.
|
|
fset := token.NewFileSet()
|
|
files, err := parseAndAugment(bpkg, false, fset)
|
|
if err != nil {
|
|
t.Fatalf("github.com/gopherjs/gopherjs/build.parseAndAugment: %v", err)
|
|
}
|
|
|
|
// Verify imports of normal augmented AST files.
|
|
for _, f := range files {
|
|
fileName := fset.File(f.Pos()).Name()
|
|
normalFile := !strings.HasSuffix(fileName, "_test.go")
|
|
if !normalFile {
|
|
continue
|
|
}
|
|
for _, imp := range f.Imports {
|
|
importPath, err := strconv.Unquote(imp.Path.Value)
|
|
if err != nil {
|
|
t.Fatalf("strconv.Unquote(%v): %v", imp.Path.Value, err)
|
|
}
|
|
if importPath == "github.com/gopherjs/gopherjs/js" {
|
|
continue
|
|
}
|
|
if _, ok := realImports[importPath]; !ok {
|
|
t.Errorf("augmented normal package %q imports %q in file %v, but real %q doesn't:\nrealImports = %v", bpkg.ImportPath, importPath, fileName, bpkg.ImportPath, realImports)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test package.
|
|
{
|
|
// Import the real test package, and populate its real import set.
|
|
bpkg, err := gobuild.Import(pkg, "", gobuild.ImportComment)
|
|
if err != nil {
|
|
t.Fatalf("gobuild.Import: %v", err)
|
|
}
|
|
realTestImports := make(stringSet)
|
|
populateImportSet(bpkg.TestImports, &realTestImports)
|
|
|
|
// Use parseAndAugment to get a list of augmented AST files.
|
|
fset := token.NewFileSet()
|
|
files, err := parseAndAugment(bpkg, true, fset)
|
|
if err != nil {
|
|
t.Fatalf("github.com/gopherjs/gopherjs/build.parseAndAugment: %v", err)
|
|
}
|
|
|
|
// Verify imports of test augmented AST files.
|
|
for _, f := range files {
|
|
fileName, pkgName := fset.File(f.Pos()).Name(), f.Name.String()
|
|
testFile := strings.HasSuffix(fileName, "_test.go") && !strings.HasSuffix(pkgName, "_test")
|
|
if !testFile {
|
|
continue
|
|
}
|
|
for _, imp := range f.Imports {
|
|
importPath, err := strconv.Unquote(imp.Path.Value)
|
|
if err != nil {
|
|
t.Fatalf("strconv.Unquote(%v): %v", imp.Path.Value, err)
|
|
}
|
|
if importPath == "github.com/gopherjs/gopherjs/js" {
|
|
continue
|
|
}
|
|
if _, ok := realTestImports[importPath]; !ok {
|
|
t.Errorf("augmented test package %q imports %q in file %v, but real %q doesn't:\nrealTestImports = %v", bpkg.ImportPath, importPath, fileName, bpkg.ImportPath, realTestImports)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// External test package.
|
|
{
|
|
// Import the real external test package, and populate its real import set.
|
|
bpkg, err := gobuild.Import(pkg, "", gobuild.ImportComment)
|
|
if err != nil {
|
|
t.Fatalf("gobuild.Import: %v", err)
|
|
}
|
|
realXTestImports := make(stringSet)
|
|
populateImportSet(bpkg.XTestImports, &realXTestImports)
|
|
|
|
// Add _test suffix to import path to cause parseAndAugment to use external test mode.
|
|
bpkg.ImportPath += "_test"
|
|
|
|
// Use parseAndAugment to get a list of augmented AST files, then check only the external test files.
|
|
fset := token.NewFileSet()
|
|
files, err := parseAndAugment(bpkg, true, fset)
|
|
if err != nil {
|
|
t.Fatalf("github.com/gopherjs/gopherjs/build.parseAndAugment: %v", err)
|
|
}
|
|
|
|
// Verify imports of external test augmented AST files.
|
|
for _, f := range files {
|
|
fileName, pkgName := fset.File(f.Pos()).Name(), f.Name.String()
|
|
xTestFile := strings.HasSuffix(fileName, "_test.go") && strings.HasSuffix(pkgName, "_test")
|
|
if !xTestFile {
|
|
continue
|
|
}
|
|
for _, imp := range f.Imports {
|
|
importPath, err := strconv.Unquote(imp.Path.Value)
|
|
if err != nil {
|
|
t.Fatalf("strconv.Unquote(%v): %v", imp.Path.Value, err)
|
|
}
|
|
if importPath == "github.com/gopherjs/gopherjs/js" {
|
|
continue
|
|
}
|
|
if _, ok := realXTestImports[importPath]; !ok {
|
|
t.Errorf("augmented external test package %q imports %q in file %v, but real %q doesn't:\nrealXTestImports = %v", bpkg.ImportPath, importPath, fileName, bpkg.ImportPath, realXTestImports)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// stringSet is used to print a set of strings in a more readable way.
|
|
type stringSet map[string]struct{}
|
|
|
|
func (m stringSet) String() string {
|
|
s := make([]string, 0, len(m))
|
|
for v := range m {
|
|
s = append(s, v)
|
|
}
|
|
return fmt.Sprintf("%q", s)
|
|
}
|