1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
|
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)
}
|