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  }