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