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