golang.org/x/tools@v0.21.0/go/analysis/unitchecker/unitchecker_test.go (about)

     1  // Copyright 2018 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  package unitchecker_test
     6  
     7  import (
     8  	"flag"
     9  	"os"
    10  	"os/exec"
    11  	"regexp"
    12  	"runtime"
    13  	"strings"
    14  	"testing"
    15  
    16  	"golang.org/x/tools/go/analysis/passes/assign"
    17  	"golang.org/x/tools/go/analysis/passes/findcall"
    18  	"golang.org/x/tools/go/analysis/passes/printf"
    19  	"golang.org/x/tools/go/analysis/unitchecker"
    20  	"golang.org/x/tools/go/packages/packagestest"
    21  )
    22  
    23  func TestMain(m *testing.M) {
    24  	// child process?
    25  	switch os.Getenv("ENTRYPOINT") {
    26  	case "vet":
    27  		vet()
    28  		panic("unreachable")
    29  	case "minivet":
    30  		minivet()
    31  		panic("unreachable")
    32  	case "worker":
    33  		worker() // see ExampleSeparateAnalysis
    34  		panic("unreachable")
    35  	}
    36  
    37  	// test process
    38  	flag.Parse()
    39  	os.Exit(m.Run())
    40  }
    41  
    42  // minivet is a vet-like tool with a few analyzers, for testing.
    43  func minivet() {
    44  	unitchecker.Main(
    45  		findcall.Analyzer,
    46  		printf.Analyzer,
    47  		assign.Analyzer,
    48  	)
    49  }
    50  
    51  // This is a very basic integration test of modular
    52  // analysis with facts using unitchecker under "go vet".
    53  // It fork/execs the main function above.
    54  func TestIntegration(t *testing.T) { packagestest.TestAll(t, testIntegration) }
    55  func testIntegration(t *testing.T, exporter packagestest.Exporter) {
    56  	if runtime.GOOS != "linux" && runtime.GOOS != "darwin" {
    57  		t.Skipf("skipping fork/exec test on this platform")
    58  	}
    59  
    60  	exported := packagestest.Export(t, exporter, []packagestest.Module{{
    61  		Name: "golang.org/fake",
    62  		Files: map[string]interface{}{
    63  			"a/a.go": `package a
    64  
    65  func _() {
    66  	MyFunc123()
    67  }
    68  
    69  func MyFunc123() {}
    70  `,
    71  			"b/b.go": `package b
    72  
    73  import "golang.org/fake/a"
    74  
    75  func _() {
    76  	a.MyFunc123()
    77  	MyFunc123()
    78  }
    79  
    80  func MyFunc123() {}
    81  `,
    82  			"c/c.go": `package c
    83  
    84  func _() {
    85      i := 5
    86      i = i
    87  }
    88  `,
    89  		}}})
    90  	defer exported.Cleanup()
    91  
    92  	const wantA = `# golang.org/fake/a
    93  ([/._\-a-zA-Z0-9]+[\\/]fake[\\/])?a/a.go:4:11: call of MyFunc123\(...\)
    94  `
    95  	const wantB = `# golang.org/fake/b
    96  ([/._\-a-zA-Z0-9]+[\\/]fake[\\/])?b/b.go:6:13: call of MyFunc123\(...\)
    97  ([/._\-a-zA-Z0-9]+[\\/]fake[\\/])?b/b.go:7:11: call of MyFunc123\(...\)
    98  `
    99  	const wantC = `# golang.org/fake/c
   100  ([/._\-a-zA-Z0-9]+[\\/]fake[\\/])?c/c.go:5:5: self-assignment of i to i
   101  `
   102  	const wantAJSON = `# golang.org/fake/a
   103  \{
   104  	"golang.org/fake/a": \{
   105  		"findcall": \[
   106  			\{
   107  				"posn": "([/._\-a-zA-Z0-9]+[\\/]fake[\\/])?a/a.go:4:11",
   108  				"message": "call of MyFunc123\(...\)",
   109  				"suggested_fixes": \[
   110  					\{
   111  						"message": "Add '_TEST_'",
   112  						"edits": \[
   113  							\{
   114  								"filename": "([/._\-a-zA-Z0-9]+[\\/]fake[\\/])?a/a.go",
   115  								"start": 32,
   116  								"end": 32,
   117  								"new": "_TEST_"
   118  							\}
   119  						\]
   120  					\}
   121  				\]
   122  			\}
   123  		\]
   124  	\}
   125  \}
   126  `
   127  	const wantCJSON = `# golang.org/fake/c
   128  \{
   129  	"golang.org/fake/c": \{
   130  		"assign": \[
   131  			\{
   132  				"posn": "([/._\-a-zA-Z0-9]+[\\/]fake[\\/])?c/c.go:5:5",
   133  				"message": "self-assignment of i to i",
   134  				"suggested_fixes": \[
   135  					\{
   136  						"message": "Remove",
   137  						"edits": \[
   138  							\{
   139  								"filename": "([/._\-a-zA-Z0-9]+[\\/]fake[\\/])?c/c.go",
   140  								"start": 37,
   141  								"end": 42,
   142  								"new": ""
   143  							\}
   144  						\]
   145  					\}
   146  				\]
   147  			\}
   148  		\]
   149  	\}
   150  \}
   151  `
   152  	for _, test := range []struct {
   153  		args          string
   154  		wantOut       string
   155  		wantExitError bool
   156  	}{
   157  		{args: "golang.org/fake/a", wantOut: wantA, wantExitError: true},
   158  		{args: "golang.org/fake/b", wantOut: wantB, wantExitError: true},
   159  		{args: "golang.org/fake/c", wantOut: wantC, wantExitError: true},
   160  		{args: "golang.org/fake/a golang.org/fake/b", wantOut: wantA + wantB, wantExitError: true},
   161  		{args: "-json golang.org/fake/a", wantOut: wantAJSON, wantExitError: false},
   162  		{args: "-json golang.org/fake/c", wantOut: wantCJSON, wantExitError: false},
   163  		{args: "-c=0 golang.org/fake/a", wantOut: wantA + "4		MyFunc123\\(\\)\n", wantExitError: true},
   164  	} {
   165  		cmd := exec.Command("go", "vet", "-vettool="+os.Args[0], "-findcall.name=MyFunc123")
   166  		cmd.Args = append(cmd.Args, strings.Fields(test.args)...)
   167  		cmd.Env = append(exported.Config.Env, "ENTRYPOINT=minivet")
   168  		cmd.Dir = exported.Config.Dir
   169  
   170  		// TODO(golang/go#65729): this is unsound: any extra
   171  		// logging by the child process (e.g. due to GODEBUG
   172  		// options) will add noise to stderr, causing the
   173  		// CombinedOutput to be unparseable as JSON. But we
   174  		// can't simply use Output here as some of the tests
   175  		// look for substrings of stderr. Rework the test to
   176  		// be specific about which output stream to match.
   177  		out, err := cmd.CombinedOutput()
   178  		exitcode := 0
   179  		if exitErr, ok := err.(*exec.ExitError); ok {
   180  			exitcode = exitErr.ExitCode()
   181  		}
   182  		if (exitcode != 0) != test.wantExitError {
   183  			want := "zero"
   184  			if test.wantExitError {
   185  				want = "nonzero"
   186  			}
   187  			t.Errorf("%s: got exit code %d, want %s", test.args, exitcode, want)
   188  		}
   189  
   190  		matched, err := regexp.Match(test.wantOut, out)
   191  		if err != nil {
   192  			t.Fatalf("regexp.Match(<<%s>>): %v", test.wantOut, err)
   193  		}
   194  		if !matched {
   195  			t.Errorf("%s: got <<%s>>, want match of regexp <<%s>>", test.args, out, test.wantOut)
   196  		}
   197  	}
   198  }