golangci-lint/test/run_test.go
Denis Isaev 8a478c47ac Prepare for #164: rename GAS to gosec
1. Rename in a backward compatible way
2. Remove gosec default exclude list because gosec is already disabled
by default.
3. Warn about unmatched linter names in //nolint directives
4. Process linter names in //nolint directives in upper case
5. Disable gosec for golangci-lint in .golangci.yml
2018-09-02 09:34:35 +03:00

436 lines
10 KiB
Go

package test
import (
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
"sync"
"syscall"
"testing"
"github.com/golangci/golangci-lint/pkg/exitcodes"
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
"github.com/stretchr/testify/assert"
)
var root = filepath.Join("..", "...")
var installOnce sync.Once
const noIssuesOut = ""
func installBinary(t assert.TestingT) {
installOnce.Do(func() {
cmd := exec.Command("go", "install", filepath.Join("..", "cmd", binName))
assert.NoError(t, cmd.Run(), "Can't go install %s", binName)
})
}
func checkNoIssuesRun(t *testing.T, out string, exitCode int) {
assert.Equal(t, exitcodes.Success, exitCode)
assert.Equal(t, noIssuesOut, out)
}
func TestNoCongratsMessage(t *testing.T) {
out, exitCode := runGolangciLint(t, "../...")
assert.Equal(t, exitcodes.Success, exitCode)
assert.Equal(t, "", out)
}
func TestCongratsMessageIfNoIssues(t *testing.T) {
out, exitCode := runGolangciLint(t, root)
checkNoIssuesRun(t, out, exitCode)
}
func TestAutogeneratedNoIssues(t *testing.T) {
out, exitCode := runGolangciLint(t, filepath.Join(testdataDir, "autogenerated"))
checkNoIssuesRun(t, out, exitCode)
}
func TestSymlinkLoop(t *testing.T) {
out, exitCode := runGolangciLint(t, filepath.Join(testdataDir, "symlink_loop", "..."))
checkNoIssuesRun(t, out, exitCode)
}
func TestRunOnAbsPath(t *testing.T) {
absPath, err := filepath.Abs(filepath.Join(testdataDir, ".."))
assert.NoError(t, err)
out, exitCode := runGolangciLint(t, "--no-config", "--fast", absPath)
checkNoIssuesRun(t, out, exitCode)
out, exitCode = runGolangciLint(t, "--no-config", absPath)
checkNoIssuesRun(t, out, exitCode)
}
func TestDeadline(t *testing.T) {
out, exitCode := runGolangciLint(t, "--deadline=1ms", root)
assert.Equal(t, exitcodes.Timeout, exitCode)
assert.Contains(t, out, "deadline exceeded: try increase it by passing --deadline option")
}
func runGolangciLint(t *testing.T, args ...string) (string, int) {
installBinary(t)
runArgs := append([]string{"run"}, args...)
log.Printf("golangci-lint %s", strings.Join(runArgs, " "))
cmd := exec.Command("golangci-lint", runArgs...)
out, err := cmd.CombinedOutput()
if err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
t.Logf("stderr: %s", exitError.Stderr)
ws := exitError.Sys().(syscall.WaitStatus)
return string(out), ws.ExitStatus()
}
t.Fatalf("can't get error code from %s", err)
return "", -1
}
// success, exitCode should be 0 if go is ok
ws := cmd.ProcessState.Sys().(syscall.WaitStatus)
return string(out), ws.ExitStatus()
}
func runGolangciLintWithYamlConfig(t *testing.T, cfg string, args ...string) string {
out, ec := runGolangciLintWithYamlConfigWithCode(t, cfg, args...)
assert.Equal(t, exitcodes.Success, ec)
return out
}
func runGolangciLintWithYamlConfigWithCode(t *testing.T, cfg string, args ...string) (string, int) {
f, err := ioutil.TempFile("", "golangci_lint_test")
assert.NoError(t, err)
f.Close()
cfgPath := f.Name() + ".yml"
err = os.Rename(f.Name(), cfgPath)
assert.NoError(t, err)
defer os.Remove(cfgPath)
cfg = strings.TrimSpace(cfg)
cfg = strings.Replace(cfg, "\t", " ", -1)
err = ioutil.WriteFile(cfgPath, []byte(cfg), os.ModePerm)
assert.NoError(t, err)
pargs := append([]string{"-c", cfgPath}, args...)
return runGolangciLint(t, pargs...)
}
func TestTestsAreLintedByDefault(t *testing.T) {
out, exitCode := runGolangciLint(t, "./testdata/withtests")
assert.Equal(t, exitcodes.Success, exitCode, out)
}
func TestCgoOk(t *testing.T) {
out, exitCode := runGolangciLint(t, "--enable-all", filepath.Join(testdataDir, "cgo"))
checkNoIssuesRun(t, out, exitCode)
}
func TestUnsafeOk(t *testing.T) {
out, exitCode := runGolangciLint(t, "--enable-all", filepath.Join(testdataDir, "unsafe"))
checkNoIssuesRun(t, out, exitCode)
}
func TestDeadcodeNoFalsePositivesInMainPkg(t *testing.T) {
out, exitCode := runGolangciLint(t, "--no-config", "--disable-all", "-Edeadcode",
filepath.Join(testdataDir, "deadcode_main_pkg"))
checkNoIssuesRun(t, out, exitCode)
}
func TestConfigFileIsDetected(t *testing.T) {
checkGotConfig := func(out string, exitCode int) {
assert.Equal(t, exitcodes.Success, exitCode, out)
assert.Equal(t, "test\n", out) // test config contains InternalTest: true, it triggers such output
}
checkGotConfig(runGolangciLint(t, "testdata/withconfig/pkg"))
checkGotConfig(runGolangciLint(t, "testdata/withconfig/..."))
out, exitCode := runGolangciLint(t) // doesn't detect when no args
checkNoIssuesRun(t, out, exitCode)
}
func inSlice(s []string, v string) bool {
for _, sv := range s {
if sv == v {
return true
}
}
return false
}
func getEnabledByDefaultFastLintersExcept(except ...string) []string {
m := lintersdb.NewManager()
ebdl := m.GetAllEnabledByDefaultLinters()
ret := []string{}
for _, lc := range ebdl {
if lc.DoesFullImport {
continue
}
if !inSlice(except, lc.Name()) {
ret = append(ret, lc.Name())
}
}
return ret
}
func getAllFastLintersWith(with ...string) []string {
linters := lintersdb.NewManager().GetAllSupportedLinterConfigs()
ret := append([]string{}, with...)
for _, lc := range linters {
if lc.DoesFullImport {
continue
}
ret = append(ret, lc.Name())
}
return ret
}
func getEnabledByDefaultLinters() []string {
ebdl := lintersdb.NewManager().GetAllEnabledByDefaultLinters()
ret := []string{}
for _, lc := range ebdl {
ret = append(ret, lc.Name())
}
return ret
}
func getEnabledByDefaultFastLintersWith(with ...string) []string {
ebdl := lintersdb.NewManager().GetAllEnabledByDefaultLinters()
ret := append([]string{}, with...)
for _, lc := range ebdl {
if lc.DoesFullImport {
continue
}
ret = append(ret, lc.Name())
}
return ret
}
func mergeMegacheck(linters []string) []string {
if inSlice(linters, "staticcheck") &&
inSlice(linters, "gosimple") &&
inSlice(linters, "unused") {
ret := []string{"megacheck"}
for _, linter := range linters {
if !inSlice([]string{"staticcheck", "gosimple", "unused"}, linter) {
ret = append(ret, linter)
}
}
return ret
}
return linters
}
func TestEnableAllFastAndEnableCanCoexist(t *testing.T) {
out, exitCode := runGolangciLint(t, "--fast", "--enable-all", "--enable=typecheck")
checkNoIssuesRun(t, out, exitCode)
_, exitCode = runGolangciLint(t, "--enable-all", "--enable=typecheck")
assert.Equal(t, exitcodes.Failure, exitCode)
}
func TestEnabledLinters(t *testing.T) {
type tc struct {
name string
cfg string
el []string
args string
noImplicitFast bool
}
cases := []tc{
{
name: "disable govet in config",
cfg: `
linters:
disable:
- govet
`,
el: getEnabledByDefaultFastLintersExcept("govet"),
},
{
name: "enable golint in config",
cfg: `
linters:
enable:
- golint
`,
el: getEnabledByDefaultFastLintersWith("golint"),
},
{
name: "disable govet in cmd",
args: "-Dgovet",
el: getEnabledByDefaultFastLintersExcept("govet"),
},
{
name: "enable gofmt in cmd and enable golint in config",
args: "-Egofmt",
cfg: `
linters:
enable:
- golint
`,
el: getEnabledByDefaultFastLintersWith("golint", "gofmt"),
},
{
name: "fast option in config",
cfg: `
linters:
fast: true
`,
el: getEnabledByDefaultFastLintersWith(),
noImplicitFast: true,
},
{
name: "explicitly unset fast option in config",
cfg: `
linters:
fast: false
`,
el: getEnabledByDefaultLinters(),
noImplicitFast: true,
},
{
name: "set fast option in command-line",
args: "--fast",
el: getEnabledByDefaultFastLintersWith(),
noImplicitFast: true,
},
{
name: "fast option in command-line has higher priority to enable",
cfg: `
linters:
fast: false
`,
args: "--fast",
el: getEnabledByDefaultFastLintersWith(),
noImplicitFast: true,
},
{
name: "fast option in command-line has higher priority to disable",
cfg: `
linters:
fast: true
`,
args: "--fast=false",
el: getEnabledByDefaultLinters(),
noImplicitFast: true,
},
{
name: "fast option combined with enable and enable-all",
args: "--enable-all --fast --enable=typecheck",
el: getAllFastLintersWith("typecheck"),
noImplicitFast: true,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
runArgs := []string{"-v"}
if !c.noImplicitFast {
runArgs = append(runArgs, "--fast")
}
if c.args != "" {
runArgs = append(runArgs, strings.Split(c.args, " ")...)
}
out := runGolangciLintWithYamlConfig(t, c.cfg, runArgs...)
el := mergeMegacheck(c.el)
sort.StringSlice(el).Sort()
expectedLine := fmt.Sprintf("Active %d linters: [%s]", len(el), strings.Join(el, " "))
assert.Contains(t, out, expectedLine)
})
}
}
func TestGovetInFastMode(t *testing.T) {
cfg := `
linters-settings:
use-installed-packages: true
`
out := runGolangciLintWithYamlConfig(t, cfg, "--fast", "-Egovet", root)
assert.Equal(t, noIssuesOut, out)
}
func TestEnabledPresetsAreNotDuplicated(t *testing.T) {
out, _ := runGolangciLint(t, "--no-config", "-v", "-p", "style,bugs")
assert.Contains(t, out, "Active presets: [bugs style]")
}
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:
Verbose: true
`,
option: "-v",
},
}
for _, c := range cases {
// Run with disallowed option set only in config
_, ec := runGolangciLintWithYamlConfigWithCode(t, c.cfg)
assert.Equal(t, exitcodes.Failure, ec)
if c.option == "" {
continue
}
args := []string{c.option, "--fast"}
// Run with disallowed option set only in command-line
_, ec = runGolangciLint(t, args...)
assert.Equal(t, exitcodes.Success, ec)
// Run with disallowed option set both in command-line and in config
_, ec = runGolangciLintWithYamlConfigWithCode(t, c.cfg, args...)
assert.Equal(t, exitcodes.Failure, ec)
}
}