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.
19 // exists reports whether the named file exists.
20 func exists(name string) bool {
21 _, err := os.Stat(name)
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")
32 for _, out := range outs {
34 name := strings.TrimSuffix(filepath.Base(out), ".golden")
35 t.Run(name, func(t *testing.T) {
37 testPrint(t, out, out)
42 // testPrint is a helper for testing the printer.
43 // It reads the file named in, reformats it, and compares
44 // the result to the file named out.
45 func testPrint(t *testing.T, in, out string) {
46 data, err := ioutil.ReadFile(in)
52 golden, err := ioutil.ReadFile(out)
58 base := "testdata/" + filepath.Base(in)
59 f, err := parse(in, data)
67 if !bytes.Equal(ndata, golden) {
68 t.Errorf("formatted %s incorrectly: diff shows -golden, +ours", base)
69 tdiff(t, string(golden), string(ndata))
74 // TestParsePunctuation verifies that certain ASCII punctuation characters
75 // (brackets, commas) are lexed as separate tokens, even when they're
76 // surrounded by identifier characters.
77 func TestParsePunctuation(t *testing.T) {
78 for _, test := range []struct {
79 desc, src, want string
81 {"paren", "require ()", "require ( )"},
82 {"brackets", "require []{},", "require [ ] { } ,"},
83 {"mix", "require a[b]c{d}e,", "require a [ b ] c { d } e ,"},
84 {"block_mix", "require (\n\ta[b]\n)", "require ( a [ b ] )"},
85 {"interval", "require [v1.0.0, v1.1.0)", "require [ v1.0.0 , v1.1.0 )"},
87 t.Run(test.desc, func(t *testing.T) {
88 f, err := parse("go.mod", []byte(test.src))
90 t.Fatalf("parsing %q: %v", test.src, err)
93 for _, stmt := range f.Stmt {
94 switch stmt := stmt.(type) {
96 tokens = append(tokens, stmt.Token...)
98 tokens = append(tokens, stmt.Token...)
99 tokens = append(tokens, "(")
100 for _, line := range stmt.Line {
101 tokens = append(tokens, line.Token...)
103 tokens = append(tokens, ")")
105 t.Fatalf("parsing %q: unexpected statement of type %T", test.src, stmt)
108 got := strings.Join(tokens, " ")
109 if got != test.want {
110 t.Errorf("parsing %q: got %q, want %q", test.src, got, test.want)
116 func TestParseLax(t *testing.T) {
117 badFile := []byte(`module m
124 retract v1.2.3 v1.2.4
125 retract (v1.2.3, v1.2.4]
132 f, err := ParseLax("file", badFile, nil)
134 t.Fatalf("ParseLax did not ignore irrelevant errors: %v", err)
136 if f.Module == nil || f.Module.Mod.Path != "m" {
137 t.Errorf("module directive was not parsed")
139 if len(f.Require) != 1 || f.Require[0].Mod.Path != "good" {
140 t.Errorf("require directive at end of file was not parsed")
144 // Test that when files in the testdata directory are parsed
145 // and printed and parsed again, we get the same parse tree
147 func TestPrintParse(t *testing.T) {
148 outs, err := filepath.Glob("testdata/*")
152 for _, out := range outs {
154 name := filepath.Base(out)
155 t.Run(name, func(t *testing.T) {
157 data, err := ioutil.ReadFile(out)
162 base := "testdata/" + filepath.Base(out)
163 f, err := parse(base, data)
165 t.Fatalf("parsing original: %v", err)
169 f2, err := parse(base, ndata)
171 t.Fatalf("parsing reformatted: %v", err)
174 eq := eqchecker{file: base}
175 if err := eq.check(f, f2); err != nil {
176 t.Errorf("not equal (parse/Format/parse): %v", err)
179 pf1, err := Parse(base, data, nil)
182 case "testdata/replace2.in", "testdata/gopkg.in.golden":
183 t.Errorf("should parse %v: %v", base, err)
187 pf2, err := Parse(base, ndata, nil)
189 t.Fatalf("Parsing reformatted: %v", err)
191 eq := eqchecker{file: base}
192 if err := eq.check(pf1, pf2); err != nil {
193 t.Errorf("not equal (parse/Format/Parse): %v", err)
196 ndata2, err := pf1.Format()
198 t.Errorf("reformat: %v", err)
200 pf3, err := Parse(base, ndata2, nil)
202 t.Fatalf("Parsing reformatted2: %v", err)
204 eq = eqchecker{file: base}
205 if err := eq.check(pf1, pf3); err != nil {
206 t.Errorf("not equal (Parse/Format/Parse): %v", err)
211 if strings.HasSuffix(out, ".in") {
212 golden, err := ioutil.ReadFile(strings.TrimSuffix(out, ".in") + ".golden")
216 if !bytes.Equal(ndata, golden) {
217 t.Errorf("formatted %s incorrectly: diff shows -golden, +ours", base)
218 tdiff(t, string(golden), string(ndata))
226 // An eqchecker holds state for checking the equality of two parse trees.
227 type eqchecker struct {
232 // errorf returns an error described by the printf-style format and arguments,
233 // inserting the current file position before the error text.
234 func (eq *eqchecker) errorf(format string, args ...interface{}) error {
235 return fmt.Errorf("%s:%d: %s", eq.file, eq.pos.Line,
236 fmt.Sprintf(format, args...))
239 // check checks that v and w represent the same parse tree.
240 // If not, it returns an error describing the first difference.
241 func (eq *eqchecker) check(v, w interface{}) error {
242 return eq.checkValue(reflect.ValueOf(v), reflect.ValueOf(w))
246 posType = reflect.TypeOf(Position{})
247 commentsType = reflect.TypeOf(Comments{})
250 // checkValue checks that v and w represent the same parse tree.
251 // If not, it returns an error describing the first difference.
252 func (eq *eqchecker) checkValue(v, w reflect.Value) error {
253 // inner returns the innermost expression for v.
254 // if v is a non-nil interface value, it returns the concrete
255 // value in the interface.
256 inner := func(v reflect.Value) reflect.Value {
258 if v.Kind() == reflect.Interface && !v.IsNil() {
269 if v.Kind() == reflect.Invalid && w.Kind() == reflect.Invalid {
272 if v.Kind() == reflect.Invalid {
273 return eq.errorf("nil interface became %s", w.Type())
275 if w.Kind() == reflect.Invalid {
276 return eq.errorf("%s became nil interface", v.Type())
279 if v.Type() != w.Type() {
280 return eq.errorf("%s became %s", v.Type(), w.Type())
283 if p, ok := v.Interface().(Expr); ok {
289 return eq.errorf("unexpected type %s", v.Type())
291 case reflect.Bool, reflect.Int, reflect.String:
295 return eq.errorf("%v became %v", vi, wi)
301 for i := 0; i < vl || i < wl; i++ {
303 return eq.errorf("unexpected %s", w.Index(i).Type())
306 return eq.errorf("missing %s", v.Index(i).Type())
308 if err := eq.checkValue(v.Index(i), w.Index(i)); err != nil {
314 // Fields in struct must match.
317 for i := 0; i < n; i++ {
321 if err := eq.checkValue(v.Field(i), w.Field(i)); err != nil {
325 case tf.Type == posType: // ignore positions
326 case tf.Type == commentsType: // ignore comment assignment
330 case reflect.Ptr, reflect.Interface:
331 if v.IsNil() != w.IsNil() {
333 return eq.errorf("unexpected %s", w.Elem().Type())
335 return eq.errorf("missing %s", v.Elem().Type())
337 if err := eq.checkValue(v.Elem(), w.Elem()); err != nil {
344 // diff returns the output of running diff on b1 and b2.
345 func diff(b1, b2 []byte) (data []byte, err error) {
346 f1, err := ioutil.TempFile("", "testdiff")
350 defer os.Remove(f1.Name())
353 f2, err := ioutil.TempFile("", "testdiff")
357 defer os.Remove(f2.Name())
363 data, err = exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput()
365 // diff exits with a non-zero status when the files don't match.
366 // Ignore that failure as long as we get output.
372 // tdiff logs the diff output to t.Error.
373 func tdiff(t *testing.T, a, b string) {
374 data, err := diff([]byte(a), []byte(b))
379 t.Error(string(data))
382 var modulePathTests = []struct {
386 {input: []byte("module \"github.com/rsc/vgotest\""), expected: "github.com/rsc/vgotest"},
387 {input: []byte("module github.com/rsc/vgotest"), expected: "github.com/rsc/vgotest"},
388 {input: []byte("module \"github.com/rsc/vgotest\""), expected: "github.com/rsc/vgotest"},
389 {input: []byte("module github.com/rsc/vgotest"), expected: "github.com/rsc/vgotest"},
390 {input: []byte("module `github.com/rsc/vgotest`"), expected: "github.com/rsc/vgotest"},
391 {input: []byte("module \"github.com/rsc/vgotest/v2\""), expected: "github.com/rsc/vgotest/v2"},
392 {input: []byte("module github.com/rsc/vgotest/v2"), expected: "github.com/rsc/vgotest/v2"},
393 {input: []byte("module \"gopkg.in/yaml.v2\""), expected: "gopkg.in/yaml.v2"},
394 {input: []byte("module gopkg.in/yaml.v2"), expected: "gopkg.in/yaml.v2"},
395 {input: []byte("module \"gopkg.in/check.v1\"\n"), expected: "gopkg.in/check.v1"},
396 {input: []byte("module \"gopkg.in/check.v1\n\""), expected: ""},
397 {input: []byte("module gopkg.in/check.v1\n"), expected: "gopkg.in/check.v1"},
398 {input: []byte("module \"gopkg.in/check.v1\"\r\n"), expected: "gopkg.in/check.v1"},
399 {input: []byte("module gopkg.in/check.v1\r\n"), expected: "gopkg.in/check.v1"},
400 {input: []byte("module \"gopkg.in/check.v1\"\n\n"), expected: "gopkg.in/check.v1"},
401 {input: []byte("module gopkg.in/check.v1\n\n"), expected: "gopkg.in/check.v1"},
402 {input: []byte("module \n\"gopkg.in/check.v1\"\n\n"), expected: ""},
403 {input: []byte("module \ngopkg.in/check.v1\n\n"), expected: ""},
404 {input: []byte("module \"gopkg.in/check.v1\"asd"), expected: ""},
405 {input: []byte("module \n\"gopkg.in/check.v1\"\n\n"), expected: ""},
406 {input: []byte("module \ngopkg.in/check.v1\n\n"), expected: ""},
407 {input: []byte("module \"gopkg.in/check.v1\"asd"), expected: ""},
408 {input: []byte("module \nmodule a/b/c "), expected: "a/b/c"},
409 {input: []byte("module \" \""), expected: " "},
410 {input: []byte("module "), expected: ""},
411 {input: []byte("module \" a/b/c \""), expected: " a/b/c "},
412 {input: []byte("module \"github.com/rsc/vgotest1\" // with a comment"), expected: "github.com/rsc/vgotest1"},
415 func TestModulePath(t *testing.T) {
416 for _, test := range modulePathTests {
417 t.Run(string(test.input), func(t *testing.T) {
418 result := ModulePath(test.input)
419 if result != test.expected {
420 t.Fatalf("ModulePath(%q): %s, want %s", string(test.input), result, test.expected)
426 func TestGoVersion(t *testing.T) {
427 for _, test := range []struct {
431 {desc: "empty", input: "module m\ngo \n", ok: false},
432 {desc: "one", input: "module m\ngo 1\n", ok: false},
433 {desc: "two", input: "module m\ngo 1.22\n", ok: true},
434 {desc: "three", input: "module m\ngo 1.22.333", ok: false},
435 {desc: "before", input: "module m\ngo v1.2\n", ok: false},
436 {desc: "after", input: "module m\ngo 1.2rc1\n", ok: false},
437 {desc: "space", input: "module m\ngo 1.2 3.4\n", ok: false},
439 t.Run(test.desc, func(t *testing.T) {
440 if _, err := Parse("go.mod", []byte(test.input), nil); err == nil && !test.ok {
441 t.Error("unexpected success")
442 } else if err != nil && test.ok {
443 t.Errorf("unexpected error: %v", err)