github.com/bazelbuild/bazel-gazelle@v0.36.1-0.20240520142334-61b277ba6fed/cmd/gazelle/fix_test.go (about) 1 /* Copyright 2016 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 main 17 18 import ( 19 "flag" 20 "fmt" 21 "os" 22 "path/filepath" 23 "runtime" 24 "strings" 25 "testing" 26 27 "github.com/bazelbuild/bazel-gazelle/testtools" 28 "github.com/bazelbuild/rules_go/go/tools/bazel" 29 ) 30 31 var goSdk = flag.String("go_sdk", "", "name of the go_sdk repository when invoked by Bazel") 32 33 func TestMain(m *testing.M) { 34 status := 1 35 defer func() { 36 os.Exit(status) 37 }() 38 39 flag.Parse() 40 41 var err error 42 tmpDir, err := os.MkdirTemp(os.Getenv("TEST_TMPDIR"), "gazelle_test") 43 if err != nil { 44 fmt.Fprintln(os.Stderr, err) 45 return 46 } 47 defer func() { 48 // Before deleting files in the temporary directory, add write permission 49 // to any files that don't have it. Files and directories in the module cache 50 // are read-only, and on Windows, the read-only bit prevents deletion and 51 // prevents Bazel from cleaning up the source tree. 52 _ = filepath.Walk(tmpDir, func(path string, info os.FileInfo, err error) error { 53 if err != nil { 54 return err 55 } 56 if mode := info.Mode(); mode&0o200 == 0 { 57 err = os.Chmod(path, mode|0o200) 58 } 59 return err 60 }) 61 os.RemoveAll(tmpDir) 62 }() 63 64 if *goSdk != "" { 65 // This flag is only set when the test is run by Bazel. Figure out where 66 // the Go binary is and set GOROOT appropriately. 67 entries, err := bazel.ListRunfiles() 68 if err != nil { 69 fmt.Fprintln(os.Stderr, err) 70 return 71 } 72 73 var goToolPath string 74 ext := "" 75 if runtime.GOOS == "windows" { 76 ext = ".exe" 77 } 78 for _, entry := range entries { 79 if entry.Workspace == *goSdk && entry.ShortPath == "bin/go"+ext { 80 goToolPath = entry.Path 81 break 82 } 83 } 84 if goToolPath == "" { 85 fmt.Fprintln(os.Stderr, "could not locate go tool") 86 return 87 } 88 os.Setenv("GOROOT", filepath.Dir(filepath.Dir(goToolPath))) 89 } 90 os.Setenv("GOCACHE", filepath.Join(tmpDir, "gocache")) 91 os.Setenv("GOPATH", filepath.Join(tmpDir, "gopath")) 92 93 status = m.Run() 94 } 95 96 func defaultArgs(dir string) []string { 97 return []string{ 98 "-repo_root", dir, 99 "-go_prefix", "example.com/repo", 100 dir, 101 } 102 } 103 104 func TestCreateFile(t *testing.T) { 105 // Create a directory with a simple .go file. 106 tmpdir := os.Getenv("TEST_TMPDIR") 107 dir, err := os.MkdirTemp(tmpdir, "") 108 if err != nil { 109 t.Fatalf("os.MkdirTemp(%q, %q) failed with %v; want success", tmpdir, "", err) 110 } 111 defer os.RemoveAll(dir) 112 113 goFile := filepath.Join(dir, "main.go") 114 if err = os.WriteFile(goFile, []byte("package main"), 0o600); err != nil { 115 t.Fatalf("error writing file %q: %v", goFile, err) 116 } 117 118 // Check that Gazelle creates a new file named "BUILD.bazel". 119 if err = run(dir, defaultArgs(dir)); err != nil { 120 t.Fatalf("run failed: %v", err) 121 } 122 123 buildFile := filepath.Join(dir, "BUILD.bazel") 124 if _, err = os.Stat(buildFile); err != nil { 125 t.Errorf("could not stat BUILD.bazel: %v", err) 126 } 127 } 128 129 func TestUpdateFile(t *testing.T) { 130 // Create a directory with a simple .go file and an empty BUILD file. 131 tmpdir := os.Getenv("TEST_TMPDIR") 132 dir, err := os.MkdirTemp(tmpdir, "") 133 if err != nil { 134 t.Fatalf("os.MkdirTemp(%q, %q) failed with %v; want success", tmpdir, "", err) 135 } 136 defer os.RemoveAll(dir) 137 138 goFile := filepath.Join(dir, "main.go") 139 if err = os.WriteFile(goFile, []byte("package main"), 0o600); err != nil { 140 t.Fatalf("error writing file %q: %v", goFile, err) 141 } 142 143 buildFile := filepath.Join(dir, "BUILD") 144 if err = os.WriteFile(buildFile, nil, 0o600); err != nil { 145 t.Fatalf("error writing file %q: %v", buildFile, err) 146 } 147 148 // Check that Gazelle updates the BUILD file in place. 149 if err = run(dir, defaultArgs(dir)); err != nil { 150 t.Fatalf("run failed: %v", err) 151 } 152 153 if st, err := os.Stat(buildFile); err != nil { 154 t.Errorf("could not stat BUILD: %v", err) 155 } else if st.Size() == 0 { 156 t.Errorf("BUILD was not updated") 157 } 158 159 if _, err = os.Stat(filepath.Join(dir, "BUILD.bazel")); err == nil { 160 t.Errorf("BUILD.bazel should not exist") 161 } 162 } 163 164 func TestNoChanges(t *testing.T) { 165 // Create a directory with a BUILD file that doesn't need any changes. 166 tmpdir := os.Getenv("TEST_TMPDIR") 167 dir, err := os.MkdirTemp(tmpdir, "") 168 if err != nil { 169 t.Fatalf("os.MkdirTemp(%q, %q) failed with %v; want success", tmpdir, "", err) 170 } 171 defer os.RemoveAll(dir) 172 173 goFile := filepath.Join(dir, "main.go") 174 if err = os.WriteFile(goFile, []byte("package main\n\nfunc main() {}"), 0o600); err != nil { 175 t.Fatalf("error writing file %q: %v", goFile, err) 176 } 177 178 buildFile := filepath.Join(dir, "BUILD") 179 if err = os.WriteFile(buildFile, []byte(`load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 180 181 go_library( 182 name = "go_default_library", 183 srcs = ["main.go"], 184 importpath = "example.com/repo", 185 visibility = ["//visibility:private"], 186 ) 187 188 go_binary( 189 name = "hello", 190 embed = [":go_default_library"], 191 visibility = ["//visibility:public"], 192 ) 193 `), 0o600); err != nil { 194 t.Fatalf("error writing file %q: %v", buildFile, err) 195 } 196 st, err := os.Stat(buildFile) 197 if err != nil { 198 t.Errorf("could not stat BUILD: %v", err) 199 } 200 modTime := st.ModTime() 201 202 // Ensure that Gazelle does not write to the BUILD file. 203 if err = run(dir, defaultArgs(dir)); err != nil { 204 t.Fatalf("run failed: %v", err) 205 } 206 207 if st, err := os.Stat(buildFile); err != nil { 208 t.Errorf("could not stat BUILD: %v", err) 209 } else if !modTime.Equal(st.ModTime()) { 210 t.Errorf("unexpected modificaiton to BUILD") 211 } 212 } 213 214 func TestFixReadWriteDir(t *testing.T) { 215 buildInFile := testtools.FileSpec{ 216 Path: "in/BUILD.in", 217 Content: ` 218 go_binary( 219 name = "hello", 220 pure = "on", 221 ) 222 `, 223 } 224 buildSrcFile := testtools.FileSpec{ 225 Path: "src/BUILD.bazel", 226 Content: `# src build file`, 227 } 228 oldFiles := []testtools.FileSpec{ 229 buildInFile, 230 buildSrcFile, 231 { 232 Path: "src/hello.go", 233 Content: ` 234 package main 235 236 func main() {} 237 `, 238 }, 239 { 240 Path: "out/BUILD", 241 Content: `this should get replaced`, 242 }, 243 } 244 245 for _, tc := range []struct { 246 desc string 247 args []string 248 want []testtools.FileSpec 249 }{ 250 { 251 desc: "read", 252 args: []string{ 253 "-repo_root={{dir}}/src", 254 "-experimental_read_build_files_dir={{dir}}/in", 255 "-build_file_name=BUILD.bazel,BUILD,BUILD.in", 256 "-go_prefix=example.com/repo", 257 "{{dir}}/src", 258 }, 259 want: []testtools.FileSpec{ 260 buildInFile, 261 { 262 Path: "src/BUILD.bazel", 263 Content: ` 264 load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 265 266 go_binary( 267 name = "hello", 268 embed = [":repo_lib"], 269 pure = "on", 270 visibility = ["//visibility:public"], 271 ) 272 273 go_library( 274 name = "repo_lib", 275 srcs = ["hello.go"], 276 importpath = "example.com/repo", 277 visibility = ["//visibility:private"], 278 ) 279 `, 280 }, 281 }, 282 }, { 283 desc: "write", 284 args: []string{ 285 "-repo_root={{dir}}/src", 286 "-experimental_write_build_files_dir={{dir}}/out", 287 "-build_file_name=BUILD.bazel,BUILD,BUILD.in", 288 "-go_prefix=example.com/repo", 289 "{{dir}}/src", 290 }, 291 want: []testtools.FileSpec{ 292 buildInFile, 293 buildSrcFile, 294 { 295 Path: "out/BUILD", 296 Content: ` 297 load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 298 299 # src build file 300 301 go_library( 302 name = "repo_lib", 303 srcs = ["hello.go"], 304 importpath = "example.com/repo", 305 visibility = ["//visibility:private"], 306 ) 307 308 go_binary( 309 name = "repo", 310 embed = [":repo_lib"], 311 visibility = ["//visibility:public"], 312 ) 313 `, 314 }, 315 }, 316 }, { 317 desc: "read_and_write", 318 args: []string{ 319 "-repo_root={{dir}}/src", 320 "-experimental_read_build_files_dir={{dir}}/in", 321 "-experimental_write_build_files_dir={{dir}}/out", 322 "-build_file_name=BUILD.bazel,BUILD,BUILD.in", 323 "-go_prefix=example.com/repo", 324 "{{dir}}/src", 325 }, 326 want: []testtools.FileSpec{ 327 buildInFile, 328 { 329 Path: "out/BUILD", 330 Content: ` 331 load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 332 333 go_binary( 334 name = "hello", 335 embed = [":repo_lib"], 336 pure = "on", 337 visibility = ["//visibility:public"], 338 ) 339 340 go_library( 341 name = "repo_lib", 342 srcs = ["hello.go"], 343 importpath = "example.com/repo", 344 visibility = ["//visibility:private"], 345 ) 346 `, 347 }, 348 }, 349 }, 350 } { 351 t.Run(tc.desc, func(t *testing.T) { 352 dir, cleanup := testtools.CreateFiles(t, oldFiles) 353 defer cleanup() 354 replacer := strings.NewReplacer("{{dir}}", dir, "/", string(os.PathSeparator)) 355 for i := range tc.args { 356 if strings.HasPrefix(tc.args[i], "-go_prefix=") { 357 continue // don't put backslashes in prefix on windows 358 } 359 tc.args[i] = replacer.Replace(tc.args[i]) 360 } 361 if err := run(dir, tc.args); err != nil { 362 t.Error(err) 363 } 364 testtools.CheckFiles(t, dir, tc.want) 365 }) 366 } 367 } 368 369 func TestFix_LangFilter(t *testing.T) { 370 fixture := []testtools.FileSpec{ 371 { 372 Path: "BUILD.bazel", 373 Content: ` 374 load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 375 376 go_binary( 377 name = "nofix", 378 library = ":go_default_library", 379 visibility = ["//visibility:public"], 380 ) 381 382 go_library( 383 name = "go_default_library", 384 srcs = ["main.go"], 385 importpath = "example.com/repo", 386 visibility = ["//visibility:public"], 387 )`, 388 }, 389 { 390 Path: "main.go", 391 Content: `package main`, 392 }, 393 } 394 395 dir, cleanup := testtools.CreateFiles(t, fixture) 396 defer cleanup() 397 398 // Check that Gazelle does not update the BUILD file, due to lang filter. 399 if err := run(dir, []string{ 400 "-repo_root", dir, 401 "-go_prefix", "example.com/repo", 402 "-lang=proto", 403 dir, 404 }); err != nil { 405 t.Fatalf("run failed: %v", err) 406 } 407 408 testtools.CheckFiles(t, dir, fixture) 409 } 410 411 func TestFix_MapKind_Argument(t *testing.T) { 412 for name, tc := range map[string]struct { 413 before []testtools.FileSpec 414 after []testtools.FileSpec 415 }{ 416 "same-name": { 417 before: []testtools.FileSpec{ 418 { 419 Path: "BUILD.bazel", 420 Content: ` 421 load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 422 423 # gazelle:map_kind go_binary go_binary //my:custom.bzl 424 425 maybe( 426 go_binary, 427 name = "nofix", 428 library = ":go_default_library", 429 visibility = ["//visibility:public"], 430 ) 431 432 go_library( 433 name = "go_default_library", 434 srcs = ["some.go"], 435 importpath = "example.com/repo", 436 visibility = ["//visibility:public"], 437 )`, 438 }, 439 { 440 Path: "some.go", 441 Content: `package some`, 442 }, 443 }, 444 after: []testtools.FileSpec{ 445 { 446 Path: "BUILD.bazel", 447 Content: ` 448 load("@io_bazel_rules_go//go:def.bzl", "go_library") 449 load("//my:custom.bzl", "go_binary") 450 451 # gazelle:map_kind go_binary go_binary //my:custom.bzl 452 453 maybe( 454 go_binary, 455 name = "nofix", 456 library = ":go_default_library", 457 visibility = ["//visibility:public"], 458 ) 459 460 go_library( 461 name = "go_default_library", 462 srcs = ["some.go"], 463 importpath = "example.com/repo", 464 visibility = ["//visibility:public"], 465 )`, 466 }, 467 }, 468 }, 469 "different-name": { 470 before: []testtools.FileSpec{ 471 { 472 Path: "BUILD.bazel", 473 Content: ` 474 load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 475 476 # gazelle:map_kind go_binary custom_go_binary //my:custom.bzl 477 478 maybe( 479 go_binary, 480 name = "nofix", 481 library = ":go_default_library", 482 visibility = ["//visibility:public"], 483 ) 484 485 go_library( 486 name = "go_default_library", 487 srcs = ["some.go"], 488 importpath = "example.com/repo", 489 visibility = ["//visibility:public"], 490 )`, 491 }, 492 { 493 Path: "some.go", 494 Content: `package some`, 495 }, 496 }, 497 after: []testtools.FileSpec{ 498 { 499 Path: "BUILD.bazel", 500 Content: ` 501 load("@io_bazel_rules_go//go:def.bzl", "go_library") 502 load("//my:custom.bzl", "custom_go_binary") 503 504 # gazelle:map_kind go_binary custom_go_binary //my:custom.bzl 505 506 maybe( 507 custom_go_binary, 508 name = "nofix", 509 library = ":go_default_library", 510 visibility = ["//visibility:public"], 511 ) 512 513 go_library( 514 name = "go_default_library", 515 srcs = ["some.go"], 516 importpath = "example.com/repo", 517 visibility = ["//visibility:public"], 518 )`, 519 }, 520 }, 521 }, 522 "non-loaded-symbol": { 523 before: []testtools.FileSpec{ 524 { 525 Path: "BUILD.bazel", 526 Content: ` 527 load("@io_bazel_rules_go//go:def.bzl", "go_library") 528 529 custom_go_library = go_library 530 531 # This will be ignored because it's not a directly loaded symbol when used: 532 # gazelle:map_kind custom_go_library custom_go_library //my:custom.bzl 533 534 maybe( 535 custom_go_library, 536 name = "nofix", 537 library = ":go_default_library", 538 visibility = ["//visibility:public"], 539 ) 540 541 go_library( 542 name = "go_default_library", 543 srcs = ["some.go"], 544 importpath = "example.com/repo", 545 visibility = ["//visibility:public"], 546 ) 547 `, 548 }, 549 { 550 Path: "some.go", 551 Content: `package some`, 552 }, 553 }, 554 after: []testtools.FileSpec{ 555 { 556 Path: "BUILD.bazel", 557 Content: ` 558 load("@io_bazel_rules_go//go:def.bzl", "go_library") 559 560 custom_go_library = go_library 561 562 # This will be ignored because it's not a directly loaded symbol when used: 563 # gazelle:map_kind custom_go_library custom_go_library //my:custom.bzl 564 565 maybe( 566 custom_go_library, 567 name = "nofix", 568 library = ":go_default_library", 569 visibility = ["//visibility:public"], 570 ) 571 572 go_library( 573 name = "go_default_library", 574 srcs = ["some.go"], 575 importpath = "example.com/repo", 576 visibility = ["//visibility:public"], 577 ) 578 `, 579 }, 580 }, 581 }, 582 "not-arg-0": { 583 before: []testtools.FileSpec{ 584 { 585 Path: "BUILD.bazel", 586 Content: ` 587 load("@io_bazel_rules_go//go:def.bzl", "go_library") 588 load("@other_rules//:def.bzl", "something_custom") 589 590 # gazelle:map_kind something_custom something_custom //my:custom.bzl 591 592 maybe( 593 go_library, 594 something_custom, 595 name = "nofix", 596 library = ":go_default_library", 597 visibility = ["//visibility:public"], 598 ) 599 600 go_library( 601 name = "go_default_library", 602 srcs = ["some.go"], 603 importpath = "example.com/repo", 604 visibility = ["//visibility:public"], 605 ) 606 `, 607 }, 608 { 609 Path: "some.go", 610 Content: `package some`, 611 }, 612 }, 613 after: []testtools.FileSpec{ 614 { 615 Path: "BUILD.bazel", 616 Content: ` 617 load("@io_bazel_rules_go//go:def.bzl", "go_library") 618 load("@other_rules//:def.bzl", "something_custom") 619 620 # gazelle:map_kind something_custom something_custom //my:custom.bzl 621 622 maybe( 623 go_library, 624 something_custom, 625 name = "nofix", 626 library = ":go_default_library", 627 visibility = ["//visibility:public"], 628 ) 629 630 go_library( 631 name = "go_default_library", 632 srcs = ["some.go"], 633 importpath = "example.com/repo", 634 visibility = ["//visibility:public"], 635 ) 636 `, 637 }, 638 }, 639 }, 640 } { 641 t.Run(name, func(t *testing.T) { 642 dir, cleanup := testtools.CreateFiles(t, tc.before) 643 defer cleanup() 644 645 if err := run(dir, []string{ 646 "-repo_root", dir, 647 "-go_prefix", "example.com/repo", 648 dir, 649 }); err != nil { 650 t.Fatalf("run failed: %v", err) 651 } 652 653 testtools.CheckFiles(t, dir, tc.after) 654 }) 655 } 656 }