github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/refactor/importgraph/graph_test.go (about)

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Incomplete std lib sources on Android.
     6  
     7  //go:build !android
     8  // +build !android
     9  
    10  package importgraph_test
    11  
    12  import (
    13  	"fmt"
    14  	"go/build"
    15  	"os"
    16  	"sort"
    17  	"strings"
    18  	"testing"
    19  
    20  	"golang.org/x/tools/go/packages/packagestest"
    21  	"golang.org/x/tools/refactor/importgraph"
    22  
    23  	_ "crypto/hmac" // just for test, below
    24  )
    25  
    26  const this = "golang.org/x/tools/refactor/importgraph"
    27  
    28  func TestBuild(t *testing.T) {
    29  	exported := packagestest.Export(t, packagestest.GOPATH, []packagestest.Module{
    30  		{Name: "golang.org/x/tools/refactor/importgraph", Files: packagestest.MustCopyFileTree(".")}})
    31  	defer exported.Cleanup()
    32  
    33  	var gopath string
    34  	for _, env := range exported.Config.Env {
    35  		eq := strings.Index(env, "=")
    36  		if eq == 0 {
    37  			// We sometimes see keys with a single leading "=" in the environment on Windows.
    38  			// TODO(#49886): What is the correct way to parse them in general?
    39  			eq = strings.Index(env[1:], "=") + 1
    40  		}
    41  		if eq < 0 {
    42  			t.Fatalf("invalid variable in exported.Config.Env: %q", env)
    43  		}
    44  		k := env[:eq]
    45  		v := env[eq+1:]
    46  		if k == "GOPATH" {
    47  			gopath = v
    48  		}
    49  
    50  		if os.Getenv(k) == v {
    51  			continue
    52  		}
    53  		defer func(prev string, prevOK bool) {
    54  			if !prevOK {
    55  				if err := os.Unsetenv(k); err != nil {
    56  					t.Fatal(err)
    57  				}
    58  			} else {
    59  				if err := os.Setenv(k, prev); err != nil {
    60  					t.Fatal(err)
    61  				}
    62  			}
    63  		}(os.LookupEnv(k))
    64  
    65  		if err := os.Setenv(k, v); err != nil {
    66  			t.Fatal(err)
    67  		}
    68  		t.Logf("%s=%s", k, v)
    69  	}
    70  	if gopath == "" {
    71  		t.Fatal("Failed to fish GOPATH out of env: ", exported.Config.Env)
    72  	}
    73  
    74  	var buildContext = build.Default
    75  	buildContext.GOPATH = gopath
    76  	buildContext.Dir = exported.Config.Dir
    77  
    78  	forward, reverse, errs := importgraph.Build(&buildContext)
    79  	for path, err := range errs {
    80  		t.Errorf("%s: %s", path, err)
    81  	}
    82  	if t.Failed() {
    83  		return
    84  	}
    85  
    86  	// Log the complete graph before the errors, so that the errors are near the
    87  	// end of the log (where we expect them to be).
    88  	nodePrinted := map[string]bool{}
    89  	printNode := func(direction string, from string) {
    90  		key := fmt.Sprintf("%s[%q]", direction, from)
    91  		if nodePrinted[key] {
    92  			return
    93  		}
    94  		nodePrinted[key] = true
    95  
    96  		var g importgraph.Graph
    97  		switch direction {
    98  		case "forward":
    99  			g = forward
   100  		case "reverse":
   101  			g = reverse
   102  		default:
   103  			t.Helper()
   104  			t.Fatalf("bad direction: %q", direction)
   105  		}
   106  
   107  		t.Log(key)
   108  		var pkgs []string
   109  		for pkg := range g[from] {
   110  			pkgs = append(pkgs, pkg)
   111  		}
   112  		sort.Strings(pkgs)
   113  		for _, pkg := range pkgs {
   114  			t.Logf("\t%s", pkg)
   115  		}
   116  	}
   117  
   118  	if testing.Verbose() {
   119  		printNode("forward", this)
   120  		printNode("reverse", this)
   121  	}
   122  
   123  	// Test direct edges.
   124  	// We throw in crypto/hmac to prove that external test files
   125  	// (such as this one) are inspected.
   126  	for _, p := range []string{"go/build", "testing", "crypto/hmac"} {
   127  		if !forward[this][p] {
   128  			printNode("forward", this)
   129  			t.Errorf("forward[%q][%q] not found", this, p)
   130  		}
   131  		if !reverse[p][this] {
   132  			printNode("reverse", p)
   133  			t.Errorf("reverse[%q][%q] not found", p, this)
   134  		}
   135  	}
   136  
   137  	// Test non-existent direct edges
   138  	for _, p := range []string{"errors", "reflect"} {
   139  		if forward[this][p] {
   140  			printNode("forward", this)
   141  			t.Errorf("unexpected: forward[%q][%q] found", this, p)
   142  		}
   143  		if reverse[p][this] {
   144  			printNode("reverse", p)
   145  			t.Errorf("unexpected: reverse[%q][%q] found", p, this)
   146  		}
   147  	}
   148  
   149  	// Test Search is reflexive.
   150  	if !forward.Search(this)[this] {
   151  		printNode("forward", this)
   152  		t.Errorf("irreflexive: forward.Search(importgraph)[importgraph] not found")
   153  	}
   154  	if !reverse.Search(this)[this] {
   155  		printNode("reverse", this)
   156  		t.Errorf("irrefexive: reverse.Search(importgraph)[importgraph] not found")
   157  	}
   158  
   159  	// Test Search is transitive.  (There is no direct edge to these packages.)
   160  	for _, p := range []string{"errors", "reflect", "unsafe"} {
   161  		if !forward.Search(this)[p] {
   162  			printNode("forward", this)
   163  			t.Errorf("intransitive: forward.Search(importgraph)[%s] not found", p)
   164  		}
   165  		if !reverse.Search(p)[this] {
   166  			printNode("reverse", p)
   167  			t.Errorf("intransitive: reverse.Search(%s)[importgraph] not found", p)
   168  		}
   169  	}
   170  
   171  	// Test strongly-connected components.  Because A's external
   172  	// test package can depend on B, and vice versa, most of the
   173  	// standard libraries are mutually dependent when their external
   174  	// tests are considered.
   175  	//
   176  	// For any nodes x, y in the same SCC, y appears in the results
   177  	// of both forward and reverse searches starting from x
   178  	if !forward.Search("fmt")["io"] ||
   179  		!forward.Search("io")["fmt"] ||
   180  		!reverse.Search("fmt")["io"] ||
   181  		!reverse.Search("io")["fmt"] {
   182  		printNode("forward", "fmt")
   183  		printNode("forward", "io")
   184  		printNode("reverse", "fmt")
   185  		printNode("reverse", "io")
   186  		t.Errorf("fmt and io are not mutually reachable despite being in the same SCC")
   187  	}
   188  }