github.com/llvm-mirror/llgo@v0.0.0-20190322182713-bf6f0a60fce1/third_party/gotools/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  package loader_test
     6  
     7  import (
     8  	"fmt"
     9  	"go/build"
    10  	"reflect"
    11  	"sort"
    12  	"strings"
    13  	"sync"
    14  	"testing"
    15  
    16  	"llvm.org/llgo/third_party/gotools/go/buildutil"
    17  	"llvm.org/llgo/third_party/gotools/go/loader"
    18  )
    19  
    20  // TestFromArgs checks that conf.FromArgs populates conf correctly.
    21  // It does no I/O.
    22  func TestFromArgs(t *testing.T) {
    23  	type result struct {
    24  		Err        string
    25  		Rest       []string
    26  		ImportPkgs map[string]bool
    27  		CreatePkgs []loader.PkgSpec
    28  	}
    29  	for _, test := range []struct {
    30  		args  []string
    31  		tests bool
    32  		want  result
    33  	}{
    34  		// Mix of existing and non-existent packages.
    35  		{
    36  			args: []string{"nosuchpkg", "errors"},
    37  			want: result{
    38  				ImportPkgs: map[string]bool{"errors": false, "nosuchpkg": false},
    39  			},
    40  		},
    41  		// Same, with -test flag.
    42  		{
    43  			args:  []string{"nosuchpkg", "errors"},
    44  			tests: true,
    45  			want: result{
    46  				ImportPkgs: map[string]bool{"errors": true, "nosuchpkg": true},
    47  			},
    48  		},
    49  		// Surplus arguments.
    50  		{
    51  			args: []string{"fmt", "errors", "--", "surplus"},
    52  			want: result{
    53  				Rest:       []string{"surplus"},
    54  				ImportPkgs: map[string]bool{"errors": false, "fmt": false},
    55  			},
    56  		},
    57  		// Ad hoc package specified as *.go files.
    58  		{
    59  			args: []string{"foo.go", "bar.go"},
    60  			want: result{CreatePkgs: []loader.PkgSpec{{
    61  				Filenames: []string{"foo.go", "bar.go"},
    62  			}}},
    63  		},
    64  		// Mixture of *.go and import paths.
    65  		{
    66  			args: []string{"foo.go", "fmt"},
    67  			want: result{
    68  				Err: "named files must be .go files: fmt",
    69  			},
    70  		},
    71  	} {
    72  		var conf loader.Config
    73  		rest, err := conf.FromArgs(test.args, test.tests)
    74  		got := result{
    75  			Rest:       rest,
    76  			ImportPkgs: conf.ImportPkgs,
    77  			CreatePkgs: conf.CreatePkgs,
    78  		}
    79  		if err != nil {
    80  			got.Err = err.Error()
    81  		}
    82  		if !reflect.DeepEqual(got, test.want) {
    83  			t.Errorf("FromArgs(%q) = %+v, want %+v", test.args, got, test.want)
    84  		}
    85  	}
    86  }
    87  
    88  func TestLoad_NoInitialPackages(t *testing.T) {
    89  	var conf loader.Config
    90  
    91  	const wantErr = "no initial packages were loaded"
    92  
    93  	prog, err := conf.Load()
    94  	if err == nil {
    95  		t.Errorf("Load succeeded unexpectedly, want %q", wantErr)
    96  	} else if err.Error() != wantErr {
    97  		t.Errorf("Load failed with wrong error %q, want %q", err, wantErr)
    98  	}
    99  	if prog != nil {
   100  		t.Errorf("Load unexpectedly returned a Program")
   101  	}
   102  }
   103  
   104  func TestLoad_MissingInitialPackage(t *testing.T) {
   105  	var conf loader.Config
   106  	conf.Import("nosuchpkg")
   107  	conf.Import("errors")
   108  
   109  	const wantErr = "couldn't load packages due to errors: nosuchpkg"
   110  
   111  	prog, err := conf.Load()
   112  	if err == nil {
   113  		t.Errorf("Load succeeded unexpectedly, want %q", wantErr)
   114  	} else if err.Error() != wantErr {
   115  		t.Errorf("Load failed with wrong error %q, want %q", err, wantErr)
   116  	}
   117  	if prog != nil {
   118  		t.Errorf("Load unexpectedly returned a Program")
   119  	}
   120  }
   121  
   122  func TestLoad_MissingInitialPackage_AllowErrors(t *testing.T) {
   123  	var conf loader.Config
   124  	conf.AllowErrors = true
   125  	conf.Import("nosuchpkg")
   126  	conf.ImportWithTests("errors")
   127  
   128  	prog, err := conf.Load()
   129  	if err != nil {
   130  		t.Errorf("Load failed unexpectedly: %v", err)
   131  	}
   132  	if prog == nil {
   133  		t.Fatalf("Load returned a nil Program")
   134  	}
   135  	if got, want := created(prog), "errors_test"; got != want {
   136  		t.Errorf("Created = %s, want %s", got, want)
   137  	}
   138  	if got, want := imported(prog), "errors"; got != want {
   139  		t.Errorf("Imported = %s, want %s", got, want)
   140  	}
   141  }
   142  
   143  func TestCreateUnnamedPackage(t *testing.T) {
   144  	var conf loader.Config
   145  	conf.CreateFromFilenames("")
   146  	prog, err := conf.Load()
   147  	if err != nil {
   148  		t.Fatalf("Load failed: %v", err)
   149  	}
   150  	if got, want := fmt.Sprint(prog.InitialPackages()), "[(unnamed)]"; got != want {
   151  		t.Errorf("InitialPackages = %s, want %s", got, want)
   152  	}
   153  }
   154  
   155  func TestLoad_MissingFileInCreatedPackage(t *testing.T) {
   156  	var conf loader.Config
   157  	conf.CreateFromFilenames("", "missing.go")
   158  
   159  	const wantErr = "couldn't load packages due to errors: (unnamed)"
   160  
   161  	prog, err := conf.Load()
   162  	if prog != nil {
   163  		t.Errorf("Load unexpectedly returned a Program")
   164  	}
   165  	if err == nil {
   166  		t.Fatalf("Load succeeded unexpectedly, want %q", wantErr)
   167  	}
   168  	if err.Error() != wantErr {
   169  		t.Fatalf("Load failed with wrong error %q, want %q", err, wantErr)
   170  	}
   171  }
   172  
   173  func TestLoad_MissingFileInCreatedPackage_AllowErrors(t *testing.T) {
   174  	conf := loader.Config{AllowErrors: true}
   175  	conf.CreateFromFilenames("", "missing.go")
   176  
   177  	prog, err := conf.Load()
   178  	if err != nil {
   179  		t.Errorf("Load failed: %v", err)
   180  	}
   181  	if got, want := fmt.Sprint(prog.InitialPackages()), "[(unnamed)]"; got != want {
   182  		t.Fatalf("InitialPackages = %s, want %s", got, want)
   183  	}
   184  }
   185  
   186  func TestLoad_ParseError(t *testing.T) {
   187  	var conf loader.Config
   188  	conf.CreateFromFilenames("badpkg", "testdata/badpkgdecl.go")
   189  
   190  	const wantErr = "couldn't load packages due to errors: badpkg"
   191  
   192  	prog, err := conf.Load()
   193  	if prog != nil {
   194  		t.Errorf("Load unexpectedly returned a Program")
   195  	}
   196  	if err == nil {
   197  		t.Fatalf("Load succeeded unexpectedly, want %q", wantErr)
   198  	}
   199  	if err.Error() != wantErr {
   200  		t.Fatalf("Load failed with wrong error %q, want %q", err, wantErr)
   201  	}
   202  }
   203  
   204  func TestLoad_ParseError_AllowErrors(t *testing.T) {
   205  	var conf loader.Config
   206  	conf.AllowErrors = true
   207  	conf.CreateFromFilenames("badpkg", "testdata/badpkgdecl.go")
   208  
   209  	prog, err := conf.Load()
   210  	if err != nil {
   211  		t.Errorf("Load failed unexpectedly: %v", err)
   212  	}
   213  	if prog == nil {
   214  		t.Fatalf("Load returned a nil Program")
   215  	}
   216  	if got, want := created(prog), "badpkg"; got != want {
   217  		t.Errorf("Created = %s, want %s", got, want)
   218  	}
   219  
   220  	badpkg := prog.Created[0]
   221  	if len(badpkg.Files) != 1 {
   222  		t.Errorf("badpkg has %d files, want 1", len(badpkg.Files))
   223  	}
   224  	wantErr := "testdata/badpkgdecl.go:1:34: expected 'package', found 'EOF'"
   225  	if !hasError(badpkg.Errors, wantErr) {
   226  		t.Errorf("badpkg.Errors = %v, want %s", badpkg.Errors, wantErr)
   227  	}
   228  }
   229  
   230  func TestLoad_FromSource_Success(t *testing.T) {
   231  	var conf loader.Config
   232  	conf.CreateFromFilenames("P", "testdata/a.go", "testdata/b.go")
   233  
   234  	prog, err := conf.Load()
   235  	if err != nil {
   236  		t.Errorf("Load failed unexpectedly: %v", err)
   237  	}
   238  	if prog == nil {
   239  		t.Fatalf("Load returned a nil Program")
   240  	}
   241  	if got, want := created(prog), "P"; got != want {
   242  		t.Errorf("Created = %s, want %s", got, want)
   243  	}
   244  }
   245  
   246  func TestLoad_FromImports_Success(t *testing.T) {
   247  	var conf loader.Config
   248  	conf.ImportWithTests("fmt")
   249  	conf.ImportWithTests("errors")
   250  
   251  	prog, err := conf.Load()
   252  	if err != nil {
   253  		t.Errorf("Load failed unexpectedly: %v", err)
   254  	}
   255  	if prog == nil {
   256  		t.Fatalf("Load returned a nil Program")
   257  	}
   258  	if got, want := created(prog), "errors_test fmt_test"; got != want {
   259  		t.Errorf("Created = %q, want %s", got, want)
   260  	}
   261  	if got, want := imported(prog), "errors fmt"; got != want {
   262  		t.Errorf("Imported = %s, want %s", got, want)
   263  	}
   264  	// Check set of transitive packages.
   265  	// There are >30 and the set may grow over time, so only check a few.
   266  	want := map[string]bool{
   267  		"strings": true,
   268  		"time":    true,
   269  		"runtime": true,
   270  		"testing": true,
   271  		"unicode": true,
   272  	}
   273  	for _, path := range all(prog) {
   274  		delete(want, path)
   275  	}
   276  	if len(want) > 0 {
   277  		t.Errorf("AllPackages is missing these keys: %q", keys(want))
   278  	}
   279  }
   280  
   281  func TestLoad_MissingIndirectImport(t *testing.T) {
   282  	pkgs := map[string]string{
   283  		"a": `package a; import _ "b"`,
   284  		"b": `package b; import _ "c"`,
   285  	}
   286  	conf := loader.Config{Build: fakeContext(pkgs)}
   287  	conf.Import("a")
   288  
   289  	const wantErr = "couldn't load packages due to errors: b"
   290  
   291  	prog, err := conf.Load()
   292  	if err == nil {
   293  		t.Errorf("Load succeeded unexpectedly, want %q", wantErr)
   294  	} else if err.Error() != wantErr {
   295  		t.Errorf("Load failed with wrong error %q, want %q", err, wantErr)
   296  	}
   297  	if prog != nil {
   298  		t.Errorf("Load unexpectedly returned a Program")
   299  	}
   300  }
   301  
   302  func TestLoad_BadDependency_AllowErrors(t *testing.T) {
   303  	for _, test := range []struct {
   304  		descr    string
   305  		pkgs     map[string]string
   306  		wantPkgs string
   307  	}{
   308  
   309  		{
   310  			descr: "missing dependency",
   311  			pkgs: map[string]string{
   312  				"a": `package a; import _ "b"`,
   313  				"b": `package b; import _ "c"`,
   314  			},
   315  			wantPkgs: "a b",
   316  		},
   317  		{
   318  			descr: "bad package decl in dependency",
   319  			pkgs: map[string]string{
   320  				"a": `package a; import _ "b"`,
   321  				"b": `package b; import _ "c"`,
   322  				"c": `package`,
   323  			},
   324  			wantPkgs: "a b",
   325  		},
   326  		{
   327  			descr: "parse error in dependency",
   328  			pkgs: map[string]string{
   329  				"a": `package a; import _ "b"`,
   330  				"b": `package b; import _ "c"`,
   331  				"c": `package c; var x = `,
   332  			},
   333  			wantPkgs: "a b c",
   334  		},
   335  	} {
   336  		conf := loader.Config{
   337  			AllowErrors: true,
   338  			Build:       fakeContext(test.pkgs),
   339  		}
   340  		conf.Import("a")
   341  
   342  		prog, err := conf.Load()
   343  		if err != nil {
   344  			t.Errorf("%s: Load failed unexpectedly: %v", test.descr, err)
   345  		}
   346  		if prog == nil {
   347  			t.Fatalf("%s: Load returned a nil Program", test.descr)
   348  		}
   349  
   350  		if got, want := imported(prog), "a"; got != want {
   351  			t.Errorf("%s: Imported = %s, want %s", test.descr, got, want)
   352  		}
   353  		if got := all(prog); strings.Join(got, " ") != test.wantPkgs {
   354  			t.Errorf("%s: AllPackages = %s, want %s", test.descr, got, test.wantPkgs)
   355  		}
   356  	}
   357  }
   358  
   359  func TestCwd(t *testing.T) {
   360  	ctxt := fakeContext(map[string]string{"one/two/three": `package three`})
   361  	for _, test := range []struct {
   362  		cwd, arg, want string
   363  	}{
   364  		{cwd: "/go/src/one", arg: "./two/three", want: "one/two/three"},
   365  		{cwd: "/go/src/one", arg: "../one/two/three", want: "one/two/three"},
   366  		{cwd: "/go/src/one", arg: "one/two/three", want: "one/two/three"},
   367  		{cwd: "/go/src/one/two/three", arg: ".", want: "one/two/three"},
   368  		{cwd: "/go/src/one", arg: "two/three", want: ""},
   369  	} {
   370  		conf := loader.Config{
   371  			Cwd:   test.cwd,
   372  			Build: ctxt,
   373  		}
   374  		conf.Import(test.arg)
   375  
   376  		var got string
   377  		prog, err := conf.Load()
   378  		if prog != nil {
   379  			got = imported(prog)
   380  		}
   381  		if got != test.want {
   382  			t.Errorf("Load(%s) from %s: Imported = %s, want %s",
   383  				test.arg, test.cwd, got, test.want)
   384  			if err != nil {
   385  				t.Errorf("Load failed: %v", err)
   386  			}
   387  		}
   388  	}
   389  }
   390  
   391  // TODO(adonovan): more Load tests:
   392  //
   393  // failures:
   394  // - to parse package decl of *_test.go files
   395  // - to parse package decl of external *_test.go files
   396  // - to parse whole of *_test.go files
   397  // - to parse whole of external *_test.go files
   398  // - to open a *.go file during import scanning
   399  // - to import from binary
   400  
   401  // features:
   402  // - InitialPackages
   403  // - PackageCreated hook
   404  // - TypeCheckFuncBodies hook
   405  
   406  func TestTransitivelyErrorFreeFlag(t *testing.T) {
   407  	// Create an minimal custom build.Context
   408  	// that fakes the following packages:
   409  	//
   410  	// a --> b --> c!   c has an error
   411  	//   \              d and e are transitively error-free.
   412  	//    e --> d
   413  	//
   414  	// Each package [a-e] consists of one file, x.go.
   415  	pkgs := map[string]string{
   416  		"a": `package a; import (_ "b"; _ "e")`,
   417  		"b": `package b; import _ "c"`,
   418  		"c": `package c; func f() { _ = int(false) }`, // type error within function body
   419  		"d": `package d;`,
   420  		"e": `package e; import _ "d"`,
   421  	}
   422  	conf := loader.Config{
   423  		AllowErrors: true,
   424  		Build:       fakeContext(pkgs),
   425  	}
   426  	conf.Import("a")
   427  
   428  	prog, err := conf.Load()
   429  	if err != nil {
   430  		t.Errorf("Load failed: %s", err)
   431  	}
   432  	if prog == nil {
   433  		t.Fatalf("Load returned nil *Program")
   434  	}
   435  
   436  	for pkg, info := range prog.AllPackages {
   437  		var wantErr, wantTEF bool
   438  		switch pkg.Path() {
   439  		case "a", "b":
   440  		case "c":
   441  			wantErr = true
   442  		case "d", "e":
   443  			wantTEF = true
   444  		default:
   445  			t.Errorf("unexpected package: %q", pkg.Path())
   446  			continue
   447  		}
   448  
   449  		if (info.Errors != nil) != wantErr {
   450  			if wantErr {
   451  				t.Errorf("Package %q.Error = nil, want error", pkg.Path())
   452  			} else {
   453  				t.Errorf("Package %q has unexpected Errors: %v",
   454  					pkg.Path(), info.Errors)
   455  			}
   456  		}
   457  
   458  		if info.TransitivelyErrorFree != wantTEF {
   459  			t.Errorf("Package %q.TransitivelyErrorFree=%t, want %t",
   460  				pkg.Path(), info.TransitivelyErrorFree, wantTEF)
   461  		}
   462  	}
   463  }
   464  
   465  // Test that syntax (scan/parse), type, and loader errors are recorded
   466  // (in PackageInfo.Errors) and reported (via Config.TypeChecker.Error).
   467  func TestErrorReporting(t *testing.T) {
   468  	pkgs := map[string]string{
   469  		"a": `package a; import (_ "b"; _ "c"); var x int = false`,
   470  		"b": `package b; 'syntax error!`,
   471  	}
   472  	conf := loader.Config{
   473  		AllowErrors: true,
   474  		Build:       fakeContext(pkgs),
   475  	}
   476  	var mu sync.Mutex
   477  	var allErrors []error
   478  	conf.TypeChecker.Error = func(err error) {
   479  		mu.Lock()
   480  		allErrors = append(allErrors, err)
   481  		mu.Unlock()
   482  	}
   483  	conf.Import("a")
   484  
   485  	prog, err := conf.Load()
   486  	if err != nil {
   487  		t.Errorf("Load failed: %s", err)
   488  	}
   489  	if prog == nil {
   490  		t.Fatalf("Load returned nil *Program")
   491  	}
   492  
   493  	// TODO(adonovan): test keys of ImportMap.
   494  
   495  	// Check errors recorded in each PackageInfo.
   496  	for pkg, info := range prog.AllPackages {
   497  		switch pkg.Path() {
   498  		case "a":
   499  			if !hasError(info.Errors, "cannot convert false") {
   500  				t.Errorf("a.Errors = %v, want bool conversion (type) error", info.Errors)
   501  			}
   502  			if !hasError(info.Errors, "could not import c") {
   503  				t.Errorf("a.Errors = %v, want import (loader) error", info.Errors)
   504  			}
   505  		case "b":
   506  			if !hasError(info.Errors, "rune literal not terminated") {
   507  				t.Errorf("b.Errors = %v, want unterminated literal (syntax) error", info.Errors)
   508  			}
   509  		}
   510  	}
   511  
   512  	// Check errors reported via error handler.
   513  	if !hasError(allErrors, "cannot convert false") ||
   514  		!hasError(allErrors, "rune literal not terminated") ||
   515  		!hasError(allErrors, "could not import c") {
   516  		t.Errorf("allErrors = %v, want syntax, type and loader errors", allErrors)
   517  	}
   518  }
   519  
   520  func TestCycles(t *testing.T) {
   521  	for _, test := range []struct {
   522  		descr   string
   523  		ctxt    *build.Context
   524  		wantErr string
   525  	}{
   526  		{
   527  			"self-cycle",
   528  			fakeContext(map[string]string{
   529  				"main":      `package main; import _ "selfcycle"`,
   530  				"selfcycle": `package selfcycle; import _ "selfcycle"`,
   531  			}),
   532  			`import cycle: selfcycle -> selfcycle`,
   533  		},
   534  		{
   535  			"three-package cycle",
   536  			fakeContext(map[string]string{
   537  				"main": `package main; import _ "a"`,
   538  				"a":    `package a; import _ "b"`,
   539  				"b":    `package b; import _ "c"`,
   540  				"c":    `package c; import _ "a"`,
   541  			}),
   542  			`import cycle: c -> a -> b -> c`,
   543  		},
   544  		{
   545  			"self-cycle in dependency of test file",
   546  			buildutil.FakeContext(map[string]map[string]string{
   547  				"main": {
   548  					"main.go":      `package main`,
   549  					"main_test.go": `package main; import _ "a"`,
   550  				},
   551  				"a": {
   552  					"a.go": `package a; import _ "a"`,
   553  				},
   554  			}),
   555  			`import cycle: a -> a`,
   556  		},
   557  		// TODO(adonovan): fix: these fail
   558  		// {
   559  		// 	"two-package cycle in dependency of test file",
   560  		// 	buildutil.FakeContext(map[string]map[string]string{
   561  		// 		"main": {
   562  		// 			"main.go":      `package main`,
   563  		// 			"main_test.go": `package main; import _ "a"`,
   564  		// 		},
   565  		// 		"a": {
   566  		// 			"a.go": `package a; import _ "main"`,
   567  		// 		},
   568  		// 	}),
   569  		// 	`import cycle: main -> a -> main`,
   570  		// },
   571  		// {
   572  		// 	"self-cycle in augmented package",
   573  		// 	buildutil.FakeContext(map[string]map[string]string{
   574  		// 		"main": {
   575  		// 			"main.go":      `package main`,
   576  		// 			"main_test.go": `package main; import _ "main"`,
   577  		// 		},
   578  		// 	}),
   579  		// 	`import cycle: main -> main`,
   580  		// },
   581  	} {
   582  		conf := loader.Config{
   583  			AllowErrors: true,
   584  			Build:       test.ctxt,
   585  		}
   586  		var mu sync.Mutex
   587  		var allErrors []error
   588  		conf.TypeChecker.Error = func(err error) {
   589  			mu.Lock()
   590  			allErrors = append(allErrors, err)
   591  			mu.Unlock()
   592  		}
   593  		conf.ImportWithTests("main")
   594  
   595  		prog, err := conf.Load()
   596  		if err != nil {
   597  			t.Errorf("%s: Load failed: %s", test.descr, err)
   598  		}
   599  		if prog == nil {
   600  			t.Fatalf("%s: Load returned nil *Program", test.descr)
   601  		}
   602  
   603  		if !hasError(allErrors, test.wantErr) {
   604  			t.Errorf("%s: Load() errors = %q, want %q",
   605  				test.descr, allErrors, test.wantErr)
   606  		}
   607  	}
   608  
   609  	// TODO(adonovan):
   610  	// - Test that in a legal test cycle, none of the symbols
   611  	//   defined by augmentation are visible via import.
   612  }
   613  
   614  // ---- utilities ----
   615  
   616  // Simplifying wrapper around buildutil.FakeContext for single-file packages.
   617  func fakeContext(pkgs map[string]string) *build.Context {
   618  	pkgs2 := make(map[string]map[string]string)
   619  	for path, content := range pkgs {
   620  		pkgs2[path] = map[string]string{"x.go": content}
   621  	}
   622  	return buildutil.FakeContext(pkgs2)
   623  }
   624  
   625  func hasError(errors []error, substr string) bool {
   626  	for _, err := range errors {
   627  		if strings.Contains(err.Error(), substr) {
   628  			return true
   629  		}
   630  	}
   631  	return false
   632  }
   633  
   634  func keys(m map[string]bool) (keys []string) {
   635  	for key := range m {
   636  		keys = append(keys, key)
   637  	}
   638  	sort.Strings(keys)
   639  	return
   640  }
   641  
   642  // Returns all loaded packages.
   643  func all(prog *loader.Program) []string {
   644  	var pkgs []string
   645  	for _, info := range prog.AllPackages {
   646  		pkgs = append(pkgs, info.Pkg.Path())
   647  	}
   648  	sort.Strings(pkgs)
   649  	return pkgs
   650  }
   651  
   652  // Returns initially imported packages, as a string.
   653  func imported(prog *loader.Program) string {
   654  	var pkgs []string
   655  	for _, info := range prog.Imported {
   656  		pkgs = append(pkgs, info.Pkg.Path())
   657  	}
   658  	sort.Strings(pkgs)
   659  	return strings.Join(pkgs, " ")
   660  }
   661  
   662  // Returns initially created packages, as a string.
   663  func created(prog *loader.Program) string {
   664  	var pkgs []string
   665  	for _, info := range prog.Created {
   666  		pkgs = append(pkgs, info.Pkg.Path())
   667  	}
   668  	return strings.Join(pkgs, " ")
   669  }