Skip to content

Commit 66617a6

Browse files
Fix(tests): Make system_python_fallback test hermetic
1 parent 95a68e5 commit 66617a6

File tree

1 file changed

+85
-8
lines changed

1 file changed

+85
-8
lines changed

crates/djls-project/src/lib.rs

+85-8
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,8 @@ impl fmt::Display for PythonEnvironment {
231231
mod tests {
232232
use super::*;
233233
use std::fs;
234+
#[cfg(unix)]
235+
use std::os::unix::fs::PermissionsExt; // Needed for setting execute permission
234236
use tempfile::tempdir;
235237

236238
mod env_discovery {
@@ -289,6 +291,29 @@ mod tests {
289291
}
290292
}
291293

294+
// Guard struct to temporarily modify the PATH environment variable
295+
struct PathGuard {
296+
original_path: Option<String>,
297+
}
298+
299+
impl PathGuard {
300+
fn set(new_path_val: &str) -> Self {
301+
let original_path = env::var("PATH").ok();
302+
env::set_var("PATH", new_path_val);
303+
Self { original_path }
304+
}
305+
}
306+
307+
impl Drop for PathGuard {
308+
fn drop(&mut self) {
309+
// Restore original PATH, or remove if it wasn't set initially
310+
match self.original_path.as_deref() {
311+
Some(val) => env::set_var("PATH", val),
312+
None => env::remove_var("PATH"),
313+
}
314+
}
315+
}
316+
292317
#[test]
293318
fn test_explicit_venv_path_found() {
294319
let project_dir = tempdir().unwrap();
@@ -417,31 +442,83 @@ mod tests {
417442
}
418443

419444
#[test]
420-
#[ignore = "Relies on system python being available and having standard layout"]
445+
// #[ignore = "Relies on system python being available and having standard layout"] // No longer ignored!
421446
fn test_system_python_fallback() {
422-
let project_dir = tempdir().unwrap();
447+
let project_dir = tempdir().unwrap(); // Dummy project dir, not used for discovery here.
423448

424449
// Set VIRTUAL_ENV to something known to be invalid to ensure it's ignored.
425450
let invalid_virtual_env_path =
426451
project_dir.path().join("non_existent_venv_sys_fallback");
427452
let _guard =
428453
VirtualEnvGuard::set("VIRTUAL_ENV", invalid_virtual_env_path.to_str().unwrap());
454+
455+
// --- Set up mock system python ---
456+
let mock_sys_python_dir = tempdir().unwrap();
457+
let mock_sys_python_prefix = mock_sys_python_dir.path();
458+
459+
#[cfg(unix)]
460+
let (bin_subdir, python_exe, site_packages_rel_path) = (
461+
"bin",
462+
"python",
463+
Path::new("lib").join("python3.9").join("site-packages"),
464+
);
465+
#[cfg(windows)]
466+
let (bin_subdir, python_exe, site_packages_rel_path) = (
467+
"Scripts",
468+
"python.exe",
469+
Path::new("Lib").join("site-packages"),
470+
);
471+
472+
let bin_dir = mock_sys_python_prefix.join(bin_subdir);
473+
fs::create_dir_all(&bin_dir).unwrap();
474+
let python_path = bin_dir.join(python_exe);
475+
fs::write(&python_path, "").unwrap(); // Create dummy executable
476+
477+
// Ensure the dummy executable has execute permissions (required by `which` on Unix)
478+
#[cfg(unix)]
479+
{
480+
let mut perms = fs::metadata(&python_path).unwrap().permissions();
481+
perms.set_mode(0o755); // rwxr-xr-x
482+
fs::set_permissions(&python_path, perms).unwrap();
483+
}
484+
485+
let site_packages_path = mock_sys_python_prefix.join(site_packages_rel_path);
486+
fs::create_dir_all(&site_packages_path).unwrap();
487+
488+
// --- Manipulate PATH ---
489+
// Completely overwrite PATH to only include the mock bin directory
490+
let canonical_bin_dir =
491+
bin_dir.canonicalize().expect("Failed to canonicalize mock bin dir");
492+
let new_path = canonical_bin_dir.to_str().unwrap().to_string();
493+
let _path_guard = PathGuard::set(&new_path);
494+
429495
// We don't create any venvs in project_dir
430496

431497
// This test assumes `which python` works and points to a standard layout
432498
let system_env = PythonEnvironment::new(project_dir.path(), None);
433499

434500
assert!(
435501
system_env.is_some(),
436-
"Should fall back to system python if available"
502+
"Should fall back to the mock system python"
437503
);
438504

439505
if let Some(env) = system_env {
440-
// Basic checks - exact paths depend heavily on the test environment
441-
assert!(env.python_path.exists());
442-
assert!(env.sys_prefix.exists());
443-
assert!(!env.sys_path.is_empty());
444-
assert!(env.sys_path[0].exists()); // Should contain the bin/Scripts dir
506+
assert_eq!(env.python_path, python_path, "Python path should match mock");
507+
assert_eq!(
508+
env.sys_prefix,
509+
mock_sys_python_prefix,
510+
"Sys prefix should match mock prefix"
511+
);
512+
assert!(
513+
env.sys_path.contains(&bin_dir),
514+
"Sys path should contain mock bin dir"
515+
);
516+
assert!(
517+
env.sys_path.contains(&site_packages_path),
518+
"Sys path should contain mock site-packages"
519+
);
520+
} else {
521+
panic!("Expected to find environment, but got None"); // Should not happen if assert above passed
445522
}
446523
}
447524

0 commit comments

Comments
 (0)