| // 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 | |
| package importgraph_test | |
| import ( | |
| "fmt" | |
| "go/build" | |
| "os" | |
| "sort" | |
| "strings" | |
| "testing" | |
| "golang.org/x/tools/internal/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("irreflexive: 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") | |
| } | |
| } |