use std::path::{Path, PathBuf};
fn crate_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}
fn sources() -> Vec<(PathBuf, String)> {
fn walk(dir: &Path, out: &mut Vec<PathBuf>) {
let mut entries: Vec<_> = std::fs::read_dir(dir)
.unwrap_or_else(|e| panic!("read {}: {e}", dir.display()))
.map(|e| e.expect("dir entry").path())
.collect();
entries.sort();
for path in entries {
if path.is_dir() {
walk(&path, out);
} else if path.extension().is_some_and(|e| e == "rs") {
out.push(path);
}
}
}
let mut paths = Vec::new();
walk(&crate_root().join("src"), &mut paths);
assert!(paths.len() > 20, "the source walk found almost nothing");
paths
.into_iter()
.map(|p| {
let text = std::fs::read_to_string(&p).expect("read source");
(p, text)
})
.collect()
}
#[test]
fn the_crate_reaches_for_no_feature_that_replaces_a_wire_format() {
let manifest = std::fs::read_to_string(crate_root().join("Cargo.toml")).expect("Cargo.toml");
let code: String = manifest
.lines()
.map(|line| line.split_once(" #").map_or(line, |(before, _)| before))
.filter(|line| !line.trim_start().starts_with('#'))
.collect::<Vec<_>>()
.join("\n");
for (feature, what) in [
(
"serde-str",
"replaces the global Deserialize for rust_decimal::Decimal, so a JSON number \
stops deserialising in every crate in the consumer's graph",
),
(
"serde-float",
"turns an exact decimal into an f64 on the way out, in a graph whose whole \
claim is exact arithmetic",
),
(
"serde-arbitrary-precision",
"changes how a number is represented on the wire",
),
(
"serde-human-readable",
"replaces time's own impls, so an OffsetDateTime goes to JSON as a formatted \
string instead of an ordinal-date array — or the reverse, depending on who \
else in the graph asked",
),
] {
assert!(
!code.contains(feature),
"Cargo.toml reaches for the {feature:?} feature, which {what}. That is not \
an impl added, it is one replaced — for every crate in the consumer's build \
graph, including crates that never named this one. State the representation \
on the field instead, as `metering::wire` does."
);
}
for path in ["rust_decimal/serde", "chrono/serde", "time/serde-"] {
assert!(
!code.contains(path),
"Cargo.toml enables {path:?} on a crate the consumer also uses. Only \
additive features may be turned on for someone else's build."
);
}
}
#[test]
fn no_serde_type_in_this_crate_leaves_its_representation_to_the_build_graph() {
const AMBIGUOUS: [&str; 4] = ["OffsetDateTime", "Decimal", "Date", "time::Duration"];
let mut offenders = Vec::new();
for (path, text) in sources() {
let mut derives_serde = false;
let mut depth = 0usize;
let mut stated = false;
for line in text.lines() {
let trimmed = line.trim();
if trimmed.starts_with("#[serde(with") || trimmed.contains("serde(with =") {
stated = true;
continue;
}
if trimmed.starts_with("#[derive") || trimmed.starts_with("#[cfg_attr") {
if trimmed.contains("Serialize") || trimmed.contains("Deserialize") {
derives_serde = true;
}
continue;
}
if derives_serde {
depth += trimmed.matches('{').count();
depth -= depth.min(trimmed.matches('}').count());
if depth == 0 && trimmed.contains('}') {
derives_serde = false;
stated = false;
continue;
}
if depth > 0
&& !stated
&& trimmed.contains(':')
&& !trimmed.starts_with("//")
&& AMBIGUOUS.iter().any(|ty| trimmed.contains(ty))
{
offenders.push(format!("{}: {trimmed}", path.display()));
}
stated = false;
}
}
}
assert!(
offenders.is_empty(),
"a serde-derived type carries a field whose representation the build graph \
decides:\n {}\nState it on the field — `#[serde(with = \"…\")]` — the way \
`metering::wire` does, or the bytes change when an unrelated crate turns a \
feature on.",
offenders.join("\n ")
);
}
#[test]
fn the_features_this_crate_turns_on_for_a_consumer_are_only_additive() {
let lock = std::fs::read_to_string(crate_root().join("Cargo.lock")).expect("Cargo.lock");
assert!(
lock.contains("name = \"metering\""),
"the lockfile does not name metering"
);
let manifest = std::fs::read_to_string(crate_root().join("Cargo.toml")).expect("Cargo.toml");
let metering_line = manifest
.lines()
.find(|l| l.trim_start().starts_with("metering = "))
.expect("metering is a direct dependency");
assert!(
metering_line.contains("features = [\"serde\"]"),
"the metering dependency changed shape; re-read what its features pull in: \
{metering_line}"
);
}