github.com/gopherjs/gopherjs@v1.19.0-beta1.0.20240506212314-27071a8796e4/build/build_test.go (about)

     1  package build
     2  
     3  import (
     4  	"fmt"
     5  	gobuild "go/build"
     6  	"go/token"
     7  	"strconv"
     8  	"testing"
     9  
    10  	"github.com/gopherjs/gopherjs/internal/srctesting"
    11  	"github.com/shurcooL/go/importgraphutil"
    12  )
    13  
    14  // Natives augment the standard library with GopherJS-specific changes.
    15  // This test ensures that none of the standard library packages are modified
    16  // in a way that adds imports which the original upstream standard library package
    17  // does not already import. Doing that can increase generated output size or cause
    18  // other unexpected issues (since the cmd/go tool does not know about these extra imports),
    19  // so it's best to avoid it.
    20  //
    21  // It checks all standard library packages. Each package is considered as a normal
    22  // package, as a test package, and as an external test package.
    23  func TestNativesDontImportExtraPackages(t *testing.T) {
    24  	// Calculate the forward import graph for all standard library packages.
    25  	// It's needed for populateImportSet.
    26  	stdOnly := goCtx(DefaultEnv())
    27  	// Skip post-load package tweaks, since we are interested in the complete set
    28  	// of original sources.
    29  	stdOnly.noPostTweaks = true
    30  	// We only care about standard library, so skip all GOPATH packages.
    31  	stdOnly.bctx.GOPATH = ""
    32  	forward, _, err := importgraphutil.BuildNoTests(&stdOnly.bctx)
    33  	if err != nil {
    34  		t.Fatalf("importgraphutil.BuildNoTests: %v", err)
    35  	}
    36  
    37  	// populateImportSet takes a slice of imports, and populates set with those
    38  	// imports, as well as their transitive dependencies. That way, the set can
    39  	// be quickly queried to check if a package is in the import graph of imports.
    40  	//
    41  	// Note, this does not include transitive imports of test/xtest packages,
    42  	// which could cause some false positives. It currently doesn't, but if it does,
    43  	// then support for that should be added here.
    44  	populateImportSet := func(imports []string) stringSet {
    45  		set := stringSet{}
    46  		for _, p := range imports {
    47  			set[p] = struct{}{}
    48  			switch p {
    49  			case "sync":
    50  				set["github.com/gopherjs/gopherjs/nosync"] = struct{}{}
    51  			}
    52  			transitiveImports := forward.Search(p)
    53  			for p := range transitiveImports {
    54  				set[p] = struct{}{}
    55  			}
    56  		}
    57  		return set
    58  	}
    59  
    60  	// Check all standard library packages.
    61  	//
    62  	// The general strategy is to first import each standard library package using the
    63  	// normal build.Import, which returns a *build.Package. That contains Imports, TestImports,
    64  	// and XTestImports values that are considered the "real imports".
    65  	//
    66  	// That list of direct imports is then expanded to the transitive closure by populateImportSet,
    67  	// meaning all packages that are indirectly imported are also added to the set.
    68  	//
    69  	// Then, github.com/gopherjs/gopherjs/build.parseAndAugment(*build.Package) returns []*ast.File.
    70  	// Those augmented parsed Go files of the package are checked, one file at at time, one import
    71  	// at a time. Each import is verified to belong in the set of allowed real imports.
    72  	matches, matchErr := stdOnly.Match([]string{"std"})
    73  	if matchErr != nil {
    74  		t.Fatalf("Failed to list standard library packages: %s", err)
    75  	}
    76  	for _, pkgName := range matches {
    77  		pkgName := pkgName // Capture for the goroutine.
    78  		t.Run(pkgName, func(t *testing.T) {
    79  			t.Parallel()
    80  
    81  			pkg, err := stdOnly.Import(pkgName, "", gobuild.ImportComment)
    82  			if err != nil {
    83  				t.Fatalf("gobuild.Import: %v", err)
    84  			}
    85  
    86  			for _, pkgVariant := range []*PackageData{pkg, pkg.TestPackage(), pkg.XTestPackage()} {
    87  				t.Logf("Checking package %s...", pkgVariant)
    88  
    89  				// Capture the set of unmodified package imports.
    90  				realImports := populateImportSet(pkgVariant.Imports)
    91  
    92  				// Use parseAndAugment to get a list of augmented AST files.
    93  				fset := token.NewFileSet()
    94  				files, _, err := parseAndAugment(stdOnly, pkgVariant, pkgVariant.IsTest, fset)
    95  				if err != nil {
    96  					t.Fatalf("github.com/gopherjs/gopherjs/build.parseAndAugment: %v", err)
    97  				}
    98  
    99  				// Verify imports of augmented AST files.
   100  				for _, f := range files {
   101  					fileName := fset.File(f.Pos()).Name()
   102  					for _, imp := range f.Imports {
   103  						importPath, err := strconv.Unquote(imp.Path.Value)
   104  						if err != nil {
   105  							t.Fatalf("strconv.Unquote(%v): %v", imp.Path.Value, err)
   106  						}
   107  						if importPath == "github.com/gopherjs/gopherjs/js" {
   108  							continue
   109  						}
   110  						if _, ok := realImports[importPath]; !ok {
   111  							t.Errorf("augmented package %q imports %q in file %v, but real %q doesn't:\nrealImports = %v",
   112  								pkgVariant, importPath, fileName, pkgVariant.ImportPath, realImports)
   113  						}
   114  					}
   115  				}
   116  			}
   117  		})
   118  	}
   119  }
   120  
   121  // stringSet is used to print a set of strings in a more readable way.
   122  type stringSet map[string]struct{}
   123  
   124  func (m stringSet) String() string {
   125  	s := make([]string, 0, len(m))
   126  	for v := range m {
   127  		s = append(s, v)
   128  	}
   129  	return fmt.Sprintf("%q", s)
   130  }
   131  
   132  func TestOverlayAugmentation(t *testing.T) {
   133  	tests := []struct {
   134  		desc         string
   135  		src          string
   136  		noCodeChange bool
   137  		want         string
   138  		expInfo      map[string]overrideInfo
   139  	}{
   140  		{
   141  			desc: `remove function`,
   142  			src: `func Foo(a, b int) int {
   143  					return a + b
   144  				}`,
   145  			noCodeChange: true,
   146  			expInfo: map[string]overrideInfo{
   147  				`Foo`: {},
   148  			},
   149  		}, {
   150  			desc: `keep function`,
   151  			src: `//gopherjs:keep-original
   152  				func Foo(a, b int) int {
   153  					return a + b
   154  				}`,
   155  			noCodeChange: true,
   156  			expInfo: map[string]overrideInfo{
   157  				`Foo`: {keepOriginal: true},
   158  			},
   159  		}, {
   160  			desc: `remove constants and values`,
   161  			src: `import "time"
   162  
   163  				const (
   164  					foo = 42
   165  					bar = "gopherjs"
   166  				)
   167  
   168  				var now = time.Now`,
   169  			noCodeChange: true,
   170  			expInfo: map[string]overrideInfo{
   171  				`foo`: {},
   172  				`bar`: {},
   173  				`now`: {},
   174  			},
   175  		}, {
   176  			desc: `remove types`,
   177  			src: `type (
   178  					foo struct {}
   179  					bar int
   180  				)
   181  
   182  				type bob interface {}`,
   183  			noCodeChange: true,
   184  			expInfo: map[string]overrideInfo{
   185  				`foo`: {},
   186  				`bar`: {},
   187  				`bob`: {},
   188  			},
   189  		}, {
   190  			desc: `remove methods`,
   191  			src: `type Foo struct {
   192  					bar int
   193  				}
   194  
   195  				func (x *Foo) GetBar() int    { return x.bar }
   196  				func (x *Foo) SetBar(bar int) { x.bar = bar }`,
   197  			noCodeChange: true,
   198  			expInfo: map[string]overrideInfo{
   199  				`Foo`:        {},
   200  				`Foo.GetBar`: {},
   201  				`Foo.SetBar`: {},
   202  			},
   203  		}, {
   204  			desc: `remove generics`,
   205  			src: `import "cmp"
   206  
   207  				type Pointer[T any] struct {}
   208  
   209  				func Sort[S ~[]E, E cmp.Ordered](x S) {}
   210  
   211  				// this is a stub for "func Equal[S ~[]E, E any](s1, s2 S) bool {}"
   212  				func Equal[S ~[]E, E any](s1, s2 S) bool {}`,
   213  			noCodeChange: true,
   214  			expInfo: map[string]overrideInfo{
   215  				`Pointer`: {},
   216  				`Sort`:    {},
   217  				`Equal`:   {},
   218  			},
   219  		}, {
   220  			desc:    `prune an unused import`,
   221  			src:     `import foo "some/other/bar"`,
   222  			want:    ``,
   223  			expInfo: map[string]overrideInfo{},
   224  		}, {
   225  			desc: `purge function`,
   226  			src: `//gopherjs:purge
   227  				func Foo(a, b int) int {
   228  					return a + b
   229  				}`,
   230  			want: ``,
   231  			expInfo: map[string]overrideInfo{
   232  				`Foo`: {},
   233  			},
   234  		}, {
   235  			desc: `purge struct removes an import`,
   236  			src: `import "bytes"
   237  				import "math"
   238  
   239  				//gopherjs:purge
   240  				type Foo struct {
   241  					bar *bytes.Buffer
   242  				}
   243  
   244  				const Tau = math.Pi * 2.0`,
   245  			want: `import "math"
   246  
   247  				const Tau = math.Pi * 2.0`,
   248  			expInfo: map[string]overrideInfo{
   249  				`Foo`: {purgeMethods: true},
   250  				`Tau`: {},
   251  			},
   252  		}, {
   253  			desc: `purge whole type decl`,
   254  			src: `//gopherjs:purge
   255  				type (
   256  					Foo struct {}
   257  					bar interface{}
   258  					bob int
   259  				)`,
   260  			want: ``,
   261  			expInfo: map[string]overrideInfo{
   262  				`Foo`: {purgeMethods: true},
   263  				`bar`: {purgeMethods: true},
   264  				`bob`: {purgeMethods: true},
   265  			},
   266  		}, {
   267  			desc: `purge part of type decl`,
   268  			src: `type (
   269  					Foo struct {}
   270  
   271  					//gopherjs:purge
   272  					bar interface{}
   273  
   274  					//gopherjs:purge
   275  					bob int
   276  				)`,
   277  			want: `type (
   278  					Foo struct {}
   279  				)`,
   280  			expInfo: map[string]overrideInfo{
   281  				`Foo`: {},
   282  				`bar`: {purgeMethods: true},
   283  				`bob`: {purgeMethods: true},
   284  			},
   285  		}, {
   286  			desc: `purge all of a type decl`,
   287  			src: `type (
   288  					//gopherjs:purge
   289  					Foo struct {}
   290  				)`,
   291  			want: ``,
   292  			expInfo: map[string]overrideInfo{
   293  				`Foo`: {purgeMethods: true},
   294  			},
   295  		}, {
   296  			desc: `remove and purge values`,
   297  			src: `import "time"
   298  
   299  				const (
   300  					foo = 42
   301  					//gopherjs:purge
   302  					bar = "gopherjs"
   303  				)
   304  
   305  				//gopherjs:purge
   306  				var now = time.Now`,
   307  			want: `const (
   308  					foo = 42
   309  				)`,
   310  			expInfo: map[string]overrideInfo{
   311  				`foo`: {},
   312  				`bar`: {},
   313  				`now`: {},
   314  			},
   315  		}, {
   316  			desc: `purge all value names`,
   317  			src: `//gopherjs:purge
   318  				var foo, bar int
   319  
   320  				//gopherjs:purge
   321  				const bob, sal = 12, 42`,
   322  			want: ``,
   323  			expInfo: map[string]overrideInfo{
   324  				`foo`: {},
   325  				`bar`: {},
   326  				`bob`: {},
   327  				`sal`: {},
   328  			},
   329  		}, {
   330  			desc: `imports not confused by local variables`,
   331  			src: `import (
   332  					"cmp"
   333  					"time"
   334  				)
   335  
   336  				//gopherjs:purge
   337  				func Sort[S ~[]E, E cmp.Ordered](x S) {}
   338  
   339  				func SecondsSince(start time.Time) int {
   340  					cmp := time.Now().Sub(start)
   341  					return int(cmp.Second())
   342  				}`,
   343  			want: `import (
   344  					"time"
   345  				)
   346  
   347  				func SecondsSince(start time.Time) int {
   348  					cmp := time.Now().Sub(start)
   349  					return int(cmp.Second())
   350  				}`,
   351  			expInfo: map[string]overrideInfo{
   352  				`Sort`:         {},
   353  				`SecondsSince`: {},
   354  			},
   355  		}, {
   356  			desc: `purge generics`,
   357  			src: `import "cmp"
   358  
   359  				//gopherjs:purge
   360  				type Pointer[T any] struct {}
   361  
   362  				//gopherjs:purge
   363  				func Sort[S ~[]E, E cmp.Ordered](x S) {}
   364  
   365  				// stub for "func Equal[S ~[]E, E any](s1, s2 S) bool"
   366  				func Equal() {}`,
   367  			want: `// stub for "func Equal[S ~[]E, E any](s1, s2 S) bool"
   368  				func Equal() {}`,
   369  			expInfo: map[string]overrideInfo{
   370  				`Pointer`: {purgeMethods: true},
   371  				`Sort`:    {},
   372  				`Equal`:   {},
   373  			},
   374  		}, {
   375  			desc: `remove unsafe and embed if not needed`,
   376  			src: `import "unsafe"
   377  				import "embed"
   378  
   379  				//gopherjs:purge
   380  				var eFile embed.FS
   381  
   382  				//gopherjs:purge
   383  				func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)`,
   384  			want: ``,
   385  			expInfo: map[string]overrideInfo{
   386  				`SwapPointer`: {},
   387  				`eFile`:       {},
   388  			},
   389  		}, {
   390  			desc: `keep unsafe and embed for directives`,
   391  			src: `import "unsafe"
   392  				import "embed"
   393  
   394  				//go:embed hello.txt
   395  				var eFile embed.FS
   396  
   397  				//go:linkname runtimeNano runtime.nanotime
   398  				func runtimeNano() int64`,
   399  			want: `import _ "unsafe"
   400  				import "embed"
   401  
   402  				//go:embed hello.txt
   403  				var eFile embed.FS
   404  				
   405  				//go:linkname runtimeNano runtime.nanotime
   406  				func runtimeNano() int64`,
   407  			expInfo: map[string]overrideInfo{
   408  				`eFile`:       {},
   409  				`runtimeNano`: {},
   410  			},
   411  		},
   412  	}
   413  
   414  	for _, test := range tests {
   415  		t.Run(test.desc, func(t *testing.T) {
   416  			const pkgName = "package testpackage\n\n"
   417  			if test.noCodeChange {
   418  				test.want = test.src
   419  			}
   420  
   421  			f := srctesting.New(t)
   422  			fileSrc := f.Parse("test.go", pkgName+test.src)
   423  
   424  			overrides := map[string]overrideInfo{}
   425  			augmentOverlayFile(fileSrc, overrides)
   426  			pruneImports(fileSrc)
   427  
   428  			got := srctesting.Format(t, f.FileSet, fileSrc)
   429  
   430  			fileWant := f.Parse("test.go", pkgName+test.want)
   431  			want := srctesting.Format(t, f.FileSet, fileWant)
   432  
   433  			if got != want {
   434  				t.Errorf("augmentOverlayFile and pruneImports got unexpected code:\n"+
   435  					"returned:\n\t%q\nwant:\n\t%q", got, want)
   436  			}
   437  
   438  			for key, expInfo := range test.expInfo {
   439  				if gotInfo, ok := overrides[key]; !ok {
   440  					t.Errorf(`%q was expected but not gotten`, key)
   441  				} else if expInfo != gotInfo {
   442  					t.Errorf(`%q had wrong info, got %+v`, key, gotInfo)
   443  				}
   444  			}
   445  			for key, gotInfo := range overrides {
   446  				if _, ok := test.expInfo[key]; !ok {
   447  					t.Errorf(`%q with %+v was not expected`, key, gotInfo)
   448  				}
   449  			}
   450  		})
   451  	}
   452  }
   453  
   454  func TestOriginalAugmentation(t *testing.T) {
   455  	tests := []struct {
   456  		desc string
   457  		info map[string]overrideInfo
   458  		src  string
   459  		want string
   460  	}{
   461  		{
   462  			desc: `do not affect function`,
   463  			info: map[string]overrideInfo{},
   464  			src: `func Foo(a, b int) int {
   465  						return a + b
   466  					}`,
   467  			want: `func Foo(a, b int) int {
   468  						return a + b
   469  					}`,
   470  		}, {
   471  			desc: `change unnamed sync import`,
   472  			info: map[string]overrideInfo{},
   473  			src: `import "sync"
   474  
   475  				var _ = &sync.Mutex{}`,
   476  			want: `import sync "github.com/gopherjs/gopherjs/nosync"
   477  
   478  				var _ = &sync.Mutex{}`,
   479  		}, {
   480  			desc: `change named sync import`,
   481  			info: map[string]overrideInfo{},
   482  			src: `import foo "sync"
   483  
   484  				var _ = &foo.Mutex{}`,
   485  			want: `import foo "github.com/gopherjs/gopherjs/nosync"
   486  
   487  				var _ = &foo.Mutex{}`,
   488  		}, {
   489  			desc: `remove function`,
   490  			info: map[string]overrideInfo{
   491  				`Foo`: {},
   492  			},
   493  			src: `func Foo(a, b int) int {
   494  					return a + b
   495  				}`,
   496  			want: ``,
   497  		}, {
   498  			desc: `keep original function`,
   499  			info: map[string]overrideInfo{
   500  				`Foo`: {keepOriginal: true},
   501  			},
   502  			src: `func Foo(a, b int) int {
   503  					return a + b
   504  				}`,
   505  			want: `func _gopherjs_original_Foo(a, b int) int {
   506  					return a + b
   507  				}`,
   508  		}, {
   509  			desc: `remove types and values`,
   510  			info: map[string]overrideInfo{
   511  				`Foo`:  {},
   512  				`now`:  {},
   513  				`bar1`: {},
   514  			},
   515  			src: `import "time"
   516  
   517  				type Foo interface{
   518  					bob(a, b string) string
   519  				}
   520  
   521  				var now = time.Now
   522  				const bar1, bar2 = 21, 42`,
   523  			want: `const bar2 = 42`,
   524  		}, {
   525  			desc: `remove in multi-value context`,
   526  			info: map[string]overrideInfo{
   527  				`bar`: {},
   528  			},
   529  			src: `const foo, bar = func() (int, int) {
   530  					return 24, 12
   531  				}()`,
   532  			want: `const foo, _ = func() (int, int) {
   533  					return 24, 12
   534  				}()`,
   535  		}, {
   536  			desc: `full remove in multi-value context`,
   537  			info: map[string]overrideInfo{
   538  				`bar`: {},
   539  			},
   540  			src: `const _, bar = func() (int, int) {
   541  					return 24, 12
   542  				}()`,
   543  			want: ``,
   544  		}, {
   545  			desc: `remove methods`,
   546  			info: map[string]overrideInfo{
   547  				`Foo.GetBar`: {},
   548  				`Foo.SetBar`: {},
   549  			},
   550  			src: `
   551  				func (x Foo) GetBar() int     { return x.bar }
   552  				func (x *Foo) SetBar(bar int) { x.bar = bar }`,
   553  			want: ``,
   554  		}, {
   555  			desc: `purge struct and methods`,
   556  			info: map[string]overrideInfo{
   557  				`Foo`: {purgeMethods: true},
   558  			},
   559  			src: `type Foo struct{
   560  					bar int
   561  				}
   562  
   563  				func (f Foo) GetBar() int     { return f.bar }
   564  				func (f *Foo) SetBar(bar int) { f.bar = bar }
   565  
   566  				func NewFoo(bar int) *Foo { return &Foo{bar: bar} }`,
   567  			// NewFoo is not removed automatically since
   568  			// only functions with Foo as a receiver are removed.
   569  			want: `func NewFoo(bar int) *Foo { return &Foo{bar: bar} }`,
   570  		}, {
   571  			desc: `remove generics`,
   572  			info: map[string]overrideInfo{
   573  				`Pointer`: {},
   574  				`Sort`:    {},
   575  				`Equal`:   {},
   576  			},
   577  			src: `import "cmp"
   578  			
   579  				// keeps the isOnlyImports from skipping what is being tested.
   580  				func foo() {}
   581  
   582  				type Pointer[T any] struct {}
   583  
   584  				func Sort[S ~[]E, E cmp.Ordered](x S) {}
   585  
   586  				// overlay had stub "func Equal() {}"
   587  				func Equal[S ~[]E, E any](s1, s2 S) bool {}`,
   588  			want: `// keeps the isOnlyImports from skipping what is being tested.
   589  				func foo() {}`,
   590  		}, {
   591  			desc: `purge generics`,
   592  			info: map[string]overrideInfo{
   593  				`Pointer`: {purgeMethods: true},
   594  				`Sort`:    {},
   595  				`Equal`:   {},
   596  			},
   597  			src: `import "cmp"
   598  
   599  				// keeps the isOnlyImports from skipping what is being tested.
   600  				func foo() {}
   601  
   602  				type Pointer[T any] struct {}
   603  				func (x *Pointer[T]) Load() *T {}
   604  				func (x *Pointer[T]) Store(val *T) {}
   605  
   606  				func Sort[S ~[]E, E cmp.Ordered](x S) {}
   607  
   608  				// overlay had stub "func Equal() {}"
   609  				func Equal[S ~[]E, E any](s1, s2 S) bool {}`,
   610  			want: `// keeps the isOnlyImports from skipping what is being tested.
   611  				func foo() {}`,
   612  		}, {
   613  			desc: `prune an unused import`,
   614  			info: map[string]overrideInfo{},
   615  			src: `import foo "some/other/bar"
   616  			
   617  				// keeps the isOnlyImports from skipping what is being tested.
   618  				func foo() {}`,
   619  			want: `// keeps the isOnlyImports from skipping what is being tested.
   620  				func foo() {}`,
   621  		}, {
   622  			desc: `override signature of function`,
   623  			info: map[string]overrideInfo{
   624  				`Foo`: {
   625  					overrideSignature: srctesting.ParseFuncDecl(t,
   626  						`package whatever
   627  						func Foo(a, b any) (any, bool) {}`),
   628  				},
   629  			},
   630  			src: `func Foo[T comparable](a, b T) (T, bool) {
   631  					if a == b {
   632  						return a, true
   633  					}
   634  					return b, false
   635  				}`,
   636  			want: `func Foo(a, b any) (any, bool) {
   637  				if a == b {
   638  					return a, true
   639  				}
   640  				return b, false
   641  			}`,
   642  		}, {
   643  			desc: `override signature of method`,
   644  			info: map[string]overrideInfo{
   645  				`Foo.Bar`: {
   646  					overrideSignature: srctesting.ParseFuncDecl(t,
   647  						`package whatever
   648  						func (r *Foo) Bar(a, b any) (any, bool) {}`),
   649  				},
   650  			},
   651  			src: `func (r *Foo[T]) Bar(a, b T) (T, bool) {
   652  					if r.isSame(a, b) {
   653  						return a, true
   654  					}
   655  					return b, false
   656  				}`,
   657  			want: `func (r *Foo) Bar(a, b any) (any, bool) {
   658  					if r.isSame(a, b) {
   659  						return a, true
   660  					}
   661  					return b, false
   662  				}`,
   663  		}, {
   664  			desc: `empty file removes all imports`,
   665  			info: map[string]overrideInfo{
   666  				`foo`: {},
   667  			},
   668  			src: `import . "math/rand"
   669  				func foo() int {
   670  					return Int()
   671  				}`,
   672  			want: ``,
   673  		}, {
   674  			desc: `empty file with directive`,
   675  			info: map[string]overrideInfo{
   676  				`foo`: {},
   677  			},
   678  			src: `//go:linkname foo bar
   679  				import _ "unsafe"`,
   680  			want: `//go:linkname foo bar
   681  				import _ "unsafe"`,
   682  		}, {
   683  			desc: `multiple imports for directives`,
   684  			info: map[string]overrideInfo{
   685  				`A`: {},
   686  				`C`: {},
   687  			},
   688  			src: `import "unsafe"
   689  				import "embed"
   690  
   691  				//go:embed hello.txt
   692  				var A embed.FS
   693  
   694  				//go:embed goodbye.txt
   695  				var B string
   696  				
   697  				var C unsafe.Pointer
   698  				
   699  				// override Now with hardcoded time for testing
   700  				//go:linkname timeNow time.Now
   701  				func timeNow() time.Time {
   702  					return time.Date(2012, 8, 6, 0, 0, 0, 0, time.UTC)
   703  				}`,
   704  			want: `import _ "unsafe"
   705  				import _ "embed"
   706  
   707  				//go:embed goodbye.txt
   708  				var B string
   709  
   710  				// override Now with hardcoded time for testing
   711  				//go:linkname timeNow time.Now
   712  				func timeNow() time.Time {
   713  					return time.Date(2012, 8, 6, 0, 0, 0, 0, time.UTC)
   714  				}`,
   715  		},
   716  	}
   717  
   718  	for _, test := range tests {
   719  		t.Run(test.desc, func(t *testing.T) {
   720  			pkgName := "package testpackage\n\n"
   721  			importPath := `math/rand`
   722  			f := srctesting.New(t)
   723  			fileSrc := f.Parse("test.go", pkgName+test.src)
   724  
   725  			augmentOriginalImports(importPath, fileSrc)
   726  			augmentOriginalFile(fileSrc, test.info)
   727  			pruneImports(fileSrc)
   728  
   729  			got := srctesting.Format(t, f.FileSet, fileSrc)
   730  
   731  			fileWant := f.Parse("test.go", pkgName+test.want)
   732  			want := srctesting.Format(t, f.FileSet, fileWant)
   733  
   734  			if got != want {
   735  				t.Errorf("augmentOriginalImports, augmentOriginalFile, and pruneImports got unexpected code:\n"+
   736  					"returned:\n\t%q\nwant:\n\t%q", got, want)
   737  			}
   738  		})
   739  	}
   740  }