From 95f23d2ed0847354c4eb562f3a7b7472e5b6db4c Mon Sep 17 00:00:00 2001 From: Callum Gardner <10970827+ctgardner@users.noreply.github.com> Date: Thu, 5 Dec 2024 12:07:37 +1100 Subject: [PATCH] ci: update linting config (#1) * ci: update linting config These changes have been copied from https://github.com/cultureamp/ecs-task-runner-buildkite-plugin/pull/3 * ci: disable gci linter This linter isn't required because Go includes built-in functionality for sorting import statements. --- .github/actions/go-coverage/action.yml | 154 +++++++++++++++++++++++++ .github/workflows/go-checks.yml | 26 ++--- .golangci.yml | 67 +++++------ src/plugin/config_test.go | 5 +- src/plugin/example_test.go | 2 +- 5 files changed, 200 insertions(+), 54 deletions(-) create mode 100644 .github/actions/go-coverage/action.yml diff --git a/.github/actions/go-coverage/action.yml b/.github/actions/go-coverage/action.yml new file mode 100644 index 0000000..2f41cee --- /dev/null +++ b/.github/actions/go-coverage/action.yml @@ -0,0 +1,154 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-action.json + +name: 'Go Coverage' +description: 'Creates a coverage message in a PR comment' + +inputs: + working-directory: + description: 'Location of go.mod where tests should run' + required: false + default: '.' + gotest-packages: + description: 'The Go module path for the packages and paths to test. Defaults to ./...' + required: false + default: './...' + gotest-arguments: + description: 'Additional arguments to pass to "go test", e.g. "-race".' + required: false + default: '' + coverage-threshold-min: + description: 'The minimum coverage percentage to regarded as acceptable.' + required: false + default: '50' + coverage-threshold-healthy: + description: 'The coverage percentage to regard as healthy for this codebase.' + required: false + default: '75' + +runs: + using: "composite" + steps: + + - name: Setup outputs + id: setup + working-directory: '${{ inputs.working-directory }}' + shell: bash + run: | + # find PR for branch + pr_number="${PR_NUMBER}" + if [[ -z "${pr_number:-}" ]]; then + echo "determining PR from branch" + pr_number="$(gh pr list --state open --head "${branch_name}" --repo "${REPOSITORY:-}" --json number | jq -r '.[0].number | select(. != null)')" + fi + echo "pr=${pr_number}" >> "$GITHUB_OUTPUT" + + # create a temp directory for the results + tmp="$(mktemp --directory 'go-tests.XXXXXXXX')" + mkdir -p "$tmp" + + echo "artifacts-dir=${tmp}" >> "$GITHUB_OUTPUT" + env: + GH_TOKEN: "${{ github.token }}" + PR_NUMBER: "${{ github.event.number }}" + + - name: Install tooling dependencies + working-directory: '${{ inputs.working-directory }}' + shell: bash + run: | + # install required tooling + go install github.com/boumenot/gocover-cobertura@v1.2.0 + go install github.com/gotesttools/gotestfmt/v2/cmd/gotestfmt@v2.5.0 + + - name: Run tests + working-directory: '${{ inputs.working-directory }}' + shell: bash + run: | + # run go test + go test "${GOTEST_PACKAGES}" -json -v "-coverprofile=${ARTIFACTS_DIR}/coverage.out" -covermode atomic ${GOTEST_ARGUMENTS} 2>&1 | tee "${ARTIFACTS_DIR}/gotest.log" | gotestfmt + env: + ARTIFACTS_DIR: "${{ steps.setup.outputs.artifacts-dir }}" + GOTEST_PACKAGES: "${{ inputs.gotest-packages }}" + GOTEST_ARGUMENTS: "${{ inputs.gotest-arguments }}" + + - name: Process package exclusions + working-directory: '${{ inputs.working-directory }}' + shell: bash + run: | + # exclude packages from coverage based on optional ".coverage-exclusions" configuration file + configuration_file=".coverage-exclusions" + coverage_file="${ARTIFACTS_DIR}/coverage.out" + + if [[ -e "${configuration_file}" ]]; then + echo "Filtering coverage exclusions" + + while IFS="" read -r exclusion + do + # ignore empty lines and comments + if [[ -z "${exclusion}" || "${exclusion}" = \#* ]]; then + continue + fi + + printf 'exclude %s\n' "${exclusion}" + + # each line in a coverage file looks like: github.com/cultureamp/ecrscanresults/registry/ecr.go:85.21,86.27 1 4 + # excluding by package means matching from the start of the line, then any file name, stopping at the colon + grep --invert-match '^'"${exclusion}"'/[^/]*:' "${coverage_file}" > "${coverage_file}.tmp" + mv "${coverage_file}.tmp" "${coverage_file}" + done < "${configuration_file}" + fi + env: + ARTIFACTS_DIR: "${{ steps.setup.outputs.artifacts-dir }}" + + - name: Convert go coverage to cobertura format + id: convert-format + shell: bash + working-directory: '${{ inputs.working-directory }}' + run: | + # transform go coverage to cobertura format + gocover-cobertura < "${ARTIFACTS_DIR}/coverage.out" > "${ARTIFACTS_DIR}/coverage.xml" + + echo "cobertura-coverage=${ARTIFACTS_DIR}/coverage.xml" >> "${GITHUB_OUTPUT}" + env: + ARTIFACTS_DIR: "${{ steps.setup.outputs.artifacts-dir }}" + + - name: Generate code coverage report + uses: irongut/CodeCoverageSummary@v1.3.0 + with: + filename: '${{ inputs.working-directory }}/${{ steps.convert-format.outputs.cobertura-coverage }}' + badge: false + fail_below_min: false + format: markdown + hide_branch_rate: false + hide_complexity: true + indicators: true + output: both + thresholds: '${{ inputs.coverage-threshold-min }} ${{ inputs.coverage-threshold-healthy }}' + + - name: Add Coverage PR Comment + if: steps.setup.outputs.pr != '' + uses: marocchino/sticky-pull-request-comment@v2 + with: + recreate: true + number: "${{ steps.setup.outputs.pr }}" + path: code-coverage-results.md + + - name: Write to Job Summary + shell: bash + run: | + # write coverage to job summary + cat code-coverage-results.md >> "${GITHUB_STEP_SUMMARY}" + + - name: Cleanup + if: 'always()' + working-directory: '${{ inputs.working-directory }}' + shell: bash + run: | + # Cleanup whatever temporary files were created by this step, as we + # don't need this step cluttering the working directory for other steps. + # The code coverage summary step runs in Docker, and doesn't have access + # to the runner temp directory. + rm -rfv "${ARTIFACTS_DIR}" || true + rm -fv code-coverage-results.md || true + + env: + ARTIFACTS_DIR: "${{ steps.setup.outputs.artifacts-dir }}" diff --git a/.github/workflows/go-checks.yml b/.github/workflows/go-checks.yml index f69a4ef..b816006 100644 --- a/.github/workflows/go-checks.yml +++ b/.github/workflows/go-checks.yml @@ -11,12 +11,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - - uses: actions/setup-go@v3 + - uses: actions/setup-go@v5 with: go-version-file: src/go.mod - cache: true cache-dependency-path: src/go.sum - name: Check Go Modules @@ -26,16 +25,17 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - - uses: actions/setup-go@v3 + - uses: actions/setup-go@v5 with: go-version-file: src/go.mod + cache: false # the lint action does its own caching - name: Lint code - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v6 with: - version: v1.51.2 + version: v1.62.2 working-directory: src args: "-v --timeout=2m" @@ -43,14 +43,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - - uses: actions/setup-go@v3 + - uses: actions/setup-go@v5 with: go-version-file: src/go.mod - cache: true cache-dependency-path: src/go.sum - - name: Test code - run: | - make test-ci + - id: Test + uses: ./.github/actions/go-coverage + with: + working-directory: src diff --git a/.golangci.yml b/.golangci.yml index 4935450..8536a3a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -8,44 +8,35 @@ issues: linters: enable-all: true disable: - - gochecknoglobals - - wrapcheck - - varnamelen - - tagliatelle - - testpackage - - paralleltest - - gomnd - - goerr113 - - dupl - - forbidigo - - funlen - - unparam - - wsl - - errname - - exhaustivestruct - - exhaustruct - - nilnil - - nlreturn - - goconst - - lll - - asciicheck - - gocognit - - godot - - godox - - gofumpt - - nestif - - prealloc - - revive - # deprecated linters - - interfacer - - golint - - scopelint - - maligned - - deadcode - - ifshort - - structcheck - - nosnakecase - - varcheck + - asciicheck + - depguard + - dupl + - err113 + - errname + - exhaustruct + - exportloopref + - forbidigo + - funlen + - gci + - gochecknoglobals + - gocognit + - goconst + - godot + - godox + - gofumpt + - lll + - nestif + - nilnil + - nlreturn + - paralleltest + - prealloc + - revive + - tagliatelle + - testpackage + - unparam + - varnamelen + - wrapcheck + - wsl linters-settings: gosec: diff --git a/src/plugin/config_test.go b/src/plugin/config_test.go index 12ca6d5..7ea1f27 100644 --- a/src/plugin/config_test.go +++ b/src/plugin/config_test.go @@ -6,6 +6,7 @@ import ( "github.com/cultureamp/examplego/plugin" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestFailOnMissingEnvironment(t *testing.T) { @@ -29,6 +30,6 @@ func TestFetchConfigFromEnvironment(t *testing.T) { err := fetcher.Fetch(&config) - assert.Nil(t, err, "fetch should not error") - assert.Equal(t, config.Message, "test-message", "fetched message should match environment") + require.NoError(t, err, "fetch should not error") + assert.Equal(t, "test-message", config.Message, "fetched message should match environment") } diff --git a/src/plugin/example_test.go b/src/plugin/example_test.go index 72c3873..69cf7cf 100644 --- a/src/plugin/example_test.go +++ b/src/plugin/example_test.go @@ -20,5 +20,5 @@ func TestDoesAnnotate(t *testing.T) { err := examplePlugin.Run(ctx, fetcher, agent) - assert.Nil(t, err, "should not error") + assert.NoError(t, err, "should not error") }