#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use salsa::{Database, EventKind, Setter};
#[salsa::db]
struct GcTestDb {
storage: salsa::Storage<Self>,
}
#[salsa::db]
impl Database for GcTestDb {}
impl GcTestDb {
fn with_discard_counter(counter: Arc<AtomicUsize>) -> Self {
let storage = salsa::StorageHandle::<Self>::new(Some(Box::new(move |event| {
if matches!(event.kind, EventKind::DidDiscard { .. }) {
counter.fetch_add(1, Ordering::Relaxed);
}
})))
.into_storage();
Self { storage }
}
}
#[salsa::input]
struct ActiveFileCount {
n: u32,
}
#[salsa::tracked]
struct TrackedFile<'db> {
file_id: u32,
}
#[salsa::tracked]
fn active_files<'db>(db: &'db dyn Database, count: ActiveFileCount) -> Vec<TrackedFile<'db>> {
(0..count.n(db))
.map(|id| TrackedFile::new(db, id))
.collect()
}
#[salsa::tracked]
fn file_hash<'db>(db: &'db dyn Database, file: TrackedFile<'db>) -> u64 {
file.file_id(db) as u64 * 0xdeadbeef
}
#[test]
fn tracked_struct_deletion_fires_discard_event() {
let discard_count = Arc::new(AtomicUsize::new(0));
let mut db = GcTestDb::with_discard_counter(Arc::clone(&discard_count));
let count_input = ActiveFileCount::new(&db, 3);
{
let files = active_files(&db, count_input);
for &f in &files {
let _ = file_hash(&db, f);
}
}
assert_eq!(
discard_count.load(Ordering::Relaxed),
0,
"no discards before any revision change"
);
count_input.set_n(&mut db).to(2);
let _ = active_files(&db, count_input);
let discards = discard_count.load(Ordering::Relaxed);
assert!(
discards > 0,
"salsa 0.26.x must fire DidDiscard when a tracked struct is deleted \
(L2-gate-1); got 0 discards — tracked-struct GC does not free memory \
and the L2 migration would be a no-op"
);
}
}