package test import ( "path/filepath" "strings" "testing" "github.com/stretchr/testify/assert" _ "github.com/valyala/quicktemplate" "github.com/golangci/golangci-lint/pkg/exitcodes" "github.com/golangci/golangci-lint/test/testshared" ) func getCommonRunArgs() []string { return []string{"--skip-dirs", "testdata_etc/,pkg/golinters/goanalysis/(checker|passes)"} } func withCommonRunArgs(args ...string) []string { return append(getCommonRunArgs(), args...) } func TestAutogeneratedNoIssues(t *testing.T) { testshared.NewLintRunner(t).Run(getTestDataDir("autogenerated")).ExpectNoIssues() } func TestEmptyDirRun(t *testing.T) { testshared.NewLintRunner(t, "GO111MODULE=off").Run(getTestDataDir("nogofiles")). ExpectExitCode(exitcodes.NoGoFiles). ExpectOutputContains(": no go files to analyze") } func TestNotExistingDirRun(t *testing.T) { testshared.NewLintRunner(t, "GO111MODULE=off").Run(getTestDataDir("no_such_dir")). ExpectExitCode(exitcodes.Failure). ExpectOutputContains("cannot find package"). ExpectOutputContains("/testdata/no_such_dir") } func TestSymlinkLoop(t *testing.T) { testshared.NewLintRunner(t).Run(getTestDataDir("symlink_loop", "...")).ExpectNoIssues() } func TestDeadline(t *testing.T) { testshared.NewLintRunner(t).Run("--deadline=1ms", getProjectRoot()). ExpectExitCode(exitcodes.Timeout). ExpectOutputContains(`Timeout exceeded: try increasing it by passing --timeout option`) } func TestTimeout(t *testing.T) { testshared.NewLintRunner(t).Run("--timeout=1ms", getProjectRoot()). ExpectExitCode(exitcodes.Timeout). ExpectOutputContains(`Timeout exceeded: try increasing it by passing --timeout option`) } func TestTimeoutInConfig(t *testing.T) { type tc struct { cfg string } cases := []tc{ { cfg: ` run: deadline: 1ms `, }, { cfg: ` run: timeout: 1ms `, }, { // timeout should override deadline cfg: ` run: deadline: 100s timeout: 1ms `, }, } r := testshared.NewLintRunner(t) for _, c := range cases { // Run with disallowed option set only in config r.RunWithYamlConfig(c.cfg, withCommonRunArgs(minimalPkg)...).ExpectExitCode(exitcodes.Timeout). ExpectOutputContains(`Timeout exceeded: try increasing it by passing --timeout option`) } } func TestTestsAreLintedByDefault(t *testing.T) { testshared.NewLintRunner(t).Run(getTestDataDir("withtests")). ExpectHasIssue("`if` block ends with a `return`") } func TestCgoOk(t *testing.T) { testshared.NewLintRunner(t).Run("--no-config", "--enable-all", getTestDataDir("cgo")).ExpectNoIssues() } func TestCgoWithIssues(t *testing.T) { r := testshared.NewLintRunner(t) r.Run("--no-config", "--disable-all", "-Egovet", getTestDataDir("cgo_with_issues")). ExpectHasIssue("Printf format %t has arg cs of wrong type") r.Run("--no-config", "--disable-all", "-Estaticcheck", getTestDataDir("cgo_with_issues")). ExpectHasIssue("SA5009: Printf format %t has arg #1 of wrong type") } func TestUnsafeOk(t *testing.T) { testshared.NewLintRunner(t).Run("--no-config", "--enable-all", getTestDataDir("unsafe")).ExpectNoIssues() } func TestGovetCustomFormatter(t *testing.T) { testshared.NewLintRunner(t).Run(getTestDataDir("govet_custom_formatter")).ExpectNoIssues() } func TestLineDirectiveProcessedFilesLiteLoading(t *testing.T) { r := testshared.NewLintRunner(t).Run("--print-issued-lines=false", "--no-config", "--exclude-use-default=false", "-Egolint", getTestDataDir("quicktemplate")) output := strings.Join([]string{ "testdata/quicktemplate/hello.qtpl.go:26:1: exported function `StreamHello` should have comment or be unexported (golint)", "testdata/quicktemplate/hello.qtpl.go:50:1: exported function `Hello` should have comment or be unexported (golint)", "testdata/quicktemplate/hello.qtpl.go:39:1: exported function `WriteHello` should have comment or be unexported (golint)", }, "\n") r.ExpectExitCode(exitcodes.IssuesFound).ExpectOutputEq(output + "\n") } func TestSortedResults(t *testing.T) { var testCases = []struct { opt string want string }{ { "--sort-results=false", strings.Join([]string{ "testdata/sort_results/main.go:12:5: `db` is unused (deadcode)", "testdata/sort_results/main.go:15:13: Error return value of `returnError` is not checked (errcheck)", "testdata/sort_results/main.go:8:6: func `returnError` is unused (unused)", }, "\n"), }, { "--sort-results=true", strings.Join([]string{ "testdata/sort_results/main.go:8:6: func `returnError` is unused (unused)", "testdata/sort_results/main.go:12:5: `db` is unused (deadcode)", "testdata/sort_results/main.go:15:13: Error return value of `returnError` is not checked (errcheck)", }, "\n"), }, } dir := getTestDataDir("sort_results") t.Parallel() for i := range testCases { test := testCases[i] t.Run(test.opt, func(t *testing.T) { r := testshared.NewLintRunner(t).Run("--print-issued-lines=false", test.opt, dir) r.ExpectExitCode(exitcodes.IssuesFound).ExpectOutputEq(test.want + "\n") }) } } func TestLineDirectiveProcessedFilesFullLoading(t *testing.T) { r := testshared.NewLintRunner(t).Run("--print-issued-lines=false", "--no-config", "--exclude-use-default=false", "-Egolint,govet", getTestDataDir("quicktemplate")) output := strings.Join([]string{ "testdata/quicktemplate/hello.qtpl.go:26:1: exported function `StreamHello` should have comment or be unexported (golint)", "testdata/quicktemplate/hello.qtpl.go:50:1: exported function `Hello` should have comment or be unexported (golint)", "testdata/quicktemplate/hello.qtpl.go:39:1: exported function `WriteHello` should have comment or be unexported (golint)", }, "\n") r.ExpectExitCode(exitcodes.IssuesFound).ExpectOutputEq(output + "\n") } func TestLintFilesWithLineDirective(t *testing.T) { r := testshared.NewLintRunner(t) r.Run("-Edupl", "--disable-all", "--config=testdata/linedirective/dupl.yml", getTestDataDir("linedirective")). ExpectHasIssue("21-23 lines are duplicate of `testdata/linedirective/hello.go:25-27` (dupl)") r.Run("-Egofmt", "--disable-all", "--no-config", getTestDataDir("linedirective")). ExpectHasIssue("File is not `gofmt`-ed with `-s` (gofmt)") r.Run("-Egoimports", "--disable-all", "--no-config", getTestDataDir("linedirective")). ExpectHasIssue("File is not `goimports`-ed (goimports)") r. Run("-Egomodguard", "--disable-all", "--config=testdata/linedirective/gomodguard.yml", getTestDataDir("linedirective")). ExpectHasIssue("import of package `github.com/ryancurrah/gomodguard` is blocked because the module is not " + "in the allowed modules list. (gomodguard)") r.Run("-Eineffassign", "--disable-all", "--no-config", getTestDataDir("linedirective")). ExpectHasIssue("ineffectual assignment to `x` (ineffassign)") r.Run("-Elll", "--disable-all", "--config=testdata/linedirective/lll.yml", getTestDataDir("linedirective")). ExpectHasIssue("line is 57 characters (lll)") r.Run("-Emisspell", "--disable-all", "--no-config", getTestDataDir("linedirective")). ExpectHasIssue("is a misspelling of `language` (misspell)") r.Run("-Ewsl", "--disable-all", "--no-config", getTestDataDir("linedirective")). ExpectHasIssue("block should not start with a whitespace (wsl)") } func TestSkippedDirsNoMatchArg(t *testing.T) { dir := getTestDataDir("skipdirs", "skip_me", "nested") res := testshared.NewLintRunner(t).Run("--print-issued-lines=false", "--no-config", "--skip-dirs", dir, "-Egolint", dir) res.ExpectExitCode(exitcodes.IssuesFound). ExpectOutputEq("testdata/skipdirs/skip_me/nested/with_issue.go:8:9: `if` block ends with " + "a `return` statement, so drop this `else` and outdent its block (golint)\n") } func TestSkippedDirsTestdata(t *testing.T) { r := testshared.NewLintRunner(t).Run("--print-issued-lines=false", "--no-config", "-Egolint", getTestDataDir("skipdirs", "...")) r.ExpectNoIssues() // all was skipped because in testdata } func TestDeadcodeNoFalsePositivesInMainPkg(t *testing.T) { testshared.NewLintRunner(t).Run("--no-config", "--disable-all", "-Edeadcode", getTestDataDir("deadcode_main_pkg")).ExpectNoIssues() } func TestIdentifierUsedOnlyInTests(t *testing.T) { testshared.NewLintRunner(t).Run("--no-config", "--disable-all", "-Eunused", getTestDataDir("used_only_in_tests")).ExpectNoIssues() } func TestUnusedCheckExported(t *testing.T) { t.Skip("Issue955") testshared.NewLintRunner(t).Run("-c", "testdata_etc/unused_exported/golangci.yml", "testdata_etc/unused_exported/...").ExpectNoIssues() } func TestConfigFileIsDetected(t *testing.T) { checkGotConfig := func(r *testshared.RunResult) { r.ExpectExitCode(exitcodes.Success). ExpectOutputEq("test\n") // test config contains InternalTest: true, it triggers such output } r := testshared.NewLintRunner(t) checkGotConfig(r.Run(getTestDataDir("withconfig", "pkg"))) checkGotConfig(r.Run(getTestDataDir("withconfig", "..."))) } func TestEnableAllFastAndEnableCanCoexist(t *testing.T) { r := testshared.NewLintRunner(t) r.Run(withCommonRunArgs("--no-config", "--fast", "--enable-all", "--enable=typecheck", minimalPkg)...). ExpectExitCode(exitcodes.Success, exitcodes.IssuesFound) r.Run(withCommonRunArgs("--no-config", "--enable-all", "--enable=typecheck", minimalPkg)...). ExpectExitCode(exitcodes.Failure) } func TestEnabledPresetsAreNotDuplicated(t *testing.T) { testshared.NewLintRunner(t).Run("--no-config", "-v", "-p", "style,bugs", minimalPkg). ExpectOutputContains("Active presets: [bugs style]") } func TestAbsPathDirAnalysis(t *testing.T) { dir := filepath.Join("testdata_etc", "abspath") // abs paths don't work with testdata dir absDir, err := filepath.Abs(dir) assert.NoError(t, err) r := testshared.NewLintRunner(t).Run("--print-issued-lines=false", "--no-config", "-Egolint", absDir) r.ExpectHasIssue("`if` block ends with a `return` statement") } func TestAbsPathFileAnalysis(t *testing.T) { dir := filepath.Join("testdata_etc", "abspath", "with_issue.go") // abs paths don't work with testdata dir absDir, err := filepath.Abs(dir) assert.NoError(t, err) r := testshared.NewLintRunner(t).Run("--print-issued-lines=false", "--no-config", "-Egolint", absDir) r.ExpectHasIssue("`if` block ends with a `return` statement") } func TestDisallowedOptionsInConfig(t *testing.T) { type tc struct { cfg string option string } cases := []tc{ { cfg: ` ruN: Args: - 1 `, }, { cfg: ` run: CPUProfilePath: path `, option: "--cpu-profile-path=path", }, { cfg: ` run: MemProfilePath: path `, option: "--mem-profile-path=path", }, { cfg: ` run: TracePath: path `, option: "--trace-path=path", }, { cfg: ` run: Verbose: true `, option: "-v", }, } r := testshared.NewLintRunner(t) for _, c := range cases { // Run with disallowed option set only in config r.RunWithYamlConfig(c.cfg, withCommonRunArgs(minimalPkg)...).ExpectExitCode(exitcodes.Failure) if c.option == "" { continue } args := []string{c.option, "--fast", minimalPkg} // Run with disallowed option set only in command-line r.Run(withCommonRunArgs(args...)...).ExpectExitCode(exitcodes.Success) // Run with disallowed option set both in command-line and in config r.RunWithYamlConfig(c.cfg, withCommonRunArgs(args...)...).ExpectExitCode(exitcodes.Failure) } } func TestPathPrefix(t *testing.T) { for _, tt := range []struct { Name string Args []string Pattern string }{ {"empty", nil, "^testdata/withtests/"}, {"prefixed", []string{"--path-prefix=cool"}, "^cool/testdata/withtests"}, } { t.Run(tt.Name, func(t *testing.T) { testshared.NewLintRunner(t).Run( append(tt.Args, getTestDataDir("withtests"))..., //nolint:scopelint ).ExpectOutputRegexp( tt.Pattern, //nolint:scopelint ) }) } }