github.com/gernest/nezuko@v0.1.2/internal/modfile/read_test.go (about)

     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  }