...

Source file src/golang.org/x/mod/modfile/read_test.go

Documentation: golang.org/x/mod/modfile

     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  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"reflect"
    14  	"strings"
    15  	"testing"
    16  )
    17  
    18  // exists reports whether the named file exists.
    19  func exists(name string) bool {
    20  	_, err := os.Stat(name)
    21  	return err == nil
    22  }
    23  
    24  // Test that reading and then writing the golden files
    25  // does not change their output.
    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  // testPrint is a helper for testing the printer.
    42  // It reads the file named in, reformats it, and compares
    43  // the result to the file named out.
    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  // TestParsePunctuation verifies that certain ASCII punctuation characters
    74  // (brackets, commas) are lexed as separate tokens, even when they're
    75  // surrounded by identifier characters.
    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  // Test that when files in the testdata directory are parsed
   144  // and printed and parsed again, we get the same parse tree
   145  // both times.
   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  					// ignore
   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  // An eqchecker holds state for checking the equality of two parse trees.
   235  type eqchecker struct {
   236  	file string
   237  	pos  Position
   238  }
   239  
   240  // errorf returns an error described by the printf-style format and arguments,
   241  // inserting the current file position before the error text.
   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  // check checks that v and w represent the same parse tree.
   248  // If not, it returns an error describing the first difference.
   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  // checkValue checks that v and w represent the same parse tree.
   259  // If not, it returns an error describing the first difference.
   260  func (eq *eqchecker) checkValue(v, w reflect.Value) error {
   261  	// inner returns the innermost expression for v.
   262  	// if v is a non-nil interface value, it returns the concrete
   263  	// value in the interface.
   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  		// Fields in struct must match.
   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: // ignore positions
   334  			case tf.Type == commentsType: // ignore comment assignment
   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  // diff returns the output of running diff on b1 and b2.
   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  		// diff exits with a non-zero status when the files don't match.
   374  		// Ignore that failure as long as we get output.
   375  		err = nil
   376  	}
   377  	return
   378  }
   379  
   380  // tdiff logs the diff output to t.Error.
   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 // ok=true implies laxOK=true; only set if ok=false
   439  	}{
   440  		// go lines
   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  		// toolchain lines
   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