95a35e8cce
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
62 lines
2.4 KiB
Rust
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:?}"
|
|
);
|
|
}
|