1use std::path::{Path, PathBuf};
34
35use super::data_dir::{ensure_dir_permissions, has_data_files, lean_ctx_data_dir};
36
37fn env_path(name: &str) -> Option<PathBuf> {
39 std::env::var(name)
40 .ok()
41 .map(|v| v.trim().to_string())
42 .filter(|v| !v.is_empty())
43 .map(PathBuf::from)
44}
45
46const DIR_OVERRIDES: [&str; 4] = [
49 "LEAN_CTX_CONFIG_DIR",
50 "LEAN_CTX_DATA_DIR",
51 "LEAN_CTX_STATE_DIR",
52 "LEAN_CTX_CACHE_DIR",
53];
54
55#[must_use]
60pub(crate) fn dir_override_active() -> bool {
61 DIR_OVERRIDES.iter().any(|name| env_path(name).is_some())
62}
63
64pub(crate) fn xdg_base(env_name: &str, home_fallback: &str) -> Result<PathBuf, String> {
68 if let Some(p) = env_path(env_name) {
69 return Ok(p);
70 }
71 dirs::home_dir()
72 .map(|h| h.join(home_fallback))
73 .ok_or_else(|| "Cannot determine home directory".to_string())
74}
75
76fn resolve(
79 category_override: Option<PathBuf>,
80 single: Option<PathBuf>,
81 xdg_base_dir: &Path,
82) -> PathBuf {
83 category_override
84 .or(single)
85 .unwrap_or_else(|| xdg_base_dir.join("lean-ctx"))
86}
87
88pub(crate) fn single_dir_override() -> Option<PathBuf> {
106 if let Some(p) = env_path("LEAN_CTX_DATA_DIR")
107 && !is_standard_xdg_data_dir(&p)
108 {
109 return Some(p);
110 }
111 let home = dirs::home_dir()?;
112 let xdg_config_base = xdg_base("XDG_CONFIG_HOME", ".config").ok()?;
113 let xdg_data_base = xdg_base("XDG_DATA_HOME", ".local/share").ok();
114 single_dir_override_fs(&home, &xdg_config_base, xdg_data_base.as_deref())
115}
116
117fn is_standard_xdg_data_dir(p: &Path) -> bool {
121 xdg_base("XDG_DATA_HOME", ".local/share")
122 .map(|base| base.join("lean-ctx"))
123 .is_ok_and(|standard| standard.as_path() == p)
124}
125
126pub fn data_pin_diverges_config(pin: &Path) -> bool {
134 !is_standard_xdg_data_dir(pin)
135}
136
137pub(crate) fn single_dir_override_fs(
139 home: &Path,
140 xdg_config_base: &Path,
141 xdg_data_base: Option<&Path>,
142) -> Option<PathBuf> {
143 if crate::core::layout_pin::is_xdg_pinned_in(xdg_config_base) {
147 return None;
148 }
149 let legacy = home.join(".lean-ctx");
150 if legacy.exists() && has_data_files(&legacy) {
151 return Some(legacy);
152 }
153 let mixed = xdg_config_base.join("lean-ctx");
154 if mixed.exists() && has_data_files(&mixed) {
155 if let Some(data_base) = xdg_data_base
162 && has_data_files(&data_base.join("lean-ctx"))
163 {
164 return None;
165 }
166 return Some(mixed);
167 }
168 None
169}
170
171fn category_dir(cat_env: &str, xdg_env: &str, home_fallback: &str) -> Result<PathBuf, String> {
173 let dir = resolve_category_dir(cat_env, xdg_env, home_fallback)?;
174 ensure_dir_permissions(&dir);
175 Ok(dir)
176}
177
178#[cfg_attr(test, allow(clippy::unnecessary_wraps))]
182fn resolve_category_dir(
183 cat_env: &str,
184 xdg_env: &str,
185 home_fallback: &str,
186) -> Result<PathBuf, String> {
187 let category_override = env_path(cat_env);
188
189 if super::data_dir::sandboxed() {
192 if let Some(p) = category_override {
193 return Ok(p);
194 }
195 return Ok(super::data_dir::test_sandbox_dir());
199 }
200 let base = xdg_base(xdg_env, home_fallback)?;
201 Ok(resolve(category_override, single_dir_override(), &base))
202}
203
204pub fn config_dir() -> Result<PathBuf, String> {
207 category_dir("LEAN_CTX_CONFIG_DIR", "XDG_CONFIG_HOME", ".config")
208}
209
210pub fn config_dir_read_only() -> Result<PathBuf, String> {
212 resolve_category_dir("LEAN_CTX_CONFIG_DIR", "XDG_CONFIG_HOME", ".config")
213}
214
215pub fn config_dir_member(sub: &str) -> Result<PathBuf, String> {
232 let canonical = config_dir()?.join(sub);
233 if legacy_adoption_allowed(super::data_dir::sandboxed(), dir_override_active())
234 && let Some(legacy_base) = dirs::config_dir()
235 {
236 adopt_legacy_config_member(&legacy_base.join("lean-ctx").join(sub), &canonical);
237 }
238 Ok(canonical)
239}
240
241fn legacy_adoption_allowed(sandboxed: bool, override_active: bool) -> bool {
244 !sandboxed && !override_active
245}
246
247fn legacy_adoption_source(legacy: &Path, canonical: &Path) -> Option<PathBuf> {
251 if canonical.exists() || legacy == canonical || !legacy.exists() {
252 return None;
253 }
254 Some(legacy.to_path_buf())
255}
256
257fn copy_tree(src: &Path, dst: &Path) -> std::io::Result<()> {
261 if src.is_dir() {
262 std::fs::create_dir_all(dst)?;
263 for entry in std::fs::read_dir(src)? {
264 let entry = entry?;
265 let child = entry.path();
266 if entry.file_type()?.is_dir() {
267 copy_tree(&child, &dst.join(entry.file_name()))?;
268 } else {
269 std::fs::copy(&child, dst.join(entry.file_name()))?;
270 }
271 }
272 } else {
273 std::fs::copy(src, dst)?;
274 }
275 Ok(())
276}
277
278fn remove_entry(path: &Path) -> std::io::Result<()> {
279 if path.is_dir() {
280 std::fs::remove_dir_all(path)
281 } else {
282 std::fs::remove_file(path)
283 }
284}
285
286fn adopt_copy(src: &Path, dst: &Path) -> std::io::Result<()> {
290 let name = dst
291 .file_name()
292 .map(|n| n.to_string_lossy().into_owned())
293 .unwrap_or_default();
294 let staging = dst.with_file_name(format!(".{name}.adopt-{}", std::process::id()));
295 if staging.exists() {
296 remove_entry(&staging)?;
297 }
298 let result = copy_tree(src, &staging).and_then(|()| std::fs::rename(&staging, dst));
299 if result.is_err() && staging.exists() {
300 let _ = remove_entry(&staging);
301 }
302 result
303}
304
305fn adopted_name(legacy: &Path) -> Option<PathBuf> {
308 let name = legacy.file_name()?.to_string_lossy().into_owned();
309 (1..=100)
310 .map(|n| {
311 let suffix = if n == 1 {
312 ".adopted".to_string()
313 } else {
314 format!(".adopted-{n}")
315 };
316 legacy.with_file_name(format!("{name}{suffix}"))
317 })
318 .find(|candidate| !candidate.exists() && candidate.symlink_metadata().is_err())
319}
320
321fn adopt_legacy_config_member(legacy: &Path, canonical: &Path) {
328 let Some(src) = legacy_adoption_source(legacy, canonical) else {
329 return;
330 };
331 if let Some(parent) = canonical.parent()
332 && std::fs::create_dir_all(parent).is_err()
333 {
334 return;
335 }
336 if adopt_copy(&src, canonical).is_err() {
337 return;
338 }
339 if let Some(retired) = adopted_name(&src) {
340 let _ = std::fs::rename(&src, retired);
341 }
342}
343
344pub fn data_dir() -> Result<PathBuf, String> {
351 lean_ctx_data_dir()
352}
353
354pub fn state_dir() -> Result<PathBuf, String> {
357 category_dir("LEAN_CTX_STATE_DIR", "XDG_STATE_HOME", ".local/state")
358}
359
360pub fn state_dir_read_only() -> Result<PathBuf, String> {
362 resolve_category_dir("LEAN_CTX_STATE_DIR", "XDG_STATE_HOME", ".local/state")
363}
364
365pub fn cache_dir() -> Result<PathBuf, String> {
368 category_dir("LEAN_CTX_CACHE_DIR", "XDG_CACHE_HOME", ".cache")
369}
370
371pub(crate) fn cache_dir_read_only() -> Result<PathBuf, String> {
373 resolve_category_dir("LEAN_CTX_CACHE_DIR", "XDG_CACHE_HOME", ".cache")
374}
375
376pub fn runtime_dir() -> Result<PathBuf, String> {
382 if let Some(base) = env_path("XDG_RUNTIME_DIR") {
383 return Ok(base.join("lean-ctx"));
384 }
385 state_dir()
386}
387
388fn raw_category_dir(cat_env: &str, xdg_env: &str, home_fallback: &str) -> Result<PathBuf, String> {
396 if let Some(p) = env_path(cat_env) {
397 return Ok(p);
398 }
399 Ok(xdg_base(xdg_env, home_fallback)?.join("lean-ctx"))
400}
401
402pub(crate) fn config_split_target() -> Result<PathBuf, String> {
404 raw_category_dir("LEAN_CTX_CONFIG_DIR", "XDG_CONFIG_HOME", ".config")
405}
406
407pub(crate) fn xdg_config_lean_ctx_dir() -> Option<PathBuf> {
412 xdg_base("XDG_CONFIG_HOME", ".config")
413 .ok()
414 .map(|b| b.join("lean-ctx"))
415}
416
417pub(crate) fn data_split_target() -> Result<PathBuf, String> {
419 raw_category_dir("LEAN_CTX_DATA_DIR", "XDG_DATA_HOME", ".local/share")
420}
421
422pub fn data_dir_read_only() -> Result<PathBuf, String> {
424 raw_category_dir("LEAN_CTX_DATA_DIR", "XDG_DATA_HOME", ".local/share")
425}
426
427pub(crate) fn state_split_target() -> Result<PathBuf, String> {
429 raw_category_dir("LEAN_CTX_STATE_DIR", "XDG_STATE_HOME", ".local/state")
430}
431
432pub(crate) fn cache_split_target() -> Result<PathBuf, String> {
434 raw_category_dir("LEAN_CTX_CACHE_DIR", "XDG_CACHE_HOME", ".cache")
435}
436
437#[cfg(test)]
438mod tests {
439 use super::*;
440
441 #[test]
442 fn resolve_prefers_override_then_single_then_xdg() {
443 let over = PathBuf::from("/over/ride");
444 let single = PathBuf::from("/single/dir");
445 let base = PathBuf::from("/xdg/base");
446
447 assert_eq!(
448 resolve(Some(over.clone()), Some(single.clone()), &base),
449 over
450 );
451 assert_eq!(resolve(None, Some(single.clone()), &base), single);
452 assert_eq!(
453 resolve(None, None, &base),
454 PathBuf::from("/xdg/base/lean-ctx")
455 );
456 }
457
458 #[test]
459 fn single_dir_fs_detects_legacy_with_data() {
460 let home = tempfile::tempdir().unwrap();
461 let xdg = tempfile::tempdir().unwrap();
462 let legacy = home.path().join(".lean-ctx");
463 std::fs::create_dir_all(&legacy).unwrap();
464 std::fs::write(legacy.join("stats.json"), "{}").unwrap();
465
466 assert_eq!(
467 single_dir_override_fs(home.path(), xdg.path(), None),
468 Some(legacy)
469 );
470 }
471
472 #[test]
473 fn single_dir_fs_detects_mixed_with_data() {
474 let home = tempfile::tempdir().unwrap();
475 let xdg = tempfile::tempdir().unwrap();
476 let mixed = xdg.path().join("lean-ctx");
477 std::fs::create_dir_all(&mixed).unwrap();
478 std::fs::write(mixed.join("stats.json"), "{}").unwrap();
481
482 assert_eq!(
483 single_dir_override_fs(home.path(), xdg.path(), None),
484 Some(mixed)
485 );
486 }
487
488 #[test]
489 fn single_dir_fs_ignores_config_only_dir() {
490 let home = tempfile::tempdir().unwrap();
493 let xdg = tempfile::tempdir().unwrap();
494 let mixed = xdg.path().join("lean-ctx");
495 std::fs::create_dir_all(&mixed).unwrap();
496 std::fs::write(mixed.join("config.toml"), "").unwrap();
497 std::fs::write(mixed.join("shell-hook.zsh"), "").unwrap();
498
499 assert_eq!(single_dir_override_fs(home.path(), xdg.path(), None), None);
500 }
501
502 #[test]
503 fn single_dir_fs_prefers_legacy_over_mixed() {
504 let home = tempfile::tempdir().unwrap();
505 let xdg = tempfile::tempdir().unwrap();
506 let legacy = home.path().join(".lean-ctx");
507 std::fs::create_dir_all(&legacy).unwrap();
508 std::fs::write(legacy.join("sessions"), "x").unwrap();
509 let mixed = xdg.path().join("lean-ctx");
510 std::fs::create_dir_all(&mixed).unwrap();
511 std::fs::write(mixed.join("stats.json"), "{}").unwrap();
512
513 assert_eq!(
514 single_dir_override_fs(home.path(), xdg.path(), None),
515 Some(legacy)
516 );
517 }
518
519 #[test]
520 fn xdg_pinned_install_ignores_stray_legacy_marker() {
521 let home = tempfile::tempdir().unwrap();
525 let xdg = tempfile::tempdir().unwrap();
526 crate::core::layout_pin::write_xdg_pin_in(xdg.path()).unwrap();
527
528 let legacy = home.path().join(".lean-ctx");
529 std::fs::create_dir_all(&legacy).unwrap();
530 std::fs::write(legacy.join("stats.json"), "{}").unwrap();
531
532 assert_eq!(single_dir_override_fs(home.path(), xdg.path(), None), None);
533 }
534
535 #[test]
536 fn xdg_pinned_install_ignores_stray_mixed_marker() {
537 let home = tempfile::tempdir().unwrap();
540 let xdg = tempfile::tempdir().unwrap();
541 crate::core::layout_pin::write_xdg_pin_in(xdg.path()).unwrap();
542
543 let mixed = xdg.path().join("lean-ctx");
544 std::fs::write(mixed.join("stats.json"), "{}").unwrap();
545
546 assert_eq!(single_dir_override_fs(home.path(), xdg.path(), None), None);
547 }
548
549 #[test]
550 fn split_install_ignores_stray_mixed_marker_when_xdg_data_present() {
551 let home = tempfile::tempdir().unwrap();
558 let xdg_config = tempfile::tempdir().unwrap();
559 let xdg_data = tempfile::tempdir().unwrap();
560
561 let mixed = xdg_config.path().join("lean-ctx");
562 std::fs::create_dir_all(&mixed).unwrap();
563 std::fs::write(mixed.join("stats.json"), "{}").unwrap();
564
565 let data = xdg_data.path().join("lean-ctx");
566 std::fs::create_dir_all(&data).unwrap();
567 std::fs::write(data.join("stats.json"), "{}").unwrap();
568
569 assert_eq!(
570 single_dir_override_fs(home.path(), xdg_config.path(), Some(xdg_data.path())),
571 None,
572 "a populated XDG data dir must keep the config dir from collapsing"
573 );
574
575 let empty_data = tempfile::tempdir().unwrap();
578 assert_eq!(
579 single_dir_override_fs(home.path(), xdg_config.path(), Some(empty_data.path())),
580 Some(mixed)
581 );
582 }
583
584 #[test]
585 fn single_dir_fs_ignores_empty_dirs() {
586 let home = tempfile::tempdir().unwrap();
587 let xdg = tempfile::tempdir().unwrap();
588 std::fs::create_dir_all(home.path().join(".lean-ctx")).unwrap();
589 std::fs::create_dir_all(xdg.path().join("lean-ctx")).unwrap();
590
591 assert_eq!(single_dir_override_fs(home.path(), xdg.path(), None), None);
592 }
593
594 #[test]
595 fn single_dir_fs_ignores_non_marker_files() {
596 let home = tempfile::tempdir().unwrap();
597 let xdg = tempfile::tempdir().unwrap();
598 let mixed = xdg.path().join("lean-ctx");
599 std::fs::create_dir_all(&mixed).unwrap();
600 std::fs::write(mixed.join("random.txt"), "x").unwrap();
601
602 assert_eq!(single_dir_override_fs(home.path(), xdg.path(), None), None);
603 }
604
605 #[test]
606 fn xdg_base_honors_env_then_home_fallback() {
607 let _lock = crate::core::data_dir::test_env_lock();
608 let tmp = tempfile::tempdir().unwrap();
609 crate::test_env::set_var("XDG_CONFIG_HOME", tmp.path());
610 let from_env = xdg_base("XDG_CONFIG_HOME", ".config").unwrap();
611 crate::test_env::remove_var("XDG_CONFIG_HOME");
612 assert_eq!(from_env, tmp.path());
613
614 let fallback = xdg_base("LEAN_CTX_NONEXISTENT_XDG_VAR", ".cache").unwrap();
616 assert!(fallback.ends_with(".cache"), "got: {}", fallback.display());
617 }
618
619 #[test]
620 fn legacy_adoption_source_only_when_canonical_absent() {
621 let tmp = tempfile::tempdir().unwrap();
622 let legacy = tmp.path().join("legacy");
623 let canonical = tmp.path().join("canonical");
624
625 assert_eq!(legacy_adoption_source(&legacy, &canonical), None);
627
628 std::fs::create_dir_all(&legacy).unwrap();
630 assert_eq!(
631 legacy_adoption_source(&legacy, &canonical),
632 Some(legacy.clone())
633 );
634
635 std::fs::create_dir_all(&canonical).unwrap();
637 assert_eq!(legacy_adoption_source(&legacy, &canonical), None);
638 }
639
640 #[test]
641 fn legacy_adoption_never_runs_under_an_override_or_sandbox() {
642 assert!(legacy_adoption_allowed(false, false));
643 assert!(!legacy_adoption_allowed(false, true), "dir override");
644 assert!(!legacy_adoption_allowed(true, false), "test sandbox");
645 assert!(!legacy_adoption_allowed(true, true));
646 }
647
648 #[test]
649 fn adoption_copies_then_retires_the_original_as_adopted() {
650 let tmp = tempfile::tempdir().unwrap();
651
652 let src_file = tmp.path().join("legacy/providers.toml");
654 std::fs::create_dir_all(src_file.parent().unwrap()).unwrap();
655 std::fs::write(&src_file, "id = \"x\"\n").unwrap();
656 let dst_file = tmp.path().join("config/lean-ctx/providers.toml");
657 adopt_legacy_config_member(&src_file, &dst_file);
658 assert!(!src_file.exists(), "no live stale duplicate stays behind");
659 assert_eq!(
660 std::fs::read_to_string(tmp.path().join("legacy/providers.toml.adopted")).unwrap(),
661 "id = \"x\"\n",
662 "the original is kept as <name>.adopted"
663 );
664 assert_eq!(std::fs::read_to_string(&dst_file).unwrap(), "id = \"x\"\n");
665
666 let src_dir = tmp.path().join("legacy/personas");
668 std::fs::create_dir_all(src_dir.join("nested")).unwrap();
669 std::fs::write(src_dir.join("a.toml"), "a").unwrap();
670 std::fs::write(src_dir.join("nested/b.toml"), "b").unwrap();
671 let dst_dir = tmp.path().join("config/lean-ctx/personas");
672 adopt_legacy_config_member(&src_dir, &dst_dir);
673 let retired_dir = tmp.path().join("legacy/personas.adopted");
674 assert!(!src_dir.exists());
675 for (rel, body) in [("a.toml", "a"), ("nested/b.toml", "b")] {
676 assert_eq!(
677 std::fs::read_to_string(retired_dir.join(rel)).unwrap(),
678 body
679 );
680 assert_eq!(std::fs::read_to_string(dst_dir.join(rel)).unwrap(), body);
681 }
682
683 std::fs::remove_file(&dst_file).unwrap();
685 std::fs::write(&src_file, "id = \"y\"\n").unwrap();
686 adopt_legacy_config_member(&src_file, &dst_file);
687 assert_eq!(std::fs::read_to_string(&dst_file).unwrap(), "id = \"y\"\n");
688 assert_eq!(
689 std::fs::read_to_string(tmp.path().join("legacy/providers.toml.adopted")).unwrap(),
690 "id = \"x\"\n"
691 );
692 assert_eq!(
693 std::fs::read_to_string(tmp.path().join("legacy/providers.toml.adopted-2")).unwrap(),
694 "id = \"y\"\n"
695 );
696 std::fs::remove_file(&dst_file).unwrap();
697 std::fs::write(&dst_file, "id = \"x\"\n").unwrap();
698
699 let names: Vec<String> = std::fs::read_dir(tmp.path().join("config/lean-ctx"))
701 .unwrap()
702 .map(|e| e.unwrap().file_name().to_string_lossy().into_owned())
703 .collect();
704 assert!(
705 names.iter().all(|n| !n.contains(".adopt-")),
706 "staging left behind: {names:?}"
707 );
708
709 std::fs::write(&src_file, "id = \"changed\"\n").unwrap();
711 adopt_legacy_config_member(&src_file, &dst_file);
712 assert_eq!(std::fs::read_to_string(&dst_file).unwrap(), "id = \"x\"\n");
713 }
714
715 #[test]
716 fn single_dir_override_honors_data_dir_env() {
717 let _lock = crate::core::data_dir::test_env_lock();
718 let tmp = tempfile::tempdir().unwrap();
719 crate::test_env::set_var("LEAN_CTX_DATA_DIR", tmp.path());
720 let got = single_dir_override();
721 crate::test_env::remove_var("LEAN_CTX_DATA_DIR");
722 assert_eq!(got, Some(tmp.path().to_path_buf()));
724 }
725
726 #[test]
727 fn is_standard_xdg_data_dir_matches_xdg_data_home() {
728 let _lock = crate::core::data_dir::test_env_lock();
729 let data_home = tempfile::tempdir().unwrap();
730 crate::test_env::set_var("XDG_DATA_HOME", data_home.path());
731 let is_std = is_standard_xdg_data_dir(&data_home.path().join("lean-ctx"));
732 let is_custom = is_standard_xdg_data_dir(Path::new("/some/custom/lean-ctx"));
733 crate::test_env::remove_var("XDG_DATA_HOME");
734 assert!(is_std, "$XDG_DATA_HOME/lean-ctx is the standard data dir");
735 assert!(!is_custom, "a custom path is not the standard data dir");
736 }
737
738 #[test]
739 fn standard_data_pin_does_not_collapse_categories() {
740 let _lock = crate::core::data_dir::test_env_lock();
744 let home = tempfile::tempdir().unwrap();
745 let xdg_config = tempfile::tempdir().unwrap();
746 let xdg_data = tempfile::tempdir().unwrap();
747 let data_pin = xdg_data.path().join("lean-ctx");
748 crate::test_env::set_var("HOME", home.path());
749 crate::test_env::set_var("XDG_CONFIG_HOME", xdg_config.path());
750 crate::test_env::set_var("XDG_DATA_HOME", xdg_data.path());
751 crate::test_env::set_var("LEAN_CTX_DATA_DIR", &data_pin);
752
753 let got = single_dir_override();
754
755 crate::test_env::remove_var("LEAN_CTX_DATA_DIR");
756 crate::test_env::remove_var("XDG_DATA_HOME");
757 crate::test_env::remove_var("XDG_CONFIG_HOME");
758 crate::test_env::remove_var("HOME");
759
760 assert_eq!(got, None);
761 }
762
763 #[test]
764 fn config_dir_honors_explicit_override() {
765 let _lock = crate::core::data_dir::test_env_lock();
766 let tmp = tempfile::tempdir().unwrap();
767 crate::test_env::set_var("LEAN_CTX_CONFIG_DIR", tmp.path());
768 let got = config_dir().unwrap();
769 crate::test_env::remove_var("LEAN_CTX_CONFIG_DIR");
770 assert_eq!(got, tmp.path());
771 }
772
773 #[test]
774 fn state_and_cache_dirs_honor_explicit_overrides() {
775 let _lock = crate::core::data_dir::test_env_lock();
776 let state = tempfile::tempdir().unwrap();
777 let cache = tempfile::tempdir().unwrap();
778 crate::test_env::set_var("LEAN_CTX_STATE_DIR", state.path());
779 crate::test_env::set_var("LEAN_CTX_CACHE_DIR", cache.path());
780 let got_state = state_dir().unwrap();
781 let got_cache = cache_dir().unwrap();
782 crate::test_env::remove_var("LEAN_CTX_STATE_DIR");
783 crate::test_env::remove_var("LEAN_CTX_CACHE_DIR");
784 assert_eq!(got_state, state.path());
785 assert_eq!(got_cache, cache.path());
786 }
787
788 #[test]
789 fn data_dir_matches_lean_ctx_data_dir() {
790 let _guard = crate::core::data_dir::isolated_data_dir();
791 assert_eq!(
792 data_dir().unwrap(),
793 crate::core::data_dir::lean_ctx_data_dir().unwrap()
794 );
795 }
796
797 #[test]
798 fn runtime_dir_honors_xdg_runtime_dir() {
799 let _lock = crate::core::data_dir::test_env_lock();
800 let tmp = tempfile::tempdir().unwrap();
801 crate::test_env::set_var("XDG_RUNTIME_DIR", tmp.path());
802 let got = runtime_dir().unwrap();
803 crate::test_env::remove_var("XDG_RUNTIME_DIR");
804 assert_eq!(got, tmp.path().join("lean-ctx"));
805 }
806}