Skip to content

Commit caf3aa6

Browse files
fmt: apply cargo fmt changes
1 parent 7e36aa5 commit caf3aa6

File tree

2 files changed

+149
-58
lines changed

2 files changed

+149
-58
lines changed

crates/djls-project/src/finder.rs

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
use std::path::PathBuf;
2+
use which::Error as WhichError;
3+
4+
pub fn find(name: &str) -> Result<PathBuf, WhichError> {
5+
#[cfg(not(test))]
6+
{
7+
which::which(name)
8+
}
9+
#[cfg(test)]
10+
{
11+
mock::find_mocked(name)
12+
}
13+
}
14+
15+
#[cfg(test)]
16+
pub mod mock {
17+
use super::*;
18+
use std::cell::RefCell;
19+
use std::collections::HashMap;
20+
use std::thread_local;
21+
22+
thread_local! {
23+
static MOCK_RESULTS: RefCell<HashMap<String, Result<PathBuf, WhichError>>> = RefCell::new(HashMap::new());
24+
}
25+
26+
pub(super) fn find_mocked(name: &str) -> Result<PathBuf, WhichError> {
27+
MOCK_RESULTS.with(|mocks| {
28+
let mocks = mocks.borrow();
29+
mocks
30+
.get(name)
31+
.cloned()
32+
.unwrap_or({ Err(WhichError::CannotFindBinaryPath) })
33+
})
34+
}
35+
36+
pub struct MockGuard;
37+
38+
impl Drop for MockGuard {
39+
fn drop(&mut self) {
40+
MOCK_RESULTS.with(|mocks| mocks.borrow_mut().clear());
41+
}
42+
}
43+
44+
pub fn set_mock_path(name: &str, path: PathBuf) {
45+
MOCK_RESULTS.with(|mocks| {
46+
mocks.borrow_mut().insert(name.to_string(), Ok(path));
47+
});
48+
}
49+
50+
pub fn set_mock_error(name: &str, error: WhichError) {
51+
MOCK_RESULTS.with(|mocks| {
52+
mocks.borrow_mut().insert(name.to_string(), Err(error));
53+
});
54+
}
55+
}
56+
57+
#[cfg(test)]
58+
mod tests {
59+
use super::mock::{self as finder_mock, MockGuard};
60+
use super::*; // Import find function etc.
61+
use std::path::PathBuf;
62+
use which::Error as WhichError;
63+
64+
#[test]
65+
fn test_mock_path_retrieval() {
66+
let _guard = MockGuard;
67+
let expected_path = PathBuf::from("/mock/path/to/python");
68+
finder_mock::set_mock_path("python", expected_path.clone());
69+
70+
let result = find("python");
71+
assert_eq!(result.unwrap(), expected_path);
72+
}
73+
74+
#[test]
75+
fn test_mock_error_retrieval() {
76+
let _guard = MockGuard;
77+
finder_mock::set_mock_error("cargo", WhichError::CannotFindBinaryPath);
78+
79+
let result = find("cargo");
80+
assert!(matches!(result, Err(WhichError::CannotFindBinaryPath)));
81+
}
82+
83+
#[test]
84+
fn test_mock_default_error_if_unmocked() {
85+
let _guard = MockGuard;
86+
let result = find("git");
87+
assert!(matches!(result, Err(WhichError::CannotFindBinaryPath)));
88+
}
89+
90+
#[test]
91+
fn test_mock_guard_clears_mocks() {
92+
let expected_path = PathBuf::from("/tmp/myprog");
93+
94+
{
95+
let _guard = MockGuard;
96+
finder_mock::set_mock_path("myprog", expected_path.clone());
97+
}
98+
99+
let _guard = MockGuard;
100+
let result = find("myprog");
101+
// Should default back to error because the previous mock was cleared
102+
assert!(matches!(result, Err(WhichError::CannotFindBinaryPath)));
103+
}
104+
105+
#[test]
106+
fn test_mocks_are_separate_between_tests() {
107+
let _guard = MockGuard;
108+
109+
// Ensure "python" (mocked in another test) is not mocked here
110+
let result_python = find("python");
111+
assert!(matches!(
112+
result_python,
113+
Err(WhichError::CannotFindBinaryPath)
114+
));
115+
116+
// Set a mock specific to this test
117+
let expected_path_node = PathBuf::from("/usr/bin/node");
118+
finder_mock::set_mock_path("node", expected_path_node.clone());
119+
let result_node = find("node");
120+
assert_eq!(result_node.unwrap(), expected_path_node);
121+
}
122+
}

