Skip to content

Commit d482aa7

Browse files
authored
test: add Mac python.org locator coverage (#430)
Adds focused direct coverage for the Mac python.org locator while continuing the #389 coverage work. - Adds deterministic tests for python.org framework path matching, locator metadata, and non-macOS rejection. - Narrows python.org classification to interpreter-shaped framework paths under /Library/Frameworks/Python.framework/Versions/{version}/bin/. - Covers false positives for python-config scripts, nested bin paths, Homebrew framework paths, and unrelated frameworks. Validation: - cargo fmt --all - wsl bash -lc 'cd /mnt/c/GIT/projects/python-environment-tools && cargo test -p pet-mac-python-org' - wsl bash -lc 'cd /mnt/c/GIT/projects/python-environment-tools && cargo clippy --all -- -D warnings' Refs #389
1 parent d26de2e commit d482aa7

1 file changed

Lines changed: 195 additions & 4 deletions

File tree

  • crates/pet-mac-python-org/src

crates/pet-mac-python-org/src/lib.rs

Lines changed: 195 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,7 @@ impl Locator for MacPythonOrg {
4646
}
4747

4848
let mut executable = resolve_symlink(&env.executable).unwrap_or(env.executable.clone());
49-
if !executable
50-
.to_string_lossy()
51-
.starts_with("/Library/Frameworks/Python.framework/Versions/")
52-
{
49+
if !is_mac_python_org_framework_path(&executable) {
5350
return None;
5451
}
5552

@@ -166,3 +163,197 @@ impl Locator for MacPythonOrg {
166163
}
167164
}
168165
}
166+
167+
fn is_mac_python_org_framework_path(executable: &std::path::Path) -> bool {
168+
let Ok(framework_entry) =
169+
executable.strip_prefix("/Library/Frameworks/Python.framework/Versions")
170+
else {
171+
return false;
172+
};
173+
174+
let mut framework_parts = framework_entry.components();
175+
matches!(
176+
framework_parts.next(),
177+
Some(std::path::Component::Normal(version))
178+
if version.to_str().is_some_and(is_macos_framework_version_dir)
179+
) && matches!(
180+
framework_parts.next(),
181+
Some(std::path::Component::Normal(part)) if part == std::ffi::OsStr::new("bin")
182+
) && matches!(
183+
framework_parts.next(),
184+
Some(std::path::Component::Normal(executable_name))
185+
if executable_name.to_str().is_some_and(is_macos_python_executable_name)
186+
) && framework_parts.next().is_none()
187+
}
188+
189+
fn is_macos_python_executable_name(executable: &str) -> bool {
190+
if executable == "python" || executable == "python3" {
191+
return true;
192+
}
193+
194+
let Some(minor) = executable.strip_prefix("python3.") else {
195+
return false;
196+
};
197+
198+
!minor.is_empty() && minor.chars().all(|ch| ch.is_ascii_digit())
199+
}
200+
201+
fn is_macos_framework_version_dir(version: &str) -> bool {
202+
if version == "Current" {
203+
return true;
204+
}
205+
206+
let mut parts = version.split('.');
207+
parts
208+
.next()
209+
.is_some_and(|major| !major.is_empty() && major.chars().all(|ch| ch.is_ascii_digit()))
210+
&& parts
211+
.next()
212+
.is_some_and(|minor| !minor.is_empty() && minor.chars().all(|ch| ch.is_ascii_digit()))
213+
&& parts.next().is_none()
214+
}
215+
216+
#[cfg(test)]
217+
mod tests {
218+
use super::*;
219+
use pet_core::Locator;
220+
use std::path::Path;
221+
222+
#[test]
223+
fn locator_metadata_matches_python_org_kind() {
224+
let locator = MacPythonOrg::new();
225+
226+
assert_eq!(locator.get_kind(), LocatorKind::MacPythonOrg);
227+
assert_eq!(
228+
locator.supported_categories(),
229+
vec![PythonEnvironmentKind::MacPythonOrg]
230+
);
231+
}
232+
233+
#[test]
234+
fn framework_path_accepts_versioned_python3() {
235+
assert!(is_mac_python_org_framework_path(Path::new(
236+
"/Library/Frameworks/Python.framework/Versions/3.12/bin/python3"
237+
)));
238+
}
239+
240+
#[test]
241+
fn framework_path_accepts_unversioned_python() {
242+
assert!(is_mac_python_org_framework_path(Path::new(
243+
"/Library/Frameworks/Python.framework/Versions/3.12/bin/python"
244+
)));
245+
}
246+
247+
#[test]
248+
fn framework_path_accepts_versioned_python_executable() {
249+
assert!(is_mac_python_org_framework_path(Path::new(
250+
"/Library/Frameworks/Python.framework/Versions/3.12/bin/python3.12"
251+
)));
252+
}
253+
254+
#[test]
255+
fn framework_path_accepts_current_python() {
256+
assert!(is_mac_python_org_framework_path(Path::new(
257+
"/Library/Frameworks/Python.framework/Versions/Current/bin/python3"
258+
)));
259+
}
260+
261+
#[test]
262+
fn framework_path_rejects_non_python_file() {
263+
assert!(!is_mac_python_org_framework_path(Path::new(
264+
"/Library/Frameworks/Python.framework/Versions/3.12/Resources/Info.plist"
265+
)));
266+
}
267+
268+
#[test]
269+
fn framework_path_rejects_python_config_script() {
270+
assert!(!is_mac_python_org_framework_path(Path::new(
271+
"/Library/Frameworks/Python.framework/Versions/3.12/bin/python-config"
272+
)));
273+
}
274+
275+
#[test]
276+
fn framework_path_rejects_versioned_python_config_script() {
277+
assert!(!is_mac_python_org_framework_path(Path::new(
278+
"/Library/Frameworks/Python.framework/Versions/3.12/bin/python3.12-config"
279+
)));
280+
}
281+
282+
#[test]
283+
fn framework_path_rejects_patch_version_python_name() {
284+
assert!(!is_mac_python_org_framework_path(Path::new(
285+
"/Library/Frameworks/Python.framework/Versions/3.12/bin/python3.12.0"
286+
)));
287+
}
288+
289+
#[test]
290+
fn framework_path_rejects_compact_version_python_name() {
291+
assert!(!is_mac_python_org_framework_path(Path::new(
292+
"/Library/Frameworks/Python.framework/Versions/3.12/bin/python312"
293+
)));
294+
}
295+
296+
#[test]
297+
fn framework_path_rejects_python2_name() {
298+
assert!(!is_mac_python_org_framework_path(Path::new(
299+
"/Library/Frameworks/Python.framework/Versions/3.12/bin/python2"
300+
)));
301+
}
302+
303+
#[test]
304+
fn framework_path_rejects_patch_version_dir() {
305+
assert!(!is_mac_python_org_framework_path(Path::new(
306+
"/Library/Frameworks/Python.framework/Versions/3.12.0/bin/python3"
307+
)));
308+
}
309+
310+
#[test]
311+
fn framework_path_rejects_invalid_version_dir() {
312+
assert!(!is_mac_python_org_framework_path(Path::new(
313+
"/Library/Frameworks/Python.framework/Versions/Foo/bin/python3"
314+
)));
315+
}
316+
317+
#[test]
318+
fn framework_path_rejects_other_framework() {
319+
assert!(!is_mac_python_org_framework_path(Path::new(
320+
"/Library/Frameworks/Other.framework/Versions/3.12/bin/python3"
321+
)));
322+
}
323+
324+
#[test]
325+
fn framework_path_rejects_non_library_path() {
326+
assert!(!is_mac_python_org_framework_path(Path::new(
327+
"/tmp/Python.framework/Versions/3.12/bin/python3"
328+
)));
329+
}
330+
331+
#[test]
332+
fn framework_path_rejects_homebrew_framework_path() {
333+
assert!(!is_mac_python_org_framework_path(Path::new(
334+
"/opt/homebrew/Cellar/python@3.12/3.12.1/Frameworks/Python.framework/Versions/3.12/bin/python3"
335+
)));
336+
}
337+
338+
#[test]
339+
fn framework_path_rejects_nested_bin_entry() {
340+
assert!(!is_mac_python_org_framework_path(Path::new(
341+
"/Library/Frameworks/Python.framework/Versions/3.12/bin/nested/python3"
342+
)));
343+
}
344+
345+
#[cfg(not(target_os = "macos"))]
346+
#[test]
347+
fn try_from_rejects_python_org_path_off_macos() {
348+
let locator = MacPythonOrg::new();
349+
let env = PythonEnv::new(
350+
PathBuf::from("/Library/Frameworks/Python.framework/Versions/3.12/bin/python3"),
351+
Some(PathBuf::from(
352+
"/Library/Frameworks/Python.framework/Versions/3.12",
353+
)),
354+
Some("3.12.0".to_string()),
355+
);
356+
357+
assert!(locator.try_from(&env).is_none());
358+
}
359+
}

0 commit comments

Comments
 (0)