Files
improvise/tests/json_numbers.rs
T
Edward Langley 95a35e8cce Every JSON parser reads numbers exactly, and the check derives its crates (improvise-6yvg, improvise-dz8c)
improvise-6yvg reproduced. A JSON import in improvise-command and a legacy
JSON .improv load in improvise-io stored 0.24815714285714285 as
0.24815714285714283 in each crate's own test build. The root binary's
`improvise import` used the same lossy parser. Now float_roundtrip is on every
normal serde_json dependency in the workspace: root, improvise-command,
improvise-io, and the optional test-support dependency of improvise-protocol.

improvise-dz8c: the hand-listed cargo-tree check over three frame-parsing
crates is replaced by tests/json_numbers.rs. It reads
`cargo metadata --no-deps` and names every package, including target-specific
entries, whose normal serde_json dependency lacks the feature. The rule and why
it beats "parses a frame", the build graph, and enabling the feature once in
the protocol crate are in context/code/protocol.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 22:19:05 -07:00

62 lines
2.4 KiB
Rust

//! A number read through serde_json must be the number written (improvise-6yvg,
//! improvise-dz8c). The rule and its derivation: `context/code/protocol.md`.
use serde_json::Value;
/// Every NORMAL serde_json dependency in the workspace, and those lacking
/// `float_roundtrip`, each spelled `package (target)`.
fn lossy_json_dependencies(metadata: &Value) -> (Vec<String>, Vec<String>) {
let mut checked = vec![];
let mut lossy = vec![];
let packages = metadata["packages"]
.as_array()
.expect("metadata lists packages");
for package in packages {
let name = package["name"].as_str().expect("a package has a name");
let deps = package["dependencies"]
.as_array()
.expect("a package lists deps");
for dep in deps {
if dep["name"] != "serde_json" || !dep["kind"].is_null() {
continue;
}
let target = dep["target"].as_str().unwrap_or("all targets");
let spelled = format!("{name} ({target})");
let features = dep["features"].as_array().expect("a dep lists features");
if !features.iter().any(|f| f == "float_roundtrip") {
lossy.push(spelled.clone());
}
checked.push(spelled);
}
}
(checked, lossy)
}
/// Derived from `cargo metadata`, not listed: a new crate that can parse JSON
/// is covered the day it declares the dependency. Evidence file.
#[test]
fn every_crate_that_can_parse_json_enables_float_roundtrip() {
let out = std::process::Command::new(env!("CARGO"))
.current_dir(env!("CARGO_MANIFEST_DIR"))
.args(["metadata", "--no-deps", "--locked", "--format-version", "1"])
.output()
.expect("cargo metadata must run");
assert!(
out.status.success(),
"cargo metadata failed: {}",
String::from_utf8_lossy(&out.stderr)
);
let metadata: Value = serde_json::from_slice(&out.stdout).expect("metadata is JSON");
let (checked, lossy) = lossy_json_dependencies(&metadata);
assert!(
checked.len() >= 2,
"the derivation found {checked:?}: too few to be reading the metadata right"
);
assert!(
lossy.is_empty(),
"these parse JSON numbers with serde_json's lossy default, one ULP off \
for roughly one decimal in seven; add features = [\"float_roundtrip\"] \
to each crate's own manifest: {lossy:?}"
);
}