Mateus Oliveira a727aa5780
dev: remove unrelated flags from config and linters command (#4284)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-02-03 20:11:24 +01:00

361 lines
7.2 KiB
Go

package testshared
import (
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"syscall"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/golangci/golangci-lint/pkg/exitcodes"
"github.com/golangci/golangci-lint/pkg/fsutils"
"github.com/golangci/golangci-lint/pkg/logutils"
)
const (
// value: "1"
envKeepTempFiles = "GL_KEEP_TEMP_FILES"
// value: "true"
envGolangciLintInstalled = "GOLANGCI_LINT_INSTALLED"
)
type RunnerBuilder struct {
tb testing.TB
log logutils.Log
binPath string
command string
env []string
configPath string
noConfig bool
allowParallelRunners bool
args []string
target string
}
func NewRunnerBuilder(tb testing.TB) *RunnerBuilder {
tb.Helper()
log := logutils.NewStderrLog(logutils.DebugKeyTest)
log.SetLevel(logutils.LogLevelInfo)
return &RunnerBuilder{
tb: tb,
log: log,
binPath: defaultBinaryName(),
command: "run",
allowParallelRunners: true,
}
}
func (b *RunnerBuilder) WithBinPath(binPath string) *RunnerBuilder {
b.binPath = binPath
return b
}
func (b *RunnerBuilder) WithCommand(command string) *RunnerBuilder {
b.command = command
return b
}
func (b *RunnerBuilder) WithNoConfig() *RunnerBuilder {
b.noConfig = true
return b
}
func (b *RunnerBuilder) WithConfigFile(cfgPath string) *RunnerBuilder {
if cfgPath != "" {
b.configPath = filepath.FromSlash(cfgPath)
}
b.noConfig = cfgPath == ""
return b
}
func (b *RunnerBuilder) WithConfig(cfg string) *RunnerBuilder {
b.tb.Helper()
content := strings.ReplaceAll(strings.TrimSpace(cfg), "\t", " ")
if content == "" {
return b.WithNoConfig()
}
cfgFile, err := os.CreateTemp("", "golangci_lint_test*.yml")
require.NoError(b.tb, err)
cfgPath := cfgFile.Name()
b.tb.Cleanup(func() {
if os.Getenv(envKeepTempFiles) != "1" {
_ = os.Remove(cfgPath)
}
})
_, err = cfgFile.WriteString(content)
require.NoError(b.tb, err)
return b.WithConfigFile(cfgPath)
}
func (b *RunnerBuilder) WithRunContext(rc *RunContext) *RunnerBuilder {
if rc == nil {
return b
}
dir, err := os.Getwd()
require.NoError(b.tb, err)
configPath := filepath.FromSlash(rc.ConfigPath)
base := filepath.Base(dir)
if strings.HasPrefix(configPath, base) {
configPath = strings.TrimPrefix(configPath, base+string(filepath.Separator))
}
return b.WithConfigFile(configPath).WithArgs(rc.Args...)
}
func (b *RunnerBuilder) WithDirectives(sourcePath string) *RunnerBuilder {
b.tb.Helper()
return b.WithRunContext(ParseTestDirectives(b.tb, sourcePath))
}
func (b *RunnerBuilder) WithEnviron(environ ...string) *RunnerBuilder {
b.env = environ
return b
}
func (b *RunnerBuilder) WithNoParallelRunners() *RunnerBuilder {
b.allowParallelRunners = false
return b
}
func (b *RunnerBuilder) WithArgs(args ...string) *RunnerBuilder {
b.args = append(b.args, args...)
return b
}
func (b *RunnerBuilder) WithTargetPath(targets ...string) *RunnerBuilder {
b.target = filepath.Join(targets...)
return b
}
func (b *RunnerBuilder) Runner() *Runner {
b.tb.Helper()
if b.noConfig && b.configPath != "" {
b.tb.Fatal("--no-config and -c cannot be used at the same time")
}
var arguments []string
if b.command == "run" {
arguments = append(arguments, "--internal-cmd-test")
if b.allowParallelRunners {
arguments = append(arguments, "--allow-parallel-runners")
}
}
if b.noConfig {
arguments = append(arguments, "--no-config")
}
if b.configPath != "" {
arguments = append(arguments, "-c", b.configPath)
}
if len(b.args) != 0 {
arguments = append(arguments, b.args...)
}
if b.target != "" {
arguments = append(arguments, b.target)
}
return &Runner{
binPath: b.binPath,
log: b.log,
tb: b.tb,
env: b.env,
command: b.command,
args: arguments,
}
}
type Runner struct {
log logutils.Log
tb testing.TB
binPath string
env []string
command string
args []string
installOnce sync.Once
}
func (r *Runner) Install() *Runner {
r.tb.Helper()
r.installOnce.Do(func() {
InstallGolangciLint(r.tb)
})
return r
}
func (r *Runner) Run() *RunnerResult {
r.tb.Helper()
runArgs := append([]string{r.command}, r.args...)
defer func(startedAt time.Time) {
r.log.Infof("ran [%s %s] in %s", r.binPath, strings.Join(runArgs, " "), time.Since(startedAt))
}(time.Now())
cmd := r.Command()
out, err := cmd.CombinedOutput()
if err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
if len(exitError.Stderr) != 0 {
r.log.Infof("stderr: %s", exitError.Stderr)
}
ws := exitError.Sys().(syscall.WaitStatus)
return &RunnerResult{
tb: r.tb,
output: string(out),
exitCode: ws.ExitStatus(),
}
}
r.tb.Errorf("can't get error code from %s", err)
return nil
}
// success, exitCode should be 0 if go is ok
ws := cmd.ProcessState.Sys().(syscall.WaitStatus)
return &RunnerResult{
tb: r.tb,
output: string(out),
exitCode: ws.ExitStatus(),
}
}
func (r *Runner) Command() *exec.Cmd {
r.tb.Helper()
runArgs := append([]string{r.command}, r.args...)
//nolint:gosec
cmd := exec.Command(r.binPath, runArgs...)
cmd.Env = append(os.Environ(), r.env...)
return cmd
}
type RunnerResult struct {
tb testing.TB
output string
exitCode int
}
func (r *RunnerResult) ExpectNoIssues() {
r.tb.Helper()
assert.Equal(r.tb, "", r.output, "exit code is %d", r.exitCode)
assert.Equal(r.tb, exitcodes.Success, r.exitCode, "output is %s", r.output)
}
func (r *RunnerResult) ExpectExitCode(possibleCodes ...int) *RunnerResult {
r.tb.Helper()
for _, pc := range possibleCodes {
if pc == r.exitCode {
return r
}
}
assert.Fail(r.tb, "invalid exit code", "exit code (%d) must be one of %v: %s", r.exitCode, possibleCodes, r.output)
return r
}
// ExpectOutputRegexp can be called with either a string or compiled regexp
func (r *RunnerResult) ExpectOutputRegexp(s string) *RunnerResult {
r.tb.Helper()
assert.Regexp(r.tb, fsutils.NormalizePathInRegex(s), r.output, "exit code is %d", r.exitCode)
return r
}
func (r *RunnerResult) ExpectOutputContains(s ...string) *RunnerResult {
r.tb.Helper()
for _, expected := range s {
assert.Contains(r.tb, r.output, normalizeFilePath(expected), "exit code is %d", r.exitCode)
}
return r
}
func (r *RunnerResult) ExpectOutputNotContains(s string) *RunnerResult {
r.tb.Helper()
assert.NotContains(r.tb, r.output, s, "exit code is %d", r.exitCode)
return r
}
func (r *RunnerResult) ExpectOutputEq(s string) *RunnerResult {
r.tb.Helper()
assert.Equal(r.tb, normalizeFilePath(s), r.output, "exit code is %d", r.exitCode)
return r
}
func (r *RunnerResult) ExpectHasIssue(issueText string) *RunnerResult {
r.tb.Helper()
return r.ExpectExitCode(exitcodes.IssuesFound).ExpectOutputContains(issueText)
}
func InstallGolangciLint(tb testing.TB) string {
tb.Helper()
if os.Getenv(envGolangciLintInstalled) != "true" {
cmd := exec.Command("make", "-C", "..", "build")
output, err := cmd.CombinedOutput()
if err != nil {
tb.Log(string(output))
}
require.NoError(tb, err, "Can't go install golangci-lint %s", string(output))
}
abs, err := filepath.Abs(defaultBinaryName())
require.NoError(tb, err)
return abs
}