diff options
Diffstat (limited to 'libgo/go/cmd/go/internal/modfile/read_test.go')
-rw-r--r-- | libgo/go/cmd/go/internal/modfile/read_test.go | 365 |
1 files changed, 365 insertions, 0 deletions
diff --git a/libgo/go/cmd/go/internal/modfile/read_test.go b/libgo/go/cmd/go/internal/modfile/read_test.go new file mode 100644 index 00000000000..8cb1a3908c5 --- /dev/null +++ b/libgo/go/cmd/go/internal/modfile/read_test.go @@ -0,0 +1,365 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modfile + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "reflect" + "strings" + "testing" +) + +// exists reports whether the named file exists. +func exists(name string) bool { + _, err := os.Stat(name) + return err == nil +} + +// Test that reading and then writing the golden files +// does not change their output. +func TestPrintGolden(t *testing.T) { + outs, err := filepath.Glob("testdata/*.golden") + if err != nil { + t.Fatal(err) + } + for _, out := range outs { + testPrint(t, out, out) + } +} + +// testPrint is a helper for testing the printer. +// It reads the file named in, reformats it, and compares +// the result to the file named out. +func testPrint(t *testing.T, in, out string) { + data, err := ioutil.ReadFile(in) + if err != nil { + t.Error(err) + return + } + + golden, err := ioutil.ReadFile(out) + if err != nil { + t.Error(err) + return + } + + base := "testdata/" + filepath.Base(in) + f, err := parse(in, data) + if err != nil { + t.Error(err) + return + } + + ndata := Format(f) + + if !bytes.Equal(ndata, golden) { + t.Errorf("formatted %s incorrectly: diff shows -golden, +ours", base) + tdiff(t, string(golden), string(ndata)) + return + } +} + +func TestParseLax(t *testing.T) { + badFile := []byte(`module m + surprise attack + x y ( + z + ) + exclude v1.2.3 + replace <-!!! + `) + _, err := ParseLax("file", badFile, nil) + if err != nil { + t.Fatalf("ParseLax did not ignore irrelevant errors: %v", err) + } +} + +// Test that when files in the testdata directory are parsed +// and printed and parsed again, we get the same parse tree +// both times. +func TestPrintParse(t *testing.T) { + outs, err := filepath.Glob("testdata/*") + if err != nil { + t.Fatal(err) + } + for _, out := range outs { + data, err := ioutil.ReadFile(out) + if err != nil { + t.Error(err) + continue + } + + base := "testdata/" + filepath.Base(out) + f, err := parse(base, data) + if err != nil { + t.Errorf("parsing original: %v", err) + continue + } + + ndata := Format(f) + f2, err := parse(base, ndata) + if err != nil { + t.Errorf("parsing reformatted: %v", err) + continue + } + + eq := eqchecker{file: base} + if err := eq.check(f, f2); err != nil { + t.Errorf("not equal (parse/Format/parse): %v", err) + } + + pf1, err := Parse(base, data, nil) + if err != nil { + switch base { + case "testdata/replace2.in", "testdata/gopkg.in.golden": + t.Errorf("should parse %v: %v", base, err) + } + } + if err == nil { + pf2, err := Parse(base, ndata, nil) + if err != nil { + t.Errorf("Parsing reformatted: %v", err) + continue + } + eq := eqchecker{file: base} + if err := eq.check(pf1, pf2); err != nil { + t.Errorf("not equal (parse/Format/Parse): %v", err) + } + + ndata2, err := pf1.Format() + if err != nil { + t.Errorf("reformat: %v", err) + } + pf3, err := Parse(base, ndata2, nil) + if err != nil { + t.Errorf("Parsing reformatted2: %v", err) + continue + } + eq = eqchecker{file: base} + if err := eq.check(pf1, pf3); err != nil { + t.Errorf("not equal (Parse/Format/Parse): %v", err) + } + ndata = ndata2 + } + + if strings.HasSuffix(out, ".in") { + golden, err := ioutil.ReadFile(strings.TrimSuffix(out, ".in") + ".golden") + if err != nil { + t.Error(err) + continue + } + if !bytes.Equal(ndata, golden) { + t.Errorf("formatted %s incorrectly: diff shows -golden, +ours", base) + tdiff(t, string(golden), string(ndata)) + return + } + } + } +} + +// An eqchecker holds state for checking the equality of two parse trees. +type eqchecker struct { + file string + pos Position +} + +// errorf returns an error described by the printf-style format and arguments, +// inserting the current file position before the error text. +func (eq *eqchecker) errorf(format string, args ...interface{}) error { + return fmt.Errorf("%s:%d: %s", eq.file, eq.pos.Line, + fmt.Sprintf(format, args...)) +} + +// check checks that v and w represent the same parse tree. +// If not, it returns an error describing the first difference. +func (eq *eqchecker) check(v, w interface{}) error { + return eq.checkValue(reflect.ValueOf(v), reflect.ValueOf(w)) +} + +var ( + posType = reflect.TypeOf(Position{}) + commentsType = reflect.TypeOf(Comments{}) +) + +// checkValue checks that v and w represent the same parse tree. +// If not, it returns an error describing the first difference. +func (eq *eqchecker) checkValue(v, w reflect.Value) error { + // inner returns the innermost expression for v. + // if v is a non-nil interface value, it returns the concrete + // value in the interface. + inner := func(v reflect.Value) reflect.Value { + for { + if v.Kind() == reflect.Interface && !v.IsNil() { + v = v.Elem() + continue + } + break + } + return v + } + + v = inner(v) + w = inner(w) + if v.Kind() == reflect.Invalid && w.Kind() == reflect.Invalid { + return nil + } + if v.Kind() == reflect.Invalid { + return eq.errorf("nil interface became %s", w.Type()) + } + if w.Kind() == reflect.Invalid { + return eq.errorf("%s became nil interface", v.Type()) + } + + if v.Type() != w.Type() { + return eq.errorf("%s became %s", v.Type(), w.Type()) + } + + if p, ok := v.Interface().(Expr); ok { + eq.pos, _ = p.Span() + } + + switch v.Kind() { + default: + return eq.errorf("unexpected type %s", v.Type()) + + case reflect.Bool, reflect.Int, reflect.String: + vi := v.Interface() + wi := w.Interface() + if vi != wi { + return eq.errorf("%v became %v", vi, wi) + } + + case reflect.Slice: + vl := v.Len() + wl := w.Len() + for i := 0; i < vl || i < wl; i++ { + if i >= vl { + return eq.errorf("unexpected %s", w.Index(i).Type()) + } + if i >= wl { + return eq.errorf("missing %s", v.Index(i).Type()) + } + if err := eq.checkValue(v.Index(i), w.Index(i)); err != nil { + return err + } + } + + case reflect.Struct: + // Fields in struct must match. + t := v.Type() + n := t.NumField() + for i := 0; i < n; i++ { + tf := t.Field(i) + switch { + default: + if err := eq.checkValue(v.Field(i), w.Field(i)); err != nil { + return err + } + + case tf.Type == posType: // ignore positions + case tf.Type == commentsType: // ignore comment assignment + } + } + + case reflect.Ptr, reflect.Interface: + if v.IsNil() != w.IsNil() { + if v.IsNil() { + return eq.errorf("unexpected %s", w.Elem().Type()) + } + return eq.errorf("missing %s", v.Elem().Type()) + } + if err := eq.checkValue(v.Elem(), w.Elem()); err != nil { + return err + } + } + return nil +} + +// diff returns the output of running diff on b1 and b2. +func diff(b1, b2 []byte) (data []byte, err error) { + f1, err := ioutil.TempFile("", "testdiff") + if err != nil { + return nil, err + } + defer os.Remove(f1.Name()) + defer f1.Close() + + f2, err := ioutil.TempFile("", "testdiff") + if err != nil { + return nil, err + } + defer os.Remove(f2.Name()) + defer f2.Close() + + f1.Write(b1) + f2.Write(b2) + + data, err = exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput() + if len(data) > 0 { + // diff exits with a non-zero status when the files don't match. + // Ignore that failure as long as we get output. + err = nil + } + return +} + +// tdiff logs the diff output to t.Error. +func tdiff(t *testing.T, a, b string) { + data, err := diff([]byte(a), []byte(b)) + if err != nil { + t.Error(err) + return + } + t.Error(string(data)) +} + +var modulePathTests = []struct { + input []byte + expected string +}{ + {input: []byte("module \"github.com/rsc/vgotest\""), expected: "github.com/rsc/vgotest"}, + {input: []byte("module github.com/rsc/vgotest"), expected: "github.com/rsc/vgotest"}, + {input: []byte("module \"github.com/rsc/vgotest\""), expected: "github.com/rsc/vgotest"}, + {input: []byte("module github.com/rsc/vgotest"), expected: "github.com/rsc/vgotest"}, + {input: []byte("module `github.com/rsc/vgotest`"), expected: "github.com/rsc/vgotest"}, + {input: []byte("module \"github.com/rsc/vgotest/v2\""), expected: "github.com/rsc/vgotest/v2"}, + {input: []byte("module github.com/rsc/vgotest/v2"), expected: "github.com/rsc/vgotest/v2"}, + {input: []byte("module \"gopkg.in/yaml.v2\""), expected: "gopkg.in/yaml.v2"}, + {input: []byte("module gopkg.in/yaml.v2"), expected: "gopkg.in/yaml.v2"}, + {input: []byte("module \"gopkg.in/check.v1\"\n"), expected: "gopkg.in/check.v1"}, + {input: []byte("module \"gopkg.in/check.v1\n\""), expected: ""}, + {input: []byte("module gopkg.in/check.v1\n"), expected: "gopkg.in/check.v1"}, + {input: []byte("module \"gopkg.in/check.v1\"\r\n"), expected: "gopkg.in/check.v1"}, + {input: []byte("module gopkg.in/check.v1\r\n"), expected: "gopkg.in/check.v1"}, + {input: []byte("module \"gopkg.in/check.v1\"\n\n"), expected: "gopkg.in/check.v1"}, + {input: []byte("module gopkg.in/check.v1\n\n"), expected: "gopkg.in/check.v1"}, + {input: []byte("module \n\"gopkg.in/check.v1\"\n\n"), expected: ""}, + {input: []byte("module \ngopkg.in/check.v1\n\n"), expected: ""}, + {input: []byte("module \"gopkg.in/check.v1\"asd"), expected: ""}, + {input: []byte("module \n\"gopkg.in/check.v1\"\n\n"), expected: ""}, + {input: []byte("module \ngopkg.in/check.v1\n\n"), expected: ""}, + {input: []byte("module \"gopkg.in/check.v1\"asd"), expected: ""}, + {input: []byte("module \nmodule a/b/c "), expected: "a/b/c"}, + {input: []byte("module \" \""), expected: " "}, + {input: []byte("module "), expected: ""}, + {input: []byte("module \" a/b/c \""), expected: " a/b/c "}, + {input: []byte("module \"github.com/rsc/vgotest1\" // with a comment"), expected: "github.com/rsc/vgotest1"}, +} + +func TestModulePath(t *testing.T) { + for _, test := range modulePathTests { + t.Run(string(test.input), func(t *testing.T) { + result := ModulePath(test.input) + if result != test.expected { + t.Fatalf("ModulePath(%q): %s, want %s", string(test.input), result, test.expected) + } + }) + } +} |