1
2
3
4
5 package modfile
6
7 import (
8 "bytes"
9 "fmt"
10 "os"
11 "os/exec"
12 "path/filepath"
13 "reflect"
14 "strings"
15 "testing"
16 )
17
18
19 func exists(name string) bool {
20 _, err := os.Stat(name)
21 return err == nil
22 }
23
24
25
26 func TestPrintGolden(t *testing.T) {
27 outs, err := filepath.Glob("testdata/*.golden")
28 if err != nil {
29 t.Fatal(err)
30 }
31 for _, out := range outs {
32 out := out
33 name := strings.TrimSuffix(filepath.Base(out), ".golden")
34 t.Run(name, func(t *testing.T) {
35 t.Parallel()
36 testPrint(t, out, out)
37 })
38 }
39 }
40
41
42
43
44 func testPrint(t *testing.T, in, out string) {
45 data, err := os.ReadFile(in)
46 if err != nil {
47 t.Error(err)
48 return
49 }
50
51 golden, err := os.ReadFile(out)
52 if err != nil {
53 t.Error(err)
54 return
55 }
56
57 base := "testdata/" + filepath.Base(in)
58 f, err := parse(in, data)
59 if err != nil {
60 t.Error(err)
61 return
62 }
63
64 ndata := Format(f)
65
66 if !bytes.Equal(ndata, golden) {
67 t.Errorf("formatted %s incorrectly: diff shows -golden, +ours", base)
68 tdiff(t, string(golden), string(ndata))
69 return
70 }
71 }
72
73
74
75
76 func TestParsePunctuation(t *testing.T) {
77 for _, test := range []struct {
78 desc, src, want string
79 }{
80 {"paren", "require ()", "require ( )"},
81 {"brackets", "require []{},", "require [ ] { } ,"},
82 {"mix", "require a[b]c{d}e,", "require a [ b ] c { d } e ,"},
83 {"block_mix", "require (\n\ta[b]\n)", "require ( a [ b ] )"},
84 {"interval", "require [v1.0.0, v1.1.0)", "require [ v1.0.0 , v1.1.0 )"},
85 } {
86 t.Run(test.desc, func(t *testing.T) {
87 f, err := parse("go.mod", []byte(test.src))
88 if err != nil {
89 t.Fatalf("parsing %q: %v", test.src, err)
90 }
91 var tokens []string
92 for _, stmt := range f.Stmt {
93 switch stmt := stmt.(type) {
94 case *Line:
95 tokens = append(tokens, stmt.Token...)
96 case *LineBlock:
97 tokens = append(tokens, stmt.Token...)
98 tokens = append(tokens, "(")
99 for _, line := range stmt.Line {
100 tokens = append(tokens, line.Token...)
101 }
102 tokens = append(tokens, ")")
103 default:
104 t.Fatalf("parsing %q: unexpected statement of type %T", test.src, stmt)
105 }
106 }
107 got := strings.Join(tokens, " ")
108 if got != test.want {
109 t.Errorf("parsing %q: got %q, want %q", test.src, got, test.want)
110 }
111 })
112 }
113 }
114
115 func TestParseLax(t *testing.T) {
116 badFile := []byte(`module m
117 surprise attack
118 x y (
119 z
120 )
121 exclude v1.2.3
122 replace <-!!!
123 retract v1.2.3 v1.2.4
124 retract (v1.2.3, v1.2.4]
125 retract v1.2.3 (
126 key1 value1
127 key2 value2
128 )
129 require good v1.0.0
130 `)
131 f, err := ParseLax("file", badFile, nil)
132 if err != nil {
133 t.Fatalf("ParseLax did not ignore irrelevant errors: %v", err)
134 }
135 if f.Module == nil || f.Module.Mod.Path != "m" {
136 t.Errorf("module directive was not parsed")
137 }
138 if len(f.Require) != 1 || f.Require[0].Mod.Path != "good" {
139 t.Errorf("require directive at end of file was not parsed")
140 }
141 }
142
143
144
145
146 func TestPrintParse(t *testing.T) {
147 outs, err := filepath.Glob("testdata/*")
148 if err != nil {
149 t.Fatal(err)
150 }
151 for _, out := range outs {
152 out := out
153 name := filepath.Base(out)
154 if !strings.HasSuffix(out, ".in") && !strings.HasSuffix(out, ".golden") {
155 continue
156 }
157 t.Run(name, func(t *testing.T) {
158 t.Parallel()
159 data, err := os.ReadFile(out)
160 if err != nil {
161 t.Fatal(err)
162 }
163
164 base := "testdata/" + filepath.Base(out)
165 f, err := parse(base, data)
166 if err != nil {
167 t.Fatalf("parsing original: %v", err)
168 }
169
170 ndata := Format(f)
171 f2, err := parse(base, ndata)
172 if err != nil {
173 t.Fatalf("parsing reformatted: %v", err)
174 }
175
176 eq := eqchecker{file: base}
177 if err := eq.check(f, f2); err != nil {
178 t.Errorf("not equal (parse/Format/parse): %v", err)
179 }
180
181 pf1, err := Parse(base, data, nil)
182 if err != nil {
183 switch base {
184 case "testdata/block.golden",
185 "testdata/block.in",
186 "testdata/comment.golden",
187 "testdata/comment.in",
188 "testdata/rule1.golden":
189
190 default:
191 t.Errorf("should parse %v: %v", base, err)
192 }
193 }
194 if err == nil {
195 pf2, err := Parse(base, ndata, nil)
196 if err != nil {
197 t.Fatalf("Parsing reformatted: %v", err)
198 }
199 eq := eqchecker{file: base}
200 if err := eq.check(pf1, pf2); err != nil {
201 t.Errorf("not equal (parse/Format/Parse): %v", err)
202 }
203
204 ndata2, err := pf1.Format()
205 if err != nil {
206 t.Errorf("reformat: %v", err)
207 }
208 pf3, err := Parse(base, ndata2, nil)
209 if err != nil {
210 t.Fatalf("Parsing reformatted2: %v", err)
211 }
212 eq = eqchecker{file: base}
213 if err := eq.check(pf1, pf3); err != nil {
214 t.Errorf("not equal (Parse/Format/Parse): %v", err)
215 }
216 ndata = ndata2
217 }
218
219 if strings.HasSuffix(out, ".in") {
220 golden, err := os.ReadFile(strings.TrimSuffix(out, ".in") + ".golden")
221 if err != nil {
222 t.Fatal(err)
223 }
224 if !bytes.Equal(ndata, golden) {
225 t.Errorf("formatted %s incorrectly: diff shows -golden, +ours", base)
226 tdiff(t, string(golden), string(ndata))
227 return
228 }
229 }
230 })
231 }
232 }
233
234
235 type eqchecker struct {
236 file string
237 pos Position
238 }
239
240
241
242 func (eq *eqchecker) errorf(format string, args ...interface{}) error {
243 return fmt.Errorf("%s:%d: %s", eq.file, eq.pos.Line,
244 fmt.Sprintf(format, args...))
245 }
246
247
248
249 func (eq *eqchecker) check(v, w interface{}) error {
250 return eq.checkValue(reflect.ValueOf(v), reflect.ValueOf(w))
251 }
252
253 var (
254 posType = reflect.TypeOf(Position{})
255 commentsType = reflect.TypeOf(Comments{})
256 )
257
258
259
260 func (eq *eqchecker) checkValue(v, w reflect.Value) error {
261
262
263
264 inner := func(v reflect.Value) reflect.Value {
265 for {
266 if v.Kind() == reflect.Interface && !v.IsNil() {
267 v = v.Elem()
268 continue
269 }
270 break
271 }
272 return v
273 }
274
275 v = inner(v)
276 w = inner(w)
277 if v.Kind() == reflect.Invalid && w.Kind() == reflect.Invalid {
278 return nil
279 }
280 if v.Kind() == reflect.Invalid {
281 return eq.errorf("nil interface became %s", w.Type())
282 }
283 if w.Kind() == reflect.Invalid {
284 return eq.errorf("%s became nil interface", v.Type())
285 }
286
287 if v.Type() != w.Type() {
288 return eq.errorf("%s became %s", v.Type(), w.Type())
289 }
290
291 if p, ok := v.Interface().(Expr); ok {
292 eq.pos, _ = p.Span()
293 }
294
295 switch v.Kind() {
296 default:
297 return eq.errorf("unexpected type %s", v.Type())
298
299 case reflect.Bool, reflect.Int, reflect.String:
300 vi := v.Interface()
301 wi := w.Interface()
302 if vi != wi {
303 return eq.errorf("%v became %v", vi, wi)
304 }
305
306 case reflect.Slice:
307 vl := v.Len()
308 wl := w.Len()
309 for i := 0; i < vl || i < wl; i++ {
310 if i >= vl {
311 return eq.errorf("unexpected %s", w.Index(i).Type())
312 }
313 if i >= wl {
314 return eq.errorf("missing %s", v.Index(i).Type())
315 }
316 if err := eq.checkValue(v.Index(i), w.Index(i)); err != nil {
317 return err
318 }
319 }
320
321 case reflect.Struct:
322
323 t := v.Type()
324 n := t.NumField()
325 for i := 0; i < n; i++ {
326 tf := t.Field(i)
327 switch {
328 default:
329 if err := eq.checkValue(v.Field(i), w.Field(i)); err != nil {
330 return err
331 }
332
333 case tf.Type == posType:
334 case tf.Type == commentsType:
335 }
336 }
337
338 case reflect.Ptr, reflect.Interface:
339 if v.IsNil() != w.IsNil() {
340 if v.IsNil() {
341 return eq.errorf("unexpected %s", w.Elem().Type())
342 }
343 return eq.errorf("missing %s", v.Elem().Type())
344 }
345 if err := eq.checkValue(v.Elem(), w.Elem()); err != nil {
346 return err
347 }
348 }
349 return nil
350 }
351
352
353 func diff(b1, b2 []byte) (data []byte, err error) {
354 f1, err := os.CreateTemp("", "testdiff")
355 if err != nil {
356 return nil, err
357 }
358 defer os.Remove(f1.Name())
359 defer f1.Close()
360
361 f2, err := os.CreateTemp("", "testdiff")
362 if err != nil {
363 return nil, err
364 }
365 defer os.Remove(f2.Name())
366 defer f2.Close()
367
368 f1.Write(b1)
369 f2.Write(b2)
370
371 data, err = exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput()
372 if len(data) > 0 {
373
374
375 err = nil
376 }
377 return
378 }
379
380
381 func tdiff(t *testing.T, a, b string) {
382 data, err := diff([]byte(a), []byte(b))
383 if err != nil {
384 t.Error(err)
385 return
386 }
387 t.Error(string(data))
388 }
389
390 var modulePathTests = []struct {
391 input []byte
392 expected string
393 }{
394 {input: []byte("module \"github.com/rsc/vgotest\""), expected: "github.com/rsc/vgotest"},
395 {input: []byte("module github.com/rsc/vgotest"), expected: "github.com/rsc/vgotest"},
396 {input: []byte("module \"github.com/rsc/vgotest\""), expected: "github.com/rsc/vgotest"},
397 {input: []byte("module github.com/rsc/vgotest"), expected: "github.com/rsc/vgotest"},
398 {input: []byte("module `github.com/rsc/vgotest`"), expected: "github.com/rsc/vgotest"},
399 {input: []byte("module \"github.com/rsc/vgotest/v2\""), expected: "github.com/rsc/vgotest/v2"},
400 {input: []byte("module github.com/rsc/vgotest/v2"), expected: "github.com/rsc/vgotest/v2"},
401 {input: []byte("module \"gopkg.in/yaml.v2\""), expected: "gopkg.in/yaml.v2"},
402 {input: []byte("module gopkg.in/yaml.v2"), expected: "gopkg.in/yaml.v2"},
403 {input: []byte("module \"gopkg.in/check.v1\"\n"), expected: "gopkg.in/check.v1"},
404 {input: []byte("module \"gopkg.in/check.v1\n\""), expected: ""},
405 {input: []byte("module gopkg.in/check.v1\n"), expected: "gopkg.in/check.v1"},
406 {input: []byte("module \"gopkg.in/check.v1\"\r\n"), expected: "gopkg.in/check.v1"},
407 {input: []byte("module gopkg.in/check.v1\r\n"), expected: "gopkg.in/check.v1"},
408 {input: []byte("module \"gopkg.in/check.v1\"\n\n"), expected: "gopkg.in/check.v1"},
409 {input: []byte("module gopkg.in/check.v1\n\n"), expected: "gopkg.in/check.v1"},
410 {input: []byte("module \n\"gopkg.in/check.v1\"\n\n"), expected: ""},
411 {input: []byte("module \ngopkg.in/check.v1\n\n"), expected: ""},
412 {input: []byte("module \"gopkg.in/check.v1\"asd"), expected: ""},
413 {input: []byte("module \n\"gopkg.in/check.v1\"\n\n"), expected: ""},
414 {input: []byte("module \ngopkg.in/check.v1\n\n"), expected: ""},
415 {input: []byte("module \"gopkg.in/check.v1\"asd"), expected: ""},
416 {input: []byte("module \nmodule a/b/c "), expected: "a/b/c"},
417 {input: []byte("module \" \""), expected: " "},
418 {input: []byte("module "), expected: ""},
419 {input: []byte("module \" a/b/c \""), expected: " a/b/c "},
420 {input: []byte("module \"github.com/rsc/vgotest1\" // with a comment"), expected: "github.com/rsc/vgotest1"},
421 }
422
423 func TestModulePath(t *testing.T) {
424 for _, test := range modulePathTests {
425 t.Run(string(test.input), func(t *testing.T) {
426 result := ModulePath(test.input)
427 if result != test.expected {
428 t.Fatalf("ModulePath(%q): %s, want %s", string(test.input), result, test.expected)
429 }
430 })
431 }
432 }
433
434 func TestParseVersions(t *testing.T) {
435 tests := []struct {
436 desc, input string
437 ok bool
438 laxOK bool
439 }{
440
441 {desc: "empty", input: "module m\ngo \n", ok: false},
442 {desc: "one", input: "module m\ngo 1\n", ok: false},
443 {desc: "two", input: "module m\ngo 1.22\n", ok: true},
444 {desc: "three", input: "module m\ngo 1.22.333", ok: true},
445 {desc: "before", input: "module m\ngo v1.2\n", ok: false},
446 {desc: "after", input: "module m\ngo 1.2rc1\n", ok: true},
447 {desc: "space", input: "module m\ngo 1.2 3.4\n", ok: false},
448 {desc: "alt1", input: "module m\ngo 1.2.3\n", ok: true},
449 {desc: "alt2", input: "module m\ngo 1.2rc1\n", ok: true},
450 {desc: "alt3", input: "module m\ngo 1.2beta1\n", ok: true},
451 {desc: "alt4", input: "module m\ngo 1.2.beta1\n", ok: false, laxOK: true},
452 {desc: "alt1", input: "module m\ngo v1.2.3\n", ok: false, laxOK: true},
453 {desc: "alt2", input: "module m\ngo v1.2rc1\n", ok: false, laxOK: true},
454 {desc: "alt3", input: "module m\ngo v1.2beta1\n", ok: false, laxOK: true},
455 {desc: "alt4", input: "module m\ngo v1.2.beta1\n", ok: false, laxOK: true},
456 {desc: "alt1", input: "module m\ngo v1.2\n", ok: false, laxOK: true},
457
458
459 {desc: "tool", input: "module m\ntoolchain go1.2\n", ok: true},
460 {desc: "tool1", input: "module m\ntoolchain go1.2.3\n", ok: true},
461 {desc: "tool2", input: "module m\ntoolchain go1.2rc1\n", ok: true},
462 {desc: "tool3", input: "module m\ntoolchain go1.2rc1-gccgo\n", ok: true},
463 {desc: "tool4", input: "module m\ntoolchain default\n", ok: true},
464 {desc: "tool5", input: "module m\ntoolchain inconceivable!\n", ok: false, laxOK: true},
465 }
466 t.Run("Strict", func(t *testing.T) {
467 for _, test := range tests {
468 t.Run(test.desc, func(t *testing.T) {
469 if _, err := Parse("go.mod", []byte(test.input), nil); err == nil && !test.ok {
470 t.Error("unexpected success")
471 } else if err != nil && test.ok {
472 t.Errorf("unexpected error: %v", err)
473 }
474 })
475 }
476 })
477 t.Run("Lax", func(t *testing.T) {
478 for _, test := range tests {
479 t.Run(test.desc, func(t *testing.T) {
480 if _, err := ParseLax("go.mod", []byte(test.input), nil); err == nil && !(test.ok || test.laxOK) {
481 t.Error("unexpected success")
482 } else if err != nil && test.ok {
483 t.Errorf("unexpected error: %v", err)
484 }
485 })
486 }
487 })
488 }
489
490 func TestComments(t *testing.T) {
491 for _, test := range []struct {
492 desc, input, want string
493 }{
494 {
495 desc: "comment_only",
496 input: `
497 // a
498 // b
499 `,
500 want: `
501 comments before "// a"
502 comments before "// b"
503 `,
504 }, {
505 desc: "line",
506 input: `
507 // a
508
509 // b
510 module m // c
511 // d
512
513 // e
514 `,
515 want: `
516 comments before "// a"
517 line before "// b"
518 line suffix "// c"
519 comments before "// d"
520 comments before "// e"
521 `,
522 }, {
523 desc: "block",
524 input: `
525 // a
526
527 // b
528 block ( // c
529 // d
530
531 // e
532 x // f
533 // g
534
535 // h
536 ) // i
537 // j
538
539 // k
540 `,
541 want: `
542 comments before "// a"
543 block before "// b"
544 lparen suffix "// c"
545 blockline before "// d"
546 blockline before ""
547 blockline before "// e"
548 blockline suffix "// f"
549 rparen before "// g"
550 rparen before ""
551 rparen before "// h"
552 rparen suffix "// i"
553 comments before "// j"
554 comments before "// k"
555 `,
556 }, {
557 desc: "cr_removed",
558 input: "// a\r\r\n",
559 want: `comments before "// a\r"`,
560 },
561 } {
562 t.Run(test.desc, func(t *testing.T) {
563 f, err := ParseLax("go.mod", []byte(test.input), nil)
564 if err != nil {
565 t.Fatal(err)
566 }
567
568 buf := &bytes.Buffer{}
569 printComments := func(prefix string, cs *Comments) {
570 for _, c := range cs.Before {
571 fmt.Fprintf(buf, "%s before %q\n", prefix, c.Token)
572 }
573 for _, c := range cs.Suffix {
574 fmt.Fprintf(buf, "%s suffix %q\n", prefix, c.Token)
575 }
576 for _, c := range cs.After {
577 fmt.Fprintf(buf, "%s after %q\n", prefix, c.Token)
578 }
579 }
580
581 printComments("file", &f.Syntax.Comments)
582 for _, stmt := range f.Syntax.Stmt {
583 switch stmt := stmt.(type) {
584 case *CommentBlock:
585 printComments("comments", stmt.Comment())
586 case *Line:
587 printComments("line", stmt.Comment())
588 case *LineBlock:
589 printComments("block", stmt.Comment())
590 printComments("lparen", stmt.LParen.Comment())
591 for _, line := range stmt.Line {
592 printComments("blockline", line.Comment())
593 }
594 printComments("rparen", stmt.RParen.Comment())
595 }
596 }
597
598 got := strings.TrimSpace(buf.String())
599 want := strings.TrimSpace(test.want)
600 if got != want {
601 t.Errorf("got:\n%s\nwant:\n%s", got, want)
602 }
603 })
604 }
605 }
606
View as plain text