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 }