func TestBuild(t *testing.T) { saved := runtime.GOMAXPROCS(8) // Build is highly parallel defer runtime.GOMAXPROCS(saved) forward, reverse, errors := importgraph.Build(&build.Default) // Test direct edges. // We throw in crypto/hmac to prove that external test files // (such as this one) are inspected. for _, p := range []string{"go/build", "runtime", "testing", "crypto/hmac"} { if !forward[this][p] { t.Errorf("forward[importgraph][%s] not found", p) } if !reverse[p][this] { t.Errorf("reverse[%s][importgraph] not found", p) } } // Test non-existent direct edges for _, p := range []string{"fmt", "errors", "reflect"} { if forward[this][p] { t.Errorf("unexpected: forward[importgraph][%s] found", p) } if reverse[p][this] { t.Errorf("unexpected: reverse[%s][importgraph] found", p) } } // Test Search is reflexive. if !forward.Search(this)[this] { t.Errorf("irreflexive: forward.Search(importgraph)[importgraph] not found") } if !reverse.Search(this)[this] { t.Errorf("irrefexive: reverse.Search(importgraph)[importgraph] not found") } // Test Search is transitive. (There is no direct edge to these packages.) for _, p := range []string{"errors", "reflect", "unsafe"} { if !forward.Search(this)[p] { t.Errorf("intransitive: forward.Search(importgraph)[%s] not found", p) } if !reverse.Search(p)[this] { t.Errorf("intransitive: reverse.Search(%s)[importgraph] not found", p) } } // Test strongly-connected components. Because A's external // test package can depend on B, and vice versa, most of the // standard libraries are mutually dependent when their external // tests are considered. // // For any nodes x, y in the same SCC, y appears in the results // of both forward and reverse searches starting from x if !forward.Search("fmt")["io"] || !forward.Search("io")["fmt"] || !reverse.Search("fmt")["io"] || !reverse.Search("io")["fmt"] { t.Errorf("fmt and io are not mutually reachable despite being in the same SCC") } // debugging if false { for path, err := range errors { t.Logf("%s: %s", path, err) } printSorted := func(direction string, g importgraph.Graph, start string) { t.Log(direction) var pkgs []string for pkg := range g.Search(start) { pkgs = append(pkgs, pkg) } sort.Strings(pkgs) for _, pkg := range pkgs { t.Logf("\t%s", pkg) } } printSorted("forward", forward, this) printSorted("reverse", reverse, this) } }
func Main(ctxt *build.Context, offsetFlag, fromFlag, to string) error { // -- Parse the -from or -offset specifier ---------------------------- if (offsetFlag == "") == (fromFlag == "") { return fmt.Errorf("exactly one of the -from and -offset flags must be specified") } if !isValidIdentifier(to) { return fmt.Errorf("-to %q: not a valid identifier", to) } var spec *spec var err error if fromFlag != "" { spec, err = parseFromFlag(ctxt, fromFlag) } else { spec, err = parseOffsetFlag(ctxt, offsetFlag) } if err != nil { return err } if spec.fromName == to { return fmt.Errorf("the old and new names are the same: %s", to) } // -- Load the program consisting of the initial package ------------- iprog, err := loadProgram(ctxt, map[string]bool{spec.pkg: true}) if err != nil { return err } fromObjects, err := findFromObjects(iprog, spec) if err != nil { return err } // -- Load a larger program, for global renamings --------------------- if requiresGlobalRename(fromObjects, to) { // For a local refactoring, we needn't load more // packages, but if the renaming affects the package's // API, we we must load all packages that depend on the // package defining the object, plus their tests. if Verbose { fmt.Fprintln(os.Stderr, "Potentially global renaming; scanning workspace...") } // Scan the workspace and build the import graph. _, rev, errors := importgraph.Build(ctxt) if len(errors) > 0 { fmt.Fprintf(os.Stderr, "While scanning Go workspace:\n") for path, err := range errors { fmt.Fprintf(os.Stderr, "Package %q: %s.\n", path, err) } } // Enumerate the set of potentially affected packages. affectedPackages := make(map[string]bool) for _, obj := range fromObjects { // External test packages are never imported, // so they will never appear in the graph. for path := range rev.Search(obj.Pkg().Path()) { affectedPackages[path] = true } } // TODO(adonovan): allow the user to specify the scope, // or -ignore patterns? Computing the scope when we // don't (yet) support inputs containing errors can make // the tool rather brittle. // Re-load the larger program. iprog, err = loadProgram(ctxt, affectedPackages) if err != nil { return err } fromObjects, err = findFromObjects(iprog, spec) if err != nil { return err } } // -- Do the renaming ------------------------------------------------- r := renamer{ iprog: iprog, objsToUpdate: make(map[types.Object]bool), to: to, packages: make(map[*types.Package]*loader.PackageInfo), } // Only the initially imported packages (iprog.Imported) and // their external tests (iprog.Created) should be inspected or // modified, as only they have type-checked functions bodies. // The rest are just dependencies, needed only for package-level // type information. for _, info := range iprog.Imported { r.packages[info.Pkg] = info } for _, info := range iprog.Created { // (tests) r.packages[info.Pkg] = info } for _, from := range fromObjects { r.check(from) } if r.hadConflicts && !Force { return ConflictError } if DryRun { // TODO(adonovan): print the delta? return nil } return r.update() }