github.com/bazelbuild/bazel-gazelle@v0.36.1-0.20240520142334-61b277ba6fed/rule/rule_test.go (about) 1 /* Copyright 2018 The Bazel Authors. All rights reserved. 2 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 */ 15 16 package rule 17 18 import ( 19 "path/filepath" 20 "reflect" 21 "sort" 22 "strings" 23 "testing" 24 25 bzl "github.com/bazelbuild/buildtools/build" 26 ) 27 28 // This file contains tests for File, Load, Rule, and related functions. 29 // Tests only cover some basic functionality and a few non-obvious cases. 30 // Most test coverage will come from clients of this package. 31 32 func TestEditAndSync(t *testing.T) { 33 old := []byte(` 34 load("a.bzl", "x_library") 35 36 x_library(name = "foo") 37 38 load("b.bzl", y_library = "y") 39 40 y_library(name = "bar") 41 `) 42 f, err := LoadData(filepath.Join("old", "BUILD.bazel"), "", old) 43 if err != nil { 44 t.Fatal(err) 45 } 46 47 loadA := f.Loads[0] 48 loadA.Delete() 49 loadB := f.Loads[1] 50 loadB.Add("x_library") 51 loadB.Remove("y_library") 52 loadC := NewLoad("c.bzl") 53 loadC.AddAlias("z_library", "z") 54 loadC.Add("y_library") 55 loadC.Insert(f, 3) 56 57 foo := f.Rules[0] 58 foo.Delete() 59 bar := f.Rules[1] 60 bar.SetAttr("srcs", []string{"bar.y"}) 61 loadMaybe := NewLoad("//some:maybe.bzl") 62 loadMaybe.Add("maybe") 63 loadMaybe.Insert(f, 0) 64 baz := NewRule("maybe", "baz") 65 baz.AddArg(&bzl.LiteralExpr{Token: "z"}) 66 baz.AddArg(&bzl.LiteralExpr{Token: "z"}) 67 if err := baz.UpdateArg(0, &bzl.LiteralExpr{Token: "z0"}); err != nil { 68 t.Fatal(err) 69 } 70 if err := baz.UpdateArg(10, &bzl.LiteralExpr{Token: "blah"}); err == nil { 71 t.Fatalf("want error because tried to modify an arg outside of arg bounds, got nil") 72 } 73 baz.SetAttr("srcs", GlobValue{ 74 Patterns: []string{"**"}, 75 Excludes: []string{"*.pem"}, 76 }) 77 baz.Insert(f) 78 79 got := strings.TrimSpace(string(f.Format())) 80 want := strings.TrimSpace(` 81 load("//some:maybe.bzl", "maybe") 82 load("b.bzl", "x_library") 83 load( 84 "c.bzl", 85 "y_library", 86 z = "z_library", 87 ) 88 89 y_library( 90 name = "bar", 91 srcs = ["bar.y"], 92 ) 93 94 maybe( 95 z0, 96 z, 97 name = "baz", 98 srcs = glob( 99 ["**"], 100 exclude = ["*.pem"], 101 ), 102 ) 103 `) 104 if got != want { 105 t.Errorf("got:\n%s\nwant:\n%s", got, want) 106 } 107 } 108 109 func TestPassInserted(t *testing.T) { 110 old := []byte(` 111 load("a.bzl", "baz") 112 113 def foo(): 114 go_repository(name = "bar") 115 `) 116 f, err := LoadMacroData(filepath.Join("old", "repo.bzl"), "", "foo", old) 117 if err != nil { 118 t.Fatal(err) 119 } 120 121 f.Rules[0].Delete() 122 f.Sync() 123 got := strings.TrimSpace(string(f.Format())) 124 want := strings.TrimSpace(` 125 load("a.bzl", "baz") 126 127 def foo(): 128 pass 129 `) 130 131 if got != want { 132 t.Errorf("got:\n%s\nwant:%s", got, want) 133 } 134 } 135 136 func TestPassRemoved(t *testing.T) { 137 old := []byte(` 138 load("a.bzl", "baz") 139 140 def foo(): 141 pass 142 `) 143 f, err := LoadMacroData(filepath.Join("old", "repo.bzl"), "", "foo", old) 144 if err != nil { 145 t.Fatal(err) 146 } 147 148 bar := NewRule("go_repository", "bar") 149 bar.Insert(f) 150 f.Sync() 151 got := strings.TrimSpace(string(f.Format())) 152 want := strings.TrimSpace(` 153 load("a.bzl", "baz") 154 155 def foo(): 156 go_repository(name = "bar") 157 `) 158 159 if got != want { 160 t.Errorf("got:\n%s\nwant:%s", got, want) 161 } 162 } 163 164 func TestFunctionInserted(t *testing.T) { 165 f, err := LoadMacroData(filepath.Join("old", "repo.bzl"), "", "foo", nil) 166 if err != nil { 167 t.Fatal(err) 168 } 169 170 bar := NewRule("go_repository", "bar") 171 bar.Insert(f) 172 f.Sync() 173 got := strings.TrimSpace(string(f.Format())) 174 want := strings.TrimSpace(` 175 def foo(): 176 go_repository(name = "bar") 177 `) 178 179 if got != want { 180 t.Errorf("got:\n%s\nwant:%s", got, want) 181 } 182 } 183 184 func TestArgsAlwaysEndUpBeforeKwargs(t *testing.T) { 185 f, err := LoadData(filepath.Join("old", "BUILD.bazel"), "", nil) 186 if err != nil { 187 t.Fatal(err) 188 } 189 190 bar := NewRule("maybe", "bar") 191 bar.SetAttr("url", "https://doesnotexist.com") 192 bar.AddArg(&bzl.Ident{Name: "http_archive"}) 193 bar.Insert(f) 194 f.Sync() 195 got := strings.TrimSpace(string(f.Format())) 196 want := strings.TrimSpace(` 197 maybe( 198 http_archive, 199 name = "bar", 200 url = "https://doesnotexist.com", 201 ) 202 `) 203 204 if got != want { 205 t.Errorf("got:\n%s\nwant:%s", got, want) 206 } 207 } 208 209 func TestDeleteSyncDelete(t *testing.T) { 210 old := []byte(` 211 x_library(name = "foo") 212 213 # comment 214 215 x_library(name = "bar") 216 `) 217 f, err := LoadData(filepath.Join("old", "BUILD.bazel"), "", old) 218 if err != nil { 219 t.Fatal(err) 220 } 221 222 foo := f.Rules[0] 223 bar := f.Rules[1] 224 foo.Delete() 225 f.Sync() 226 bar.Delete() 227 f.Sync() 228 got := strings.TrimSpace(string(f.Format())) 229 want := strings.TrimSpace(`# comment`) 230 if got != want { 231 t.Errorf("got:\n%s\nwant:%s", got, want) 232 } 233 } 234 235 func TestInsertDeleteSync(t *testing.T) { 236 old := []byte("") 237 f, err := LoadData(filepath.Join("old", "BUILD.bazel"), "", old) 238 if err != nil { 239 t.Fatal(err) 240 } 241 242 foo := NewRule("filegroup", "test") 243 foo.Insert(f) 244 foo.Delete() 245 f.Sync() 246 got := strings.TrimSpace(string(f.Format())) 247 want := "" 248 if got != want { 249 t.Errorf("got:\n%s\nwant:%s", got, want) 250 } 251 } 252 253 func TestSymbolsReturnsKeys(t *testing.T) { 254 f, err := LoadData(filepath.Join("load", "BUILD.bazel"), "", []byte(`load("a.bzl", "y", z = "a")`)) 255 if err != nil { 256 t.Fatal(err) 257 } 258 got := f.Loads[0].Symbols() 259 want := []string{"y", "z"} 260 if !reflect.DeepEqual(got, want) { 261 t.Errorf("got %#v; want %#v", got, want) 262 } 263 } 264 265 func TestLoadCommentsAreRetained(t *testing.T) { 266 f, err := LoadData(filepath.Join("load", "BUILD.bazel"), "", []byte(` 267 load( 268 "a.bzl", 269 # Comment for a symbol that will be deleted. 270 "baz", 271 # Some comment without remapping. 272 "foo", 273 # Some comment with remapping. 274 my_bar = "bar", 275 ) 276 `)) 277 if err != nil { 278 t.Fatal(err) 279 } 280 l := f.Loads[0] 281 l.Remove("baz") 282 f.Sync() 283 l.Add("new_baz") 284 f.Sync() 285 286 got := strings.TrimSpace(string(f.Format())) 287 want := strings.TrimSpace(` 288 load( 289 "a.bzl", 290 # Some comment without remapping. 291 "foo", 292 "new_baz", 293 # Some comment with remapping. 294 my_bar = "bar", 295 ) 296 `) 297 298 if got != want { 299 t.Errorf("got:\n%s\nwant:%s", got, want) 300 } 301 } 302 303 func TestKeepRule(t *testing.T) { 304 for _, tc := range []struct { 305 desc, src string 306 want bool 307 }{ 308 { 309 desc: "prefix", 310 src: ` 311 # keep 312 x_library(name = "x") 313 `, 314 want: true, 315 }, { 316 desc: "prefix with description", 317 src: ` 318 # keep: hack, see more in ticket #42 319 x_library(name = "x") 320 `, 321 want: true, 322 }, { 323 desc: "compact_suffix", 324 src: ` 325 x_library(name = "x") # keep 326 `, 327 want: true, 328 }, { 329 desc: "multiline_internal", 330 src: ` 331 x_library( # keep 332 name = "x", 333 ) 334 `, 335 want: false, 336 }, { 337 desc: "multiline_suffix", 338 src: ` 339 x_library( 340 name = "x", 341 ) # keep 342 `, 343 want: true, 344 }, { 345 desc: "after", 346 src: ` 347 x_library(name = "x") 348 # keep 349 `, 350 want: false, 351 }, 352 } { 353 t.Run(tc.desc, func(t *testing.T) { 354 f, err := LoadData(filepath.Join(tc.desc, "BUILD.bazel"), "", []byte(tc.src)) 355 if err != nil { 356 t.Fatal(err) 357 } 358 if got := f.Rules[0].ShouldKeep(); got != tc.want { 359 t.Errorf("got %v; want %v", got, tc.want) 360 } 361 }) 362 } 363 } 364 365 func TestShouldKeepExpr(t *testing.T) { 366 for _, tc := range []struct { 367 desc, src string 368 path func(e bzl.Expr) bzl.Expr 369 want bool 370 }{ 371 { 372 desc: "before", 373 src: ` 374 # keep 375 "s" 376 `, 377 want: true, 378 }, { 379 desc: "before with description", 380 src: ` 381 # keep: we need it for the ninja feature 382 "s" 383 `, 384 want: true, 385 }, { 386 desc: "before but not the correct prefix (keeping)", 387 src: ` 388 # keeping this for now 389 "s" 390 `, 391 want: false, 392 }, { 393 desc: "before but not the correct prefix (no colon)", 394 src: ` 395 # keep this around for the time being 396 "s" 397 `, 398 want: false, 399 }, { 400 desc: "suffix", 401 src: ` 402 "s" # keep 403 `, 404 want: true, 405 }, { 406 desc: "after", 407 src: ` 408 "s" 409 # keep 410 `, 411 want: false, 412 }, { 413 desc: "list_elem_prefix", 414 src: ` 415 [ 416 # keep 417 "s", 418 ] 419 `, 420 path: func(e bzl.Expr) bzl.Expr { return e.(*bzl.ListExpr).List[0] }, 421 want: true, 422 }, { 423 desc: "list_elem_suffix", 424 src: ` 425 [ 426 "s", # keep 427 ] 428 `, 429 path: func(e bzl.Expr) bzl.Expr { return e.(*bzl.ListExpr).List[0] }, 430 want: true, 431 }, 432 } { 433 t.Run(tc.desc, func(t *testing.T) { 434 ast, err := bzl.Parse(tc.desc, []byte(tc.src)) 435 if err != nil { 436 t.Fatal(err) 437 } 438 expr := ast.Stmt[0] 439 if tc.path != nil { 440 expr = tc.path(expr) 441 } 442 got := ShouldKeep(expr) 443 if got != tc.want { 444 t.Errorf("got %v; want %v", got, tc.want) 445 } 446 }) 447 } 448 } 449 450 func TestInternalVisibility(t *testing.T) { 451 tests := []struct { 452 rel string 453 expected string 454 }{ 455 {rel: "internal", expected: "//:__subpackages__"}, 456 {rel: "a/b/internal", expected: "//a/b:__subpackages__"}, 457 {rel: "a/b/internal/c", expected: "//a/b:__subpackages__"}, 458 {rel: "a/b/internal/c/d", expected: "//a/b:__subpackages__"}, 459 {rel: "a/b/internal/c/internal", expected: "//a/b/internal/c:__subpackages__"}, 460 {rel: "a/b/internal/c/internal/d", expected: "//a/b/internal/c:__subpackages__"}, 461 } 462 463 for _, tt := range tests { 464 if actual := CheckInternalVisibility(tt.rel, "default"); actual != tt.expected { 465 t.Errorf("got %v; want %v", actual, tt.expected) 466 } 467 } 468 } 469 470 func TestSortLoadsByName(t *testing.T) { 471 f, err := LoadMacroData( 472 filepath.Join("third_party", "repos.bzl"), 473 "", "repos", 474 []byte(`load("@bazel_gazelle//:deps.bzl", "go_repository") 475 load("//some:maybe.bzl", "maybe") 476 load("//some2:maybe.bzl", "maybe2") 477 load("//some1:maybe4.bzl", "maybe1") 478 load("b.bzl", "x_library") 479 def repos(): 480 go_repository( 481 name = "com_github_bazelbuild_bazel_gazelle", 482 ) 483 `)) 484 if err != nil { 485 t.Error(err) 486 } 487 sort.Stable(loadsByName{ 488 loads: f.Loads, 489 exprs: f.File.Stmt, 490 }) 491 f.Sync() 492 493 got := strings.TrimSpace(string(f.Format())) 494 want := strings.TrimSpace(` 495 load("@bazel_gazelle//:deps.bzl", "go_repository") 496 load("//some:maybe.bzl", "maybe") 497 load("//some1:maybe4.bzl", "maybe1") 498 load("//some2:maybe.bzl", "maybe2") 499 load("b.bzl", "x_library") 500 501 def repos(): 502 go_repository( 503 name = "com_github_bazelbuild_bazel_gazelle", 504 ) 505 `) 506 507 if got != want { 508 t.Errorf("got:\n%s\nwant:%s", got, want) 509 } 510 } 511 512 func TestSortRulesByKindAndName(t *testing.T) { 513 f, err := LoadMacroData( 514 filepath.Join("third_party", "repos.bzl"), 515 "", "repos", 516 []byte(`load("@bazel_gazelle//:deps.bzl", "go_repository") 517 def repos(): 518 some_other_call_rule() 519 go_repository( 520 name = "com_github_andybalholm_cascadia", 521 ) 522 go_repository( 523 name = "com_github_bazelbuild_buildtools", 524 ) 525 go_repository( 526 name = "com_github_bazelbuild_rules_go", 527 ) 528 go_repository( 529 name = "com_github_bazelbuild_bazel_gazelle", 530 ) 531 a_rule( 532 name = "name1", 533 ) 534 `)) 535 if err != nil { 536 t.Error(err) 537 } 538 sort.Stable(rulesByKindAndName{ 539 rules: f.Rules, 540 exprs: f.function.stmt.Body, 541 }) 542 repos := []string{ 543 "name1", 544 "com_github_andybalholm_cascadia", 545 "com_github_bazelbuild_bazel_gazelle", 546 "com_github_bazelbuild_buildtools", 547 "com_github_bazelbuild_rules_go", 548 } 549 for i, r := range repos { 550 rule := f.Rules[i] 551 if rule.Name() != r { 552 t.Errorf("expect rule %s at %d, got %s", r, i, rule.Name()) 553 } 554 if rule.Index() != i { 555 t.Errorf("expect rule %s with index %d, got %d", r, i, rule.Index()) 556 } 557 if f.function.stmt.Body[i] != rule.expr { 558 t.Errorf("underlying syntax tree of rule %s not sorted", r) 559 } 560 } 561 562 got := strings.TrimSpace(string(f.Format())) 563 want := strings.TrimSpace(` 564 load("@bazel_gazelle//:deps.bzl", "go_repository") 565 566 def repos(): 567 a_rule( 568 name = "name1", 569 ) 570 go_repository( 571 name = "com_github_andybalholm_cascadia", 572 ) 573 574 go_repository( 575 name = "com_github_bazelbuild_bazel_gazelle", 576 ) 577 go_repository( 578 name = "com_github_bazelbuild_buildtools", 579 ) 580 go_repository( 581 name = "com_github_bazelbuild_rules_go", 582 ) 583 some_other_call_rule() 584 `) 585 586 if got != want { 587 t.Errorf("got:%s\nwant:%s", got, want) 588 } 589 } 590 591 func TestCheckFile(t *testing.T) { 592 f := File{Rules: []*Rule{ 593 NewRule("go_repository", "com_google_cloud_go_pubsub"), 594 NewRule("go_repository", "com_google_cloud_go_pubsub"), 595 }} 596 err := checkFile(&f) 597 if err == nil { 598 t.Errorf("muliple rules with the same name should not be tolerated") 599 } 600 601 f = File{Rules: []*Rule{ 602 NewRule("go_rules_dependencies", ""), 603 NewRule("go_register_toolchains", ""), 604 }} 605 err = checkFile(&f) 606 if err != nil { 607 t.Errorf("unexpected error: %v", err) 608 } 609 } 610 611 func TestAttributeComment(t *testing.T) { 612 f, err := LoadData(filepath.Join("old", "BUILD.bazel"), "", nil) 613 if err != nil { 614 t.Fatal(err) 615 } 616 617 r := NewRule("a_rule", "name1") 618 r.SetAttr("deps", []string{"foo", "bar", "baz"}) 619 r.SetAttr("hdrs", []string{"foo", "bar", "baz"}) 620 hdrComments := r.AttrComments("hdrs") 621 hdrComments.Before = append(hdrComments.Before, bzl.Comment{ 622 Token: "# do not sort", 623 }) 624 625 r.Insert(f) 626 627 got := strings.TrimSpace(string(f.Format())) 628 want := strings.TrimSpace(` 629 a_rule( 630 name = "name1", 631 # do not sort 632 hdrs = [ 633 "foo", 634 "bar", 635 "baz", 636 ], 637 deps = [ 638 "bar", 639 "baz", 640 "foo", 641 ], 642 ) 643 `) 644 645 if got != want { 646 t.Errorf("got:%s\nwant:%s", got, want) 647 } 648 } 649 650 func TestSimpleArgument(t *testing.T) { 651 f := EmptyFile("foo", "bar") 652 653 r := NewRule("export_files", "") 654 r.AddArg(&bzl.CallExpr{ 655 X: &bzl.Ident{Name: "glob"}, 656 List: []bzl.Expr{ 657 &bzl.ListExpr{ 658 List: []bzl.Expr{ 659 &bzl.StringExpr{Value: "**"}, 660 }, 661 }, 662 }, 663 }) 664 665 r.Insert(f) 666 f.Sync() 667 668 got := strings.TrimSpace(string(bzl.FormatWithoutRewriting(f.File))) 669 want := strings.TrimSpace(` 670 export_files(glob(["**"])) 671 `) 672 673 if got != want { 674 t.Errorf("got:%s\nwant:%s", got, want) 675 } 676 } 677 678 func TestAttributeValueSorting(t *testing.T) { 679 f := EmptyFile("foo", "bar") 680 681 r := NewRule("a_rule", "") 682 r.SetAttr("deps", []string{"foo", "bar", "baz"}) 683 r.SetAttr("srcs", UnsortedStrings{"foo", "bar", "baz"}) 684 r.SetAttr("hdrs", []string{"foo", "bar", "baz"}) 685 686 r.Insert(f) 687 f.Sync() 688 689 got := strings.TrimSpace(string(bzl.FormatWithoutRewriting(f.File))) 690 want := strings.TrimSpace(` 691 a_rule( 692 srcs = [ 693 "foo", 694 "bar", 695 "baz", 696 ], 697 hdrs = [ 698 "foo", 699 "bar", 700 "baz", 701 ], 702 deps = [ 703 "bar", 704 "baz", 705 "foo", 706 ], 707 ) 708 `) 709 710 if got != want { 711 t.Errorf("got:%s\nwant:%s", got, want) 712 } 713 } 714 715 func TestAttributeValueSortingOverride(t *testing.T) { 716 f := EmptyFile("foo", "bar") 717 718 r := NewRule("a_rule", "") 719 r.SetAttr("deps", []string{"foo", "bar", "baz"}) 720 r.SetAttr("srcs", UnsortedStrings{"foo", "bar", "baz"}) 721 r.SetAttr("hdrs", []string{"foo", "bar", "baz"}) 722 r.SetSortedAttrs([]string{"srcs", "hdrs"}) 723 724 r.Insert(f) 725 f.Sync() 726 727 got := strings.TrimSpace(string(bzl.FormatWithoutRewriting(f.File))) 728 want := strings.TrimSpace(` 729 a_rule( 730 srcs = [ 731 "foo", 732 "bar", 733 "baz", 734 ], 735 hdrs = [ 736 "bar", 737 "baz", 738 "foo", 739 ], 740 deps = [ 741 "foo", 742 "bar", 743 "baz", 744 ], 745 ) 746 `) 747 748 if got != want { 749 t.Errorf("got:%s\nwant:%s", got, want) 750 } 751 752 if !reflect.DeepEqual(r.SortedAttrs(), []string{"srcs", "hdrs"}) { 753 t.Errorf("Unexpected r.SortedAttrs(): %v", r.SortedAttrs()) 754 } 755 }