use super::super::*;
use super::{load_manifest, sample_plan, synthetic_target, write};
use std::path::Path;
fn sample_thin_v2_plan(root: &Path) -> RustArtifactPlanV1 {
RustArtifactPlanV1 {
allowed_artifact_classes: vec![
RustArtifactClass::CargoFingerprintMeta,
RustArtifactClass::DepInfo,
RustArtifactClass::BuildScriptMetadata,
RustArtifactClass::BuildScriptOutput,
],
cache_schema_version: 2,
cache_profile: Some("thin-v2".to_string()),
dropped_artifact_classes: vec![
RustArtifactClass::Incremental,
RustArtifactClass::BuildScriptBuild,
RustArtifactClass::Rlib,
RustArtifactClass::Rmeta,
RustArtifactClass::ProcMacro,
RustArtifactClass::Dwo,
RustArtifactClass::Pdb,
RustArtifactClass::Dsym,
RustArtifactClass::CargoFingerprintOutputs,
],
..sample_plan(root, RustPlanMode::Thin)
}
}
#[test]
fn from_json_value_accepts_thin_v2_cache_profile_and_drop_list() {
let dir = tempfile::tempdir().unwrap();
let plan = sample_thin_v2_plan(dir.path());
let value = serde_json::to_value(&plan).unwrap();
let loaded = RustArtifactPlanV1::from_json_value(value).unwrap();
assert_eq!(loaded.cache_profile.as_deref(), Some("thin-v2"));
assert_eq!(loaded.cache_schema_version, 2);
assert!(loaded
.dropped_artifact_classes
.contains(&RustArtifactClass::Rlib));
assert!(loaded
.allowed_artifact_classes
.contains(&RustArtifactClass::CargoFingerprintMeta));
}
#[test]
fn from_json_value_ignores_unknown_forward_compat_fields() {
let dir = tempfile::tempdir().unwrap();
let plan = sample_plan(dir.path(), RustPlanMode::Thin);
let mut value = serde_json::to_value(&plan).unwrap();
value.as_object_mut().unwrap().insert(
"future_soldr_field_that_does_not_exist_yet".to_string(),
serde_json::json!({"any": "shape", "version": 9001}),
);
let loaded =
RustArtifactPlanV1::from_json_value(value).expect("unknown fields must be ignored");
assert_eq!(loaded.schema_version, 1);
}
#[test]
fn legacy_thin_v1_plan_without_new_fields_still_deserializes() {
let dir = tempfile::tempdir().unwrap();
let plan = sample_plan(dir.path(), RustPlanMode::Thin);
let mut value = serde_json::to_value(&plan).unwrap();
let obj = value.as_object_mut().unwrap();
obj.remove("cache_profile");
obj.remove("dropped_artifact_classes");
let loaded = RustArtifactPlanV1::from_json_value(value).unwrap();
assert!(loaded.cache_profile.is_none());
assert!(loaded.dropped_artifact_classes.is_empty());
assert_eq!(loaded.cache_schema_version, 1);
}
#[test]
fn from_json_value_accepts_cache_schema_version_2() {
let dir = tempfile::tempdir().unwrap();
let mut plan = sample_plan(dir.path(), RustPlanMode::Thin);
plan.cache_schema_version = 2;
let value = serde_json::to_value(&plan).unwrap();
let loaded = RustArtifactPlanV1::from_json_value(value).unwrap();
assert_eq!(loaded.cache_schema_version, 2);
}
#[test]
fn thin_v2_save_drops_rlib_rmeta_even_when_allowed_list_lists_them() {
let dir = tempfile::tempdir().unwrap();
synthetic_target(dir.path());
let mut plan = sample_thin_v2_plan(dir.path());
plan.allowed_artifact_classes = vec![
RustArtifactClass::Rlib,
RustArtifactClass::Rmeta,
RustArtifactClass::DepInfo,
RustArtifactClass::CargoFingerprintMeta,
RustArtifactClass::BuildScriptMetadata,
RustArtifactClass::BuildScriptOutput,
];
let cache = dir.path().join("cache");
let saved = save_rust_plan_local(&plan, &cache).unwrap();
let bundle_dir = rust_plan_bundle_dir(&cache, &rust_plan_cache_key(&plan));
let manifest = load_manifest(&bundle_dir);
for artifact in &manifest.artifacts {
assert!(
!artifact.relative_path.ends_with(".rlib"),
"thin-v2 drop list must skip .rlib; got {}",
artifact.relative_path
);
assert!(
!artifact.relative_path.ends_with(".rmeta"),
"thin-v2 drop list must skip .rmeta; got {}",
artifact.relative_path
);
}
assert!(saved
.skipped_reasons
.get("artifact_class_disallowed_by_plan")
.is_some_and(|n| *n >= 3));
}
#[test]
fn thin_v2_save_keeps_fingerprint_meta_but_drops_fingerprint_outputs() {
let dir = tempfile::tempdir().unwrap();
let target = dir.path().join("target").join("debug");
write(
&target.join(".fingerprint/serde-abc/invoked.timestamp"),
b"ts",
);
write(&target.join(".fingerprint/serde-abc/dep-lib-serde"), b"dep");
write(
&target.join(".fingerprint/serde-abc/lib-serde"),
b"libstamp",
);
write(
&target.join(".fingerprint/serde-abc/serde-abc.json"),
b"output",
);
write(&target.join("deps/serde-abc.d"), b"depinfo");
write(&target.join("incremental/state.bin"), b"transient");
let plan = sample_thin_v2_plan(dir.path());
let cache = dir.path().join("cache");
save_rust_plan_local(&plan, &cache).unwrap();
let bundle_dir = rust_plan_bundle_dir(&cache, &rust_plan_cache_key(&plan));
let manifest = load_manifest(&bundle_dir);
let kept_paths: Vec<&str> = manifest
.artifacts
.iter()
.map(|a| a.relative_path.as_str())
.collect();
assert!(
kept_paths
.iter()
.any(|p| p.ends_with(".fingerprint/serde-abc/invoked.timestamp")),
"invoked.timestamp must be kept; got {kept_paths:?}",
);
assert!(
kept_paths
.iter()
.any(|p| p.ends_with(".fingerprint/serde-abc/dep-lib-serde")),
"dep-* must be kept; got {kept_paths:?}",
);
assert!(
kept_paths
.iter()
.any(|p| p.ends_with(".fingerprint/serde-abc/lib-serde")),
"lib-* must be kept; got {kept_paths:?}",
);
assert!(
!kept_paths
.iter()
.any(|p| p.ends_with(".fingerprint/serde-abc/serde-abc.json")),
"fingerprint output .json must be dropped; got {kept_paths:?}",
);
assert!(
kept_paths.iter().any(|p| p.ends_with("deps/serde-abc.d")),
"dep_info must still be kept; got {kept_paths:?}",
);
}
#[test]
fn thin_v2_classifier_recognizes_new_classes() {
let bsb = if cfg!(windows) {
Path::new("debug/build/serde-abc/build-script-build.exe")
} else {
Path::new("debug/build/serde-abc/build-script-build")
};
assert_eq!(
classify_artifact(bsb, RustPlanMode::Thin, true),
Some(RustArtifactClass::BuildScriptBuild),
);
assert_eq!(
classify_artifact(
Path::new("debug/deps/libserde-abc.dwo"),
RustPlanMode::Thin,
true,
),
Some(RustArtifactClass::Dwo),
);
assert_eq!(
classify_artifact(
Path::new("debug/deps/libserde-abc.pdb"),
RustPlanMode::Thin,
true,
),
Some(RustArtifactClass::Pdb),
);
assert_eq!(
classify_artifact(
Path::new("debug/deps/app.dSYM/Contents/Info.plist"),
RustPlanMode::Thin,
true,
),
Some(RustArtifactClass::Dsym),
);
assert_eq!(
classify_artifact(
Path::new("debug/.fingerprint/serde-abc/invoked.timestamp"),
RustPlanMode::Thin,
true,
),
Some(RustArtifactClass::CargoFingerprintMeta),
);
assert_eq!(
classify_artifact(
Path::new("debug/.fingerprint/serde-abc/serde-abc.json"),
RustPlanMode::Thin,
true,
),
Some(RustArtifactClass::CargoFingerprintOutputs),
);
assert_eq!(
classify_artifact(
Path::new("debug/.fingerprint/serde-abc/invoked.timestamp"),
RustPlanMode::Thin,
false,
),
Some(RustArtifactClass::CargoFingerprint),
);
}
#[test]
fn thin_v2_and_thin_v1_identity_hashes_differ() {
let dir = tempfile::tempdir().unwrap();
let v1 = sample_plan(dir.path(), RustPlanMode::Thin);
let v2 = sample_thin_v2_plan(dir.path());
assert_ne!(
rust_plan_identity_hash(&v1),
rust_plan_identity_hash(&v2),
"thin-v1 and thin-v2 identity hashes must not collide",
);
}