golangci-lint/test/run_test.go

684 lines
16 KiB
Go

package test
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
_ "github.com/valyala/quicktemplate"
"github.com/golangci/golangci-lint/pkg/exitcodes"
"github.com/golangci/golangci-lint/test/testshared"
)
const minimalPkg = "minimalpkg"
func TestAutogeneratedNoIssues(t *testing.T) {
testshared.NewRunnerBuilder(t).
WithTargetPath(testdataDir, "autogenerated").
Runner().
Install().
Run().
ExpectNoIssues()
}
func TestEmptyDirRun(t *testing.T) {
testshared.NewRunnerBuilder(t).
WithEnviron("GO111MODULE=off").
WithTargetPath(testdataDir, "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(testdataDir, "no_such_dir").
Runner().
Install().
Run().
ExpectExitCode(exitcodes.Failure).
ExpectOutputContains("cannot find package").
ExpectOutputContains(testshared.NormalizeFileInString("/testdata/no_such_dir"))
}
func TestSymlinkLoop(t *testing.T) {
testshared.NewRunnerBuilder(t).
WithTargetPath(testdataDir, "symlink_loop", "...").
Runner().
Install().
Run().
ExpectNoIssues()
}
// TODO(ldez): remove this in v2.
func TestDeadline(t *testing.T) {
projectRoot := filepath.Join("..", "...")
testshared.NewRunnerBuilder(t).
WithArgs("--deadline=1ms").
WithTargetPath(projectRoot).
Runner().
Install().
Run().
ExpectExitCode(exitcodes.Timeout).
ExpectOutputContains(`Timeout exceeded: try increasing it by passing --timeout option`)
}
func TestTimeout(t *testing.T) {
projectRoot := filepath.Join("..", "...")
testshared.NewRunnerBuilder(t).
WithArgs("--timeout=1ms").
WithTargetPath(projectRoot).
Runner().
Install().
Run().
ExpectExitCode(exitcodes.Timeout).
ExpectOutputContains(`Timeout exceeded: try increasing it by passing --timeout option`)
}
func TestTimeoutInConfig(t *testing.T) {
cases := []struct {
cfg string
}{
{
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).
WithTargetPath(testdataDir, 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(testdataDir, "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(testdataDir, "cgo").
Runner().
Install().
Run().
ExpectNoIssues()
}
func TestCgoWithIssues(t *testing.T) {
testshared.InstallGolangciLint(t)
testCases := []struct {
desc string
args []string
dir string
expected string
}{
{
desc: "govet",
args: []string{"--no-config", "--disable-all", "-Egovet"},
dir: "cgo_with_issues",
expected: "Printf format %t has arg cs of wrong type",
},
{
desc: "staticcheck",
args: []string{"--no-config", "--disable-all", "-Estaticcheck"},
dir: "cgo_with_issues",
expected: "SA5009: Printf format %t has arg #1 of wrong type",
},
{
desc: "gofmt",
args: []string{"--no-config", "--disable-all", "-Egofmt"},
dir: "cgo_with_issues",
expected: "File is not `gofmt`-ed with `-s` (gofmt)",
},
{
desc: "revive",
args: []string{"--no-config", "--disable-all", "-Erevive"},
dir: "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(testdataDir, test.dir).
Runner().
Run().
ExpectHasIssue(test.expected)
})
}
}
// https://pkg.go.dev/cmd/compile#hdr-Compiler_Directives
func TestLineDirective(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: "linedirective",
expected: "21-23 lines are duplicate of `testdata/linedirective/hello.go:25-27` (dupl)",
},
{
desc: "gofmt",
args: []string{
"-Egofmt",
"--disable-all",
},
targetPath: "linedirective",
expected: "File is not `gofmt`-ed with `-s` (gofmt)",
},
{
desc: "goimports",
args: []string{
"-Egoimports",
"--disable-all",
},
targetPath: "linedirective",
expected: "File is not `goimports`-ed (goimports)",
},
{
desc: "gomodguard",
args: []string{
"-Egomodguard",
"--disable-all",
},
configPath: "testdata/linedirective/gomodguard.yml",
targetPath: "linedirective",
expected: "import of package `golang.org/x/tools/go/analysis` 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: "linedirective",
expected: "line is 57 characters (lll)",
},
{
desc: "misspell",
args: []string{
"-Emisspell",
"--disable-all",
},
configPath: "",
targetPath: "linedirective",
expected: "is a misspelling of `language` (misspell)",
},
{
desc: "wsl",
args: []string{
"-Ewsl",
"--disable-all",
},
configPath: "",
targetPath: "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(testdataDir, test.targetPath).
WithConfigFile(test.configPath).
Runner().
Run().
ExpectHasIssue(test.expected)
})
}
}
// https://pkg.go.dev/cmd/compile#hdr-Compiler_Directives
func TestLineDirectiveProcessedFiles(t *testing.T) {
testCases := []struct {
desc string
args []string
target string
expected []string
}{
{
desc: "lite loading",
args: []string{
"--print-issued-lines=false",
"--exclude-use-default=false",
"-Erevive",
},
target: "quicktemplate",
expected: []string{
"testdata/quicktemplate/hello.qtpl.go:10:1: package-comments: should have a package comment (revive)",
"testdata/quicktemplate/hello.qtpl.go:26:1: exported: exported function StreamHello should have comment or be unexported (revive)",
"testdata/quicktemplate/hello.qtpl.go:39:1: exported: exported function WriteHello should have comment or be unexported (revive)",
"testdata/quicktemplate/hello.qtpl.go:50:1: exported: exported function Hello should have comment or be unexported (revive)",
},
},
{
desc: "full loading",
args: []string{
"--print-issued-lines=false",
"--exclude-use-default=false",
"-Erevive,govet",
},
target: "quicktemplate",
expected: []string{
"testdata/quicktemplate/hello.qtpl.go:10:1: package-comments: should have a package comment (revive)",
"testdata/quicktemplate/hello.qtpl.go:26:1: exported: exported function StreamHello should have comment or be unexported (revive)",
"testdata/quicktemplate/hello.qtpl.go:39:1: exported: exported function WriteHello should have comment or be unexported (revive)",
"testdata/quicktemplate/hello.qtpl.go:50:1: exported: exported function Hello should have comment or be unexported (revive)",
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
testshared.NewRunnerBuilder(t).
WithNoConfig().
WithArgs(test.args...).
WithTargetPath(testdataDir, test.target).
Runner().
Install().
Run().
ExpectExitCode(exitcodes.IssuesFound).
ExpectOutputContains(test.expected...)
})
}
}
func TestUnsafeOk(t *testing.T) {
testshared.NewRunnerBuilder(t).
WithNoConfig().
WithArgs("--enable-all").
WithTargetPath(testdataDir, "unsafe").
Runner().
Install().
Run().
ExpectNoIssues()
}
func TestSortedResults(t *testing.T) {
testCases := []struct {
opt string
want string
}{
{
opt: "--sort-results=false",
want: "testdata/sort_results/main.go:15:13: Error return value is not checked (errcheck)" + "\n" +
"testdata/sort_results/main.go:12:5: var `db` is unused (unused)",
},
{
opt: "--sort-results=true",
want: "testdata/sort_results/main.go:12:5: var `db` is unused (unused)" + "\n" +
"testdata/sort_results/main.go:15:13: Error return value is not checked (errcheck)",
},
}
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(testdataDir, "sort_results").
Runner().
Run().
ExpectExitCode(exitcodes.IssuesFound).ExpectOutputEq(test.want + "\n")
})
}
}
func TestSkippedDirsNoMatchArg(t *testing.T) {
dir := filepath.Join(testdataDir, "skipdirs", "skip_me", "nested")
testshared.NewRunnerBuilder(t).
WithNoConfig().
WithArgs(
"--print-issued-lines=false",
"--skip-dirs", dir,
"-Erevive",
).
WithTargetPath(dir).
Runner().
Install().
Run().
ExpectExitCode(exitcodes.IssuesFound).
ExpectOutputEq("testdata/skipdirs/skip_me/nested/with_issue.go:8:9: " +
"indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (revive)\n")
}
func TestSkippedDirsTestdata(t *testing.T) {
testshared.NewRunnerBuilder(t).
WithNoConfig().
WithArgs(
"--print-issued-lines=false",
"-Erevive",
).
WithTargetPath(testdataDir, "skipdirs", "...").
Runner().
Install().
Run().
ExpectNoIssues() // all was skipped because in testdata
}
func TestIdentifierUsedOnlyInTests(t *testing.T) {
testshared.NewRunnerBuilder(t).
WithNoConfig().
WithArgs("--disable-all", "-Eunused").
WithTargetPath(testdataDir, "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: filepath.Join(testdataDir, "withconfig", "pkg"),
},
{
desc: "recursive",
targetPath: filepath.Join(testdataDir, "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(test.args...).
WithTargetPath(testdataDir, minimalPkg).
Runner().
Run().
ExpectExitCode(test.expected...)
})
}
}
func TestEnabledPresetsAreNotDuplicated(t *testing.T) {
testshared.NewRunnerBuilder(t).
WithNoConfig().
WithArgs("-v", "-p", "style,bugs").
WithTargetPath(testdataDir, 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",
"-Erevive",
).
WithTargetPath(absDir).
Runner().
Install().
Run().
ExpectHasIssue("testdata_etc/abspath/with_issue.go:8:9: " +
"indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (revive)")
}
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",
"-Erevive",
).
WithTargetPath(absDir).
Runner().
Install().
Run().
ExpectHasIssue("indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (revive)")
}
func TestDisallowedOptionsInConfig(t *testing.T) {
cases := []struct {
cfg string
option string
}{
{
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).
WithTargetPath(testdataDir, 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(args...).
WithTargetPath(testdataDir, 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(args...).
WithTargetPath(testdataDir, 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(testdataDir, "withtests").
Runner().
Run().
ExpectOutputRegexp(test.pattern)
})
}
}