702 lines
16 KiB
Go
702 lines
16 KiB
Go
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 TestAutogeneratedNoIssues(t *testing.T) {
|
|
testshared.NewRunnerBuilder(t).
|
|
WithTargetPath(getTestDataDir("autogenerated")).
|
|
Runner().
|
|
Install().
|
|
Run().
|
|
ExpectNoIssues()
|
|
}
|
|
|
|
func TestEmptyDirRun(t *testing.T) {
|
|
testshared.NewRunnerBuilder(t).
|
|
WithEnviron("GO111MODULE=off").
|
|
WithTargetPath(getTestDataDir("nogofiles")).
|
|
Runner().
|
|
Install().
|
|
Run().
|
|
ExpectExitCode(exitcodes.NoGoFiles).
|
|
ExpectOutputContains(": no go files to analyze")
|
|
}
|
|
|
|
func TestNotExistingDirRun(t *testing.T) {
|
|
testshared.NewRunnerBuilder(t).
|
|
WithEnviron("GO111MODULE=off").
|
|
WithTargetPath(getTestDataDir("no_such_dir")).
|
|
Runner().
|
|
Install().
|
|
Run().
|
|
ExpectExitCode(exitcodes.Failure).
|
|
ExpectOutputContains("cannot find package").
|
|
ExpectOutputContains("/testdata/no_such_dir")
|
|
}
|
|
|
|
func TestSymlinkLoop(t *testing.T) {
|
|
testshared.NewRunnerBuilder(t).
|
|
WithTargetPath(getTestDataDir("symlink_loop", "...")).
|
|
Runner().
|
|
Install().
|
|
Run().
|
|
ExpectNoIssues()
|
|
}
|
|
|
|
// TODO(ldez): remove this in v2.
|
|
func TestDeadline(t *testing.T) {
|
|
testshared.NewRunnerBuilder(t).
|
|
WithArgs("--deadline=1ms").
|
|
WithTargetPath(getProjectRoot()).
|
|
Runner().
|
|
Install().
|
|
Run().
|
|
ExpectExitCode(exitcodes.Timeout).
|
|
ExpectOutputContains(`Timeout exceeded: try increasing it by passing --timeout option`)
|
|
}
|
|
|
|
func TestTimeout(t *testing.T) {
|
|
testshared.NewRunnerBuilder(t).
|
|
WithArgs("--timeout=1ms").
|
|
WithTargetPath(getProjectRoot()).
|
|
Runner().
|
|
Install().
|
|
Run().
|
|
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
|
|
`,
|
|
},
|
|
}
|
|
|
|
testshared.InstallGolangciLint(t)
|
|
|
|
for _, c := range cases {
|
|
// Run with disallowed option set only in config
|
|
testshared.NewRunnerBuilder(t).
|
|
WithConfig(c.cfg).
|
|
WithArgs(getCommonRunArgs()...).
|
|
WithTargetPath(minimalPkg).
|
|
Runner().
|
|
Run().
|
|
ExpectExitCode(exitcodes.Timeout).
|
|
ExpectOutputContains(`Timeout exceeded: try increasing it by passing --timeout option`)
|
|
}
|
|
}
|
|
|
|
func TestTestsAreLintedByDefault(t *testing.T) {
|
|
testshared.NewRunnerBuilder(t).
|
|
WithTargetPath(getTestDataDir("withtests")).
|
|
Runner().
|
|
Install().
|
|
Run().
|
|
ExpectHasIssue("don't use `init` function")
|
|
}
|
|
|
|
func TestCgoOk(t *testing.T) {
|
|
testshared.NewRunnerBuilder(t).
|
|
WithNoConfig().
|
|
WithArgs(
|
|
"--timeout=3m",
|
|
"--enable-all",
|
|
"-D",
|
|
"nosnakecase,gci",
|
|
).
|
|
WithTargetPath(getTestDataDir("cgo")).
|
|
Runner().
|
|
Install().
|
|
Run().
|
|
ExpectNoIssues()
|
|
}
|
|
|
|
func TestCgoWithIssues(t *testing.T) {
|
|
testshared.InstallGolangciLint(t)
|
|
|
|
testCases := []struct {
|
|
desc string
|
|
args []string
|
|
targetPath string
|
|
expected string
|
|
}{
|
|
{
|
|
desc: "govet",
|
|
args: []string{"--no-config", "--disable-all", "-Egovet"},
|
|
targetPath: getTestDataDir("cgo_with_issues"),
|
|
expected: "Printf format %t has arg cs of wrong type",
|
|
},
|
|
{
|
|
desc: "staticcheck",
|
|
args: []string{"--no-config", "--disable-all", "-Estaticcheck"},
|
|
targetPath: getTestDataDir("cgo_with_issues"),
|
|
expected: "SA5009: Printf format %t has arg #1 of wrong type",
|
|
},
|
|
{
|
|
desc: "gofmt",
|
|
args: []string{"--no-config", "--disable-all", "-Egofmt"},
|
|
targetPath: getTestDataDir("cgo_with_issues"),
|
|
expected: "File is not `gofmt`-ed with `-s` (gofmt)",
|
|
},
|
|
{
|
|
desc: "revive",
|
|
args: []string{"--no-config", "--disable-all", "-Erevive"},
|
|
targetPath: getTestDataDir("cgo_with_issues"),
|
|
expected: "indent-error-flow: if block ends with a return statement",
|
|
},
|
|
}
|
|
|
|
for _, test := range testCases {
|
|
test := test
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testshared.NewRunnerBuilder(t).
|
|
WithArgs(test.args...).
|
|
WithTargetPath(test.targetPath).
|
|
Runner().
|
|
Run().
|
|
ExpectHasIssue(test.expected)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUnsafeOk(t *testing.T) {
|
|
testshared.NewRunnerBuilder(t).
|
|
WithNoConfig().
|
|
WithArgs("--enable-all").
|
|
WithTargetPath(getTestDataDir("unsafe")).
|
|
Runner().
|
|
Install().
|
|
Run().
|
|
ExpectNoIssues()
|
|
}
|
|
|
|
func TestGovetCustomFormatter(t *testing.T) {
|
|
testshared.NewRunnerBuilder(t).
|
|
WithTargetPath(getTestDataDir("govet_custom_formatter")).
|
|
Runner().
|
|
Install().
|
|
Run().
|
|
ExpectNoIssues()
|
|
}
|
|
|
|
func TestLineDirectiveProcessedFilesLiteLoading(t *testing.T) {
|
|
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")
|
|
|
|
testshared.NewRunnerBuilder(t).
|
|
WithNoConfig().
|
|
WithArgs(
|
|
"--print-issued-lines=false",
|
|
"--exclude-use-default=false",
|
|
"-Egolint",
|
|
).
|
|
WithTargetPath(getTestDataDir("quicktemplate")).
|
|
Runner().
|
|
Install().
|
|
Run().
|
|
ExpectExitCode(exitcodes.IssuesFound).ExpectOutputEq(output + "\n")
|
|
}
|
|
|
|
func TestSortedResults(t *testing.T) {
|
|
testCases := []struct {
|
|
opt string
|
|
want string
|
|
}{
|
|
{
|
|
opt: "--sort-results=false",
|
|
want: strings.Join([]string{
|
|
"testdata/sort_results/main.go:12:5: `db` is unused (deadcode)",
|
|
"testdata/sort_results/main.go:15:13: Error return value is not checked (errcheck)",
|
|
"testdata/sort_results/main.go:8:6: func `returnError` is unused (unused)",
|
|
}, "\n"),
|
|
},
|
|
{
|
|
opt: "--sort-results=true",
|
|
want: 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 is not checked (errcheck)",
|
|
}, "\n"),
|
|
},
|
|
}
|
|
|
|
dir := getTestDataDir("sort_results")
|
|
|
|
testshared.InstallGolangciLint(t)
|
|
|
|
for _, test := range testCases {
|
|
test := test
|
|
t.Run(test.opt, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testshared.NewRunnerBuilder(t).
|
|
WithNoConfig().
|
|
WithArgs("--print-issued-lines=false", test.opt).
|
|
WithTargetPath(dir).
|
|
Runner().
|
|
Run().
|
|
ExpectExitCode(exitcodes.IssuesFound).ExpectOutputEq(test.want + "\n")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLineDirectiveProcessedFilesFullLoading(t *testing.T) {
|
|
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")
|
|
|
|
testshared.NewRunnerBuilder(t).
|
|
WithNoConfig().
|
|
WithArgs(
|
|
"--print-issued-lines=false",
|
|
"--exclude-use-default=false",
|
|
"-Egolint,govet",
|
|
).
|
|
WithTargetPath(getTestDataDir("quicktemplate")).
|
|
Runner().
|
|
Install().
|
|
Run().
|
|
ExpectExitCode(exitcodes.IssuesFound).ExpectOutputEq(output + "\n")
|
|
}
|
|
|
|
func TestLintFilesWithLineDirective(t *testing.T) {
|
|
testshared.InstallGolangciLint(t)
|
|
|
|
testCases := []struct {
|
|
desc string
|
|
args []string
|
|
configPath string
|
|
targetPath string
|
|
expected string
|
|
}{
|
|
{
|
|
desc: "dupl",
|
|
args: []string{
|
|
"-Edupl",
|
|
"--disable-all",
|
|
},
|
|
configPath: "testdata/linedirective/dupl.yml",
|
|
targetPath: getTestDataDir("linedirective"),
|
|
expected: "21-23 lines are duplicate of `testdata/linedirective/hello.go:25-27` (dupl)",
|
|
},
|
|
{
|
|
desc: "gofmt",
|
|
args: []string{
|
|
"-Egofmt",
|
|
"--disable-all",
|
|
},
|
|
targetPath: getTestDataDir("linedirective"),
|
|
expected: "File is not `gofmt`-ed with `-s` (gofmt)",
|
|
},
|
|
{
|
|
desc: "goimports",
|
|
args: []string{
|
|
"-Egoimports",
|
|
"--disable-all",
|
|
},
|
|
targetPath: getTestDataDir("linedirective"),
|
|
expected: "File is not `goimports`-ed (goimports)",
|
|
},
|
|
{
|
|
desc: "gomodguard",
|
|
args: []string{
|
|
"-Egomodguard",
|
|
"--disable-all",
|
|
},
|
|
configPath: "testdata/linedirective/gomodguard.yml",
|
|
targetPath: getTestDataDir("linedirective"),
|
|
expected: "import of package `github.com/ryancurrah/gomodguard` is blocked because the module is not " +
|
|
"in the allowed modules list. (gomodguard)",
|
|
},
|
|
{
|
|
desc: "lll",
|
|
args: []string{
|
|
"-Elll",
|
|
"--disable-all",
|
|
},
|
|
configPath: "testdata/linedirective/lll.yml",
|
|
targetPath: getTestDataDir("linedirective"),
|
|
expected: "line is 57 characters (lll)",
|
|
},
|
|
{
|
|
desc: "misspell",
|
|
args: []string{
|
|
"-Emisspell",
|
|
"--disable-all",
|
|
},
|
|
configPath: "",
|
|
targetPath: getTestDataDir("linedirective"),
|
|
expected: "is a misspelling of `language` (misspell)",
|
|
},
|
|
{
|
|
desc: "wsl",
|
|
args: []string{
|
|
"-Ewsl",
|
|
"--disable-all",
|
|
},
|
|
configPath: "",
|
|
targetPath: getTestDataDir("linedirective"),
|
|
expected: "block should not start with a whitespace (wsl)",
|
|
},
|
|
}
|
|
|
|
for _, test := range testCases {
|
|
test := test
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testshared.NewRunnerBuilder(t).
|
|
WithArgs(test.args...).
|
|
WithTargetPath(test.targetPath).
|
|
WithConfigFile(test.configPath).
|
|
Runner().
|
|
Run().
|
|
ExpectHasIssue(test.expected)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSkippedDirsNoMatchArg(t *testing.T) {
|
|
dir := getTestDataDir("skipdirs", "skip_me", "nested")
|
|
|
|
testshared.NewRunnerBuilder(t).
|
|
WithNoConfig().
|
|
WithArgs(
|
|
"--print-issued-lines=false",
|
|
"--skip-dirs", dir,
|
|
"-Egolint",
|
|
).
|
|
WithTargetPath(dir).
|
|
Runner().
|
|
Install().
|
|
Run().
|
|
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) {
|
|
testshared.NewRunnerBuilder(t).
|
|
WithNoConfig().
|
|
WithArgs(
|
|
"--print-issued-lines=false",
|
|
"-Egolint",
|
|
).
|
|
WithTargetPath(getTestDataDir("skipdirs", "...")).
|
|
Runner().
|
|
Install().
|
|
Run().
|
|
ExpectNoIssues() // all was skipped because in testdata
|
|
}
|
|
|
|
func TestDeadcodeNoFalsePositivesInMainPkg(t *testing.T) {
|
|
testshared.NewRunnerBuilder(t).
|
|
WithNoConfig().
|
|
WithArgs("--disable-all", "-Edeadcode").
|
|
WithTargetPath(getTestDataDir("deadcode_main_pkg")).
|
|
Runner().
|
|
Install().
|
|
Run().
|
|
ExpectNoIssues()
|
|
}
|
|
|
|
func TestIdentifierUsedOnlyInTests(t *testing.T) {
|
|
testshared.NewRunnerBuilder(t).
|
|
WithNoConfig().
|
|
WithArgs("--disable-all", "-Eunused").
|
|
WithTargetPath(getTestDataDir("used_only_in_tests")).
|
|
Runner().
|
|
Install().
|
|
Run().
|
|
ExpectNoIssues()
|
|
}
|
|
|
|
func TestUnusedCheckExported(t *testing.T) {
|
|
testshared.NewRunnerBuilder(t).
|
|
WithConfigFile("testdata_etc/unused_exported/golangci.yml").
|
|
WithTargetPath("testdata_etc/unused_exported/...").
|
|
Runner().
|
|
Install().
|
|
Run().
|
|
ExpectNoIssues()
|
|
}
|
|
|
|
func TestConfigFileIsDetected(t *testing.T) {
|
|
testshared.InstallGolangciLint(t)
|
|
|
|
testCases := []struct {
|
|
desc string
|
|
targetPath string
|
|
}{
|
|
{
|
|
desc: "explicit",
|
|
targetPath: getTestDataDir("withconfig", "pkg"),
|
|
},
|
|
{
|
|
desc: "recursive",
|
|
targetPath: getTestDataDir("withconfig", "..."),
|
|
},
|
|
}
|
|
|
|
for _, test := range testCases {
|
|
test := test
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testshared.NewRunnerBuilder(t).
|
|
// WithNoConfig().
|
|
WithTargetPath(test.targetPath).
|
|
Runner().
|
|
Run().
|
|
ExpectExitCode(exitcodes.Success).
|
|
// test config contains InternalTest: true, it triggers such output
|
|
ExpectOutputEq("test\n")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEnableAllFastAndEnableCanCoexist(t *testing.T) {
|
|
testshared.InstallGolangciLint(t)
|
|
|
|
testCases := []struct {
|
|
desc string
|
|
args []string
|
|
expected []int
|
|
}{
|
|
{
|
|
desc: "fast",
|
|
args: []string{"--fast", "--enable-all", "--enable=typecheck"},
|
|
expected: []int{exitcodes.Success, exitcodes.IssuesFound},
|
|
},
|
|
{
|
|
desc: "all",
|
|
args: []string{"--enable-all", "--enable=typecheck"},
|
|
expected: []int{exitcodes.Failure},
|
|
},
|
|
}
|
|
|
|
for _, test := range testCases {
|
|
test := test
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testshared.NewRunnerBuilder(t).
|
|
WithNoConfig().
|
|
WithArgs(getCommonRunArgs()...).
|
|
WithArgs(test.args...).
|
|
WithTargetPath(minimalPkg).
|
|
Runner().
|
|
Run().
|
|
ExpectExitCode(test.expected...)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEnabledPresetsAreNotDuplicated(t *testing.T) {
|
|
testshared.NewRunnerBuilder(t).
|
|
WithNoConfig().
|
|
WithArgs("-v", "-p", "style,bugs").
|
|
WithTargetPath(minimalPkg).
|
|
Runner().
|
|
Install().
|
|
Run().
|
|
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)
|
|
|
|
testshared.NewRunnerBuilder(t).
|
|
WithNoConfig().
|
|
WithArgs(
|
|
"--print-issued-lines=false",
|
|
"-Egolint",
|
|
).
|
|
WithTargetPath(absDir).
|
|
Runner().
|
|
Install().
|
|
Run().
|
|
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)
|
|
|
|
testshared.NewRunnerBuilder(t).
|
|
WithNoConfig().
|
|
WithArgs(
|
|
"--print-issued-lines=false",
|
|
"-Egolint",
|
|
).
|
|
WithTargetPath(absDir).
|
|
Runner().
|
|
Install().
|
|
Run().
|
|
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",
|
|
},
|
|
}
|
|
|
|
testshared.InstallGolangciLint(t)
|
|
|
|
for _, c := range cases {
|
|
// Run with disallowed option set only in config
|
|
testshared.NewRunnerBuilder(t).
|
|
WithConfig(c.cfg).
|
|
WithArgs(getCommonRunArgs()...).
|
|
WithTargetPath(minimalPkg).
|
|
Runner().
|
|
Run().
|
|
ExpectExitCode(exitcodes.Failure)
|
|
|
|
if c.option == "" {
|
|
continue
|
|
}
|
|
|
|
args := []string{c.option, "--fast"}
|
|
|
|
// Run with disallowed option set only in command-line
|
|
testshared.NewRunnerBuilder(t).
|
|
WithNoConfig().
|
|
WithArgs(getCommonRunArgs()...).
|
|
WithArgs(args...).
|
|
WithTargetPath(minimalPkg).
|
|
Runner().
|
|
Run().
|
|
ExpectExitCode(exitcodes.Success)
|
|
|
|
// Run with disallowed option set both in command-line and in config
|
|
|
|
testshared.NewRunnerBuilder(t).
|
|
WithConfig(c.cfg).
|
|
WithArgs(getCommonRunArgs()...).
|
|
WithArgs(args...).
|
|
WithTargetPath(minimalPkg).
|
|
Runner().
|
|
Run().
|
|
ExpectExitCode(exitcodes.Failure)
|
|
}
|
|
}
|
|
|
|
func TestPathPrefix(t *testing.T) {
|
|
testCases := []struct {
|
|
desc string
|
|
args []string
|
|
pattern string
|
|
}{
|
|
{
|
|
desc: "empty",
|
|
pattern: "^testdata/withtests/",
|
|
},
|
|
{
|
|
desc: "prefixed",
|
|
args: []string{"--path-prefix=cool"},
|
|
pattern: "^cool/testdata/withtests",
|
|
},
|
|
}
|
|
|
|
testshared.InstallGolangciLint(t)
|
|
|
|
for _, test := range testCases {
|
|
test := test
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
testshared.NewRunnerBuilder(t).
|
|
WithArgs(test.args...).
|
|
WithTargetPath(getTestDataDir("withtests")).
|
|
Runner().
|
|
Run().
|
|
ExpectOutputRegexp(test.pattern)
|
|
})
|
|
}
|
|
}
|