github.com/chenfeining/golangci-lint@v1.0.2-0.20230730162517-14c6c67868df/test/run_test.go (about) 1 package test 2 3 import ( 4 "path/filepath" 5 "testing" 6 7 "github.com/stretchr/testify/assert" 8 _ "github.com/valyala/quicktemplate" 9 10 "github.com/chenfeining/golangci-lint/pkg/exitcodes" 11 "github.com/chenfeining/golangci-lint/test/testshared" 12 ) 13 14 const minimalPkg = "minimalpkg" 15 16 func TestAutogeneratedNoIssues(t *testing.T) { 17 testshared.NewRunnerBuilder(t). 18 WithTargetPath(testdataDir, "autogenerated"). 19 Runner(). 20 Install(). 21 Run(). 22 ExpectNoIssues() 23 } 24 25 func TestEmptyDirRun(t *testing.T) { 26 testshared.NewRunnerBuilder(t). 27 WithEnviron("GO111MODULE=off"). 28 WithTargetPath(testdataDir, "nogofiles"). 29 Runner(). 30 Install(). 31 Run(). 32 ExpectExitCode(exitcodes.NoGoFiles). 33 ExpectOutputContains(": no go files to analyze") 34 } 35 36 func TestNotExistingDirRun(t *testing.T) { 37 testshared.NewRunnerBuilder(t). 38 WithEnviron("GO111MODULE=off"). 39 WithTargetPath(testdataDir, "no_such_dir"). 40 Runner(). 41 Install(). 42 Run(). 43 ExpectExitCode(exitcodes.Failure). 44 ExpectOutputContains("cannot find package"). 45 ExpectOutputContains(testshared.NormalizeFileInString("/testdata/no_such_dir")) 46 } 47 48 func TestSymlinkLoop(t *testing.T) { 49 testshared.NewRunnerBuilder(t). 50 WithTargetPath(testdataDir, "symlink_loop", "..."). 51 Runner(). 52 Install(). 53 Run(). 54 ExpectNoIssues() 55 } 56 57 // TODO(ldez): remove this in v2. 58 func TestDeadline(t *testing.T) { 59 projectRoot := filepath.Join("..", "...") 60 61 testshared.NewRunnerBuilder(t). 62 WithArgs("--deadline=1ms"). 63 WithTargetPath(projectRoot). 64 Runner(). 65 Install(). 66 Run(). 67 ExpectExitCode(exitcodes.Timeout). 68 ExpectOutputContains(`Timeout exceeded: try increasing it by passing --timeout option`) 69 } 70 71 func TestTimeout(t *testing.T) { 72 projectRoot := filepath.Join("..", "...") 73 74 testshared.NewRunnerBuilder(t). 75 WithArgs("--timeout=1ms"). 76 WithTargetPath(projectRoot). 77 Runner(). 78 Install(). 79 Run(). 80 ExpectExitCode(exitcodes.Timeout). 81 ExpectOutputContains(`Timeout exceeded: try increasing it by passing --timeout option`) 82 } 83 84 func TestTimeoutInConfig(t *testing.T) { 85 cases := []struct { 86 cfg string 87 }{ 88 { 89 cfg: ` 90 run: 91 deadline: 1ms 92 `, 93 }, 94 { 95 cfg: ` 96 run: 97 timeout: 1ms 98 `, 99 }, 100 { 101 // timeout should override deadline 102 cfg: ` 103 run: 104 deadline: 100s 105 timeout: 1ms 106 `, 107 }, 108 } 109 110 testshared.InstallGolangciLint(t) 111 112 for _, c := range cases { 113 // Run with disallowed option set only in config 114 testshared.NewRunnerBuilder(t). 115 WithConfig(c.cfg). 116 WithTargetPath(testdataDir, minimalPkg). 117 Runner(). 118 Run(). 119 ExpectExitCode(exitcodes.Timeout). 120 ExpectOutputContains(`Timeout exceeded: try increasing it by passing --timeout option`) 121 } 122 } 123 124 func TestTestsAreLintedByDefault(t *testing.T) { 125 testshared.NewRunnerBuilder(t). 126 WithTargetPath(testdataDir, "withtests"). 127 Runner(). 128 Install(). 129 Run(). 130 ExpectHasIssue("don't use `init` function") 131 } 132 133 func TestCgoOk(t *testing.T) { 134 testshared.NewRunnerBuilder(t). 135 WithNoConfig(). 136 WithArgs( 137 "--timeout=3m", 138 "--enable-all", 139 "-D", 140 "nosnakecase,gci", 141 ). 142 WithTargetPath(testdataDir, "cgo"). 143 Runner(). 144 Install(). 145 Run(). 146 ExpectNoIssues() 147 } 148 149 func TestCgoWithIssues(t *testing.T) { 150 testshared.InstallGolangciLint(t) 151 152 testCases := []struct { 153 desc string 154 args []string 155 dir string 156 expected string 157 }{ 158 { 159 desc: "govet", 160 args: []string{"--no-config", "--disable-all", "-Egovet"}, 161 dir: "cgo_with_issues", 162 expected: "Printf format %t has arg cs of wrong type", 163 }, 164 { 165 desc: "staticcheck", 166 args: []string{"--no-config", "--disable-all", "-Estaticcheck"}, 167 dir: "cgo_with_issues", 168 expected: "SA5009: Printf format %t has arg #1 of wrong type", 169 }, 170 { 171 desc: "gofmt", 172 args: []string{"--no-config", "--disable-all", "-Egofmt"}, 173 dir: "cgo_with_issues", 174 expected: "File is not `gofmt`-ed with `-s` (gofmt)", 175 }, 176 { 177 desc: "revive", 178 args: []string{"--no-config", "--disable-all", "-Erevive"}, 179 dir: "cgo_with_issues", 180 expected: "indent-error-flow: if block ends with a return statement", 181 }, 182 } 183 184 for _, test := range testCases { 185 test := test 186 t.Run(test.desc, func(t *testing.T) { 187 t.Parallel() 188 189 testshared.NewRunnerBuilder(t). 190 WithArgs(test.args...). 191 WithTargetPath(testdataDir, test.dir). 192 Runner(). 193 Run(). 194 ExpectHasIssue(test.expected) 195 }) 196 } 197 } 198 199 // https://pkg.go.dev/cmd/compile#hdr-Compiler_Directives 200 func TestLineDirective(t *testing.T) { 201 testshared.InstallGolangciLint(t) 202 203 testCases := []struct { 204 desc string 205 args []string 206 configPath string 207 targetPath string 208 expected string 209 }{ 210 { 211 desc: "dupl", 212 args: []string{ 213 "-Edupl", 214 "--disable-all", 215 }, 216 configPath: "testdata/linedirective/dupl.yml", 217 targetPath: "linedirective", 218 expected: "21-23 lines are duplicate of `testdata/linedirective/hello.go:25-27` (dupl)", 219 }, 220 { 221 desc: "gofmt", 222 args: []string{ 223 "-Egofmt", 224 "--disable-all", 225 }, 226 targetPath: "linedirective", 227 expected: "File is not `gofmt`-ed with `-s` (gofmt)", 228 }, 229 { 230 desc: "goimports", 231 args: []string{ 232 "-Egoimports", 233 "--disable-all", 234 }, 235 targetPath: "linedirective", 236 expected: "File is not `goimports`-ed (goimports)", 237 }, 238 { 239 desc: "gomodguard", 240 args: []string{ 241 "-Egomodguard", 242 "--disable-all", 243 }, 244 configPath: "testdata/linedirective/gomodguard.yml", 245 targetPath: "linedirective", 246 expected: "import of package `golang.org/x/tools/go/analysis` is blocked because the module is not " + 247 "in the allowed modules list. (gomodguard)", 248 }, 249 { 250 desc: "lll", 251 args: []string{ 252 "-Elll", 253 "--disable-all", 254 }, 255 configPath: "testdata/linedirective/lll.yml", 256 targetPath: "linedirective", 257 expected: "line is 57 characters (lll)", 258 }, 259 { 260 desc: "misspell", 261 args: []string{ 262 "-Emisspell", 263 "--disable-all", 264 }, 265 configPath: "", 266 targetPath: "linedirective", 267 expected: "is a misspelling of `language` (misspell)", 268 }, 269 { 270 desc: "wsl", 271 args: []string{ 272 "-Ewsl", 273 "--disable-all", 274 }, 275 configPath: "", 276 targetPath: "linedirective", 277 expected: "block should not start with a whitespace (wsl)", 278 }, 279 } 280 281 for _, test := range testCases { 282 test := test 283 t.Run(test.desc, func(t *testing.T) { 284 t.Parallel() 285 286 testshared.NewRunnerBuilder(t). 287 WithArgs(test.args...). 288 WithTargetPath(testdataDir, test.targetPath). 289 WithConfigFile(test.configPath). 290 Runner(). 291 Run(). 292 ExpectHasIssue(test.expected) 293 }) 294 } 295 } 296 297 // https://pkg.go.dev/cmd/compile#hdr-Compiler_Directives 298 func TestLineDirectiveProcessedFiles(t *testing.T) { 299 testCases := []struct { 300 desc string 301 args []string 302 target string 303 expected []string 304 }{ 305 { 306 desc: "lite loading", 307 args: []string{ 308 "--print-issued-lines=false", 309 "--exclude-use-default=false", 310 "-Erevive", 311 }, 312 target: "quicktemplate", 313 expected: []string{ 314 "testdata/quicktemplate/hello.qtpl.go:10:1: package-comments: should have a package comment (revive)", 315 "testdata/quicktemplate/hello.qtpl.go:26:1: exported: exported function StreamHello should have comment or be unexported (revive)", 316 "testdata/quicktemplate/hello.qtpl.go:39:1: exported: exported function WriteHello should have comment or be unexported (revive)", 317 "testdata/quicktemplate/hello.qtpl.go:50:1: exported: exported function Hello should have comment or be unexported (revive)", 318 }, 319 }, 320 { 321 desc: "full loading", 322 args: []string{ 323 "--print-issued-lines=false", 324 "--exclude-use-default=false", 325 "-Erevive,govet", 326 }, 327 target: "quicktemplate", 328 expected: []string{ 329 "testdata/quicktemplate/hello.qtpl.go:10:1: package-comments: should have a package comment (revive)", 330 "testdata/quicktemplate/hello.qtpl.go:26:1: exported: exported function StreamHello should have comment or be unexported (revive)", 331 "testdata/quicktemplate/hello.qtpl.go:39:1: exported: exported function WriteHello should have comment or be unexported (revive)", 332 "testdata/quicktemplate/hello.qtpl.go:50:1: exported: exported function Hello should have comment or be unexported (revive)", 333 }, 334 }, 335 } 336 337 for _, test := range testCases { 338 test := test 339 t.Run(test.desc, func(t *testing.T) { 340 t.Parallel() 341 342 testshared.NewRunnerBuilder(t). 343 WithNoConfig(). 344 WithArgs(test.args...). 345 WithTargetPath(testdataDir, test.target). 346 Runner(). 347 Install(). 348 Run(). 349 ExpectExitCode(exitcodes.IssuesFound). 350 ExpectOutputContains(test.expected...) 351 }) 352 } 353 } 354 355 func TestUnsafeOk(t *testing.T) { 356 testshared.NewRunnerBuilder(t). 357 WithNoConfig(). 358 WithArgs("--enable-all"). 359 WithTargetPath(testdataDir, "unsafe"). 360 Runner(). 361 Install(). 362 Run(). 363 ExpectNoIssues() 364 } 365 366 func TestSortedResults(t *testing.T) { 367 testCases := []struct { 368 opt string 369 want string 370 }{ 371 { 372 opt: "--sort-results=false", 373 want: "testdata/sort_results/main.go:15:13: Error return value is not checked (errcheck)" + "\n" + 374 "testdata/sort_results/main.go:12:5: var `db` is unused (unused)", 375 }, 376 { 377 opt: "--sort-results=true", 378 want: "testdata/sort_results/main.go:12:5: var `db` is unused (unused)" + "\n" + 379 "testdata/sort_results/main.go:15:13: Error return value is not checked (errcheck)", 380 }, 381 } 382 383 testshared.InstallGolangciLint(t) 384 385 for _, test := range testCases { 386 test := test 387 t.Run(test.opt, func(t *testing.T) { 388 t.Parallel() 389 390 testshared.NewRunnerBuilder(t). 391 WithNoConfig(). 392 WithArgs("--print-issued-lines=false", test.opt). 393 WithTargetPath(testdataDir, "sort_results"). 394 Runner(). 395 Run(). 396 ExpectExitCode(exitcodes.IssuesFound).ExpectOutputEq(test.want + "\n") 397 }) 398 } 399 } 400 401 func TestSkippedDirsNoMatchArg(t *testing.T) { 402 dir := filepath.Join(testdataDir, "skipdirs", "skip_me", "nested") 403 404 testshared.NewRunnerBuilder(t). 405 WithNoConfig(). 406 WithArgs( 407 "--print-issued-lines=false", 408 "--skip-dirs", dir, 409 "-Erevive", 410 ). 411 WithTargetPath(dir). 412 Runner(). 413 Install(). 414 Run(). 415 ExpectExitCode(exitcodes.IssuesFound). 416 ExpectOutputEq("testdata/skipdirs/skip_me/nested/with_issue.go:8:9: " + 417 "indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (revive)\n") 418 } 419 420 func TestSkippedDirsTestdata(t *testing.T) { 421 testshared.NewRunnerBuilder(t). 422 WithNoConfig(). 423 WithArgs( 424 "--print-issued-lines=false", 425 "-Erevive", 426 ). 427 WithTargetPath(testdataDir, "skipdirs", "..."). 428 Runner(). 429 Install(). 430 Run(). 431 ExpectNoIssues() // all was skipped because in testdata 432 } 433 434 func TestIdentifierUsedOnlyInTests(t *testing.T) { 435 testshared.NewRunnerBuilder(t). 436 WithNoConfig(). 437 WithArgs("--disable-all", "-Eunused"). 438 WithTargetPath(testdataDir, "used_only_in_tests"). 439 Runner(). 440 Install(). 441 Run(). 442 ExpectNoIssues() 443 } 444 445 func TestUnusedCheckExported(t *testing.T) { 446 testshared.NewRunnerBuilder(t). 447 WithConfigFile("testdata_etc/unused_exported/golangci.yml"). 448 WithTargetPath("testdata_etc/unused_exported/..."). 449 Runner(). 450 Install(). 451 Run(). 452 ExpectNoIssues() 453 } 454 455 func TestConfigFileIsDetected(t *testing.T) { 456 testshared.InstallGolangciLint(t) 457 458 testCases := []struct { 459 desc string 460 targetPath string 461 }{ 462 { 463 desc: "explicit", 464 targetPath: filepath.Join(testdataDir, "withconfig", "pkg"), 465 }, 466 { 467 desc: "recursive", 468 targetPath: filepath.Join(testdataDir, "withconfig", "..."), 469 }, 470 } 471 472 for _, test := range testCases { 473 test := test 474 t.Run(test.desc, func(t *testing.T) { 475 t.Parallel() 476 477 testshared.NewRunnerBuilder(t). 478 // WithNoConfig(). 479 WithTargetPath(test.targetPath). 480 Runner(). 481 Run(). 482 ExpectExitCode(exitcodes.Success). 483 // test config contains InternalTest: true, it triggers such output 484 ExpectOutputEq("test\n") 485 }) 486 } 487 } 488 489 func TestEnableAllFastAndEnableCanCoexist(t *testing.T) { 490 testshared.InstallGolangciLint(t) 491 492 testCases := []struct { 493 desc string 494 args []string 495 expected []int 496 }{ 497 { 498 desc: "fast", 499 args: []string{"--fast", "--enable-all", "--enable=typecheck"}, 500 expected: []int{exitcodes.Success, exitcodes.IssuesFound}, 501 }, 502 { 503 desc: "all", 504 args: []string{"--enable-all", "--enable=typecheck"}, 505 expected: []int{exitcodes.Failure}, 506 }, 507 } 508 509 for _, test := range testCases { 510 test := test 511 t.Run(test.desc, func(t *testing.T) { 512 t.Parallel() 513 514 testshared.NewRunnerBuilder(t). 515 WithNoConfig(). 516 WithArgs(test.args...). 517 WithTargetPath(testdataDir, minimalPkg). 518 Runner(). 519 Run(). 520 ExpectExitCode(test.expected...) 521 }) 522 } 523 } 524 525 func TestEnabledPresetsAreNotDuplicated(t *testing.T) { 526 testshared.NewRunnerBuilder(t). 527 WithNoConfig(). 528 WithArgs("-v", "-p", "style,bugs"). 529 WithTargetPath(testdataDir, minimalPkg). 530 Runner(). 531 Install(). 532 Run(). 533 ExpectOutputContains("Active presets: [bugs style]") 534 } 535 536 func TestAbsPathDirAnalysis(t *testing.T) { 537 dir := filepath.Join("testdata_etc", "abspath") // abs paths don't work with testdata dir 538 absDir, err := filepath.Abs(dir) 539 assert.NoError(t, err) 540 541 testshared.NewRunnerBuilder(t). 542 WithNoConfig(). 543 WithArgs( 544 "--print-issued-lines=false", 545 "-Erevive", 546 ). 547 WithTargetPath(absDir). 548 Runner(). 549 Install(). 550 Run(). 551 ExpectHasIssue("testdata_etc/abspath/with_issue.go:8:9: " + 552 "indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (revive)") 553 } 554 555 func TestAbsPathFileAnalysis(t *testing.T) { 556 dir := filepath.Join("testdata_etc", "abspath", "with_issue.go") // abs paths don't work with testdata dir 557 absDir, err := filepath.Abs(dir) 558 assert.NoError(t, err) 559 560 testshared.NewRunnerBuilder(t). 561 WithNoConfig(). 562 WithArgs( 563 "--print-issued-lines=false", 564 "-Erevive", 565 ). 566 WithTargetPath(absDir). 567 Runner(). 568 Install(). 569 Run(). 570 ExpectHasIssue("indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (revive)") 571 } 572 573 func TestDisallowedOptionsInConfig(t *testing.T) { 574 cases := []struct { 575 cfg string 576 option string 577 }{ 578 { 579 cfg: ` 580 ruN: 581 Args: 582 - 1 583 `, 584 }, 585 { 586 cfg: ` 587 run: 588 CPUProfilePath: path 589 `, 590 option: "--cpu-profile-path=path", 591 }, 592 { 593 cfg: ` 594 run: 595 MemProfilePath: path 596 `, 597 option: "--mem-profile-path=path", 598 }, 599 { 600 cfg: ` 601 run: 602 TracePath: path 603 `, 604 option: "--trace-path=path", 605 }, 606 { 607 cfg: ` 608 run: 609 Verbose: true 610 `, 611 option: "-v", 612 }, 613 } 614 615 testshared.InstallGolangciLint(t) 616 617 for _, c := range cases { 618 // Run with disallowed option set only in config 619 testshared.NewRunnerBuilder(t). 620 WithConfig(c.cfg). 621 WithTargetPath(testdataDir, minimalPkg). 622 Runner(). 623 Run(). 624 ExpectExitCode(exitcodes.Failure) 625 626 if c.option == "" { 627 continue 628 } 629 630 args := []string{c.option, "--fast"} 631 632 // Run with disallowed option set only in command-line 633 testshared.NewRunnerBuilder(t). 634 WithNoConfig(). 635 WithArgs(args...). 636 WithTargetPath(testdataDir, minimalPkg). 637 Runner(). 638 Run(). 639 ExpectExitCode(exitcodes.Success) 640 641 // Run with disallowed option set both in command-line and in config 642 643 testshared.NewRunnerBuilder(t). 644 WithConfig(c.cfg). 645 WithArgs(args...). 646 WithTargetPath(testdataDir, minimalPkg). 647 Runner(). 648 Run(). 649 ExpectExitCode(exitcodes.Failure) 650 } 651 } 652 653 func TestPathPrefix(t *testing.T) { 654 testCases := []struct { 655 desc string 656 args []string 657 pattern string 658 }{ 659 { 660 desc: "empty", 661 pattern: "^testdata/withtests/", 662 }, 663 { 664 desc: "prefixed", 665 args: []string{"--path-prefix=cool"}, 666 pattern: "^cool/testdata/withtests", 667 }, 668 } 669 670 testshared.InstallGolangciLint(t) 671 672 for _, test := range testCases { 673 test := test 674 t.Run(test.desc, func(t *testing.T) { 675 testshared.NewRunnerBuilder(t). 676 WithArgs(test.args...). 677 WithTargetPath(testdataDir, "withtests"). 678 Runner(). 679 Run(). 680 ExpectOutputRegexp(test.pattern) 681 }) 682 } 683 }