Run tests workflow (maruel/panicparse)
The Run tests workflow from maruel/panicparse, explained and optimized by Latchkey.
CI health: C - fair
Run this on Latchkey for self-healing, caching, and up to 58% lower cost.
Grade your own workflow free or run it on Latchkey →What it does
This is the Run tests workflow from the maruel/panicparse repository, a real project running GitHub Actions. It is shown here with attribution under its Apache-2.0 license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
# Copyright 2020 Marc-Antoine Ruel. All rights reserved.
# Use of this source code is governed under the Apache License, Version 2.0
# that can be found in the LICENSE file.
on: [push, pull_request]
name: Run tests
jobs:
# Runs go test both with code coverage sent to codecov, race detector and
# benchmarks. At the end do a quick check to ensure the tests to not leave
# files in the tree.
test:
name: "test: go${{matrix.gover}}.x/${{matrix.os}}"
runs-on: "${{matrix.os}}"
continue-on-error: true
defaults:
run:
shell: bash
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
# Do not forget to bump every 6 months!
gover: ["1.24"]
env:
PYTHONDONTWRITEBYTECODE: x
steps:
- name: Turn off git core.autocrlf
if: matrix.os == 'windows-latest'
run: git config --global core.autocrlf false
- uses: actions/checkout@v4
with:
fetch-depth: 2
- uses: actions/setup-go@v5
with:
go-version: "~${{matrix.gover}}.0"
- name: 'go install necessary tools'
if: always()
run: |
go install github.com/maruel/pat/cmd/ba@latest
- name: 'Check: go test -cover'
if: always()
run: go test -timeout=120s -covermode=count -coverprofile coverage.txt -bench=. -benchtime=1x ./...
# Don't send code coverage if anything failed to reduce spam.
- uses: codecov/codecov-action@v4
with:
token: ${{secrets.CODECOV_TOKEN}}
- name: 'Cleanup'
if: always()
run: rm coverage.txt
- name: 'Check: go test -race'
run: go test -timeout=120s -race -bench=. -benchtime=1x ./...
- name: 'Check: benchmark 📈'
run: ba -against HEAD~1
- name: 'Check: go test -short (CGO_ENABLED=0)'
env:
CGO_ENABLED: 0
run: go test -timeout=120s -short -bench=. -benchtime=1x ./...
- name: 'Check: go test -short (32 bits)'
if: matrix.os != 'macos-latest'
env:
GOARCH: 386
run: go test -timeout=120s -short -bench=. -benchtime=1x ./...
- name: "Check: tree is clean"
if: always()
run: |
# Nothing should have changed in the tree up to that point and no
# unsuspected file was created.
TOUCHED=$(git status --porcelain --ignored)
if ! test -z "$TOUCHED"; then
echo "Oops, something touched these files, please cleanup:"
echo "$TOUCHED"
git diff
false
fi
# Run linters. This workflow can be merged with the test_all one if desired
# to cut on runtime, at the cost of latency. I dislike waiting for results
# so I prefer to run them in parallel.
lint:
name: "lint: go${{matrix.gover}}.x/${{matrix.os}}"
runs-on: "${{matrix.os}}"
continue-on-error: true
defaults:
run:
shell: bash
strategy:
fail-fast: false
matrix:
# You may want to run only on linux to save on cost. Projects with
# OS-specific code benefits from explicitly linting on macOS and
# Windows.
os: [ubuntu-latest, macos-latest, windows-latest]
# Do not forget to bump every 6 months!
gover: ["1.23"]
env:
PYTHONDONTWRITEBYTECODE: x
steps:
- name: Turn off git core.autocrlf
if: matrix.os == 'windows-latest'
run: git config --global core.autocrlf false
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "~${{matrix.gover}}.0"
- name: 'go install necessary tools'
if: always()
run: |
go install github.com/gordonklaus/ineffassign@latest
go install github.com/securego/gosec/v2/cmd/gosec@v2.18.2
go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@v0.24.0
go install honnef.co/go/tools/cmd/staticcheck@v0.5.1
- name: 'go install necessary tools (ubuntu)'
if: always() && matrix.os == 'ubuntu-latest'
run: |
go install github.com/client9/misspell/cmd/misspell@latest
go install github.com/google/addlicense@latest
- name: 'Check: go vet'
if: always()
run: go vet -unsafeptr=false ./...
- name: 'Check: go vet shadow; shadowed variables'
if: always()
run: |
SHADOW_TOOL="$(which shadow)"
if [ -f "${SHADOW_TOOL}.exe" ]; then
SHADOW_TOOL="${SHADOW_TOOL}.exe"
fi
go vet -vettool=$SHADOW_TOOL ./...
- name: 'Check: inefficient variable assignment'
if: always()
run: ineffassign ./...
- name: 'Check: staticcheck'
if: always()
run: staticcheck ./...
- name: 'Check: gosec'
if: always()
run: gosec -fmt=golint -quiet ./...
# The following checks are not dependent on the OS or go build tags. Only
# run them on ubuntu-latest since it's the fastest one.
- name: 'Check: no executable was committed (ubuntu)'
if: always() && matrix.os == 'ubuntu-latest'
run: |
if find . -path '*.sh' -prune -o -path ./.git -prune -o -type f -executable -print | grep -e . ; then
echo 'Do not commit executables beside shell scripts'
false
fi
- name: 'Check: addlicense; all sources have a license header (ubuntu)'
if: always() && matrix.os == 'ubuntu-latest'
run: addlicense -check .
- name: 'Check: gofmt; code is well formatted (ubuntu)'
if: always() && matrix.os == 'ubuntu-latest'
run: |
FILES=$(gofmt -s -l .)
if ! test -z "$FILES"; then
echo 'Please run `gofmt -s -w` on the following files:' >> _gofmt.txt
echo "" >> _gofmt.txt
for FILE in ${FILES}; do
echo "- ${FILE}" >> _gofmt.txt
done
cat _gofmt.txt
echo "## ⚠ gofmt Failed" >> ../_comments.txt
echo "" >> ../_comments.txt
cat _gofmt.txt >> ../_comments.txt
echo "" >> ../_comments.txt
false
fi
- name: "Check: misspelling; code doesn't contain misspelling (ubuntu)"
if: always() && matrix.os == 'ubuntu-latest'
run: |
ERR=$(misspell .)
if ! test -z "$ERR"; then
echo "$ERR"
echo "## ⚠ misspell Failed" >> ../_comments.txt
echo "" >> ../_comments.txt
echo "$ERR" >> ../_comments.txt
echo "" >> ../_comments.txt
false
fi
- name: 'Send comments'
if: failure()
run: |
if [ -f ../_comments.txt ]; then
URL="${{github.event.issue.pull_request.url}}"
if test -z "$URL"; then
URL="${{github.api_url}}/repos/${{github.repository}}/commits/${{github.sha}}/comments"
fi
echo "Sending $(cat ../_comments.txt|wc -l) lines of comments to ${URL}"
curl -sS --request POST \
--header "Authorization: Bearer ${{secrets.GITHUB_TOKEN}}" \
--header "Content-Type: application/json" \
--data "$(cat ../_comments.txt | jq -R --slurp '{body: .}')" \
"${URL}" > /dev/null
rm ../_comments.txt
fi
- name: "Check: go generate doesn't modify files"
if: always()
run: |
go generate ./...
# Also test for untracked files. go generate should not generate ignored
# files either.
TOUCHED=$(git status --porcelain --ignored)
if ! test -z "$TOUCHED"; then
echo "go generate created these files, please fix:"
echo "$TOUCHED"
false
fi
- name: "Check: go mod tidy doesn't modify files"
if: always()
run: |
go mod tidy
TOUCHED=$(git status --porcelain --ignored)
if ! test -z "$TOUCHED"; then
echo "go mod tidy was not clean, please update:"
git diff
false
fi
# Ensure tests pass on oldest supported Go version.
# Do while inside GOPATH, which tests different parts
# of the code. This is panicparse specific and will be removed after 2022.
old:
name: "test: go${{matrix.gover}}/${{matrix.os}}"
runs-on: "${{matrix.os}}"
continue-on-error: true
defaults:
run:
shell: bash
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
# TODO: switch to an older Go version once tests are made compatible
gover: ['1.23']
env:
PYTHONDONTWRITEBYTECODE: x
steps:
- name: Turn off git core.autocrlf
if: matrix.os == 'windows-latest'
run: git config --global core.autocrlf false
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "=${{matrix.gover}}"
- name: 'Check: go test'
run: go test -timeout=120s -bench=. -benchtime=1x ./...
codeql:
name: "codeql: go${{matrix.gover}}.x/${{matrix.os}}"
runs-on: "${{matrix.os}}"
continue-on-error: true
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
# Do not forget to bump every 6 months!
gover: ["1.23"]
permissions:
security-events: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "~${{matrix.gover}}.0"
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: go
- name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
The same workflow, on Latchkey
Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.
# Copyright 2020 Marc-Antoine Ruel. All rights reserved. # Use of this source code is governed under the Apache License, Version 2.0 # that can be found in the LICENSE file. on: [push, pull_request] name: Run tests concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: # Runs go test both with code coverage sent to codecov, race detector and # benchmarks. At the end do a quick check to ensure the tests to not leave # files in the tree. test: timeout-minutes: 30 name: "test: go${{matrix.gover}}.x/${{matrix.os}}" runs-on: "${{matrix.os}}" continue-on-error: true defaults: run: shell: bash strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] # Do not forget to bump every 6 months! gover: ["1.24"] env: PYTHONDONTWRITEBYTECODE: x steps: - name: Turn off git core.autocrlf if: matrix.os == 'windows-latest' run: git config --global core.autocrlf false - uses: actions/checkout@v4 with: fetch-depth: 2 - uses: actions/setup-go@v5 with: go-version: "~${{matrix.gover}}.0" - name: 'go install necessary tools' if: always() run: | go install github.com/maruel/pat/cmd/ba@latest - name: 'Check: go test -cover' if: always() run: go test -timeout=120s -covermode=count -coverprofile coverage.txt -bench=. -benchtime=1x ./... # Don't send code coverage if anything failed to reduce spam. - uses: codecov/codecov-action@v4 with: token: ${{secrets.CODECOV_TOKEN}} - name: 'Cleanup' if: always() run: rm coverage.txt - name: 'Check: go test -race' run: go test -timeout=120s -race -bench=. -benchtime=1x ./... - name: 'Check: benchmark 📈' run: ba -against HEAD~1 - name: 'Check: go test -short (CGO_ENABLED=0)' env: CGO_ENABLED: 0 run: go test -timeout=120s -short -bench=. -benchtime=1x ./... - name: 'Check: go test -short (32 bits)' if: matrix.os != 'macos-latest' env: GOARCH: 386 run: go test -timeout=120s -short -bench=. -benchtime=1x ./... - name: "Check: tree is clean" if: always() run: | # Nothing should have changed in the tree up to that point and no # unsuspected file was created. TOUCHED=$(git status --porcelain --ignored) if ! test -z "$TOUCHED"; then echo "Oops, something touched these files, please cleanup:" echo "$TOUCHED" git diff false fi # Run linters. This workflow can be merged with the test_all one if desired # to cut on runtime, at the cost of latency. I dislike waiting for results # so I prefer to run them in parallel. lint: timeout-minutes: 30 name: "lint: go${{matrix.gover}}.x/${{matrix.os}}" runs-on: "${{matrix.os}}" continue-on-error: true defaults: run: shell: bash strategy: fail-fast: false matrix: # You may want to run only on linux to save on cost. Projects with # OS-specific code benefits from explicitly linting on macOS and # Windows. os: [ubuntu-latest, macos-latest, windows-latest] # Do not forget to bump every 6 months! gover: ["1.23"] env: PYTHONDONTWRITEBYTECODE: x steps: - name: Turn off git core.autocrlf if: matrix.os == 'windows-latest' run: git config --global core.autocrlf false - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version: "~${{matrix.gover}}.0" - name: 'go install necessary tools' if: always() run: | go install github.com/gordonklaus/ineffassign@latest go install github.com/securego/gosec/v2/cmd/gosec@v2.18.2 go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@v0.24.0 go install honnef.co/go/tools/cmd/staticcheck@v0.5.1 - name: 'go install necessary tools (ubuntu)' if: always() && matrix.os == 'ubuntu-latest' run: | go install github.com/client9/misspell/cmd/misspell@latest go install github.com/google/addlicense@latest - name: 'Check: go vet' if: always() run: go vet -unsafeptr=false ./... - name: 'Check: go vet shadow; shadowed variables' if: always() run: | SHADOW_TOOL="$(which shadow)" if [ -f "${SHADOW_TOOL}.exe" ]; then SHADOW_TOOL="${SHADOW_TOOL}.exe" fi go vet -vettool=$SHADOW_TOOL ./... - name: 'Check: inefficient variable assignment' if: always() run: ineffassign ./... - name: 'Check: staticcheck' if: always() run: staticcheck ./... - name: 'Check: gosec' if: always() run: gosec -fmt=golint -quiet ./... # The following checks are not dependent on the OS or go build tags. Only # run them on ubuntu-latest since it's the fastest one. - name: 'Check: no executable was committed (ubuntu)' if: always() && matrix.os == 'ubuntu-latest' run: | if find . -path '*.sh' -prune -o -path ./.git -prune -o -type f -executable -print | grep -e . ; then echo 'Do not commit executables beside shell scripts' false fi - name: 'Check: addlicense; all sources have a license header (ubuntu)' if: always() && matrix.os == 'ubuntu-latest' run: addlicense -check . - name: 'Check: gofmt; code is well formatted (ubuntu)' if: always() && matrix.os == 'ubuntu-latest' run: | FILES=$(gofmt -s -l .) if ! test -z "$FILES"; then echo 'Please run `gofmt -s -w` on the following files:' >> _gofmt.txt echo "" >> _gofmt.txt for FILE in ${FILES}; do echo "- ${FILE}" >> _gofmt.txt done cat _gofmt.txt echo "## ⚠ gofmt Failed" >> ../_comments.txt echo "" >> ../_comments.txt cat _gofmt.txt >> ../_comments.txt echo "" >> ../_comments.txt false fi - name: "Check: misspelling; code doesn't contain misspelling (ubuntu)" if: always() && matrix.os == 'ubuntu-latest' run: | ERR=$(misspell .) if ! test -z "$ERR"; then echo "$ERR" echo "## ⚠ misspell Failed" >> ../_comments.txt echo "" >> ../_comments.txt echo "$ERR" >> ../_comments.txt echo "" >> ../_comments.txt false fi - name: 'Send comments' if: failure() run: | if [ -f ../_comments.txt ]; then URL="${{github.event.issue.pull_request.url}}" if test -z "$URL"; then URL="${{github.api_url}}/repos/${{github.repository}}/commits/${{github.sha}}/comments" fi echo "Sending $(cat ../_comments.txt|wc -l) lines of comments to ${URL}" curl -sS --request POST \ --header "Authorization: Bearer ${{secrets.GITHUB_TOKEN}}" \ --header "Content-Type: application/json" \ --data "$(cat ../_comments.txt | jq -R --slurp '{body: .}')" \ "${URL}" > /dev/null rm ../_comments.txt fi - name: "Check: go generate doesn't modify files" if: always() run: | go generate ./... # Also test for untracked files. go generate should not generate ignored # files either. TOUCHED=$(git status --porcelain --ignored) if ! test -z "$TOUCHED"; then echo "go generate created these files, please fix:" echo "$TOUCHED" false fi - name: "Check: go mod tidy doesn't modify files" if: always() run: | go mod tidy TOUCHED=$(git status --porcelain --ignored) if ! test -z "$TOUCHED"; then echo "go mod tidy was not clean, please update:" git diff false fi # Ensure tests pass on oldest supported Go version. # Do while inside GOPATH, which tests different parts # of the code. This is panicparse specific and will be removed after 2022. old: timeout-minutes: 30 name: "test: go${{matrix.gover}}/${{matrix.os}}" runs-on: "${{matrix.os}}" continue-on-error: true defaults: run: shell: bash strategy: fail-fast: false matrix: os: [ubuntu-latest] # TODO: switch to an older Go version once tests are made compatible gover: ['1.23'] env: PYTHONDONTWRITEBYTECODE: x steps: - name: Turn off git core.autocrlf if: matrix.os == 'windows-latest' run: git config --global core.autocrlf false - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version: "=${{matrix.gover}}" - name: 'Check: go test' run: go test -timeout=120s -bench=. -benchtime=1x ./... codeql: timeout-minutes: 30 name: "codeql: go${{matrix.gover}}.x/${{matrix.os}}" runs-on: "${{matrix.os}}" continue-on-error: true strategy: fail-fast: false matrix: os: [ubuntu-latest] # Do not forget to bump every 6 months! gover: ["1.23"] permissions: security-events: write steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version: "~${{matrix.gover}}.0" - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: go - name: Autobuild uses: github/codeql-action/autobuild@v3 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3
What changed
- Cancel superseded runs when a branch or PR gets a newer push.
- Add a job timeout so a hung step cannot burn hours of runner time.
1 third-party action is referenced by a movable tag. Pin it to the commit SHA (Latchkey resolves and applies this automatically) so a repointed tag cannot change what runs.
What Latchkey heals here
This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:
- Network fetches
This workflow runs 4 jobs (8 with the matrix expanded) per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.