github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/gnovm/pkg/gnomod/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 gnomod
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"strings"
    11  	"testing"
    12  
    13  	"golang.org/x/mod/modfile"
    14  )
    15  
    16  // TestParsePunctuation verifies that certain ASCII punctuation characters
    17  // (brackets, commas) are lexed as separate tokens, even when they're
    18  // surrounded by identifier characters.
    19  func TestParsePunctuation(t *testing.T) {
    20  	for _, test := range []struct {
    21  		desc, src, want string
    22  	}{
    23  		{"paren", "require ()", "require ( )"},
    24  		{"brackets", "require []{},", "require [ ] { } ,"},
    25  		{"mix", "require a[b]c{d}e,", "require a [ b ] c { d } e ,"},
    26  		{"block_mix", "require (\n\ta[b]\n)", "require ( a [ b ] )"},
    27  		{"interval", "require [v1.0.0, v1.1.0)", "require [ v1.0.0 , v1.1.0 )"},
    28  	} {
    29  		t.Run(test.desc, func(t *testing.T) {
    30  			f, err := parse("gno.mod", []byte(test.src))
    31  			if err != nil {
    32  				t.Fatalf("parsing %q: %v", test.src, err)
    33  			}
    34  			var tokens []string
    35  			for _, stmt := range f.Stmt {
    36  				switch stmt := stmt.(type) {
    37  				case *modfile.Line:
    38  					tokens = append(tokens, stmt.Token...)
    39  				case *modfile.LineBlock:
    40  					tokens = append(tokens, stmt.Token...)
    41  					tokens = append(tokens, "(")
    42  					for _, line := range stmt.Line {
    43  						tokens = append(tokens, line.Token...)
    44  					}
    45  					tokens = append(tokens, ")")
    46  				default:
    47  					t.Fatalf("parsing %q: unexpected statement of type %T", test.src, stmt)
    48  				}
    49  			}
    50  			got := strings.Join(tokens, " ")
    51  			if got != test.want {
    52  				t.Errorf("parsing %q: got %q, want %q", test.src, got, test.want)
    53  			}
    54  		})
    55  	}
    56  }
    57  
    58  var modulePathTests = []struct {
    59  	input    []byte
    60  	expected string
    61  }{
    62  	{input: []byte("module \"github.com/rsc/vgotest\""), expected: "github.com/rsc/vgotest"},
    63  	{input: []byte("module github.com/rsc/vgotest"), expected: "github.com/rsc/vgotest"},
    64  	{input: []byte("module  \"github.com/rsc/vgotest\""), expected: "github.com/rsc/vgotest"},
    65  	{input: []byte("module  github.com/rsc/vgotest"), expected: "github.com/rsc/vgotest"},
    66  	{input: []byte("module `github.com/rsc/vgotest`"), expected: "github.com/rsc/vgotest"},
    67  	{input: []byte("module \"github.com/rsc/vgotest/v2\""), expected: "github.com/rsc/vgotest/v2"},
    68  	{input: []byte("module github.com/rsc/vgotest/v2"), expected: "github.com/rsc/vgotest/v2"},
    69  	{input: []byte("module \"gopkg.in/yaml.v2\""), expected: "gopkg.in/yaml.v2"},
    70  	{input: []byte("module gopkg.in/yaml.v2"), expected: "gopkg.in/yaml.v2"},
    71  	{input: []byte("module \"gopkg.in/check.v1\"\n"), expected: "gopkg.in/check.v1"},
    72  	{input: []byte("module \"gopkg.in/check.v1\n\""), expected: ""},
    73  	{input: []byte("module gopkg.in/check.v1\n"), expected: "gopkg.in/check.v1"},
    74  	{input: []byte("module \"gopkg.in/check.v1\"\r\n"), expected: "gopkg.in/check.v1"},
    75  	{input: []byte("module gopkg.in/check.v1\r\n"), expected: "gopkg.in/check.v1"},
    76  	{input: []byte("module \"gopkg.in/check.v1\"\n\n"), expected: "gopkg.in/check.v1"},
    77  	{input: []byte("module gopkg.in/check.v1\n\n"), expected: "gopkg.in/check.v1"},
    78  	{input: []byte("module \n\"gopkg.in/check.v1\"\n\n"), expected: ""},
    79  	{input: []byte("module \ngopkg.in/check.v1\n\n"), expected: ""},
    80  	{input: []byte("module \"gopkg.in/check.v1\"asd"), expected: ""},
    81  	{input: []byte("module \n\"gopkg.in/check.v1\"\n\n"), expected: ""},
    82  	{input: []byte("module \ngopkg.in/check.v1\n\n"), expected: ""},
    83  	{input: []byte("module \"gopkg.in/check.v1\"asd"), expected: ""},
    84  	{input: []byte("module  \nmodule a/b/c "), expected: "a/b/c"},
    85  	{input: []byte("module \"   \""), expected: "   "},
    86  	{input: []byte("module   "), expected: ""},
    87  	{input: []byte("module \"  a/b/c  \""), expected: "  a/b/c  "},
    88  	{input: []byte("module \"github.com/rsc/vgotest1\" // with a comment"), expected: "github.com/rsc/vgotest1"},
    89  }
    90  
    91  func TestModulePath(t *testing.T) {
    92  	for _, test := range modulePathTests {
    93  		t.Run(string(test.input), func(t *testing.T) {
    94  			result := ModulePath(test.input)
    95  			if result != test.expected {
    96  				t.Fatalf("ModulePath(%q): %s, want %s", string(test.input), result, test.expected)
    97  			}
    98  		})
    99  	}
   100  }
   101  
   102  func TestParseVersions(t *testing.T) {
   103  	tests := []struct {
   104  		desc, input string
   105  		ok          bool
   106  	}{
   107  		// go lines
   108  		{desc: "empty", input: "module m\ngo \n", ok: false},
   109  		{desc: "one", input: "module m\ngo 1\n", ok: false},
   110  		{desc: "two", input: "module m\ngo 1.22\n", ok: true},
   111  		{desc: "three", input: "module m\ngo 1.22.333", ok: true},
   112  		{desc: "before", input: "module m\ngo v1.2\n", ok: false},
   113  		{desc: "after", input: "module m\ngo 1.2rc1\n", ok: true},
   114  		{desc: "space", input: "module m\ngo 1.2 3.4\n", ok: false},
   115  		{desc: "alt1", input: "module m\ngo 1.2.3\n", ok: true},
   116  		{desc: "alt2", input: "module m\ngo 1.2rc1\n", ok: true},
   117  		{desc: "alt3", input: "module m\ngo 1.2beta1\n", ok: true},
   118  		{desc: "alt4", input: "module m\ngo 1.2.beta1\n", ok: false},
   119  	}
   120  	t.Run("Strict", func(t *testing.T) {
   121  		for _, test := range tests {
   122  			t.Run(test.desc, func(t *testing.T) {
   123  				if _, err := Parse("gno.mod", []byte(test.input)); err == nil && !test.ok {
   124  					t.Error("unexpected success")
   125  				} else if err != nil && test.ok {
   126  					t.Errorf("unexpected error: %v", err)
   127  				}
   128  			})
   129  		}
   130  	})
   131  }
   132  
   133  func TestComments(t *testing.T) {
   134  	for _, test := range []struct {
   135  		desc, input, want string
   136  	}{
   137  		{
   138  			desc: "comment_only",
   139  			input: `
   140  // a
   141  // b
   142  `,
   143  			want: `
   144  comments before "// a"
   145  comments before "// b"
   146  `,
   147  		}, {
   148  			desc: "line",
   149  			input: `
   150  // a
   151  
   152  // b
   153  module m // c
   154  // d
   155  
   156  // e
   157  `,
   158  			want: `
   159  comments before "// a"
   160  line before "// b"
   161  line suffix "// c"
   162  comments before "// d"
   163  comments before "// e"
   164  `,
   165  		}, {
   166  			desc:  "cr_removed",
   167  			input: "// a\r\r\n",
   168  			want:  `comments before "// a\r"`,
   169  		},
   170  	} {
   171  		t.Run(test.desc, func(t *testing.T) {
   172  			f, err := Parse("gno.mod", []byte(test.input))
   173  			if err != nil {
   174  				t.Fatal(err)
   175  			}
   176  
   177  			if test.desc == "block" {
   178  				panic("hov")
   179  			}
   180  
   181  			buf := &bytes.Buffer{}
   182  			printComments := func(prefix string, cs *modfile.Comments) {
   183  				for _, c := range cs.Before {
   184  					fmt.Fprintf(buf, "%s before %q\n", prefix, c.Token)
   185  				}
   186  				for _, c := range cs.Suffix {
   187  					fmt.Fprintf(buf, "%s suffix %q\n", prefix, c.Token)
   188  				}
   189  				for _, c := range cs.After {
   190  					fmt.Fprintf(buf, "%s after %q\n", prefix, c.Token)
   191  				}
   192  			}
   193  
   194  			printComments("file", &f.Syntax.Comments)
   195  			for _, stmt := range f.Syntax.Stmt {
   196  				switch stmt := stmt.(type) {
   197  				case *modfile.CommentBlock:
   198  					printComments("comments", stmt.Comment())
   199  				case *modfile.Line:
   200  					printComments("line", stmt.Comment())
   201  				}
   202  			}
   203  
   204  			got := strings.TrimSpace(buf.String())
   205  			want := strings.TrimSpace(test.want)
   206  			if got != want {
   207  				t.Errorf("got:\n%s\nwant:\n%s", got, want)
   208  			}
   209  		})
   210  	}
   211  }
   212  
   213  var addRequireTests = []struct {
   214  	desc string
   215  	in   string
   216  	path string
   217  	vers string
   218  	out  string
   219  }{
   220  	{
   221  		`existing`,
   222  		`
   223  		module m
   224  		require x.y/z v1.2.3
   225  		`,
   226  		"x.y/z", "v1.5.6",
   227  		`
   228  		module m
   229  		require x.y/z v1.5.6
   230  		`,
   231  	},
   232  	{
   233  		`existing2`,
   234  		`
   235  		module m
   236  		require (
   237  			x.y/z v1.2.3 // first
   238  			x.z/a v0.1.0 // first-a
   239  		)
   240  		require x.y/z v1.4.5 // second
   241  		require (
   242  			x.y/z v1.6.7 // third
   243  			x.z/a v0.2.0 // third-a
   244  		)
   245  		`,
   246  		"x.y/z", "v1.8.9",
   247  		`
   248  		module m
   249  
   250  		require (
   251  			x.y/z v1.8.9 // first
   252  			x.z/a v0.1.0 // first-a
   253  		)
   254  
   255  		require x.z/a v0.2.0 // third-a
   256  		`,
   257  	},
   258  	{
   259  		`new`,
   260  		`
   261  		module m
   262  		require x.y/z v1.2.3
   263  		`,
   264  		"x.y/w", "v1.5.6",
   265  		`
   266  		module m
   267  		require (
   268  			x.y/z v1.2.3
   269  			x.y/w v1.5.6
   270  		)
   271  		`,
   272  	},
   273  	{
   274  		`new2`,
   275  		`
   276  		module m
   277  		require x.y/z v1.2.3
   278  		require x.y/q/v2 v2.3.4
   279  		`,
   280  		"x.y/w", "v1.5.6",
   281  		`
   282  		module m
   283  		require x.y/z v1.2.3
   284  		require (
   285  			x.y/q/v2 v2.3.4
   286  			x.y/w v1.5.6
   287  		)
   288  		`,
   289  	},
   290  }
   291  
   292  var addModuleStmtTests = []struct {
   293  	desc string
   294  	in   string
   295  	path string
   296  	out  string
   297  }{
   298  	{
   299  		`existing`,
   300  		`
   301  		module m
   302  		require x.y/z v1.2.3
   303  		`,
   304  		"n",
   305  		`
   306  		module n
   307  		require x.y/z v1.2.3
   308  		`,
   309  	},
   310  	{
   311  		`new`,
   312  		``,
   313  		"m",
   314  		`
   315  		module m
   316  		`,
   317  	},
   318  }
   319  
   320  var addReplaceTests = []struct {
   321  	desc    string
   322  	in      string
   323  	oldPath string
   324  	oldVers string
   325  	newPath string
   326  	newVers string
   327  	out     string
   328  }{
   329  	{
   330  		`replace_with_module`,
   331  		`
   332  		module m
   333  		require x.y/z v1.2.3
   334  		`,
   335  		"x.y/z",
   336  		"v1.5.6",
   337  		"a.b/c",
   338  		"v1.5.6",
   339  		`
   340  		module m
   341  		require x.y/z v1.2.3
   342  		replace x.y/z v1.5.6 => a.b/c v1.5.6
   343  		`,
   344  	},
   345  	{
   346  		`replace_with_dir`,
   347  		`
   348  		module m
   349  		require x.y/z v1.2.3
   350  		`,
   351  		"x.y/z",
   352  		"v1.5.6",
   353  		"/path/to/dir",
   354  		"",
   355  		`
   356  		module m
   357  		require x.y/z v1.2.3
   358  		replace x.y/z v1.5.6 => /path/to/dir
   359  		`,
   360  	},
   361  }
   362  
   363  var dropRequireTests = []struct {
   364  	desc string
   365  	in   string
   366  	path string
   367  	out  string
   368  }{
   369  	{
   370  		`existing`,
   371  		`
   372  		module m
   373  		require x.y/z v1.2.3
   374  		`,
   375  		"x.y/z",
   376  		`
   377  		module m
   378  		`,
   379  	},
   380  	{
   381  		`existing2`,
   382  		`
   383  		module m
   384  		require (
   385  			x.y/z v1.2.3 // first
   386  			x.z/a v0.1.0 // first-a
   387  		)
   388  		require x.y/z v1.4.5 // second
   389  		require (
   390  			x.y/z v1.6.7 // third
   391  			x.z/a v0.2.0 // third-a
   392  		)
   393  		`,
   394  		"x.y/z",
   395  		`
   396  		module m
   397  
   398  		require x.z/a v0.1.0 // first-a
   399  
   400  		require x.z/a v0.2.0 // third-a
   401  		`,
   402  	},
   403  	{
   404  		`not_exists`,
   405  		`
   406  		module m
   407  		require x.y/z v1.2.3
   408  		`,
   409  		"a.b/c",
   410  		`
   411  		module m
   412  		require x.y/z v1.2.3
   413  		`,
   414  	},
   415  }
   416  
   417  var dropReplaceTests = []struct {
   418  	desc string
   419  	in   string
   420  	path string
   421  	vers string
   422  	out  string
   423  }{
   424  	{
   425  		`existing`,
   426  		`
   427  		module m
   428  		require x.y/z v1.2.3
   429  
   430  		replace x.y/z v1.2.3 => a.b/c v1.5.6
   431  		`,
   432  		"x.y/z",
   433  		"v1.2.3",
   434  		`
   435  		module m
   436  		require x.y/z v1.2.3
   437  		`,
   438  	},
   439  	{
   440  		`not_exists`,
   441  		`
   442  		module m
   443  		require x.y/z v1.2.3
   444  
   445  		replace x.y/z v1.2.3 => a.b/c v1.5.6
   446  		`,
   447  		"a.b/c",
   448  		"v3.2.1",
   449  		`
   450  		module m
   451  		require x.y/z v1.2.3
   452  
   453  		replace x.y/z v1.2.3 => a.b/c v1.5.6
   454  		`,
   455  	},
   456  }
   457  
   458  func TestAddRequire(t *testing.T) {
   459  	for _, tt := range addRequireTests {
   460  		t.Run(tt.desc, func(t *testing.T) {
   461  			testEdit(t, tt.in, tt.out, func(f *File) error {
   462  				err := f.AddRequire(tt.path, tt.vers)
   463  				f.Syntax.Cleanup()
   464  				return err
   465  			})
   466  		})
   467  	}
   468  }
   469  
   470  func TestAddModuleStmt(t *testing.T) {
   471  	for _, tt := range addModuleStmtTests {
   472  		t.Run(tt.desc, func(t *testing.T) {
   473  			testEdit(t, tt.in, tt.out, func(f *File) error {
   474  				err := f.AddModuleStmt(tt.path)
   475  				f.Syntax.Cleanup()
   476  				return err
   477  			})
   478  		})
   479  	}
   480  }
   481  
   482  func TestAddReplace(t *testing.T) {
   483  	for _, tt := range addReplaceTests {
   484  		t.Run(tt.desc, func(t *testing.T) {
   485  			testEdit(t, tt.in, tt.out, func(f *File) error {
   486  				f.AddReplace(tt.oldPath, tt.oldVers, tt.newPath, tt.newVers)
   487  				f.Syntax.Cleanup()
   488  				return nil
   489  			})
   490  		})
   491  	}
   492  }
   493  
   494  func TestDropRequire(t *testing.T) {
   495  	for _, tt := range dropRequireTests {
   496  		t.Run(tt.desc, func(t *testing.T) {
   497  			testEdit(t, tt.in, tt.out, func(f *File) error {
   498  				err := f.DropRequire(tt.path)
   499  				f.Syntax.Cleanup()
   500  				return err
   501  			})
   502  		})
   503  	}
   504  }
   505  
   506  func TestDropReplace(t *testing.T) {
   507  	for _, tt := range dropReplaceTests {
   508  		t.Run(tt.desc, func(t *testing.T) {
   509  			testEdit(t, tt.in, tt.out, func(f *File) error {
   510  				err := f.DropReplace(tt.path, tt.vers)
   511  				f.Syntax.Cleanup()
   512  				return err
   513  			})
   514  		})
   515  	}
   516  }
   517  
   518  func testEdit(t *testing.T, in, want string, transform func(f *File) error) *File {
   519  	t.Helper()
   520  	f, err := Parse("in", []byte(in))
   521  	if err != nil {
   522  		t.Fatal(err)
   523  	}
   524  	g, err := Parse("out", []byte(want))
   525  	if err != nil {
   526  		t.Fatal(err)
   527  	}
   528  	golden := modfile.Format(g.Syntax)
   529  	if err := transform(f); err != nil {
   530  		t.Fatal(err)
   531  	}
   532  	out := modfile.Format(f.Syntax)
   533  	if err != nil {
   534  		t.Fatal(err)
   535  	}
   536  	if !bytes.Equal(out, golden) {
   537  		t.Errorf("have:\n%s\nwant:\n%s", out, golden)
   538  	}
   539  
   540  	return f
   541  }