Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 26 additions & 9 deletions crates/pet-uv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,21 +350,33 @@ fn parse_version_from_uv_dir_name(dir_name: &str) -> Option<String> {
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;
Expand Down Expand Up @@ -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]
Expand Down
17 changes: 10 additions & 7 deletions crates/pet/tests/jsonrpc_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,20 +95,23 @@ pub struct PetJsonRpcClient {

impl PetJsonRpcClient {
pub fn spawn() -> Result<Self, String> {
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())
// Clear all inherited env vars to prevent host-specific tool
// 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}"))?;

Expand Down
Loading