1use std::fmt;
54use std::path::{Path, PathBuf};
55use std::time::Duration;
56
57use runner_manager_domain::model::Arch;
58
59use super::WslError;
60use super::discovery::{DistributionTable, validate_distribution_name};
61use super::exec::{ChildInput, CommandOutput, CommandRequest, CommandRunner, OutputLimits};
62
63pub const LINUX_USER: &str = "root";
71
72#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct WslExecutable(PathBuf);
89
90impl WslExecutable {
91 #[must_use]
96 pub fn locate() -> Self {
97 Self(locate_in_system32("wsl.exe"))
98 }
99
100 #[must_use]
102 pub fn at(path: impl Into<PathBuf>) -> Self {
103 Self(path.into())
104 }
105
106 #[must_use]
108 pub fn path(&self) -> &Path {
109 &self.0
110 }
111}
112
113impl Default for WslExecutable {
114 fn default() -> Self {
115 Self::locate()
116 }
117}
118
119impl fmt::Display for WslExecutable {
120 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121 write!(f, "{}", self.0.display())
122 }
123}
124
125pub(crate) fn locate_in_system32(program: &str) -> PathBuf {
131 locate_from(std::env::var_os("SystemRoot").map(PathBuf::from), program)
132}
133
134fn locate_from(system_root: Option<PathBuf>, program: &str) -> PathBuf {
137 if let Some(root) = system_root {
138 let candidate = root.join("System32").join(program);
139 if candidate.is_file() {
140 return candidate;
141 }
142 }
143 PathBuf::from(program)
144}
145
146#[derive(Debug)]
152pub struct LinuxCommand {
153 distribution: String,
154 user: String,
155 program: String,
156 arguments: Vec<String>,
157 input: ChildInput,
158 timeout: Duration,
159 limits: OutputLimits,
160}
161
162impl LinuxCommand {
163 #[must_use]
165 pub fn new(distribution: impl Into<String>, program: impl Into<String>) -> Self {
166 Self {
167 distribution: distribution.into(),
168 user: LINUX_USER.to_string(),
169 program: program.into(),
170 arguments: Vec::new(),
171 input: ChildInput::Empty,
172 timeout: super::exec::DEFAULT_TIMEOUT,
173 limits: OutputLimits::default(),
174 }
175 }
176
177 #[must_use]
179 pub fn args<I, S>(mut self, arguments: I) -> Self
180 where
181 I: IntoIterator<Item = S>,
182 S: Into<String>,
183 {
184 self.arguments.extend(arguments.into_iter().map(Into::into));
185 self
186 }
187
188 #[must_use]
190 pub fn with_input(mut self, input: ChildInput) -> Self {
191 self.input = input;
192 self
193 }
194
195 #[must_use]
197 pub fn with_timeout(mut self, timeout: Duration) -> Self {
198 self.timeout = timeout;
199 self
200 }
201
202 #[must_use]
204 pub fn with_limits(mut self, limits: OutputLimits) -> Self {
205 self.limits = limits;
206 self
207 }
208
209 #[must_use]
211 pub fn distribution(&self) -> &str {
212 &self.distribution
213 }
214
215 #[must_use]
217 pub fn program(&self) -> &str {
218 &self.program
219 }
220
221 #[must_use]
227 pub fn wsl_arguments(&self) -> Vec<String> {
228 let mut argv = vec![
229 "--distribution".to_string(),
230 self.distribution.clone(),
231 "--user".to_string(),
232 self.user.clone(),
233 "--exec".to_string(),
234 self.program.clone(),
235 ];
236 argv.extend(self.arguments.iter().cloned());
237 argv
238 }
239}
240
241#[derive(Debug, Clone, Copy)]
247pub struct WslInvoker<'runner> {
248 runner: &'runner dyn CommandRunner,
249 executable: &'runner WslExecutable,
250}
251
252impl<'runner> WslInvoker<'runner> {
253 #[must_use]
255 pub fn new(runner: &'runner dyn CommandRunner, executable: &'runner WslExecutable) -> Self {
256 Self { runner, executable }
257 }
258
259 #[must_use]
261 pub fn executable(&self) -> &WslExecutable {
262 self.executable
263 }
264
265 pub fn list(&self) -> Result<DistributionTable, WslError> {
273 let request = CommandRequest::new(self.executable.path())
274 .arg("--list")
275 .arg("--verbose");
276 let output = self.runner.run(&request)?;
277 if !output.success() {
278 let table = DistributionTable::from_console_output(output.stdout());
282 if table.is_empty() && table.unreadable().is_empty() {
283 return Err(WslError::CommandFailed {
284 what: "list the installed WSL distributions",
285 program: self.executable.path().to_path_buf(),
286 exit_code: output.exit_code(),
287 detail: output.diagnostic(),
288 });
289 }
290 return Ok(table);
291 }
292 Ok(DistributionTable::from_console_output(output.stdout()))
293 }
294
295 pub fn platform_status(&self) -> Result<(), WslError> {
298 let request = CommandRequest::new(self.executable.path()).arg("--status");
299 let output = self.runner.run(&request)?;
300 if output.success() {
301 return Ok(());
302 }
303 Err(WslError::CommandFailed {
304 what: "read WSL platform status",
305 program: self.executable.path().to_path_buf(),
306 exit_code: output.exit_code(),
307 detail: output.diagnostic(),
308 })
309 }
310
311 pub fn terminate_named(&self, distribution: &str) -> Result<(), WslError> {
315 validate_distribution_name(distribution)?;
316 let request = CommandRequest::new(self.executable.path())
317 .arg("--terminate")
318 .arg(distribution);
319 let output = self.runner.run(&request)?;
320 if output.success() {
321 return Ok(());
322 }
323 Err(WslError::CommandFailed {
324 what: "terminate the named WSL distribution",
325 program: self.executable.path().to_path_buf(),
326 exit_code: output.exit_code(),
327 detail: output.diagnostic(),
328 })
329 }
330
331 pub fn exec(&self, command: LinuxCommand) -> Result<CommandOutput, WslError> {
350 validate_distribution_name(&command.distribution)?;
351 let request = CommandRequest::new(self.executable.path())
352 .args(command.wsl_arguments())
353 .with_timeout(command.timeout)
354 .with_limits(command.limits)
355 .with_input(command.input);
356 self.runner.run(&request)
357 }
358
359 pub fn exec_ok(
366 &self,
367 what: &'static str,
368 command: LinuxCommand,
369 ) -> Result<CommandOutput, WslError> {
370 let program = PathBuf::from(command.program());
371 let output = self.exec(command)?;
372 if output.success() {
373 return Ok(output);
374 }
375 Err(WslError::CommandFailed {
376 what,
377 program,
378 exit_code: output.exit_code(),
379 detail: output.diagnostic(),
380 })
381 }
382}
383
384#[derive(Debug, Clone, PartialEq, Eq)]
390pub enum SystemdState {
391 Running,
393 Degraded,
396 Starting,
400 Unavailable(String),
402}
403
404impl SystemdState {
405 #[must_use]
407 pub fn from_report(word: &str) -> Self {
408 match word.trim() {
409 "running" => Self::Running,
410 "degraded" => Self::Degraded,
411 "initializing" | "starting" => Self::Starting,
412 "" => Self::Unavailable("it said nothing".to_string()),
413 other => Self::Unavailable(other.to_string()),
414 }
415 }
416
417 #[must_use]
419 pub fn is_usable(&self) -> bool {
420 matches!(self, Self::Running | Self::Degraded | Self::Starting)
421 }
422}
423
424impl fmt::Display for SystemdState {
425 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
426 match self {
427 Self::Running => f.write_str("running"),
428 Self::Degraded => f.write_str("degraded"),
429 Self::Starting => f.write_str("starting"),
430 Self::Unavailable(word) => write!(f, "unavailable ({word})"),
431 }
432 }
433}
434
435pub fn architecture_from_uname(distribution: &str, machine: &str) -> Result<Arch, WslError> {
446 match machine.trim() {
447 "x86_64" | "amd64" => Ok(Arch::X64),
448 "aarch64" | "arm64" => Ok(Arch::Arm64),
449 other => Err(WslError::UnsupportedArchitecture {
450 distribution: distribution.to_string(),
451 reported: other.to_string(),
452 }),
453 }
454}
455
456#[derive(Debug, Clone, PartialEq, Eq)]
458pub struct DistributionReadiness {
459 name: String,
460 wsl_version: u8,
461 default: bool,
462 architecture: Arch,
463 machine: String,
464 systemd: SystemdState,
465}
466
467impl DistributionReadiness {
468 #[must_use]
470 pub fn name(&self) -> &str {
471 &self.name
472 }
473
474 #[must_use]
477 pub fn wsl_version(&self) -> u8 {
478 self.wsl_version
479 }
480
481 #[must_use]
483 pub fn is_default(&self) -> bool {
484 self.default
485 }
486
487 #[must_use]
489 pub fn architecture(&self) -> Arch {
490 self.architecture
491 }
492
493 #[must_use]
495 pub fn machine(&self) -> &str {
496 &self.machine
497 }
498
499 #[must_use]
501 pub fn systemd(&self) -> &SystemdState {
502 &self.systemd
503 }
504}
505
506pub fn probe_readiness(
516 invoker: &WslInvoker<'_>,
517 name: &str,
518) -> Result<DistributionReadiness, WslError> {
519 validate_distribution_name(name)?;
520 let table = invoker.list()?;
521 let installed = table.exactly(name)?;
522 installed.require_wsl2()?;
523 let wsl_version = installed.wsl_version();
524 let default = installed.is_default();
525
526 let identity = invoker.exec(LinuxCommand::new(name, "id").args(["-u"]))?;
530 if !identity.success() {
531 return Err(WslError::CommandFailed {
537 what: "verify root access in the distribution",
538 program: PathBuf::from("id"),
539 exit_code: identity.exit_code(),
540 detail: identity.diagnostic(),
541 });
542 }
543 let reported = identity.stdout_text();
544 if reported != "0" {
545 return Err(WslError::NoRootAccess {
546 distribution: name.to_string(),
547 detail: format!("`id -u` answered {reported}, not 0"),
548 });
549 }
550
551 let uname = invoker.exec_ok(
552 "read the distribution's architecture",
553 LinuxCommand::new(name, "uname").args(["-m"]),
554 )?;
555 let machine = uname.stdout_text();
556 let architecture = architecture_from_uname(name, &machine)?;
557
558 let systemd_report =
561 invoker.exec(LinuxCommand::new(name, "systemctl").args(["is-system-running"]))?;
562 if !systemd_report.success()
568 && systemd_report.stdout_text().is_empty()
569 && systemd_report.diagnostic().contains("Wsl/")
570 {
571 return Err(WslError::CommandFailed {
572 what: "verify systemd in the distribution",
573 program: PathBuf::from("systemctl"),
574 exit_code: systemd_report.exit_code(),
575 detail: systemd_report.diagnostic(),
576 });
577 }
578 let systemd = SystemdState::from_report(&systemd_report.stdout_text());
579 if !systemd.is_usable() {
580 return Err(WslError::SystemdUnavailable {
581 distribution: name.to_string(),
582 detail: match &systemd {
583 SystemdState::Unavailable(word) => {
584 let stderr = systemd_report.stderr_text();
585 if stderr.is_empty() {
586 format!("`systemctl is-system-running` answered `{word}`")
587 } else {
588 format!("`systemctl is-system-running` answered `{word}`: {stderr}")
589 }
590 }
591 other => other.to_string(),
592 },
593 });
594 }
595
596 Ok(DistributionReadiness {
597 name: name.to_string(),
598 wsl_version,
599 default,
600 architecture,
601 machine,
602 systemd,
603 })
604}
605
606#[cfg(test)]
607mod tests {
608 use super::*;
609 use crate::wsl::exec::{PipedInput, ScriptedRunner};
610 use secrecy::{ExposeSecret, SecretString};
611
612 fn executable() -> WslExecutable {
613 WslExecutable::at("wsl.exe")
614 }
615
616 fn table() -> CommandOutput {
617 CommandOutput::exited(
618 0,
619 concat!(
620 " NAME STATE VERSION\n",
621 "* Ubuntu Running 2\n",
622 " Legacy Stopped 1\n",
623 ),
624 "",
625 )
626 }
627
628 fn healthy() -> ScriptedRunner {
631 ScriptedRunner::new()
632 .always("--list --verbose", table())
633 .always("--exec id -u", CommandOutput::exited(0, "0\n", ""))
634 .always("--exec uname -m", CommandOutput::exited(0, "x86_64\n", ""))
635 .always(
636 "--exec systemctl is-system-running",
637 CommandOutput::exited(0, "running\n", ""),
638 )
639 }
640
641 #[test]
644 fn a_linux_command_is_an_argument_vector_with_no_shell_in_it() {
645 let command = LinuxCommand::new("Debian GNU/Linux 12", "/usr/local/bin/runner-manager")
646 .args(["service", "install", "--start-at", "boot"]);
647 assert_eq!(
648 command.wsl_arguments(),
649 vec![
650 "--distribution",
651 "Debian GNU/Linux 12",
652 "--user",
653 "root",
654 "--exec",
655 "/usr/local/bin/runner-manager",
656 "service",
657 "install",
658 "--start-at",
659 "boot",
660 ]
661 );
662 }
663
664 #[test]
665 fn a_hostile_distribution_name_stays_one_argument() {
666 let command = LinuxCommand::new("Ubuntu; rm -rf /", "id").args(["-u"]);
667 let argv = command.wsl_arguments();
668 assert_eq!(argv[1], "Ubuntu; rm -rf /");
669 assert!(
670 argv.iter().all(|argument| argument != "rm"),
671 "the name must never become its own argument: {argv:?}"
672 );
673 assert!(argv.contains(&"--exec".to_string()));
676 }
677
678 #[test]
679 fn the_invoker_passes_the_vector_through_untouched() {
680 let runner = healthy();
681 let executable = executable();
682 let invoker = WslInvoker::new(&runner, &executable);
683 invoker
684 .exec(LinuxCommand::new("Ubuntu", "id").args(["-u"]))
685 .expect("scripted");
686 let recorded = runner.recorded();
687 assert_eq!(recorded.len(), 1);
688 assert_eq!(
689 recorded[0].arguments,
690 vec![
691 "--distribution",
692 "Ubuntu",
693 "--user",
694 "root",
695 "--exec",
696 "id",
697 "-u"
698 ]
699 );
700 }
701
702 #[test]
703 fn a_name_that_reads_as_an_option_is_refused_before_anything_is_launched() {
704 let runner = ScriptedRunner::new();
705 let executable = executable();
706 let invoker = WslInvoker::new(&runner, &executable);
707 let error = invoker
708 .exec(LinuxCommand::new("--shutdown", "id"))
709 .expect_err("refused");
710 assert!(matches!(error, WslError::InvalidName { .. }), "{error:?}");
711 assert_eq!(runner.call_count(), 0, "nothing may be launched");
712 }
713
714 #[test]
715 fn recovery_can_terminate_only_one_validated_named_distribution() {
716 let runner =
717 ScriptedRunner::new().always("--terminate Ubuntu", CommandOutput::exited(0, "", ""));
718 let executable = executable();
719 let invoker = WslInvoker::new(&runner, &executable);
720 invoker
721 .terminate_named("Ubuntu")
722 .expect("named termination");
723 assert_eq!(
724 runner.recorded()[0].arguments,
725 vec!["--terminate", "Ubuntu"]
726 );
727 assert!(
728 runner
729 .recorded()
730 .iter()
731 .all(|request| !request.arguments.iter().any(|arg| arg == "--shutdown"))
732 );
733
734 let error = invoker
735 .terminate_named("--shutdown")
736 .expect_err("option-like names are refused");
737 assert!(matches!(error, WslError::InvalidName { .. }));
738 assert_eq!(runner.call_count(), 1);
739 }
740
741 #[test]
742 fn a_piped_payload_reaches_the_child_and_no_argument() {
743 let secret = SecretString::from(format!("{}{}", "ghu_", "a1ProbeFixtureNotARealToken0000"));
744 let runner = ScriptedRunner::new();
745 let executable = executable();
746 let invoker = WslInvoker::new(&runner, &executable);
747 invoker
748 .exec(
749 LinuxCommand::new("Ubuntu", "/usr/local/bin/runner-manager")
750 .args(["auth", "receive", "--start-at", "boot"])
751 .with_input(ChildInput::Piped(PipedInput::from_secret_text(&secret))),
752 )
753 .expect("scripted");
754 assert_eq!(runner.piped_input(), secret.expose_secret().as_bytes());
755 assert!(
756 runner
757 .command_lines()
758 .iter()
759 .all(|line| !line.contains(secret.expose_secret())),
760 "the canary must not be in any command line"
761 );
762 }
763
764 #[test]
767 fn a_healthy_distribution_reports_every_fact_it_established() {
768 let runner = healthy();
769 let executable = executable();
770 let ready =
771 probe_readiness(&WslInvoker::new(&runner, &executable), "Ubuntu").expect("healthy");
772 assert_eq!(ready.name(), "Ubuntu");
773 assert_eq!(ready.wsl_version(), 2);
774 assert!(ready.is_default());
775 assert_eq!(ready.architecture(), Arch::X64);
776 assert_eq!(ready.machine(), "x86_64");
777 assert_eq!(ready.systemd(), &SystemdState::Running);
778 }
779
780 #[test]
781 fn a_wsl1_distribution_is_refused_before_any_linux_command_runs() {
782 let runner = healthy();
783 let executable = executable();
784 let error =
785 probe_readiness(&WslInvoker::new(&runner, &executable), "Legacy").expect_err("WSL1");
786 assert!(matches!(error, WslError::NotWsl2 { .. }), "{error:?}");
787 assert_eq!(
788 runner.call_count(),
789 1,
790 "only `--list` should have run: {:?}",
791 runner.command_lines()
792 );
793 }
794
795 #[test]
796 fn a_distribution_that_is_not_installed_lists_the_ones_that_are() {
797 let runner = healthy();
798 let executable = executable();
799 let error =
800 probe_readiness(&WslInvoker::new(&runner, &executable), "Fedora").expect_err("absent");
801 assert!(error.to_string().contains("Ubuntu"), "{error}");
802 }
803
804 #[test]
805 fn a_distribution_that_does_not_start_as_root_is_refused() {
806 let runner = ScriptedRunner::new()
807 .always("--list --verbose", table())
808 .always("--exec id -u", CommandOutput::exited(0, "1000\n", ""));
809 let executable = executable();
810 let error = probe_readiness(&WslInvoker::new(&runner, &executable), "Ubuntu")
811 .expect_err("not root");
812 let WslError::NoRootAccess { detail, .. } = &error else {
813 panic!("unexpected error: {error:?}");
814 };
815 assert!(detail.contains("1000"), "{detail}");
816 }
817
818 #[test]
819 fn a_wsl_startup_failure_is_not_mislabeled_as_missing_root_access() {
820 let diagnostic = concat!(
821 "A connection attempt failed because the connected party did not respond.\n",
822 "Error code: Wsl/Service/0x8007274c\n",
823 );
824 let runner = ScriptedRunner::new()
825 .always("--list --verbose", table())
826 .always("--exec id -u", CommandOutput::exited(-1, "", diagnostic));
827 let executable = executable();
828 let error = probe_readiness(&WslInvoker::new(&runner, &executable), "Ubuntu")
829 .expect_err("WSL startup failed");
830
831 let WslError::CommandFailed {
832 what,
833 exit_code,
834 detail,
835 ..
836 } = &error
837 else {
838 panic!("unexpected error: {error:?}");
839 };
840 assert_eq!(*what, "verify root access in the distribution");
841 assert_eq!(*exit_code, Some(-1));
842 assert!(detail.contains("Wsl/Service/0x8007274c"), "{detail}");
843 assert!(
844 !error.to_string().contains("does not start as root"),
845 "{error}"
846 );
847 }
848
849 #[test]
850 fn an_unsupported_architecture_names_what_the_distribution_said() {
851 let runner = ScriptedRunner::new()
852 .always("--list --verbose", table())
853 .always("--exec id -u", CommandOutput::exited(0, "0\n", ""))
854 .always("--exec uname -m", CommandOutput::exited(0, "armv7l\n", ""));
855 let executable = executable();
856 let error = probe_readiness(&WslInvoker::new(&runner, &executable), "Ubuntu")
857 .expect_err("armv7l has no published artifact");
858 assert!(
859 matches!(&error, WslError::UnsupportedArchitecture { reported, .. } if reported == "armv7l"),
860 "{error:?}"
861 );
862 assert!(error.to_string().contains("armv7l"));
863 }
864
865 #[test]
866 fn architecture_mapping_covers_both_published_linux_targets_and_nothing_else() {
867 assert_eq!(
868 architecture_from_uname("d", "x86_64").expect("x64"),
869 Arch::X64
870 );
871 assert_eq!(
872 architecture_from_uname("d", "amd64").expect("x64"),
873 Arch::X64
874 );
875 assert_eq!(
876 architecture_from_uname("d", "aarch64").expect("arm64"),
877 Arch::Arm64
878 );
879 assert_eq!(
880 architecture_from_uname("d", "arm64\n").expect("arm64"),
881 Arch::Arm64
882 );
883 for machine in ["armv7l", "i686", "riscv64", "s390x", ""] {
884 assert!(
885 architecture_from_uname("d", machine).is_err(),
886 "{machine} has no published Linux artifact"
887 );
888 }
889 }
890
891 #[test]
892 fn a_distribution_without_systemd_is_refused_with_what_it_answered() {
893 let runner = ScriptedRunner::new()
894 .always("--list --verbose", table())
895 .always("--exec id -u", CommandOutput::exited(0, "0\n", ""))
896 .always("--exec uname -m", CommandOutput::exited(0, "x86_64\n", ""))
897 .always(
898 "--exec systemctl is-system-running",
899 CommandOutput::exited(1, "offline\n", ""),
900 );
901 let executable = executable();
902 let error = probe_readiness(&WslInvoker::new(&runner, &executable), "Ubuntu")
903 .expect_err("no systemd");
904 let WslError::SystemdUnavailable { detail, .. } = &error else {
905 panic!("unexpected error: {error:?}");
906 };
907 assert!(detail.contains("offline"), "{detail}");
908 }
909
910 #[test]
911 fn a_wsl_transport_failure_during_systemd_probe_is_not_mislabeled_as_missing_systemd() {
912 let runner = ScriptedRunner::new()
913 .always("--list --verbose", table())
914 .always("--exec id -u", CommandOutput::exited(0, "0\n", ""))
915 .always("--exec uname -m", CommandOutput::exited(0, "x86_64\n", ""))
916 .always(
917 "--exec systemctl is-system-running",
918 CommandOutput::exited(
919 1,
920 "",
921 "A connection attempt failed. Error code: Wsl/Service/0x8007274c",
922 ),
923 );
924 let executable = executable();
925 let error = probe_readiness(&WslInvoker::new(&runner, &executable), "Ubuntu")
926 .expect_err("the transport failed");
927 assert!(matches!(error, WslError::CommandFailed { .. }), "{error:?}");
928 assert!(error.to_string().contains("0x8007274c"), "{error}");
929 }
930
931 #[test]
932 fn degraded_and_starting_systemd_are_usable_because_the_products_own_unit_is_what_matters() {
933 for word in ["degraded", "starting", "initializing"] {
934 let runner = ScriptedRunner::new()
935 .always("--list --verbose", table())
936 .always("--exec id -u", CommandOutput::exited(0, "0\n", ""))
937 .always("--exec uname -m", CommandOutput::exited(0, "x86_64\n", ""))
938 .always(
939 "--exec systemctl is-system-running",
940 CommandOutput::exited(1, format!("{word}\n"), ""),
941 );
942 let executable = executable();
943 let ready = probe_readiness(&WslInvoker::new(&runner, &executable), "Ubuntu")
944 .unwrap_or_else(|error| panic!("{word} should be usable: {error}"));
945 assert!(ready.systemd().is_usable());
946 }
947 }
948
949 #[test]
950 fn the_preflight_mutates_nothing() {
951 let runner = healthy();
955 let executable = executable();
956 probe_readiness(&WslInvoker::new(&runner, &executable), "Ubuntu").expect("healthy");
957 for line in runner.command_lines() {
958 assert!(
959 [
960 "--list --verbose",
961 "id -u",
962 "uname -m",
963 "systemctl is-system-running"
964 ]
965 .iter()
966 .any(|read| line.contains(read)),
967 "the preflight ran something that is not a read: {line}"
968 );
969 }
970 }
971
972 #[test]
975 fn a_missing_system_root_falls_back_to_the_path_lookup() {
976 assert_eq!(locate_from(None, "wsl.exe"), PathBuf::from("wsl.exe"));
977 assert_eq!(
978 locate_from(Some(PathBuf::from("Q:\\NoSuchWindows")), "wsl.exe"),
979 PathBuf::from("wsl.exe")
980 );
981 }
982
983 #[test]
984 fn a_system_root_that_really_holds_the_executable_is_used() {
985 let root = tempfile::tempdir().expect("a temporary directory");
986 let system32 = root.path().join("System32");
987 std::fs::create_dir_all(&system32).expect("create System32");
988 std::fs::write(system32.join("wsl.exe"), b"not really an executable")
989 .expect("write the stand-in");
990 assert_eq!(
991 locate_from(Some(root.path().to_path_buf()), "wsl.exe"),
992 system32.join("wsl.exe")
993 );
994 }
995}