crates/djls-project/src/lib.rs

+27-58
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod finder;
12
mod templatetags;
23

34
pub use templatetags::TemplateTags;
@@ -6,7 +7,6 @@ use pyo3::prelude::*;
67
use std::env;
78
use std::fmt;
89
use std::path::{Path, PathBuf};
9-
use which::which;
1010

1111
#[derive(Debug)]
1212
pub struct DjangoProject {
@@ -126,7 +126,6 @@ impl PythonEnvironment {
126126
#[cfg(windows)]
127127
let python_path = prefix.join("Scripts").join("python.exe");
128128

129-
// Check if the *prefix* and the *binary* exist.
130129
if !prefix.is_dir() || !python_path.exists() {
131130
return None;
132131
}
@@ -137,10 +136,9 @@ impl PythonEnvironment {
137136
let bin_dir = prefix.join("Scripts");
138137

139138
let mut sys_path = Vec::new();
140-
sys_path.push(bin_dir); // Add bin/ or Scripts/
139+
sys_path.push(bin_dir);
141140

142141
if let Some(site_packages) = Self::find_site_packages(prefix) {
143-
// Check existence inside the if let, as find_site_packages might return a path that doesn't exist
144142
if site_packages.is_dir() {
145143
sys_path.push(site_packages);
146144
}
@@ -167,12 +165,10 @@ impl PythonEnvironment {
167165
}
168166

169167
fn from_system_python() -> Option<Self> {
170-
let python_path = match which("python") {
168+
let python_path = match finder::find("python") {
171169
Ok(p) => p,
172170
Err(_) => return None,
173171
};
174-
// which() might return a path inside a bin/Scripts dir, or directly the executable
175-
// We need the prefix, which is usually two levels up from the executable in standard layouts
176172
let bin_dir = python_path.parent()?;
177173
let prefix = bin_dir.parent()?;
178174

@@ -232,11 +228,13 @@ mod tests {
232228
use super::*;
233229
use std::fs;
234230
#[cfg(unix)]
235-
use std::os::unix::fs::PermissionsExt; // Needed for setting execute permission
231+
use std::os::unix::fs::PermissionsExt;
236232
use tempfile::tempdir;
237233

238234
mod env_discovery {
235+
use super::finder::mock::{self as exec_mock, MockGuard};
239236
use super::*;
237+
use which::Error as WhichError;
240238

241239
fn create_mock_venv(dir: &Path, version: Option<&str>) -> PathBuf {
242240
let prefix = dir.to_path_buf();
@@ -245,7 +243,7 @@ mod tests {
245243
{
246244
let bin_dir = prefix.join("bin");
247245
fs::create_dir_all(&bin_dir).unwrap();
248-
fs::write(bin_dir.join("python"), "").unwrap(); // Create dummy executable
246+
fs::write(bin_dir.join("python"), "").unwrap();
249247
let lib_dir = prefix.join("lib");
250248
fs::create_dir_all(&lib_dir).unwrap();
251249
let py_version_dir = lib_dir.join(version.unwrap_or("python3.9"));
@@ -256,7 +254,7 @@ mod tests {
256254
{
257255
let bin_dir = prefix.join("Scripts");
258256
fs::create_dir_all(&bin_dir).unwrap();
259-
fs::write(bin_dir.join("python.exe"), "").unwrap(); // Create dummy executable
257+
fs::write(bin_dir.join("python.exe"), "").unwrap();
260258
let lib_dir = prefix.join("Lib");
261259
fs::create_dir_all(&lib_dir).unwrap();
262260
fs::create_dir_all(lib_dir.join("site-packages")).unwrap();
@@ -291,29 +289,6 @@ mod tests {
291289
}
292290
}
293291

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-
317292
#[test]
318293
fn test_explicit_venv_path_found() {
319294
let project_dir = tempdir().unwrap();
@@ -442,17 +417,15 @@ mod tests {
442417
}
443418

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

449-
// Set VIRTUAL_ENV to something known to be invalid to ensure it's ignored.
423+
// Set VIRTUAL_ENV to invalid to ensure it's ignored.
450424
let invalid_virtual_env_path =
451425
project_dir.path().join("non_existent_venv_sys_fallback");
452426
let _guard =
453427
VirtualEnvGuard::set("VIRTUAL_ENV", invalid_virtual_env_path.to_str().unwrap());
454428

455-
// --- Set up mock system python ---
456429
let mock_sys_python_dir = tempdir().unwrap();
457430
let mock_sys_python_prefix = mock_sys_python_dir.path();
458431

@@ -472,29 +445,23 @@ mod tests {
472445
let bin_dir = mock_sys_python_prefix.join(bin_subdir);
473446
fs::create_dir_all(&bin_dir).unwrap();
474447
let python_path = bin_dir.join(python_exe);
475-
fs::write(&python_path, "").unwrap(); // Create dummy executable
448+
fs::write(&python_path, "").unwrap();
476449

477450
// Ensure the dummy executable has execute permissions (required by `which` on Unix)
478451
#[cfg(unix)]
479452
{
480453
let mut perms = fs::metadata(&python_path).unwrap().permissions();
481-
perms.set_mode(0o755); // rwxr-xr-x
454+
perms.set_mode(0o755);
482455
fs::set_permissions(&python_path, perms).unwrap();
483456
}
484457

485458
let site_packages_path = mock_sys_python_prefix.join(site_packages_rel_path);
486459
fs::create_dir_all(&site_packages_path).unwrap();
487460

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);
461+
let _guard = MockGuard;
462+
exec_mock::set_mock_path("python", python_path.clone());
494463

495464
// We don't create any venvs in project_dir
496-
497-
// This test assumes `which python` works and points to a standard layout
498465
let system_env = PythonEnvironment::new(project_dir.path(), None);
499466

500467
assert!(
@@ -503,10 +470,12 @@ mod tests {
503470
);
504471

505472
if let Some(env) = system_env {
506-
assert_eq!(env.python_path, python_path, "Python path should match mock");
507473
assert_eq!(
508-
env.sys_prefix,
509-
mock_sys_python_prefix,
474+
env.python_path, python_path,
475+
"Python path should match mock"
476+
);
477+
assert_eq!(
478+
env.sys_prefix, mock_sys_python_prefix,
510479
"Sys prefix should match mock prefix"
511480
);
512481
assert!(
@@ -517,8 +486,7 @@ mod tests {
517486
env.sys_path.contains(&site_packages_path),
518487
"Sys path should contain mock site-packages"
519488
);
520-
} else {
521-
panic!("Expected to find environment, but got None"); // Should not happen if assert above passed
489+
panic!("Expected to find environment, but got None");
522490
}
523491
}
524492

@@ -528,17 +496,18 @@ mod tests {
528496

529497
// Ensure no explicit path, no project venvs, and set VIRTUAL_ENV to invalid.
530498
let invalid_virtual_env_path = project_dir.path().join("non_existent_venv_no_python");
531-
let _guard =
499+
let _guard_venv =
532500
VirtualEnvGuard::set("VIRTUAL_ENV", invalid_virtual_env_path.to_str().unwrap());
533501

534-
// Overwrite PATH with an empty value to ensure `which("python")` fails.
535-
let _path_guard = PathGuard::set("");
502+
let _guard_mock = MockGuard;
503+
exec_mock::set_mock_error("python", WhichError::CannotFindBinaryPath);
536504

537-
// Call the function under test
538505
let env = PythonEnvironment::new(project_dir.path(), None);
539506

540-
// Assert that no environment was found
541-
assert!(env.is_none(), "Expected no environment to be found when all discovery methods fail");
507+
assert!(
508+
env.is_none(),
509+
"Expected no environment to be found when all discovery methods fail"
510+
);
542511
}
543512

544513
#[test]

0 commit comments

Comments
 (0)