| // Copyright 2015 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Incomplete std lib sources on Android. |
| |
| //go:build !android |
| // +build !android |
| |
| package importgraph_test |
| |
| import ( |
| "fmt" |
| "go/build" |
| "os" |
| "sort" |
| "strings" |
| "testing" |
| |
| "golang.org/x/tools/go/packages/packagestest" |
| "golang.org/x/tools/refactor/importgraph" |
| |
| _ "crypto/hmac" // just for test, below |
| ) |
| |
| const this = "golang.org/x/tools/refactor/importgraph" |
| |
| func TestBuild(t *testing.T) { |
| exported := packagestest.Export(t, packagestest.GOPATH, []packagestest.Module{ |
| {Name: "golang.org/x/tools/refactor/importgraph", Files: packagestest.MustCopyFileTree(".")}}) |
| defer exported.Cleanup() |
| |
| var gopath string |
| for _, env := range exported.Config.Env { |
| eq := strings.Index(env, "=") |
| if eq == 0 { |
| // We sometimes see keys with a single leading "=" in the environment on Windows. |
| // TODO(#49886): What is the correct way to parse them in general? |
| eq = strings.Index(env[1:], "=") + 1 |
| } |
| if eq < 0 { |
| t.Fatalf("invalid variable in exported.Config.Env: %q", env) |
| } |
| k := env[:eq] |
| v := env[eq+1:] |
| if k == "GOPATH" { |
| gopath = v |
| } |
| |
| if os.Getenv(k) == v { |
| continue |
| } |
| defer func(prev string, prevOK bool) { |
| if !prevOK { |
| if err := os.Unsetenv(k); err != nil { |
| t.Fatal(err) |
| } |
| } else { |
| if err := os.Setenv(k, prev); err != nil { |
| t.Fatal(err) |
| } |
| } |
| }(os.LookupEnv(k)) |
| |
| if err := os.Setenv(k, v); err != nil { |
| t.Fatal(err) |
| } |
| t.Logf("%s=%s", k, v) |
| } |
| if gopath == "" { |
| t.Fatal("Failed to fish GOPATH out of env: ", exported.Config.Env) |
| } |
| |
| var buildContext = build.Default |
| buildContext.GOPATH = gopath |
| buildContext.Dir = exported.Config.Dir |
| |
| forward, reverse, errs := importgraph.Build(&buildContext) |
| for path, err := range errs { |
| t.Errorf("%s: %s", path, err) |
| } |
| if t.Failed() { |
| return |
| } |
| |
| // Log the complete graph before the errors, so that the errors are near the |
| // end of the log (where we expect them to be). |
| nodePrinted := map[string]bool{} |
| printNode := func(direction string, from string) { |
| key := fmt.Sprintf("%s[%q]", direction, from) |
| if nodePrinted[key] { |
| return |
| } |
| nodePrinted[key] = true |
| |
| var g importgraph.Graph |
| switch direction { |
| case "forward": |
| g = forward |
| case "reverse": |
| g = reverse |
| default: |
| t.Helper() |
| t.Fatalf("bad direction: %q", direction) |
| } |
| |
| t.Log(key) |
| var pkgs []string |
| for pkg := range g[from] { |
| pkgs = append(pkgs, pkg) |
| } |
| sort.Strings(pkgs) |
| for _, pkg := range pkgs { |
| t.Logf("\t%s", pkg) |
| } |
| } |
| |
| if testing.Verbose() { |
| printNode("forward", this) |
| printNode("reverse", this) |
| } |
| |
| // 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", "testing", "crypto/hmac"} { |
| if !forward[this][p] { |
| printNode("forward", this) |
| t.Errorf("forward[%q][%q] not found", this, p) |
| } |
| if !reverse[p][this] { |
| printNode("reverse", p) |
| t.Errorf("reverse[%q][%q] not found", p, this) |
| } |
| } |
| |
| // Test non-existent direct edges |
| for _, p := range []string{"errors", "reflect"} { |
| if forward[this][p] { |
| printNode("forward", this) |
| t.Errorf("unexpected: forward[%q][%q] found", this, p) |
| } |
| if reverse[p][this] { |
| printNode("reverse", p) |
| t.Errorf("unexpected: reverse[%q][%q] found", p, this) |
| } |
| } |
| |
| // Test Search is reflexive. |
| if !forward.Search(this)[this] { |
| printNode("forward", this) |
| t.Errorf("irreflexive: forward.Search(importgraph)[importgraph] not found") |
| } |
| if !reverse.Search(this)[this] { |
| printNode("reverse", 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] { |
| printNode("forward", this) |
| t.Errorf("intransitive: forward.Search(importgraph)[%s] not found", p) |
| } |
| if !reverse.Search(p)[this] { |
| printNode("reverse", p) |
| 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"] { |
| printNode("forward", "fmt") |
| printNode("forward", "io") |
| printNode("reverse", "fmt") |
| printNode("reverse", "io") |
| t.Errorf("fmt and io are not mutually reachable despite being in the same SCC") |
| } |
| } |