github.com/goplusjs/gopherjs@v1.2.6-0.20211206034512-f187917453b8/build/build_test.go (about) 1 package build 2 3 import ( 4 "fmt" 5 gobuild "go/build" 6 "go/token" 7 "strconv" 8 "strings" 9 "testing" 10 11 "github.com/kisielk/gotool" 12 "github.com/shurcooL/go/importgraphutil" 13 ) 14 15 // Natives augment the standard library with GopherJS-specific changes. 16 // This test ensures that none of the standard library packages are modified 17 // in a way that adds imports which the original upstream standard library package 18 // does not already import. Doing that can increase generated output size or cause 19 // other unexpected issues (since the cmd/go tool does not know about these extra imports), 20 // so it's best to avoid it. 21 // 22 // It checks all standard library packages. Each package is considered as a normal 23 // package, as a test package, and as an external test package. 24 func TestNativesDontImportExtraPackages(t *testing.T) { 25 // Calculate the forward import graph for all standard library packages. 26 // It's needed for populateImportSet. 27 stdOnly := gobuild.Default 28 stdOnly.GOPATH = "" // We only care about standard library, so skip all GOPATH packages. 29 forward, _, err := importgraphutil.BuildNoTests(&stdOnly) 30 if err != nil { 31 t.Fatalf("importgraphutil.BuildNoTests: %v", err) 32 } 33 34 // populateImportSet takes a slice of imports, and populates set with those 35 // imports, as well as their transitive dependencies. That way, the set can 36 // be quickly queried to check if a package is in the import graph of imports. 37 // 38 // Note, this does not include transitive imports of test/xtest packages, 39 // which could cause some false positives. It currently doesn't, but if it does, 40 // then support for that should be added here. 41 populateImportSet := func(imports []string, set *stringSet) { 42 for _, p := range imports { 43 (*set)[p] = struct{}{} 44 switch p { 45 case "sync": 46 (*set)["github.com/gopherjs/gopherjs/nosync"] = struct{}{} 47 } 48 transitiveImports := forward.Search(p) 49 for p := range transitiveImports { 50 (*set)[p] = struct{}{} 51 } 52 } 53 } 54 55 // Check all standard library packages. 56 // 57 // The general strategy is to first import each standard library package using the 58 // normal build.Import, which returns a *build.Package. That contains Imports, TestImports, 59 // and XTestImports values that are considered the "real imports". 60 // 61 // That list of direct imports is then expanded to the transitive closure by populateImportSet, 62 // meaning all packages that are indirectly imported are also added to the set. 63 // 64 // Then, github.com/gopherjs/gopherjs/build.parseAndAugment(*build.Package) returns []*ast.File. 65 // Those augmented parsed Go files of the package are checked, one file at at time, one import 66 // at a time. Each import is verified to belong in the set of allowed real imports. 67 for _, pkg := range gotool.ImportPaths([]string{"std"}) { 68 // Normal package. 69 { 70 // Import the real normal package, and populate its real import set. 71 bpkg, err := gobuild.Import(pkg, "", gobuild.ImportComment) 72 if err != nil { 73 t.Fatalf("gobuild.Import: %v", err) 74 } 75 realImports := make(stringSet) 76 populateImportSet(bpkg.Imports, &realImports) 77 78 // Use parseAndAugment to get a list of augmented AST files. 79 fset := token.NewFileSet() 80 files, err := parseAndAugment(NewBuildContext("", nil), bpkg, false, fset) 81 if err != nil { 82 t.Fatalf("github.com/gopherjs/gopherjs/build.parseAndAugment: %v", err) 83 } 84 85 // Verify imports of normal augmented AST files. 86 for _, f := range files { 87 fileName := fset.File(f.Pos()).Name() 88 normalFile := !strings.HasSuffix(fileName, "_test.go") 89 if !normalFile { 90 continue 91 } 92 for _, imp := range f.Imports { 93 importPath, err := strconv.Unquote(imp.Path.Value) 94 if err != nil { 95 t.Fatalf("strconv.Unquote(%v): %v", imp.Path.Value, err) 96 } 97 if importPath == "github.com/gopherjs/gopherjs/js" { 98 continue 99 } 100 if _, ok := realImports[importPath]; !ok { 101 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) 102 } 103 } 104 } 105 } 106 107 // Test package. 108 { 109 // Import the real test package, and populate its real import set. 110 bpkg, err := gobuild.Import(pkg, "", gobuild.ImportComment) 111 if err != nil { 112 t.Fatalf("gobuild.Import: %v", err) 113 } 114 realTestImports := make(stringSet) 115 populateImportSet(bpkg.TestImports, &realTestImports) 116 117 // Use parseAndAugment to get a list of augmented AST files. 118 fset := token.NewFileSet() 119 files, err := parseAndAugment(NewBuildContext("", nil), bpkg, true, fset) 120 if err != nil { 121 t.Fatalf("github.com/gopherjs/gopherjs/build.parseAndAugment: %v", err) 122 } 123 124 // Verify imports of test augmented AST files. 125 for _, f := range files { 126 fileName, pkgName := fset.File(f.Pos()).Name(), f.Name.String() 127 testFile := strings.HasSuffix(fileName, "_test.go") && !strings.HasSuffix(pkgName, "_test") 128 if !testFile { 129 continue 130 } 131 for _, imp := range f.Imports { 132 importPath, err := strconv.Unquote(imp.Path.Value) 133 if err != nil { 134 t.Fatalf("strconv.Unquote(%v): %v", imp.Path.Value, err) 135 } 136 if importPath == "github.com/gopherjs/gopherjs/js" { 137 continue 138 } 139 if _, ok := realTestImports[importPath]; !ok { 140 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) 141 } 142 } 143 } 144 } 145 146 // External test package. 147 { 148 // Import the real external test package, and populate its real import set. 149 bpkg, err := gobuild.Import(pkg, "", gobuild.ImportComment) 150 if err != nil { 151 t.Fatalf("gobuild.Import: %v", err) 152 } 153 realXTestImports := make(stringSet) 154 populateImportSet(bpkg.XTestImports, &realXTestImports) 155 156 // Add _test suffix to import path to cause parseAndAugment to use external test mode. 157 bpkg.ImportPath += "_test" 158 159 // Use parseAndAugment to get a list of augmented AST files, then check only the external test files. 160 fset := token.NewFileSet() 161 files, err := parseAndAugment(NewBuildContext("", nil), bpkg, true, fset) 162 if err != nil { 163 t.Fatalf("github.com/gopherjs/gopherjs/build.parseAndAugment: %v", err) 164 } 165 166 // Verify imports of external test augmented AST files. 167 for _, f := range files { 168 fileName, pkgName := fset.File(f.Pos()).Name(), f.Name.String() 169 xTestFile := strings.HasSuffix(fileName, "_test.go") && strings.HasSuffix(pkgName, "_test") 170 if !xTestFile { 171 continue 172 } 173 for _, imp := range f.Imports { 174 importPath, err := strconv.Unquote(imp.Path.Value) 175 if err != nil { 176 t.Fatalf("strconv.Unquote(%v): %v", imp.Path.Value, err) 177 } 178 if importPath == "github.com/gopherjs/gopherjs/js" { 179 continue 180 } 181 if _, ok := realXTestImports[importPath]; !ok { 182 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) 183 } 184 } 185 } 186 } 187 } 188 } 189 190 // stringSet is used to print a set of strings in a more readable way. 191 type stringSet map[string]struct{} 192 193 func (m stringSet) String() string { 194 s := make([]string, 0, len(m)) 195 for v := range m { 196 s = append(s, v) 197 } 198 return fmt.Sprintf("%q", s) 199 }