diff --git a/crates/pet-uv/src/lib.rs b/crates/pet-uv/src/lib.rs index ffd29c9e..c415fd39 100644 --- a/crates/pet-uv/src/lib.rs +++ b/crates/pet-uv/src/lib.rs @@ -350,21 +350,33 @@ fn parse_version_from_uv_dir_name(dir_name: &str) -> Option { return None; } // Verify at minimum X.Y format (e.g., "3.12" or "3.12.3"). - // Components before the last must be purely numeric. - // The last component may have a pre-release suffix (e.g., "0a4", "0rc1"). + // Major and minor must always be purely numeric. + // Only the patch component (3rd+) may have a pre-release suffix (e.g., "0a4", "0rc1"). let components: Vec<&str> = version.split('.').collect(); if components.len() < 2 { return None; } - // All components except the last must be purely numeric. - let all_but_last = &components[..components.len() - 1]; - if !all_but_last - .iter() - .all(|c| !c.is_empty() && c.chars().all(|ch| ch.is_ascii_digit())) - { + + let is_numeric = |c: &str| !c.is_empty() && c.chars().all(|ch| ch.is_ascii_digit()); + + // Major and minor must be purely numeric. + if !is_numeric(components[0]) || !is_numeric(components[1]) { + return None; + } + + // For X.Y versions (no patch), we're done. + if components.len() == 2 { + return Some(version.to_string()); + } + + // Any components between minor and the last must be purely numeric. + let middle = &components[2..components.len() - 1]; + if !middle.iter().all(|c| is_numeric(c)) { return None; } - // The last component must start with a digit (allows pre-release suffix like "0a4"). + + // The last component (patch or beyond) must start with a digit + // (allows pre-release suffix like "0a4"). let last = components.last()?; if last.is_empty() || !last.starts_with(|ch: char| ch.is_ascii_digit()) { return None; @@ -1141,6 +1153,11 @@ exclude = ["packages/legacy"]"#; parse_version_from_uv_dir_name("cpython-3.12abc.1-linux"), None ); + // 2-component version with non-numeric minor must be rejected. + assert_eq!( + parse_version_from_uv_dir_name("cpython-3.12abc-linux-x86_64-gnu"), + None + ); } #[test] diff --git a/crates/pet/tests/jsonrpc_client.rs b/crates/pet/tests/jsonrpc_client.rs index 2f43f845..86fcad01 100644 --- a/crates/pet/tests/jsonrpc_client.rs +++ b/crates/pet/tests/jsonrpc_client.rs @@ -95,8 +95,8 @@ pub struct PetJsonRpcClient { impl PetJsonRpcClient { pub fn spawn() -> Result { - let mut process = Command::new(env!("CARGO_BIN_EXE_pet")) - .arg("server") + let mut cmd = Command::new(env!("CARGO_BIN_EXE_pet")); + cmd.arg("server") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) @@ -104,11 +104,14 @@ impl PetJsonRpcClient { // configuration from leaking into the test environment, then // restore only the minimum required for the OS to function. .env_clear() - .env("PATH", "") - .env( - "SYSTEMROOT", - std::env::var("SYSTEMROOT").unwrap_or_default(), - ) + .env("PATH", ""); + // On Windows, SYSTEMROOT is required for basic OS functionality + // (crypto, networking, etc.). Only set it when present. + #[cfg(windows)] + if let Ok(val) = std::env::var("SYSTEMROOT") { + cmd.env("SYSTEMROOT", val); + } + let mut process = cmd .spawn() .map_err(|e| format!("Failed to spawn pet server: {e}"))?;