285 lines
6.5 KiB
Go
285 lines
6.5 KiB
Go
// 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 confyg
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"reflect"
|
|
"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
|
|
}
|
|
}
|
|
|
|
// // 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: %v", err)
|
|
// }
|
|
|
|
// pf1, err := Parse(base, data)
|
|
// if err == nil {
|
|
// pf2, err := Parse(base, ndata)
|
|
// 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: %v", err)
|
|
// }
|
|
// }
|
|
|
|
// 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))
|
|
}
|