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::config::cargo_configured("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() || !meta.is_dir() {
255 return check(
256 section,
257 Level::Fail,
258 "cache root is a symlink 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 check(section, Level::Pass, "cache root is private (owner-only)")
288}
289
290fn check_ancestor_configs() -> Vec<Check> {
294 let section: &'static str = SEC_CONFIG;
295 let mut out = Vec::new();
296 let home_cfg = crate::config::cargo_config_path().ok();
297 let mut dir = match std::env::current_dir() {
298 Ok(dir) => dir,
299 Err(_) => return out,
300 };
301
302 let mut found = false;
303 loop {
304 let candidate = dir.join(".cargo").join("config.toml");
305 let is_home = home_cfg.as_ref().map_or(false, |h| *h == candidate);
306 if candidate.exists() && !is_home {
307 found = true;
308 for key in ["build-dir", "rustc-wrapper"] {
309 let Some(ancestor) = crate::config::read_build_table_key(&candidate, key) else {
310 continue; };
312 let home_value = crate::config::cargo_configured(key).ok().flatten();
313 if home_value.as_deref() != Some(ancestor.as_str()) {
314 out.push(check(
315 section,
316 Level::Fail,
317 format!(
318 "{} sets {key} = '{ancestor}' differently from the global config \
319 (deep config wins per-key) — epicenter refuses project-controlled state",
320 candidate.display()
321 ),
322 ));
323 } else {
324 out.push(check(
325 section,
326 Level::Pass,
327 format!(
328 "{} sets {key} = '{ancestor}', matching the global config",
329 candidate.display()
330 ),
331 ));
332 }
333 }
334 }
335 match dir.parent() {
336 Some(parent) => dir = parent.to_path_buf(),
337 None => break,
338 }
339 }
340
341 if !found {
342 out.push(check(
343 section,
344 Level::Pass,
345 "no ancestor .cargo/config.toml shadows the global config",
346 ));
347 }
348 out
349}
350
351fn check_rustc_wrapper() -> Vec<Check> {
356 let section: &'static str = SEC_WRAPPER;
357 let mut out = Vec::new();
358
359 for var in ["RUSTC_WRAPPER", "CARGO_BUILD_RUSTC_WRAPPER"] {
360 if let Some(value) = std::env::var_os(var) {
361 out.push(check(
362 section,
363 Level::Warn,
364 format!(
365 "{var}={} — environment wins over config files",
366 value.to_string_lossy()
367 ),
368 ));
369 }
370 }
371
372 let configured = crate::config::cargo_configured("rustc-wrapper");
373 match configured {
374 Err(e) => out.push(check(section, Level::Warn, format!("could not read the cargo config: {e:#}"))),
375 Ok(None) => out.push(check(section,
376 Level::Info,
377 "no rustc-wrapper configured — kache/sccache are optional layers (`epicenter setup --with-wrapper kache`)",
378 )),
379 Ok(Some(name)) => match crate::util::which_path(&name) {
380 None => out.push(check(section,
381 Level::Warn,
382 format!(
383 "rustc-wrapper '{name}' is configured but not found in PATH or $CARGO_HOME/bin — \
384 builds fail until it is installed or removed (`epicenter setup --without-wrapper`)"
385 ),
386 )),
387 Some(path) => {
388 out.push(check(section,
389 Level::Pass,
390 format!("rustc-wrapper '{name}' discovered: {}", path.display()),
391 ));
392 if !crate::util::on_path(&name) {
393 out.push(check(section,
394 Level::Warn,
395 format!(
396 "'{name}' lives in the cargo bin dir but NOT on PATH — cargo spawns \
397 the wrapper by NAME at build time, so add the bin dir to PATH"
398 ),
399 ));
400 }
401 }
402 },
403 }
404 out
405}
406
407fn check_exdev(root: &Path) -> Option<Check> {
410 let section: &'static str = SEC_FS;
411 let target = std::env::current_dir().ok()?.join("target");
412 if !target.is_dir() {
413 return Some(check(
414 section,
415 Level::Info,
416 "no ./target next to this invocation — filesystem-match check skipped",
417 ));
418 }
419 match (device_id(&target), device_id(root)) {
420 (Some(target_dev), Some(root_dev)) if target_dev != root_dev => Some(check(
421 section,
422 Level::Warn,
423 format!(
424 "cache filesystem differs from ./target/ ({} vs {}) — final artifacts are \
425 copied per project, never hardlinked (expected when projects live on /mnt)",
426 root.display(),
427 target.display()
428 ),
429 )),
430 (Some(_), Some(_)) => Some(check(
431 section,
432 Level::Pass,
433 "cache and ./target/ share one filesystem — hardlink reuse is possible",
434 )),
435 _ => Some(check(
436 section,
437 Level::Info,
438 "filesystem identity not measurable on this platform",
439 )),
440 }
441}
442
443#[cfg(unix)]
444fn device_id(path: &Path) -> Option<u64> {
445 std::fs::metadata(path)
446 .ok()
447 .map(|meta| std::os::unix::fs::MetadataExt::dev(&meta))
448}
449
450#[cfg(not(unix))]
451fn device_id(path: &Path) -> Option<u64> {
452 use std::collections::hash_map::DefaultHasher;
459 use std::hash::{Hash, Hasher};
460
461 let canonical = path.canonicalize().ok()?;
462 let text = canonical.to_string_lossy();
463 let key = strip_volume_prefix(&text)?;
464 let mut hasher = DefaultHasher::new();
466 key.hash(&mut hasher);
467 Some(hasher.finish())
468}
469
470#[cfg(not(unix))]
472fn strip_volume_prefix(canonical: &str) -> Option<String> {
473 let rest = canonical.strip_prefix(r"\\?\")?;
474 let mut chars = rest.chars();
475 let (first, second) = (chars.next()?, chars.next()?);
476 if second == ':' {
477 Some(rest[..2].to_string()) } else if rest.starts_with("UNC\\") {
479 let unc = &rest["UNC\\".len()..];
480 let mut segments = unc.split('\\');
481 let server = segments.next()?;
482 let share = segments.next()?;
483 Some(format!(r"\\{server}\{share}"))
484 } else {
485 None
486 }
487}
488
489fn check_rustc() -> Check {
491 let section: &'static str = SEC_TC;
492 let output = crate::util::command_for("rustc")
493 .and_then(|mut command| Ok(command.arg("--version").output()?));
494 match output {
495 Ok(out) if out.status.success() => {
496 let version = String::from_utf8_lossy(&out.stdout).trim().to_string();
497 check(
498 section,
499 Level::Info,
500 format!(
501 "{version} — after toolchain upgrades, run `epicenter prune` to drop old artifacts"
502 ),
503 )
504 }
505 _ => check(section, Level::Warn, "could not run `rustc --version`"),
506 }
507}
508
509fn check_toolchain_generation(rows: &[(Unit, ArtifactInfo)]) -> Vec<Check> {
519 if rows.is_empty() {
520 return vec![check(
521 SEC_TC,
522 Level::Info,
523 "shared cache holds no fingerprint units yet",
524 )];
525 }
526
527 let newest_art = rows.iter().filter_map(|(_, arts)| arts.newest).max();
528 let active_gen = rows
530 .iter()
531 .filter(|(_, arts)| arts.newest == newest_art)
532 .find_map(|(unit, _)| unit.json.as_ref().map(|json| json.rustc.clone()))
533 .unwrap_or_else(|| "unknown".into());
534
535 let mut checks = Vec::new();
536
537 match (newest_art, toolchain_mtime()) {
538 (Some(newest), Some(toolchain)) if newest < toolchain => checks.push(check(
539 SEC_TC,
540 Level::Fail,
541 format!(
542 "installed toolchain is newer than the newest cached unit ({}): the cache is \
543 stale — the next build recompiles it; outdated units can be reclaimed below",
544 human_age(newest)
545 ),
546 )),
547 (Some(newest), Some(_)) => checks.push(check(
548 SEC_TC,
549 Level::Pass,
550 format!(
551 "cache is aligned with the installed toolchain (newest unit {})",
552 human_age(newest)
553 ),
554 )),
555 _ => checks.push(check(
556 SEC_TC,
557 Level::Info,
558 "toolchain install time could not be read (`rustc --print sysroot` failed)",
559 )),
560 }
561
562 let stale: Vec<&(crate::fingerprint::Unit, crate::fingerprint::ArtifactInfo)> = rows
563 .iter()
564 .filter(|(unit, _)| who::rustc_of(unit) != active_gen)
565 .collect();
566 if !stale.is_empty() {
567 let stale_bytes: u64 = stale.iter().map(|(_, arts)| arts.bytes).sum();
568 checks.push(check(
569 SEC_TC,
570 Level::Warn,
571 format!(
572 "{} unit(s) on older toolchain generations ({}) — reclaim with `epicenter evict --stale`",
573 stale.len(),
574 human_bytes(stale_bytes)
575 ),
576 ));
577 }
578 checks
579}
580
581fn check_mtime_ordering(rows: &[(Unit, ArtifactInfo)]) -> Vec<Check> {
587 if rows.is_empty() {
588 return Vec::new();
589 }
590 let units: Vec<Unit> = rows.iter().map(|(unit, _)| unit.clone()).collect();
591 let edges = crate::fingerprint::dep_edges(&units);
592
593 let mut checked = 0usize;
594 let mut violations: Vec<(usize, usize)> = Vec::new();
595 for (dependent, deps) in edges.iter().enumerate() {
596 let own = rows[dependent].1.newest;
597 for &dep in deps {
598 checked += 1;
599 let (Some(own), Some(dep_newest)) = (own, rows[dep].1.newest) else {
600 continue;
601 };
602 if dep_newest > own {
603 violations.push((dependent, dep));
604 }
605 }
606 }
607
608 if violations.is_empty() {
609 return vec![check(
610 SEC_MTIME,
611 Level::Pass,
612 format!(
613 "every unit is at least as new as its dependencies ({checked} edge(s) checked)"
614 ),
615 )];
616 }
617
618 let mut checks = vec![check(
619 SEC_MTIME,
620 Level::Warn,
621 format!(
622 "{} unit(s) recompile once — a dependency is newer ({} edge(s) checked)",
623 violations.len(),
624 checked
625 ),
626 )];
627 for (dependent, dep) in violations.iter().take(3) {
628 let own = rows[*dependent].1.newest.expect("violation has timestamps");
629 let dep_newest = rows[*dep].1.newest.expect("violation has timestamps");
630 checks.push(check(
631 SEC_MTIME,
632 Level::Info,
633 format!(
634 "{} ← {} is {} newer (cargo: \"the dependency was rebuilt\")",
635 units[*dependent].dir_name,
636 units[*dep].dir_name,
637 human_duration(dep_newest.duration_since(own).unwrap_or_default())
638 ),
639 ));
640 }
641 if violations.len() > 3 {
642 checks.push(check(
643 SEC_MTIME,
644 Level::Info,
645 format!("… and {} more", violations.len() - 3),
646 ));
647 }
648 checks
649}
650
651fn check_locking() -> Check {
653 let env_on = std::env::var("CARGO_UNSTABLE_FINE_GRAIN_LOCKING")
654 .map(|value| is_truthy(&value))
655 .unwrap_or(false);
656 let config_on = crate::config::cargo_configured_in("unstable", "fine-grain-locking")
657 .ok()
658 .flatten()
659 .map(|value| is_truthy(&value))
660 .unwrap_or(false);
661
662 if env_on || config_on {
663 return check(
664 SEC_TC,
665 Level::Pass,
666 "fine-grain locking enabled — concurrent builds on the shared build-dir overlap \
667 instead of serializing",
668 );
669 }
670 if toolchain_is_nightly() {
671 check(
672 SEC_TC,
673 Level::Warn,
674 "fine-grain locking is available but OFF — concurrent builds serialize on the \
675 shared build-dir; enable with `[unstable] fine-grain-locking = true` in \
676 ~/.cargo/config.toml",
677 )
678 } else {
679 check(
680 SEC_TC,
681 Level::Info,
682 "one file lock guards the shared build-dir: concurrent builds serialize; \
683 fine-grain locking (`-Z fine-grain-locking`) is nightly-only",
684 )
685 }
686}
687
688fn is_truthy(value: &str) -> bool {
689 matches!(
690 value.trim().to_ascii_lowercase().as_str(),
691 "1" | "true" | "yes" | "on"
692 )
693}
694
695fn toolchain_mtime() -> Option<SystemTime> {
699 crate::vault::toolchain_install_time()
700}
701
702fn toolchain_is_nightly() -> bool {
703 crate::util::command_for("rustc")
704 .and_then(|mut command| Ok(command.arg("--version").output()?))
705 .ok()
706 .map(|out| String::from_utf8_lossy(&out.stdout).contains("nightly"))
707 .unwrap_or(false)
708}
709
710#[cfg(test)]
711mod tests {
712 use super::*;
713 use std::fs;
714 use std::time::Duration;
715
716 #[test]
717 fn truthiness_parsing() {
718 assert!(is_truthy("true"));
719 assert!(is_truthy("1"));
720 assert!(is_truthy(" YES "));
721 assert!(is_truthy("On"));
722 assert!(!is_truthy("false"));
723 assert!(!is_truthy("0"));
724 assert!(!is_truthy(""));
725 assert!(!is_truthy("nightly"));
726 }
727
728 #[test]
729 fn flags_older_toolchain_generations_for_reclaim() -> Result<()> {
730 let root = tempfile::tempdir()?;
733 let make_unit = |pkg: &str, hex: &str, rustc: &str, artifact: &str| -> Result<()> {
734 let dir = root
735 .path()
736 .join("debug/.fingerprint")
737 .join(format!("{pkg}-{hex}"));
738 fs::create_dir_all(&dir)?;
739 fs::write(dir.join(format!("lib-{pkg}")), "0011223344556677")?;
740 fs::write(
741 dir.join(format!("lib-{pkg}.json")),
742 format!(
743 r#"{{"rustc":{rustc},"features":"[]","declared_features":"[]","deps":[]}}"#
744 ),
745 )?;
746 fs::create_dir_all(root.path().join("debug/deps"))?;
747 fs::write(root.path().join("debug/deps").join(artifact), [0u8; 8])?;
748 Ok(())
749 };
750 make_unit(
751 "oldcrate",
752 "aaaaaaaaaaaaaaaa",
753 "111",
754 "liboldcrate-aaaaaaaaaaaaaaaa.rlib",
755 )?;
756 std::thread::sleep(std::time::Duration::from_millis(20));
757 make_unit(
758 "newcrate",
759 "bbbbbbbbbbbbbbbb",
760 "222",
761 "libnewcrate-bbbbbbbbbbbbbbbb.rlib",
762 )?;
763
764 let rows = who::collect(root.path())?;
765 let checks = check_toolchain_generation(&rows);
766 let reclaim = checks
767 .iter()
768 .find(|c| c.message.contains("older toolchain generations"))
769 .expect("reclaim warning expected");
770 assert_eq!(reclaim.level as u8, Level::Warn as u8);
771 assert!(
772 reclaim.message.contains("1 unit(s)"),
773 "got: {}",
774 reclaim.message
775 );
776 Ok(())
777 }
778
779 fn edge_build_dir(root: &Path, dep_at: u64, unit_at: u64) -> Result<()> {
783 let stamp = |path: &Path, secs: u64| -> Result<()> {
784 let time = SystemTime::UNIX_EPOCH + Duration::from_secs(secs);
785 fs::File::options()
786 .write(true)
787 .open(path)?
788 .set_modified(time)?;
789 Ok(())
790 };
791 fs::create_dir_all(root.join("debug/deps"))?;
792
793 let core = root.join("debug/.fingerprint/serde_core-b468be6fa8c1f936");
794 fs::create_dir_all(&core)?;
795 fs::write(core.join("lib-serde_core"), "71a9f323efa5ae82")?;
796 let core_rlib = root.join("debug/deps/libserde_core-b468be6fa8c1f936.rlib");
797 fs::write(&core_rlib, [1u8; 32])?;
798 stamp(&core_rlib, dep_at)?;
799
800 let unit = root.join("debug/.fingerprint/serde-746d173581f1a314");
801 fs::create_dir_all(&unit)?;
802 fs::write(unit.join("lib-serde"), "5d82713a7f2d05e2")?;
803 fs::write(
804 unit.join("lib-serde.json"),
805 r#"{"rustc":5244445300240561903,"features":"[]","declared_features":"[]","deps":[[11029742160753049355,"serde_core",false,9416646317397223793]]}"#,
806 )?;
807 let unit_rlib = root.join("debug/deps/libserde-746d173581f1a314.rlib");
808 fs::write(&unit_rlib, [1u8; 32])?;
809 stamp(&unit_rlib, unit_at)?;
810 Ok(())
811 }
812
813 #[test]
814 fn flags_units_older_than_their_dependencies() -> Result<()> {
815 let root = tempfile::tempdir()?;
816 edge_build_dir(root.path(), 1_700_000_600, 1_700_000_000)?;
817 let rows = who::collect(root.path())?;
818 let checks = check_mtime_ordering(&rows);
819 assert!(
820 matches!(checks[0].level, Level::Warn),
821 "{:?}",
822 checks[0].message
823 );
824 assert!(
825 checks[0].message.contains("recompile once"),
826 "got: {}",
827 checks[0].message
828 );
829 assert_eq!(checks.len(), 2, "one example follows the count");
830 assert!(checks[1].message.contains("serde-746d173581f1a314"));
831 assert!(checks[1].message.contains("serde_core-b468be6fa8c1f936"));
832 Ok(())
833 }
834
835 #[test]
836 fn ordering_passes_when_units_are_current() -> Result<()> {
837 let root = tempfile::tempdir()?;
838 edge_build_dir(root.path(), 1_700_000_000, 1_700_000_600)?;
839 let rows = who::collect(root.path())?;
840 let checks = check_mtime_ordering(&rows);
841 assert!(matches!(checks[0].level, Level::Pass));
842 assert!(
843 checks[0].message.contains("at least as new"),
844 "got: {}",
845 checks[0].message
846 );
847 assert_eq!(checks.len(), 1);
848 Ok(())
849 }
850
851 #[test]
852 fn staleness_requires_both_timestamps() {
853 let now = SystemTime::now();
854 let older = now - Duration::from_secs(60);
855 assert!(older < now, "sanity");
856 assert!(!(older >= now));
857 assert!(now > older);
858 }
859}