1use std::path::{Path, PathBuf};
16use std::time::{SystemTime, UNIX_EPOCH};
17
18use crate::constants::{INSTRUCTIONS_TRUNCATION_MARKER, MAX_INSTRUCTIONS_BYTES};
19
20pub const INSTRUCTION_FILENAMES: &[&str] = &["AGENTS.md", "MERMAID.md"];
25
26const MAX_WALK_DEPTH: usize = 32;
29
30#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
33pub struct InstructionSource {
34 pub path: PathBuf,
35 pub mtime: SystemTime,
36 pub byte_len: usize,
37}
38
39#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
43pub struct LoadedInstructions {
44 pub path: PathBuf,
48 pub content: String,
51 pub mtime: SystemTime,
54 pub byte_len: usize,
56 pub truncated: bool,
59 pub sources: Vec<InstructionSource>,
61}
62
63impl LoadedInstructions {
64 pub fn approx_tokens(&self) -> usize {
67 self.content.len() / 4
68 }
69}
70
71#[derive(Debug, PartialEq, Eq)]
74pub enum ReloadOutcome {
75 Unchanged,
77 LoadedFirst { tokens: usize },
80 Reloaded {
82 old_tokens: usize,
83 new_tokens: usize,
84 },
85 Removed,
87}
88
89pub fn find_instruction_files(start: &Path) -> Vec<PathBuf> {
99 find_instruction_files_bounded(start, home_dir_boundary().as_deref())
100}
101
102fn home_dir_boundary() -> Option<PathBuf> {
110 let home = std::env::var_os("HOME");
111 #[cfg(windows)]
112 let resolved = pick_home_boundary(
113 home.as_deref(),
114 std::env::var_os("USERPROFILE").as_deref(),
115 std::env::var_os("HOMEDRIVE").as_deref(),
116 std::env::var_os("HOMEPATH").as_deref(),
117 );
118 #[cfg(not(windows))]
120 let resolved = pick_home_boundary(home.as_deref(), None, None, None);
121 resolved
122}
123
124fn pick_home_boundary(
130 home: Option<&std::ffi::OsStr>,
131 userprofile: Option<&std::ffi::OsStr>,
132 homedrive: Option<&std::ffi::OsStr>,
133 homepath: Option<&std::ffi::OsStr>,
134) -> Option<PathBuf> {
135 fn nonempty(v: Option<&std::ffi::OsStr>) -> Option<&std::ffi::OsStr> {
136 v.filter(|s| !s.is_empty())
137 }
138 if let Some(home) = nonempty(home) {
139 return Some(PathBuf::from(home));
140 }
141 if let Some(profile) = nonempty(userprofile) {
142 return Some(PathBuf::from(profile));
143 }
144 if let (Some(drive), Some(path)) = (nonempty(homedrive), nonempty(homepath)) {
145 let mut combined = drive.to_os_string();
146 combined.push(path);
147 return Some(PathBuf::from(combined));
148 }
149 None
150}
151
152fn find_instruction_files_bounded(start: &Path, home: Option<&Path>) -> Vec<PathBuf> {
156 let mut current = start.to_path_buf();
157 for _ in 0..MAX_WALK_DEPTH {
158 if home == Some(current.as_path()) {
163 return Vec::new();
164 }
165 let found: Vec<PathBuf> = INSTRUCTION_FILENAMES
166 .iter()
167 .map(|name| current.join(name))
168 .filter(|candidate| candidate.is_file())
169 .collect();
170 if !found.is_empty() {
171 return found;
172 }
173 if current.join(".git").exists() {
177 return Vec::new();
178 }
179 match current.parent() {
181 Some(parent) if parent != current => current = parent.to_path_buf(),
182 _ => return Vec::new(),
183 }
184 }
185 Vec::new()
186}
187
188pub fn load_from_path(path: &Path) -> Option<LoadedInstructions> {
192 load_from_paths(&[path.to_path_buf()])
193}
194
195pub fn load_from_paths(paths: &[PathBuf]) -> Option<LoadedInstructions> {
198 let mut sources = Vec::new();
199 let mut bodies = Vec::new();
200 let mut total_byte_len = 0usize;
201 let mut latest_mtime = UNIX_EPOCH;
202
203 for path in paths {
204 let Ok(metadata) = std::fs::metadata(path) else {
210 continue;
211 };
212 let Ok(mtime) = metadata.modified() else {
213 continue;
214 };
215 let true_len = metadata.len() as usize;
216 let Ok((bytes, _truncated)) =
222 crate::utils::read_file_capped(path, MAX_INSTRUCTIONS_BYTES.saturating_add(1))
223 else {
224 continue;
225 };
226 let raw = String::from_utf8_lossy(&bytes).into_owned();
227 total_byte_len = total_byte_len.saturating_add(true_len);
228 if mtime > latest_mtime {
229 latest_mtime = mtime;
230 }
231 sources.push(InstructionSource {
232 path: path.to_path_buf(),
233 mtime,
234 byte_len: true_len,
235 });
236 bodies.push((path.to_path_buf(), raw));
237 }
238 let primary = sources.first()?.path.clone();
239 let sections = label_instruction_bodies(bodies);
240 let byte_len = total_byte_len;
241 let (content, truncated) = combine_and_cap_sections(sections, MAX_INSTRUCTIONS_BYTES);
242 Some(LoadedInstructions {
243 path: primary,
244 content,
245 mtime: latest_mtime,
246 byte_len,
247 truncated,
248 sources,
249 })
250}
251
252pub fn refresh(
259 current: Option<LoadedInstructions>,
260 cwd: &Path,
261) -> (Option<LoadedInstructions>, ReloadOutcome) {
262 match current {
263 Some(prior) => {
264 let paths: Vec<PathBuf> = if prior.sources.is_empty() {
266 vec![prior.path.clone()]
267 } else {
268 prior
269 .sources
270 .iter()
271 .map(|source| source.path.clone())
272 .collect()
273 };
274 let changed = if prior.sources.is_empty() {
275 std::fs::metadata(&prior.path)
276 .and_then(|m| m.modified())
277 .map(|mtime| mtime != prior.mtime)
278 .unwrap_or(true)
279 } else {
280 prior.sources.iter().any(|source| {
281 std::fs::metadata(&source.path)
282 .and_then(|m| m.modified())
283 .map(|mtime| mtime != source.mtime)
284 .unwrap_or(true)
285 })
286 };
287 if !changed {
288 return (Some(prior), ReloadOutcome::Unchanged);
289 }
290 let old_tokens = prior.approx_tokens();
291 match load_from_paths(&paths) {
292 Some(reloaded) => {
293 let new_tokens = reloaded.approx_tokens();
294 (
295 Some(reloaded),
296 ReloadOutcome::Reloaded {
297 old_tokens,
298 new_tokens,
299 },
300 )
301 },
302 None => {
303 (None, ReloadOutcome::Removed)
306 },
307 }
308 },
309 None => {
310 match load_from_paths(&find_instruction_files(cwd)) {
313 Some(loaded) => {
314 let tokens = loaded.approx_tokens();
315 (Some(loaded), ReloadOutcome::LoadedFirst { tokens })
316 },
317 None => (None, ReloadOutcome::Unchanged),
318 }
319 },
320 }
321}
322
323pub fn load_project_context(
332 cwd: &Path,
333 mem_cfg: &crate::app::MemoryConfig,
334) -> (
335 Option<LoadedInstructions>,
336 Option<crate::app::memory::LoadedMemory>,
337 Option<crate::app::skills::LoadedSkills>,
338) {
339 let (instructions, _) = refresh(None, cwd);
340 let (memory, _) = crate::app::memory::refresh(None, cwd, mem_cfg);
341 let skills = crate::app::skills::load(cwd);
342 (instructions, memory, skills)
343}
344
345const INSTRUCTION_SECTION_SEPARATOR: &str = "\n\n---\n\n";
347
348fn label_instruction_bodies(bodies: Vec<(PathBuf, String)>) -> Vec<String> {
354 bodies
355 .into_iter()
356 .map(|(path, body)| {
357 let name = path
358 .file_name()
359 .and_then(|name| name.to_str())
360 .unwrap_or("instructions");
361 format!("# Project Instructions: {}\n\n{}", name, body)
362 })
363 .collect()
364}
365
366fn combine_and_cap_sections(sections: Vec<String>, cap: usize) -> (String, bool) {
384 const SEP: &str = INSTRUCTION_SECTION_SEPARATOR;
385 let marker = INSTRUCTIONS_TRUNCATION_MARKER;
386
387 let full = sections.join(SEP);
388 if full.len() <= cap {
389 return (full, false);
390 }
391 let Some(winner) = sections.last() else {
394 return (String::new(), false);
395 };
396 if winner.len() >= cap {
400 let cut = winner.floor_char_boundary(cap);
401 let mut clipped = winner[..cut].to_string();
402 clipped.push_str(marker);
403 return (clipped, true);
404 }
405 let earlier_len = sections.len() - 1;
410 let earlier_full = sections[..earlier_len].join(SEP);
411 let avail = cap.saturating_sub(winner.len() + SEP.len());
412 let cut = earlier_full.floor_char_boundary(avail);
413 if cut == 0 {
414 let mut body = winner.clone();
417 body.push_str(marker);
418 return (body, true);
419 }
420 let mut body = String::with_capacity(cut + marker.len() + SEP.len() + winner.len());
421 body.push_str(&earlier_full[..cut]);
422 body.push_str(marker);
423 body.push_str(SEP);
424 body.push_str(winner.as_str());
425 (body, true)
426}
427
428#[cfg(test)]
429mod tests {
430 use super::*;
431 use std::fs;
432 use std::sync::Mutex;
433
434 static FS_LOCK: Mutex<()> = Mutex::new(());
437
438 fn temp_dir(name: &str) -> PathBuf {
439 let p = std::env::temp_dir().join(format!("mermaid_instructions_test_{}", name));
440 let _ = fs::remove_dir_all(&p);
441 fs::create_dir_all(&p).expect("create temp dir");
442 p
443 }
444
445 #[test]
446 fn find_instruction_files_finds_in_cwd() {
447 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
448 let dir = temp_dir("cwd");
449 fs::write(dir.join("MERMAID.md"), "rules").unwrap();
450 let found = find_instruction_files(&dir);
451 assert_eq!(found, vec![dir.join("MERMAID.md")]);
452 let _ = fs::remove_dir_all(&dir);
453 }
454
455 #[test]
456 fn find_instruction_files_loads_both_in_precedence_order() {
457 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
458 let dir = temp_dir("both");
459 fs::write(dir.join("AGENTS.md"), "agent rules").unwrap();
460 fs::write(dir.join("MERMAID.md"), "mermaid rules").unwrap();
461 let found = find_instruction_files(&dir);
462 assert_eq!(found, vec![dir.join("AGENTS.md"), dir.join("MERMAID.md")]);
464 let loaded = load_from_paths(&found).expect("load combined");
465 assert!(loaded.content.contains("# Project Instructions: AGENTS.md"));
466 assert!(loaded.content.contains("agent rules"));
467 assert!(
468 loaded
469 .content
470 .contains("# Project Instructions: MERMAID.md")
471 );
472 assert!(loaded.content.contains("mermaid rules"));
473 assert!(
475 loaded.content.find("mermaid rules") > loaded.content.find("agent rules"),
476 "MERMAID.md must come last so its guidance overrides AGENTS.md"
477 );
478 assert_eq!(loaded.sources.len(), 2);
479 let _ = fs::remove_dir_all(&dir);
480 }
481
482 #[test]
483 fn find_instruction_files_walks_up_to_git_root() {
484 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
485 let root = temp_dir("walkup");
486 fs::create_dir(root.join(".git")).unwrap();
487 fs::write(root.join("MERMAID.md"), "root rules").unwrap();
488 let sub = root.join("subdir/deeper");
489 fs::create_dir_all(&sub).unwrap();
490 let found = find_instruction_files(&sub);
491 assert_eq!(found, vec![root.join("MERMAID.md")]);
492 let _ = fs::remove_dir_all(&root);
493 }
494
495 #[test]
496 fn find_instruction_files_stops_at_git_root_without_file() {
497 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
498 let root = temp_dir("git_no_md");
499 fs::create_dir(root.join(".git")).unwrap();
500 let parent = root.parent().unwrap();
503 let above_md = parent.join("MERMAID.md");
504 fs::write(&above_md, "outside").unwrap();
505 let sub = root.join("subdir");
506 fs::create_dir_all(&sub).unwrap();
507 let found = find_instruction_files(&sub);
508 assert!(found.is_empty(), "walk must stop at .git boundary");
509 let _ = fs::remove_dir_all(&root);
510 let _ = fs::remove_file(&above_md);
511 }
512
513 #[test]
514 fn find_instruction_files_returns_empty_if_absent() {
515 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
516 let dir = temp_dir("absent");
517 fs::create_dir(dir.join(".git")).unwrap();
520 let found = find_instruction_files(&dir);
521 assert!(found.is_empty());
522 let _ = fs::remove_dir_all(&dir);
523 }
524
525 #[test]
526 fn find_instruction_files_stops_at_home_boundary() {
527 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
532 let home = temp_dir("home_boundary");
533 fs::write(home.join("AGENTS.md"), "home rules").unwrap();
534 let child = home.join("project");
535 fs::create_dir_all(&child).unwrap();
536 let found = find_instruction_files_bounded(&child, Some(home.as_path()));
539 assert!(
540 found.is_empty(),
541 "walk must stop at $HOME and not load ~/AGENTS.md, got {found:?}"
542 );
543 let _ = fs::remove_dir_all(&home);
544 }
545
546 #[test]
547 fn single_file_instructions_get_labeled_header() {
548 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
552 let dir = temp_dir("single_header");
553 fs::write(dir.join("MERMAID.md"), "do the thing").unwrap();
554 let loaded = load_from_path(&dir.join("MERMAID.md")).expect("load");
555 assert!(
556 loaded
557 .content
558 .starts_with("# Project Instructions: MERMAID.md"),
559 "single-file instructions must carry a labeled header, got: {:?}",
560 loaded.content
561 );
562 assert!(loaded.content.contains("do the thing"));
563 let _ = fs::remove_dir_all(&dir);
564 }
565
566 #[test]
567 fn load_from_path_truncates_oversized_file() {
568 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
569 let dir = temp_dir("oversized");
570 let path = dir.join("MERMAID.md");
571 let big = "a".repeat(50_000);
573 fs::write(&path, &big).unwrap();
574 let loaded = load_from_path(&path).expect("load");
575 assert!(loaded.truncated);
576 assert_eq!(loaded.byte_len, 50_000); assert!(loaded.content.ends_with(INSTRUCTIONS_TRUNCATION_MARKER));
578 assert_eq!(
580 loaded.content.len(),
581 MAX_INSTRUCTIONS_BYTES + INSTRUCTIONS_TRUNCATION_MARKER.len()
582 );
583 let _ = fs::remove_dir_all(&dir);
584 }
585
586 #[test]
587 fn load_from_path_returns_none_when_missing() {
588 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
589 let dir = temp_dir("missing");
590 assert!(load_from_path(&dir.join("nope.md")).is_none());
591 let _ = fs::remove_dir_all(&dir);
592 }
593
594 #[test]
595 fn refresh_returns_unchanged_when_mtime_stable() {
596 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
597 let dir = temp_dir("stable");
598 let path = dir.join("MERMAID.md");
599 fs::write(&path, "v1").unwrap();
600 let prior = load_from_path(&path).unwrap();
601 let (after, outcome) = refresh(Some(prior.clone()), &dir);
602 assert_eq!(outcome, ReloadOutcome::Unchanged);
603 assert!(after.is_some());
604 let _ = fs::remove_dir_all(&dir);
605 }
606
607 #[test]
608 fn refresh_returns_reloaded_on_content_change() {
609 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
610 let dir = temp_dir("changed");
611 let path = dir.join("MERMAID.md");
612 fs::write(&path, "v1").unwrap();
613 let prior = load_from_path(&path).unwrap();
614 std::thread::sleep(std::time::Duration::from_millis(1100));
617 fs::write(&path, "v2 longer content here").unwrap();
618 let (after, outcome) = refresh(Some(prior), &dir);
619 assert!(matches!(outcome, ReloadOutcome::Reloaded { .. }));
620 let content = after.unwrap().content;
621 assert!(content.contains("# Project Instructions: MERMAID.md"));
622 assert!(content.contains("v2 longer content here"));
623 let _ = fs::remove_dir_all(&dir);
624 }
625
626 #[test]
627 fn refresh_returns_removed_when_file_deleted() {
628 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
629 let dir = temp_dir("removed");
630 let path = dir.join("MERMAID.md");
631 fs::write(&path, "v1").unwrap();
632 let prior = load_from_path(&path).unwrap();
633 fs::remove_file(&path).unwrap();
634 let (after, outcome) = refresh(Some(prior), &dir);
635 assert_eq!(outcome, ReloadOutcome::Removed);
636 assert!(after.is_none());
637 let _ = fs::remove_dir_all(&dir);
638 }
639
640 #[test]
641 fn refresh_returns_loaded_first_on_initial_discovery() {
642 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
643 let dir = temp_dir("first");
644 fs::create_dir(dir.join(".git")).unwrap();
646 fs::write(dir.join("MERMAID.md"), "fresh").unwrap();
648 let (after, outcome) = refresh(None, &dir);
649 assert!(matches!(outcome, ReloadOutcome::LoadedFirst { .. }));
650 let content = after.unwrap().content;
651 assert!(content.contains("# Project Instructions: MERMAID.md"));
652 assert!(content.contains("fresh"));
653 let _ = fs::remove_dir_all(&dir);
654 }
655
656 #[test]
657 fn load_project_context_loads_instructions_synchronously() {
658 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
662 let dir = temp_dir("project_context");
663 fs::create_dir(dir.join(".git")).unwrap();
664 fs::write(dir.join("MERMAID.md"), "sync-loaded instructions").unwrap();
665 let (instructions, _memory, _skills) =
666 load_project_context(&dir, &crate::app::MemoryConfig::default());
667 let content = instructions
668 .expect("instructions must load synchronously")
669 .content;
670 assert!(content.contains("sync-loaded instructions"));
671 let _ = fs::remove_dir_all(&dir);
672 }
673
674 #[test]
675 fn oversized_agents_does_not_drop_mermaid_winner() {
676 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
681 let dir = temp_dir("agents_huge");
682 fs::write(dir.join("AGENTS.md"), "A".repeat(60_000)).unwrap();
683 fs::write(dir.join("MERMAID.md"), "MERMAID_WINS_SENTINEL").unwrap();
684 let loaded =
685 load_from_paths(&[dir.join("AGENTS.md"), dir.join("MERMAID.md")]).expect("load");
686 assert!(loaded.truncated, "combined body exceeds the cap");
687 assert!(
688 loaded.content.contains("MERMAID_WINS_SENTINEL"),
689 "MERMAID.md (the winner) must survive the cap, not be dropped"
690 );
691 assert!(
692 loaded
693 .content
694 .contains("# Project Instructions: MERMAID.md")
695 );
696 assert!(loaded.content.contains(INSTRUCTIONS_TRUNCATION_MARKER));
697 let marker_at = loaded.content.find(INSTRUCTIONS_TRUNCATION_MARKER).unwrap();
700 let winner_at = loaded.content.find("MERMAID_WINS_SENTINEL").unwrap();
701 assert!(
702 winner_at > marker_at,
703 "MERMAID.md must come after the truncated AGENTS.md"
704 );
705 assert!(
707 loaded.content.len() <= MAX_INSTRUCTIONS_BYTES + INSTRUCTIONS_TRUNCATION_MARKER.len()
708 );
709 let _ = fs::remove_dir_all(&dir);
710 }
711
712 #[test]
713 fn combine_and_cap_protects_the_last_section() {
714 let lower = format!("# Project Instructions: AGENTS.md\n\n{}", "L".repeat(200));
717 let winner = "# Project Instructions: MERMAID.md\n\nWIN".to_string();
718 let (body, truncated) = combine_and_cap_sections(vec![lower, winner], 100);
719 assert!(truncated);
720 assert!(body.contains("WIN"), "winner survives the cap");
721 assert!(body.contains(INSTRUCTIONS_TRUNCATION_MARKER));
722 let marker_at = body.find(INSTRUCTIONS_TRUNCATION_MARKER).unwrap();
723 assert!(
724 body.find("WIN").unwrap() > marker_at,
725 "winner stays last so it overrides on conflict"
726 );
727 assert!(body.len() <= 100 + INSTRUCTIONS_TRUNCATION_MARKER.len());
728 }
729
730 #[test]
731 fn combine_and_cap_clips_winner_when_it_alone_overflows() {
732 let lower = "# Project Instructions: AGENTS.md\n\nlower-content".to_string();
735 let winner = format!("# Project Instructions: MERMAID.md\n\n{}", "W".repeat(300));
736 let (body, truncated) = combine_and_cap_sections(vec![lower, winner], 100);
737 assert!(truncated);
738 assert!(body.ends_with(INSTRUCTIONS_TRUNCATION_MARKER));
739 assert_eq!(body.len(), 100 + INSTRUCTIONS_TRUNCATION_MARKER.len());
740 assert!(
741 !body.contains("lower-content"),
742 "no room for the lower-precedence section"
743 );
744 }
745
746 #[test]
747 fn combine_and_cap_passes_through_when_it_fits() {
748 let a = "# Project Instructions: AGENTS.md\n\naye".to_string();
749 let b = "# Project Instructions: MERMAID.md\n\nbee".to_string();
750 let (body, truncated) = combine_and_cap_sections(vec![a, b], 10_000);
751 assert!(!truncated);
752 assert!(body.contains("aye") && body.contains("bee"));
753 assert!(
754 body.find("bee").unwrap() > body.find("aye").unwrap(),
755 "highest-precedence section stays last"
756 );
757 }
758
759 #[test]
760 fn load_from_paths_tolerates_a_missing_file() {
761 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
765 let dir = temp_dir("partial_load");
766 fs::write(dir.join("AGENTS.md"), "agent rules").unwrap();
767 let missing = dir.join("MERMAID.md"); let loaded = load_from_paths(&[dir.join("AGENTS.md"), missing])
769 .expect("AGENTS.md must still load when MERMAID.md is absent");
770 assert!(loaded.content.contains("# Project Instructions: AGENTS.md"));
771 assert!(loaded.content.contains("agent rules"));
772 assert_eq!(loaded.sources.len(), 1, "only the present file is a source");
773 assert_eq!(loaded.path, dir.join("AGENTS.md"));
774 let _ = fs::remove_dir_all(&dir);
775 }
776
777 #[test]
778 fn load_from_paths_none_when_all_missing() {
779 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
780 let dir = temp_dir("all_missing");
781 assert!(
782 load_from_paths(&[dir.join("AGENTS.md"), dir.join("MERMAID.md")]).is_none(),
783 "no present files => None"
784 );
785 let _ = fs::remove_dir_all(&dir);
786 }
787
788 #[test]
789 fn pick_home_boundary_resolves_windows_home_vars() {
790 use std::ffi::OsStr;
794 assert_eq!(
796 pick_home_boundary(
797 Some(OsStr::new("/home/me")),
798 Some(OsStr::new("C:\\Users\\me")),
799 None,
800 None
801 ),
802 Some(PathBuf::from("/home/me"))
803 );
804 assert_eq!(
806 pick_home_boundary(None, Some(OsStr::new("C:\\Users\\me")), None, None),
807 Some(PathBuf::from("C:\\Users\\me"))
808 );
809 assert_eq!(
811 pick_home_boundary(
812 None,
813 None,
814 Some(OsStr::new("C:")),
815 Some(OsStr::new("\\Users\\me"))
816 ),
817 Some(PathBuf::from("C:\\Users\\me"))
818 );
819 assert_eq!(
821 pick_home_boundary(Some(OsStr::new("")), Some(OsStr::new("")), None, None),
822 None
823 );
824 assert_eq!(
826 pick_home_boundary(None, None, Some(OsStr::new("C:")), None),
827 None
828 );
829 assert_eq!(
830 pick_home_boundary(None, None, None, Some(OsStr::new("\\Users\\me"))),
831 None
832 );
833 }
834}