nigig-org/.forgejo/workflows/nigig-build.yml
andodeki a8167ac36f ci(cad): CORE-00 CAD-owned workflow plus fail-closed stale scans
Adds .forgejo/workflows/cad.yml owning cad-core, cad-ui and shared
tooling: non-empty source-root gates, exact ignored-test budget (20),
empty-fixture proof, locked cargo check/test/clippy/fmt.

Guards every stale nigig-build/.../workspace/cad scan and both
coverage harnesses to fail closed (exit 1 with move pointer) until
BUILD-00 removes/retargets them; an empty grep scan is never green.

Verified: sun 14/14 + measure 19/19 standalone (DVec3 shim);
coverage harnesses exit 1; stale-guard loop fail=0; git diff --check clean.
2026-09-14 11:25:09 +03:00

626 lines
29 KiB
YAML

name: nigig-build (CAD)
# Phase 0.3 of the CAD remediation plan.
#
# Enforces:
# 1. Dependency resolution is pinned and reproducible (--locked).
# 2. Every git dependency is pinned to a rev, not a branch.
# 3. `nigig-build` compiles (lib + tests).
# 4. The test suite is green.
#
# History: the crate had 44 compile errors when this file was added, and
# then 17 failing tests once it built. Both are now fixed, so the test step
# asserts a plain pass instead of a "no worse than baseline" threshold.
on:
push:
paths:
- 'crates/apps/nigig-build/**'
- 'crates/apps/doc/**'
- 'crates/apps/spreadsheet/**'
- 'crates/nigig-core/**'
- 'crates/nigig-uikit/**'
- 'crates/matrix_client/**'
- 'tools/test-cad-coverage.sh'
- 'tools/test-doc-workspace-coverage.sh'
- 'Cargo.lock'
- 'Cargo.toml'
- 'rust-toolchain.toml'
- '.forgejo/workflows/nigig-build.yml'
pull_request:
paths:
- 'crates/apps/nigig-build/**'
- 'crates/apps/doc/**'
- 'crates/apps/spreadsheet/**'
- 'crates/nigig-core/**'
- 'crates/nigig-uikit/**'
- 'crates/matrix_client/**'
- 'tools/test-cad-coverage.sh'
- 'tools/test-doc-workspace-coverage.sh'
- 'Cargo.lock'
- 'Cargo.toml'
- 'rust-toolchain.toml'
- '.forgejo/workflows/nigig-build.yml'
jobs:
supply-chain:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
# A git dependency on a *branch* re-resolves on every build and is a
# direct code-execution path into CI if the branch is force-pushed.
# Phase 0.1 pinned all 33 manifests to an explicit rev; this keeps
# them pinned.
- name: Every git dependency must be pinned to a rev
run: |
set -euo pipefail
# Strip commented-out lines before checking; only live
# dependency declarations are in scope.
if grep -rn 'git = ' --include=Cargo.toml . \
| grep -v ':[0-9]*:[[:space:]]*#' \
| grep -v 'rev = '; then
echo
echo "ERROR: the git dependencies above are not pinned to a rev."
echo "Add rev = \"<full-40-char-sha>\" to each."
exit 1
fi
# ...and the rev must be the full 40-character SHA. An
# abbreviated rev resolves only while no other object shares
# its prefix; that is a property of the repository's current
# object count, not a guarantee. Git's own abbreviation length
# grows as a repo grows, so a short pin silently becomes
# ambiguous -- and an attacker who can push to the fork can
# try to manufacture a colliding prefix. This message has
# claimed "full-40-char-sha" since it was written without
# actually checking it.
if grep -rn 'rev = ' --include=Cargo.toml . \
| grep -v ':[0-9]*:[[:space:]]*#' \
| grep -vE 'rev = "[0-9a-f]{40}"'; then
echo
echo "ERROR: the rev(s) above are abbreviated. Use the full"
echo "40-character SHA so the pin cannot become ambiguous."
exit 1
fi
echo "OK: all git dependencies are pinned to a full SHA."
# A lockfile that changes during CI means the committed one was stale.
# An unused dependency is not cosmetic here. robius-sms was declared
# by nigig-build (and transitively by nigig-core and nigig-uikit)
# and never called once -- zero references in any of their sources.
# It dragged in polkit -> gio -> glib, which is the ONLY reason this
# crate needed RUSTSEC-2024-0370 and RUSTSEC-2024-0429 exemptions
# and carried an unanswered LGPL-2.1 distribution question. Deleting
# three manifest lines removed all of it.
#
# Scoped to the specific packages rather than a blanket
# `cargo machete`: five other unused dependencies exist across these
# crates (chrono, futures, postcard, rand, serde_json) and a gate
# that fails on day one gets switched off. Widen this list as those
# are cleared.
- name: The removed platform deps must not come back
run: |
set -euo pipefail
bad=0
for manifest in \
crates/apps/nigig-build/Cargo.toml \
crates/apps/nigig-email/Cargo.toml \
crates/nigig-core/Cargo.toml \
crates/nigig-uikit/Cargo.toml; do
crate_dir="$(dirname "$manifest")"
for dep in robius-sms robius-location; do
declared=$(grep -cE "^[[:space:]]*${dep}[[:space:]]*=" "$manifest" || true)
[ "$declared" -eq 0 ] && continue
underscored="${dep//-/_}"
used=$(grep -rl "$underscored" "$crate_dir/src" 2>/dev/null | wc -l)
if [ "$used" -eq 0 ]; then
echo "ERROR: $manifest declares $dep but $crate_dir/src never uses it."
bad=1
fi
done
done
if [ "$bad" -ne 0 ]; then
echo
echo "These pull polkit/gio/glib into the graph and reintroduce"
echo "RUSTSEC-2024-0370, RUSTSEC-2024-0429 and an LGPL-2.1"
echo "distribution question, for code that is never called."
exit 1
fi
echo "OK"
- name: Lockfile must be committed and current
run: |
set -euo pipefail
test -f Cargo.lock || { echo "missing Cargo.lock at workspace root"; exit 1; }
cargo metadata --locked --format-version 1 > /dev/null
git diff --exit-code -- Cargo.lock
# Phase 2: these two classes of defect are easy to reintroduce by
# copy-paste and invisible in review.
- name: No build-time paths used at runtime in the CAD module
run: |
set -euo pipefail
cad=crates/apps/nigig-build/src/construction_frame/pages/workspace/cad
# CORE-00: fail closed when the scan target is empty. This tree
# was removed (CAD now lives under crates/apps/cad); an
# unguarded grep over a missing dir reports "OK" while scanning
# nothing. Removal of these stale gates is tracked under BUILD-00.
test -d "$cad" || { echo "ERROR: CAD scan root $cad does not exist."; exit 1; }
# Rust sources only. ARCHITECTURE.md documents this rule and so
# necessarily names the macro; scanning Markdown made the gate
# fail on its own documentation.
#
# Then strip the "file:line:" prefix and drop any line whose code
# starts with a comment marker. Doc/inline comments mentioning the
# macro are fine; a real call is not.
if grep -rn --include='*.rs' 'env!("CARGO_MANIFEST_DIR")' "$cad" \
| sed 's/^[^:]*:[0-9]*://' \
| grep -vE '^[[:space:]]*(//|/\*|\*)'; then
echo
echo "ERROR: CARGO_MANIFEST_DIR is a BUILD-time path. Using it at"
echo "runtime bakes the build machine's source tree into the"
echo "binary. Use persistence::cad_data_dir() instead."
exit 1
fi
echo "OK"
- name: No hardcoded network endpoints in the CAD module
run: |
set -euo pipefail
cad=crates/apps/nigig-build/src/construction_frame/pages/workspace/cad
# CORE-00: fail closed when the scan target is empty. This tree
# was removed (CAD now lives under crates/apps/cad); an
# unguarded grep over a missing dir reports "OK" while scanning
# nothing. Removal of these stale gates is tracked under BUILD-00.
test -d "$cad" || { echo "ERROR: CAD scan root $cad does not exist."; exit 1; }
# RFC1918 literals outside comments. constants.rs is excluded
# wholesale: its only matches are the endpoint_tests that assert
# such addresses are REJECTED.
if grep -rnE --include='*.rs' \
'"https?://(10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.)' "$cad" \
--exclude=constants.rs \
| sed 's/^[^:]*:[0-9]*://' \
| grep -vE '^[[:space:]]*(//|/\*|\*)'; then
echo
echo "ERROR: a private-network endpoint is hardcoded above."
echo "Read it from the environment (see constants::local_openai_url)."
exit 1
fi
echo "OK"
# CadNode::pos/rot/size return Vec3f BY VALUE. `node.pos().x = v`
# compiles, mutates a temporary and throws it away. This silently
# broke all nine properties-panel inputs and the Extend tool; it
# produces no warning and no runtime error, only a control that
# does nothing. Mutation must go through set_pos/set_rot/set_size.
- name: No writes through the by-value Vec3f getters
run: |
set -euo pipefail
cad=crates/apps/nigig-build/src/construction_frame/pages/workspace/cad
# CORE-00: fail closed when the scan target is empty. This tree
# was removed (CAD now lives under crates/apps/cad); an
# unguarded grep over a missing dir reports "OK" while scanning
# nothing. Removal of these stale gates is tracked under BUILD-00.
test -d "$cad" || { echo "ERROR: CAD scan root $cad does not exist."; exit 1; }
# Exclude the two test modules that demonstrate the trap.
if grep -rnE --include='*.rs' \
'\.(pos|rot|size)\(\)\.[xyz][[:space:]]*[-+*/]?=[^=]' "$cad" \
| grep -v 'setters_write_through_but_getters_are_copies' \
| sed 's/^[^:]*:[0-9]*://' \
| grep -vE '^[[:space:]]*(//|/\*|\*)' \
| grep -v 'n\.pos()\.x = 42\.0' \
| grep -v 'n\.rot()\.y = 42\.0' \
| grep -v 'p\.size()\.y = v'; then
echo
echo "ERROR: the assignment(s) above write to a temporary copy"
echo "and are discarded. CadNode::pos/rot/size return Vec3f by"
echo "value. Read into a local, mutate it, then call set_pos /"
echo "set_rot / set_size."
exit 1
fi
echo "OK"
# eval_cad_script_in_vm is the single chokepoint where user- and
# AI-authored source reaches the script VM, and vm.eval is one
# blocking call. Without a run budget an unterminated loop wedges the
# rebuild worker permanently -- reproduced before the budget existed:
# still running after 90 seconds. Losing this line would reintroduce
# a hang that no test failure announces, only a frozen app.
- name: The CAD script evaluator must set a run budget
run: |
set -euo pipefail
f=crates/apps/nigig-build/src/construction_frame/pages/workspace/cad/script_bindings.rs
# CORE-00: fail closed when the scan target is empty (see above).
test -f "$f" || { echo "ERROR: CAD scan file $f does not exist."; exit 1; }
if ! grep -q 'vm.bx.run_budget = Some(' "$f"; then
echo "ERROR: eval_cad_script_in_vm no longer installs a"
echo "ScriptRunBudget. An unterminated CAD script would hang the"
echo "rebuild worker forever. See CAD_SCRIPT_TIME_BUDGET."
exit 1
fi
echo "OK"
# Phase 0.4. Blocked until Cargo.lock was committed in Phase 0.2,
# because cargo-deny resolves the graph from the lockfile.
#
# Gates security advisories and yanked crates. The licence, bans and
# source checks also run, but are configured to reflect what this
# graph actually is -- see deny-nigig-build.toml, where every
# exception is named and justified rather than blanket-disabled. A
# NEW unlicensed crate, or a new advisory, still fails.
- name: Dependency audit, licences, bans and sources
run: |
set -euo pipefail
version=0.18.6
url="https://github.com/EmbarkStudios/cargo-deny/releases/download"
curl -sSLf -o /tmp/cargo-deny.tar.gz \
"$url/$version/cargo-deny-$version-x86_64-unknown-linux-musl.tar.gz"
tar xzf /tmp/cargo-deny.tar.gz -C /tmp
install -m 0755 \
"/tmp/cargo-deny-$version-x86_64-unknown-linux-musl/cargo-deny" \
/usr/local/bin/cargo-deny
# --config is resolved relative to the manifest, not the working
# directory, so it must be absolute.
cargo-deny --manifest-path crates/apps/nigig-build/Cargo.toml \
--all-features check --config "$PWD/deny-nigig-build.toml"
# A bare identifier in a match pattern that is NOT a known variant
# is parsed as a new binding that matches everything. Four such
# names (KeyEnter, KeyBackspace, BracketLeft, BracketRight) plus
# Digit0-9/Equal/LeftBracket/RightBracket/Apostrophe silently turned
# keyboard handlers into catch-alls: the whole direct-distance-entry
# feature was unreachable, and typing any digit produced '0'.
#
# It compiles, and no test catches it. rustc reports it as
# `unreachable_pattern` plus an `unused_variables` warning on a
# capitalised name -- which is the signature grepped for here.
- name: No match arms binding a non-existent enum variant
run: |
set -euo pipefail
# A capitalised "unused variable" is almost always a mistyped
# variant used as a pattern.
out=$(cargo build --locked -p nigig-build --lib --message-format=short 2>&1 \
| grep -E 'unused variable: `[A-Z]' || true)
if [ -n "$out" ]; then
echo "$out"
echo
echo "ERROR: the pattern(s) above bind a new variable instead of"
echo "matching an enum variant -- check the spelling against the"
echo "enum definition. These silently swallow every input."
exit 1
fi
echo "OK"
# `save_cad_script(..).ok();` compiles, discards a real io::Error,
# and used to be followed by an unconditional "Saved" label -- the
# user was told their work was safe when the write had failed. A
# save path is the one place a dropped Result is data loss.
- name: No discarded Results on CAD save/write paths
run: |
set -euo pipefail
cad=crates/apps/nigig-build/src/construction_frame/pages/workspace/cad
# CORE-00: fail closed when the scan target is empty. This tree
# was removed (CAD now lives under crates/apps/cad); an
# unguarded grep over a missing dir reports "OK" while scanning
# nothing. Removal of these stale gates is tracked under BUILD-00.
test -d "$cad" || { echo "ERROR: CAD scan root $cad does not exist."; exit 1; }
if grep -rnE --include='*.rs' \
'(save|write|persist|store|export)[A-Za-z_]*\([^;]*\)\.ok\(\);' \
"$cad" \
| sed 's/^[^:]*:[0-9]*://' \
| grep -vE '^[[:space:]]*(//|/\*|\*)'; then
echo
echo "ERROR: the call(s) above throw away a Result from a write"
echo "path. Surface it -- see save_status_message()."
exit 1
fi
echo "OK"
# A CAD editor holds unsaved work. A panic on a UI path takes the
# whole model with it, which is strictly worse than the mistake
# being reported. `Command::merge`'s default panicked on a
# reachable path -- a command overriding can_merge but not merge --
# while the code five lines away already refused to panic on an
# unreachable one. Use debug_assert!, or return.
- name: No panicking macros in CAD production code
run: |
set -euo pipefail
cad=crates/apps/nigig-build/src/construction_frame/pages/workspace/cad
# CORE-00: fail closed when the scan target is empty. This tree
# was removed (CAD now lives under crates/apps/cad); an
# unguarded grep over a missing dir reports "OK" while scanning
# nothing. Removal of these stale gates is tracked under BUILD-00.
test -d "$cad" || { echo "ERROR: CAD scan root $cad does not exist."; exit 1; }
hits=0
for f in "$cad"/*.rs; do
# Stop at the first #[cfg(test)]: test code may panic freely.
out=$(awk '/^#\[cfg\(test\)\]/{exit}
/unreachable!\(|panic!\(|todo!\(|unimplemented!\(/{
printf "%d: %s\n", NR, $0 }' "$f" \
| grep -vE '^[0-9]+:[[:space:]]*(//|/\*|\*)' || true)
if [ -n "$out" ]; then
echo "$f"; echo "$out"; hits=1
fi
done
if [ "$hits" -ne 0 ]; then
echo
echo "ERROR: the panicking macro(s) above are in production code."
echo "Prefer debug_assert! (loud in dev, survivable in release)"
echo "or an early return."
exit 1
fi
echo "OK"
# unwrap()/expect() in CAD production code. A panic here kills the
# whole editor and loses unsaved work, so each surviving one must be
# a real constructor invariant whose message says which.
#
# Skips #[cfg(test)] modules by BRACE DEPTH, not by stopping at the
# first one. arch_gltf.rs has production code after two test
# modules, so a "stop at the first #[cfg(test)]" scan -- which is
# what the panicking-macro gate below does -- silently misses it.
# The allowlist is deliberate: a bare count drifts upward quietly
# and a blanket ban just gets #[allow]-ed.
- name: No new unwrap/expect in CAD production code
run: |
set -euo pipefail
python3 - <<'EOF'
import re, glob, sys
CAD = "crates/apps/nigig-build/src/construction_frame/pages/workspace/cad"
# CORE-00: fail closed when the scan target is empty (see above).
import os
if not os.path.isdir(CAD):
print(f"ERROR: CAD scan root {CAD} does not exist.")
sys.exit(1)
# Known-good, each a documented invariant:
# cad_scene.rs x4 -- SceneBuilder::new always inserts the
# "default" material and layer (see ~line 850).
# arch_gltf.rs x1 -- serde_json::to_vec over a Value built in
# this file; it has no non-serialisable branch.
ALLOWED = 5
def production(path):
depth, td, pending = 0, None, False
for i, line in enumerate(open(path), 1):
if re.match(r"#\[cfg\(test\)\]", line.strip()):
pending = True
o, c = line.count("{"), line.count("}")
if pending and o:
td, pending = depth, False
if td is None:
yield i, line.rstrip()
depth += o - c
if td is not None and depth <= td:
td = None
hits = []
for f in sorted(glob.glob(CAD + "/*.rs")):
for n, line in production(f):
s = line.strip()
if s.startswith(("//", "/*", "*")):
continue
if ".unwrap()" in line or ".expect(" in line:
hits.append(f"{f}:{n}: {s[:100]}")
if len(hits) > ALLOWED:
print(f"ERROR: {len(hits)} unwrap/expect in CAD production code, allowed {ALLOWED}.")
print("A panic on a UI path kills the editor and loses unsaved work.")
print("Use Option/Result or an early return. If it really is an")
print("invariant, raise ALLOWED here and name the invariant above.")
print()
for h in hits:
print(" " + h)
sys.exit(1)
if len(hits) < ALLOWED:
print(f"NOTE: {len(hits)} found, allowance {ALLOWED}. Lower it to lock the win in.")
print(f"OK ({len(hits)}/{ALLOWED})")
EOF
# BufWriter flushes on drop and DISCARDS any error it hits. Every
# CAD export buffered its output, so a write that failed only at
# flush time -- full disk, revoked permission, network mount gone --
# returned Ok(()) and the status label said the file was written.
# Route file exports through exporters::export_to_file, which
# flushes and reports.
- name: No unflushed BufWriter in CAD export paths
run: |
set -euo pipefail
cad=crates/apps/nigig-build/src/construction_frame/pages/workspace/cad
# CORE-00: fail closed when the scan target is empty. This tree
# was removed (CAD now lives under crates/apps/cad); an
# unguarded grep over a missing dir reports "OK" while scanning
# nothing. Removal of these stale gates is tracked under BUILD-00.
test -d "$cad" || { echo "ERROR: CAD scan root $cad does not exist."; exit 1; }
# exporters.rs owns the one legitimate BufWriter (inside
# export_to_file, which flushes). arch_pdf writes into a Vec,
# where flush cannot fail.
if grep -rnE --include='*.rs' 'BufWriter::new' "$cad" \
--exclude=exporters.rs --exclude=arch_pdf.rs \
| sed 's/^[^:]*:[0-9]*://' \
| grep -vE '^[[:space:]]*(//|/\*|\*)'; then
echo
echo "ERROR: the BufWriter(s) above are outside the flushing"
echo "helper. Use exporters::export_to_file so a failed flush"
echo "is reported instead of silently truncating the file."
exit 1
fi
echo "OK"
- name: Reject whitespace errors
run: git diff --check
cad-module:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
# Makepad needs a desktop/GL stack even for a check build.
- name: Install native dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq \
pkg-config libwayland-dev libxcursor-dev libxrandr-dev \
libxi-dev libx11-dev libgl1-mesa-dev libasound2-dev \
libpolkit-gobject-1-dev libpolkit-agent-1-dev \
libglib2.0-dev libssl-dev libsqlite3-dev libudev-dev
# Named for the whole crate, not "CAD module": `-p nigig-build`
# covers doc/, project_management/, cost_estimator/ and tests/ as
# well. When this gate was first executed it reported 1,559 diffs
# across 89 files, and the three largest were all outside cad/ --
# so the old name sent anyone reading the failure to the wrong
# directory.
- name: Formatting (nigig-build crate)
run: |
cargo fmt --version
cargo fmt -p nigig-build -- --check \
|| { echo "run: cargo fmt -p nigig-build"; exit 1; }
full-crate-check:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
# libpulse/libxkbcommon are link-time-only: `cargo check` passes
# without them, `cargo test` does not.
- name: Install native dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq \
pkg-config libwayland-dev libxcursor-dev libxrandr-dev \
libxi-dev libx11-dev libgl1-mesa-dev libasound2-dev \
libpolkit-gobject-1-dev libpolkit-agent-1-dev \
libglib2.0-dev libssl-dev libsqlite3-dev libudev-dev \
libpulse-dev libxkbcommon-dev
- name: Check
run: cargo check --locked -p nigig-build --lib
- name: Test (lib)
run: cargo test --locked -p nigig-build --lib
# Phase 4.7 moved the CAD integration suite out of src/ into its own
# test target. `--lib` does not build it, so without this step the
# 154 tests it contains would run in no pipeline at all.
#
# This names the target explicitly rather than running the whole
# crate's tests, because `--test cost_estimator` and
# `--test cost_estimator_ui` do not currently compile. That is
# pre-existing breakage, not this workflow's to hide -- but gating
# on it would make this job red for reasons unrelated to the CAD
# module, and a step that is always red gets ignored. Add those
# targets here the moment they build.
- name: Test (CAD integration suite)
run: cargo test --locked -p nigig-build --test cad_integration
# NOTE: `cargo clippy -- -D warnings` is not enabled yet -- the crate
# currently emits ~290 warnings. Enable it once that backlog is
# cleared; a step that cannot fail is worse than no step.
# ---------------------------------------------------------------------
# Source coverage for the CAD engine.
#
# `cargo test -p nigig-build` needs wayland/X11/GL/alsa/polkit, so the
# module had never been run under instrumentation and "well tested" was
# an assertion rather than a measurement. tools/test-cad-coverage.sh
# copies the twenty pure engine files into a host-only crate with the
# same module path, runs them plus tests/cad_integration.rs under
# -C instrument-coverage, and enforces a total floor plus a per-file
# floor. The per-file floors are the point: losing every test in one
# file moves the total by a point or two and a single number would let
# that through.
#
# No native dependencies and no `runs-on` GPU: the harness pulls only
# makepad-math and makepad-csg, both dependency-free Rust. It installs
# its own toolchain into a temp dir and deletes everything -- toolchain,
# cargo home, target dir, profraw data, fetched Makepad tree -- through
# a shell trap, so nothing is cached between runs and nothing is left
# in the workspace.
#
# This does NOT cover the widget layer (mod.rs, viewport*.rs,
# workspace*.rs, script_bindings.rs, cad_editor_sheet.rs,
# code_editor.rs). Those need live_design!, Cx and an event loop;
# full-crate-check above is what gates them. `tools.rs` is pure and is
# included by the harness; `profile_benchmarks.rs` is included only by
# its explicit CAD_BENCH=1 mode.
cad-engine-coverage:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- name: Engine coverage, with floors
run: ./tools/test-cad-coverage.sh
# ---------------------------------------------------------------------
# Source coverage for the document workspace's pure layer.
#
# Same shape as the CAD gate above: tools/test-doc-workspace-coverage.sh
# copies the doc module's dependency-free sources (model, layout,
# editing, collaboration, advanced JSON, CRDT bridge, projection layout/
# session, mobile gestures, persistence seams) plus tests_pure.rs into a
# host-only crate with a makepad-math shim, runs them under
# -C instrument-coverage, and enforces a total floor plus a per-file
# floor for every instrumented file. Everything -- toolchain, cargo
# home, target dir, fetched Makepad tree, profraw data -- lives in a
# mktemp dir removed by a shell trap on every exit path.
#
# This does NOT cover the widget layer (mod.rs, crdt_widget.rs,
# widgets/, render/, projection_renderer.rs): those need live_design!,
# Cx and an event loop, and are gated by the full-crate build and test
# jobs above. persistence.rs keeps a lower floor on purpose: three
# write-path entry points save into the host's real application-data
# directory and are covered only through their path-injected seams
# (save_doc_state_to / load_saved_doc_state_with); see the doc module's
# COVERAGE.md for the honest exclusion list.
doc-workspace-coverage:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- name: Doc workspace coverage, with floors
run: ./tools/test-doc-workspace-coverage.sh
# ---------------------------------------------------------------------
# The CAD widget layer: viewport*.rs, workspace*.rs, mod.rs and
# friends. The engine gate above is structurally blind to them, so
# "97% covered" has always been a statement about the smaller half.
#
# Measured when this job was added: 13.25% of 10,637 lines, with
# viewport_input.rs, viewport_render.rs, workspace_actions.rs,
# cad_editor_sheet.rs, viewport_2d.rs and code_editor.rs at exactly
# zero. 9,228 lines that no test has ever executed.
#
# REPORT-ONLY, deliberately, and this is the whole argument for the
# job existing: a floor at 13% reads as a blessing rather than a debt.
# What it buys is that the number is printed on every push instead of
# being rediscovered in six months. Per Phase 0 of
# REVIEWS/REPO_COVERAGE_100_PLAN.md the first real input test should
# set a floor behind it -- at measured-minus-one, in this job.
#
# Unlike the engine harness this builds the real crate, so it needs
# Makepad's Linux packages and a toolchain.
cad-widget-coverage:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- name: Install Makepad's native dependencies
run: bash tools/makepad-native-libs.sh --install
# NOT actions/setup-rust@v1 -- see .forgejo/RUNNER.md.
- name: Install the declared toolchain
run: |
set -e
version="$(sed -n 's/^[[:space:]]*channel[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' \
rust-toolchain.toml | head -n 1)"
curl --fail --location --proto '=https' --tlsv1.2 https://sh.rustup.rs -o /tmp/rustup-init
chmod 700 /tmp/rustup-init
/tmp/rustup-init -y --profile minimal --default-toolchain "$version" \
--component llvm-tools-preview --no-modify-path
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Widget layer coverage (report-only)
run: ./tools/test-cad-widget-coverage.sh