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