use std::fs;
use std::path::PathBuf;
use std::thread;
use std::time::Duration;
use tempfile::TempDir;
use windjammer::compiler::cache_management;
#[test]
fn test_is_output_fresh_when_rs_newer_than_wj() {
let dir = TempDir::new().unwrap();
let wj = dir.path().join("foo.wj");
let rs = dir.path().join("foo.rs");
fs::write(&wj, "fn main() {}\n").unwrap();
thread::sleep(Duration::from_millis(10));
fs::write(&rs, "// generated\n").unwrap();
assert!(cache_management::is_output_fresh(&wj, &rs));
}
#[test]
fn test_is_output_fresh_when_wj_newer_than_rs() {
let dir = TempDir::new().unwrap();
let wj = dir.path().join("foo.wj");
let rs = dir.path().join("foo.rs");
fs::write(&rs, "// generated\n").unwrap();
thread::sleep(Duration::from_millis(10));
fs::write(&wj, "fn main() {}\n").unwrap();
assert!(!cache_management::is_output_fresh(&wj, &rs));
}
#[test]
fn test_compiler_stamp_invalidation() {
let dir = TempDir::new().unwrap();
cache_management::write_compiler_stamp(dir.path()).unwrap();
assert!(cache_management::is_compiler_stamp_fresh(dir.path()));
}
#[test]
fn test_compute_dirty_files_marks_all_when_no_output() {
let dir = TempDir::new().unwrap();
let src = dir.path().join("src");
fs::create_dir_all(&src).unwrap();
let wj = src.join("a.wj");
fs::write(&wj, "fn a() {}\n").unwrap();
cache_management::write_compiler_stamp(dir.path()).unwrap();
let sources = vec![(wj, "fn a() {}\n".to_string())];
let (dirty, skipped) =
cache_management::compute_dirty_files(&sources, &src, dir.path(), &[]);
assert_eq!(skipped, 0);
assert_eq!(dirty.len(), 1);
}
#[test]
fn test_all_sources_fresh_false_without_outputs() {
let dir = TempDir::new().unwrap();
let src = dir.path().join("src");
fs::create_dir_all(&src).unwrap();
let wj = src.join("a.wj");
fs::write(&wj, "fn a() {}\n").unwrap();
cache_management::write_compiler_stamp(dir.path()).unwrap();
let sources = vec![(wj, "fn a() {}\n".to_string())];
assert!(!cache_management::all_sources_fresh(
&sources,
&src,
dir.path(),
&[] as &[PathBuf],
));
}
#[test]
fn test_compute_dirty_when_rs_mtime_newer_but_source_content_changed() {
use windjammer::compiler::incremental::fingerprint_for_emit;
use windjammer::metadata::ModuleMetadata;
let dir = TempDir::new().unwrap();
let src = dir.path().join("src");
fs::create_dir_all(&src).unwrap();
cache_management::write_compiler_stamp(dir.path()).unwrap();
let wj = src.join("foo.wj");
let rs = dir.path().join("foo.rs");
let original = "fn original() {}\n";
let updated = "fn updated() {}\n";
fs::write(&wj, original).unwrap();
let fp = fingerprint_for_emit(original, &[]);
let meta = ModuleMetadata {
module_path: "foo".to_string(),
analysis_fingerprint: Some(fp.into()),
..ModuleMetadata::new("foo".to_string())
};
let meta_path = windjammer::metadata::meta_cache_path(&wj);
if let Some(parent) = meta_path.parent() {
fs::create_dir_all(parent).unwrap();
}
fs::write(&meta_path, serde_json::to_string(&meta).unwrap()).unwrap();
let stale_rs = "// stale generated from original\n";
fs::write(&rs, stale_rs).unwrap();
thread::sleep(Duration::from_millis(10));
fs::write(&wj, updated).unwrap();
thread::sleep(Duration::from_millis(10));
fs::write(&rs, stale_rs).unwrap();
assert!(cache_management::is_output_fresh(&wj, &rs));
assert!(!cache_management::is_codegen_cache_valid(
updated,
&wj,
&rs,
&[] as &[PathBuf],
));
let sources = vec![(wj, updated.to_string())];
let (dirty, skipped) =
cache_management::compute_dirty_files(&sources, &src, dir.path(), &[]);
assert_eq!(skipped, 0, "must not silently skip when content changed");
assert_eq!(dirty.len(), 1);
}
#[test]
fn test_find_stale_codegen_outputs_detects_fingerprint_mismatch() {
use windjammer::compiler::incremental::fingerprint_for_emit;
use windjammer::metadata::ModuleMetadata;
let dir = TempDir::new().unwrap();
let src = dir.path().join("src");
fs::create_dir_all(&src).unwrap();
cache_management::write_compiler_stamp(dir.path()).unwrap();
let wj = src.join("foo.wj");
let rs = dir.path().join("foo.rs");
let original = "fn original() {}\n";
let updated = "fn updated() {}\n";
fs::write(&wj, original).unwrap();
let fp = fingerprint_for_emit(original, &[]);
let meta = ModuleMetadata {
module_path: "foo".to_string(),
analysis_fingerprint: Some(fp.into()),
..ModuleMetadata::new("foo".to_string())
};
let meta_path = windjammer::metadata::meta_cache_path(&wj);
if let Some(parent) = meta_path.parent() {
fs::create_dir_all(parent).unwrap();
}
fs::write(&meta_path, serde_json::to_string(&meta).unwrap()).unwrap();
fs::write(&wj, updated).unwrap();
thread::sleep(Duration::from_millis(10));
fs::write(&rs, "// stale\n").unwrap();
let sources = vec![(wj.clone(), updated.to_string())];
let stale = cache_management::find_stale_codegen_outputs(&sources, &src, dir.path(), &[]);
assert_eq!(stale, vec![wj]);
}
#[test]
fn test_mod_wj_merged_into_mod_rs_is_not_stale() {
use windjammer::compiler::incremental::fingerprint_for_emit;
use windjammer::metadata::ModuleMetadata;
let dir = TempDir::new().unwrap();
let src = dir.path().join("src");
fs::create_dir_all(&src).unwrap();
cache_management::write_compiler_stamp(dir.path()).unwrap();
let mod_wj = src.join("mod.wj");
let source = "pub use player::Player;\n";
fs::write(&mod_wj, source).unwrap();
thread::sleep(Duration::from_millis(10));
let mod_rs = dir.path().join("mod.rs");
fs::write(&mod_rs, "// Auto-generated mod.rs\npub mod player;\n").unwrap();
let fp = fingerprint_for_emit(source, &[]);
let meta = ModuleMetadata {
module_path: "mod".to_string(),
analysis_fingerprint: Some(fp.into()),
..ModuleMetadata::new("mod".to_string())
};
let meta_path = windjammer::metadata::meta_cache_path(&mod_wj);
if let Some(parent) = meta_path.parent() {
fs::create_dir_all(parent).unwrap();
}
fs::write(&meta_path, serde_json::to_string(&meta).unwrap()).unwrap();
let items_path = dir.path().join("_mod_items.rs");
assert!(
!items_path.exists(),
"merged mod.wj should not require _mod_items.rs on disk"
);
let sources = vec![(mod_wj.clone(), source.to_string())];
assert!(
cache_management::find_stale_codegen_outputs(&sources, &src, dir.path(), &[]).is_empty(),
"declaration-only mod.wj merged into mod.rs must not be reported stale"
);
}
#[test]
fn test_find_stale_codegen_outputs_skips_out_of_tree_stdlib_injects() {
let dir = TempDir::new().unwrap();
let src = dir.path().join("src");
fs::create_dir_all(&src).unwrap();
cache_management::write_compiler_stamp(dir.path()).unwrap();
let user_wj = src.join("game.wj");
fs::write(&user_wj, "use std::collections::HashMap\n").unwrap();
let user_rs = dir.path().join("game.rs");
fs::write(&user_rs, "// generated\n").unwrap();
let stdlib_wj = dir.path().join("stdlib/collections.wj");
fs::create_dir_all(stdlib_wj.parent().unwrap()).unwrap();
fs::write(&stdlib_wj, "pub struct HashMap {}\n").unwrap();
let sources = vec![
(stdlib_wj.clone(), "pub struct HashMap {}\n".to_string()),
(user_wj.clone(), "use std::collections::HashMap\n".to_string()),
];
let stale = cache_management::find_stale_codegen_outputs(&sources, &src, dir.path(), &[]);
assert!(
!stale.contains(&stdlib_wj),
"out-of-tree stdlib inject must not be stale-checked, got {:?}",
stale
);
}