diff --git a/.github/actions/golangci-lint/action.yml b/.github/actions/golangci-lint/action.yml index 20ad2689deb..142b19e7b98 100644 --- a/.github/actions/golangci-lint/action.yml +++ b/.github/actions/golangci-lint/action.yml @@ -42,10 +42,6 @@ runs: - name: Touching core/web/assets/index.html shell: bash run: mkdir -p core/web/assets && touch core/web/assets/index.html - - name: Build binary - working-directory: ${{ inputs.go-directory }} - shell: bash - run: go build ./... - name: Set golangci-lint working directory shell: bash id: set-working-directory @@ -56,12 +52,23 @@ runs: else echo "golangci-lint-working-directory=${{ inputs.go-directory }}" | tee -a $GITHUB_OUTPUT fi + - name: Set golangci-lint config + # some working directories are modules with own .golangci.yml + # so we need to set the corresponding config file to use + shell: bash + id: set-config + run: | + if [ "${{ inputs.go-directory }}" == "deployment" ]; then + echo "golangci-lint-config=./deployment/.golangci.yml" | tee -a $GITHUB_OUTPUT + else + echo "golangci-lint-config=./.golangci.yml" | tee -a $GITHUB_OUTPUT + fi - name: golangci-lint uses: golangci/golangci-lint-action@38e1018663fa5173f3968ea0777460d3de38f256 # v5.3.0 with: version: v1.61.0 only-new-issues: true - args: --out-format colored-line-number,checkstyle:golangci-lint-report.xml + args: --out-format colored-line-number,checkstyle:golangci-lint-report.xml --config ${{ steps.set-config.outputs.golangci-lint-config }} working-directory: ${{ steps.set-working-directory.outputs.golangci-lint-working-directory }} - name: Print lint report artifact if: failure() diff --git a/.github/workflows/ci-core.yml b/.github/workflows/ci-core.yml index c38ecd918ae..64623e69616 100644 --- a/.github/workflows/ci-core.yml +++ b/.github/workflows/ci-core.yml @@ -33,6 +33,7 @@ jobs: permissions: pull-requests: read outputs: + affected-packages: ${{ steps.affected-modules.outputs.changes }} deployment-changes: ${{ steps.match-some.outputs.deployment == 'true' }} should-run-ci-core: ${{ steps.match-some.outputs.core-ci == 'true' || steps.match-every.outputs.non-ignored == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' }} should-run-golangci: ${{ steps.match-some.outputs.golang-ci == 'true' || steps.match-every.outputs.non-ignored == 'true' || github.event_name == 'workflow_dispatch' }} @@ -47,7 +48,7 @@ jobs: with: # "if any changed file matches one or more of the conditions" (https://github.com/dorny/paths-filter/issues/225) predicate-quantifier: some - # deployment - any changes to files in `deployments/` + # deployment - any changes to files in the `deployments/` # core-ci - any changes that could affect this workflow definition # golang-ci - any changes that could affect the linting result filters: | @@ -75,6 +76,7 @@ jobs: non-ignored: - '**' - '!docs/**' + - '!fuzz/**' - '!integration-tests/**' - '!tools/secrets/**' - '!tools/goreleaser-config/**' @@ -91,24 +93,43 @@ jobs: - '!nix-darwin-shell-hook.sh' - '!LICENSE' - '!.github/**' - + - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + id: affected-modules + with: + # to get affected packages as output (not simply `true` or `false`) + # use the following syntax `package_name: 'path/to/package'` + filters: | + ccip: 'ccip/**' + common: 'common/**' + core: 'core/**' + dashboard-lib: 'dashboard-lib/**' + deployment: 'deployment/**' + plugins: 'plugins/**' + golangci: - # We don't directly merge dependabot PRs, so let's not waste the resources - if: ${{ (github.event_name == 'pull_request' || github.event_name == 'schedule') && github.actor != 'dependabot[bot]' }} name: lint + # We don't directly merge dependabot PRs, so let's not waste the resources. + # toJson/fromJson is used to account for empty array formatting differences ([], [ ], etc.) + # if: ${{ (github.event_name == 'pull_request' || github.event_name == 'schedule') && github.actor != 'dependabot[bot]' && needs.filter.outputs.should-run-golangci == 'true' && (toJson(fromJson(needs.filter.outputs.affected-packages)) != '[]' && needs.filter.outputs.affected-packages != '')}} + if: always() + needs: [filter, run-frequency] permissions: - # For golangci-lint-actions to annotate code in the PR. + # To annotate code in the PR. checks: write contents: read # For golangci-lint-action's `only-new-issues` option. pull-requests: read runs-on: ubuntu-24.04-8cores-32GB-ARM - needs: [filter, run-frequency] + strategy: + matrix: + modules: ${{ fromJson(needs.filter.outputs.affected-packages) }} steps: - - uses: actions/checkout@v4.2.1 - - name: Golang Lint + - name: Checkout + uses: actions/checkout@v4.2.1 + - name: Golang Lint (${{ matrix.modules }}) uses: ./.github/actions/golangci-lint - if: ${{ needs.filter.outputs.should-run-golangci == 'true' }} + with: + go-directory: ${{ matrix.modules }} - name: Notify Slack if: ${{ failure() && needs.run-frequency.outputs.one-per-day-frequency == 'true' }} uses: slackapi/slack-github-action@6c661ce58804a1a20f6dc5fbee7f0381b469e001 # v1.25.0 diff --git a/.golangci.yml b/.golangci.yml index ca8cf4dade5..5d89e0fff19 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,5 +1,7 @@ run: timeout: 15m0s + allow-parallel-runners: true + allow-serial-runners: true linters: enable: - containedctx diff --git a/ccip/fail_test.go b/ccip/fail_test.go new file mode 100644 index 00000000000..dfc325856c0 --- /dev/null +++ b/ccip/fail_test.go @@ -0,0 +1,42 @@ +package ccip + +import ( + "sync" + "testing" +) + +func TestFail(t *testing.T) { + if testing.Short() { + t.Skip() + } + t.Fatal("fake failure") +} + +func TestRace(t *testing.T) { + var v int + var wg sync.WaitGroup + wg.Add(100) + for i := 0; i < 100; i++ { + go func() { + defer wg.Done() + v++ + v-- + }() + } + wg.Wait() + t.Log(v) +} + +// func TestLint(t *testing.T) { +// const v1 = (true && false) && (true && false) // SQ Identical expressions should not be used on both sides of a binary operator +// a := 1 +// if !(a == 2) { // SQ boolean check should not be inverted +// } +// const UnusedVar = 1 // lint should complain for unused variable +// const ALL_CAPS = 10 // should be AllCaps +// err := os.ErrNotExist +// if err == os.ErrNotExist { // should use errors.Is +// err := errors.New("fake error") // shadowed variable +// t.Log(err) +// } +// } diff --git a/core/fail_test.go b/core/fail_test.go new file mode 100644 index 00000000000..7483c3da146 --- /dev/null +++ b/core/fail_test.go @@ -0,0 +1,42 @@ +package core + +import ( + "sync" + "testing" +) + +func TestFail(t *testing.T) { + if testing.Short() { + t.Skip() + } + t.Fatal("fake failure") +} + +func TestRace(t *testing.T) { + var v int + var wg sync.WaitGroup + wg.Add(100) + for i := 0; i < 100; i++ { + go func() { + defer wg.Done() + v++ + v-- + }() + } + wg.Wait() + t.Log(v) +} + +// func TestLint(t *testing.T) { +// const v1 = (true && false) && (true && false) // SQ Identical expressions should not be used on both sides of a binary operator +// a := 1 +// if !(a == 2) { // SQ boolean check should not be inverted +// } +// const UnusedVar = 1 // lint should complain for unused variable +// const ALL_CAPS = 10 // should be AllCaps +// err := os.ErrNotExist +// if err == os.ErrNotExist { // should use errors.Is +// err := errors.New("fake error") // shadowed variable +// t.Log(err) +// } +// } diff --git a/dashboard-lib/fail_test.go b/dashboard-lib/fail_test.go new file mode 100644 index 00000000000..c9c78e5091c --- /dev/null +++ b/dashboard-lib/fail_test.go @@ -0,0 +1,44 @@ +package deployment + +import ( + "errors" + "os" + "sync" + "testing" +) + +func TestFail(t *testing.T) { + if testing.Short() { + t.Skip() + } + t.Fatal("fake failure") +} + +func TestRace(t *testing.T) { + var v int + var wg sync.WaitGroup + wg.Add(100) + for i := 0; i < 100; i++ { + go func() { + defer wg.Done() + v++ + v-- + }() + } + wg.Wait() + t.Log(v) +} + +func TestLint(t *testing.T) { + const v1 = (true && false) && (true && false) // SQ Identical expressions should not be used on both sides of a binary operator + a := 1 + if !(a == 2) { // SQ boolean check should not be inverted + } + const UnusedVar = 1 // lint should complain for unused variable + const ALL_CAPS = 10 // should be AllCaps + err := os.ErrNotExist + if err == os.ErrNotExist { // should use errors.Is + err := errors.New("fake error") // shadowed variable + t.Log(err) + } +}