golang.org/x/tools@v0.21.0/go/loader/loader_test.go (about)

     1  // Copyright 2013 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // No testdata on Android.
     6  
     7  //go:build !android
     8  // +build !android
     9  
    10  package loader_test
    11  
    12  import (
    13  	"fmt"
    14  	"go/build"
    15  	"go/constant"
    16  	"go/types"
    17  	"os"
    18  	"path/filepath"
    19  	"reflect"
    20  	"runtime"
    21  	"sort"
    22  	"strings"
    23  	"sync"
    24  	"testing"
    25  
    26  	"golang.org/x/tools/go/buildutil"
    27  	"golang.org/x/tools/go/loader"
    28  	"golang.org/x/tools/internal/testenv"
    29  )
    30  
    31  func TestMain(m *testing.M) {
    32  	testenv.ExitIfSmallMachine()
    33  	os.Exit(m.Run())
    34  }
    35  
    36  // TestFromArgs checks that conf.FromArgs populates conf correctly.
    37  // It does no I/O.
    38  func TestFromArgs(t *testing.T) {
    39  	type result struct {
    40  		Err        string
    41  		Rest       []string
    42  		ImportPkgs map[string]bool
    43  		CreatePkgs []loader.PkgSpec
    44  	}
    45  	for _, test := range []struct {
    46  		args  []string
    47  		tests bool
    48  		want  result
    49  	}{
    50  		// Mix of existing and non-existent packages.
    51  		{
    52  			args: []string{"nosuchpkg", "errors"},
    53  			want: result{
    54  				ImportPkgs: map[string]bool{"errors": false, "nosuchpkg": false},
    55  			},
    56  		},
    57  		// Same, with -test flag.
    58  		{
    59  			args:  []string{"nosuchpkg", "errors"},
    60  			tests: true,
    61  			want: result{
    62  				ImportPkgs: map[string]bool{"errors": true, "nosuchpkg": true},
    63  			},
    64  		},
    65  		// Surplus arguments.
    66  		{
    67  			args: []string{"fmt", "errors", "--", "surplus"},
    68  			want: result{
    69  				Rest:       []string{"surplus"},
    70  				ImportPkgs: map[string]bool{"errors": false, "fmt": false},
    71  			},
    72  		},
    73  		// Ad hoc package specified as *.go files.
    74  		{
    75  			args: []string{"foo.go", "bar.go"},
    76  			want: result{CreatePkgs: []loader.PkgSpec{{
    77  				Filenames: []string{"foo.go", "bar.go"},
    78  			}}},
    79  		},
    80  		// Mixture of *.go and import paths.
    81  		{
    82  			args: []string{"foo.go", "fmt"},
    83  			want: result{
    84  				Err: "named files must be .go files: fmt",
    85  			},
    86  		},
    87  	} {
    88  		var conf loader.Config
    89  		rest, err := conf.FromArgs(test.args, test.tests)
    90  		got := result{
    91  			Rest:       rest,
    92  			ImportPkgs: conf.ImportPkgs,
    93  			CreatePkgs: conf.CreatePkgs,
    94  		}
    95  		if err != nil {
    96  			got.Err = err.Error()
    97  		}
    98  		if !reflect.DeepEqual(got, test.want) {
    99  			t.Errorf("FromArgs(%q) = %+v, want %+v", test.args, got, test.want)
   100  		}
   101  	}
   102  }
   103  
   104  func TestLoad_NoInitialPackages(t *testing.T) {
   105  	var conf loader.Config
   106  
   107  	const wantErr = "no initial packages were loaded"
   108  
   109  	prog, err := conf.Load()
   110  	if err == nil {
   111  		t.Errorf("Load succeeded unexpectedly, want %q", wantErr)
   112  	} else if err.Error() != wantErr {
   113  		t.Errorf("Load failed with wrong error %q, want %q", err, wantErr)
   114  	}
   115  	if prog != nil {
   116  		t.Errorf("Load unexpectedly returned a Program")
   117  	}
   118  }
   119  
   120  func TestLoad_MissingInitialPackage(t *testing.T) {
   121  	var conf loader.Config
   122  	conf.Import("nosuchpkg")
   123  	conf.Import("errors")
   124  
   125  	const wantErr = "couldn't load packages due to errors: nosuchpkg"
   126  
   127  	prog, err := conf.Load()
   128  	if err == nil {
   129  		t.Errorf("Load succeeded unexpectedly, want %q", wantErr)
   130  	} else if err.Error() != wantErr {
   131  		t.Errorf("Load failed with wrong error %q, want %q", err, wantErr)
   132  	}
   133  	if prog != nil {
   134  		t.Errorf("Load unexpectedly returned a Program")
   135  	}
   136  }
   137  
   138  func TestLoad_MissingInitialPackage_AllowErrors(t *testing.T) {
   139  	if runtime.Compiler == "gccgo" {
   140  		t.Skip("gccgo has no standard library test files")
   141  	}
   142  
   143  	var conf loader.Config
   144  	conf.AllowErrors = true
   145  	conf.Import("nosuchpkg")
   146  	conf.ImportWithTests("errors")
   147  
   148  	prog, err := conf.Load()
   149  	if err != nil {
   150  		t.Errorf("Load failed unexpectedly: %v", err)
   151  	}
   152  	if prog == nil {
   153  		t.Fatalf("Load returned a nil Program")
   154  	}
   155  	if got, want := created(prog), "errors_test"; got != want {
   156  		t.Errorf("Created = %s, want %s", got, want)
   157  	}
   158  	if got, want := imported(prog), "errors"; got != want {
   159  		t.Errorf("Imported = %s, want %s", got, want)
   160  	}
   161  }
   162  
   163  func TestCreateUnnamedPackage(t *testing.T) {
   164  	var conf loader.Config
   165  	conf.CreateFromFilenames("")
   166  	prog, err := conf.Load()
   167  	if err != nil {
   168  		t.Fatalf("Load failed: %v", err)
   169  	}
   170  	if got, want := fmt.Sprint(prog.InitialPackages()), "[(unnamed)]"; got != want {
   171  		t.Errorf("InitialPackages = %s, want %s", got, want)
   172  	}
   173  }
   174  
   175  func TestLoad_MissingFileInCreatedPackage(t *testing.T) {
   176  	var conf loader.Config
   177  	conf.CreateFromFilenames("", "missing.go")
   178  
   179  	const wantErr = "couldn't load packages due to errors: (unnamed)"
   180  
   181  	prog, err := conf.Load()
   182  	if prog != nil {
   183  		t.Errorf("Load unexpectedly returned a Program")
   184  	}
   185  	if err == nil {
   186  		t.Fatalf("Load succeeded unexpectedly, want %q", wantErr)
   187  	}
   188  	if err.Error() != wantErr {
   189  		t.Fatalf("Load failed with wrong error %q, want %q", err, wantErr)
   190  	}
   191  }
   192  
   193  func TestLoad_MissingFileInCreatedPackage_AllowErrors(t *testing.T) {
   194  	conf := loader.Config{AllowErrors: true}
   195  	conf.CreateFromFilenames("", "missing.go")
   196  
   197  	prog, err := conf.Load()
   198  	if err != nil {
   199  		t.Errorf("Load failed: %v", err)
   200  	}
   201  	if got, want := fmt.Sprint(prog.InitialPackages()), "[(unnamed)]"; got != want {
   202  		t.Fatalf("InitialPackages = %s, want %s", got, want)
   203  	}
   204  }
   205  
   206  func TestLoad_ParseError(t *testing.T) {
   207  	var conf loader.Config
   208  	conf.CreateFromFilenames("badpkg", "testdata/badpkgdecl.go")
   209  
   210  	const wantErr = "couldn't load packages due to errors: badpkg"
   211  
   212  	prog, err := conf.Load()
   213  	if prog != nil {
   214  		t.Errorf("Load unexpectedly returned a Program")
   215  	}
   216  	if err == nil {
   217  		t.Fatalf("Load succeeded unexpectedly, want %q", wantErr)
   218  	}
   219  	if err.Error() != wantErr {
   220  		t.Fatalf("Load failed with wrong error %q, want %q", err, wantErr)
   221  	}
   222  }
   223  
   224  func TestLoad_ParseError_AllowErrors(t *testing.T) {
   225  	var conf loader.Config
   226  	conf.AllowErrors = true
   227  	conf.CreateFromFilenames("badpkg", "testdata/badpkgdecl.go")
   228  
   229  	prog, err := conf.Load()
   230  	if err != nil {
   231  		t.Errorf("Load failed unexpectedly: %v", err)
   232  	}
   233  	if prog == nil {
   234  		t.Fatalf("Load returned a nil Program")
   235  	}
   236  	if got, want := created(prog), "badpkg"; got != want {
   237  		t.Errorf("Created = %s, want %s", got, want)
   238  	}
   239  
   240  	badpkg := prog.Created[0]
   241  	if len(badpkg.Files) != 1 {
   242  		t.Errorf("badpkg has %d files, want 1", len(badpkg.Files))
   243  	}
   244  	wantErr := filepath.Join("testdata", "badpkgdecl.go") + ":1:34: expected 'package', found 'EOF'"
   245  	if !hasError(badpkg.Errors, wantErr) {
   246  		t.Errorf("badpkg.Errors = %v, want %s", badpkg.Errors, wantErr)
   247  	}
   248  }
   249  
   250  func TestLoad_FromSource_Success(t *testing.T) {
   251  	var conf loader.Config
   252  	conf.CreateFromFilenames("P", "testdata/a.go", "testdata/b.go")
   253  
   254  	prog, err := conf.Load()
   255  	if err != nil {
   256  		t.Errorf("Load failed unexpectedly: %v", err)
   257  	}
   258  	if prog == nil {
   259  		t.Fatalf("Load returned a nil Program")
   260  	}
   261  	if got, want := created(prog), "P"; got != want {
   262  		t.Errorf("Created = %s, want %s", got, want)
   263  	}
   264  }
   265  
   266  func TestLoad_FromImports_Success(t *testing.T) {
   267  	if runtime.Compiler == "gccgo" {
   268  		t.Skip("gccgo has no standard library test files")
   269  	}
   270  
   271  	var conf loader.Config
   272  	conf.ImportWithTests("fmt")
   273  	conf.ImportWithTests("errors")
   274  
   275  	prog, err := conf.Load()
   276  	if err != nil {
   277  		t.Errorf("Load failed unexpectedly: %v", err)
   278  	}
   279  	if prog == nil {
   280  		t.Fatalf("Load returned a nil Program")
   281  	}
   282  	if got, want := created(prog), "errors_test fmt_test"; got != want {
   283  		t.Errorf("Created = %q, want %s", got, want)
   284  	}
   285  	if got, want := imported(prog), "errors fmt"; got != want {
   286  		t.Errorf("Imported = %s, want %s", got, want)
   287  	}
   288  	// Check set of transitive packages.
   289  	// There are >30 and the set may grow over time, so only check a few.
   290  	want := map[string]bool{
   291  		"strings": true,
   292  		"time":    true,
   293  		"runtime": true,
   294  		"testing": true,
   295  		"unicode": true,
   296  	}
   297  	for _, path := range all(prog) {
   298  		delete(want, path)
   299  	}
   300  	if len(want) > 0 {
   301  		t.Errorf("AllPackages is missing these keys: %q", keys(want))
   302  	}
   303  }
   304  
   305  func TestLoad_MissingIndirectImport(t *testing.T) {
   306  	pkgs := map[string]string{
   307  		"a": `package a; import _ "b"`,
   308  		"b": `package b; import _ "c"`,
   309  	}
   310  	conf := loader.Config{Build: fakeContext(pkgs)}
   311  	conf.Import("a")
   312  
   313  	const wantErr = "couldn't load packages due to errors: b"
   314  
   315  	prog, err := conf.Load()
   316  	if err == nil {
   317  		t.Errorf("Load succeeded unexpectedly, want %q", wantErr)
   318  	} else if err.Error() != wantErr {
   319  		t.Errorf("Load failed with wrong error %q, want %q", err, wantErr)
   320  	}
   321  	if prog != nil {
   322  		t.Errorf("Load unexpectedly returned a Program")
   323  	}
   324  }
   325  
   326  func TestLoad_BadDependency_AllowErrors(t *testing.T) {
   327  	for _, test := range []struct {
   328  		descr    string
   329  		pkgs     map[string]string
   330  		wantPkgs string
   331  	}{
   332  
   333  		{
   334  			descr: "missing dependency",
   335  			pkgs: map[string]string{
   336  				"a": `package a; import _ "b"`,
   337  				"b": `package b; import _ "c"`,
   338  			},
   339  			wantPkgs: "a b",
   340  		},
   341  		{
   342  			descr: "bad package decl in dependency",
   343  			pkgs: map[string]string{
   344  				"a": `package a; import _ "b"`,
   345  				"b": `package b; import _ "c"`,
   346  				"c": `package`,
   347  			},
   348  			wantPkgs: "a b",
   349  		},
   350  		{
   351  			descr: "parse error in dependency",
   352  			pkgs: map[string]string{
   353  				"a": `package a; import _ "b"`,
   354  				"b": `package b; import _ "c"`,
   355  				"c": `package c; var x = `,
   356  			},
   357  			wantPkgs: "a b c",
   358  		},
   359  	} {
   360  		conf := loader.Config{
   361  			AllowErrors: true,
   362  			Build:       fakeContext(test.pkgs),
   363  		}
   364  		conf.Import("a")
   365  
   366  		prog, err := conf.Load()
   367  		if err != nil {
   368  			t.Errorf("%s: Load failed unexpectedly: %v", test.descr, err)
   369  		}
   370  		if prog == nil {
   371  			t.Fatalf("%s: Load returned a nil Program", test.descr)
   372  		}
   373  
   374  		if got, want := imported(prog), "a"; got != want {
   375  			t.Errorf("%s: Imported = %s, want %s", test.descr, got, want)
   376  		}
   377  		if got := all(prog); strings.Join(got, " ") != test.wantPkgs {
   378  			t.Errorf("%s: AllPackages = %s, want %s", test.descr, got, test.wantPkgs)
   379  		}
   380  	}
   381  }
   382  
   383  func TestCwd(t *testing.T) {
   384  	ctxt := fakeContext(map[string]string{"one/two/three": `package three`})
   385  	for _, test := range []struct {
   386  		cwd, arg, want string
   387  	}{
   388  		{cwd: "/go/src/one", arg: "./two/three", want: "one/two/three"},
   389  		{cwd: "/go/src/one", arg: "../one/two/three", want: "one/two/three"},
   390  		{cwd: "/go/src/one", arg: "one/two/three", want: "one/two/three"},
   391  		{cwd: "/go/src/one/two/three", arg: ".", want: "one/two/three"},
   392  		{cwd: "/go/src/one", arg: "two/three", want: ""},
   393  	} {
   394  		conf := loader.Config{
   395  			Cwd:   test.cwd,
   396  			Build: ctxt,
   397  		}
   398  		conf.Import(test.arg)
   399  
   400  		var got string
   401  		prog, err := conf.Load()
   402  		if prog != nil {
   403  			got = imported(prog)
   404  		}
   405  		if got != test.want {
   406  			t.Errorf("Load(%s) from %s: Imported = %s, want %s",
   407  				test.arg, test.cwd, got, test.want)
   408  			if err != nil {
   409  				t.Errorf("Load failed: %v", err)
   410  			}
   411  		}
   412  	}
   413  }
   414  
   415  func TestLoad_vendor(t *testing.T) {
   416  	pkgs := map[string]string{
   417  		"a":          `package a; import _ "x"`,
   418  		"a/vendor":   ``, // mkdir a/vendor
   419  		"a/vendor/x": `package xa`,
   420  		"b":          `package b; import _ "x"`,
   421  		"b/vendor":   ``, // mkdir b/vendor
   422  		"b/vendor/x": `package xb`,
   423  		"c":          `package c; import _ "x"`,
   424  		"x":          `package xc`,
   425  	}
   426  	conf := loader.Config{Build: fakeContext(pkgs)}
   427  	conf.Import("a")
   428  	conf.Import("b")
   429  	conf.Import("c")
   430  
   431  	prog, err := conf.Load()
   432  	if err != nil {
   433  		t.Fatal(err)
   434  	}
   435  
   436  	// Check that a, b, and c see different versions of x.
   437  	for _, r := range "abc" {
   438  		name := string(r)
   439  		got := prog.Package(name).Pkg.Imports()[0]
   440  		want := "x" + name
   441  		if got.Name() != want {
   442  			t.Errorf("package %s import %q = %s, want %s",
   443  				name, "x", got.Name(), want)
   444  		}
   445  	}
   446  }
   447  
   448  func TestVendorCwd(t *testing.T) {
   449  	// Test the interaction of cwd and vendor directories.
   450  	ctxt := fakeContext(map[string]string{
   451  		"net":          ``, // mkdir net
   452  		"net/http":     `package http; import _ "hpack"`,
   453  		"vendor":       ``, // mkdir vendor
   454  		"vendor/hpack": `package vendorhpack`,
   455  		"hpack":        `package hpack`,
   456  	})
   457  	for i, test := range []struct {
   458  		cwd, arg, want string
   459  	}{
   460  		{cwd: "/go/src/net", arg: "http"}, // not found
   461  		{cwd: "/go/src/net", arg: "./http", want: "net/http vendor/hpack"},
   462  		{cwd: "/go/src/net", arg: "hpack", want: "vendor/hpack"},
   463  		{cwd: "/go/src/vendor", arg: "hpack", want: "vendor/hpack"},
   464  		{cwd: "/go/src/vendor", arg: "./hpack", want: "vendor/hpack"},
   465  	} {
   466  		conf := loader.Config{
   467  			Cwd:   test.cwd,
   468  			Build: ctxt,
   469  		}
   470  		conf.Import(test.arg)
   471  
   472  		var got string
   473  		prog, err := conf.Load()
   474  		if prog != nil {
   475  			got = strings.Join(all(prog), " ")
   476  		}
   477  		if got != test.want {
   478  			t.Errorf("#%d: Load(%s) from %s: got %s, want %s",
   479  				i, test.arg, test.cwd, got, test.want)
   480  			if err != nil {
   481  				t.Errorf("Load failed: %v", err)
   482  			}
   483  		}
   484  	}
   485  }
   486  
   487  func TestVendorCwdIssue16580(t *testing.T) {
   488  	// Regression test for Go issue 16580.
   489  	// Import decls in "created" packages were vendor-resolved
   490  	// w.r.t. cwd, not the parent directory of the package's files.
   491  	ctxt := fakeContext(map[string]string{
   492  		"a":          ``, // mkdir a
   493  		"a/vendor":   ``, // mkdir a/vendor
   494  		"a/vendor/b": `package b; const X = true`,
   495  		"b":          `package b; const X = false`,
   496  	})
   497  	for _, test := range []struct {
   498  		filename, cwd string
   499  		want          bool // expected value of b.X; depends on filename, not on cwd
   500  	}{
   501  		{filename: "c.go", cwd: "/go/src", want: false},
   502  		{filename: "c.go", cwd: "/go/src/a", want: false},
   503  		{filename: "c.go", cwd: "/go/src/a/b", want: false},
   504  		{filename: "c.go", cwd: "/go/src/a/vendor/b", want: false},
   505  
   506  		{filename: "/go/src/a/c.go", cwd: "/go/src", want: true},
   507  		{filename: "/go/src/a/c.go", cwd: "/go/src/a", want: true},
   508  		{filename: "/go/src/a/c.go", cwd: "/go/src/a/b", want: true},
   509  		{filename: "/go/src/a/c.go", cwd: "/go/src/a/vendor/b", want: true},
   510  
   511  		{filename: "/go/src/c/c.go", cwd: "/go/src", want: false},
   512  		{filename: "/go/src/c/c.go", cwd: "/go/src/a", want: false},
   513  		{filename: "/go/src/c/c.go", cwd: "/go/src/a/b", want: false},
   514  		{filename: "/go/src/c/c.go", cwd: "/go/src/a/vendor/b", want: false},
   515  	} {
   516  		conf := loader.Config{
   517  			Cwd:   test.cwd,
   518  			Build: ctxt,
   519  		}
   520  		f, err := conf.ParseFile(test.filename, `package dummy; import "b"; const X = b.X`)
   521  		if err != nil {
   522  			t.Fatal(f)
   523  		}
   524  		conf.CreateFromFiles("dummy", f)
   525  
   526  		prog, err := conf.Load()
   527  		if err != nil {
   528  			t.Errorf("%+v: Load failed: %v", test, err)
   529  			continue
   530  		}
   531  
   532  		x := constant.BoolVal(prog.Created[0].Pkg.Scope().Lookup("X").(*types.Const).Val())
   533  		if x != test.want {
   534  			t.Errorf("%+v: b.X = %t", test, x)
   535  		}
   536  	}
   537  
   538  	// TODO(adonovan): also test imports within XTestGoFiles.
   539  }
   540  
   541  // TODO(adonovan): more Load tests:
   542  //
   543  // failures:
   544  // - to parse package decl of *_test.go files
   545  // - to parse package decl of external *_test.go files
   546  // - to parse whole of *_test.go files
   547  // - to parse whole of external *_test.go files
   548  // - to open a *.go file during import scanning
   549  // - to import from binary
   550  
   551  // features:
   552  // - InitialPackages
   553  // - PackageCreated hook
   554  // - TypeCheckFuncBodies hook
   555  
   556  func TestTransitivelyErrorFreeFlag(t *testing.T) {
   557  	// Create an minimal custom build.Context
   558  	// that fakes the following packages:
   559  	//
   560  	// a --> b --> c!   c has an error
   561  	//   \              d and e are transitively error-free.
   562  	//    e --> d
   563  	//
   564  	// Each package [a-e] consists of one file, x.go.
   565  	pkgs := map[string]string{
   566  		"a": `package a; import (_ "b"; _ "e")`,
   567  		"b": `package b; import _ "c"`,
   568  		"c": `package c; func f() { _ = int(false) }`, // type error within function body
   569  		"d": `package d;`,
   570  		"e": `package e; import _ "d"`,
   571  	}
   572  	conf := loader.Config{
   573  		AllowErrors: true,
   574  		Build:       fakeContext(pkgs),
   575  	}
   576  	conf.Import("a")
   577  
   578  	prog, err := conf.Load()
   579  	if err != nil {
   580  		t.Errorf("Load failed: %s", err)
   581  	}
   582  	if prog == nil {
   583  		t.Fatalf("Load returned nil *Program")
   584  	}
   585  
   586  	for pkg, info := range prog.AllPackages {
   587  		var wantErr, wantTEF bool
   588  		switch pkg.Path() {
   589  		case "a", "b":
   590  		case "c":
   591  			wantErr = true
   592  		case "d", "e":
   593  			wantTEF = true
   594  		default:
   595  			t.Errorf("unexpected package: %q", pkg.Path())
   596  			continue
   597  		}
   598  
   599  		if (info.Errors != nil) != wantErr {
   600  			if wantErr {
   601  				t.Errorf("Package %q.Error = nil, want error", pkg.Path())
   602  			} else {
   603  				t.Errorf("Package %q has unexpected Errors: %v",
   604  					pkg.Path(), info.Errors)
   605  			}
   606  		}
   607  
   608  		if info.TransitivelyErrorFree != wantTEF {
   609  			t.Errorf("Package %q.TransitivelyErrorFree=%t, want %t",
   610  				pkg.Path(), info.TransitivelyErrorFree, wantTEF)
   611  		}
   612  	}
   613  }
   614  
   615  // Test that syntax (scan/parse), type, and loader errors are recorded
   616  // (in PackageInfo.Errors) and reported (via Config.TypeChecker.Error).
   617  func TestErrorReporting(t *testing.T) {
   618  	pkgs := map[string]string{
   619  		"a": `package a; import (_ "b"; _ "c"); var x int = false`,
   620  		"b": `package b; 'syntax error!`,
   621  	}
   622  	conf := loader.Config{
   623  		AllowErrors: true,
   624  		Build:       fakeContext(pkgs),
   625  	}
   626  	var mu sync.Mutex
   627  	var allErrors []error
   628  	conf.TypeChecker.Error = func(err error) {
   629  		mu.Lock()
   630  		allErrors = append(allErrors, err)
   631  		mu.Unlock()
   632  	}
   633  	conf.Import("a")
   634  
   635  	prog, err := conf.Load()
   636  	if err != nil {
   637  		t.Errorf("Load failed: %s", err)
   638  	}
   639  	if prog == nil {
   640  		t.Fatalf("Load returned nil *Program")
   641  	}
   642  
   643  	// TODO(adonovan): test keys of ImportMap.
   644  
   645  	// Check errors recorded in each PackageInfo.
   646  	for pkg, info := range prog.AllPackages {
   647  		switch pkg.Path() {
   648  		case "a":
   649  			// The match below is unfortunately vague, because in go1.16 the error
   650  			// message in go/types changed from "cannot convert ..." to "cannot use
   651  			// ... as ... in assignment".
   652  			if !hasError(info.Errors, "cannot") {
   653  				t.Errorf("a.Errors = %v, want bool assignment (type) error", info.Errors)
   654  			}
   655  			if !hasError(info.Errors, "could not import c") {
   656  				t.Errorf("a.Errors = %v, want import (loader) error", info.Errors)
   657  			}
   658  		case "b":
   659  			if !hasError(info.Errors, "rune literal not terminated") {
   660  				t.Errorf("b.Errors = %v, want unterminated literal (syntax) error", info.Errors)
   661  			}
   662  		}
   663  	}
   664  
   665  	// Check errors reported via error handler.
   666  	if !hasError(allErrors, "cannot") ||
   667  		!hasError(allErrors, "rune literal not terminated") ||
   668  		!hasError(allErrors, "could not import c") {
   669  		t.Errorf("allErrors = %v, want syntax, type and loader errors", allErrors)
   670  	}
   671  }
   672  
   673  func TestCycles(t *testing.T) {
   674  	for _, test := range []struct {
   675  		descr   string
   676  		ctxt    *build.Context
   677  		wantErr string
   678  	}{
   679  		{
   680  			"self-cycle",
   681  			fakeContext(map[string]string{
   682  				"main":      `package main; import _ "selfcycle"`,
   683  				"selfcycle": `package selfcycle; import _ "selfcycle"`,
   684  			}),
   685  			`import cycle: selfcycle -> selfcycle`,
   686  		},
   687  		{
   688  			"three-package cycle",
   689  			fakeContext(map[string]string{
   690  				"main": `package main; import _ "a"`,
   691  				"a":    `package a; import _ "b"`,
   692  				"b":    `package b; import _ "c"`,
   693  				"c":    `package c; import _ "a"`,
   694  			}),
   695  			`import cycle: c -> a -> b -> c`,
   696  		},
   697  		{
   698  			"self-cycle in dependency of test file",
   699  			buildutil.FakeContext(map[string]map[string]string{
   700  				"main": {
   701  					"main.go":      `package main`,
   702  					"main_test.go": `package main; import _ "a"`,
   703  				},
   704  				"a": {
   705  					"a.go": `package a; import _ "a"`,
   706  				},
   707  			}),
   708  			`import cycle: a -> a`,
   709  		},
   710  		// TODO(adonovan): fix: these fail
   711  		// {
   712  		// 	"two-package cycle in dependency of test file",
   713  		// 	buildutil.FakeContext(map[string]map[string]string{
   714  		// 		"main": {
   715  		// 			"main.go":      `package main`,
   716  		// 			"main_test.go": `package main; import _ "a"`,
   717  		// 		},
   718  		// 		"a": {
   719  		// 			"a.go": `package a; import _ "main"`,
   720  		// 		},
   721  		// 	}),
   722  		// 	`import cycle: main -> a -> main`,
   723  		// },
   724  		// {
   725  		// 	"self-cycle in augmented package",
   726  		// 	buildutil.FakeContext(map[string]map[string]string{
   727  		// 		"main": {
   728  		// 			"main.go":      `package main`,
   729  		// 			"main_test.go": `package main; import _ "main"`,
   730  		// 		},
   731  		// 	}),
   732  		// 	`import cycle: main -> main`,
   733  		// },
   734  	} {
   735  		conf := loader.Config{
   736  			AllowErrors: true,
   737  			Build:       test.ctxt,
   738  		}
   739  		var mu sync.Mutex
   740  		var allErrors []error
   741  		conf.TypeChecker.Error = func(err error) {
   742  			mu.Lock()
   743  			allErrors = append(allErrors, err)
   744  			mu.Unlock()
   745  		}
   746  		conf.ImportWithTests("main")
   747  
   748  		prog, err := conf.Load()
   749  		if err != nil {
   750  			t.Errorf("%s: Load failed: %s", test.descr, err)
   751  		}
   752  		if prog == nil {
   753  			t.Fatalf("%s: Load returned nil *Program", test.descr)
   754  		}
   755  
   756  		if !hasError(allErrors, test.wantErr) {
   757  			t.Errorf("%s: Load() errors = %q, want %q",
   758  				test.descr, allErrors, test.wantErr)
   759  		}
   760  	}
   761  
   762  	// TODO(adonovan):
   763  	// - Test that in a legal test cycle, none of the symbols
   764  	//   defined by augmentation are visible via import.
   765  }
   766  
   767  // ---- utilities ----
   768  
   769  // Simplifying wrapper around buildutil.FakeContext for single-file packages.
   770  func fakeContext(pkgs map[string]string) *build.Context {
   771  	pkgs2 := make(map[string]map[string]string)
   772  	for path, content := range pkgs {
   773  		pkgs2[path] = map[string]string{"x.go": content}
   774  	}
   775  	return buildutil.FakeContext(pkgs2)
   776  }
   777  
   778  func hasError(errors []error, substr string) bool {
   779  	for _, err := range errors {
   780  		if strings.Contains(err.Error(), substr) {
   781  			return true
   782  		}
   783  	}
   784  	return false
   785  }
   786  
   787  func keys(m map[string]bool) (keys []string) {
   788  	for key := range m {
   789  		keys = append(keys, key)
   790  	}
   791  	sort.Strings(keys)
   792  	return
   793  }
   794  
   795  // Returns all loaded packages.
   796  func all(prog *loader.Program) []string {
   797  	var pkgs []string
   798  	for _, info := range prog.AllPackages {
   799  		pkgs = append(pkgs, info.Pkg.Path())
   800  	}
   801  	sort.Strings(pkgs)
   802  	return pkgs
   803  }
   804  
   805  // Returns initially imported packages, as a string.
   806  func imported(prog *loader.Program) string {
   807  	var pkgs []string
   808  	for _, info := range prog.Imported {
   809  		pkgs = append(pkgs, info.Pkg.Path())
   810  	}
   811  	sort.Strings(pkgs)
   812  	return strings.Join(pkgs, " ")
   813  }
   814  
   815  // Returns initially created packages, as a string.
   816  func created(prog *loader.Program) string {
   817  	var pkgs []string
   818  	for _, info := range prog.Created {
   819  		pkgs = append(pkgs, info.Pkg.Path())
   820  	}
   821  	return strings.Join(pkgs, " ")
   822  }
   823  
   824  // Load package "io" twice in parallel.
   825  // When run with -race, this is a regression test for Go issue 20718, in
   826  // which the global "unsafe" package was modified concurrently.
   827  func TestLoad1(t *testing.T) { loadIO(t) }
   828  func TestLoad2(t *testing.T) { loadIO(t) }
   829  
   830  func loadIO(t *testing.T) {
   831  	t.Parallel()
   832  	conf := &loader.Config{ImportPkgs: map[string]bool{"io": false}}
   833  	if _, err := conf.Load(); err != nil {
   834  		t.Fatal(err)
   835  	}
   836  }
   837  
   838  func TestCgoCwdIssue46877(t *testing.T) {
   839  	testenv.NeedsTool(t, "go")
   840  	testenv.NeedsTool(t, "cgo")
   841  	var conf loader.Config
   842  	conf.Import("golang.org/x/tools/go/loader/testdata/issue46877")
   843  	if _, err := conf.Load(); err != nil {
   844  		t.Errorf("Load failed: %v", err)
   845  	}
   846  }