name: Build & Test
on:
workflow_dispatch:
workflow_call:
inputs:
plan:
required: false
type: string
push:
branches:
- master
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
nextest:
runs-on: ${{matrix.runner}}
timeout-minutes: 45
strategy:
fail-fast: false
matrix: &matrix
build: [linux, macos, windows]
include:
- build: linux
runner: ubuntu-latest
- build: macos
runner: macos-latest
- build: windows
runner: windows-latest
permissions:
contents: read
steps:
- &zellij-install
name: Install zellij (Linux/macOS)
if: runner.os != 'Windows'
uses: taiki-e/install-action@v2.85.4
with:
tool: zellij@0.44.3
- name: Install zellij (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
winget install --exact --id Zellij.Zellij --version 0.44.3 --source winget --accept-source-agreements --accept-package-agreements --disable-interactivity
# The winget package is an MSI that installs zellij and adds its dir to
# the machine PATH in the registry. Neither the current process nor
# GITHUB_PATH sees that until reloaded, so refresh PATH from the
# registry (with a Program Files fallback), then expose zellij's dir to
# later steps.
$env:PATH = [Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [Environment]::GetEnvironmentVariable('Path','User') + ';' + $env:PATH
$exe = (Get-Command zellij.exe -ErrorAction SilentlyContinue).Source
if (-not $exe) { $exe = (Get-ChildItem 'C:\Program Files' -Recurse -Filter zellij.exe -ErrorAction SilentlyContinue | Select-Object -First 1).FullName }
if (-not $exe) { throw "zellij.exe not found after winget install" }
$dir = Split-Path -Parent $exe
Write-Host "zellij installed at: $dir"
Add-Content -Path $env:GITHUB_PATH -Value $dir
& $exe --version
- name: Show locale
run: locale
if: runner.os != 'Windows'
- &checkout
name: Checkout repository
uses: actions/checkout@v7
with:
fetch-depth: 1
- &toolchain
name: Install rust toolchain
run: rustup toolchain install
- &nextest-install
name: Install nextest
uses: taiki-e/install-action@v2.85.4
with:
tool: nextest@0.9
- &cache
name: Setup cargo cache
uses: Swatinem/rust-cache@v2
- name: Run doctests
run: cargo test --doc
- name: "Run tests"
run: cargo nextest run --release --profile ci --bins --lib --examples --tests
env:
LC_ALL: en_US.UTF-8
TERM: xterm-256color
- name: Show snapshot diffs on failure
if: failure()
run: |
find tests/snapshots/ -name "*.snap.new" | while read -r new_snap; do
base="${new_snap%.new}"
echo "=== Snapshot diff: $base ==="
echo "new: $new_snap"
cat "$new_snap"
if [ -f "$base" ]; then
echo "old: $base"
cat "$base"
diff "$base" "$new_snap"
fi
done
shell: bash
coverage:
runs-on: ubuntu-latest
continue-on-error: true
permissions:
contents: write
steps:
- *zellij-install
- *checkout
- *toolchain
- *nextest-install
- uses: taiki-e/install-action@v2.85.4
with:
tool: cargo-llvm-cov@0.8
- *cache
- name: "Run tests with coverage"
run: |
cargo +nightly llvm-cov nextest --release --profile ci --branch --bins --lib --examples --tests --no-report
cargo +nightly llvm-cov report --release --cobertura --output-path coverage.xml
cargo +nightly llvm-cov report --release --html
echo "COVERAGE_PERCENT=$(cargo +nightly llvm-cov report --release | tail -n1 | awk '{ print $13 }')" | tee --append $GITHUB_ENV
env:
LC_ALL: en_US.UTF-8
TERM: xterm-256color
- name: "Generate coverage badge"
if: &if-master github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
uses: emibcn/badge-action@v2.0.4
with:
label: 'Coverage'
status: ${{ env.COVERAGE_PERCENT }}
color: 'blue'
path: 'target/llvm-cov/html/coverage.svg'
- name: "Deploy coverage to gh-pages under /coverage"
if: *if-master
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: target/llvm-cov/html
destination_dir: coverage
keep_files: true
clippy:
runs-on: ${{matrix.runner}}
strategy:
matrix: *matrix
steps:
- *checkout
- *toolchain
- *cache
- name: Clippy
run: cargo clippy
rustfmt:
runs-on: ${{matrix.runner}}
strategy:
matrix: *matrix
steps:
- *checkout
- *toolchain
- name: Check formatting
run: |
cargo fmt --all -- --check
clippy-no-default-features:
runs-on: ${{matrix.runner}}
strategy:
matrix: *matrix
steps:
- *checkout
- *toolchain
- *cache
- name: Run clippy with specific features
run: |
cargo clippy --no-default-features -- -Dwarnings
cargo clippy --no-default-features --features cli -- -Dwarnings
cargo clippy --no-default-features --features image -- -Dwarnings
cargo clippy --no-default-features --features listen -- -Dwarnings
cargo clippy --no-default-features --features frizbee -- -Dwarnings
msrv:
runs-on: ubuntu-latest
steps:
- *checkout
- *toolchain
- uses: taiki-e/install-action@v2.85.4
with:
tool: cargo-msrv@0.19
- name: MSRV Verify
run: cargo msrv verify
fuzz:
permissions:
contents: read
runs-on: ${{matrix.runner}}
strategy:
matrix: *matrix
steps:
- *checkout
- *toolchain
- *cache
- name: Set up MSVC dev environment
if: runner.os == 'Windows'
uses: ilammy/msvc-dev-cmd@v1
- uses: taiki-e/install-action@v2.85.4
with:
tool: cargo-fuzz@0.13
- name: Run fuzz targets
shell: bash
run: |
host_target="$(rustc +nightly -vV | sed -n 's/^host: //p')"
if [ "$RUNNER_OS" = "Windows" ]; then
# Git Bash's own coreutils `link` (for hardlinks) sits ahead of
# the MSVC one on PATH within this shell, so cargo/rustc would
# otherwise invoke the wrong `link.exe`. Point at the real MSVC
# linker explicitly, using the dev env ilammy/msvc-dev-cmd set up.
export CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER="${VCToolsInstallDir}bin\\HostX64\\x64\\link.exe"
fi
for target in ansi_strip field_extract fuzzy_match query_match keymap_parse; do
cargo +nightly fuzz run "$target" --target "$host_target" -- -max_total_time=60
done
- name: Upload crash artifacts
if: failure()
uses: actions/upload-artifact@v7
with:
name: fuzz-crashes-${{ runner.os }}
path: fuzz/artifacts/
if-no-files-found: ignore
public-api:
name: Check for public API breaking changes
permissions:
contents: read
continue-on-error: true
runs-on: ubuntu-latest
steps:
- *checkout
- *toolchain
- *cache
- uses: taiki-e/install-action@v2.85.4
with:
tool: cargo-public-api@0.52
- run: rustup toolchain install nightly-x86_64-unknown-linux-gnu
name: Install nightly toolchain
- run: cargo public-api diff latest --deny removed --deny changed
name: Run cargo-public-api
ci-success:
name: CI Success
if: always()
needs:
- nextest
- coverage
- clippy
- rustfmt
- clippy-no-default-features
- msrv
- fuzz
runs-on: ubuntu-latest
steps:
- name: Check all required jobs succeeded
run: |
echo "${{ toJson(needs) }}"
if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" || "${{ contains(needs.*.result, 'cancelled') }}" == "true" ]]; then
echo "One or more required jobs failed or were cancelled."
exit 1
fi
echo "All required jobs passed."