dev: fix CI workflow for Windows (#3134)

This commit is contained in:
Ludovic Fernandez 2022-08-24 22:10:51 +02:00 committed by GitHub
parent 890a82659b
commit bddc63a234
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 280 additions and 77 deletions

2
.gitattributes vendored
View File

@ -1 +1,3 @@
go.sum linguist-generated
* text=auto eol=lf
*.ps1 text eol=crlf

View File

@ -9,7 +9,7 @@ env:
GO_VERSION: 1.19
jobs:
# Check if there any dirty change for go mod tidy
# Check if there is any dirty change for go mod tidy
go-mod:
runs-on: ubuntu-latest
steps:
@ -41,8 +41,7 @@ jobs:
# ex:
# - 1.18beta1 -> 1.18.0-beta.1
# - 1.18rc1 -> 1.18.0-rc.1
# go-version: ${{ env.GO_VERSION }} # todo(ldez) uncomment after the next release v1.48.0
go-version: 1.18
go-version: ${{ env.GO_VERSION }}
- name: lint
uses: golangci/golangci-lint-action@v3.2.0
with:
@ -66,7 +65,6 @@ jobs:
go-version: ${{ env.GO_VERSION }} # test only the latest go version to speed up CI
- name: Run tests
run: make.exe test
continue-on-error: true
tests-on-macos:
needs: golangci-lint # run after golangci-lint action to not produce duplicated errors

View File

@ -4,17 +4,22 @@
# enable consistent Go 1.12/1.13 GOPROXY behavior.
export GOPROXY = https://proxy.golang.org
BINARY = golangci-lint
ifeq ($(OS),Windows_NT)
BINARY := $(BINARY).exe
endif
# Build
build: golangci-lint
build: $(BINARY)
.PHONY: build
build_race:
go build -race -o golangci-lint ./cmd/golangci-lint
go build -race -o $(BINARY) ./cmd/golangci-lint
.PHONY: build_race
clean:
rm -f golangci-lint
rm -f $(BINARY)
rm -f test/path
rm -f tools/Dracula.itermcolors
rm -f tools/goreleaser
@ -25,7 +30,7 @@ clean:
# Test
test: export GOLANGCI_LINT_INSTALLED = true
test: build
GL_TEST_RUN=1 ./golangci-lint run -v
GL_TEST_RUN=1 ./$(BINARY) run -v
GL_TEST_RUN=1 go test -v -parallel 2 ./...
.PHONY: test
@ -36,7 +41,7 @@ test_fix: build
.PHONY: test_fix
test_race: build_race
GL_TEST_RUN=1 ./golangci-lint run -v --timeout=5m
GL_TEST_RUN=1 ./$(BINARY) run -v --timeout=5m
.PHONY: test_race
test_linters:
@ -67,7 +72,7 @@ snapshot: .goreleaser.yml tools/goreleaser
# Non-PHONY targets (real files)
golangci-lint: FORCE
$(BINARY): FORCE
go build -o $@ ./cmd/golangci-lint
tools/goreleaser: export GOFLAGS = -mod=readonly
@ -87,7 +92,7 @@ tools/Dracula.itermcolors:
assets/demo.svg: tools/svg-term tools/Dracula.itermcolors
./tools/svg-term --cast=183662 --out assets/demo.svg --window --width 110 --height 30 --from 2000 --to 20000 --profile ./tools/Dracula.itermcolors --term iterm2
assets/github-action-config.json: FORCE golangci-lint
assets/github-action-config.json: FORCE $(BINARY)
# go run ./scripts/gen_github_action_config/main.go $@
cd ./scripts/gen_github_action_config/; go run ./main.go ../../$@

View File

@ -1,10 +1,6 @@
package processors
import (
"path/filepath"
"regexp"
"strings"
"github.com/pkg/errors"
"github.com/golangci/golangci-lint/pkg/result"
@ -48,15 +44,3 @@ func transformIssues(issues []result.Issue, transform func(i *result.Issue) *res
return retIssues
}
var separatorToReplace = regexp.QuoteMeta(string(filepath.Separator))
func normalizePathInRegex(path string) string {
if filepath.Separator == '/' {
return path
}
// This replacing should be safe because "/" are disallowed in Windows
// https://docs.microsoft.com/ru-ru/windows/win32/fileio/naming-a-file
return strings.ReplaceAll(path, "/", separatorToReplace)
}

View File

@ -1,7 +1,7 @@
package processors
import (
"path"
"path/filepath"
"github.com/golangci/golangci-lint/pkg/result"
)
@ -27,7 +27,7 @@ func (*PathPrefixer) Name() string {
func (p *PathPrefixer) Process(issues []result.Issue) ([]result.Issue, error) {
if p.prefix != "" {
for i := range issues {
issues[i].Pos.Filename = path.Join(p.prefix, issues[i].Pos.Filename)
issues[i].Pos.Filename = filepath.Join(p.prefix, issues[i].Pos.Filename)
}
}
return issues, nil

View File

@ -2,8 +2,10 @@ package processors
import (
"go/token"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/golangci/golangci-lint/pkg/result"
@ -12,26 +14,40 @@ import (
func TestPathPrefixer_Process(t *testing.T) {
paths := func(ps ...string) (issues []result.Issue) {
for _, p := range ps {
issues = append(issues, result.Issue{Pos: token.Position{Filename: p}})
issues = append(issues, result.Issue{Pos: token.Position{Filename: filepath.FromSlash(p)}})
}
return
}
for _, tt := range []struct {
name, prefix string
issues, want []result.Issue
}{
{"empty prefix", "", paths("some/path", "cool"), paths("some/path", "cool")},
{"prefix", "ok", paths("some/path", "cool"), paths("ok/some/path", "ok/cool")},
{"prefix slashed", "ok/", paths("some/path", "cool"), paths("ok/some/path", "ok/cool")},
{
name: "empty prefix",
issues: paths("some/path", "cool"),
want: paths("some/path", "cool"),
},
{
name: "prefix",
prefix: "ok",
issues: paths("some/path", "cool"),
want: paths("ok/some/path", "ok/cool"),
},
{
name: "prefix slashed",
prefix: "ok/",
issues: paths("some/path", "cool"),
want: paths("ok/some/path", "ok/cool"),
},
} {
t.Run(tt.name, func(t *testing.T) {
r := require.New(t)
p := NewPathPrefixer(tt.prefix)
got, err := p.Process(tt.issues)
r.NoError(err, "prefixer should never error")
r.Equal(got, tt.want)
got, err := p.Process(tt.issues)
require.NoError(t, err)
assert.Equal(t, got, tt.want)
})
}
}

View File

@ -0,0 +1,8 @@
//go:build !windows
package processors
// normalizePathInRegex it's a noop function on Unix.
func normalizePathInRegex(path string) string {
return path
}

View File

@ -0,0 +1,19 @@
//go:build windows
package processors
import (
"path/filepath"
"regexp"
"strings"
)
var separatorToReplace = regexp.QuoteMeta(string(filepath.Separator))
// normalizePathInRegex normalizes path in regular expressions.
// noop on Unix.
// This replacing should be safe because "/" are disallowed in Windows
// https://docs.microsoft.com/windows/win32/fileio/naming-a-file
func normalizePathInRegex(path string) string {
return strings.ReplaceAll(path, "/", separatorToReplace)
}

View File

@ -49,7 +49,8 @@ func createSeverityRules(rules []SeverityRule, prefix string) []severityRule {
parsedRule.source = regexp.MustCompile(prefix + rule.Source)
}
if rule.Path != "" {
parsedRule.path = regexp.MustCompile(rule.Path)
path := normalizePathInRegex(rule.Path)
parsedRule.path = regexp.MustCompile(path)
}
parsedRules = append(parsedRules, parsedRule)
}

View File

@ -2,6 +2,8 @@ package processors
import (
"go/token"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
@ -33,14 +35,15 @@ func TestSkipFiles(t *testing.T) {
processAssertEmpty(t, newTestSkipFiles(t, ".*"), newFileIssue("any.go"))
processAssertEmpty(t, newTestSkipFiles(t, "a/b/c.go"), newFileIssue("a/b/c.go"))
processAssertSame(t, newTestSkipFiles(t, "a/b/c.go"), newFileIssue("a/b/d.go"))
cleanPath := strings.ReplaceAll(filepath.FromSlash("a/b/c.go"), `\`, `\\`)
processAssertEmpty(t, newTestSkipFiles(t, cleanPath), newFileIssue(filepath.FromSlash("a/b/c.go")))
processAssertSame(t, newTestSkipFiles(t, cleanPath), newFileIssue(filepath.FromSlash("a/b/d.go")))
processAssertEmpty(t, newTestSkipFiles(t, ".*\\.pb\\.go"), newFileIssue("a/b.pb.go"))
processAssertSame(t, newTestSkipFiles(t, ".*\\.pb\\.go"), newFileIssue("a/b.go"))
processAssertEmpty(t, newTestSkipFiles(t, ".*\\.pb\\.go"), newFileIssue(filepath.FromSlash("a/b.pb.go")))
processAssertSame(t, newTestSkipFiles(t, ".*\\.pb\\.go"), newFileIssue(filepath.FromSlash("a/b.go")))
processAssertEmpty(t, newTestSkipFiles(t, ".*\\.pb\\.go$"), newFileIssue("a/b.pb.go"))
processAssertSame(t, newTestSkipFiles(t, ".*\\.pb\\.go$"), newFileIssue("a/b.go"))
processAssertEmpty(t, newTestSkipFiles(t, ".*\\.pb\\.go$"), newFileIssue(filepath.FromSlash("a/b.pb.go")))
processAssertSame(t, newTestSkipFiles(t, ".*\\.pb\\.go$"), newFileIssue(filepath.FromSlash("a/b.go")))
}
func TestSkipFilesInvalidPattern(t *testing.T) {

View File

@ -12,6 +12,8 @@ import (
)
func TestFix(t *testing.T) {
testshared.SkipOnWindows(t)
tmpDir := filepath.Join(testdataDir, "fix.tmp")
_ = os.RemoveAll(tmpDir) // cleanup previous runs

View File

@ -22,6 +22,8 @@ func TestSourcesFromTestdata(t *testing.T) {
}
func TestTypecheck(t *testing.T) {
testshared.SkipOnWindows(t)
testSourcesFromDir(t, filepath.Join(testdataDir, "notcompiles"))
}

View File

@ -3,7 +3,6 @@ package test
import (
"fmt"
"os"
"path"
"path/filepath"
"testing"
@ -50,11 +49,11 @@ func TestOutput_Stderr(t *testing.T) {
Runner().
Install().
Run().
ExpectHasIssue(expectedJSONOutput)
ExpectHasIssue(testshared.NormalizeFilePathInJSON(expectedJSONOutput))
}
func TestOutput_File(t *testing.T) {
resultPath := path.Join(t.TempDir(), "golangci_lint_test_result")
resultPath := filepath.Join(t.TempDir(), "golangci_lint_test_result")
sourcePath := filepath.Join(testdataDir, "misspell.go")
@ -74,7 +73,7 @@ func TestOutput_File(t *testing.T) {
b, err := os.ReadFile(resultPath)
require.NoError(t, err)
require.Contains(t, string(b), expectedJSONOutput)
require.Contains(t, string(b), testshared.NormalizeFilePathInJSON(expectedJSONOutput))
}
func TestOutput_Multiple(t *testing.T) {
@ -94,5 +93,5 @@ func TestOutput_Multiple(t *testing.T) {
Run().
//nolint:misspell
ExpectHasIssue("testdata/misspell.go:6:38: `occured` is a misspelling of `occurred`").
ExpectOutputContains(expectedJSONOutput)
ExpectOutputContains(testshared.NormalizeFilePathInJSON(expectedJSONOutput))
}

View File

@ -45,7 +45,7 @@ func TestNotExistingDirRun(t *testing.T) {
Run().
ExpectExitCode(exitcodes.Failure).
ExpectOutputContains("cannot find package").
ExpectOutputContains("/testdata/no_such_dir")
ExpectOutputContains(testshared.NormalizeFileInString("/testdata/no_such_dir"))
}
func TestSymlinkLoop(t *testing.T) {

View File

@ -1,3 +1,5 @@
//go:build !windows
//golangcitest:args -Edepguard
//golangcitest:config_path testdata/configs/depguard_ignore_file_rules.yml
//golangcitest:expected_exitcode 0

View File

@ -1,3 +1,5 @@
//go:build !windows
//golangcitest:args -Eifshort --internal-cmd-test
package testdata

View File

@ -1,5 +1,4 @@
//go:build go1.18
// +build go1.18
//golangcitest:args -Etenv
package testdata

View File

@ -1,5 +1,4 @@
//go:build go1.18
// +build go1.18
//golangcitest:args -Ethelper
package testdata

View File

@ -46,7 +46,7 @@ func Analyze(t *testing.T, sourcePath string, rawData []byte) {
var reportData jsonResult
err = json.Unmarshal(rawData, &reportData)
require.NoError(t, err)
require.NoError(t, err, string(rawData))
for _, issue := range reportData.Issues {
checkMessage(t, want, issue.Pos, "diagnostic", issue.FromLinter, issue.Text)

View File

@ -15,7 +15,7 @@ import (
"github.com/golangci/golangci-lint/pkg/exitcodes"
)
// RunContext FIXME rename?
// RunContext the information extracted from directives.
type RunContext struct {
Args []string
ConfigPath string
@ -51,11 +51,8 @@ func ParseTestDirectives(tb testing.TB, sourcePath string) *RunContext {
break
}
if strings.HasPrefix(line, "//go:build") || strings.HasPrefix(line, "// +build") {
parse, err := constraint.Parse(line)
require.NoError(tb, err)
if !parse.Eval(buildTagGoVersion) {
if constraint.IsGoBuild(line) {
if !evaluateBuildTags(tb, line) {
return nil
}
@ -120,6 +117,25 @@ func skipMultilineComment(scanner *bufio.Scanner) {
}
}
// evaluateBuildTags Naive implementation of the evaluation of the build tags.
// Inspired by https://github.com/golang/go/blob/1dcef7b3bdcea4a829ea22c821e6a9484c325d61/src/cmd/go/internal/modindex/build.go#L914-L972
func evaluateBuildTags(tb testing.TB, line string) bool {
parse, err := constraint.Parse(line)
require.NoError(tb, err)
return parse.Eval(func(tag string) bool {
if tag == runtime.GOOS {
return true
}
if buildTagGoVersion(tag) {
return true
}
return false
})
}
func buildTagGoVersion(tag string) bool {
vRuntime, err := hcversion.NewVersion(strings.TrimPrefix(runtime.Version(), "go"))
if err != nil {

View File

@ -1,6 +1,7 @@
package testshared
import (
"runtime"
"testing"
"github.com/stretchr/testify/assert"
@ -21,3 +22,61 @@ func TestParseTestDirectives(t *testing.T) {
}
assert.Equal(t, expected, rc)
}
func Test_evaluateBuildTags(t *testing.T) {
testCases := []struct {
desc string
tag string
assert assert.BoolAssertionFunc
}{
{
desc: "",
tag: "// +build go1.18",
assert: assert.True,
},
{
desc: "",
tag: "// +build go1.42",
assert: assert.False,
},
{
desc: "",
tag: "//go:build go1.18",
assert: assert.True,
},
{
desc: "",
tag: "//go:build go1.42",
assert: assert.False,
},
{
desc: "",
tag: "//go:build " + runtime.GOOS,
assert: assert.True,
},
{
desc: "",
tag: "//go:build !wondiws",
assert: assert.True,
},
{
desc: "",
tag: "//go:build wondiws",
assert: assert.False,
},
{
desc: "",
tag: "//go:build go1.18 && " + runtime.GOOS,
assert: assert.True,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
test.assert(t, evaluateBuildTags(t, test.tag))
})
}
}

View File

@ -17,8 +17,6 @@ import (
"github.com/golangci/golangci-lint/pkg/logutils"
)
const defaultBinPath = "../golangci-lint"
type RunnerBuilder struct {
tb testing.TB
log logutils.Log
@ -43,7 +41,7 @@ func NewRunnerBuilder(tb testing.TB) *RunnerBuilder {
return &RunnerBuilder{
tb: tb,
log: log,
binPath: defaultBinPath,
binPath: defaultBinaryName(),
command: "run",
allowParallelRunners: true,
}
@ -68,7 +66,10 @@ func (b *RunnerBuilder) WithNoConfig() *RunnerBuilder {
}
func (b *RunnerBuilder) WithConfigFile(cfgPath string) *RunnerBuilder {
b.configPath = cfgPath
if cfgPath != "" {
b.configPath = filepath.FromSlash(cfgPath)
}
b.noConfig = cfgPath == ""
return b
@ -293,17 +294,17 @@ func (r *RunnerResult) ExpectExitCode(possibleCodes ...int) *RunnerResult {
}
// ExpectOutputRegexp can be called with either a string or compiled regexp
func (r *RunnerResult) ExpectOutputRegexp(s interface{}) *RunnerResult {
func (r *RunnerResult) ExpectOutputRegexp(s string) *RunnerResult {
r.tb.Helper()
assert.Regexp(r.tb, s, r.output, "exit code is %d", r.exitCode)
assert.Regexp(r.tb, normalizePathInRegex(s), r.output, "exit code is %d", r.exitCode)
return r
}
func (r *RunnerResult) ExpectOutputContains(s string) *RunnerResult {
r.tb.Helper()
assert.Contains(r.tb, r.output, s, "exit code is %d", r.exitCode)
assert.Contains(r.tb, r.output, normalizeFilePath(s), "exit code is %d", r.exitCode)
return r
}
@ -317,7 +318,7 @@ func (r *RunnerResult) ExpectOutputNotContains(s string) *RunnerResult {
func (r *RunnerResult) ExpectOutputEq(s string) *RunnerResult {
r.tb.Helper()
assert.Equal(r.tb, s, r.output, "exit code is %d", r.exitCode)
assert.Equal(r.tb, normalizeFilePath(s), r.output, "exit code is %d", r.exitCode)
return r
}
@ -338,10 +339,10 @@ func InstallGolangciLint(tb testing.TB) string {
tb.Log(string(output))
}
require.NoError(tb, err, "Can't go install golangci-lint")
assert.NoError(tb, err, "Can't go install golangci-lint %s", string(output))
}
abs, err := filepath.Abs(defaultBinPath)
abs, err := filepath.Abs(defaultBinaryName())
require.NoError(tb, err)
return abs

View File

@ -1,7 +1,7 @@
package testshared
import (
"regexp"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
@ -67,7 +67,7 @@ func TestRunnerBuilder_Runner(t *testing.T) {
"--internal-cmd-test",
"--allow-parallel-runners",
"-c",
"./testdata/example.yml",
filepath.FromSlash("./testdata/example.yml"),
},
},
},
@ -82,7 +82,7 @@ func TestRunnerBuilder_Runner(t *testing.T) {
"--internal-cmd-test",
"--allow-parallel-runners",
"-c",
"testdata/example.yml",
filepath.FromSlash("testdata/example.yml"),
"-Efoo",
"--simple",
"--hello=world",
@ -140,7 +140,7 @@ func TestRunnerBuilder_Runner(t *testing.T) {
"--go=1.17",
"--internal-cmd-test",
"--allow-parallel-runners",
"testdata/all.go",
filepath.FromSlash("testdata/all.go"),
},
},
},
@ -149,7 +149,7 @@ func TestRunnerBuilder_Runner(t *testing.T) {
builder: NewRunnerBuilder(t).
WithRunContext(&RunContext{
Args: []string{"-Efoo", "--simple", "--hello=world"},
ConfigPath: "testdata/example.yml",
ConfigPath: filepath.FromSlash("testdata/example.yml"),
ExpectedLinter: "test",
}),
expected: &Runner{
@ -160,7 +160,7 @@ func TestRunnerBuilder_Runner(t *testing.T) {
"--internal-cmd-test",
"--allow-parallel-runners",
"-c",
"testdata/example.yml",
filepath.FromSlash("testdata/example.yml"),
"-Efoo",
"--simple",
"--hello=world",
@ -219,7 +219,6 @@ func TestRunnerResult_ExpectOutputNotContains(t *testing.T) {
func TestRunnerResult_ExpectOutputRegexp(t *testing.T) {
r := &RunnerResult{tb: t, output: "this is an output"}
r.ExpectOutputRegexp(regexp.MustCompile(`an.+`))
r.ExpectOutputRegexp(`an.+`)
r.ExpectOutputRegexp("an")
}

View File

@ -0,0 +1,36 @@
//go:build !windows
package testshared
import (
"path/filepath"
"testing"
)
// SkipOnWindows it's a noop function on Unix.
func SkipOnWindows(_ testing.TB) {}
// NormalizeFilePathInJSON it's a noop function on Unix.
func NormalizeFilePathInJSON(in string) string {
return in
}
// NormalizeFileInString it's a noop function on Unix.
func NormalizeFileInString(in string) string {
return in
}
// defaultBinaryName returns the path to the default binary.
func defaultBinaryName() string {
return filepath.Join("..", "golangci-lint")
}
// normalizeFilePath it's a noop function on Unix.
func normalizeFilePath(in string) string {
return in
}
// normalizePathInRegex it's a noop function on Unix.
func normalizePathInRegex(path string) string {
return path
}

View File

@ -0,0 +1,51 @@
//go:build windows
package testshared
import (
"path/filepath"
"regexp"
"strings"
"testing"
)
// SkipOnWindows skip test on Windows.
func SkipOnWindows(tb testing.TB) {
tb.Skip("not supported on Windows")
}
// NormalizeFilePathInJSON find Go file path and replace `/` with `\\\\`.
func NormalizeFilePathInJSON(in string) string {
exp := regexp.MustCompile(`(?:^|\b)[\w-/.]+\.go`)
return exp.ReplaceAllStringFunc(in, func(s string) string {
return strings.ReplaceAll(s, "/", "\\\\")
})
}
// NormalizeFileInString normalizes in quoted string, ie. replace `\\` with `\\\\`.
func NormalizeFileInString(in string) string {
return strings.ReplaceAll(filepath.FromSlash(in), "\\", "\\\\")
}
// defaultBinaryName returns the path to the default binary.
func defaultBinaryName() string {
return filepath.Join("..", "golangci-lint.exe")
}
// normalizeFilePath find Go file path and replace `/` with `\\`.
func normalizeFilePath(in string) string {
exp := regexp.MustCompile(`(?:^|\b)[\w-/.]+\.go`)
return exp.ReplaceAllStringFunc(in, func(s string) string {
return strings.ReplaceAll(s, "/", "\\")
})
}
// normalizePathInRegex normalizes path in regular expressions.
// Replace all `/` with `\\`.
// This replacing should be safe because "/" are disallowed in Windows
// https://docs.microsoft.com/windows/win32/fileio/naming-a-file
func normalizePathInRegex(path string) string {
return strings.ReplaceAll(path, "/", regexp.QuoteMeta(string(filepath.Separator)))
}