func doOutput(args []string) { exitIfErr(outputFlags.Parse(args)) var conf loader.Config rest, err := conf.FromArgs(outputFlags.Args(), true) if len(rest) > 0 { fmt.Fprintf(os.Stderr, "Unrecognized arguments:\n%v\n\n", strings.Join(rest, "\n")) } if err != nil { fmt.Fprintf(os.Stderr, "Error identifying packages: %v\n\n", err) } if len(rest) > 0 || err != nil { usage() } conf.SourceImports = true prog, err := conf.Load() if err != nil { fmt.Fprintf(os.Stderr, "Error loading packages: %v\n\n", err) usage() } gen.Generate(prog, ioutil.ReadFile, func(importPath, filename string) io.WriteCloser { if *w { return createFileHook(filename, "") } return nopCloser{os.Stdout} }) }
func TestTransitivelyErrorFreeFlag(t *testing.T) { // Create an minimal custom build.Context // that fakes the following packages: // // a --> b --> c! c has an error // \ d and e are transitively error-free. // e --> d // // Each package [a-e] consists of one file, x.go. pkgs := map[string]string{ "a": `package a; import (_ "b"; _ "e")`, "b": `package b; import _ "c"`, "c": `package c; func f() { _ = int(false) }`, // type error within function body "d": `package d;`, "e": `package e; import _ "d"`, } conf := loader.Config{ AllowErrors: true, SourceImports: true, Build: fakeContext(pkgs), } conf.Import("a") prog, err := conf.Load() if err != nil { t.Errorf("Load failed: %s", err) } if prog == nil { t.Fatalf("Load returned nil *Program") } for pkg, info := range prog.AllPackages { var wantErr, wantTEF bool switch pkg.Path() { case "a", "b": case "c": wantErr = true case "d", "e": wantTEF = true default: t.Errorf("unexpected package: %q", pkg.Path()) continue } if (info.Errors != nil) != wantErr { if wantErr { t.Errorf("Package %q.Error = nil, want error", pkg.Path()) } else { t.Errorf("Package %q has unexpected Errors: %v", pkg.Path(), info.Errors) } } if info.TransitivelyErrorFree != wantTEF { t.Errorf("Package %q.TransitivelyErrorFree=%t, want %t", pkg.Path(), info.TransitivelyErrorFree, wantTEF) } } }
// Test that both syntax (scan/parse) and type errors are both recorded // (in PackageInfo.Errors) and reported (via Config.TypeChecker.Error). func TestErrorReporting(t *testing.T) { pkgs := map[string]string{ "a": `package a; import _ "b"; var x int = false`, "b": `package b; 'syntax error!`, } conf := loader.Config{ AllowErrors: true, SourceImports: true, Build: fakeContext(pkgs), } var mu sync.Mutex var allErrors []error conf.TypeChecker.Error = func(err error) { mu.Lock() allErrors = append(allErrors, err) mu.Unlock() } conf.Import("a") prog, err := conf.Load() if err != nil { t.Errorf("Load failed: %s", err) } if prog == nil { t.Fatalf("Load returned nil *Program") } // TODO(adonovan): test keys of ImportMap. // Check errors recorded in each PackageInfo. for pkg, info := range prog.AllPackages { switch pkg.Path() { case "a": if !hasError(info.Errors, "cannot convert false") { t.Errorf("a.Errors = %v, want bool conversion (type) error", info.Errors) } case "b": if !hasError(info.Errors, "rune literal not terminated") { t.Errorf("b.Errors = %v, want unterminated literal (syntax) error", info.Errors) } } } // Check errors reported via error handler. if !hasError(allErrors, "cannot convert false") || !hasError(allErrors, "rune literal not terminated") { t.Errorf("allErrors = %v, want both syntax and type errors", allErrors) } }
func generateSourceFiles(conf *loader.Config, subcommand string) (tmpDirPath string) { // Make a temp directory. tmpDir := makeTmpDir() if *work { // Print the name of the directory and don't clean it up on exit. fmt.Println(tmpDir) } else { // Clean up the directory on exit. atexit.Run(func() { removeDir(tmpDir) }) } // Load the whole program from source files. This almost certainly causes more work than we need to do, // but it's an easy fix for a few problems I've encountered. Deleting this may be a good target for // future optimization work. conf.SourceImports = true // Mark the extra packages we want to instrument. var pkgs []string *instrument = strings.Trim(*instrument, ", ") if *instrument != "" { pkgs = strings.Split(*instrument, ",") } all := false for _, pkg := range pkgs { // check for the special reserved package names: "main", "all", and "std" // see 'go help packages' switch pkg { case "main": switch subcommand { case "run": // The main package is always instrumented anyway. Carry on. case "test": logFatal(`godebug test: can't pass reserved name "main" in the -instrument flag.`) } case "all": if !all { all = true fmt.Println(`godebug run: heads up: "all" means "all except std". godebug can't step into the standard library yet.` + "\n") } case "std": logFatalf("godebug %s: reserved name \"std\" cannot be passed in the -instrument flag."+ "\ngodebug cannot currently instrument packages in the standard library."+ "\nDo you wish it could? Chime in at https://github.com/mailgun/godebug/issues/12", subcommand) default: for _, path := range gotool.ImportPaths([]string{pkg}) { // wildcard "..." expansion conf.Import(path) } } } // Load the program. prog, err := conf.Load() exitIfErr(err) // If we're in "all" mode, mark all but the standard library packages and godebug itself for instrumenting. stdLib := getStdLibPkgs() if all { markAlmostAllPackages(prog, stdLib) } // Warn the user if they have breakpoints set in files that we are not instrumenting. checkForUnusedBreakpoints(subcommand, prog, stdLib) // Generate debugging-enabled source files. wd := getwd() gen.Generate(prog, ioutil.ReadFile, func(importPath, filename string) io.WriteCloser { if importPath == "main" { filename = filepath.Join(tmpDir, filepath.Base(filename)) } else { importPath, _ = findUnderGopath(wd, importPath) exitIfErr(os.MkdirAll(filepath.Join(tmpDir, "src", importPath), 0770)) filename = filepath.Join(tmpDir, "src", importPath, filepath.Base(filename)) } return createFileHook(filename, tmpDir) }) return tmpDir }
func TestCycles(t *testing.T) { for _, test := range []struct { descr string ctxt *build.Context wantErr string }{ { "self-cycle", fakeContext(map[string]string{ "main": `package main; import _ "selfcycle"`, "selfcycle": `package selfcycle; import _ "selfcycle"`, }), `import cycle: selfcycle -> selfcycle`, }, { "three-package cycle", fakeContext(map[string]string{ "main": `package main; import _ "a"`, "a": `package a; import _ "b"`, "b": `package b; import _ "c"`, "c": `package c; import _ "a"`, }), `import cycle: c -> a -> b -> c`, }, { "self-cycle in dependency of test file", buildutil.FakeContext(map[string]map[string]string{ "main": { "main.go": `package main`, "main_test.go": `package main; import _ "a"`, }, "a": { "a.go": `package a; import _ "a"`, }, }), `import cycle: a -> a`, }, // TODO(adonovan): fix: these fail // { // "two-package cycle in dependency of test file", // buildutil.FakeContext(map[string]map[string]string{ // "main": { // "main.go": `package main`, // "main_test.go": `package main; import _ "a"`, // }, // "a": { // "a.go": `package a; import _ "main"`, // }, // }), // `import cycle: main -> a -> main`, // }, // { // "self-cycle in augmented package", // buildutil.FakeContext(map[string]map[string]string{ // "main": { // "main.go": `package main`, // "main_test.go": `package main; import _ "main"`, // }, // }), // `import cycle: main -> main`, // }, } { conf := loader.Config{ AllowErrors: true, SourceImports: true, Build: test.ctxt, } var mu sync.Mutex var allErrors []error conf.TypeChecker.Error = func(err error) { mu.Lock() allErrors = append(allErrors, err) mu.Unlock() } conf.ImportWithTests("main") prog, err := conf.Load() if err != nil { t.Errorf("%s: Load failed: %s", test.descr, err) } if prog == nil { t.Fatalf("%s: Load returned nil *Program", test.descr) } if !hasError(allErrors, test.wantErr) { t.Errorf("%s: Load() errors = %q, want %q", test.descr, allErrors, test.wantErr) } } // TODO(adonovan): // - Test that in a legal test cycle, none of the symbols // defined by augmentation are visible via import. }
func TestStdlib(t *testing.T) { runtime.GC() t0 := time.Now() var memstats runtime.MemStats runtime.ReadMemStats(&memstats) alloc := memstats.Alloc // Load, parse and type-check the program. ctxt := build.Default // copy ctxt.GOPATH = "" // disable GOPATH conf := loader.Config{Build: &ctxt} for _, path := range buildutil.AllPackages(conf.Build) { if err := conf.ImportWithTests(path); err != nil { t.Error(err) } } prog, err := conf.Load() if err != nil { t.Fatalf("Load failed: %v", err) } t1 := time.Now() runtime.GC() runtime.ReadMemStats(&memstats) numPkgs := len(prog.AllPackages) if want := 205; numPkgs < want { t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want) } // Dump package members. if false { for pkg := range prog.AllPackages { fmt.Printf("Package %s:\n", pkg.Path()) scope := pkg.Scope() for _, name := range scope.Names() { if ast.IsExported(name) { fmt.Printf("\t%s\n", types.ObjectString(pkg, scope.Lookup(name))) } } fmt.Println() } } // Check that Test functions for io/ioutil, regexp and // compress/bzip2 are all simultaneously present. // (The apparent cycle formed when augmenting all three of // these packages by their tests was the original motivation // for reporting b/7114.) // // compress/bzip2.TestBitReader in bzip2_test.go imports io/ioutil // io/ioutil.TestTempFile in tempfile_test.go imports regexp // regexp.TestRE2Search in exec_test.go imports compress/bzip2 for _, test := range []struct{ pkg, fn string }{ {"io/ioutil", "TestTempFile"}, {"regexp", "TestRE2Search"}, {"compress/bzip2", "TestBitReader"}, } { info := prog.Imported[test.pkg] if info == nil { t.Errorf("failed to load package %q", test.pkg) continue } obj, _ := info.Pkg.Scope().Lookup(test.fn).(*types.Func) if obj == nil { t.Errorf("package %q has no func %q", test.pkg, test.fn) continue } } // Dump some statistics. // determine line count var lineCount int prog.Fset.Iterate(func(f *token.File) bool { lineCount += f.LineCount() return true }) t.Log("GOMAXPROCS: ", runtime.GOMAXPROCS(0)) t.Log("#Source lines: ", lineCount) t.Log("Load/parse/typecheck: ", t1.Sub(t0)) t.Log("#MB: ", int64(memstats.Alloc-alloc)/1000000) }
func TestCgoOption(t *testing.T) { switch runtime.GOOS { // On these systems, the net and os/user packages don't use cgo. case "plan9", "solaris", "windows": return } // In nocgo builds (e.g. linux-amd64-nocgo), // there is no "runtime/cgo" package, // so cgo-generated Go files will have a failing import. if !build.Default.CgoEnabled { return } // Test that we can load cgo-using packages with // CGO_ENABLED=[01], which causes go/build to select pure // Go/native implementations, respectively, based on build // tags. // // Each entry specifies a package-level object and the generic // file expected to define it when cgo is disabled. // When cgo is enabled, the exact file is not specified (since // it varies by platform), but must differ from the generic one. // // The test also loads the actual file to verify that the // object is indeed defined at that location. for _, test := range []struct { pkg, name, genericFile string }{ {"net", "cgoLookupHost", "cgo_stub.go"}, {"os/user", "lookupId", "lookup_stubs.go"}, } { ctxt := build.Default for _, ctxt.CgoEnabled = range []bool{false, true} { conf := loader.Config{Build: &ctxt} conf.Import(test.pkg) prog, err := conf.Load() if err != nil { t.Errorf("Load failed: %v", err) continue } info := prog.Imported[test.pkg] if info == nil { t.Errorf("package %s not found", test.pkg) continue } obj := info.Pkg.Scope().Lookup(test.name) if obj == nil { t.Errorf("no object %s.%s", test.pkg, test.name) continue } posn := prog.Fset.Position(obj.Pos()) t.Logf("%s: %s (CgoEnabled=%t)", posn, obj, ctxt.CgoEnabled) gotFile := filepath.Base(posn.Filename) filesMatch := gotFile == test.genericFile if ctxt.CgoEnabled && filesMatch { t.Errorf("CGO_ENABLED=1: %s found in %s, want native file", obj, gotFile) } else if !ctxt.CgoEnabled && !filesMatch { t.Errorf("CGO_ENABLED=0: %s found in %s, want %s", obj, gotFile, test.genericFile) } // Load the file and check the object is declared at the right place. b, err := ioutil.ReadFile(posn.Filename) if err != nil { t.Errorf("can't read %s: %s", posn.Filename, err) continue } line := string(bytes.Split(b, []byte("\n"))[posn.Line-1]) ident := line[posn.Column-1:] if !strings.HasPrefix(ident, test.name) { t.Errorf("%s: %s not declared here (looking at %q)", posn, obj, ident) } } } }
func TestEnclosingFunction(t *testing.T) { tests := []struct { input string // the input file substr string // first occurrence of this string denotes interval fn string // name of expected containing function }{ // We use distinctive numbers as syntactic landmarks. // Ordinary function: {`package main func f() { println(1003) }`, "100", "main.f"}, // Methods: {`package main type T int func (t T) f() { println(200) }`, "200", "(main.T).f"}, // Function literal: {`package main func f() { println(func() { print(300) }) }`, "300", "main.f$1"}, // Doubly nested {`package main func f() { println(func() { print(func() { print(350) })})}`, "350", "main.f$1$1"}, // Implicit init for package-level var initializer. {"package main; var a = 400", "400", "main.init"}, // No code for constants: {"package main; const a = 500", "500", "(none)"}, // Explicit init() {"package main; func init() { println(600) }", "600", "main.init#1"}, // Multiple explicit init functions: {`package main func init() { println("foo") } func init() { println(800) }`, "800", "main.init#2"}, // init() containing FuncLit. {`package main func init() { println(func(){print(900)}) }`, "900", "main.init#1$1"}, } for _, test := range tests { conf := loader.Config{Fset: token.NewFileSet()} f, start, end := findInterval(t, conf.Fset, test.input, test.substr) if f == nil { continue } path, exact := astutil.PathEnclosingInterval(f, start, end) if !exact { t.Errorf("EnclosingFunction(%q) not exact", test.substr) continue } conf.CreateFromFiles("main", f) iprog, err := conf.Load() if err != nil { t.Error(err) continue } prog := ssa.Create(iprog, 0) pkg := prog.Package(iprog.Created[0].Pkg) pkg.Build() name := "(none)" fn := ssa.EnclosingFunction(pkg, path) if fn != nil { name = fn.String() } if name != test.fn { t.Errorf("EnclosingFunction(%q in %q) got %s, want %s", test.substr, test.input, name, test.fn) continue } // While we're here: test HasEnclosingFunction. if has := ssa.HasEnclosingFunction(pkg, path); has != (fn != nil) { t.Errorf("HasEnclosingFunction(%q in %q) got %v, want %v", test.substr, test.input, has, fn != nil) continue } } }