Testing workflow (nodegit/nodegit)
The Testing workflow from nodegit/nodegit, explained and optimized by Latchkey.
CI health: D - needs work
Point runs-on at Latchkey and get caching, run de-duplication, job timeouts, self-healing for flaky steps, and up to 58% lower cost, applied automatically.
What it does
This is the Testing workflow from the nodegit/nodegit repository, a real project running GitHub Actions. It is shown here with attribution under its MIT license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
on:
push:
branches:
- master
- backport/*
tags:
- v*.*.*
pull_request:
name: Testing
jobs:
linux-tests:
name: "Linux Tests"
strategy:
matrix:
node: [20, 22, 24]
fail-fast: false
runs-on: ubuntu-22.04
steps:
- name: Install Dependencies for Ubuntu
run: sudo apt-get update && sudo apt-get install -y software-properties-common git build-essential clang libssl-dev libkrb5-dev libc++-dev wget zlib1g-dev
- uses: actions/checkout@v4
- name: Setup Environment
run: |
set -e
mkdir ~/.ssh_tests
chmod 700 ~/.ssh_tests
printf "%b" "Host *\n\tStrictHostKeyChecking no\n" > ~/.ssh_tests/config
cat test/id_rsa.pub > ~/.ssh_tests/id_rsa.pub
cat test/id_rsa.enc | base64 -d > ~/.ssh_tests/id_rsa
chmod 600 ~/.ssh_tests/id_rsa*
git config --global user.name "John Doe"
git config --global user.email johndoe@example.com
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
check-latest: true
- name: Install
run: npm install
- name: Test
run: |
set -e
eval `ssh-agent -s`
ssh-add ~/.ssh_tests/id_rsa
node utils/retry npm test
- name: Deploy
if: startsWith(github.ref, 'refs/tags/v')
env:
node_pre_gyp_bucket: ${{ secrets.node_pre_gyp_bucket }}
AWS_ACCESS_KEY_ID: ${{ secrets.node_pre_gyp_accessKeyId }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.node_pre_gyp_secretAccessKey }}
run: |
npm install -g @mapbox/node-pre-gyp aws-sdk
node lifecycleScripts/clean
node-pre-gyp package
node-pre-gyp publish
macos-tests:
name: "macOS Tests"
strategy:
matrix:
node: [20, 22, 24]
arch: [x64, arm64]
fail-fast: false
runs-on: ${{ matrix.arch == 'x64' && 'macos-15-intel' || 'macos-15' }}
steps:
- uses: actions/checkout@v4
- name: Setup Environment
run: |
mkdir ~/.ssh_tests
chmod 700 ~/.ssh_tests
printf "%b" "Host *\n\tStrictHostKeyChecking no\n" > ~/.ssh_tests/config
cat test/id_rsa.pub > ~/.ssh_tests/id_rsa.pub
cat test/id_rsa.enc | base64 -d > ~/.ssh_tests/id_rsa
chmod 600 ~/.ssh_tests/id_rsa*
git config --global user.name "John Doe"
git config --global user.email johndoe@example.com
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
check-latest: true
- name: Install
run: npm install
- name: Test
run: |
set -e
eval `ssh-agent -s`
ssh-add ~/.ssh_tests/id_rsa
node utils/retry npm test
- name: Deploy
if: startsWith(github.ref, 'refs/tags/v')
env:
node_pre_gyp_bucket: ${{ secrets.node_pre_gyp_bucket }}
AWS_ACCESS_KEY_ID: ${{ secrets.node_pre_gyp_accessKeyId }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.node_pre_gyp_secretAccessKey }}
run: |
npm install -g @mapbox/node-pre-gyp aws-sdk
node lifecycleScripts/clean
node-pre-gyp package
node-pre-gyp publish
windows-tests:
name: Windows Tests
strategy:
matrix:
node: [20, 22, 24]
arch: [x86, x64, arm64]
exclude:
- node: 24
arch: x86
fail-fast: false
runs-on: windows-2022
steps:
- name: Setup Environment
run: |
git config --file C:\ProgramData\Git\config core.autocrlf input
git config --system core.autocrlf input
git config --global core.autocrlf input
git config --global user.name "John Doe"
git config --global user.email johndoe@example.com
- uses: actions/checkout@v4
- name: Use Node.js
if: matrix.arch == 'x86'
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
check-latest: true
architecture: x86
- name: Use Node.js
uses: actions/setup-node@v4
if: matrix.arch != 'x86'
with:
node-version: ${{ matrix.node }}
check-latest: true
- name: Install
env:
npm_config_arch: ${{ matrix.arch == 'x86' && 'ia32' || matrix.arch }}
run: npm install
- name: Test
# need arm64 runners or an emulator to run tests
if: matrix.arch != 'arm64'
env:
GIT_SSH: ${{ github.workspace }}\vendor\plink.exe
run: |
$encodedKey = Get-Content -Path test\private.ppk.enc
$finalPath = Join-Path -Path $HOME -ChildPath .ssh_tests\private.ppk
mkdir ~\.ssh_tests
Set-Content -Value $([System.Convert]::FromBase64String($encodedKey)) -Path $finalPath -AsByteStream
powershell -command "Start-Process .\vendor\pageant\pageant_${{ matrix.arch }}.exe $finalPath"
node utils/retry npm test
# You're probably wondering why this isn't a single `run: |` step, it certainly is for *nix,
# but it's not, because the CI runner for windows doesn't wait for each step as listed here
# and it treats each additional step past the first as an orphaned process.
- name: Deploy (Dependencies)
if: startsWith(github.ref, 'refs/tags/v')
run: npm install -g @mapbox/node-pre-gyp aws-sdk
- name: Deploy (Clean)
if: startsWith(github.ref, 'refs/tags/v')
run: node lifecycleScripts\clean
- name: Deploy (Package)
if: startsWith(github.ref, 'refs/tags/v')
run: node-pre-gyp package --target_arch=${{ matrix.arch }}
- name: Deploy (Publish)
if: startsWith(github.ref, 'refs/tags/v')
env:
node_pre_gyp_bucket: ${{ secrets.node_pre_gyp_bucket }}
AWS_ACCESS_KEY_ID: ${{ secrets.node_pre_gyp_accessKeyId }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.node_pre_gyp_secretAccessKey }}
run: node-pre-gyp publish --target_arch=${{ matrix.arch }}
The same workflow, on Latchkey
Estimated ~20% faster on cache hits, plus fewer wasted runs and a safer supply chain. Added and changed lines are highlighted.
on: push: branches: - master - backport/* tags: - v*.*.* pull_request: name: Testing concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: linux-tests: timeout-minutes: 30 name: "Linux Tests" strategy: matrix: node: [20, 22, 24] fail-fast: false runs-on: latchkey-small steps: - name: Install Dependencies for Ubuntu run: sudo apt-get update && sudo apt-get install -y software-properties-common git build-essential clang libssl-dev libkrb5-dev libc++-dev wget zlib1g-dev - uses: actions/checkout@v4 - name: Setup Environment run: | set -e mkdir ~/.ssh_tests chmod 700 ~/.ssh_tests printf "%b" "Host *\n\tStrictHostKeyChecking no\n" > ~/.ssh_tests/config cat test/id_rsa.pub > ~/.ssh_tests/id_rsa.pub cat test/id_rsa.enc | base64 -d > ~/.ssh_tests/id_rsa chmod 600 ~/.ssh_tests/id_rsa* git config --global user.name "John Doe" git config --global user.email johndoe@example.com - uses: actions/setup-python@v5 with: python-version: "3.11" - name: Use Node.js uses: actions/setup-node@v4 with: cache: 'npm' node-version: ${{ matrix.node }} check-latest: true - name: Install run: npm install - name: Test run: | set -e eval `ssh-agent -s` ssh-add ~/.ssh_tests/id_rsa node utils/retry npm test - name: Deploy if: startsWith(github.ref, 'refs/tags/v') env: node_pre_gyp_bucket: ${{ secrets.node_pre_gyp_bucket }} AWS_ACCESS_KEY_ID: ${{ secrets.node_pre_gyp_accessKeyId }} AWS_SECRET_ACCESS_KEY: ${{ secrets.node_pre_gyp_secretAccessKey }} run: | npm install -g @mapbox/node-pre-gyp aws-sdk node lifecycleScripts/clean node-pre-gyp package node-pre-gyp publish macos-tests: timeout-minutes: 30 name: "macOS Tests" strategy: matrix: node: [20, 22, 24] arch: [x64, arm64] fail-fast: false runs-on: ${{ matrix.arch == 'x64' && 'macos-15-intel' || 'macos-15' }} steps: - uses: actions/checkout@v4 - name: Setup Environment run: | mkdir ~/.ssh_tests chmod 700 ~/.ssh_tests printf "%b" "Host *\n\tStrictHostKeyChecking no\n" > ~/.ssh_tests/config cat test/id_rsa.pub > ~/.ssh_tests/id_rsa.pub cat test/id_rsa.enc | base64 -d > ~/.ssh_tests/id_rsa chmod 600 ~/.ssh_tests/id_rsa* git config --global user.name "John Doe" git config --global user.email johndoe@example.com - name: Use Node.js uses: actions/setup-node@v4 with: cache: 'npm' node-version: ${{ matrix.node }} check-latest: true - name: Install run: npm install - name: Test run: | set -e eval `ssh-agent -s` ssh-add ~/.ssh_tests/id_rsa node utils/retry npm test - name: Deploy if: startsWith(github.ref, 'refs/tags/v') env: node_pre_gyp_bucket: ${{ secrets.node_pre_gyp_bucket }} AWS_ACCESS_KEY_ID: ${{ secrets.node_pre_gyp_accessKeyId }} AWS_SECRET_ACCESS_KEY: ${{ secrets.node_pre_gyp_secretAccessKey }} run: | npm install -g @mapbox/node-pre-gyp aws-sdk node lifecycleScripts/clean node-pre-gyp package node-pre-gyp publish windows-tests: timeout-minutes: 30 name: Windows Tests strategy: matrix: node: [20, 22, 24] arch: [x86, x64, arm64] exclude: - node: 24 arch: x86 fail-fast: false runs-on: windows-2022 steps: - name: Setup Environment run: | git config --file C:\ProgramData\Git\config core.autocrlf input git config --system core.autocrlf input git config --global core.autocrlf input git config --global user.name "John Doe" git config --global user.email johndoe@example.com - uses: actions/checkout@v4 - name: Use Node.js if: matrix.arch == 'x86' uses: actions/setup-node@v4 with: cache: 'npm' node-version: ${{ matrix.node }} check-latest: true architecture: x86 - name: Use Node.js uses: actions/setup-node@v4 if: matrix.arch != 'x86' with: cache: 'npm' node-version: ${{ matrix.node }} check-latest: true - name: Install env: npm_config_arch: ${{ matrix.arch == 'x86' && 'ia32' || matrix.arch }} run: npm install - name: Test # need arm64 runners or an emulator to run tests if: matrix.arch != 'arm64' env: GIT_SSH: ${{ github.workspace }}\vendor\plink.exe run: | $encodedKey = Get-Content -Path test\private.ppk.enc $finalPath = Join-Path -Path $HOME -ChildPath .ssh_tests\private.ppk mkdir ~\.ssh_tests Set-Content -Value $([System.Convert]::FromBase64String($encodedKey)) -Path $finalPath -AsByteStream powershell -command "Start-Process .\vendor\pageant\pageant_${{ matrix.arch }}.exe $finalPath" node utils/retry npm test # You're probably wondering why this isn't a single `run: |` step, it certainly is for *nix, # but it's not, because the CI runner for windows doesn't wait for each step as listed here # and it treats each additional step past the first as an orphaned process. - name: Deploy (Dependencies) if: startsWith(github.ref, 'refs/tags/v') run: npm install -g @mapbox/node-pre-gyp aws-sdk - name: Deploy (Clean) if: startsWith(github.ref, 'refs/tags/v') run: node lifecycleScripts\clean - name: Deploy (Package) if: startsWith(github.ref, 'refs/tags/v') run: node-pre-gyp package --target_arch=${{ matrix.arch }} - name: Deploy (Publish) if: startsWith(github.ref, 'refs/tags/v') env: node_pre_gyp_bucket: ${{ secrets.node_pre_gyp_bucket }} AWS_ACCESS_KEY_ID: ${{ secrets.node_pre_gyp_accessKeyId }} AWS_SECRET_ACCESS_KEY: ${{ secrets.node_pre_gyp_secretAccessKey }} run: node-pre-gyp publish --target_arch=${{ matrix.arch }}
What changed
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Cancel superseded runs when a branch or PR gets a newer push.
- Cache dependency installs on the setup step so they are served from cache.
- Add a job timeout so a hung step cannot burn hours of runner time.
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:
- Dependency installs
- Network fetches
This workflow runs 3 jobs (18 with the matrix expanded) per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.