github.com/bazelbuild/bazel-gazelle@v0.36.1-0.20240520142334-61b277ba6fed/language/go/fileinfo_test.go (about) 1 /* Copyright 2017 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 golang 17 18 import ( 19 "os" 20 "path/filepath" 21 "testing" 22 23 "github.com/google/go-cmp/cmp" 24 ) 25 26 func TestOtherFileInfo(t *testing.T) { 27 dir := "." 28 for _, tc := range []struct { 29 desc, name, source string 30 wantTags *buildTags 31 }{ 32 { 33 "empty file", 34 "foo.c", 35 "", 36 nil, 37 }, 38 { 39 "tags file", 40 "foo.c", 41 `// +build foo bar 42 // +build baz,!ignore 43 44 `, 45 &buildTags{ 46 expr: mustParseBuildTag(t, "(foo || bar) && (baz && !ignore)"), 47 rawTags: []string{"foo", "bar", "baz", "ignore"}, 48 }, 49 }, 50 } { 51 t.Run(tc.desc, func(t *testing.T) { 52 if err := os.WriteFile(tc.name, []byte(tc.source), 0o600); err != nil { 53 t.Fatal(err) 54 } 55 defer os.Remove(tc.name) 56 57 got := otherFileInfo(filepath.Join(dir, tc.name)) 58 59 // Only check that we can extract tags. Everything else is covered 60 // by other tests. 61 if diff := cmp.Diff(tc.wantTags, got.tags, fileInfoCmpOption); diff != "" { 62 t.Errorf("(-want, +got): %s", diff) 63 } 64 }) 65 } 66 } 67 68 func TestFileNameInfo(t *testing.T) { 69 for _, tc := range []struct { 70 desc, name string 71 want fileInfo 72 }{ 73 { 74 "simple go file", 75 "simple.go", 76 fileInfo{ 77 ext: goExt, 78 }, 79 }, 80 { 81 "simple go test", 82 "foo_test.go", 83 fileInfo{ 84 ext: goExt, 85 isTest: true, 86 }, 87 }, 88 { 89 "test source", 90 "test.go", 91 fileInfo{ 92 ext: goExt, 93 isTest: false, 94 }, 95 }, 96 { 97 "_test source", 98 "_test.go", 99 fileInfo{ 100 ext: unknownExt, 101 }, 102 }, 103 { 104 "source with goos", 105 "foo_linux.go", 106 fileInfo{ 107 ext: goExt, 108 goos: "linux", 109 }, 110 }, 111 { 112 "source with goarch", 113 "foo_amd64.go", 114 fileInfo{ 115 ext: goExt, 116 goarch: "amd64", 117 }, 118 }, 119 { 120 "source with goos then goarch", 121 "foo_linux_amd64.go", 122 fileInfo{ 123 ext: goExt, 124 goos: "linux", 125 goarch: "amd64", 126 }, 127 }, 128 { 129 "source with goarch then goos", 130 "foo_amd64_linux.go", 131 fileInfo{ 132 ext: goExt, 133 goos: "linux", 134 }, 135 }, 136 { 137 "test with goos and goarch", 138 "foo_linux_amd64_test.go", 139 fileInfo{ 140 ext: goExt, 141 goos: "linux", 142 goarch: "amd64", 143 isTest: true, 144 }, 145 }, 146 { 147 "test then goos", 148 "foo_test_linux.go", 149 fileInfo{ 150 ext: goExt, 151 goos: "linux", 152 }, 153 }, 154 { 155 "goos source", 156 "linux.go", 157 fileInfo{ 158 ext: goExt, 159 goos: "", 160 }, 161 }, 162 { 163 "goarch source", 164 "amd64.go", 165 fileInfo{ 166 ext: goExt, 167 goarch: "", 168 }, 169 }, 170 { 171 "goos test", 172 "linux_test.go", 173 fileInfo{ 174 ext: goExt, 175 goos: "", 176 isTest: true, 177 }, 178 }, 179 { 180 "c file", 181 "foo_test.cxx", 182 fileInfo{ 183 ext: cExt, 184 isTest: false, 185 }, 186 }, 187 { 188 "c os test file", 189 "foo_linux_test.c", 190 fileInfo{ 191 ext: cExt, 192 isTest: false, 193 goos: "linux", 194 }, 195 }, 196 { 197 "h file", 198 "foo_linux.h", 199 fileInfo{ 200 ext: hExt, 201 goos: "linux", 202 }, 203 }, 204 { 205 "go asm file", 206 "foo_amd64.s", 207 fileInfo{ 208 ext: sExt, 209 goarch: "amd64", 210 }, 211 }, 212 { 213 "c asm file", 214 "foo.S", 215 fileInfo{ 216 ext: csExt, 217 }, 218 }, 219 { 220 "unsupported file", 221 "foo.m", 222 fileInfo{ 223 ext: cExt, 224 }, 225 }, 226 { 227 "ignored test file", 228 "foo_test.py", 229 fileInfo{ 230 isTest: false, 231 }, 232 }, 233 { 234 "ignored xtest file", 235 "foo_xtest.py", 236 fileInfo{ 237 isTest: false, 238 }, 239 }, 240 { 241 "ignored file", 242 "foo.txt", 243 fileInfo{ 244 ext: unknownExt, 245 }, 246 }, 247 { 248 "hidden file", 249 ".foo.go", 250 fileInfo{ 251 ext: unknownExt, 252 }, 253 }, 254 } { 255 t.Run(tc.desc, func(t *testing.T) { 256 tc.want.name = tc.name 257 tc.want.path = filepath.Join("dir", tc.name) 258 got := fileNameInfo(tc.want.path) 259 if diff := cmp.Diff(tc.want, got, fileInfoCmpOption); diff != "" { 260 t.Errorf("(-want, +got): %s", diff) 261 } 262 }) 263 } 264 } 265 266 func TestReadTags(t *testing.T) { 267 for _, tc := range []struct { 268 desc, source string 269 want *buildTags 270 }{ 271 { 272 "empty file", 273 "", 274 nil, 275 }, 276 { 277 "single comment without blank line", 278 "// +build foo\npackage main", 279 nil, 280 }, 281 { 282 "multiple comments without blank link", 283 `// +build foo 284 285 // +build bar 286 package main 287 288 `, 289 &buildTags{ 290 expr: mustParseBuildTag(t, "foo"), 291 rawTags: []string{"foo"}, 292 }, 293 }, 294 { 295 "single comment", 296 "// +build foo\n\n", 297 &buildTags{ 298 expr: mustParseBuildTag(t, "foo"), 299 rawTags: []string{"foo"}, 300 }, 301 }, 302 { 303 "multiple comments", 304 `// +build foo 305 // +build bar 306 307 package main`, 308 &buildTags{ 309 expr: mustParseBuildTag(t, "foo && bar"), 310 rawTags: []string{"foo", "bar"}, 311 }, 312 }, 313 { 314 "multiple comments with blank", 315 `// +build foo 316 317 // +build bar 318 319 package main`, 320 &buildTags{ 321 expr: mustParseBuildTag(t, "foo && bar"), 322 rawTags: []string{"foo", "bar"}, 323 }, 324 }, 325 { 326 "Basic go:build", 327 `//go:build foo && bar 328 329 package main`, 330 &buildTags{ 331 expr: mustParseBuildTag(t, "foo && bar"), 332 rawTags: []string{"foo", "bar"}, 333 }, 334 }, 335 { 336 "Both go:build and +build", 337 `//go:build foo && bar 338 // +build foo,bar 339 340 package main`, 341 &buildTags{ 342 expr: mustParseBuildTag(t, "foo && bar"), 343 rawTags: []string{"foo", "bar"}, 344 }, 345 }, 346 { 347 "comment with space", 348 " // +build foo bar \n\n", 349 &buildTags{ 350 expr: mustParseBuildTag(t, "foo || bar"), 351 rawTags: []string{"foo", "bar"}, 352 }, 353 }, 354 { 355 "slash star comment", 356 "/* +build foo */\n\n", 357 nil, 358 }, 359 } { 360 t.Run(tc.desc, func(t *testing.T) { 361 f, err := os.CreateTemp(".", "TestReadTags") 362 if err != nil { 363 t.Fatal(err) 364 } 365 path := f.Name() 366 defer os.Remove(path) 367 368 if _, err := f.WriteString(tc.source); err != nil { 369 t.Fatal(err) 370 } 371 372 if got, err := readTags(path); err != nil { 373 t.Fatal(err) 374 } else if diff := cmp.Diff(tc.want, got, fileInfoCmpOption); diff != "" { 375 t.Errorf("(-want, +got): %s", diff) 376 } 377 }) 378 } 379 } 380 381 func TestCheckConstraints(t *testing.T) { 382 dir, err := os.MkdirTemp(os.Getenv("TEST_TEMPDIR"), "TestCheckConstraints") 383 if err != nil { 384 t.Fatal(err) 385 } 386 defer os.RemoveAll(dir) 387 for _, tc := range []struct { 388 desc string 389 genericTags map[string]bool 390 os, arch, filename, content string 391 want bool 392 }{ 393 { 394 desc: "unconstrained", 395 want: true, 396 }, { 397 desc: "goos satisfied", 398 filename: "foo_linux.go", 399 os: "linux", 400 want: true, 401 }, { 402 desc: "goos unsatisfied", 403 filename: "foo_linux.go", 404 os: "darwin", 405 want: false, 406 }, { 407 desc: "goarch satisfied", 408 filename: "foo_amd64.go", 409 arch: "amd64", 410 want: true, 411 }, { 412 desc: "goarch unsatisfied", 413 filename: "foo_amd64.go", 414 arch: "arm", 415 want: false, 416 }, { 417 desc: "goos goarch satisfied", 418 filename: "foo_linux_amd64.go", 419 os: "linux", 420 arch: "amd64", 421 want: true, 422 }, { 423 desc: "goos goarch unsatisfied", 424 filename: "foo_linux_amd64.go", 425 os: "darwin", 426 arch: "amd64", 427 want: false, 428 }, { 429 desc: "unix filename on darwin", 430 filename: "foo_unix.go", 431 os: "darwin", 432 want: true, 433 }, { 434 desc: "unix filename on windows", 435 filename: "foo_unix.go", 436 os: "windows", 437 want: true, 438 }, { 439 desc: "non-unix tag on linux", 440 filename: "foo_bar.go", 441 os: "darwin", 442 content: "//go:build !unix\n\npackage foo", 443 want: false, 444 }, { 445 desc: "non-unix tag on windows", 446 filename: "foo_bar.go", 447 os: "windows", 448 content: "//go:build !unix\n\npackage foo", 449 want: true, 450 }, { 451 desc: "unix tag on windows", 452 filename: "foo_bar.go", 453 os: "windows", 454 content: "//go:build unix\n\npackage foo", 455 want: false, 456 }, { 457 desc: "unix tag on linux", 458 filename: "foo_bar.go", 459 os: "linux", 460 content: "//go:build unix\n\npackage foo", 461 want: true, 462 }, { 463 desc: "goos unsatisfied tags satisfied", 464 filename: "foo_linux.go", 465 content: "// +build foo\n\npackage foo", 466 want: false, 467 }, { 468 desc: "tags all satisfied", 469 genericTags: map[string]bool{"a": true, "b": true}, 470 content: "// +build a,b\n\npackage foo", 471 want: true, 472 }, { 473 desc: "tags some satisfied", 474 genericTags: map[string]bool{"a": true}, 475 content: "// +build a,b\n\npackage foo", 476 want: false, 477 }, { 478 desc: "tag unsatisfied negated", 479 content: "// +build !a\n\npackage foo", 480 want: true, 481 }, { 482 desc: "tag satisfied negated", 483 genericTags: map[string]bool{"a": true}, 484 content: "// +build !a\n\npackage foo", 485 want: false, 486 }, { 487 desc: "tag double negative", 488 content: "// +build !!a\n\npackage foo", 489 want: false, 490 }, { 491 desc: "tag group and satisfied", 492 genericTags: map[string]bool{"foo": true, "bar": true}, 493 content: "// +build foo,bar\n\npackage foo", 494 want: true, 495 }, { 496 desc: "tag group and unsatisfied", 497 genericTags: map[string]bool{"foo": true}, 498 content: "// +build foo,bar\n\npackage foo", 499 want: false, 500 }, { 501 desc: "tag line or satisfied", 502 genericTags: map[string]bool{"foo": true}, 503 content: "// +build foo bar\n\npackage foo", 504 want: true, 505 }, { 506 desc: "tag line or unsatisfied", 507 genericTags: map[string]bool{"foo": true}, 508 content: "// +build !foo bar\n\npackage foo", 509 want: false, 510 }, { 511 desc: "tag lines and satisfied", 512 genericTags: map[string]bool{"foo": true, "bar": true}, 513 content: ` 514 // +build foo 515 // +build bar 516 517 package foo`, 518 want: true, 519 }, { 520 desc: "tag lines and unsatisfied", 521 genericTags: map[string]bool{"foo": true}, 522 content: ` 523 // +build foo 524 // +build bar 525 526 package foo`, 527 want: false, 528 }, { 529 desc: "cgo tags satisfied", 530 os: "linux", 531 genericTags: map[string]bool{"foo": true}, 532 content: ` 533 // +build foo 534 535 package foo 536 537 /* 538 #cgo linux CFLAGS: -Ilinux 539 */ 540 import "C" 541 `, 542 want: true, 543 }, { 544 desc: "cgo tags unsatisfied", 545 os: "linux", 546 content: ` 547 package foo 548 549 /* 550 #cgo !linux CFLAGS: -Inotlinux 551 */ 552 import "C" 553 `, 554 want: false, 555 }, { 556 desc: "release tags", 557 content: "// +build go1.7,go1.8,go1.9,go1.91,go2.0\n\npackage foo", 558 want: true, 559 }, { 560 desc: "release tag negated", 561 content: "// +build !go1.8\n\npackage foo", 562 want: true, 563 }, { 564 desc: "cgo tag", 565 content: "// +build cgo", 566 want: true, 567 }, { 568 desc: "cgo tag negated", 569 content: "// +build !cgo", 570 want: true, 571 }, { 572 desc: "race msan tags", 573 content: "// +build msan race", 574 want: true, 575 }, { 576 desc: "race msan tags negated", 577 content: "//+ build !msan,!race", 578 want: true, 579 }, 580 } { 581 t.Run(tc.desc, func(t *testing.T) { 582 c, _, _ := testConfig(t) 583 gc := getGoConfig(c) 584 gc.genericTags = tc.genericTags 585 if gc.genericTags == nil { 586 gc.genericTags = map[string]bool{"gc": true} 587 } 588 filename := tc.filename 589 if filename == "" { 590 filename = tc.desc + ".go" 591 } 592 content := []byte(tc.content) 593 if len(content) == 0 { 594 content = []byte(`package foo`) 595 } 596 597 path := filepath.Join(dir, filename) 598 if err := os.WriteFile(path, content, 0o666); err != nil { 599 t.Fatal(err) 600 } 601 602 fi := goFileInfo(path, "") 603 var cgoTags *cgoTagsAndOpts 604 if len(fi.copts) > 0 { 605 cgoTags = fi.copts[0] 606 } 607 608 got := checkConstraints(c, tc.os, tc.arch, fi.goos, fi.goarch, fi.tags, cgoTags) 609 if diff := cmp.Diff(tc.want, got); diff != "" { 610 t.Errorf("(-want, +got): %s", diff) 611 } 612 }) 613 } 614 } 615 616 func TestIsOSArchSpecific(t *testing.T) { 617 for _, tc := range []struct { 618 desc string 619 filename, content string 620 621 expectOSSpecific bool 622 expectArchSpecific bool 623 }{ 624 { 625 desc: "normal", 626 filename: "foo.go", 627 content: "package foo", 628 expectOSSpecific: false, 629 expectArchSpecific: false, 630 }, 631 { 632 desc: "unix directive", 633 filename: "foo.go", 634 content: "//go:build unix\n\npackage foo", 635 expectOSSpecific: true, 636 expectArchSpecific: false, 637 }, 638 { 639 desc: "exclude-unix directive", 640 filename: "foo.go", 641 content: "//go:build !unix\n\npackage foo", 642 expectOSSpecific: true, 643 expectArchSpecific: false, 644 }, 645 { 646 desc: "arch directive", 647 filename: "foo.go", 648 content: "//go:build arm64\n\npackage foo", 649 expectOSSpecific: false, 650 expectArchSpecific: true, 651 }, 652 { 653 desc: "exclude-arch directive", 654 filename: "foo.go", 655 content: "//go:build !arm64\n\npackage foo", 656 expectOSSpecific: false, 657 expectArchSpecific: true, 658 }, 659 { 660 desc: "os directive", 661 filename: "foo.go", 662 content: "//go:build linux\n\npackage foo", 663 expectOSSpecific: true, 664 expectArchSpecific: false, 665 }, 666 { 667 desc: "exclude-os directive", 668 filename: "foo.go", 669 content: "//go:build !linux\n\npackage foo", 670 expectOSSpecific: true, 671 expectArchSpecific: false, 672 }, 673 { 674 desc: "os and arch directive", 675 filename: "foo.go", 676 content: "//go:build linux && amd64\n\npackage foo", 677 expectOSSpecific: true, 678 expectArchSpecific: true, 679 }, 680 { 681 desc: "unix and arch directive", 682 filename: "foo.go", 683 content: "//go:build unix && amd64\n\npackage foo", 684 expectOSSpecific: true, 685 expectArchSpecific: true, 686 }, 687 } { 688 t.Run(tc.desc, func(t *testing.T) { 689 tmpDir, err := os.MkdirTemp(os.Getenv("TEST_TEMPDIR"), "TestIsOSSpecific_*") 690 if err != nil { 691 t.Fatal(err) 692 } 693 t.Cleanup(func() { 694 os.RemoveAll(tmpDir) 695 }) 696 697 path := filepath.Join(tmpDir, tc.filename) 698 if err := os.WriteFile(path, []byte(tc.content), 0o666); err != nil { 699 t.Fatal(err) 700 } 701 fi := goFileInfo(path, "") 702 var cgoTags *cgoTagsAndOpts 703 if len(fi.copts) > 0 { 704 cgoTags = fi.copts[0] 705 } 706 707 gotOSSpecific, gotArchSpecific := isOSArchSpecific(fi, cgoTags) 708 if diff := cmp.Diff(tc.expectOSSpecific, gotOSSpecific); diff != "" { 709 t.Errorf("(-want, +got): %s", diff) 710 } 711 if diff := cmp.Diff(tc.expectArchSpecific, gotArchSpecific); diff != "" { 712 t.Errorf("(-want, +got): %s", diff) 713 } 714 }) 715 } 716 }