1use std::path::Path;
6use std::time::SystemTime;
7
8use anyhow::Result;
9
10use crate::fingerprint::{ArtifactInfo, Unit};
11use crate::paths;
12use crate::util::{human_age, human_bytes, human_duration};
13use crate::who;
14
15#[derive(Debug, Clone, Copy)]
17pub enum Level {
18 Pass,
19 Info,
20 Warn,
21 Fail,
22}
23
24impl Level {
25 pub fn icon(&self) -> &'static str {
26 match self {
27 Level::Pass => crate::util::GLYPH_PASS,
28 Level::Info => crate::util::GLYPH_INFO,
29 Level::Warn => crate::util::GLYPH_WARN,
30 Level::Fail => crate::util::GLYPH_FAIL,
31 }
32 }
33}
34
35pub struct Check {
37 pub section: &'static str,
38 pub level: Level,
39 pub message: String,
40}
41
42pub const SEC_CACHE: &str = "shared cache";
43pub const SEC_CONFIG: &str = "configuration";
44pub const SEC_WRAPPER: &str = "wrapper";
45pub const SEC_FS: &str = "filesystem";
46pub const SEC_TC: &str = "toolchain";
47pub const SEC_MTIME: &str = "mtime discipline";
48
49fn check(section: &'static str, level: Level, message: impl Into<String>) -> Check {
50 Check {
51 section,
52 level,
53 message: message.into(),
54 }
55}
56
57pub fn run() -> Result<Vec<Check>> {
59 let mut checks = Vec::new();
60
61 let root = match crate::config::trusted_cache_root() {
64 Ok(root) => root,
65 Err(e) => {
66 return Ok(vec![check(
67 SEC_CACHE,
68 Level::Fail,
69 format!("could not resolve the shared cache dir ({e:#}) — run `epicenter setup`"),
70 )]);
71 }
72 };
73
74 checks.push(if root.is_dir() {
76 check(
77 SEC_CACHE,
78 Level::Pass,
79 format!("shared cache exists at {}", root.display()),
80 )
81 } else {
82 check(
83 SEC_CACHE,
84 Level::Fail,
85 format!(
86 "shared cache missing at {} — run `epicenter setup`",
87 root.display()
88 ),
89 )
90 });
91
92 checks.push(check_build_dir(&root));
94 checks.push(check_cache_permissions(&root));
95
96 checks.extend(check_ancestor_configs());
98
99 checks.extend(check_rustc_wrapper());
101
102 if root.starts_with("/mnt/") {
104 checks.push(check(
105 SEC_FS,
106 Level::Warn,
107 String::from(concat!(
108 "shared cache on a Windows-mounted drive — very slow I/O under WSL; ",
109 "prefer the default {cargo-cache-home} location"
110 )),
111 ));
112 } else {
113 checks.push(check(
114 SEC_FS,
115 Level::Pass,
116 format!("cache root on the local filesystem: {}", root.display()),
117 ));
118 }
119
120 if let Some(cross) = check_exdev(&root) {
122 checks.push(cross);
123 }
124
125 if let Ok(home) = paths::home() {
128 let legacy = home.join(".epicenter").join("config.toml");
129 if legacy.exists() {
130 checks.push(check(
131 SEC_CONFIG,
132 Level::Info,
133 "legacy ~/.epicenter/config.toml found — no longer used; safe to delete",
134 ));
135 }
136 }
137
138 let rows = match who::collect(&root) {
142 Ok(rows) => {
143 checks.extend(check_toolchain_generation(&rows));
144 Some(rows)
145 }
146 Err(e) => {
147 checks.push(check(
148 SEC_TC,
149 Level::Warn,
150 format!("could not read the shared cache: {e:#}"),
151 ));
152 None
153 }
154 };
155 checks.push(check_rustc());
156 checks.push(check_locking());
157 if let Some(rows) = &rows {
158 checks.extend(check_mtime_ordering(rows));
159 }
160
161 Ok(checks)
162}
163
164fn check_build_dir(root: &Path) -> Check {
167 let section: &'static str = SEC_CONFIG;
168 let value = match crate::paths::cargo_build_dir() {
169 Ok(Some(value)) => value,
170 Ok(None) => {
171 return check(
172 section,
173 Level::Warn,
174 "no build-dir in the global cargo config — run `epicenter setup`",
175 );
176 }
177 Err(e) => {
178 return check(
179 section,
180 Level::Warn,
181 format!("could not read the cargo config: {e:#}"),
182 );
183 }
184 };
185
186 let has_braces = value.contains('{') && value.contains('}');
187 if !has_braces {
188 if value.contains("C:\\") || value.contains("/mnt/") {
191 return check(
192 section,
193 Level::Fail,
194 String::from(concat!(
195 "build-dir value is a literal other-OS path — the cross-OS-safe form is ",
196 "'{cargo-cache-home}\\epicenter\\build'; run `epicenter setup`"
197 )),
198 );
199 }
200 return check(
201 section,
202 Level::Warn,
203 format!(
204 "build-dir = '{value}' is a literal path (no template) — fine locally, \
205 fragile when configs are shared across OSes; `epicenter setup` writes the template form"
206 ),
207 );
208 }
209
210 match paths::resolve_template(&value) {
211 Ok(resolved) if resolved == root => check(
212 section,
213 Level::Pass,
214 format!(
215 "build-dir template resolves to the shared cache: {}",
216 root.display()
217 ),
218 ),
219 Ok(resolved) => check(
220 section,
221 Level::Warn,
222 format!(
223 "config drift: the global build-dir resolves to {}, but cargo's effective \
224 build-dir is {} — an ancestor config may be winning; run `epicenter setup`",
225 resolved.display(),
226 root.display()
227 ),
228 ),
229 Err(e) => check(
230 section,
231 Level::Warn,
232 format!(
233 "could not expand the build-dir template locally ({e:#}) — cargo still \
234 expands it at build time; only epicenter's local view failed"
235 ),
236 ),
237 }
238}
239
240fn check_cache_permissions(root: &Path) -> Check {
243 let section: &'static str = SEC_CACHE;
244 let meta = match std::fs::symlink_metadata(root) {
245 Ok(meta) => meta,
246 Err(err) => {
247 return check(
248 section,
249 Level::Fail,
250 format!("could not inspect cache root: {err}"),
251 );
252 }
253 };
254 if meta.file_type().is_symlink() || crate::util::is_reparse_point(&meta) || !meta.is_dir() {
255 return check(
256 section,
257 Level::Fail,
258 "cache root is a symlink/reparse point or not a directory",
259 );
260 }
261 #[cfg(unix)]
262 {
263 use std::os::unix::fs::MetadataExt;
264 if !crate::util::owned_by_us(&meta) {
265 return check(
266 section,
267 Level::Fail,
268 format!(
269 "cache root owned by uid {} (not uid {}) — never share a mutable vault between users",
270 meta.uid(),
271 crate::util::euid()
272 ),
273 );
274 }
275 if meta.mode() & 0o077 != 0 {
276 return check(
277 section,
278 Level::Fail,
279 format!(
280 "cache root mode {:o} is accessible by other users — run `chmod 700 {}`",
281 meta.mode() & 0o777,
282 root.display()
283 ),
284 );
285 }
286 }
287 #[cfg(windows)]
288 {
289 if let Err(err) = crate::util::ensure_windows_user_path(root, "cache root") {
290 return check(section, Level::Fail, format!("{err:#}"));
291 }
292 return check(
293 section,
294 Level::Warn,
295 "cache root is under %USERPROFILE%, but Windows ACLs are not inspected — single-user cache only",
296 );
297 }
298 #[cfg(not(windows))]
299 check(section, Level::Pass, "cache root is private (owner-only)")
300}
301
302fn check_ancestor_configs() -> Vec<Check> {
306 let section: &'static str = SEC_CONFIG;
307 let mut out = Vec::new();
308 let home_cfg = crate::config::cargo_config_path().ok();
309 let mut dir = match std::env::current_dir() {
310 Ok(dir) => dir,
311 Err(_) => return out,
312 };
313
314 let mut found = false;
315 loop {
316 for name in ["config.toml", "config"] {
317 let candidate = dir.join(".cargo").join(name);
318 let is_home = home_cfg.as_ref().map_or(false, |h| *h == candidate)
319 || home_cfg
320 .as_ref()
321 .and_then(|h| h.parent())
322 .map(|p| p.join("config") == candidate)
323 .unwrap_or(false);
324 if !candidate.exists() || is_home {
325 continue;
326 }
327 found = true;
328 for key in ["build-dir", "rustc-wrapper"] {
329 let Some(ancestor) = crate::config::read_build_table_key(&candidate, key) else {
330 continue; };
332 let home_value = crate::config::cargo_configured(key).ok().flatten();
333 if home_value.as_deref() != Some(ancestor.as_str()) {
334 out.push(check(
335 section,
336 Level::Warn,
337 format!(
338 "{} sets {key} = '{ancestor}' differently from the global config \
339 (cargo applies it to builds) — epicenter ignores it for its own state",
340 candidate.display()
341 ),
342 ));
343 } else {
344 out.push(check(
345 section,
346 Level::Pass,
347 format!(
348 "{} sets {key} = '{ancestor}', matching the global config",
349 candidate.display()
350 ),
351 ));
352 }
353 }
354 }
355 match dir.parent() {
356 Some(parent) => dir = parent.to_path_buf(),
357 None => break,
358 }
359 }
360
361 if !found {
362 out.push(check(
363 section,
364 Level::Pass,
365 "no ancestor .cargo/config(.toml) shadows the global config",
366 ));
367 }
368 out
369}
370
371fn check_rustc_wrapper() -> Vec<Check> {
376 let section: &'static str = SEC_WRAPPER;
377 let mut out = Vec::new();
378
379 for var in ["RUSTC_WRAPPER", "CARGO_BUILD_RUSTC_WRAPPER"] {
380 if let Some(value) = std::env::var_os(var) {
381 out.push(check(
382 section,
383 Level::Warn,
384 format!(
385 "{var}={} — environment wins over config files",
386 value.to_string_lossy()
387 ),
388 ));
389 }
390 }
391
392 let configured = crate::config::cargo_configured("rustc-wrapper");
393 match configured {
394 Err(e) => out.push(check(section, Level::Warn, format!("could not read the cargo config: {e:#}"))),
395 Ok(None) => out.push(check(section,
396 Level::Info,
397 "no rustc-wrapper configured — kache/sccache are optional layers (`epicenter setup --with-wrapper kache`)",
398 )),
399 Ok(Some(name)) => match crate::util::which_path(&name) {
400 None => out.push(check(section,
401 Level::Warn,
402 format!(
403 "rustc-wrapper '{name}' is configured but not found in PATH or $CARGO_HOME/bin — \
404 builds fail until it is installed or removed (`epicenter setup --without-wrapper`)"
405 ),
406 )),
407 Some(path) => {
408 out.push(check(section,
409 Level::Pass,
410 format!("rustc-wrapper '{name}' discovered: {}", path.display()),
411 ));
412 if !crate::util::on_path(&name) {
413 out.push(check(section,
414 Level::Warn,
415 format!(
416 "'{name}' lives in the cargo bin dir but NOT on PATH — cargo spawns \
417 the wrapper by NAME at build time, so add the bin dir to PATH"
418 ),
419 ));
420 }
421 }
422 },
423 }
424 out
425}
426
427fn check_exdev(root: &Path) -> Option<Check> {
430 let section: &'static str = SEC_FS;
431 let target = std::env::current_dir().ok()?.join("target");
432 if !target.is_dir() {
433 return Some(check(
434 section,
435 Level::Info,
436 "no ./target next to this invocation — filesystem-match check skipped",
437 ));
438 }
439 match (device_id(&target), device_id(root)) {
440 (Some(target_dev), Some(root_dev)) if target_dev != root_dev => Some(check(
441 section,
442 Level::Warn,
443 format!(
444 "cache filesystem differs from ./target/ ({} vs {}) — final artifacts are \
445 copied per project, never hardlinked (expected when projects live on /mnt)",
446 root.display(),
447 target.display()
448 ),
449 )),
450 (Some(_), Some(_)) => Some(check(
451 section,
452 Level::Pass,
453 "cache and ./target/ share one filesystem — hardlink reuse is possible",
454 )),
455 _ => Some(check(
456 section,
457 Level::Info,
458 "filesystem identity not measurable on this platform",
459 )),
460 }
461}
462
463#[cfg(unix)]
464fn device_id(path: &Path) -> Option<u64> {
465 std::fs::metadata(path)
466 .ok()
467 .map(|meta| std::os::unix::fs::MetadataExt::dev(&meta))
468}
469
470#[cfg(not(unix))]
471fn device_id(path: &Path) -> Option<u64> {
472 use std::collections::hash_map::DefaultHasher;
479 use std::hash::{Hash, Hasher};
480
481 let canonical = path.canonicalize().ok()?;
482 let text = canonical.to_string_lossy();
483 let key = strip_volume_prefix(&text)?;
484 let mut hasher = DefaultHasher::new();
486 key.hash(&mut hasher);
487 Some(hasher.finish())
488}
489
490#[cfg(not(unix))]
492fn strip_volume_prefix(canonical: &str) -> Option<String> {
493 let rest = canonical.strip_prefix(r"\\?\")?;
494 let mut chars = rest.chars();
495 let (_first, second) = (chars.next()?, chars.next()?);
496 if second == ':' {
497 Some(rest[..2].to_string()) } else if rest.starts_with("UNC\\") {
499 let unc = &rest["UNC\\".len()..];
500 let mut segments = unc.split('\\');
501 let server = segments.next()?;
502 let share = segments.next()?;
503 Some(format!(r"\\{server}\{share}"))
504 } else {
505 None
506 }
507}
508
509fn check_rustc() -> Check {
511 let section: &'static str = SEC_TC;
512 let output = crate::util::command_for("rustc")
513 .and_then(|mut command| Ok(command.arg("--version").output()?));
514 match output {
515 Ok(out) if out.status.success() => {
516 let version = String::from_utf8_lossy(&out.stdout).trim().to_string();
517 check(
518 section,
519 Level::Info,
520 format!(
521 "{version} — after toolchain upgrades, run `epicenter prune` to drop old artifacts"
522 ),
523 )
524 }
525 _ => check(section, Level::Warn, "could not run `rustc --version`"),
526 }
527}
528
529fn check_toolchain_generation(rows: &[(Unit, ArtifactInfo)]) -> Vec<Check> {
539 if rows.is_empty() {
540 return vec![check(
541 SEC_TC,
542 Level::Info,
543 "shared cache holds no fingerprint units yet",
544 )];
545 }
546
547 let newest_art = rows.iter().filter_map(|(_, arts)| arts.newest).max();
548 let active_gen = rows
550 .iter()
551 .filter(|(_, arts)| arts.newest == newest_art)
552 .find_map(|(unit, _)| unit.json.as_ref().map(|json| json.rustc.clone()))
553 .unwrap_or_else(|| "unknown".into());
554
555 let mut checks = Vec::new();
556
557 match (newest_art, toolchain_mtime()) {
558 (Some(newest), Some(toolchain)) if newest < toolchain => checks.push(check(
559 SEC_TC,
560 Level::Fail,
561 format!(
562 "installed toolchain is newer than the newest cached unit ({}): the cache is \
563 stale — the next build recompiles it; outdated units can be reclaimed below",
564 human_age(newest)
565 ),
566 )),
567 (Some(newest), Some(_)) => checks.push(check(
568 SEC_TC,
569 Level::Pass,
570 format!(
571 "cache is aligned with the installed toolchain (newest unit {})",
572 human_age(newest)
573 ),
574 )),
575 _ => checks.push(check(
576 SEC_TC,
577 Level::Info,
578 "toolchain install time could not be read (`rustc --print sysroot` failed)",
579 )),
580 }
581
582 let stale: Vec<&(crate::fingerprint::Unit, crate::fingerprint::ArtifactInfo)> = rows
583 .iter()
584 .filter(|(unit, _)| who::rustc_of(unit) != active_gen)
585 .collect();
586 if !stale.is_empty() {
587 let stale_bytes: u64 = stale.iter().map(|(_, arts)| arts.bytes).sum();
588 checks.push(check(
589 SEC_TC,
590 Level::Warn,
591 format!(
592 "{} unit(s) on older toolchain generations ({}) — reclaim with `epicenter evict --stale`",
593 stale.len(),
594 human_bytes(stale_bytes)
595 ),
596 ));
597 }
598 checks
599}
600
601fn check_mtime_ordering(rows: &[(Unit, ArtifactInfo)]) -> Vec<Check> {
607 if rows.is_empty() {
608 return Vec::new();
609 }
610 let units: Vec<Unit> = rows.iter().map(|(unit, _)| unit.clone()).collect();
611 let edges = crate::fingerprint::dep_edges(&units);
612
613 let mut checked = 0usize;
614 let mut violations: Vec<(usize, usize)> = Vec::new();
615 for (dependent, deps) in edges.iter().enumerate() {
616 let own = rows[dependent].1.newest;
617 for &dep in deps {
618 checked += 1;
619 let (Some(own), Some(dep_newest)) = (own, rows[dep].1.newest) else {
620 continue;
621 };
622 if dep_newest > own {
623 violations.push((dependent, dep));
624 }
625 }
626 }
627
628 if violations.is_empty() {
629 return vec![check(
630 SEC_MTIME,
631 Level::Pass,
632 format!(
633 "every unit is at least as new as its dependencies ({checked} edge(s) checked)"
634 ),
635 )];
636 }
637
638 let mut checks = vec![check(
639 SEC_MTIME,
640 Level::Warn,
641 format!(
642 "{} unit(s) recompile once — a dependency is newer ({} edge(s) checked)",
643 violations.len(),
644 checked
645 ),
646 )];
647 for (dependent, dep) in violations.iter().take(3) {
648 let own = rows[*dependent].1.newest.expect("violation has timestamps");
649 let dep_newest = rows[*dep].1.newest.expect("violation has timestamps");
650 checks.push(check(
651 SEC_MTIME,
652 Level::Info,
653 format!(
654 "{} ← {} is {} newer (cargo: \"the dependency was rebuilt\")",
655 units[*dependent].dir_name,
656 units[*dep].dir_name,
657 human_duration(dep_newest.duration_since(own).unwrap_or_default())
658 ),
659 ));
660 }
661 if violations.len() > 3 {
662 checks.push(check(
663 SEC_MTIME,
664 Level::Info,
665 format!("… and {} more", violations.len() - 3),
666 ));
667 }
668 checks
669}
670
671fn check_locking() -> Check {
673 let env_on = std::env::var("CARGO_UNSTABLE_FINE_GRAIN_LOCKING")
674 .map(|value| is_truthy(&value))
675 .unwrap_or(false);
676 let config_on = crate::config::cargo_configured_in("unstable", "fine-grain-locking")
677 .ok()
678 .flatten()
679 .map(|value| is_truthy(&value))
680 .unwrap_or(false);
681
682 if env_on || config_on {
683 return check(
684 SEC_TC,
685 Level::Pass,
686 "fine-grain locking enabled — concurrent builds on the shared build-dir overlap \
687 instead of serializing",
688 );
689 }
690 if toolchain_is_nightly() {
691 check(
692 SEC_TC,
693 Level::Warn,
694 "fine-grain locking is available but OFF — concurrent builds serialize on the \
695 shared build-dir; enable with `[unstable] fine-grain-locking = true` in \
696 ~/.cargo/config.toml",
697 )
698 } else {
699 check(
700 SEC_TC,
701 Level::Info,
702 "one file lock guards the shared build-dir: concurrent builds serialize; \
703 fine-grain locking (`-Z fine-grain-locking`) is nightly-only",
704 )
705 }
706}
707
708fn is_truthy(value: &str) -> bool {
709 matches!(
710 value.trim().to_ascii_lowercase().as_str(),
711 "1" | "true" | "yes" | "on"
712 )
713}
714
715fn toolchain_mtime() -> Option<SystemTime> {
719 crate::vault::toolchain_install_time()
720}
721
722fn toolchain_is_nightly() -> bool {
723 crate::util::command_for("rustc")
724 .and_then(|mut command| Ok(command.arg("--version").output()?))
725 .ok()
726 .map(|out| String::from_utf8_lossy(&out.stdout).contains("nightly"))
727 .unwrap_or(false)
728}
729
730#[cfg(test)]
731mod tests {
732 use super::*;
733 use std::fs;
734 use std::time::Duration;
735
736 #[test]
737 fn truthiness_parsing() {
738 assert!(is_truthy("true"));
739 assert!(is_truthy("1"));
740 assert!(is_truthy(" YES "));
741 assert!(is_truthy("On"));
742 assert!(!is_truthy("false"));
743 assert!(!is_truthy("0"));
744 assert!(!is_truthy(""));
745 assert!(!is_truthy("nightly"));
746 }
747
748 #[test]
749 fn flags_older_toolchain_generations_for_reclaim() -> Result<()> {
750 let root = tempfile::tempdir()?;
753 let make_unit = |pkg: &str, hex: &str, rustc: &str, artifact: &str| -> Result<()> {
754 let dir = root
755 .path()
756 .join("debug/.fingerprint")
757 .join(format!("{pkg}-{hex}"));
758 fs::create_dir_all(&dir)?;
759 fs::write(dir.join(format!("lib-{pkg}")), "0011223344556677")?;
760 fs::write(
761 dir.join(format!("lib-{pkg}.json")),
762 format!(
763 r#"{{"rustc":{rustc},"features":"[]","declared_features":"[]","deps":[]}}"#
764 ),
765 )?;
766 fs::create_dir_all(root.path().join("debug/deps"))?;
767 fs::write(root.path().join("debug/deps").join(artifact), [0u8; 8])?;
768 Ok(())
769 };
770 make_unit(
771 "oldcrate",
772 "aaaaaaaaaaaaaaaa",
773 "111",
774 "liboldcrate-aaaaaaaaaaaaaaaa.rlib",
775 )?;
776 std::thread::sleep(std::time::Duration::from_millis(20));
777 make_unit(
778 "newcrate",
779 "bbbbbbbbbbbbbbbb",
780 "222",
781 "libnewcrate-bbbbbbbbbbbbbbbb.rlib",
782 )?;
783
784 let rows = who::collect(root.path())?;
785 let checks = check_toolchain_generation(&rows);
786 let reclaim = checks
787 .iter()
788 .find(|c| c.message.contains("older toolchain generations"))
789 .expect("reclaim warning expected");
790 assert_eq!(reclaim.level as u8, Level::Warn as u8);
791 assert!(
792 reclaim.message.contains("1 unit(s)"),
793 "got: {}",
794 reclaim.message
795 );
796 Ok(())
797 }
798
799 fn edge_build_dir(root: &Path, dep_at: u64, unit_at: u64) -> Result<()> {
803 let stamp = |path: &Path, secs: u64| -> Result<()> {
804 let time = SystemTime::UNIX_EPOCH + Duration::from_secs(secs);
805 fs::File::options()
806 .write(true)
807 .open(path)?
808 .set_modified(time)?;
809 Ok(())
810 };
811 fs::create_dir_all(root.join("debug/deps"))?;
812
813 let core = root.join("debug/.fingerprint/serde_core-b468be6fa8c1f936");
814 fs::create_dir_all(&core)?;
815 fs::write(core.join("lib-serde_core"), "71a9f323efa5ae82")?;
816 let core_rlib = root.join("debug/deps/libserde_core-b468be6fa8c1f936.rlib");
817 fs::write(&core_rlib, [1u8; 32])?;
818 stamp(&core_rlib, dep_at)?;
819
820 let unit = root.join("debug/.fingerprint/serde-746d173581f1a314");
821 fs::create_dir_all(&unit)?;
822 fs::write(unit.join("lib-serde"), "5d82713a7f2d05e2")?;
823 fs::write(
824 unit.join("lib-serde.json"),
825 r#"{"rustc":5244445300240561903,"features":"[]","declared_features":"[]","deps":[[11029742160753049355,"serde_core",false,9416646317397223793]]}"#,
826 )?;
827 let unit_rlib = root.join("debug/deps/libserde-746d173581f1a314.rlib");
828 fs::write(&unit_rlib, [1u8; 32])?;
829 stamp(&unit_rlib, unit_at)?;
830 Ok(())
831 }
832
833 #[test]
834 fn flags_units_older_than_their_dependencies() -> Result<()> {
835 let root = tempfile::tempdir()?;
836 edge_build_dir(root.path(), 1_700_000_600, 1_700_000_000)?;
837 let rows = who::collect(root.path())?;
838 let checks = check_mtime_ordering(&rows);
839 assert!(
840 matches!(checks[0].level, Level::Warn),
841 "{:?}",
842 checks[0].message
843 );
844 assert!(
845 checks[0].message.contains("recompile once"),
846 "got: {}",
847 checks[0].message
848 );
849 assert_eq!(checks.len(), 2, "one example follows the count");
850 assert!(checks[1].message.contains("serde-746d173581f1a314"));
851 assert!(checks[1].message.contains("serde_core-b468be6fa8c1f936"));
852 Ok(())
853 }
854
855 #[test]
856 fn ordering_passes_when_units_are_current() -> Result<()> {
857 let root = tempfile::tempdir()?;
858 edge_build_dir(root.path(), 1_700_000_000, 1_700_000_600)?;
859 let rows = who::collect(root.path())?;
860 let checks = check_mtime_ordering(&rows);
861 assert!(matches!(checks[0].level, Level::Pass));
862 assert!(
863 checks[0].message.contains("at least as new"),
864 "got: {}",
865 checks[0].message
866 );
867 assert_eq!(checks.len(), 1);
868 Ok(())
869 }
870
871 #[test]
872 fn staleness_requires_both_timestamps() {
873 let now = SystemTime::now();
874 let older = now - Duration::from_secs(60);
875 assert!(older < now, "sanity");
876 assert!(!(older >= now));
877 assert!(now > older);
878 }
879}