Skip to content

Commit cb58d1f

Browse files
authored
speed up CI and golangci-lint (#1070)
Run CI on mac os only with go1.13 and on windows only on go1.14. Speed up tests. Introduce --allow-parallel-runners. Block on parallel run lock 5s instead of 60s. Don't invalidate analysis cache for minor config changes.
1 parent f0012d3 commit cb58d1f

File tree

16 files changed

+145
-71
lines changed

16 files changed

+145
-71
lines changed

.github/workflows/pr.yml

+6-7
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,12 @@ jobs:
2323
tests-on-windows:
2424
needs: golangci-lint # run after golangci-lint action to not produce duplicated errors
2525
runs-on: windows-latest
26-
strategy:
27-
matrix:
28-
golang:
29-
- 1.13
30-
- 1.14
3126
steps:
3227
- uses: actions/checkout@v2
3328
- name: Install Go
3429
uses: actions/setup-go@v2
3530
with:
36-
go-version: ${{ matrix.golang }}
31+
go-version: 1.14 # test only the latest go version to speed up CI
3732
- name: Run tests on Windows
3833
run: make.exe test
3934
continue-on-error: true
@@ -47,7 +42,11 @@ jobs:
4742
- 1.14
4843
os:
4944
- ubuntu-latest
50-
- macos-latest
45+
include:
46+
- os: macos-latest
47+
# test only the one go version on Mac OS to speed up CI
48+
# TODO: use the latet go version after https://github.com/golang/go/issues/38824
49+
golang: 1.13
5150
steps:
5251
- uses: actions/checkout@v2
5352
- name: Install Go

.golangci.example.yml

+4
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ run:
5353
# the dependency descriptions in go.mod.
5454
modules-download-mode: readonly|release|vendor
5555

56+
# Allow multiple parallel golangci-lint instances running.
57+
# If false (default) - golangci-lint acquires file lock on start.
58+
allow-parallel-runners: false
59+
5660

5761
# output configuration options
5862
output:

Makefile

+1-3
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,7 @@ clean:
2929
test: export GOLANGCI_LINT_INSTALLED = true
3030
test: build
3131
GL_TEST_RUN=1 ./golangci-lint run -v
32-
GL_TEST_RUN=1 ./golangci-lint run --fast --no-config -v --skip-dirs 'test/testdata_etc,internal/(cache|renameio|robustio)'
33-
GL_TEST_RUN=1 ./golangci-lint run --no-config -v --skip-dirs 'test/testdata_etc,internal/(cache|renameio|robustio)'
34-
GL_TEST_RUN=1 go test -v ./...
32+
GL_TEST_RUN=1 go test -v -parallel 2 ./...
3533
.PHONY: test
3634

3735
test_race: build_race

README.md

+5
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,7 @@ Flags:
546546
- (^|/)builtin($|/)
547547
(default true)
548548
--skip-files strings Regexps of files to skip
549+
--allow-parallel-runners Allow multiple parallel golangci-lint instances running. If false (default) - golangci-lint acquires file lock on start.
549550
-E, --enable strings Enable specific linter
550551
-D, --disable strings Disable specific linter
551552
--disable-all Disable all linters
@@ -679,6 +680,10 @@ run:
679680
# the dependency descriptions in go.mod.
680681
modules-download-mode: readonly|release|vendor
681682
683+
# Allow multiple parallel golangci-lint instances running.
684+
# If false (default) - golangci-lint acquires file lock on start.
685+
allow-parallel-runners: false
686+
682687
683688
# output configuration options
684689
output:

internal/cache/cache_test.go

+6
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ func init() {
2020
}
2121

2222
func TestBasic(t *testing.T) {
23+
t.Parallel()
24+
2325
dir, err := ioutil.TempDir("", "cachetest-")
2426
if err != nil {
2527
t.Fatal(err)
@@ -65,6 +67,8 @@ func TestBasic(t *testing.T) {
6567
}
6668

6769
func TestGrowth(t *testing.T) {
70+
t.Parallel()
71+
6872
dir, err := ioutil.TempDir("", "cachetest-")
6973
if err != nil {
7074
t.Fatal(err)
@@ -151,6 +155,8 @@ func dummyID(x int) [HashSize]byte {
151155
}
152156

153157
func TestCacheTrim(t *testing.T) {
158+
t.Parallel()
159+
154160
dir, err := ioutil.TempDir("", "cachetest-")
155161
if err != nil {
156162
t.Fatal(err)

pkg/commands/config.go

+16-6
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,32 @@ func (e *Executor) initConfig() {
3535
cmd.AddCommand(pathCmd)
3636
}
3737

38+
func (e *Executor) getUsedConfig() string {
39+
usedConfigFile := viper.ConfigFileUsed()
40+
if usedConfigFile == "" {
41+
return ""
42+
}
43+
44+
prettyUsedConfigFile, err := fsutils.ShortestRelPath(usedConfigFile, "")
45+
if err != nil {
46+
e.log.Warnf("Can't pretty print config file path: %s", err)
47+
return usedConfigFile
48+
}
49+
50+
return prettyUsedConfigFile
51+
}
52+
3853
func (e *Executor) executePathCmd(_ *cobra.Command, args []string) {
3954
if len(args) != 0 {
4055
e.log.Fatalf("Usage: golangci-lint config path")
4156
}
4257

43-
usedConfigFile := viper.ConfigFileUsed()
58+
usedConfigFile := e.getUsedConfig()
4459
if usedConfigFile == "" {
4560
e.log.Warnf("No config file detected")
4661
os.Exit(exitcodes.NoConfigFileDetected)
4762
}
4863

49-
usedConfigFile, err := fsutils.ShortestRelPath(usedConfigFile, "")
50-
if err != nil {
51-
e.log.Warnf("Can't pretty print config file path: %s", err)
52-
}
53-
5464
fmt.Println(usedConfigFile)
5565
os.Exit(0)
5666
}

pkg/commands/executor.go

+31-15
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import (
55
"context"
66
"crypto/sha256"
77
"encoding/json"
8-
"fmt"
98
"io"
109
"os"
1110
"path/filepath"
11+
"strings"
1212
"time"
1313

1414
"github.com/fatih/color"
@@ -31,8 +31,9 @@ import (
3131
)
3232

3333
type Executor struct {
34-
rootCmd *cobra.Command
35-
runCmd *cobra.Command
34+
rootCmd *cobra.Command
35+
runCmd *cobra.Command
36+
lintersCmd *cobra.Command
3637

3738
exitCode int
3839
version, commit, date string
@@ -55,6 +56,7 @@ type Executor struct {
5556
}
5657

5758
func NewExecutor(version, commit, date string) *Executor {
59+
startedAt := time.Now()
5860
e := &Executor{
5961
cfg: config.NewDefault(),
6062
version: version,
@@ -66,9 +68,6 @@ func NewExecutor(version, commit, date string) *Executor {
6668

6769
e.debugf("Starting execution...")
6870
e.log = report.NewLogWrapper(logutils.NewStderrLog(""), &e.reportData)
69-
if ok := e.acquireFileLock(); !ok {
70-
e.log.Fatalf("Parallel golangci-lint is running")
71-
}
7271

7372
// to setup log level early we need to parse config from command line extra time to
7473
// find `-v` option
@@ -121,6 +120,7 @@ func NewExecutor(version, commit, date string) *Executor {
121120

122121
// Slice options must be explicitly set for proper merging of config and command-line options.
123122
fixSlicesFlags(e.runCmd.Flags())
123+
fixSlicesFlags(e.lintersCmd.Flags())
124124

125125
e.EnabledLintersSet = lintersdb.NewEnabledSet(e.DBManager,
126126
lintersdb.NewValidator(e.DBManager), e.log.Child("lintersdb"), e.cfg)
@@ -139,7 +139,7 @@ func NewExecutor(version, commit, date string) *Executor {
139139
if err = e.initHashSalt(version); err != nil {
140140
e.log.Fatalf("Failed to init hash salt: %s", err)
141141
}
142-
e.debugf("Initialized executor")
142+
e.debugf("Initialized executor in %s", time.Since(startedAt))
143143
return e
144144
}
145145

@@ -191,27 +191,39 @@ func computeBinarySalt(version string) ([]byte, error) {
191191
}
192192

193193
func computeConfigSalt(cfg *config.Config) ([]byte, error) {
194-
configBytes, err := json.Marshal(cfg)
194+
// We don't hash all config fields to reduce meaningless cache
195+
// invalidations. At least, it has a huge impact on tests speed.
196+
197+
lintersSettingsBytes, err := json.Marshal(cfg.LintersSettings)
195198
if err != nil {
196-
return nil, errors.Wrap(err, "failed to json marshal config")
199+
return nil, errors.Wrap(err, "failed to json marshal config linter settings")
197200
}
198201

202+
var configData bytes.Buffer
203+
configData.WriteString("linters-settings=")
204+
configData.Write(lintersSettingsBytes)
205+
configData.WriteString("\nbuild-tags=%s" + strings.Join(cfg.Run.BuildTags, ","))
206+
199207
h := sha256.New()
200-
if n, err := h.Write(configBytes); n != len(configBytes) {
201-
return nil, fmt.Errorf("failed to hash config bytes: wrote %d/%d bytes, error: %s", n, len(configBytes), err)
202-
}
208+
h.Write(configData.Bytes()) //nolint:errcheck
203209
return h.Sum(nil), nil
204210
}
205211

206212
func (e *Executor) acquireFileLock() bool {
213+
if e.cfg.Run.AllowParallelRunners {
214+
e.debugf("Parallel runners are allowed, no locking")
215+
return true
216+
}
217+
207218
lockFile := filepath.Join(os.TempDir(), "golangci-lint.lock")
208219
e.debugf("Locking on file %s...", lockFile)
209220
f := flock.New(lockFile)
210-
ctx, finish := context.WithTimeout(context.Background(), time.Minute)
221+
const totalTimeout = 5 * time.Second
222+
const retryDelay = time.Second
223+
ctx, finish := context.WithTimeout(context.Background(), totalTimeout)
211224
defer finish()
212225

213-
timeout := time.Second * 3
214-
if ok, _ := f.TryLockContext(ctx, timeout); !ok {
226+
if ok, _ := f.TryLockContext(ctx, retryDelay); !ok {
215227
return false
216228
}
217229

@@ -220,6 +232,10 @@ func (e *Executor) acquireFileLock() bool {
220232
}
221233

222234
func (e *Executor) releaseFileLock() {
235+
if e.cfg.Run.AllowParallelRunners {
236+
return
237+
}
238+
223239
if err := e.flock.Unlock(); err != nil {
224240
e.debugf("Failed to unlock on file: %s", err)
225241
}

pkg/commands/linters.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ import (
1111
)
1212

1313
func (e *Executor) initLinters() {
14-
lintersCmd := &cobra.Command{
14+
e.lintersCmd = &cobra.Command{
1515
Use: "linters",
1616
Short: "List current linters configuration",
1717
Run: e.executeLinters,
1818
}
19-
e.rootCmd.AddCommand(lintersCmd)
20-
e.initRunConfiguration(lintersCmd)
19+
e.rootCmd.AddCommand(e.lintersCmd)
20+
e.initRunConfiguration(e.lintersCmd)
2121
}
2222

2323
func (e *Executor) executeLinters(_ *cobra.Command, args []string) {

pkg/commands/root.go

-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ func (e *Executor) persistentPostRun(_ *cobra.Command, _ []string) {
7373
trace.Stop()
7474
}
7575

76-
e.releaseFileLock()
7776
os.Exit(e.exitCode)
7877
}
7978

pkg/commands/run.go

+12
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ func initFlagSet(fs *pflag.FlagSet, cfg *config.Config, m *lintersdb.Manager, is
106106
fs.BoolVar(&rc.UseDefaultSkipDirs, "skip-dirs-use-default", true, getDefaultDirectoryExcludeHelp())
107107
fs.StringSliceVar(&rc.SkipFiles, "skip-files", nil, wh("Regexps of files to skip"))
108108

109+
const allowParallelDesc = "Allow multiple parallel golangci-lint instances running. " +
110+
"If false (default) - golangci-lint acquires file lock on start."
111+
fs.BoolVar(&rc.AllowParallelRunners, "allow-parallel-runners", false, wh(allowParallelDesc))
112+
109113
// Linters settings config
110114
lsc := &cfg.LintersSettings
111115

@@ -251,6 +255,14 @@ func (e *Executor) initRun() {
251255
Use: "run",
252256
Short: welcomeMessage,
253257
Run: e.executeRun,
258+
PreRun: func(_ *cobra.Command, _ []string) {
259+
if ok := e.acquireFileLock(); !ok {
260+
e.log.Fatalf("Parallel golangci-lint is running")
261+
}
262+
},
263+
PostRun: func(_ *cobra.Command, _ []string) {
264+
e.releaseFileLock()
265+
},
254266
}
255267
e.rootCmd.AddCommand(e.runCmd)
256268

pkg/config/config.go

+2
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ type Run struct {
150150
SkipFiles []string `mapstructure:"skip-files"`
151151
SkipDirs []string `mapstructure:"skip-dirs"`
152152
UseDefaultSkipDirs bool `mapstructure:"skip-dirs-use-default"`
153+
154+
AllowParallelRunners bool `mapstructure:"allow-parallel-runners"`
153155
}
154156

155157
type LintersSettings struct {

pkg/lint/lintersdb/enabled_set.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package lintersdb
22

33
import (
4+
"os"
45
"sort"
56
"strings"
67

@@ -29,6 +30,7 @@ func NewEnabledSet(m *Manager, v *Validator, log logutils.Log, cfg *config.Confi
2930
}
3031

3132
func (es EnabledSet) build(lcfg *config.Linters, enabledByDefaultLinters []*linter.Config) map[string]*linter.Config {
33+
es.debugf("Linters config: %#v", lcfg)
3234
resultLintersSet := map[string]*linter.Config{}
3335
switch {
3436
case len(lcfg.Presets) != 0:
@@ -82,7 +84,11 @@ func (es EnabledSet) GetEnabledLintersMap() (map[string]*linter.Config, error) {
8284
return nil, err
8385
}
8486

85-
return es.build(&es.cfg.Linters, es.m.GetAllEnabledByDefaultLinters()), nil
87+
enabledLinters := es.build(&es.cfg.Linters, es.m.GetAllEnabledByDefaultLinters())
88+
if os.Getenv("GL_TEST_RUN") == "1" {
89+
es.verbosePrintLintersStatus(enabledLinters)
90+
}
91+
return enabledLinters, nil
8692
}
8793

8894
// GetOptimizedLinters returns enabled linters after optimization (merging) of multiple linters

test/enabled_linters_test.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -170,18 +170,20 @@ func TestEnabledLinters(t *testing.T) {
170170
},
171171
}
172172

173+
runner := testshared.NewLintRunner(t)
173174
for _, c := range cases {
174175
c := c
175176
t.Run(c.name, func(t *testing.T) {
177+
t.Parallel()
178+
176179
runArgs := []string{"-v"}
177180
if !c.noImplicitFast {
178181
runArgs = append(runArgs, "--fast")
179182
}
180183
if c.args != "" {
181184
runArgs = append(runArgs, strings.Split(c.args, " ")...)
182185
}
183-
runArgs = append(runArgs, minimalPkg)
184-
r := testshared.NewLintRunner(t).RunWithYamlConfig(c.cfg, runArgs...)
186+
r := runner.RunCommandWithYamlConfig(c.cfg, "linters", runArgs...)
185187
sort.StringSlice(c.el).Sort()
186188

187189
expectedLine := fmt.Sprintf("Active %d linters: [%s]", len(c.el), strings.Join(c.el, " "))

test/linters_test.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func runGoErrchk(c *exec.Cmd, files []string, t *testing.T) {
1919
output, err := c.CombinedOutput()
2020
assert.Error(t, err)
2121
_, ok := err.(*exec.ExitError)
22-
assert.True(t, ok)
22+
assert.True(t, ok, err)
2323

2424
// TODO: uncomment after deprecating go1.11
2525
// assert.Equal(t, exitcodes.IssuesFound, exitErr.ExitCode())
@@ -48,9 +48,9 @@ func testSourcesFromDir(t *testing.T, dir string) {
4848

4949
for _, s := range sources {
5050
s := s
51-
t.Run(filepath.Base(s), func(t *testing.T) {
52-
t.Parallel()
53-
testOneSource(t, s)
51+
t.Run(filepath.Base(s), func(subTest *testing.T) {
52+
subTest.Parallel()
53+
testOneSource(subTest, s)
5454
})
5555
}
5656
}
@@ -101,6 +101,7 @@ func saveConfig(t *testing.T, cfg map[string]interface{}) (cfgPath string, finis
101101
func testOneSource(t *testing.T, sourcePath string) {
102102
args := []string{
103103
"run",
104+
"--allow-parallel-runners",
104105
"--disable-all",
105106
"--print-issued-lines=false",
106107
"--print-linter-name=false",

0 commit comments

Comments
 (0)