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 | var conf loader.Config |
840 | conf.Import("golang.org/x/tools/go/loader/testdata/issue46877") |
841 | if _, err := conf.Load(); err != nil { |
842 | t.Errorf("Load failed: %v", err) |
843 | } |
844 | } |
845 |
Members