use std::path::{Path, PathBuf};
use crate::core::config::{IncludeConfig, ResolvedCrateConfig};
use crate::core::ir::ApiSurface;
const SENTINEL_TYPE: &str = "PlantedByAPreviousAlefRelease";
const PREVIOUS_ALEF_VERSION: &str = "0.0.0-previous-alef-release";
const FIXTURE_SOURCE: &str = "pub struct Record {\n pub value: String,\n}\n";
struct Fixture {
_dir: tempfile::TempDir,
_cwd: crate::test_support::CwdGuard,
config: ResolvedCrateConfig,
config_path: PathBuf,
root: PathBuf,
}
impl Fixture {
fn new() -> Self {
let dir = tempfile::tempdir().expect("create fixture directory");
let cwd = crate::test_support::CwdGuard::enter(dir.path());
let root = std::env::current_dir().expect("read fixture directory");
let manifest = root.join("Cargo.toml");
let source = root.join("lib.rs");
std::fs::write(&manifest, "[package]\nname = \"sample\"\nversion = \"1.4.0\"\n").expect("write manifest");
std::fs::write(&source, FIXTURE_SOURCE).expect("write fixture source");
let config = ResolvedCrateConfig {
name: "sample".to_string(),
sources: vec![source],
version_from: manifest.to_string_lossy().into_owned(),
include: IncludeConfig {
types: vec!["Record".to_string()],
..Default::default()
},
..Default::default()
};
Self {
config_path: root.join("alef.toml"),
root,
config,
_dir: dir,
_cwd: cwd,
}
}
fn extract(&self) -> ApiSurface {
super::super::extract(&self.config, &self.config_path, false).expect("extract fixture surface")
}
fn ir_hash_path(&self) -> PathBuf {
self.root.join(".alef").join(&self.config.name).join("ir.hash")
}
fn ir_json_path(&self) -> PathBuf {
self.root.join(".alef").join(&self.config.name).join("ir.json")
}
fn plant_sentinel_surface(&self, base: &ApiSurface) {
let mut planted = base.clone();
let mut sentinel = base.types.first().expect("fixture surface has a type").clone();
sentinel.name = SENTINEL_TYPE.to_string();
sentinel.rust_path = format!("sample::{SENTINEL_TYPE}");
planted.types.push(sentinel);
std::fs::write(
self.ir_json_path(),
serde_json::to_string_pretty(&planted).expect("serialize planted surface"),
)
.expect("plant cached surface");
}
}
fn has_sentinel(api: &ApiSurface) -> bool {
api.types.iter().any(|ty| ty.name == SENTINEL_TYPE)
}
fn read_key(path: &Path) -> String {
std::fs::read_to_string(path).expect("read cached IR key")
}
#[test]
fn ir_cache_is_actually_consulted_within_one_alef_version() {
let fixture = Fixture::new();
let first = fixture.extract();
assert!(
first.types.iter().any(|ty| ty.name == "Record"),
"fixture must extract its one public type before anything is planted"
);
assert!(!has_sentinel(&first), "the extractor cannot invent {SENTINEL_TYPE}");
fixture.plant_sentinel_surface(&first);
let replayed = fixture.extract();
assert!(
has_sentinel(&replayed),
"a second extract with the same inputs and the same alef build must serve the cached \
surface; if it does not, the staleness test in this file proves nothing"
);
}
#[test]
fn ir_cache_written_by_another_alef_version_is_not_replayed() {
let fixture = Fixture::new();
let first = fixture.extract();
fixture.plant_sentinel_surface(&first);
let current_key = read_key(&fixture.ir_hash_path());
let stale_key = super::super::ir_cache_key(&fixture.config, &fixture.config_path, PREVIOUS_ALEF_VERSION)
.expect("build the previous release's IR cache key");
assert_ne!(
stale_key.as_str(),
current_key,
"the IR cache key must differ between alef releases for identical extraction inputs; \
equal keys are the bug — a newer alef silently generating from an older one's surface"
);
std::fs::write(fixture.ir_hash_path(), stale_key.as_str()).expect("plant the previous release's key");
let refreshed = fixture.extract();
assert!(
!has_sentinel(&refreshed),
"a surface cached under another alef release's key must be re-extracted, not replayed"
);
assert!(
refreshed.types.iter().any(|ty| ty.name == "Record"),
"re-extraction must produce the real surface"
);
assert_eq!(
read_key(&fixture.ir_hash_path()),
current_key,
"re-extraction must re-key the cache to the running alef build"
);
}