Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

553 linesCodeBlameRaw
1//! What a computer still lacks before a kind of job can run sandboxed,
2//! each by name (`status --json`'s `needs`), found before anything is
3//! downloaded: the desktop app says each in plain words and sets up those
4//! it can, behind one administrator prompt.
5//!
6//! | Need | Kind of job | What is missing | Set up by the app |
7//! | --- | --- | --- | --- |
8//! | `restart` | Linux, Windows | Windows features were turned on, and take effect after a restart | No: Restart now |
9//! | `windows_update` | Linux, Windows | Windows older than 2004 (WSL 2), or than 11 24H2 (`wsb`) | No: Windows Update |
10//! | `virtualization` | all | Virtualization (VT-x, AMD-V) off in the firmware | No: steps |
11//! | `nested_virtualization` | all | A VM whose host doesn't pass virtualization through | No: steps |
12//! | `vm_platform` | Linux on Windows | The Virtual Machine Platform feature | Yes |
13//! | `wsl` | Linux on Windows | WSL | Yes |
14//! | `wsl_update` | Linux on Windows | WSL older than 2.0, or the one built into Windows | Yes |
15//! | `windows_edition` | Windows | Windows Home, which has no Windows Sandbox | No: Linux jobs still run |
16//! | `windows_sandbox` | Windows | The Windows Sandbox feature | Yes |
17//! | `kvm_module` | Linux on Linux | `/dev/kvm`: its module isn't loaded | No: command |
18//! | `kvm_group` | Linux on Linux | Permission to use `/dev/kvm` | No: command |
19//! | `qemu` | Linux on Linux | QEMU | No: command |
20//! | `macos_update` | Linux on macOS | macOS older than 13 | No: Software Update |
21//! | `homebrew` | Linux on macOS | Homebrew, which installs vfkit | No: steps |
22//! | `vfkit` | Linux on macOS | vfkit | No: command |
23//!
24//! Each is said only when the facts say it for certain: a computer that
25//! can run the sandbox is never held back by a guess. What can't be known
26//! beforehand (virtualization inside a VM) is named from the error that
27//! starting the sandbox gives instead.
28
29use std::collections::BTreeMap;
30use std::path::Path;
31use std::process::{Command, Stdio};
32
33use serde::{Deserialize, Serialize};
34
35/// The file the desktop app's setup leaves in the launcher's state folder
36/// when what it turned on takes a restart: this boot's time, as
37/// [`WindowsFacts::booted`] says it.
38pub const RESTART_FILE: &str = "restart-pending";
39
40/// The oldest Windows build with WSL 2 (Windows 10 2004).
41pub const WSL2_BUILD: u32 = 19041;
42/// The oldest WSL the sandbox takes: the one from the Microsoft Store, 2.0 or later.
43pub const WSL_VERSION: (u32, u32, u32) = (2, 0, 0);
44/// The oldest macOS vfkit runs on.
45pub const MACOS_MAJOR: u32 = 13;
46
47#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
48#[serde(rename_all = "snake_case")]
49pub enum Need {
50 Restart,
51 WindowsUpdate,
52 Virtualization,
53 NestedVirtualization,
54 VmPlatform,
55 Wsl,
56 WslUpdate,
57 WindowsEdition,
58 WindowsSandbox,
59 KvmModule,
60 KvmGroup,
61 Qemu,
62 MacosUpdate,
63 Homebrew,
64 Vfkit,
65}
66
67impl Need {
68 /// Why, and what to do, in the launcher's words: what `status` says
69 /// as a capability's `cause` and `fix`. The first sentences of those a
70 /// desktop app from before `needs` reads are kept as they were.
71 pub fn said(self, kind: &str) -> (String, String) {
72 let (cause, fix) = match (self, kind) {
73 (Need::Restart, _) => (
74 "Windows needs a restart to finish turning on what the sandbox needs.",
75 "Restart Windows; the sandbox starts after.",
76 ),
77 (Need::WindowsUpdate, "windows") => (
78 "This Windows has Windows Sandbox but not its command line, wsb, which came in Windows 11 24H2.",
79 "Update Windows to 24H2 or later.",
80 ),
81 (Need::WindowsUpdate, _) => (
82 "This Windows is older than Windows 10 version 2004, the first with WSL 2.",
83 "Update Windows under Settings, Windows Update, then restart.",
84 ),
85 (Need::Virtualization, "linux") if cfg!(target_os = "linux") => (
86 "This machine has neither Sysbox nor KVM: virtualization (Intel VT-x or AMD-V) is off in its firmware, or its processor has none.",
87 "Turn on Intel VT-x or AMD-V in the firmware (BIOS or UEFI) settings and restart, or install Sysbox 0.7 or later.",
88 ),
89 (Need::Virtualization, _) => (
90 "Virtualization (Intel VT-x or AMD-V) is turned off in this computer's firmware, and the sandbox is a small virtual machine.",
91 "Restart into the firmware (BIOS or UEFI) settings, turn on Intel Virtualization Technology (VT-x) or SVM Mode (AMD-V), save and restart.",
92 ),
93 (Need::NestedVirtualization, _) => (
94 "This computer is a virtual machine whose host doesn't pass virtualization through to it, and the sandbox is a virtual machine inside it.",
95 "Turn on nested virtualization for this VM on its host, then start it again: on Hyper-V, `Set-VMProcessor -VMName <name> -ExposeVirtualizationExtensions $true` with the VM off.",
96 ),
97 (Need::VmPlatform, _) => (
98 "Virtual Machine Platform, the Windows feature WSL 2 runs on, is turned off.",
99 "Open a terminal as administrator and run `wsl --install --no-distribution`, which turns it on, then restart Windows.",
100 ),
101 (Need::Wsl, _) => (
102 "WSL is not installed, and the sandbox for Linux jobs is a WSL distribution of its own.",
103 "Open a terminal as administrator and run `wsl --install --no-distribution`, then restart Windows. It works on every edition of Windows 11, Home included.",
104 ),
105 (Need::WslUpdate, _) => (
106 "This WSL is older than the one the sandbox needs: WSL 2.0 or later, from the Microsoft Store.",
107 "Open a terminal as administrator and run `wsl --update`.",
108 ),
109 (Need::WindowsEdition, _) => (
110 "Windows Sandbox, which Windows jobs run in, isn't in Windows Home.",
111 "Linux jobs run on every edition. Windows jobs need Windows 11 Pro, Enterprise or Education.",
112 ),
113 (Need::WindowsSandbox, _) => (
114 "Windows Sandbox is not turned on, and Windows jobs each run in one.",
115 "Turn on Windows Sandbox under Settings, System, Optional features, More Windows features, then restart.",
116 ),
117 (Need::KvmModule, _) => (
118 "This machine has neither Sysbox nor KVM: its processor can run virtual machines, but /dev/kvm isn't there.",
119 "Load KVM with `sudo modprobe kvm_intel` (or `kvm_amd`); if it says the firmware disabled it, turn on VT-x or AMD-V there. Or install Sysbox 0.7 or later.",
120 ),
121 (Need::KvmGroup, _) => ("This user may not use /dev/kvm.", "Run `sudo usermod -aG kvm $USER`, then log out and in again."),
122 (Need::Qemu, _) => (
123 "QEMU, which runs the sandbox's VM, is not installed.",
124 "Install QEMU (qemu-system-x86 on Debian and Ubuntu, qemu-kvm on Fedora), or Sysbox 0.7 or later.",
125 ),
126 (Need::MacosUpdate, _) => ("The sandbox's VM needs macOS 13 or later.", "Update macOS under System Settings, General, Software Update."),
127 (Need::Homebrew, _) => (
128 "vfkit, which runs the sandbox's VM with Apple's Virtualization framework, is not installed, nor is Homebrew, which installs it.",
129 "Install Homebrew (brew.sh), then run `brew install vfkit`.",
130 ),
131 (Need::Vfkit, _) => (
132 "vfkit, which runs the sandbox's VM with Apple's Virtualization framework, is not installed.",
133 "Install it with `brew install vfkit` (macOS 13 or later), then start the sandbox again.",
134 ),
135 };
136 (cause.to_owned(), fix.to_owned())
137 }
138}
139
140// --- Windows ------------------------------------------------------------------------
141
142/// What Windows says of itself, read without an administrator. Each is
143/// None (or empty) when it couldn't be read, and nothing is concluded from it.
144#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
145#[serde(default)]
146pub struct WindowsFacts {
147 /// `EditionID`: `Core` (Home), `Professional`, `Enterprise`, …
148 pub edition: Option<String>,
149 pub build: Option<u32>,
150 /// A hypervisor is running (`Win32_ComputerSystem.HypervisorPresent`).
151 pub hypervisor: Option<bool>,
152 /// Virtualization on in the firmware (`Win32_Processor`). It reads
153 /// false whenever a hypervisor is running, so it says something only
154 /// when none is.
155 pub firmware: Option<bool>,
156 pub model: Option<String>,
157 pub manufacturer: Option<String>,
158 /// `Win32_OptionalFeature.InstallState`: 1 on, 2 off, 3 not in this edition.
159 pub features: BTreeMap<String, u32>,
160 /// When Windows started, in UTC.
161 pub booted: Option<String>,
162}
163
164pub const VM_PLATFORM: &str = "VirtualMachinePlatform";
165pub const WINDOWS_SANDBOX: &str = "Containers-DisposableClientVM";
166
167/// The facts, as one PowerShell call prints them: about a second, so only
168/// asked while something is missing.
169const PROBE: &str = r#"$ErrorActionPreference = 'SilentlyContinue'
170$v = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
171$cs = Get-CimInstance Win32_ComputerSystem
172$cpu = Get-CimInstance Win32_Processor | Select-Object -First 1
173$features = @{}
174Get-CimInstance Win32_OptionalFeature | Where-Object { $_.Name -in @('VirtualMachinePlatform', 'Containers-DisposableClientVM') } | ForEach-Object { $features[$_.Name] = [int]$_.InstallState }
175$os = Get-CimInstance Win32_OperatingSystem
176[pscustomobject]@{
177 edition = $v.EditionID
178 build = [int]$v.CurrentBuildNumber
179 hypervisor = $cs.HypervisorPresent
180 firmware = $cpu.VirtualizationFirmwareEnabled
181 model = $cs.Model
182 manufacturer = $cs.Manufacturer
183 features = $features
184 booted = $os.LastBootUpTime.ToUniversalTime().ToString('o')
185} | ConvertTo-Json -Compress"#;
186
187/// PowerShell's `-EncodedCommand`: a script as UTF-16LE in base64, so
188/// nothing in it is read by a shell on the way.
189pub fn encoded(script: &str) -> String {
190 use base64::Engine;
191 let bytes: Vec<u8> = script.encode_utf16().flat_map(u16::to_le_bytes).collect();
192 base64::engine::general_purpose::STANDARD.encode(bytes)
193}
194
195impl WindowsFacts {
196 /// Asks Windows, once a process: what it can't say is left unknown.
197 pub fn look() -> WindowsFacts {
198 static FACTS: std::sync::OnceLock<WindowsFacts> = std::sync::OnceLock::new();
199 FACTS
200 .get_or_init(|| {
201 let mut command = Command::new("powershell.exe");
202 command.args(["-NoProfile", "-NonInteractive", "-EncodedCommand", &encoded(PROBE)]).stdin(Stdio::null()).stderr(Stdio::null());
203 super::native::hide_window(&mut command);
204 command.output().ok().and_then(|out| serde_json::from_slice(&out.stdout).ok()).unwrap_or_default()
205 })
206 .clone()
207 }
208
209 /// Windows Home (`Core`, `CoreN`, `CoreSingleLanguage`, `CoreCountrySpecific`).
210 pub fn home(&self) -> bool {
211 self.edition.as_deref().is_some_and(|e| e.starts_with("Core"))
212 }
213
214 /// This Windows is itself a virtual machine.
215 pub fn virtual_machine(&self) -> bool {
216 let said = format!("{} {}", self.model.as_deref().unwrap_or(""), self.manufacturer.as_deref().unwrap_or("")).to_ascii_lowercase();
217 ["virtual machine", "vmware", "virtualbox", "qemu", "kvm", "parallels", "xen", "bochs"].iter().any(|vm| said.contains(vm))
218 }
219
220 /// A feature on (Some(true)), off or not here (Some(false)), or not known.
221 pub fn feature(&self, name: &str) -> Option<bool> {
222 self.features.get(name).map(|state| *state == 1)
223 }
224
225 /// Virtualization is off in the firmware, for certain: a physical
226 /// computer with no hypervisor running whose processor says so.
227 pub fn firmware_off(&self) -> bool {
228 !self.virtual_machine() && self.hypervisor == Some(false) && self.firmware == Some(false)
229 }
230
231 /// What the computer lacks for virtualization, said from an error
232 /// that starting a VM gave: off in the firmware, or not passed through to this VM.
233 pub fn no_virtualization(&self) -> Need {
234 if self.virtual_machine() { Need::NestedVirtualization } else { Need::Virtualization }
235 }
236}
237
238/// Whether setup left a restart to do, this boot: the file setup wrote
239/// names the boot it ran in. One from an earlier boot is removed.
240pub fn restart_pending(dir: &Path, facts: &WindowsFacts) -> bool {
241 let path = dir.join(RESTART_FILE);
242 let Ok(said) = std::fs::read_to_string(&path) else { return false };
243 match facts.booted.as_deref() {
244 Some(booted) if said.trim() == booted => true,
245 Some(_) => {
246 let _ = std::fs::remove_file(&path);
247 false
248 }
249 // Not known: a restart is still to be done until Windows says otherwise.
250 None => true,
251 }
252}
253
254/// What WSL says of itself.
255#[derive(Clone, Debug, Default, PartialEq, Eq)]
256pub struct WslFacts {
257 /// `wsl --status` answers.
258 pub installed: bool,
259 /// `wsl --version`'s, which the WSL built into Windows doesn't answer.
260 pub version: Option<(u32, u32, u32)>,
261 /// A WSL 2 distribution is here already: WSL 2 itself works.
262 pub has_wsl2: bool,
263}
264
265/// The first `a.b.c` in `wsl --version`'s first line, in any language:
266/// `WSL version: 2.5.9.0`, `WSL-Version: 2.5.9.0`.
267pub fn wsl_version(text: &str) -> Option<(u32, u32, u32)> {
268 let line = text.replace('\0', "");
269 let first = line.lines().find(|l| !l.trim().is_empty())?.to_owned();
270 first.split(|c: char| !(c.is_ascii_digit() || c == '.')).find_map(|word| {
271 let mut parts = word.split('.').map(|p| p.parse::<u32>().ok());
272 match (parts.next().flatten(), parts.next().flatten(), parts.next().flatten()) {
273 (Some(a), Some(b), Some(c)) => Some((a, b, c)),
274 _ => None,
275 }
276 })
277}
278
279/// What Linux jobs on Windows still need, first what must come first.
280pub fn wsl_needs(win: &WindowsFacts, wsl: &WslFacts, restart: bool) -> Vec<Need> {
281 let mut needs = Vec::new();
282 if win.build.is_some_and(|b| b < WSL2_BUILD) {
283 return vec![Need::WindowsUpdate];
284 }
285 // A WSL 2 distribution that runs says virtualization and the platform are there.
286 if !wsl.has_wsl2 {
287 if win.firmware_off() && !restart {
288 return vec![Need::Virtualization];
289 }
290 if win.feature(VM_PLATFORM) == Some(false) {
291 needs.push(Need::VmPlatform);
292 }
293 }
294 if !wsl.installed {
295 needs.push(Need::Wsl);
296 } else if wsl.version.is_none_or(|v| v < WSL_VERSION) {
297 needs.push(Need::WslUpdate);
298 }
299 if restart && (needs.iter().any(|n| matches!(n, Need::VmPlatform)) || win.hypervisor == Some(false)) {
300 // What is left to set up after it is said once it's done.
301 return vec![Need::Restart];
302 }
303 needs
304}
305
306/// What Windows jobs still need: Windows Sandbox (`feature`, its program
307/// is here) and its command line (`cli`).
308pub fn windows_sandbox_needs(win: &WindowsFacts, feature: bool, cli: bool, restart: bool) -> Vec<Need> {
309 if feature && cli {
310 return vec![];
311 }
312 if feature {
313 return vec![Need::WindowsUpdate];
314 }
315 if win.home() || (win.feature(WINDOWS_SANDBOX).is_none() && win.features.contains_key(VM_PLATFORM)) {
316 // Home, or an edition whose feature list has no Windows Sandbox.
317 return vec![Need::WindowsEdition];
318 }
319 if restart {
320 return vec![Need::Restart];
321 }
322 if win.firmware_off() {
323 return vec![Need::Virtualization];
324 }
325 let mut needs = vec![Need::WindowsSandbox];
326 if win.build.is_some_and(|b| b < 26100) {
327 needs.push(Need::WindowsUpdate);
328 }
329 needs
330}
331
332/// Whether a sentence about a VM that wouldn't start is about virtualization.
333pub fn about_virtualization(text: &str) -> bool {
334 let lower = text.to_ascii_lowercase();
335 ["virtualization is not enabled", "enable virtualization", "ensure virtualization", "hcs_e_hyperv_not_installed", "hypervisor is not present", "vt-x", "nested virtualization"]
336 .iter()
337 .any(|said| lower.contains(said))
338}
339
340// --- Linux --------------------------------------------------------------------------
341
342/// What a Linux computer has for a QEMU VM with KVM.
343#[derive(Clone, Debug, Default, PartialEq, Eq)]
344pub struct KvmFacts {
345 /// `/dev/kvm` is there.
346 pub device: bool,
347 /// This user may open it.
348 pub usable: bool,
349 /// The processor says it can virtualize (`vmx`, `svm`): None on an
350 /// architecture whose flags don't say.
351 pub cpu: Option<bool>,
352 /// This Linux is itself a virtual machine (`hypervisor`).
353 pub guest: bool,
354 pub qemu: bool,
355}
356
357/// `/proc/cpuinfo`'s flags: whether the processor can virtualize, and
358/// whether this is a guest. On x86 only; elsewhere it says nothing.
359pub fn cpu_flags(cpuinfo: &str) -> (Option<bool>, bool) {
360 let Some(flags) = cpuinfo.lines().find(|l| l.starts_with("flags")).and_then(|l| l.split_once(':')).map(|(_, f)| f) else {
361 return (None, false);
362 };
363 let has = |flag: &str| flags.split_whitespace().any(|f| f == flag);
364 (Some(has("vmx") || has("svm")), has("hypervisor"))
365}
366
367pub fn kvm_needs(facts: &KvmFacts) -> Vec<Need> {
368 let mut needs = Vec::new();
369 if !facts.device {
370 needs.push(match facts.cpu {
371 Some(false) if facts.guest => Need::NestedVirtualization,
372 Some(false) => Need::Virtualization,
373 _ => Need::KvmModule,
374 });
375 } else if !facts.usable {
376 needs.push(Need::KvmGroup);
377 }
378 if !facts.qemu {
379 needs.push(Need::Qemu);
380 }
381 needs
382}
383
384// --- macOS --------------------------------------------------------------------------
385
386/// `sw_vers -productVersion`'s major version: `14.6.1` is 14.
387pub fn macos_major(text: &str) -> Option<u32> {
388 text.trim().split('.').next()?.parse().ok()
389}
390
391pub fn vfkit_needs(macos: Option<u32>, vfkit: bool, homebrew: bool) -> Vec<Need> {
392 if macos.is_some_and(|major| major < MACOS_MAJOR) {
393 return vec![Need::MacosUpdate];
394 }
395 match (vfkit, homebrew) {
396 (true, _) => vec![],
397 (false, true) => vec![Need::Vfkit],
398 (false, false) => vec![Need::Homebrew],
399 }
400}
401
402/// Where Homebrew puts programs, which a program a Mac app starts doesn't
403/// have on its PATH: Apple silicon's, then Intel's.
404pub const HOMEBREW_BINS: [&str; 2] = ["/opt/homebrew/bin", "/usr/local/bin"];
405
406#[cfg(test)]
407mod tests {
408 use super::*;
409
410 /// What the probe printed on a Windows 11 Pro desktop with Hyper-V running.
411 const PRO: &str = r#"{"edition":"Professional","build":26220,"hypervisor":true,"firmware":false,"model":"MS-7D25","manufacturer":"Micro-Star International Co., Ltd.","features":{"VirtualMachinePlatform":1,"Containers-DisposableClientVM":1},"booted":"2026-10-14T20:17:29.5000000Z"}"#;
412
413 fn facts(json: &str) -> WindowsFacts {
414 serde_json::from_str(json).unwrap()
415 }
416
417 /// A clean Windows 11 Home laptop: nothing turned on, virtualization on in its firmware.
418 fn clean_home() -> WindowsFacts {
419 facts(r#"{"edition":"Core","build":26100,"hypervisor":false,"firmware":true,"model":"XPS 13","manufacturer":"Dell Inc.","features":{"VirtualMachinePlatform":2},"booted":"2026-10-14T08:00:00Z"}"#)
420 }
421
422 fn wsl(installed: bool, version: Option<(u32, u32, u32)>, has_wsl2: bool) -> WslFacts {
423 WslFacts { installed, version, has_wsl2 }
424 }
425
426 #[test]
427 fn a_computer_that_has_everything_needs_nothing() {
428 let pro = facts(PRO);
429 assert!(!pro.home() && !pro.virtual_machine() && !pro.firmware_off());
430 // Hyper-V running: firmware reads false, and means nothing.
431 assert_eq!(wsl_needs(&pro, &wsl(true, Some((2, 5, 9)), true), false), vec![]);
432 assert_eq!(wsl_needs(&pro, &wsl(true, Some((2, 5, 9)), false), false), vec![]);
433 assert_eq!(windows_sandbox_needs(&pro, true, true, false), vec![]);
434 }
435
436 #[test]
437 fn a_clean_computer_needs_the_platform_and_wsl_set_up() {
438 let home = clean_home();
439 assert_eq!(wsl_needs(&home, &wsl(false, None, false), false), vec![Need::VmPlatform, Need::Wsl]);
440 // The WSL built into Windows answers --status but not --version.
441 assert_eq!(wsl_needs(&home, &wsl(true, None, false), false), vec![Need::VmPlatform, Need::WslUpdate]);
442 assert_eq!(wsl_needs(&home, &wsl(true, Some((1, 2, 5)), false), false), vec![Need::VmPlatform, Need::WslUpdate]);
443 // Home has no Windows Sandbox, and that's said as such, not as "off".
444 assert_eq!(windows_sandbox_needs(&home, false, false, false), vec![Need::WindowsEdition]);
445 }
446
447 #[test]
448 fn after_setup_it_says_restart_until_windows_has_restarted() {
449 let home = clean_home();
450 assert_eq!(wsl_needs(&home, &wsl(true, Some((2, 5, 9)), false), true), vec![Need::Restart]);
451 let pro = WindowsFacts { edition: Some("Professional".into()), features: [(VM_PLATFORM.to_owned(), 1), (WINDOWS_SANDBOX.to_owned(), 2)].into(), ..clean_home() };
452 assert_eq!(windows_sandbox_needs(&pro, false, false, true), vec![Need::Restart]);
453 // Restarted, with everything there: nothing.
454 let after = WindowsFacts { hypervisor: Some(true), firmware: Some(false), features: [(VM_PLATFORM.to_owned(), 1)].into(), ..clean_home() };
455 assert_eq!(wsl_needs(&after, &wsl(true, Some((2, 5, 9)), false), false), vec![]);
456 // The file names the boot it was written in; a later boot's facts remove it.
457 let dir = std::env::temp_dir().join(format!("g1t-needs-restart-{}", std::process::id()));
458 std::fs::create_dir_all(&dir).unwrap();
459 std::fs::write(dir.join(RESTART_FILE), "2026-10-14T08:00:00Z\r\n").unwrap();
460 assert!(restart_pending(&dir, &home));
461 let rebooted = WindowsFacts { booted: Some("2026-10-14T09:30:00Z".into()), ..clean_home() };
462 assert!(!restart_pending(&dir, &rebooted));
463 assert!(!dir.join(RESTART_FILE).exists());
464 let _ = std::fs::remove_dir_all(&dir);
465 }
466
467 #[test]
468 fn virtualization_off_in_the_firmware_comes_first_and_is_never_guessed() {
469 let off = WindowsFacts { firmware: Some(false), ..clean_home() };
470 assert!(off.firmware_off());
471 assert_eq!(wsl_needs(&off, &wsl(false, None, false), false), vec![Need::Virtualization]);
472 let pro = WindowsFacts { edition: Some("Professional".into()), features: [(VM_PLATFORM.to_owned(), 2), (WINDOWS_SANDBOX.to_owned(), 2)].into(), ..off.clone() };
473 assert_eq!(windows_sandbox_needs(&pro, false, false, false), vec![Need::Virtualization]);
474 // Pro whose feature list leaves Windows Sandbox out (Windows Server, N editions): no Windows Sandbox.
475 let without = WindowsFacts { features: [(VM_PLATFORM.to_owned(), 2)].into(), ..pro };
476 assert_eq!(windows_sandbox_needs(&without, false, false, false), vec![Need::WindowsEdition]);
477 // Not known: not said.
478 let unknown = WindowsFacts { firmware: None, ..off.clone() };
479 assert_eq!(wsl_needs(&unknown, &wsl(false, None, false), false), vec![Need::VmPlatform, Need::Wsl]);
480 // In a VM the firmware's flag says nothing; an error names it instead.
481 let guest = WindowsFacts { model: Some("Virtual Machine".into()), manufacturer: Some("Microsoft Corporation".into()), ..off };
482 assert!(guest.virtual_machine() && !guest.firmware_off());
483 assert_eq!(guest.no_virtualization(), Need::NestedVirtualization);
484 assert_eq!(clean_home().no_virtualization(), Need::Virtualization);
485 assert!(about_virtualization("WSL could not import the sandbox: Please enable the Virtual Machine Platform Windows feature and ensure virtualization is enabled in the BIOS."));
486 assert!(!about_virtualization("could not fetch https://g1t.sh"));
487 // Nothing read at all (PowerShell blocked): nothing concluded but what WSL says.
488 assert_eq!(wsl_needs(&WindowsFacts::default(), &wsl(false, None, false), false), vec![Need::Wsl]);
489 assert_eq!(windows_sandbox_needs(&WindowsFacts::default(), false, false, false), vec![Need::WindowsSandbox]);
490 }
491
492 #[test]
493 fn old_windows_says_update() {
494 let old = WindowsFacts { build: Some(18363), ..clean_home() };
495 assert_eq!(wsl_needs(&old, &wsl(false, None, false), false), vec![Need::WindowsUpdate]);
496 let pro = facts(PRO);
497 assert_eq!(windows_sandbox_needs(&pro, true, false, false), vec![Need::WindowsUpdate]);
498 let pro_23h2 = WindowsFacts { build: Some(22631), features: [(VM_PLATFORM.to_owned(), 1), (WINDOWS_SANDBOX.to_owned(), 2)].into(), ..pro };
499 assert_eq!(windows_sandbox_needs(&pro_23h2, false, false, false), vec![Need::WindowsSandbox, Need::WindowsUpdate]);
500 }
501
502 /// Asks this Windows: `cargo test -p g1t-runner -- --ignored this_windows --nocapture`.
503 #[test]
504 #[ignore]
505 fn this_windows_says_what_it_has() {
506 let facts = WindowsFacts::look();
507 println!("{facts:#?}");
508 assert!(facts.edition.is_some() && facts.build.is_some() && facts.booted.is_some(), "{facts:?}");
509 assert!(facts.features.contains_key(VM_PLATFORM), "{facts:?}");
510 }
511
512 #[test]
513 fn wsl_says_its_version_in_any_language() {
514 assert_eq!(wsl_version("WSL version: 2.5.9.0\nKernel version: 6.6.87.2-1\n"), Some((2, 5, 9)));
515 assert_eq!(wsl_version("W\0S\0L\0-\0V\0e\0r\0s\0i\0o\0n\0:\0 \x002\0.\x000\0.\x004\0.\x000\0"), Some((2, 0, 4)));
516 assert_eq!(wsl_version("Version de WSL : 1.2.5.0"), Some((1, 2, 5)));
517 assert_eq!(wsl_version("Invalid command line option: --version"), None);
518 }
519
520 #[test]
521 fn linux_says_what_kvm_lacks() {
522 assert_eq!(cpu_flags("processor\t: 0\nflags\t\t: fpu vme sse vmx ept\n"), (Some(true), false));
523 assert_eq!(cpu_flags("flags\t\t: fpu sse hypervisor\n"), (Some(false), true));
524 assert_eq!(cpu_flags("Features\t: fp asimd\n"), (None, false));
525 let all = KvmFacts { device: true, usable: true, cpu: Some(true), guest: false, qemu: true };
526 assert_eq!(kvm_needs(&all), vec![]);
527 assert_eq!(kvm_needs(&KvmFacts { usable: false, ..all.clone() }), vec![Need::KvmGroup]);
528 assert_eq!(kvm_needs(&KvmFacts { device: false, usable: false, ..all.clone() }), vec![Need::KvmModule]);
529 assert_eq!(kvm_needs(&KvmFacts { device: false, usable: false, cpu: Some(false), ..all.clone() }), vec![Need::Virtualization]);
530 assert_eq!(kvm_needs(&KvmFacts { device: false, usable: false, cpu: Some(false), guest: true, qemu: false }), vec![Need::NestedVirtualization, Need::Qemu]);
531 }
532
533 #[test]
534 fn a_mac_says_what_vfkit_lacks() {
535 assert_eq!(macos_major("14.6.1\n"), Some(14));
536 assert_eq!(vfkit_needs(Some(12), false, true), vec![Need::MacosUpdate]);
537 assert_eq!(vfkit_needs(Some(15), false, false), vec![Need::Homebrew]);
538 assert_eq!(vfkit_needs(Some(15), false, true), vec![Need::Vfkit]);
539 assert_eq!(vfkit_needs(None, true, false), vec![]);
540 }
541
542 #[test]
543 fn needs_are_said_by_name_and_old_apps_still_read_them() {
544 assert_eq!(serde_json::to_string(&Need::VmPlatform).unwrap(), r#""vm_platform""#);
545 assert_eq!(serde_json::from_str::<Need>(r#""nested_virtualization""#).unwrap(), Need::NestedVirtualization);
546 // A desktop app from before `needs` reads the cause's words.
547 assert!(Need::Wsl.said("linux").0.to_ascii_lowercase().contains("wsl is not installed"));
548 assert!(Need::WindowsSandbox.said("windows").0.to_ascii_lowercase().contains("windows sandbox is not turned on"));
549 assert!(Need::Virtualization.said("linux").0.to_ascii_lowercase().contains("vt-x"));
550 assert!(Need::Homebrew.said("linux").0.contains("vfkit") && Need::Homebrew.said("linux").0.contains("not installed"));
551 assert!(Need::WindowsUpdate.said("windows").0.contains("24H2"));
552 }
553}