5
5
package unitchecker_test
6
6
7
7
import (
8
+ "encoding/json"
8
9
"flag"
9
10
"fmt"
10
11
"os"
11
12
"os/exec"
12
- "regexp"
13
13
"runtime"
14
14
"strings"
15
15
"testing"
@@ -18,7 +18,9 @@ import (
18
18
"golang.org/x/tools/go/analysis/passes/findcall"
19
19
"golang.org/x/tools/go/analysis/passes/printf"
20
20
"golang.org/x/tools/go/analysis/unitchecker"
21
- "golang.org/x/tools/internal/packagestest"
21
+ "golang.org/x/tools/internal/testenv"
22
+ "golang.org/x/tools/internal/testfiles"
23
+ "golang.org/x/tools/txtar"
22
24
)
23
25
24
26
func TestMain (m * testing.M ) {
@@ -52,24 +54,27 @@ func minivet() {
52
54
// This is a very basic integration test of modular
53
55
// analysis with facts using unitchecker under "go vet".
54
56
// It fork/execs the main function above.
55
- func TestIntegration (t * testing.T ) { packagestest .TestAll (t , testIntegration ) }
56
- func testIntegration (t * testing.T , exporter packagestest.Exporter ) {
57
+ func TestIntegration (t * testing.T ) {
57
58
if runtime .GOOS != "linux" && runtime .GOOS != "darwin" {
58
59
t .Skipf ("skipping fork/exec test on this platform" )
59
60
}
60
61
61
- exported := packagestest .Export (t , exporter , []packagestest.Module {{
62
- Name : "golang.org/fake" ,
63
- Files : map [string ]any {
64
- "a/a.go" : `package a
62
+ const src = `
63
+ -- go.mod --
64
+ module golang.org/fake
65
+ go 1.18
66
+
67
+ -- a/a.go --
68
+ package a
65
69
66
70
func _() {
67
71
MyFunc123()
68
72
}
69
73
70
74
func MyFunc123() {}
71
- ` ,
72
- "b/b.go" : `package b
75
+
76
+ -- b/b.go --
77
+ package b
73
78
74
79
import "golang.org/fake/a"
75
80
@@ -79,119 +84,140 @@ func _() {
79
84
}
80
85
81
86
func MyFunc123() {}
82
- ` ,
83
- "c/c.go" : `package c
87
+
88
+ -- c/c.go --
89
+ package c
84
90
85
91
func _() {
86
92
i := 5
87
93
i = i
88
94
}
89
- ` ,
90
- }}})
91
- defer exported .Cleanup ()
92
-
93
- const wantA = `
94
- .*a/a.go:4:11: call of MyFunc123\(...\)
95
- `
96
- const wantB = `
97
- .*b/b.go:6:13: call of MyFunc123\(...\)
98
- .*b/b.go:7:11: call of MyFunc123\(...\)
99
- `
100
- const wantC = `
101
- .*c/c.go:5:5: self-assignment of i
102
95
`
103
- const wantAJSON = `
104
- \{
105
- "golang.org/fake/a": \{
106
- "findcall": \[
107
- \{
108
- "posn": ".*a/a.go:4:11",
109
- "message": "call of MyFunc123\(...\)",
110
- "suggested_fixes": \[
111
- \{
112
- "message": "Add '_TEST_'",
113
- "edits": \[
114
- \{
115
- "filename": ".*a/a.go",
116
- "start": 32,
117
- "end": 32,
118
- "new": "_TEST_"
119
- \}
120
- \]
121
- \}
122
- \]
123
- \}
124
- \]
125
- \}
126
- \}
127
- `
128
- const wantCJSON = `
129
- \{
130
- "golang.org/fake/c": \{
131
- "assign": \[
132
- \{
133
- "posn": ".*c/c.go:5:5",
134
- "message": "self-assignment of i",
135
- "suggested_fixes": \[
136
- \{
137
- "message": "Remove self-assignment",
138
- "edits": \[
139
- \{
140
- "filename": ".*c/c.go",
141
- "start": 37,
142
- "end": 42,
143
- "new": ""
144
- \}
145
- \]
146
- \}
147
- \]
148
- \}
149
- \]
150
- \}
151
- \}
152
- `
153
- for _ , test := range []struct {
154
- args string
155
- wantOut string // multiline regular expression
156
- wantExitError bool
157
- }{
158
- {args : "golang.org/fake/a" , wantOut : wantA , wantExitError : true },
159
- {args : "golang.org/fake/b" , wantOut : wantB , wantExitError : true },
160
- {args : "golang.org/fake/c" , wantOut : wantC , wantExitError : true },
161
- {args : "golang.org/fake/a golang.org/fake/b" , wantOut : wantA + ".*" + wantB , wantExitError : true },
162
- {args : "-json golang.org/fake/a" , wantOut : wantAJSON , wantExitError : false },
163
- {args : "-json golang.org/fake/c" , wantOut : wantCJSON , wantExitError : false },
164
- {args : "-c=0 golang.org/fake/a" , wantOut : wantA + "4 MyFunc123\\ (\\ )\n " , wantExitError : true },
165
- } {
96
+ // Expand archive into tmp tree.
97
+ fs , err := txtar .FS (txtar .Parse ([]byte (src )))
98
+ if err != nil {
99
+ t .Fatal (err )
100
+ }
101
+ tmpdir := testfiles .CopyToTmp (t , fs )
102
+
103
+ // -- operators --
104
+
105
+ // vet runs "go vet" with the specified arguments (plus -findcall.name=MyFunc123).
106
+ vet := func (t * testing.T , args ... string ) (exitcode int , stdout , stderr string ) {
166
107
cmd := exec .Command ("go" , "vet" , "-vettool=" + os .Args [0 ], "-findcall.name=MyFunc123" )
167
108
cmd .Stdout = new (strings.Builder )
168
109
cmd .Stderr = new (strings.Builder )
169
- cmd .Args = append (cmd .Args , strings .Fields (test .args )... )
170
- cmd .Env = append (exported .Config .Env , "ENTRYPOINT=minivet" )
171
- cmd .Dir = exported .Config .Dir
172
-
173
- // TODO(golang/go#65729): Rework the test to
174
- // be specific about which output stream to match.
175
- err := cmd .Run ()
176
- exitcode := 0
177
- if exitErr , ok := err .(* exec.ExitError ); ok {
110
+ cmd .Args = append (cmd .Args , args ... )
111
+ cmd .Env = append (os .Environ (), "ENTRYPOINT=minivet" )
112
+ cmd .Dir = tmpdir
113
+ if err := cmd .Run (); err != nil {
114
+ exitErr , ok := err .(* exec.ExitError )
115
+ if ! ok {
116
+ t .Fatalf ("couldn't exec %v: %v" , cmd , err )
117
+ }
178
118
exitcode = exitErr .ExitCode ()
179
119
}
180
- if (exitcode != 0 ) != test .wantExitError {
181
- want := "zero"
182
- if test .wantExitError {
183
- want = "nonzero"
120
+
121
+ // Sanitize filenames; this is imperfect due to
122
+ // (e.g.) /private/tmp -> /tmp symlink on macOS.
123
+ stdout = strings .ReplaceAll (fmt .Sprint (cmd .Stdout ), tmpdir , "TMPDIR" )
124
+ stderr = strings .ReplaceAll (fmt .Sprint (cmd .Stderr ), tmpdir , "TMPDIR" )
125
+
126
+ // Show vet information on failure.
127
+ t .Cleanup (func () {
128
+ if t .Failed () {
129
+ t .Logf ("command: %v" , cmd )
130
+ t .Logf ("exit code: %d" , exitcode )
131
+ t .Logf ("stdout: %s" , stdout )
132
+ t .Logf ("stderr: %s" , stderr )
184
133
}
185
- t .Errorf ("%s: got exit code %d, want %s" , test .args , exitcode , want )
134
+ })
135
+ return
136
+ }
137
+
138
+ // exitcode asserts that the exit code was "want".
139
+ exitcode := func (t * testing.T , got , want int ) {
140
+ if got != want {
141
+ t .Fatalf ("vet tool exit code was %d" , got )
186
142
}
143
+ }
187
144
188
- out := fmt .Sprintf ("stdout:\n %s\n stderr:\n %s\n " , cmd .Stdout , cmd .Stderr )
189
- matched , err := regexp .MatchString ("(?s)" + test .wantOut , out )
190
- if err != nil {
191
- t .Fatalf ("regexp.Match(<<%s>>): %v" , test .wantOut , err )
145
+ // parseJSON parses the JSON diagnostics into a simple line-oriented form.
146
+ parseJSON := func (t * testing.T , stdout string ) string {
147
+ var v map [string ]map [string ][]map [string ]any
148
+ if err := json .Unmarshal ([]byte (stdout ), & v ); err != nil {
149
+ t .Fatalf ("invalid JSON: %v" , err )
192
150
}
193
- if ! matched {
194
- t .Errorf ("%s: got <<%s>>, want match of regexp <<%s>>" , test .args , out , test .wantOut )
151
+ var res strings.Builder
152
+ for pkgpath , v := range v {
153
+ for analyzer , v := range v {
154
+ for _ , v := range v {
155
+ fmt .Fprintf (& res , "%s: [%s@%s] %v\n " ,
156
+ v ["posn" ],
157
+ analyzer , pkgpath ,
158
+ v ["message" ])
159
+ }
160
+ }
195
161
}
162
+ // Show parsed JSON information on failure.
163
+ t .Cleanup (func () {
164
+ if t .Failed () {
165
+ t .Logf ("json: %s" , & res )
166
+ }
167
+ })
168
+ return res .String ()
196
169
}
170
+
171
+ // substring asserts that the labeled output contained the substring.
172
+ substring := func (t * testing.T , label , output , substr string ) {
173
+ if ! strings .Contains (output , substr ) {
174
+ t .Fatalf ("%s: expected substring %q" , label , substr )
175
+ }
176
+ }
177
+
178
+ // -- scenarios --
179
+
180
+ t .Run ("a" , func (t * testing.T ) {
181
+ code , _ , stderr := vet (t , "golang.org/fake/a" )
182
+ exitcode (t , code , 1 )
183
+ substring (t , "stderr" , stderr , "a/a.go:4:11: call of MyFunc123" )
184
+ })
185
+ t .Run ("b" , func (t * testing.T ) {
186
+ code , _ , stderr := vet (t , "golang.org/fake/b" )
187
+ exitcode (t , code , 1 )
188
+ substring (t , "stderr" , stderr , "b/b.go:6:13: call of MyFunc123" )
189
+ substring (t , "stderr" , stderr , "b/b.go:7:11: call of MyFunc123" )
190
+ })
191
+ t .Run ("c" , func (t * testing.T ) {
192
+ code , _ , stderr := vet (t , "golang.org/fake/c" )
193
+ exitcode (t , code , 1 )
194
+ substring (t , "stderr" , stderr , "c/c.go:5:5: self-assignment of i" )
195
+ })
196
+ t .Run ("ab" , func (t * testing.T ) {
197
+ code , _ , stderr := vet (t , "golang.org/fake/a" , "golang.org/fake/b" )
198
+ exitcode (t , code , 1 )
199
+ substring (t , "stderr" , stderr , "a/a.go:4:11: call of MyFunc123" )
200
+ substring (t , "stderr" , stderr , "b/b.go:6:13: call of MyFunc123" )
201
+ substring (t , "stderr" , stderr , "b/b.go:7:11: call of MyFunc123" )
202
+ })
203
+ t .Run ("a-json" , func (t * testing.T ) {
204
+ code , stdout , _ := vet (t , "-json" , "golang.org/fake/a" )
205
+ exitcode (t , code , 0 )
206
+ testenv .NeedsGo1Point (t , 26 ) // depends on CL 702815 (go vet -json => stdout)
207
+ json := parseJSON (t , stdout )
208
+ substring (
t ,
"json" ,
json ,
"a/a.go:4:11: [[email protected] /fake/a] call of MyFunc123" )
209
+ })
210
+ t .Run ("c-json" , func (t * testing.T ) {
211
+ code , stdout , _ := vet (t , "-json" , "golang.org/fake/c" )
212
+ exitcode (t , code , 0 )
213
+ testenv .NeedsGo1Point (t , 26 ) // depends on CL 702815 (go vet -json => stdout)
214
+ json := parseJSON (t , stdout )
215
+ substring (
t ,
"json" ,
json ,
"c/c.go:5:5: [[email protected] /fake/c] self-assignment of i" )
216
+ })
217
+ t .Run ("a-context" , func (t * testing.T ) {
218
+ code , _ , stderr := vet (t , "-c=0" , "golang.org/fake/a" )
219
+ exitcode (t , code , 1 )
220
+ substring (t , "stderr" , stderr , "a/a.go:4:11: call of MyFunc123" )
221
+ substring (t , "stderr" , stderr , "4 MyFunc123" )
222
+ })
197
223
}
0 commit comments