WIP: float_roundtrip on frame parsers; failing NaN quiescence test (improvise-6oy4, improvise-4w09)

This commit is contained in:
Edward Langley
2026-09-22 21:32:00 -07:00
parent a6e36511b5
commit c831e5ea4a
5 changed files with 107 additions and 3 deletions
@@ -1280,6 +1280,66 @@ mod tests {
);
}
/// `.improv` carries `nan` as a value (improvise-55v2); the NaN sits at
/// (1, 1), off the origin. Evidence file.
fn app_with_a_nan_cell() -> App {
let wb = crate::persistence::parse_md(
"v2025-04-09\n# NaN\nInitial View: Default\n\n\
## View: Default\nRegion: row\n_Measure: column\n_Index: none\n_Dim: none\n\n\
## Category: _Measure\n- Revenue, Cost\n\n\
## Category: Region\n- North, South\n\n\
## Data\n\
Region=North, _Measure=Revenue = 1\n\
Region=North, _Measure=Cost = 2\n\
Region=South, _Measure=Revenue = 3\n\
Region=South, _Measure=Cost = nan\n",
)
.expect("a document holding nan parses");
let mut app = App::new(wb, None);
app.rebuild_layout();
app
}
/// A NaN cell compares unequal to itself under `CellValue`'s `PartialEq`,
/// so the grid never quiesced (improvise-4w09). Evidence file.
#[test]
fn a_grid_holding_nan_quiesces() {
let app = app_with_a_nan_cell();
let grid = grid_of(
&app,
Viewport {
row_offset: 0,
col_offset: 0,
rows: 10,
cols: 10,
},
);
assert!(
matches!(grid.cell(1, 1).and_then(|c| c.value.clone()),
Some(CellValue::Number(n)) if n.is_nan()),
"precondition: the NaN must be projected, or nothing below is tested"
);
assert_eq!(
grid_updates(&grid, &grid),
vec![],
"a grid diffed against itself owes the client nothing"
);
assert!(
GridCache::folded(&grid.seed()) == grid,
"the seed must compact to the cache it came from (gotcha 32)"
);
let mut sessions = Sessions::default();
sessions.connect(&app, 10, 10);
sessions.project_all(&app, Dirty::EVERYTHING);
let again = sessions.project_all(&app, Dirty::EVERYTHING);
assert!(
again.is_empty(),
"an unchanged grid must not be re-sent: {again:?}"
);
}
/// Region on Row with `East` in a group that comes **last**, so the header is
/// the final entry and nothing follows it: `axis_frame`'s trailing rule.
fn app_with_a_trailing_group(collapsed: bool) -> App {
+3 -1
View File
@@ -12,7 +12,9 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
improvise-protocol = { path = "../improvise-protocol" }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# Parses frames, so it must read back the f64 that was written: per crate, because
# the wasm bundle builds alone (improvise-6oy4, context/code/protocol.md).
serde_json = { version = "1", features = ["float_roundtrip"] }
wasm-bindgen = "0.2"
# Deliberately NOT a dependency: `improvise-command`. A keymap binding names
@@ -701,6 +701,44 @@ fn a_yanked_number_crosses_the_serialized_wire_exactly() {
);
}
/// The two tests around this one share a feature-unified build, so neither can
/// see ONE frame-parsing crate lacking `float_roundtrip`. Evidence file.
#[test]
fn every_build_that_parses_a_frame_enables_float_roundtrip() {
for (krate, target) in [
("improvise-wasm-client", Some("wasm32-unknown-unknown")),
("improvise-worker-server", Some("wasm32-unknown-unknown")),
("improvise-ws-server", None),
] {
let mut cargo = std::process::Command::new(env!("CARGO"));
cargo.current_dir(env!("CARGO_MANIFEST_DIR")).args([
"tree",
"--locked",
"--edges",
"normal,features",
"--invert",
"serde_json",
"--package",
krate,
]);
if let Some(target) = target {
cargo.args(["--target", target]);
}
let out = cargo.output().expect("cargo tree must run");
assert!(
out.status.success(),
"{krate}: cargo tree failed: {}",
String::from_utf8_lossy(&out.stderr)
);
let tree = String::from_utf8(out.stdout).expect("cargo tree prints UTF-8");
assert!(
tree.contains(r#"serde_json feature "float_roundtrip""#),
"{krate} parses frames without float_roundtrip, so its numbers land \
one ULP off:\n{tree}"
);
}
}
/// A typed number is parsed exactly in the client and re-parsed from JSON by
/// the host. Evidence file.
#[test]
+3 -1
View File
@@ -17,7 +17,9 @@ improvise-protocol = { path = "../improvise-protocol" }
improvise-command = { path = "../improvise-command" }
anyhow = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# Parses frames, so it must read back the f64 that was written: per crate, because
# the wasm bundle builds alone (improvise-6oy4, context/code/protocol.md).
serde_json = { version = "1", features = ["float_roundtrip"] }
# The browser half. Only the wasm build pulls these in, so `cargo test` on the
# host builds and drives `worker.rs`/`vfs.rs` with no JS anywhere near them —
+3 -1
View File
@@ -17,7 +17,9 @@ improvise-io = { path = "../improvise-io" }
improvise-protocol = { path = "../improvise-protocol" }
improvise-command = { path = "../improvise-command" }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# Parses frames, so it must read back the f64 that was written (improvise-6oy4,
# context/code/protocol.md).
serde_json = { version = "1", features = ["float_roundtrip"] }
anyhow = "1"
clap = { version = "4.6.7", features = ["derive"] }
tokio = { version = "1", features = ["rt", "macros", "net", "sync"] }