Skip to content

Tighten operator-facing diagnostics #50

Tighten operator-facing diagnostics

Tighten operator-facing diagnostics #50

Workflow file for this run

name: CI
on:
push:
branches: [ main, 'release/**' ]
# No branch filter. A PR into a release integration branch has to get CI too,
# or there is nothing for the merge gate to check.
pull_request:
jobs:
precommit:
name: Precommit (Elixir 1.19 / OTP 28)
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Set up Elixir
uses: erlef/setup-beam@v1
with:
elixir-version: '1.19'
otp-version: '28'
- name: Restore dependencies cache
uses: actions/cache@v5
with:
path: |
deps
_build
key: ${{ runner.os }}-${{ runner.arch }}-mix-precommit-${{ hashFiles('**/mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ runner.arch }}-mix-precommit-
- name: Install dependencies
run: mix deps.get
- name: Run precommit checks
run: mix precommit
test:
name: Test (Elixir ${{ matrix.elixir }} / OTP ${{ matrix.otp }})${{ matrix.cover && ' + coverage floor' || '' }}
# Only Linux for now. Castle is pure Elixir talking to :release_handler,
# with no shell scripts of its own and no suite that boots a release, so
# there is nothing platform-sensitive to run. Add macos-15 alongside the
# release fixture from castle#8.
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
elixir: ['1.18', '1.19', '1.20']
otp: ['27', '28', '29']
exclude:
# Elixir 1.18 supports Erlang/OTP 25-27
- elixir: '1.18'
otp: '28'
- elixir: '1.18'
otp: '29'
# Elixir 1.19 supports Erlang/OTP 26-28
- elixir: '1.19'
otp: '29'
include:
# **The one cell that checks the coverage floor, and the only place in
# CI that checks it at all.** `mix precommit` enforces the threshold in
# `mix.exs`, but the `precommit` job below is pinned to a single
# Elixir, so on its own it says nothing about the rest of the
# `~> 1.18` range that `mix.exs` declares - and cover's line
# attribution is not constant across that range. Elixir 1.20 counts
# one more relevant line in `Castle.Peer` than 1.19 does, which is how
# a floor set from the pinned toolchain came to fail a clean tree for
# contributors on a current Elixir.
#
# This is the newest Elixir in the matrix, so it reports the lowest
# figure and trips first: a canary for exactly that drift. Every other
# cell stays on a plain `mix test` deliberately - seven readings that
# disagree by version would be noise, not signal.
#
# Keep this on whichever cell is newest when the matrix moves on. The
# threshold note in `mix.exs` explains what to do if it goes red.
- elixir: '1.20'
otp: '29'
cover: true
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Set up Elixir
uses: erlef/setup-beam@v1
with:
elixir-version: ${{ matrix.elixir }}
otp-version: ${{ matrix.otp }}
- name: Restore dependencies cache
uses: actions/cache@v5
with:
path: |
deps
_build
key: ${{ runner.os }}-${{ runner.arch }}-mix-${{ matrix.elixir }}-${{ matrix.otp }}-${{ hashFiles('**/mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ runner.arch }}-mix-${{ matrix.elixir }}-${{ matrix.otp }}-
- name: Install dependencies
run: mix deps.get
- name: Compile
# Castle pokes at OTP release internals, so the deprecation surface across
# the supported Elixir and OTP range is the point of this matrix until
# there are tests to run on it.
run: mix compile --warnings-as-errors
- name: Run tests
if: ${{ !matrix.cover }}
run: mix test
# Split in two rather than one `mix test --cover`, so that the step which
# goes red names the thing that broke without anyone opening the log:
# exporting cannot fail on coverage, and checking cannot fail on a test.
- name: Run tests (exporting coverage)
if: ${{ matrix.cover }}
run: mix test --cover --export-coverage ci
- name: Check the coverage floor
# Red here means the total fell under the threshold in `mix.exs` while the
# tests passed. Either coverage genuinely dropped, or this Elixir
# attributes relevant lines differently from the pinned toolchain - read
# the threshold note in `mix.exs` before changing the number, because it
# is a floor across the whole supported range and not a reading from one.
if: ${{ matrix.cover }}
env:
MIX_ENV: test
run: mix test.coverage