Skip to content

The fact a caller needs (#967) #1469

The fact a caller needs (#967)

The fact a caller needs (#967) #1469

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: -D warnings
jobs:
check:
name: fmt, clippy, test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
- uses: taiki-e/install-action@nextest
# Formatting is not configurable (P4), so it is checked, not suggested.
- name: cargo fmt
run: cargo fmt --all -- --check
- name: cargo clippy
run: cargo clippy --workspace --all-targets -- -D warnings
- name: cargo nextest
run: cargo nextest run --workspace --profile ci
# Nextest cannot run doctests, so they are their own step rather than
# quietly not running. This is the second time that has mattered here:
# `cargo test --all-targets` skips them too, and a public signature
# change broke the `//!` examples twice before anybody noticed.
- name: cargo test --doc
run: cargo test --doc --workspace
mutants:
name: mutants on the diff, shard ${{ matrix.shard }}
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
# A mutant costs a build and a test run, so the wall clock is the count
# times about twenty seconds and a change to a compiler pass takes a
# quarter of an hour to hear back from. Nothing about that is parallel
# within one runner, so it is four of them. `fail-fast` is off because a
# surviving mutant in one shard is not a reason to stop hearing about the
# others: the list of what escaped is the whole output of this job.
strategy:
fail-fast: false
matrix:
shard: [0, 1, 2, 3]
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: taiki-e/install-action@nextest
- uses: taiki-e/install-action@cargo-mutants
# Only the lines this pull request touched. The point is not to hold the
# whole tree to a standard nothing here has met yet, it is that new code
# arrives with tests that would notice it being wrong.
#
# From the base commit rather than from a branch name, because the ref
# this job checks out is the merge commit and `origin/main` is not
# necessarily a name it has.
- name: the diff
run: git diff ${{ github.event.pull_request.base.sha }}...HEAD > pr.diff
# An empty diff makes the step below pass without testing anything, which
# is how a gate turns into decoration. The first version of this job did
# exactly that and reported success in 28 seconds.
- name: the diff is about something
run: |
rust=$(git diff --name-only ${{ github.event.pull_request.base.sha }}...HEAD -- '*.rs' | wc -l)
echo "$rust Rust files changed, $(wc -c < pr.diff) bytes of diff"
if [ "$rust" -gt 0 ] && [ ! -s pr.diff ]; then
echo "Rust changed and the diff is empty, so nothing would be mutated." >&2
exit 1
fi
# Round robin rather than consecutive ranges, because mutants arrive
# grouped by file and the ones that will not compile arrive in clumps.
# A slice can be a shard of those and finish in a minute while another
# runs the whole time.
- name: cargo mutants
run: >
cargo mutants --in-diff pr.diff --no-shuffle
--shard ${{ matrix.shard }}/4 --sharding round-robin
# How many it tested, which is worth reading and is not worth failing
# over. A pull request that only adds tests changes Rust files and has
# nothing to mutate, because `cargo mutants` does not mutate test code,
# and the first version of this step called that a failure. The hole it
# was written for, a diff the tool could not read looking exactly like a
# diff that survived it, is the step above: an unreadable diff is an
# empty one, and an empty one with Rust in the change is a failure there.
- name: how many were tested
run: |
if [ -f mutants.out/outcomes.json ]; then
python3 -c "import json;print(json.load(open('mutants.out/outcomes.json'))['total_mutants'], 'mutants tested')"
else
echo "nothing in this diff can be mutated, which is what a change to tests alone looks like"
fi
- uses: actions/upload-artifact@v7
if: always()
with:
name: mutants-${{ matrix.shard }}
path: mutants.out
wasm-size:
name: wasm artifact
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v2
# #592: a ceiling asserted here rather than hoped for. Tuned for size
# (see `[profile.wasm]`, root Cargo.toml) because this is the artifact a
# page waits on before it can do anything, not the one `deed` itself
# ships as.
- name: cargo build --profile wasm
run: cargo build --profile wasm --target wasm32-unknown-unknown -p deed-wasm --locked
# #776: building it and weighing it is not running it. The crate's own
# tests run on the host, where this target's failures cannot happen.
- name: the artifact answers
run: node crates/deed-wasm/smoke.mjs target/wasm32-unknown-unknown/wasm/deed_wasm.wasm
- name: the artifact is inside its budget
run: |
wasm=target/wasm32-unknown-unknown/wasm/deed_wasm.wasm
bytes=$(wc -c < "$wasm")
gzip=$(gzip -c "$wasm" | wc -c)
echo "uncompressed: $bytes bytes"
echo "gzip: $gzip bytes"
# Measured at 858097/280748 the day this ceiling was last moved
# (design/04-capabilities.md carries the same numbers). The margin
# is room to grow, not slack to stop counting: a ceiling that never
# gets close to being hit is a ceiling nobody has to explain moving.
ceiling_bytes=1500000
ceiling_gzip=550000
if [ "$bytes" -gt "$ceiling_bytes" ]; then
echo "the artifact is $bytes bytes, over the $ceiling_bytes byte ceiling" >&2
exit 1
fi
if [ "$gzip" -gt "$ceiling_gzip" ]; then
echo "the gzipped artifact is $gzip bytes, over the $ceiling_gzip byte ceiling" >&2
exit 1
fi
publishable:
name: it could be published
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
# A `.crate` archive is one package directory, and this workspace had
# already broken that twice in ways nothing else here could see:
# `deed-driver` embedded `std/*.deed` through three parent directories
# and would not have compiled, and `deed-explain` generated its pages
# from a build script that walked the workspace and would have compiled
# and printed nothing at all. Both were found by hand. This is the
# question asked on every commit instead, and it is asked of the real
# thing: every crate is packaged into an archive and then built out of
# that archive alone.
- name: cargo publish --workspace --dry-run
run: cargo publish --workspace --dry-run --locked
mcp-session:
name: mcp session
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: cargo build -p deed-lang
run: cargo build --release -p deed-lang --locked
# Deliberately unpinned. The crate's own tests already cover the protocol
# this repository believes in; the only thing this job adds is whether the
# clients that will actually call it still agree, and pinning the client
# would take that away.
- name: install the reference client
run: |
pip install mcp
pip show mcp | head -2
# #816: the server's tests speak to it directly, which is the same shape
# as building the wasm artifact and never running it.
- name: a real client holds a session
run: python3 crates/deed-mcp/smoke.py target/release/deed
foreign-engine:
name: a foreign engine runs it
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: cargo build -p deed-lang
run: cargo build --release -p deed-lang --locked
# The runner in `deed-codegen` is this repository's own, and it hands a
# host implementation the module's memory directly because it shares an
# address space with it. No embedder can. So a module only that runner
# accepts is a module every other engine rejects, which is exactly what
# #776 cost: a released artifact that trapped on every input, because CI
# built it and weighed it and never ran it.
#
# This is measured rather than argued: before the memory was exported,
# a real engine ran the pure functions and could not reach any operation
# carrying text.
- name: node's engine loads and runs what `deed build` wrote
run: node crates/deed-codegen/smoke.mjs target/release/deed "$RUNNER_TEMP/foreign"
# Deliberately unpinned, for the reason the MCP client is: the crate's
# own tests cover what this repository believes about the component
# model, and the only thing this adds is whether the toolchain that
# would actually consume it still agrees.
- name: install a component toolchain
run: npm install --global @bytecodealliance/jco
# `--component` writes a component binary for exports carrying numbers,
# booleans and text, so this runs both kinds through a real component
# runtime and checks the answers, and checks that a module carrying a
# list is refused by name instead. Reading a world is not running one.
- name: what a component toolchain makes of it
run: node crates/deed-codegen/component.mjs target/release/deed "$RUNNER_TEMP/component"