1
2
3
4
5 package checker_test
6
7 import (
8 "flag"
9 "fmt"
10 "go/token"
11 "log"
12 "os"
13 "os/exec"
14 "path"
15 "regexp"
16 "strings"
17 "testing"
18
19 "golang.org/x/tools/go/analysis"
20 "golang.org/x/tools/go/analysis/analysistest"
21 "golang.org/x/tools/go/analysis/multichecker"
22 "golang.org/x/tools/internal/testenv"
23 )
24
25
26
27 var candidates = map[string]*analysis.Analyzer{
28 renameAnalyzer.Name: renameAnalyzer,
29 otherAnalyzer.Name: otherAnalyzer,
30 }
31
32 func TestMain(m *testing.M) {
33
34
35
36 if s, ok := os.LookupEnv("ANALYZERS"); ok {
37 var analyzers []*analysis.Analyzer
38 for _, name := range strings.Split(s, ",") {
39 a := candidates[name]
40 if a == nil {
41 log.Fatalf("no such analyzer: %q", name)
42 }
43 analyzers = append(analyzers, a)
44 }
45 multichecker.Main(analyzers...)
46 panic("unreachable")
47 }
48
49
50 flag.Parse()
51 os.Exit(m.Run())
52 }
53
54 const (
55 exitCodeSuccess = 0
56 exitCodeFailed = 1
57 exitCodeDiagnostics = 3
58 )
59
60
61
62
63 func fix(t *testing.T, dir, analyzers string, wantExit int, patterns ...string) string {
64 testenv.NeedsExec(t)
65 testenv.NeedsTool(t, "go")
66
67 cmd := exec.Command(os.Args[0], "-fix")
68 cmd.Args = append(cmd.Args, patterns...)
69 cmd.Env = append(os.Environ(),
70 "ANALYZERS="+analyzers,
71 "GOPATH="+dir,
72 "GO111MODULE=off",
73 "GOPROXY=off")
74
75 clean := func(s string) string {
76 return strings.ReplaceAll(s, os.TempDir(), "os.TempDir/")
77 }
78 outBytes, err := cmd.CombinedOutput()
79 out := clean(string(outBytes))
80 t.Logf("$ %s\n%s", clean(fmt.Sprint(cmd)), out)
81 if err, ok := err.(*exec.ExitError); !ok {
82 t.Fatalf("failed to execute multichecker: %v", err)
83 } else if err.ExitCode() != wantExit {
84 t.Errorf("exit code was %d, want %d", err.ExitCode(), wantExit)
85 }
86 return out
87 }
88
89
90
91 func TestFixes(t *testing.T) {
92 files := map[string]string{
93 "rename/foo.go": `package rename
94
95 func Foo() {
96 bar := 12
97 _ = bar
98 }
99
100 // the end
101 `,
102 "rename/intestfile_test.go": `package rename
103
104 func InTestFile() {
105 bar := 13
106 _ = bar
107 }
108
109 // the end
110 `,
111 "rename/foo_test.go": `package rename_test
112
113 func Foo() {
114 bar := 14
115 _ = bar
116 }
117
118 // the end
119 `,
120 "duplicate/dup.go": `package duplicate
121
122 func Foo() {
123 bar := 14
124 _ = bar
125 }
126
127 // the end
128 `,
129 }
130 fixed := map[string]string{
131 "rename/foo.go": `package rename
132
133 func Foo() {
134 baz := 12
135 _ = baz
136 }
137
138 // the end
139 `,
140 "rename/intestfile_test.go": `package rename
141
142 func InTestFile() {
143 baz := 13
144 _ = baz
145 }
146
147 // the end
148 `,
149 "rename/foo_test.go": `package rename_test
150
151 func Foo() {
152 baz := 14
153 _ = baz
154 }
155
156 // the end
157 `,
158 "duplicate/dup.go": `package duplicate
159
160 func Foo() {
161 baz := 14
162 _ = baz
163 }
164
165 // the end
166 `,
167 }
168 dir, cleanup, err := analysistest.WriteFiles(files)
169 if err != nil {
170 t.Fatalf("Creating test files failed with %s", err)
171 }
172 defer cleanup()
173
174 fix(t, dir, "rename,other", exitCodeDiagnostics, "rename", "duplicate")
175
176 for name, want := range fixed {
177 path := path.Join(dir, "src", name)
178 contents, err := os.ReadFile(path)
179 if err != nil {
180 t.Errorf("error reading %s: %v", path, err)
181 }
182 if got := string(contents); got != want {
183 t.Errorf("contents of %s file did not match expectations. got=%s, want=%s", path, got, want)
184 }
185 }
186 }
187
188
189
190 func TestConflict(t *testing.T) {
191 files := map[string]string{
192 "conflict/foo.go": `package conflict
193
194 func Foo() {
195 bar := 12
196 _ = bar
197 }
198
199 // the end
200 `,
201 }
202 dir, cleanup, err := analysistest.WriteFiles(files)
203 if err != nil {
204 t.Fatalf("Creating test files failed with %s", err)
205 }
206 defer cleanup()
207
208 out := fix(t, dir, "rename,other", exitCodeFailed, "conflict")
209
210 pattern := `conflicting edits from rename and rename on .*foo.go`
211 matched, err := regexp.MatchString(pattern, out)
212 if err != nil {
213 t.Errorf("error matching pattern %s: %v", pattern, err)
214 } else if !matched {
215 t.Errorf("output did not match pattern: %s", pattern)
216 }
217
218
219 for name, want := range files {
220 path := path.Join(dir, "src", name)
221 contents, err := os.ReadFile(path)
222 if err != nil {
223 t.Errorf("error reading %s: %v", path, err)
224 }
225 if got := string(contents); got != want {
226 t.Errorf("contents of %s file updated. got=%s, want=%s", path, got, want)
227 }
228 }
229 }
230
231
232
233
234 func TestOther(t *testing.T) {
235 files := map[string]string{
236 "other/foo.go": `package other
237
238 func Foo() {
239 bar := 12
240 _ = bar
241 }
242
243 // the end
244 `,
245 }
246 dir, cleanup, err := analysistest.WriteFiles(files)
247 if err != nil {
248 t.Fatalf("Creating test files failed with %s", err)
249 }
250 defer cleanup()
251
252 out := fix(t, dir, "rename,other", exitCodeFailed, "other")
253
254 pattern := `.*conflicting edits from other and rename on .*foo.go`
255 matched, err := regexp.MatchString(pattern, out)
256 if err != nil {
257 t.Errorf("error matching pattern %s: %v", pattern, err)
258 } else if !matched {
259 t.Errorf("output did not match pattern: %s", pattern)
260 }
261
262
263 for name, want := range files {
264 path := path.Join(dir, "src", name)
265 contents, err := os.ReadFile(path)
266 if err != nil {
267 t.Errorf("error reading %s: %v", path, err)
268 }
269 if got := string(contents); got != want {
270 t.Errorf("contents of %s file updated. got=%s, want=%s", path, got, want)
271 }
272 }
273 }
274
275
276
277 func TestNoEnd(t *testing.T) {
278 files := map[string]string{
279 "a/a.go": "package a\n\nfunc F() {}",
280 }
281 dir, cleanup, err := analysistest.WriteFiles(files)
282 if err != nil {
283 t.Fatalf("Creating test files failed with %s", err)
284 }
285 defer cleanup()
286
287 fix(t, dir, "noend", exitCodeDiagnostics, "a")
288
289 got, err := os.ReadFile(path.Join(dir, "src/a/a.go"))
290 if err != nil {
291 t.Fatal(err)
292 }
293 const want = "package a\n\n/*hello*/\nfunc F() {}\n"
294 if string(got) != want {
295 t.Errorf("new file contents were <<%s>>, want <<%s>>", got, want)
296 }
297 }
298
299 func init() {
300 candidates["noend"] = &analysis.Analyzer{
301 Name: "noend",
302 Doc: "inserts /*hello*/ before first decl",
303 Run: func(pass *analysis.Pass) (any, error) {
304 decl := pass.Files[0].Decls[0]
305 pass.Report(analysis.Diagnostic{
306 Pos: decl.Pos(),
307 End: token.NoPos,
308 Message: "say hello",
309 SuggestedFixes: []analysis.SuggestedFix{{
310 Message: "say hello",
311 TextEdits: []analysis.TextEdit{
312 {
313 Pos: decl.Pos(),
314 End: token.NoPos,
315 NewText: []byte("/*hello*/"),
316 },
317 },
318 }},
319 })
320 return nil, nil
321 },
322 }
323 }
324
View as plain text