]>
Commit | Line | Data |
---|---|---|
dd931d9b ILT |
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 modfile | |
6 | ||
7 | import ( | |
8 | "bytes" | |
9 | "fmt" | |
10 | "io/ioutil" | |
11 | "os" | |
12 | "os/exec" | |
13 | "path/filepath" | |
14 | "reflect" | |
15 | "strings" | |
16 | "testing" | |
17 | ) | |
18 | ||
19 | // exists reports whether the named file exists. | |
20 | func exists(name string) bool { | |
21 | _, err := os.Stat(name) | |
22 | return err == nil | |
23 | } | |
24 | ||
25 | // Test that reading and then writing the golden files | |
26 | // does not change their output. | |
27 | func TestPrintGolden(t *testing.T) { | |
28 | outs, err := filepath.Glob("testdata/*.golden") | |
29 | if err != nil { | |
30 | t.Fatal(err) | |
31 | } | |
32 | for _, out := range outs { | |
33 | testPrint(t, out, out) | |
34 | } | |
35 | } | |
36 | ||
37 | // testPrint is a helper for testing the printer. | |
38 | // It reads the file named in, reformats it, and compares | |
39 | // the result to the file named out. | |
40 | func testPrint(t *testing.T, in, out string) { | |
41 | data, err := ioutil.ReadFile(in) | |
42 | if err != nil { | |
43 | t.Error(err) | |
44 | return | |
45 | } | |
46 | ||
47 | golden, err := ioutil.ReadFile(out) | |
48 | if err != nil { | |
49 | t.Error(err) | |
50 | return | |
51 | } | |
52 | ||
53 | base := "testdata/" + filepath.Base(in) | |
54 | f, err := parse(in, data) | |
55 | if err != nil { | |
56 | t.Error(err) | |
57 | return | |
58 | } | |
59 | ||
60 | ndata := Format(f) | |
61 | ||
62 | if !bytes.Equal(ndata, golden) { | |
63 | t.Errorf("formatted %s incorrectly: diff shows -golden, +ours", base) | |
64 | tdiff(t, string(golden), string(ndata)) | |
65 | return | |
66 | } | |
67 | } | |
68 | ||
69 | func TestParseLax(t *testing.T) { | |
70 | badFile := []byte(`module m | |
71 | surprise attack | |
72 | x y ( | |
73 | z | |
74 | ) | |
75 | exclude v1.2.3 | |
76 | replace <-!!! | |
77 | `) | |
78 | _, err := ParseLax("file", badFile, nil) | |
79 | if err != nil { | |
80 | t.Fatalf("ParseLax did not ignore irrelevant errors: %v", err) | |
81 | } | |
82 | } | |
83 | ||
84 | // Test that when files in the testdata directory are parsed | |
85 | // and printed and parsed again, we get the same parse tree | |
86 | // both times. | |
87 | func TestPrintParse(t *testing.T) { | |
88 | outs, err := filepath.Glob("testdata/*") | |
89 | if err != nil { | |
90 | t.Fatal(err) | |
91 | } | |
92 | for _, out := range outs { | |
93 | data, err := ioutil.ReadFile(out) | |
94 | if err != nil { | |
95 | t.Error(err) | |
96 | continue | |
97 | } | |
98 | ||
99 | base := "testdata/" + filepath.Base(out) | |
100 | f, err := parse(base, data) | |
101 | if err != nil { | |
102 | t.Errorf("parsing original: %v", err) | |
103 | continue | |
104 | } | |
105 | ||
106 | ndata := Format(f) | |
107 | f2, err := parse(base, ndata) | |
108 | if err != nil { | |
109 | t.Errorf("parsing reformatted: %v", err) | |
110 | continue | |
111 | } | |
112 | ||
113 | eq := eqchecker{file: base} | |
114 | if err := eq.check(f, f2); err != nil { | |
115 | t.Errorf("not equal (parse/Format/parse): %v", err) | |
116 | } | |
117 | ||
118 | pf1, err := Parse(base, data, nil) | |
119 | if err != nil { | |
120 | switch base { | |
121 | case "testdata/replace2.in", "testdata/gopkg.in.golden": | |
122 | t.Errorf("should parse %v: %v", base, err) | |
123 | } | |
124 | } | |
125 | if err == nil { | |
126 | pf2, err := Parse(base, ndata, nil) | |
127 | if err != nil { | |
128 | t.Errorf("Parsing reformatted: %v", err) | |
129 | continue | |
130 | } | |
131 | eq := eqchecker{file: base} | |
132 | if err := eq.check(pf1, pf2); err != nil { | |
133 | t.Errorf("not equal (parse/Format/Parse): %v", err) | |
134 | } | |
135 | ||
136 | ndata2, err := pf1.Format() | |
137 | if err != nil { | |
138 | t.Errorf("reformat: %v", err) | |
139 | } | |
140 | pf3, err := Parse(base, ndata2, nil) | |
141 | if err != nil { | |
142 | t.Errorf("Parsing reformatted2: %v", err) | |
143 | continue | |
144 | } | |
145 | eq = eqchecker{file: base} | |
146 | if err := eq.check(pf1, pf3); err != nil { | |
147 | t.Errorf("not equal (Parse/Format/Parse): %v", err) | |
148 | } | |
149 | ndata = ndata2 | |
150 | } | |
151 | ||
152 | if strings.HasSuffix(out, ".in") { | |
153 | golden, err := ioutil.ReadFile(strings.TrimSuffix(out, ".in") + ".golden") | |
154 | if err != nil { | |
155 | t.Error(err) | |
156 | continue | |
157 | } | |
158 | if !bytes.Equal(ndata, golden) { | |
159 | t.Errorf("formatted %s incorrectly: diff shows -golden, +ours", base) | |
160 | tdiff(t, string(golden), string(ndata)) | |
161 | return | |
162 | } | |
163 | } | |
164 | } | |
165 | } | |
166 | ||
167 | // An eqchecker holds state for checking the equality of two parse trees. | |
168 | type eqchecker struct { | |
169 | file string | |
170 | pos Position | |
171 | } | |
172 | ||
173 | // errorf returns an error described by the printf-style format and arguments, | |
174 | // inserting the current file position before the error text. | |
175 | func (eq *eqchecker) errorf(format string, args ...interface{}) error { | |
176 | return fmt.Errorf("%s:%d: %s", eq.file, eq.pos.Line, | |
177 | fmt.Sprintf(format, args...)) | |
178 | } | |
179 | ||
180 | // check checks that v and w represent the same parse tree. | |
181 | // If not, it returns an error describing the first difference. | |
182 | func (eq *eqchecker) check(v, w interface{}) error { | |
183 | return eq.checkValue(reflect.ValueOf(v), reflect.ValueOf(w)) | |
184 | } | |
185 | ||
186 | var ( | |
187 | posType = reflect.TypeOf(Position{}) | |
188 | commentsType = reflect.TypeOf(Comments{}) | |
189 | ) | |
190 | ||
191 | // checkValue checks that v and w represent the same parse tree. | |
192 | // If not, it returns an error describing the first difference. | |
193 | func (eq *eqchecker) checkValue(v, w reflect.Value) error { | |
194 | // inner returns the innermost expression for v. | |
195 | // if v is a non-nil interface value, it returns the concrete | |
196 | // value in the interface. | |
197 | inner := func(v reflect.Value) reflect.Value { | |
198 | for { | |
199 | if v.Kind() == reflect.Interface && !v.IsNil() { | |
200 | v = v.Elem() | |
201 | continue | |
202 | } | |
203 | break | |
204 | } | |
205 | return v | |
206 | } | |
207 | ||
208 | v = inner(v) | |
209 | w = inner(w) | |
210 | if v.Kind() == reflect.Invalid && w.Kind() == reflect.Invalid { | |
211 | return nil | |
212 | } | |
213 | if v.Kind() == reflect.Invalid { | |
214 | return eq.errorf("nil interface became %s", w.Type()) | |
215 | } | |
216 | if w.Kind() == reflect.Invalid { | |
217 | return eq.errorf("%s became nil interface", v.Type()) | |
218 | } | |
219 | ||
220 | if v.Type() != w.Type() { | |
221 | return eq.errorf("%s became %s", v.Type(), w.Type()) | |
222 | } | |
223 | ||
224 | if p, ok := v.Interface().(Expr); ok { | |
225 | eq.pos, _ = p.Span() | |
226 | } | |
227 | ||
228 | switch v.Kind() { | |
229 | default: | |
230 | return eq.errorf("unexpected type %s", v.Type()) | |
231 | ||
232 | case reflect.Bool, reflect.Int, reflect.String: | |
233 | vi := v.Interface() | |
234 | wi := w.Interface() | |
235 | if vi != wi { | |
236 | return eq.errorf("%v became %v", vi, wi) | |
237 | } | |
238 | ||
239 | case reflect.Slice: | |
240 | vl := v.Len() | |
241 | wl := w.Len() | |
242 | for i := 0; i < vl || i < wl; i++ { | |
243 | if i >= vl { | |
244 | return eq.errorf("unexpected %s", w.Index(i).Type()) | |
245 | } | |
246 | if i >= wl { | |
247 | return eq.errorf("missing %s", v.Index(i).Type()) | |
248 | } | |
249 | if err := eq.checkValue(v.Index(i), w.Index(i)); err != nil { | |
250 | return err | |
251 | } | |
252 | } | |
253 | ||
254 | case reflect.Struct: | |
255 | // Fields in struct must match. | |
256 | t := v.Type() | |
257 | n := t.NumField() | |
258 | for i := 0; i < n; i++ { | |
259 | tf := t.Field(i) | |
260 | switch { | |
261 | default: | |
262 | if err := eq.checkValue(v.Field(i), w.Field(i)); err != nil { | |
263 | return err | |
264 | } | |
265 | ||
266 | case tf.Type == posType: // ignore positions | |
267 | case tf.Type == commentsType: // ignore comment assignment | |
268 | } | |
269 | } | |
270 | ||
271 | case reflect.Ptr, reflect.Interface: | |
272 | if v.IsNil() != w.IsNil() { | |
273 | if v.IsNil() { | |
274 | return eq.errorf("unexpected %s", w.Elem().Type()) | |
275 | } | |
276 | return eq.errorf("missing %s", v.Elem().Type()) | |
277 | } | |
278 | if err := eq.checkValue(v.Elem(), w.Elem()); err != nil { | |
279 | return err | |
280 | } | |
281 | } | |
282 | return nil | |
283 | } | |
284 | ||
285 | // diff returns the output of running diff on b1 and b2. | |
286 | func diff(b1, b2 []byte) (data []byte, err error) { | |
287 | f1, err := ioutil.TempFile("", "testdiff") | |
288 | if err != nil { | |
289 | return nil, err | |
290 | } | |
291 | defer os.Remove(f1.Name()) | |
292 | defer f1.Close() | |
293 | ||
294 | f2, err := ioutil.TempFile("", "testdiff") | |
295 | if err != nil { | |
296 | return nil, err | |
297 | } | |
298 | defer os.Remove(f2.Name()) | |
299 | defer f2.Close() | |
300 | ||
301 | f1.Write(b1) | |
302 | f2.Write(b2) | |
303 | ||
304 | data, err = exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput() | |
305 | if len(data) > 0 { | |
306 | // diff exits with a non-zero status when the files don't match. | |
307 | // Ignore that failure as long as we get output. | |
308 | err = nil | |
309 | } | |
310 | return | |
311 | } | |
312 | ||
313 | // tdiff logs the diff output to t.Error. | |
314 | func tdiff(t *testing.T, a, b string) { | |
315 | data, err := diff([]byte(a), []byte(b)) | |
316 | if err != nil { | |
317 | t.Error(err) | |
318 | return | |
319 | } | |
320 | t.Error(string(data)) | |
321 | } | |
322 | ||
323 | var modulePathTests = []struct { | |
324 | input []byte | |
325 | expected string | |
326 | }{ | |
327 | {input: []byte("module \"github.com/rsc/vgotest\""), expected: "github.com/rsc/vgotest"}, | |
328 | {input: []byte("module github.com/rsc/vgotest"), expected: "github.com/rsc/vgotest"}, | |
329 | {input: []byte("module \"github.com/rsc/vgotest\""), expected: "github.com/rsc/vgotest"}, | |
330 | {input: []byte("module github.com/rsc/vgotest"), expected: "github.com/rsc/vgotest"}, | |
331 | {input: []byte("module `github.com/rsc/vgotest`"), expected: "github.com/rsc/vgotest"}, | |
332 | {input: []byte("module \"github.com/rsc/vgotest/v2\""), expected: "github.com/rsc/vgotest/v2"}, | |
333 | {input: []byte("module github.com/rsc/vgotest/v2"), expected: "github.com/rsc/vgotest/v2"}, | |
334 | {input: []byte("module \"gopkg.in/yaml.v2\""), expected: "gopkg.in/yaml.v2"}, | |
335 | {input: []byte("module gopkg.in/yaml.v2"), expected: "gopkg.in/yaml.v2"}, | |
336 | {input: []byte("module \"gopkg.in/check.v1\"\n"), expected: "gopkg.in/check.v1"}, | |
337 | {input: []byte("module \"gopkg.in/check.v1\n\""), expected: ""}, | |
338 | {input: []byte("module gopkg.in/check.v1\n"), expected: "gopkg.in/check.v1"}, | |
339 | {input: []byte("module \"gopkg.in/check.v1\"\r\n"), expected: "gopkg.in/check.v1"}, | |
340 | {input: []byte("module gopkg.in/check.v1\r\n"), expected: "gopkg.in/check.v1"}, | |
341 | {input: []byte("module \"gopkg.in/check.v1\"\n\n"), expected: "gopkg.in/check.v1"}, | |
342 | {input: []byte("module gopkg.in/check.v1\n\n"), expected: "gopkg.in/check.v1"}, | |
343 | {input: []byte("module \n\"gopkg.in/check.v1\"\n\n"), expected: ""}, | |
344 | {input: []byte("module \ngopkg.in/check.v1\n\n"), expected: ""}, | |
345 | {input: []byte("module \"gopkg.in/check.v1\"asd"), expected: ""}, | |
346 | {input: []byte("module \n\"gopkg.in/check.v1\"\n\n"), expected: ""}, | |
347 | {input: []byte("module \ngopkg.in/check.v1\n\n"), expected: ""}, | |
348 | {input: []byte("module \"gopkg.in/check.v1\"asd"), expected: ""}, | |
349 | {input: []byte("module \nmodule a/b/c "), expected: "a/b/c"}, | |
350 | {input: []byte("module \" \""), expected: " "}, | |
351 | {input: []byte("module "), expected: ""}, | |
352 | {input: []byte("module \" a/b/c \""), expected: " a/b/c "}, | |
353 | {input: []byte("module \"github.com/rsc/vgotest1\" // with a comment"), expected: "github.com/rsc/vgotest1"}, | |
354 | } | |
355 | ||
356 | func TestModulePath(t *testing.T) { | |
357 | for _, test := range modulePathTests { | |
358 | t.Run(string(test.input), func(t *testing.T) { | |
359 | result := ModulePath(test.input) | |
360 | if result != test.expected { | |
361 | t.Fatalf("ModulePath(%q): %s, want %s", string(test.input), result, test.expected) | |
362 | } | |
363 | }) | |
364 | } | |
365 | } |