1package testshared
2
3import (
4	"io/ioutil"
5	"os"
6	"os/exec"
7	"strings"
8	"sync"
9	"syscall"
10	"time"
11
12	"github.com/stretchr/testify/assert"
13
14	"github.com/golangci/golangci-lint/pkg/exitcodes"
15	"github.com/golangci/golangci-lint/pkg/logutils"
16)
17
18type LintRunner struct {
19	t           assert.TestingT
20	log         logutils.Log
21	env         []string
22	installOnce sync.Once
23}
24
25func NewLintRunner(t assert.TestingT, environ ...string) *LintRunner {
26	log := logutils.NewStderrLog("test")
27	log.SetLevel(logutils.LogLevelInfo)
28	return &LintRunner{
29		t:   t,
30		log: log,
31		env: environ,
32	}
33}
34
35func (r *LintRunner) Install() {
36	r.installOnce.Do(func() {
37		if os.Getenv("GOLANGCI_LINT_INSTALLED") == "true" {
38			return
39		}
40
41		cmd := exec.Command("make", "-C", "..", "build")
42		assert.NoError(r.t, cmd.Run(), "Can't go install golangci-lint")
43	})
44}
45
46type RunResult struct {
47	t assert.TestingT
48
49	output   string
50	exitCode int
51}
52
53func (r *RunResult) ExpectNoIssues() {
54	assert.Equal(r.t, "", r.output, "exit code is %d", r.exitCode)
55	assert.Equal(r.t, exitcodes.Success, r.exitCode, "output is %s", r.output)
56}
57
58func (r *RunResult) ExpectExitCode(possibleCodes ...int) *RunResult {
59	for _, pc := range possibleCodes {
60		if pc == r.exitCode {
61			return r
62		}
63	}
64
65	assert.Fail(r.t, "invalid exit code", "exit code (%d) must be one of %v: %s", r.exitCode, possibleCodes, r.output)
66	return r
67}
68
69// ExpectOutputRegexp can be called with either a string or compiled regexp
70func (r *RunResult) ExpectOutputRegexp(s interface{}) *RunResult {
71	assert.Regexp(r.t, s, r.output, "exit code is %d", r.exitCode)
72	return r
73}
74
75func (r *RunResult) ExpectOutputContains(s string) *RunResult {
76	assert.Contains(r.t, r.output, s, "exit code is %d", r.exitCode)
77	return r
78}
79
80func (r *RunResult) ExpectOutputEq(s string) *RunResult {
81	assert.Equal(r.t, s, r.output, "exit code is %d", r.exitCode)
82	return r
83}
84
85func (r *RunResult) ExpectHasIssue(issueText string) *RunResult {
86	return r.ExpectExitCode(exitcodes.IssuesFound).ExpectOutputContains(issueText)
87}
88
89func (r *LintRunner) Run(args ...string) *RunResult {
90	newArgs := append([]string{"--allow-parallel-runners"}, args...)
91	return r.RunCommand("run", newArgs...)
92}
93
94func (r *LintRunner) RunCommand(command string, args ...string) *RunResult {
95	r.Install()
96
97	runArgs := append([]string{command}, args...)
98	defer func(startedAt time.Time) {
99		r.log.Infof("ran [../golangci-lint %s] in %s", strings.Join(runArgs, " "), time.Since(startedAt))
100	}(time.Now())
101
102	cmd := exec.Command("../golangci-lint", runArgs...)
103	cmd.Env = append(os.Environ(), r.env...)
104	out, err := cmd.CombinedOutput()
105	if err != nil {
106		if exitError, ok := err.(*exec.ExitError); ok {
107			r.log.Infof("stderr: %s", exitError.Stderr)
108			ws := exitError.Sys().(syscall.WaitStatus)
109			return &RunResult{
110				t:        r.t,
111				output:   string(out),
112				exitCode: ws.ExitStatus(),
113			}
114		}
115
116		r.t.Errorf("can't get error code from %s", err)
117		return nil
118	}
119
120	// success, exitCode should be 0 if go is ok
121	ws := cmd.ProcessState.Sys().(syscall.WaitStatus)
122	return &RunResult{
123		t:        r.t,
124		output:   string(out),
125		exitCode: ws.ExitStatus(),
126	}
127}
128
129func (r *LintRunner) RunWithYamlConfig(cfg string, args ...string) *RunResult {
130	newArgs := append([]string{"--allow-parallel-runners"}, args...)
131	return r.RunCommandWithYamlConfig(cfg, "run", newArgs...)
132}
133
134func (r *LintRunner) RunCommandWithYamlConfig(cfg, command string, args ...string) *RunResult {
135	f, err := ioutil.TempFile("", "golangci_lint_test")
136	assert.NoError(r.t, err)
137	f.Close()
138
139	cfgPath := f.Name() + ".yml"
140	err = os.Rename(f.Name(), cfgPath)
141	assert.NoError(r.t, err)
142
143	if os.Getenv("GL_KEEP_TEMP_FILES") != "1" {
144		defer os.Remove(cfgPath)
145	}
146
147	cfg = strings.TrimSpace(cfg)
148	cfg = strings.Replace(cfg, "\t", " ", -1)
149
150	err = ioutil.WriteFile(cfgPath, []byte(cfg), os.ModePerm)
151	assert.NoError(r.t, err)
152
153	pargs := append([]string{"-c", cfgPath}, args...)
154	return r.RunCommand(command, pargs...)
155}
156