golangci-lint/pkg/enabled_linters_test.go
2018-05-19 22:40:14 +03:00

299 lines
7.4 KiB
Go

package pkg
import (
"bytes"
"fmt"
"go/build"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"testing"
"time"
"github.com/golangci/golangci-lint/pkg/config"
"github.com/shirou/gopsutil/mem"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)
func runGoErrchk(c *exec.Cmd, t *testing.T) {
output, err := c.CombinedOutput()
assert.NoError(t, err, "Output:\n%s", output)
// Can't check exit code: tool only prints to output
assert.False(t, bytes.Contains(output, []byte("BUG")), "Output:\n%s", output)
}
const testdataDir = "testdata"
var testdataWithIssuesDir = filepath.Join(testdataDir, "with_issues")
var testdataNotCompilingDir = filepath.Join(testdataDir, "not_compiles")
const binName = "golangci-lint"
func TestSourcesFromTestdataWithIssuesDir(t *testing.T) {
t.Log(filepath.Join(testdataWithIssuesDir, "*.go"))
sources, err := filepath.Glob(filepath.Join(testdataWithIssuesDir, "*.go"))
assert.NoError(t, err)
assert.NotEmpty(t, sources)
installBinary(t)
for _, s := range sources {
s := s
t.Run(s, func(t *testing.T) {
t.Parallel()
testOneSource(t, s)
})
}
}
func installBinary(t assert.TestingT) {
cmd := exec.Command("go", "install", filepath.Join("..", "cmd", binName))
assert.NoError(t, cmd.Run(), "Can't go install %s", binName)
}
func testOneSource(t *testing.T, sourcePath string) {
goErrchkBin := filepath.Join(runtime.GOROOT(), "test", "errchk")
cmd := exec.Command(goErrchkBin, binName, "run",
"--enable-all",
"--dupl.threshold=20",
"--gocyclo.min-complexity=20",
"--print-issued-lines=false",
"--print-linter-name=false",
"--out-format=line-number",
"--print-welcome=false",
"--govet.check-shadowing=true",
sourcePath)
runGoErrchk(cmd, t)
}
func TestNotCompilingProgram(t *testing.T) {
installBinary(t)
err := exec.Command(binName, "run", "--enable-all", testdataNotCompilingDir).Run()
assert.NoError(t, err)
}
func chdir(b *testing.B, dir string) {
if err := os.Chdir(dir); err != nil {
b.Fatalf("can't chdir to %s: %s", dir, err)
}
}
func prepareGoSource(b *testing.B) {
chdir(b, filepath.Join(build.Default.GOROOT, "src"))
}
func prepareGithubProject(owner, name string) func(*testing.B) {
return func(b *testing.B) {
dir := filepath.Join(build.Default.GOPATH, "src", "github.com", owner, name)
_, err := os.Stat(dir)
if os.IsNotExist(err) {
err = exec.Command("git", "clone", fmt.Sprintf("https://github.com/%s/%s.git", owner, name)).Run()
if err != nil {
b.Fatalf("can't git clone %s/%s: %s", owner, name, err)
}
}
chdir(b, dir)
}
}
func getBenchLintersArgsNoMegacheck() []string {
return []string{
"--enable=deadcode",
"--enable=gocyclo",
"--enable=golint",
"--enable=varcheck",
"--enable=structcheck",
"--enable=maligned",
"--enable=errcheck",
"--enable=dupl",
"--enable=ineffassign",
"--enable=interfacer",
"--enable=unconvert",
"--enable=goconst",
"--enable=gas",
}
}
func getBenchLintersArgs() []string {
return append([]string{
"--enable=megacheck",
}, getBenchLintersArgsNoMegacheck()...)
}
func getBenchFastLintersArgs() []string {
return []string{
"--enable=dupl",
"--enable=goconst",
"--enable=gocyclo",
"--enable=golint",
"--enable=ineffassign",
// don't add gas because gometalinter uses old, fast and not working for me version of it.
// golangci-lint uses new and slower version of it.
}
}
func getGometalinterCommonArgs() []string {
return []string{
"--deadline=30m",
"--skip=testdata",
"--skip=builtin",
"--vendor",
"--cyclo-over=30",
"--dupl-threshold=150",
"--exclude", fmt.Sprintf("(%s)", strings.Join(config.DefaultExcludePatterns, "|")),
"--disable-all",
"--enable=vet",
"--enable=vetshadow",
}
}
func runGometalinter(b *testing.B) {
args := []string{}
args = append(args, getGometalinterCommonArgs()...)
args = append(args, getBenchLintersArgs()...)
args = append(args, "./...")
_ = exec.Command("gometalinter", args...).Run()
}
func runGometalinterFast(b *testing.B) {
args := []string{}
args = append(args, getGometalinterCommonArgs()...)
args = append(args, getBenchFastLintersArgs()...)
args = append(args, "./...")
_ = exec.Command("gometalinter", args...).Run()
}
func getGolangciLintCommonArgs() []string {
return []string{"run", "--no-config", "--issues-exit-code=0", "--deadline=30m", "--disable-all", "--enable=govet"}
}
func runGolangciLint(b *testing.B) {
args := getGolangciLintCommonArgs()
args = append(args, getBenchLintersArgs()...)
out, err := exec.Command("golangci-lint", args...).CombinedOutput()
if err != nil {
b.Fatalf("can't run golangci-lint: %s, %s", err, out)
}
}
func runGolangciLintFast(b *testing.B) {
args := getGolangciLintCommonArgs()
args = append(args, getBenchFastLintersArgs()...)
out, err := exec.Command("golangci-lint", args...).CombinedOutput()
if err != nil {
b.Fatalf("can't run golangci-lint: %s, %s", err, out)
}
}
func getGoLinesTotalCount(b *testing.B) int {
cmd := exec.Command("bash", "-c", `find . -name "*.go" | fgrep -v vendor | xargs wc -l | tail -1`)
out, err := cmd.CombinedOutput()
if err != nil {
b.Fatalf("can't run go lines counter: %s", err)
}
parts := bytes.Split(bytes.TrimSpace(out), []byte(" "))
n, err := strconv.Atoi(string(parts[0]))
if err != nil {
b.Fatalf("can't parse go lines count: %s", err)
}
return n
}
func getUsedMemoryMb(b *testing.B) int {
v, err := mem.VirtualMemory()
if err != nil {
b.Fatalf("can't get usedmemory: %s", err)
}
return int(v.Used / 1024 / 1024)
}
func trackPeakMemoryUsage(b *testing.B, doneCh chan struct{}) chan int {
resCh := make(chan int)
go func() {
var peakUsedMemMB int
t := time.NewTicker(time.Millisecond * 50)
defer t.Stop()
for {
select {
case <-doneCh:
resCh <- peakUsedMemMB
close(resCh)
return
case <-t.C:
}
m := getUsedMemoryMb(b)
if m > peakUsedMemMB {
peakUsedMemMB = m
}
}
}()
return resCh
}
func runBench(b *testing.B, run func(*testing.B), format string, args ...interface{}) {
startUsedMemMB := getUsedMemoryMb(b)
doneCh := make(chan struct{})
peakMemCh := trackPeakMemoryUsage(b, doneCh)
name := fmt.Sprintf(format, args...)
b.Run(name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
run(b)
}
})
close(doneCh)
peakUsedMemMB := <-peakMemCh
var linterPeakMemUsage int
if peakUsedMemMB > startUsedMemMB {
linterPeakMemUsage = peakUsedMemMB - startUsedMemMB
}
logrus.Warnf("%s: start used mem is %dMB, peak used mem is %dMB, linter peak mem usage is %dMB",
name, startUsedMemMB, peakUsedMemMB, linterPeakMemUsage)
}
func BenchmarkWithGometalinter(b *testing.B) {
installBinary(b)
type bcase struct {
name string
prepare func(*testing.B)
}
bcases := []bcase{
{
name: "self repo",
prepare: prepareGithubProject("golangci", "golangci-lint"),
},
{
name: "gometalinter repo",
prepare: prepareGithubProject("alecthomas", "gometalinter"),
},
{
name: "hugo",
prepare: prepareGithubProject("gohugoio", "hugo"),
},
{
name: "go source code",
prepare: prepareGoSource,
},
}
for _, bc := range bcases {
bc.prepare(b)
lc := getGoLinesTotalCount(b)
runBench(b, runGometalinterFast, "%s/gometalinter --fast (%d lines of code)", bc.name, lc)
runBench(b, runGolangciLintFast, "%s/golangci-lint fast (%d lines of code)", bc.name, lc)
runBench(b, runGometalinter, "%s/gometalinter (%d lines of code)", bc.name, lc)
runBench(b, runGolangciLint, "%s/golangci-lint (%d lines of code)", bc.name, lc)
}
}