Record the contracts-free comparison #1494
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" | |