示例#1
0
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}
	})
}
示例#2
0
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)
		}
	}
}
示例#3
0
// 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)
	}
}
示例#4
0
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
}
示例#5
0
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.
}
示例#6
0
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)
}
示例#7
0
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)
			}
		}
	}
}
示例#8
0
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
		}
	}
}