1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
name: ci
on:
push:
branches:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
# The cross-binding gates below compare SURFACES, so this job needs the toolchains those
# surfaces are written in -- otherwise they skip, exit 0, and report success having compared
# four of seven. That is the same shape as check-exports.sh sitting in a job with no Julia:
# a gate that runs and covers less than it appears to. Measured before fixing: with a
# CI-like PATH, check-answers.sh compared 5 and silently skipped julia and zig.
- uses: julia-actions/setup-julia@v2
with:
version: "1.12"
- uses: mlugg/setup-zig@v2
with:
version: 0.16.0
- uses: actions/setup-node@v4
with:
node-version: "22"
- name: Playwright, for the wasm surface
working-directory: web-tests
run: npm install && npx playwright install --with-deps chromium
- name: Install icarus-verilog (RTL bit-exactness gate)
run: sudo apt-get update && sudo apt-get install -y iverilog
- name: One version across every package
run: scripts/check-versions.sh
- name: Every C ABI symbol reaches every binding, or the gap is written down
run: scripts/check-parity.sh
# Parity proves the names exist; this proves they compute the same thing. Bindings whose
# toolchain the runner lacks skip; a binding that is present and produces nothing FAILS.
- name: Every binding compiles one model to the same bytes
env:
FERROTHERM_REQUIRE_ALL: "1"
run: scripts/check-semantics.sh
# And SOLVES it to the same answer, which is a different question. check-semantics compares
# the model BEFORE it is solved, and its harness calls compile() explicitly -- so it exercised
# the exact step Zig's `solve` was skipping, and stayed green while that binding could not
# solve anything at all.
- name: Every binding solves one model to the same answer
env:
FERROTHERM_REQUIRE_ALL: "1"
run: scripts/check-answers.sh
# Parity proves a symbol is DECLARED in each binding. For Julia that is an @cfn line, which
# says nothing about whether a function wraps it -- both OMMX functions were once declared and
# exported with no body in between, and the module loaded fine.
- name: Unit tests
# --workspace, not the root package alone. This was `cargo test --release`, which builds
# and tests only `ferrotherm` -- so ferrotherm-serve, ferrotherm-cloud and
# ferrotherm-silicon were never compiled by CI at all, and their tests never ran.
run: cargo test --release --workspace
# The tool that found the 0.12.0 headline defect ran in no job and no script. It aborted on
# ffi.rs's deny-by-default lint, so five of the six crates and all 21 examples had never been
# linted at all. The three lints this codebase deliberately does not follow are recorded in
# Cargo.toml's [workspace.lints.clippy] with the reason for each; everything else is denied.
- name: Clippy, across the whole workspace
run: cargo clippy --release --workspace --all-targets -- -D warnings
# THE GPU PATH IS EXECUTED HERE, not merely compiled.
#
# This step used to run `cargo test -p ferrotherm-gpu` on a runner with no adapter, where
# every hardware-gated test skips. That caught an API break and verified no physics at all:
# the fastest sampler in the stack had ZERO CI coverage, and its correctness rested on
# whichever machine somebody remembered to run by hand. A second vendor then found a SIGSEGV
# that had been latent for releases.
#
# lavapipe is a software Vulkan implementation, so a hosted runner can execute the real
# shader. It says nothing about speed and everything about correctness, which is exactly what
# a gate should assert -- the same split the README already makes for the DX12/WARP run.
- name: Install lavapipe, a software Vulkan device
run: sudo apt-get update && sudo apt-get install -y mesa-vulkan-drivers
- name: GPU backend, actually run against a software Vulkan adapter
run: |
icd=$(ls /usr/share/vulkan/icd.d/lvp_icd*.json 2>/dev/null | head -1)
test -n "$icd" || { echo "::error::mesa-vulkan-drivers installed but no lvp ICD found"; exit 1; }
echo "using $icd"
out=$(VK_ICD_FILENAMES="$icd" cargo test --release -p ferrotherm-gpu 2>&1)
echo "$out"
# A SKIP IS NOW A FAILURE. A driver was installed on purpose, so "no GPU adapter" means
# it did not load -- and the shader went unverified while the job stayed green, which is
# the exact shape of the hole this step was added to close.
if grep -q "no GPU adapter" <<<"$out"; then
echo "::error::lavapipe is installed but wgpu found no adapter; the GPU path did NOT run"
exit 1
fi
# Name the test rather than counting them: a count goes stale the moment one is added,
# and this is the one that puts the whole conformance suite through the GPU.
grep -q "conform_can_finally_score_the_gpu_path ... ok" <<<"$out" || {
echo "::error::the conformance case did not run on the GPU path"
exit 1
}
# Same shape: no macmon on a Linux runner, so the measuring tests skip and the parsing and
# refusal logic -- which is most of the crate -- still runs.
- name: Power meter builds, and its tests skip rather than fail without a backend
run: cargo test --release -p ferrotherm-meter
# The OMMX bridge hand-rolls protobuf, so the check that matters is the REFERENCE
# implementation reading what it writes. Installed here on purpose; the test skips without it.
- name: OMMX bridge, checked against the reference implementation
run: |
python3 -m pip install --quiet ommx
OMMX_PYTHON=python3 cargo test --release --test ommx_reference
- name: Examples build
run: cargo build --release --examples
# RUN them, not merely build them.
#
# This step was two examples while `cargo build --examples` compiled all twenty, so eighteen
# were checked for compiling and never for working. `dtm_scale` had been panicking on a
# missing argument the whole time and nothing could see it, because a panic at runtime is
# invisible to a build.
#
# Every example that runs unattended is run, and a non-zero exit fails the job -- which is
# what the seven gate examples use to report a physics check failing. 115 s for seventeen of
# them, measured, which is affordable on every push.
#
# Three are excluded by name, with the reason, rather than by a silent filter:
# depth_vs_dimension 199 s too slow for every push
# reach_on_z1 107 s the flagship; slow, and run before a release
# dtm_scale needs a Fashion-MNIST idx3-ubyte file this runner does not have
# docs.rs is a published surface and nothing here gated it. Turned on with 22 broken
# intra-doc links already in the tree -- most of them prose like `[0,1]` or `reals[x]` that
# rustdoc read as a link and could not resolve, two of them real (`Verdict::Runnable` and
# `crate::graph::GRAPH_BUILDS` name items that do not exist). All fixed in the same commit
# that added this step, because a gate turned on over a failing tree is a gate that gets
# turned off again.
- name: The published documentation has no broken links
run: RUSTDOCFLAGS='-D warnings' cargo doc --workspace --no-deps
- name: Every unattended example runs, and a gate example failing fails the build
# FERROTHERM_ALLOW_BUSY, deliberately. `flips_bench` and `parity_bench` refuse to print a
# throughput rate above a load average of 2, because a rate taken under contention measures
# the run queue -- and a hosted runner that has just finished `cargo build --release
# --examples` is exactly that. What this step checks is that the examples RUN, not what they
# measure, so the guard is overridden here and the numbers from this job are not quotable.
# The override is not silence: both print the load average and a caveat above their tables.
env:
FERROTHERM_ALLOW_BUSY: "1"
run: |
set -euo pipefail
skip="depth_vs_dimension reach_on_z1 dtm_scale"
ran=0
for f in examples/*.rs; do
n=$(basename "$f" .rs)
case " $skip " in *" $n "*) echo " skipped $n"; continue;; esac
echo " running $n"
cargo run --release --quiet --example "$n" >/dev/null
ran=$((ran + 1))
done
# A floor: if the glob ever stops matching, this passes over nothing.
if [ "$ran" -lt 15 ]; then
echo "only $ran examples ran; this checked almost nothing" >&2
exit 2
fi
echo " $ran examples ran, all exit 0"
# Do the tests have teeth? A green suite says the code runs, not that a wrong answer would be
# noticed -- and the 0.12.0 release exists because 320 green tests could not tell an invented LP
# bound from a correct one. This breaks the code on purpose and requires the named test to go red.
mutation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Every recorded mutation is caught by the test named for it
run: scripts/mutation-suite.sh
c-abi:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Build the library the header describes
run: cargo build --release
- name: The header compiles and links against it
run: |
cc -I include include/check.c -L target/release -lferrotherm -o /tmp/check
LD_LIBRARY_PATH=target/release /tmp/check
python:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python:
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: actions/setup-python@v5
with:
# The range we CLAIM, not one point in it. requires-python said >=3.9 while CI tested a
# single version, so the floor was a claim nobody had ever run.
python-version: "${{ matrix.python }}"
- name: Build the library the bindings load
run: cargo build --release
- name: Python tests
run: |
pip install pytest
python -m pytest python/test_model.py python/test_readme.py -q
wasm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- name: wasm cdylib builds clean
run: cargo build --release --lib --target wasm32-unknown-unknown
# Not a byte comparison against a fresh build: a wasm binary is not reproducible across
# toolchain versions, so that fails on a runner whose rustc differs from the author's and
# says nothing about whether the artefact works. What matters is that every symbol the pages
# reach for is present -- a missing export makes the call `undefined`, and calling undefined
# in a click handler leaves the page looking fine and doing nothing.
- name: the committed wasm exports everything the pages call
run: scripts/check-wasm-exports.sh
zig:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: mlugg/setup-zig@v2
with:
version: 0.16.0
# `zig build test` runs cargo itself and links with an rpath, so there is one command here
# rather than a hand-written link line that can drift from what a consumer would use.
- name: Zig tests
working-directory: zig
run: zig build test --summary all
julia:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: julia-actions/setup-julia@v2
with:
version: "1.12"
- name: Build the library the binding loads
run: cargo build --release
# Moved here from the `test` job, which has no Julia -- so it printed "skipping", exited 0,
# and had never checked anything in CI while reading as a step that passed. REQUIRE_JULIA
# makes the skip a failure, so it cannot go quiet again.
- name: Every exported binding name resolves
env:
FERROTHERM_REQUIRE_JULIA: "1"
run: scripts/check-exports.sh
- name: Julia tests
working-directory: julia/Ferrotherm
env:
FERROTHERM_LIB: ${{ github.workspace }}/target/release/libferrotherm.so
run: julia --project=. -e 'using Pkg; Pkg.test()'
editor:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
- name: Install
working-directory: web-tests
run: npm install && npx playwright install --with-deps chromium
- name: Browser tests
working-directory: web-tests
run: npm test