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 }