Skip to content

Commit 3b34d9b

Browse files
committed
Add tests for #1313
1 parent 9858c1c commit 3b34d9b

2 files changed

Lines changed: 91 additions & 0 deletions

File tree

crates/oak_scan/src/tests/watch.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use std::path::Path;
44
use std::path::PathBuf;
55

66
use aether_path::FilePath;
7+
use filetime::set_file_mtime;
8+
use filetime::FileTime;
79
use oak_db::Db;
810
use oak_db::DbInputs;
911
use oak_db::OakDatabase;
@@ -69,6 +71,12 @@ fn remove_watched_file(db: &mut OakDatabase, path: FilePath) {
6971
);
7072
}
7173

74+
/// The global salsa revision. Bumps once per input setter that actually
75+
/// fires, so a delta of zero across an operation means it wrote no inputs.
76+
fn salsa_revision(db: &OakDatabase) -> salsa::Revision {
77+
salsa::plumbing::current_revision(db)
78+
}
79+
7280
/// Sync helper: force a fresh full rescan of `root`. Equivalent to the
7381
/// production trigger of a DESCRIPTION watcher event hitting the root.
7482
fn rescan_workspace_root(db: &mut OakDatabase, root: Root) {
@@ -626,3 +634,60 @@ fn test_apply_watcher_events_tolerates_non_package_description() {
626634
let root = db.workspace_roots().roots(&db)[0];
627635
assert!(root.packages(&db).is_empty());
628636
}
637+
638+
#[test]
639+
fn test_noop_rescan_does_not_bump_salsa_revision() {
640+
// Salsa has no backdating for inputs: setting an input to the value it
641+
// already holds still bumps the global revision and invalidates every
642+
// query downstream of it. So a rescan that finds nothing new on disk must
643+
// not touch any input. We observe the revision directly. A clean rescan
644+
// leaves it flat, a structural change moves it.
645+
let tmp = tempfile::tempdir().unwrap();
646+
write_package(&tmp.path().join("pkg"), "pkg", &[("a.R", "x <- 1\n")]);
647+
fs::create_dir_all(tmp.path().join("pkg/tests")).unwrap();
648+
fs::write(tmp.path().join("pkg/tests/test-foo.R"), "t\n").unwrap();
649+
fs::write(tmp.path().join("top.R"), "y <- 2\n").unwrap();
650+
let mut db = OakDatabase::new();
651+
set_workspace_paths(&mut db, &[tmp.path().to_path_buf()], &HashSet::new());
652+
let root = db.workspace_roots().roots(&db)[0];
653+
654+
// Nothing changed on disk, so the rescan reuses every `Package` / `File`
655+
// entity and finds every input field already equal.
656+
let before = salsa_revision(&db);
657+
rescan_workspace_root(&mut db, root);
658+
assert_eq!(salsa_revision(&db), before);
659+
660+
// A new file under pkg/R/ is a real structural change, so it does move the
661+
// revision. This proves the assertion above isn't vacuously true.
662+
fs::write(tmp.path().join("pkg/R/b.R"), "z <- 3\n").unwrap();
663+
rescan_workspace_root(&mut db, root);
664+
assert!(salsa_revision(&db) > before);
665+
}
666+
667+
#[test]
668+
fn test_watcher_reblip_same_mtime_does_not_bump_salsa_revision() {
669+
// Watchers coalesce and re-emit events, so one save can deliver a Changed
670+
// event whose re-stat matches the mtime we already stored.
671+
// `add_watched_file` guards the `File::revision` write against that, so a
672+
// duplicate event leaves the revision (and thus `source_text`) alone.
673+
let tmp = tempfile::tempdir().unwrap();
674+
write_package(&tmp.path().join("pkg"), "pkg", &[("a.R", "x <- 1\n")]);
675+
let mut db = OakDatabase::new();
676+
set_workspace_paths(&mut db, &[tmp.path().to_path_buf()], &HashSet::new());
677+
678+
let fs_path = tmp.path().join("pkg/R/a.R");
679+
let path = FilePath::from_path_buf(fs_path.clone()).unwrap();
680+
681+
// Duplicate event, nothing rewritten: same mtime, so the guard skips.
682+
let before = salsa_revision(&db);
683+
add_watched_file(&mut db, path.clone());
684+
assert_eq!(salsa_revision(&db), before);
685+
686+
// A genuine save carries a distinct mtime, so the revision bumps and the
687+
// next `source_text` re-reads. Set the mtime explicitly rather than trust
688+
// the filesystem's mtime granularity to notice a back-to-back write.
689+
fs::write(&fs_path, "x <- 2\n").unwrap();
690+
set_file_mtime(&fs_path, FileTime::from_unix_time(1_000_000_000, 0)).unwrap();
691+
add_watched_file(&mut db, path);
692+
assert!(salsa_revision(&db) > before);
693+
}

crates/oak_scan/tests/integration/library.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,32 @@ fn test_package_version_rereads_when_revision_bumps() {
152152
assert_eq!(pkg.version(&db), &Some("2.0.0".to_string()));
153153
}
154154

155+
#[test]
156+
fn test_reset_same_library_paths_does_not_bump_salsa_revision() {
157+
// Re-declaring the identical library paths reuses every `Root` entity, so
158+
// the `LibraryRoots.roots` vec compares equal and the guarded setter is
159+
// skipped. Salsa has no backdating for inputs, so an unguarded set here
160+
// would bump the global revision and invalidate everything keyed on the
161+
// library set. We observe the revision: no change leaves it flat, a real
162+
// change moves it.
163+
let tmp = tempfile::tempdir().unwrap();
164+
write_package(&tmp.path().join("dplyr"), "dplyr", &[("a.R", "x <- 1\n")]);
165+
let mut db = OakDatabase::new();
166+
167+
db.set_library_paths(&[tmp.path().to_path_buf()]);
168+
169+
let before = salsa::plumbing::current_revision(&db);
170+
db.set_library_paths(&[tmp.path().to_path_buf()]);
171+
assert_eq!(salsa::plumbing::current_revision(&db), before);
172+
173+
// Adding a second path is a real change, so the revision moves. This
174+
// proves the assertion above isn't vacuously true.
175+
let tmp2 = tempfile::tempdir().unwrap();
176+
write_package(&tmp2.path().join("tibble"), "tibble", &[]);
177+
db.set_library_paths(&[tmp.path().to_path_buf(), tmp2.path().to_path_buf()]);
178+
assert!(salsa::plumbing::current_revision(&db) > before);
179+
}
180+
155181
#[test]
156182
fn test_scan_multiple_library_paths_preserve_order() {
157183
let tmp1 = tempfile::tempdir().unwrap();

0 commit comments

Comments
 (0)