Compare commits

..

No commits in common. "5cb1bfe9f3ea004dbf5aa9cdda1aa9db8fef6dd1" and "8701f5df51b34ccb82ca10af2d522ccbe482a745" have entirely different histories.

6 changed files with 28 additions and 563 deletions

View file

@ -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 +: {

View file

@ -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();

View file

@ -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);
}
}

View file

@ -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)));
}
}

View file

@ -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() {

View file

@ -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=$?
local s=$?
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
rm -rf "$WORK"
echo "cleaned isolated coverage environment" >&2
fi
exit "$status"
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"
"$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
uncovered_listing "engine" "$ENGINE_BIN" "$WORK/engine.profdata" \
"$IGNORE_COMMON" "$ENGINE/src"
"$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
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