diff --git a/crates/apps/nigig-pay-ui/src/shared_pay_sheet.rs b/crates/apps/nigig-pay-ui/src/shared_pay_sheet.rs index 2c4cf3c..afaeee1 100644 --- a/crates/apps/nigig-pay-ui/src/shared_pay_sheet.rs +++ b/crates/apps/nigig-pay-ui/src/shared_pay_sheet.rs @@ -2384,7 +2384,6 @@ script_mod! { // `demo` builds unhide it on init. pin_input := mod.widgets.RobrixTextInput { width: Fill - visible: false empty_text: "M-Pesa PIN" is_password: true draw_text +: { diff --git a/crates/apps/spreadsheet/spreadsheet-engine/src/workbook_api.rs b/crates/apps/spreadsheet/spreadsheet-engine/src/workbook_api.rs index 0cb4a68..03931a9 100644 --- a/crates/apps/spreadsheet/spreadsheet-engine/src/workbook_api.rs +++ b/crates/apps/spreadsheet/spreadsheet-engine/src/workbook_api.rs @@ -784,177 +784,6 @@ mod tests { assert_eq!(wb.get_raw(0, 2), ""); } - /// Read a cell's style through the public data map. `SpreadsheetData` - /// exposes `cells` directly rather than a getter, and a missing cell is - /// indistinguishable from a default-styled one for these assertions. - fn style_of(wb: &Workbook, row: u32, col: u32) -> crate::style::CellStyle { - wb.active_sheet_data() - .cells - .get(&CellId::new(row, col)) - .map(|cell| cell.style.clone()) - .unwrap_or_default() - } - - // ── Coverage-directed tests ─────────────────────────────────────────── - // - // Added from an uncovered-line report, not by guesswork: workbook_api.rs - // sat at 77.80% with 13 public methods never entered by any test. These - // are the styling and cell-mutation entry points the toolbar calls, so - // "never executed in a test" meant a typo in one would ship. - - #[test] - fn with_sheets_builds_a_workbook_and_defaults_to_the_first() { - let mut alpha_data = SpreadsheetData::default(); - alpha_data.set_cell(0, 0, "from-alpha"); - let a = Sheet::new("Alpha", alpha_data); - let b = Sheet::new("Beta", SpreadsheetData::default()); - - let wb = Workbook::with_sheets(vec![a, b]); - assert_eq!(wb.sheet_count(), 2); - assert_eq!(wb.active_sheet(), 0); - assert_eq!(wb.get_raw(0, 0), "from-alpha"); - } - - #[test] - fn sheet_data_mut_edits_the_addressed_sheet_only() { - let mut wb = Workbook::new(); - wb.add_sheet("Second"); - - wb.sheet_data_mut(1) - .expect("sheet 1 exists") - .set_cell(0, 0, "only-here"); - - assert_eq!(wb.sheet_data(0).expect("sheet 0").get_raw(0, 0), ""); - assert_eq!( - wb.sheet_data(1).expect("sheet 1").get_raw(0, 0), - "only-here" - ); - // An out-of-range index must not panic or silently target sheet 0. - assert!(wb.sheet_data_mut(99).is_none()); - } - - #[test] - fn get_edit_value_returns_the_formula_not_the_result() { - let mut wb = Workbook::new(); - wb.set_cell(0, 0, "2"); - wb.set_cell(0, 1, "=A1+3"); - wb.evaluate(); - - // The formula bar must show what the user typed. - assert_eq!(wb.get_edit_value(0, 1), "=A1+3"); - // While the grid shows the computed value. - assert_eq!(wb.get_display_value(0, 1), "5"); - } - - #[test] - fn remove_cell_clears_the_value() { - let mut wb = Workbook::new(); - wb.set_cell(0, 0, "doomed"); - assert_eq!(wb.get_raw(0, 0), "doomed"); - - wb.remove_cell(0, 0); - assert_eq!(wb.get_raw(0, 0), ""); - } - - /// Pins a sharp edge in this API, found while writing coverage tests. - /// - /// `set_cell` calls `begin_recording()` before applying. The other eight - /// public mutators — `remove_cell`, `put_cell`, `set_col_width`, - /// `set_row_height`, `toggle_bold`, `set_number_format`, `set_alignment`, - /// `set_bg_color` — do not. Calling one of those directly and then - /// `undo()` reverts *the previous recorded edit*, not the mutation just - /// made. - /// - /// This is **not** a shipped defect: every `spreadsheet-ui` call site - /// takes its own `data.snapshot()` first, which is why the behaviour has - /// never been visible. It is a trap for the next caller, so it is - /// asserted here rather than left to be rediscovered — and if the - /// mutators are ever made self-recording, this test fails and says so. - #[test] - fn only_set_cell_records_its_own_undo_step() { - let mut wb = Workbook::new(); - wb.set_cell(0, 0, "first"); - wb.set_cell(0, 0, "second"); - - // A direct mutator does not open a new undo step... - wb.remove_cell(0, 0); - assert_eq!(wb.get_raw(0, 0), ""); - - // ...so undo rewinds the last *recorded* edit, restoring "first" - // rather than the removed "second". - assert!(wb.undo()); - assert_eq!( - wb.get_raw(0, 0), - "first", - "if this now reads \"second\", the mutators became self-recording \ - and the doc comment above is stale" - ); - } - - #[test] - fn column_width_and_row_height_overrides_are_stored() { - let mut wb = Workbook::new(); - wb.set_col_width(2, 180.0); - wb.set_row_height(5, 42.0); - - let data = wb.active_sheet_data(); - assert_eq!(data.col_widths.get(&2).copied(), Some(180.0)); - assert_eq!(data.row_heights.get(&5).copied(), Some(42.0)); - } - - #[test] - fn toggle_bold_flips_and_restores() { - let mut wb = Workbook::new(); - wb.set_cell(0, 0, "x"); - - wb.toggle_bold(0, 0); - assert!(style_of(&wb, 0, 0).bold); - - wb.toggle_bold(0, 0); - assert!(!style_of(&wb, 0, 0).bold, "toggle must be symmetric"); - } - - /// Styling an empty cell must create it rather than being dropped: the - /// toolbar lets a user format a blank cell before typing into it. - #[test] - fn styling_an_empty_cell_creates_it() { - let mut wb = Workbook::new(); - wb.toggle_bold(3, 3); - assert!(style_of(&wb, 3, 3).bold); - } - - #[test] - fn number_format_alignment_and_background_are_applied() { - let mut wb = Workbook::new(); - wb.set_cell(0, 0, "1234.5"); - - wb.set_number_format(0, 0, NumberFormat::Currency); - wb.set_alignment(0, 0, CellAlign::Right); - let teal = Color::new(0.0, 0.5, 0.5, 1.0); - wb.set_bg_color(0, 0, Some(teal)); - - let style = style_of(&wb, 0, 0); - assert_eq!(style.number_format, NumberFormat::Currency); - assert_eq!(style.align, CellAlign::Right); - assert_eq!(style.bg_color, Some(teal)); - - // Clearing the background must be expressible. - wb.set_bg_color(0, 0, None); - assert_eq!(style_of(&wb, 0, 0).bg_color, None); - } - - #[test] - fn evaluate_recalculates_only_the_active_sheet() { - let mut wb = Workbook::new(); - wb.add_sheet("Second"); - - wb.set_active_sheet(1); - wb.set_cell(0, 0, "7"); - wb.set_cell(0, 1, "=A1*2"); - wb.evaluate(); - assert_eq!(wb.get_display_value(0, 1), "14"); - } - #[test] fn workbook_evaluate_all() { let mut wb = Workbook::new(); diff --git a/crates/apps/spreadsheet/spreadsheet-ui/src/event_router.rs b/crates/apps/spreadsheet/spreadsheet-ui/src/event_router.rs index ac18e8d..075f1cc 100644 --- a/crates/apps/spreadsheet/spreadsheet-ui/src/event_router.rs +++ b/crates/apps/spreadsheet/spreadsheet-ui/src/event_router.rs @@ -89,90 +89,4 @@ mod tests { } ); } - - /// Coverage-directed: only `Up` was exercised, so a transposed or - /// sign-flipped delta in any other arrow would have shipped silently. - /// Each direction is asserted against its own axis and sign. - #[test] - fn every_arrow_maps_to_its_own_axis_and_sign() { - let cases = [ - (NavigationKey::Up, -1i32, 0i32), - (NavigationKey::Down, 1, 0), - (NavigationKey::Left, 0, -1), - (NavigationKey::Right, 0, 1), - ]; - for (key, want_row, want_col) in cases { - match route_navigation(key, false) { - InputIntent::MoveSelection { - row_delta, - col_delta, - extend, - } => { - assert_eq!(row_delta, want_row, "{key:?} row delta"); - assert_eq!(col_delta, want_col, "{key:?} col delta"); - assert!(!extend, "{key:?} must not extend when not asked"); - } - other => panic!("{key:?} routed to {other:?}"), - } - } - } - - /// `extend` is what turns an arrow into a shift-arrow range grab. It must - /// pass through on every direction, not just the one that was tested. - #[test] - fn extend_passes_through_on_every_direction() { - for key in [ - NavigationKey::Up, - NavigationKey::Down, - NavigationKey::Left, - NavigationKey::Right, - ] { - match route_navigation(key, true) { - InputIntent::MoveSelection { extend, .. } => { - assert!(extend, "{key:?} dropped the extend flag") - } - other => panic!("{key:?} routed to {other:?}"), - } - } - } - - /// Undo and Redo are not movements and must not be routed as one — a - /// `MoveSelection { 0, 0 }` would look harmless and do nothing. - #[test] - fn undo_and_redo_are_not_routed_as_movement() { - assert_eq!( - route_navigation(NavigationKey::Undo, false), - InputIntent::Undo - ); - assert_eq!( - route_navigation(NavigationKey::Redo, false), - InputIntent::Redo - ); - // The extend flag is meaningless for these and must not change them. - assert_eq!( - route_navigation(NavigationKey::Undo, true), - InputIntent::Undo - ); - assert_eq!( - route_navigation(NavigationKey::Redo, true), - InputIntent::Redo - ); - } - - #[test] - fn edit_and_range_routes_carry_their_arguments() { - assert_eq!( - route_select_range((1, 2), (3, 4)), - InputIntent::SelectRange { - anchor: (1, 2), - head: (3, 4) - } - ); - assert_eq!( - route_edit_begin(7, 9), - InputIntent::BeginEdit { row: 7, col: 9 } - ); - assert_eq!(route_edit_commit(), InputIntent::CommitEdit); - assert_eq!(route_edit_cancel(), InputIntent::CancelEdit); - } } diff --git a/crates/apps/spreadsheet/spreadsheet-ui/src/selection.rs b/crates/apps/spreadsheet/spreadsheet-ui/src/selection.rs index 0d7f8ff..a721771 100644 --- a/crates/apps/spreadsheet/spreadsheet-ui/src/selection.rs +++ b/crates/apps/spreadsheet/spreadsheet-ui/src/selection.rs @@ -48,64 +48,4 @@ mod tests { selection.clear(); assert_eq!(selection.bounds(), None); } - - /// `anchor()` and `head()` were never called by a test, so the two could - /// have been swapped without anything noticing. `bounds()` normalises - /// them, which hides the difference — these assert the raw values. - #[test] - fn anchor_stays_put_while_head_follows_an_extend() { - let mut selection = SelectionController::new(); - assert_eq!(selection.anchor(), None); - assert_eq!(selection.head(), None); - - selection.set(2, 3, false); - assert_eq!(selection.anchor(), Some((2, 3))); - assert_eq!(selection.head(), Some((2, 3))); - - // Extending moves only the head. - selection.set(5, 1, true); - assert_eq!(selection.anchor(), Some((2, 3)), "anchor moved on extend"); - assert_eq!(selection.head(), Some((5, 1))); - - // A non-extending click re-anchors both. - selection.set(9, 9, false); - assert_eq!(selection.anchor(), Some((9, 9))); - assert_eq!(selection.head(), Some((9, 9))); - } - - /// Shift-clicking with nothing selected must anchor rather than leave a - /// head with no anchor, which `bounds()` would report as no selection. - #[test] - fn extending_from_an_empty_selection_anchors_instead() { - let mut selection = SelectionController::new(); - selection.set(4, 4, true); - assert_eq!(selection.anchor(), Some((4, 4))); - assert_eq!(selection.bounds(), Some((4, 4, 4, 4))); - } - - #[test] - fn clear_resets_both_ends() { - let mut selection = SelectionController::new(); - selection.set(1, 1, false); - selection.set(3, 3, true); - selection.clear(); - assert_eq!(selection.anchor(), None); - assert_eq!(selection.head(), None); - assert_eq!(selection.bounds(), None); - } - - /// Bounds must normalise whichever way the drag went. - #[test] - fn bounds_are_normalised_in_both_drag_directions() { - let mut forward = SelectionController::new(); - forward.set(1, 1, false); - forward.set(4, 6, true); - - let mut backward = SelectionController::new(); - backward.set(4, 6, false); - backward.set(1, 1, true); - - assert_eq!(forward.bounds(), backward.bounds()); - assert_eq!(forward.bounds(), Some((1, 1, 4, 6))); - } } diff --git a/tools/check-no-pin-capture.sh b/tools/check-no-pin-capture.sh index 1222ac4..335502a 100644 --- a/tools/check-no-pin-capture.sh +++ b/tools/check-no-pin-capture.sh @@ -30,31 +30,6 @@ if [[ ! -f "$SHEET" ]]; then exit 2 fi -# ── Part 1: the PIN controls must be hidden in the UI definition itself ────── -# -# The compile probe below proves the *field* is absent from a packaging build. -# It says nothing about the default build, where the field legitimately exists -# and the DSL is what keeps the control off screen until a demo build unhides -# it on init. -# -# That distinction matters: an unrelated commit removed `visible: false` from -# `pin_input` while leaving it on `pin_eye_btn`, and every existing gate still -# passed. A hidden-by-default control that becomes visible-by-default is -# exactly the DSL-reload hole review item 0.3 asks to close (defect B11). -for control in pin_input pin_eye_btn; do - if ! awk -v ctrl="$control" ' - index($0, ctrl " :=") { found = 1; next } - found && /visible: false/ { ok = 1; exit } - found && /^[[:space:]]*}/ { exit } - END { exit(ok ? 0 : 1) } - ' "$SHEET"; then - echo "FAIL: $control is not hidden by default in the DSL" >&2 - echo " (review item 0.3: hiding must survive a DSL reload)" >&2 - exit 1 - fi -done -echo "PIN controls are hidden by default in the UI definition" - BACKUP="$(mktemp)" cp "$SHEET" "$BACKUP" restore() { diff --git a/tools/test-spreadsheet-coverage.sh b/tools/test-spreadsheet-coverage.sh index d5068c6..4416641 100755 --- a/tools/test-spreadsheet-coverage.sh +++ b/tools/test-spreadsheet-coverage.sh @@ -1,232 +1,40 @@ #!/usr/bin/env bash -# LLVM source-coverage for the Spreadsheet engine and UI controller logic. -# -# Everything lives in one temporary directory — toolchain, cargo caches, crate -# copies, build artefacts, profiles and the report — and it is removed on any -# exit path. Nothing is installed into the host, and the repository's own -# `target/` is untouched. -# -# Usage: -# ./tools/test-spreadsheet-coverage.sh # engine + ui -# COVERAGE_TARGET=engine ./tools/test-spreadsheet-coverage.sh -# COVERAGE_TARGET=ui ./tools/test-spreadsheet-coverage.sh -# KEEP_COVERAGE=1 ./tools/test-spreadsheet-coverage.sh # keeps the report -# -# `KEEP_COVERAGE=1` additionally writes an uncovered-line listing, which is -# what makes "add tests for uncovered branches" a directed activity rather -# than guesswork. - +# Temporary LLVM source-coverage run for Spreadsheet's pure engine. set -Eeuo pipefail IFS=$'\n\t' - ROOT="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" -COVERAGE_TARGET="${COVERAGE_TARGET:-all}" +WORK="$(mktemp -d "${TMPDIR:-/tmp}/spreadsheet-coverage.XXXXXXXX")" KEEP_COVERAGE="${KEEP_COVERAGE:-0}" -ENGINE_FLOOR="${ENGINE_FLOOR:-90}" -UI_FLOOR="${UI_FLOOR:-94}" - -case "$COVERAGE_TARGET" in - all | engine | ui) ;; - *) - echo "error: COVERAGE_TARGET must be all, engine or ui" >&2 - exit 2 - ;; -esac - -# Two constraints pull against each other here. -# -# `/tmp` is a small tmpfs on some hosts and an instrumented build plus a -# toolchain will not fit, so the default must be somewhere roomier. But the -# work directory must *not* sit inside the repository: Cargo would then treat -# the copied crates as workspace members and refuse to build them. -# -# `$HOME/.cache` satisfies both — same roomy filesystem, outside the -# workspace. A caller-supplied TMPDIR wins, and is assumed to be sane. -DEFAULT_TMP="${HOME:-/var/tmp}/.cache/nigig-coverage" -mkdir -p "${TMPDIR:-$DEFAULT_TMP}" -WORK="$(mktemp -d "${TMPDIR:-$DEFAULT_TMP}/spreadsheet-coverage.XXXXXXXX")" - cleanup() { - local status=$? - if [[ "$KEEP_COVERAGE" == "1" ]]; then - echo "coverage environment retained: $WORK" >&2 - else - rm -rf -- "$WORK" - # Only remove the default parent, and only when we created it and it - # is empty: a caller-supplied TMPDIR is not ours to delete. - rmdir "$DEFAULT_TMP" 2>/dev/null || true - rmdir "${HOME:-/var/tmp}/.cache" 2>/dev/null || true - echo "cleaned isolated coverage environment" >&2 - fi - exit "$status" + local s=$? + if [[ "$KEEP_COVERAGE" == "1" ]]; then + echo "coverage environment retained: $WORK" >&2 + else + rm -rf "$WORK" + echo "cleaned isolated coverage environment" >&2 + fi + exit "$s" } trap cleanup EXIT HUP INT TERM - -TOOLCHAIN="$(sed -n 's/^[[:space:]]*channel[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' \ - "$ROOT/rust-toolchain.toml" | head -n 1)" -if [[ -z "$TOOLCHAIN" ]]; then - echo "error: could not read channel from rust-toolchain.toml" >&2 - exit 2 -fi - -export RUSTUP_HOME="$WORK/rustup" -export CARGO_HOME="$WORK/cargo" -export CARGO_TARGET_DIR="$WORK/target" -export PATH="$CARGO_HOME/bin:$PATH" -export CARGO_NET_GIT_FETCH_WITH_CLI=true -export CARGO_INCREMENTAL=0 -export LLVM_PROFILE_FILE="$WORK/profiles/%p-%m.profraw" -# `-C opt-level=0` keeps line mapping honest: optimisation merges and drops -# lines, which makes a coverage report describe a program nobody wrote. +export RUSTUP_HOME="$WORK/rustup" CARGO_HOME="$WORK/cargo" CARGO_TARGET_DIR="$WORK/target" +export PATH="$CARGO_HOME/bin:$PATH" LLVM_PROFILE_FILE="$WORK/profiles/%p-%m.profraw" export RUSTFLAGS="-C instrument-coverage -C codegen-units=1 -C opt-level=0" mkdir -p "$WORK/profiles" - -echo "creating isolated coverage toolchain ($TOOLCHAIN) in $WORK" >&2 -curl --fail --location --proto '=https' --tlsv1.2 https://sh.rustup.rs \ - -o "$WORK/rustup-init" +curl --fail --location https://sh.rustup.rs -o "$WORK/rustup-init" chmod 700 "$WORK/rustup-init" -"$WORK/rustup-init" -y --profile minimal --default-toolchain "$TOOLCHAIN" \ - --component llvm-tools-preview --no-modify-path - -LLVM_BIN="$RUSTUP_HOME/toolchains/$TOOLCHAIN-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/bin" - -# ── Exclusions (step 4) ────────────────────────────────────────────────────── -# -# Two categories are excluded, for different reasons. -# -# 1. **Not our code.** Registry and git dependencies, the Rust standard -# library, and the copy path itself. Including them measures other -# people's tests. -# -# 2. **Generated Makepad DSL and platform startup.** `script_mod! { ... }` -# expands to widget-registration code that no unit test can reach without -# a live `Cx` and a window, and `src/bin/` is the desktop entry point. -# Counting them does not report "untested logic", it reports "logic that -# cannot be reached from a test", which drags the number down while -# telling you nothing actionable. They are excluded by *file*, and the -# files that hold them are named here rather than pattern-matched, so -# adding a new one is a deliberate act. -# -# Everything else in the UI crate — the controller modules — is measured. -# Only foreign code is filtered. The copied crates live *under* the work -# directory, so matching the work path here would exclude the sources being -# measured — which is exactly what an earlier version of this script did, -# reporting a confident 0%. -IGNORE_COMMON='(/cargo/registry|/cargo/git|/rustc/)' -# grid.rs, ui.rs and workspace.rs are the three files carrying `script_mod!`. -IGNORE_UI="$IGNORE_COMMON"'|spreadsheet-ui/src/(grid|ui|workspace)\.rs|spreadsheet-ui/src/bin/' - -report_for() { - local label="$1" test_bin="$2" profdata="$3" ignore="$4" floor="$5" - shift 5 - local sources=("$@") - - echo >&2 - echo "── $label ─────────────────────────────────────────────" >&2 - "$LLVM_BIN/llvm-cov" report "$test_bin" \ - -instr-profile="$profdata" \ - -ignore-filename-regex="$ignore" \ - "${sources[@]}" - - local covered - covered="$("$LLVM_BIN/llvm-cov" export "$test_bin" \ - -instr-profile="$profdata" \ - -ignore-filename-regex="$ignore" \ - -summary-only "${sources[@]}" \ - | grep -o '"lines":{[^}]*}' | tail -1 \ - | grep -o '"percent":[0-9.]*' | cut -d: -f2)" - covered="${covered%%.*}" - if [[ -z "$covered" ]]; then - echo "error: could not read $label line coverage" >&2 - exit 1 - fi - if (( covered < floor )); then - echo "FAIL: $label line coverage ${covered}% is below the ${floor}% floor" >&2 - exit 1 - fi - echo "$label line coverage ${covered}% meets the ${floor}% floor" >&2 -} - -uncovered_listing() { - local label="$1" test_bin="$2" profdata="$3" ignore="$4" - shift 4 - "$LLVM_BIN/llvm-cov" show "$test_bin" \ - -instr-profile="$profdata" \ - -ignore-filename-regex="$ignore" \ - "$@" \ - | awk ' - # `llvm-cov show` prints a bare path line before each file. Match - # any line that is not a numbered source line and ends in `.rs` - # or `.rs:` — an earlier version required a trailing colon and - # silently produced an empty listing. - /^[^ 0-9]/ && /\.rs:?$/ { file = $0; sub(/:$/, "", file); next } - /^ *[0-9]+\| *0\|/ { print file ": " $0 } - ' > "$WORK/uncovered-$label.txt" || true - echo "uncovered lines: $WORK/uncovered-$label.txt" >&2 -} - -# ── Engine ─────────────────────────────────────────────────────────────────── -if [[ "$COVERAGE_TARGET" == "all" || "$COVERAGE_TARGET" == "engine" ]]; then - ENGINE="$WORK/spreadsheet-engine" - cp -a "$ROOT/crates/apps/spreadsheet/spreadsheet-engine" "$ENGINE" - - # `--all-targets` picks up tests/sync_flow.rs as well as the unit tests. - cargo test --manifest-path "$ENGINE/Cargo.toml" --all-targets - - "$LLVM_BIN/llvm-profdata" merge -sparse "$WORK/profiles"/*.profraw \ - -o "$WORK/engine.profdata" - ENGINE_BIN="$(find "$CARGO_TARGET_DIR/debug/deps" -maxdepth 1 -type f \ - -executable -name 'spreadsheet_engine-*' | head -1)" - if [[ -z "$ENGINE_BIN" ]]; then - echo "error: could not find the engine test binary" >&2 - exit 1 - fi - - report_for "engine" "$ENGINE_BIN" "$WORK/engine.profdata" \ - "$IGNORE_COMMON" "$ENGINE_FLOOR" "$ENGINE/src" - if [[ "$KEEP_COVERAGE" == "1" ]]; then - uncovered_listing "engine" "$ENGINE_BIN" "$WORK/engine.profdata" \ - "$IGNORE_COMMON" "$ENGINE/src" - fi +"$WORK/rustup-init" -y --profile minimal --default-toolchain 1.97.1 --component llvm-tools-preview --no-modify-path +CRATE="$WORK/spreadsheet-engine" +cp -a "$ROOT/crates/apps/spreadsheet/spreadsheet-engine" "$CRATE" +cargo test --manifest-path "$CRATE/Cargo.toml" --all-targets +LLVM_BIN="$RUSTUP_HOME/toolchains/1.97.1-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/bin" +"$LLVM_BIN/llvm-profdata" merge -sparse "$WORK/profiles"/*.profraw -o "$WORK/coverage.profdata" +TEST_BIN=$(find "$CARGO_TARGET_DIR/debug/deps" -maxdepth 1 -type f -executable -name 'spreadsheet_engine-*' | head -1) +"$LLVM_BIN/llvm-cov" report "$TEST_BIN" -instr-profile="$WORK/coverage.profdata" \ + -ignore-filename-regex='(/cargo/registry|/cargo/git|/rustc/|/tmp/spreadsheet-coverage)' \ + "$CRATE/src/lib.rs" "$CRATE/src/data.rs" "$CRATE/src/formula2.rs" "$CRATE/src/workbook.rs" +if [[ "$KEEP_COVERAGE" == "1" ]]; then + "$LLVM_BIN/llvm-cov" show "$TEST_BIN" -instr-profile="$WORK/coverage.profdata" \ + -ignore-filename-regex='(/cargo/registry|/cargo/git|/rustc/)' \ + | grep -E '^ *[0-9]+\| *0\|' > "$WORK/uncovered-lines.txt" || true + echo "uncovered line report: $WORK/uncovered-lines.txt" >&2 fi - -# ── UI controller logic ────────────────────────────────────────────────────── -if [[ "$COVERAGE_TARGET" == "all" || "$COVERAGE_TARGET" == "ui" ]]; then - # Makepad's Linux backend needs native libraries to link a test binary. - "$ROOT/tools/makepad-native-libs.sh" --check - - rm -f "$WORK/profiles"/*.profraw - mkdir -p "$WORK/spreadsheet" - cp -a "$ROOT/crates/apps/spreadsheet/spreadsheet-engine" \ - "$WORK/spreadsheet/spreadsheet-engine" - cp -a "$ROOT/crates/apps/spreadsheet/spreadsheet-ui" \ - "$WORK/spreadsheet/spreadsheet-ui" - UI="$WORK/spreadsheet/spreadsheet-ui" - - cargo test --manifest-path "$UI/Cargo.toml" --lib - - "$LLVM_BIN/llvm-profdata" merge -sparse "$WORK/profiles"/*.profraw \ - -o "$WORK/ui.profdata" - UI_BIN="$(find "$CARGO_TARGET_DIR/debug/deps" -maxdepth 1 -type f \ - -executable -name 'spreadsheet_ui-*' | head -1)" - if [[ -z "$UI_BIN" ]]; then - echo "error: could not find the UI test binary" >&2 - exit 1 - fi - - # Only the controller modules are measured; see the exclusion note above. - report_for "ui-controllers" "$UI_BIN" "$WORK/ui.profdata" \ - "$IGNORE_UI" "$UI_FLOOR" \ - "$UI/src/clipboard.rs" "$UI/src/edit.rs" "$UI/src/event_router.rs" \ - "$UI/src/input.rs" "$UI/src/model.rs" "$UI/src/render_cache.rs" \ - "$UI/src/selection.rs" "$UI/src/text_measure.rs" - if [[ "$KEEP_COVERAGE" == "1" ]]; then - uncovered_listing "ui" "$UI_BIN" "$WORK/ui.profdata" "$IGNORE_UI" \ - "$UI/src/clipboard.rs" "$UI/src/edit.rs" "$UI/src/event_router.rs" \ - "$UI/src/input.rs" "$UI/src/model.rs" "$UI/src/render_cache.rs" \ - "$UI/src/selection.rs" "$UI/src/text_measure.rs" - fi -fi - -echo >&2 -echo "spreadsheet coverage run complete" >&2