mirror of
https://github.com/joshuadavidthomas/django-language-server.git
synced 2025-09-04 01:10:42 +00:00
mock system interactions to fix flaky environment tests (#129)
Some checks failed
release / linux (map[runner:ubuntu-22.04 target:aarch64]) (push) Failing after 4s
release / linux (map[runner:ubuntu-22.04 target:ppc64le]) (push) Failing after 3s
release / linux (map[runner:ubuntu-22.04 target:s390x]) (push) Failing after 3s
release / linux (map[runner:ubuntu-22.04 target:x86]) (push) Failing after 3s
release / linux (map[runner:ubuntu-22.04 target:armv7]) (push) Failing after 3s
release / linux (map[runner:ubuntu-22.04 target:x86_64]) (push) Failing after 3s
release / musllinux (map[runner:ubuntu-22.04 target:aarch64]) (push) Failing after 3s
release / musllinux (map[runner:ubuntu-22.04 target:armv7]) (push) Failing after 3s
release / musllinux (map[runner:ubuntu-22.04 target:x86]) (push) Failing after 2s
release / musllinux (map[runner:ubuntu-22.04 target:x86_64]) (push) Failing after 3s
release / test (push) Has been skipped
lint / pre-commit (push) Has been cancelled
release / windows (map[runner:windows-latest target:x64]) (push) Has been cancelled
release / windows (map[runner:windows-latest target:x86]) (push) Has been cancelled
release / macos (map[runner:macos-13 target:x86_64]) (push) Has been cancelled
release / macos (map[runner:macos-14 target:aarch64]) (push) Has been cancelled
release / sdist (push) Has been cancelled
release / release (push) Has been cancelled
Some checks failed
release / linux (map[runner:ubuntu-22.04 target:aarch64]) (push) Failing after 4s
release / linux (map[runner:ubuntu-22.04 target:ppc64le]) (push) Failing after 3s
release / linux (map[runner:ubuntu-22.04 target:s390x]) (push) Failing after 3s
release / linux (map[runner:ubuntu-22.04 target:x86]) (push) Failing after 3s
release / linux (map[runner:ubuntu-22.04 target:armv7]) (push) Failing after 3s
release / linux (map[runner:ubuntu-22.04 target:x86_64]) (push) Failing after 3s
release / musllinux (map[runner:ubuntu-22.04 target:aarch64]) (push) Failing after 3s
release / musllinux (map[runner:ubuntu-22.04 target:armv7]) (push) Failing after 3s
release / musllinux (map[runner:ubuntu-22.04 target:x86]) (push) Failing after 2s
release / musllinux (map[runner:ubuntu-22.04 target:x86_64]) (push) Failing after 3s
release / test (push) Has been skipped
lint / pre-commit (push) Has been cancelled
release / windows (map[runner:windows-latest target:x64]) (push) Has been cancelled
release / windows (map[runner:windows-latest target:x86]) (push) Has been cancelled
release / macos (map[runner:macos-13 target:x86_64]) (push) Has been cancelled
release / macos (map[runner:macos-14 target:aarch64]) (push) Has been cancelled
release / sdist (push) Has been cancelled
release / release (push) Has been cancelled
This commit is contained in:
parent
95a68e5f3a
commit
0d816ea0dc
3 changed files with 303 additions and 136 deletions
|
@ -26,6 +26,7 @@ and this project attempts to adhere to [Semantic Versioning](https://semver.org/
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- **Internal (djls-project)**: Added `system` module to improve reliability of environment discovery tests.
|
||||||
- **Internal**: Moved task queueing functionality to `djls-server` crate, renamed from `Worker` to `Queue`, and simplified API.
|
- **Internal**: Moved task queueing functionality to `djls-server` crate, renamed from `Worker` to `Queue`, and simplified API.
|
||||||
- **Internal**: Improved Python environment handling, including refactored activation logic.
|
- **Internal**: Improved Python environment handling, including refactored activation logic.
|
||||||
- **Internal**: Centralized Python linking build logic into a shared `djls-dev` crate to reduce duplication.
|
- **Internal**: Centralized Python linking build logic into a shared `djls-dev` crate to reduce duplication.
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
|
mod system;
|
||||||
mod templatetags;
|
mod templatetags;
|
||||||
|
|
||||||
pub use templatetags::TemplateTags;
|
pub use templatetags::TemplateTags;
|
||||||
|
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use std::env;
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use which::which;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DjangoProject {
|
pub struct DjangoProject {
|
||||||
|
@ -92,19 +91,16 @@ impl PythonEnvironment {
|
||||||
fn new(project_path: &Path, venv_path: Option<&str>) -> Option<Self> {
|
fn new(project_path: &Path, venv_path: Option<&str>) -> Option<Self> {
|
||||||
if let Some(path) = venv_path {
|
if let Some(path) = venv_path {
|
||||||
let prefix = PathBuf::from(path);
|
let prefix = PathBuf::from(path);
|
||||||
// If an explicit path is provided and it's a valid venv, use it immediately.
|
|
||||||
if let Some(env) = Self::from_venv_prefix(&prefix) {
|
if let Some(env) = Self::from_venv_prefix(&prefix) {
|
||||||
return Some(env);
|
return Some(env);
|
||||||
}
|
}
|
||||||
// Explicit path was provided but was invalid. Continue searching.
|
// Invalid explicit path, continue searching...
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(virtual_env) = env::var("VIRTUAL_ENV") {
|
if let Ok(virtual_env) = system::env_var("VIRTUAL_ENV") {
|
||||||
if !virtual_env.is_empty() {
|
let prefix = PathBuf::from(virtual_env);
|
||||||
let prefix = PathBuf::from(virtual_env);
|
if let Some(env) = Self::from_venv_prefix(&prefix) {
|
||||||
if let Some(env) = Self::from_venv_prefix(&prefix) {
|
return Some(env);
|
||||||
return Some(env);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +122,6 @@ impl PythonEnvironment {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let python_path = prefix.join("Scripts").join("python.exe");
|
let python_path = prefix.join("Scripts").join("python.exe");
|
||||||
|
|
||||||
// Check if the *prefix* and the *binary* exist.
|
|
||||||
if !prefix.is_dir() || !python_path.exists() {
|
if !prefix.is_dir() || !python_path.exists() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -137,10 +132,9 @@ impl PythonEnvironment {
|
||||||
let bin_dir = prefix.join("Scripts");
|
let bin_dir = prefix.join("Scripts");
|
||||||
|
|
||||||
let mut sys_path = Vec::new();
|
let mut sys_path = Vec::new();
|
||||||
sys_path.push(bin_dir); // Add bin/ or Scripts/
|
sys_path.push(bin_dir);
|
||||||
|
|
||||||
if let Some(site_packages) = Self::find_site_packages(prefix) {
|
if let Some(site_packages) = Self::find_site_packages(prefix) {
|
||||||
// Check existence inside the if let, as find_site_packages might return a path that doesn't exist
|
|
||||||
if site_packages.is_dir() {
|
if site_packages.is_dir() {
|
||||||
sys_path.push(site_packages);
|
sys_path.push(site_packages);
|
||||||
}
|
}
|
||||||
|
@ -167,12 +161,10 @@ impl PythonEnvironment {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_system_python() -> Option<Self> {
|
fn from_system_python() -> Option<Self> {
|
||||||
let python_path = match which("python") {
|
let python_path = match system::find_executable("python") {
|
||||||
Ok(p) => p,
|
Ok(p) => p,
|
||||||
Err(_) => return None,
|
Err(_) => return None,
|
||||||
};
|
};
|
||||||
// which() might return a path inside a bin/Scripts dir, or directly the executable
|
|
||||||
// We need the prefix, which is usually two levels up from the executable in standard layouts
|
|
||||||
let bin_dir = python_path.parent()?;
|
let bin_dir = python_path.parent()?;
|
||||||
let prefix = bin_dir.parent()?;
|
let prefix = bin_dir.parent()?;
|
||||||
|
|
||||||
|
@ -194,7 +186,6 @@ impl PythonEnvironment {
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn find_site_packages(prefix: &Path) -> Option<PathBuf> {
|
fn find_site_packages(prefix: &Path) -> Option<PathBuf> {
|
||||||
// Look for lib/pythonX.Y/site-packages
|
|
||||||
let lib_dir = prefix.join("lib");
|
let lib_dir = prefix.join("lib");
|
||||||
if !lib_dir.is_dir() {
|
if !lib_dir.is_dir() {
|
||||||
return None;
|
return None;
|
||||||
|
@ -203,8 +194,8 @@ impl PythonEnvironment {
|
||||||
.ok()?
|
.ok()?
|
||||||
.filter_map(Result::ok)
|
.filter_map(Result::ok)
|
||||||
.find(|e| {
|
.find(|e| {
|
||||||
e.file_type().is_ok_and(|ft| ft.is_dir()) && // Ensure it's a directory
|
e.file_type().is_ok_and(|ft| ft.is_dir())
|
||||||
e.file_name().to_string_lossy().starts_with("python")
|
&& e.file_name().to_string_lossy().starts_with("python")
|
||||||
})
|
})
|
||||||
.map(|e| e.path().join("site-packages"))
|
.map(|e| e.path().join("site-packages"))
|
||||||
}
|
}
|
||||||
|
@ -231,10 +222,14 @@ impl fmt::Display for PythonEnvironment {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
|
|
||||||
mod env_discovery {
|
mod env_discovery {
|
||||||
|
use super::system::mock::{self as sys_mock, MockGuard};
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use which::Error as WhichError;
|
||||||
|
|
||||||
fn create_mock_venv(dir: &Path, version: Option<&str>) -> PathBuf {
|
fn create_mock_venv(dir: &Path, version: Option<&str>) -> PathBuf {
|
||||||
let prefix = dir.to_path_buf();
|
let prefix = dir.to_path_buf();
|
||||||
|
@ -243,7 +238,7 @@ mod tests {
|
||||||
{
|
{
|
||||||
let bin_dir = prefix.join("bin");
|
let bin_dir = prefix.join("bin");
|
||||||
fs::create_dir_all(&bin_dir).unwrap();
|
fs::create_dir_all(&bin_dir).unwrap();
|
||||||
fs::write(bin_dir.join("python"), "").unwrap(); // Create dummy executable
|
fs::write(bin_dir.join("python"), "").unwrap();
|
||||||
let lib_dir = prefix.join("lib");
|
let lib_dir = prefix.join("lib");
|
||||||
fs::create_dir_all(&lib_dir).unwrap();
|
fs::create_dir_all(&lib_dir).unwrap();
|
||||||
let py_version_dir = lib_dir.join(version.unwrap_or("python3.9"));
|
let py_version_dir = lib_dir.join(version.unwrap_or("python3.9"));
|
||||||
|
@ -254,7 +249,7 @@ mod tests {
|
||||||
{
|
{
|
||||||
let bin_dir = prefix.join("Scripts");
|
let bin_dir = prefix.join("Scripts");
|
||||||
fs::create_dir_all(&bin_dir).unwrap();
|
fs::create_dir_all(&bin_dir).unwrap();
|
||||||
fs::write(bin_dir.join("python.exe"), "").unwrap(); // Create dummy executable
|
fs::write(bin_dir.join("python.exe"), "").unwrap();
|
||||||
let lib_dir = prefix.join("Lib");
|
let lib_dir = prefix.join("Lib");
|
||||||
fs::create_dir_all(&lib_dir).unwrap();
|
fs::create_dir_all(&lib_dir).unwrap();
|
||||||
fs::create_dir_all(lib_dir.join("site-packages")).unwrap();
|
fs::create_dir_all(lib_dir.join("site-packages")).unwrap();
|
||||||
|
@ -263,32 +258,6 @@ mod tests {
|
||||||
prefix
|
prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
struct VirtualEnvGuard<'a> {
|
|
||||||
key: &'a str,
|
|
||||||
original_value: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> VirtualEnvGuard<'a> {
|
|
||||||
fn set(key: &'a str, value: &str) -> Self {
|
|
||||||
let original_value = env::var(key).ok();
|
|
||||||
env::set_var(key, value);
|
|
||||||
Self {
|
|
||||||
key,
|
|
||||||
original_value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for VirtualEnvGuard<'_> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if let Some(ref val) = self.original_value {
|
|
||||||
env::set_var(self.key, val);
|
|
||||||
} else {
|
|
||||||
env::remove_var(self.key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_explicit_venv_path_found() {
|
fn test_explicit_venv_path_found() {
|
||||||
let project_dir = tempdir().unwrap();
|
let project_dir = tempdir().unwrap();
|
||||||
|
@ -324,11 +293,9 @@ mod tests {
|
||||||
let project_dir = tempdir().unwrap();
|
let project_dir = tempdir().unwrap();
|
||||||
let project_venv_prefix = create_mock_venv(&project_dir.path().join(".venv"), None);
|
let project_venv_prefix = create_mock_venv(&project_dir.path().join(".venv"), None);
|
||||||
|
|
||||||
// Set VIRTUAL_ENV to something known to be invalid, rather than clearing.
|
let _guard = MockGuard;
|
||||||
// This prevents the test runner's VIRTUAL_ENV (e.g., from Nox) from interfering.
|
// Ensure VIRTUAL_ENV is not set (returns VarError::NotPresent)
|
||||||
let invalid_virtual_env_path = project_dir.path().join("non_existent_virtual_env");
|
sys_mock::remove_env_var("VIRTUAL_ENV");
|
||||||
let _guard =
|
|
||||||
VirtualEnvGuard::set("VIRTUAL_ENV", invalid_virtual_env_path.to_str().unwrap());
|
|
||||||
|
|
||||||
// Provide an invalid explicit path
|
// Provide an invalid explicit path
|
||||||
let invalid_path = project_dir.path().join("non_existent_venv");
|
let invalid_path = project_dir.path().join("non_existent_venv");
|
||||||
|
@ -346,7 +313,9 @@ mod tests {
|
||||||
let venv_dir = tempdir().unwrap();
|
let venv_dir = tempdir().unwrap();
|
||||||
let venv_prefix = create_mock_venv(venv_dir.path(), None);
|
let venv_prefix = create_mock_venv(venv_dir.path(), None);
|
||||||
|
|
||||||
let _guard = VirtualEnvGuard::set("VIRTUAL_ENV", venv_prefix.to_str().unwrap());
|
let _guard = MockGuard;
|
||||||
|
// Mock VIRTUAL_ENV to point to the mock venv
|
||||||
|
sys_mock::set_env_var("VIRTUAL_ENV", venv_prefix.to_str().unwrap().to_string());
|
||||||
|
|
||||||
let env = PythonEnvironment::new(project_dir.path(), None)
|
let env = PythonEnvironment::new(project_dir.path(), None)
|
||||||
.expect("Should find environment via VIRTUAL_ENV");
|
.expect("Should find environment via VIRTUAL_ENV");
|
||||||
|
@ -363,18 +332,20 @@ mod tests {
|
||||||
fn test_explicit_path_overrides_virtual_env() {
|
fn test_explicit_path_overrides_virtual_env() {
|
||||||
let project_dir = tempdir().unwrap();
|
let project_dir = tempdir().unwrap();
|
||||||
let venv1_dir = tempdir().unwrap();
|
let venv1_dir = tempdir().unwrap();
|
||||||
let venv1_prefix = create_mock_venv(venv1_dir.path(), None); // Set by VIRTUAL_ENV
|
let venv1_prefix = create_mock_venv(venv1_dir.path(), None); // Mocked by VIRTUAL_ENV
|
||||||
let venv2_dir = tempdir().unwrap();
|
let venv2_dir = tempdir().unwrap();
|
||||||
let venv2_prefix = create_mock_venv(venv2_dir.path(), None); // Set by explicit path
|
let venv2_prefix = create_mock_venv(venv2_dir.path(), None); // Provided explicitly
|
||||||
|
|
||||||
let _guard = VirtualEnvGuard::set("VIRTUAL_ENV", venv1_prefix.to_str().unwrap());
|
let _guard = MockGuard;
|
||||||
|
// Mock VIRTUAL_ENV to point to venv1
|
||||||
|
sys_mock::set_env_var("VIRTUAL_ENV", venv1_prefix.to_str().unwrap().to_string());
|
||||||
|
|
||||||
let env = PythonEnvironment::new(
|
// Call with explicit path to venv2
|
||||||
project_dir.path(),
|
let env =
|
||||||
Some(venv2_prefix.to_str().unwrap()), // Explicit path
|
PythonEnvironment::new(project_dir.path(), Some(venv2_prefix.to_str().unwrap()))
|
||||||
)
|
.expect("Should find environment via explicit path");
|
||||||
.expect("Should find environment via explicit path");
|
|
||||||
|
|
||||||
|
// Explicit path (venv2) should take precedence
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
env.sys_prefix, venv2_prefix,
|
env.sys_prefix, venv2_prefix,
|
||||||
"Explicit path should take precedence"
|
"Explicit path should take precedence"
|
||||||
|
@ -386,10 +357,9 @@ mod tests {
|
||||||
let project_dir = tempdir().unwrap();
|
let project_dir = tempdir().unwrap();
|
||||||
let venv_prefix = create_mock_venv(&project_dir.path().join(".venv"), None);
|
let venv_prefix = create_mock_venv(&project_dir.path().join(".venv"), None);
|
||||||
|
|
||||||
// Set VIRTUAL_ENV to something known to be invalid to ensure it's ignored.
|
let _guard = MockGuard;
|
||||||
let invalid_virtual_env_path = project_dir.path().join("non_existent_venv_proj_found");
|
// Ensure VIRTUAL_ENV is not set
|
||||||
let _guard =
|
sys_mock::remove_env_var("VIRTUAL_ENV");
|
||||||
VirtualEnvGuard::set("VIRTUAL_ENV", invalid_virtual_env_path.to_str().unwrap());
|
|
||||||
|
|
||||||
let env = PythonEnvironment::new(project_dir.path(), None)
|
let env = PythonEnvironment::new(project_dir.path(), None)
|
||||||
.expect("Should find environment in project .venv");
|
.expect("Should find environment in project .venv");
|
||||||
|
@ -400,48 +370,88 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_project_venv_priority() {
|
fn test_project_venv_priority() {
|
||||||
let project_dir = tempdir().unwrap();
|
let project_dir = tempdir().unwrap();
|
||||||
// Create multiple potential venvs
|
|
||||||
let dot_venv_prefix = create_mock_venv(&project_dir.path().join(".venv"), None);
|
let dot_venv_prefix = create_mock_venv(&project_dir.path().join(".venv"), None);
|
||||||
let _venv_prefix = create_mock_venv(&project_dir.path().join("venv"), None); // Should be ignored if .venv found first
|
let _venv_prefix = create_mock_venv(&project_dir.path().join("venv"), None);
|
||||||
|
|
||||||
// Set VIRTUAL_ENV to something known to be invalid to ensure it's ignored.
|
let _guard = MockGuard;
|
||||||
let invalid_virtual_env_path = project_dir.path().join("non_existent_venv_priority");
|
// Ensure VIRTUAL_ENV is not set
|
||||||
let _guard =
|
sys_mock::remove_env_var("VIRTUAL_ENV");
|
||||||
VirtualEnvGuard::set("VIRTUAL_ENV", invalid_virtual_env_path.to_str().unwrap());
|
|
||||||
|
|
||||||
let env =
|
let env =
|
||||||
PythonEnvironment::new(project_dir.path(), None).expect("Should find environment");
|
PythonEnvironment::new(project_dir.path(), None).expect("Should find environment");
|
||||||
|
|
||||||
// Asserts it finds .venv because it's checked first in the loop
|
// Should find .venv because it's checked first in the loop
|
||||||
assert_eq!(env.sys_prefix, dot_venv_prefix);
|
assert_eq!(env.sys_prefix, dot_venv_prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore = "Relies on system python being available and having standard layout"]
|
|
||||||
fn test_system_python_fallback() {
|
fn test_system_python_fallback() {
|
||||||
let project_dir = tempdir().unwrap();
|
let project_dir = tempdir().unwrap();
|
||||||
|
|
||||||
// Set VIRTUAL_ENV to something known to be invalid to ensure it's ignored.
|
let _guard = MockGuard;
|
||||||
let invalid_virtual_env_path =
|
// Ensure VIRTUAL_ENV is not set
|
||||||
project_dir.path().join("non_existent_venv_sys_fallback");
|
sys_mock::remove_env_var("VIRTUAL_ENV");
|
||||||
let _guard =
|
|
||||||
VirtualEnvGuard::set("VIRTUAL_ENV", invalid_virtual_env_path.to_str().unwrap());
|
let mock_sys_python_dir = tempdir().unwrap();
|
||||||
// We don't create any venvs in project_dir
|
let mock_sys_python_prefix = mock_sys_python_dir.path();
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
let (bin_subdir, python_exe, site_packages_rel_path) = (
|
||||||
|
"bin",
|
||||||
|
"python",
|
||||||
|
Path::new("lib").join("python3.9").join("site-packages"),
|
||||||
|
);
|
||||||
|
#[cfg(windows)]
|
||||||
|
let (bin_subdir, python_exe, site_packages_rel_path) = (
|
||||||
|
"Scripts",
|
||||||
|
"python.exe",
|
||||||
|
Path::new("Lib").join("site-packages"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let bin_dir = mock_sys_python_prefix.join(bin_subdir);
|
||||||
|
fs::create_dir_all(&bin_dir).unwrap();
|
||||||
|
let python_path = bin_dir.join(python_exe);
|
||||||
|
fs::write(&python_path, "").unwrap();
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
let mut perms = fs::metadata(&python_path).unwrap().permissions();
|
||||||
|
perms.set_mode(0o755);
|
||||||
|
fs::set_permissions(&python_path, perms).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let site_packages_path = mock_sys_python_prefix.join(site_packages_rel_path);
|
||||||
|
fs::create_dir_all(&site_packages_path).unwrap();
|
||||||
|
|
||||||
|
sys_mock::set_exec_path("python", python_path.clone());
|
||||||
|
|
||||||
// This test assumes `which python` works and points to a standard layout
|
|
||||||
let system_env = PythonEnvironment::new(project_dir.path(), None);
|
let system_env = PythonEnvironment::new(project_dir.path(), None);
|
||||||
|
|
||||||
|
// Assert it found the mock system python via the mocked finder
|
||||||
assert!(
|
assert!(
|
||||||
system_env.is_some(),
|
system_env.is_some(),
|
||||||
"Should fall back to system python if available"
|
"Should fall back to the mock system python"
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(env) = system_env {
|
if let Some(env) = system_env {
|
||||||
// Basic checks - exact paths depend heavily on the test environment
|
assert_eq!(
|
||||||
assert!(env.python_path.exists());
|
env.python_path, python_path,
|
||||||
assert!(env.sys_prefix.exists());
|
"Python path should match mock"
|
||||||
assert!(!env.sys_path.is_empty());
|
);
|
||||||
assert!(env.sys_path[0].exists()); // Should contain the bin/Scripts dir
|
assert_eq!(
|
||||||
|
env.sys_prefix, mock_sys_python_prefix,
|
||||||
|
"Sys prefix should match mock prefix"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
env.sys_path.contains(&bin_dir),
|
||||||
|
"Sys path should contain mock bin dir"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
env.sys_path.contains(&site_packages_path),
|
||||||
|
"Sys path should contain mock site-packages"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
panic!("Expected to find environment, but got None");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -449,39 +459,19 @@ mod tests {
|
||||||
fn test_no_python_found() {
|
fn test_no_python_found() {
|
||||||
let project_dir = tempdir().unwrap();
|
let project_dir = tempdir().unwrap();
|
||||||
|
|
||||||
// Ensure no explicit path, no project venvs, and set VIRTUAL_ENV to invalid.
|
let _guard = MockGuard; // Setup guard to clear mocks
|
||||||
let invalid_virtual_env_path = project_dir.path().join("non_existent_venv_no_python");
|
|
||||||
let _guard =
|
|
||||||
VirtualEnvGuard::set("VIRTUAL_ENV", invalid_virtual_env_path.to_str().unwrap());
|
|
||||||
|
|
||||||
// To *ensure* system fallback fails, we'd need to manipulate PATH,
|
// Ensure VIRTUAL_ENV is not set
|
||||||
// which is tricky and platform-dependent. Instead, we test the scenario
|
sys_mock::remove_env_var("VIRTUAL_ENV");
|
||||||
// where `from_system_python` *would* be called but returns None.
|
|
||||||
// We can simulate this by ensuring `which("python")` fails.
|
|
||||||
// For this unit test, let's assume a scenario where all checks fail.
|
|
||||||
// A more direct test would mock `which`, but that adds complexity.
|
|
||||||
|
|
||||||
// Let's simulate the *call* path assuming `from_system_python` returns None.
|
// Ensure find_executable returns an error
|
||||||
// We can't easily force `which` to fail here without PATH manipulation.
|
sys_mock::set_exec_error("python", WhichError::CannotFindBinaryPath);
|
||||||
// So, this test mainly verifies that if all preceding steps fail,
|
|
||||||
// the result of `from_system_python` (which *could* be None) is returned.
|
|
||||||
// If system python *is* found, this test might incorrectly pass if not ignored.
|
|
||||||
// A better approach might be needed if strict testing of "None" is required.
|
|
||||||
|
|
||||||
// For now, let's assume a setup where system python isn't found by `which`.
|
|
||||||
// This test is inherently flaky if system python *is* on the PATH.
|
|
||||||
// Consider ignoring it or using mocking for `which` in a real-world scenario.
|
|
||||||
|
|
||||||
// If system python IS found, this test doesn't truly test the "None" case.
|
|
||||||
// If system python IS NOT found, it tests the final `None` return.
|
|
||||||
let env = PythonEnvironment::new(project_dir.path(), None);
|
let env = PythonEnvironment::new(project_dir.path(), None);
|
||||||
|
|
||||||
// This assertion depends on whether system python is actually found or not.
|
assert!(
|
||||||
// assert!(env.is_none(), "Expected no environment to be found");
|
env.is_none(),
|
||||||
// Given the difficulty, let's skip asserting None directly unless we mock `which`.
|
"Expected no environment to be found when all discovery methods fail"
|
||||||
println!(
|
|
||||||
"Test 'test_no_python_found' ran. Result depends on system state: {:?}",
|
|
||||||
env
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -495,7 +485,6 @@ mod tests {
|
||||||
fs::write(bin_dir.join("python"), "").unwrap();
|
fs::write(bin_dir.join("python"), "").unwrap();
|
||||||
let lib_dir = prefix.join("lib");
|
let lib_dir = prefix.join("lib");
|
||||||
fs::create_dir_all(&lib_dir).unwrap();
|
fs::create_dir_all(&lib_dir).unwrap();
|
||||||
// Create two python version dirs, ensure it picks one
|
|
||||||
let py_version_dir1 = lib_dir.join("python3.8");
|
let py_version_dir1 = lib_dir.join("python3.8");
|
||||||
fs::create_dir_all(&py_version_dir1).unwrap();
|
fs::create_dir_all(&py_version_dir1).unwrap();
|
||||||
fs::create_dir_all(py_version_dir1.join("site-packages")).unwrap();
|
fs::create_dir_all(py_version_dir1.join("site-packages")).unwrap();
|
||||||
|
@ -505,14 +494,11 @@ mod tests {
|
||||||
|
|
||||||
let env = PythonEnvironment::from_venv_prefix(prefix).unwrap();
|
let env = PythonEnvironment::from_venv_prefix(prefix).unwrap();
|
||||||
|
|
||||||
// It should find *a* site-packages dir. The exact one depends on read_dir order.
|
|
||||||
let found_site_packages = env.sys_path.iter().any(|p| p.ends_with("site-packages"));
|
let found_site_packages = env.sys_path.iter().any(|p| p.ends_with("site-packages"));
|
||||||
assert!(
|
assert!(
|
||||||
found_site_packages,
|
found_site_packages,
|
||||||
"Should have found a site-packages directory"
|
"Should have found a site-packages directory"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ensure it contains the bin dir as well
|
|
||||||
assert!(env.sys_path.contains(&prefix.join("bin")));
|
assert!(env.sys_path.contains(&prefix.join("bin")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -527,7 +513,7 @@ mod tests {
|
||||||
let lib_dir = prefix.join("Lib");
|
let lib_dir = prefix.join("Lib");
|
||||||
fs::create_dir_all(&lib_dir).unwrap();
|
fs::create_dir_all(&lib_dir).unwrap();
|
||||||
let site_packages = lib_dir.join("site-packages");
|
let site_packages = lib_dir.join("site-packages");
|
||||||
fs::create_dir_all(&site_packages).unwrap(); // Create the actual dir
|
fs::create_dir_all(&site_packages).unwrap();
|
||||||
|
|
||||||
let env = PythonEnvironment::from_venv_prefix(prefix).unwrap();
|
let env = PythonEnvironment::from_venv_prefix(prefix).unwrap();
|
||||||
|
|
||||||
|
@ -541,7 +527,6 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_from_venv_prefix_returns_none_if_dir_missing() {
|
fn test_from_venv_prefix_returns_none_if_dir_missing() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
// Don't create the venv structure
|
|
||||||
let result = PythonEnvironment::from_venv_prefix(dir.path());
|
let result = PythonEnvironment::from_venv_prefix(dir.path());
|
||||||
assert!(result.is_none());
|
assert!(result.is_none());
|
||||||
}
|
}
|
||||||
|
@ -550,7 +535,6 @@ mod tests {
|
||||||
fn test_from_venv_prefix_returns_none_if_binary_missing() {
|
fn test_from_venv_prefix_returns_none_if_binary_missing() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let prefix = dir.path();
|
let prefix = dir.path();
|
||||||
// Create prefix dir but not the binary
|
|
||||||
fs::create_dir_all(prefix).unwrap();
|
fs::create_dir_all(prefix).unwrap();
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
@ -574,7 +558,6 @@ mod tests {
|
||||||
|
|
||||||
fn create_test_env(sys_paths: Vec<PathBuf>) -> PythonEnvironment {
|
fn create_test_env(sys_paths: Vec<PathBuf>) -> PythonEnvironment {
|
||||||
PythonEnvironment {
|
PythonEnvironment {
|
||||||
// Dummy values for fields not directly used by activate
|
|
||||||
python_path: PathBuf::from("dummy/bin/python"),
|
python_path: PathBuf::from("dummy/bin/python"),
|
||||||
sys_prefix: PathBuf::from("dummy"),
|
sys_prefix: PathBuf::from("dummy"),
|
||||||
sys_path: sys_paths,
|
sys_path: sys_paths,
|
||||||
|
@ -605,8 +588,6 @@ mod tests {
|
||||||
initial_len + 2,
|
initial_len + 2,
|
||||||
"Should have added 2 paths"
|
"Should have added 2 paths"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check that the *exact* paths were appended in the correct order
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
final_sys_path.get(initial_len).unwrap(),
|
final_sys_path.get(initial_len).unwrap(),
|
||||||
path1.to_str().expect("Path 1 should be valid UTF-8")
|
path1.to_str().expect("Path 1 should be valid UTF-8")
|
||||||
|
@ -644,7 +625,6 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_activate_with_non_existent_paths() -> PyResult<()> {
|
fn test_activate_with_non_existent_paths() -> PyResult<()> {
|
||||||
let temp_dir = tempdir().unwrap();
|
let temp_dir = tempdir().unwrap();
|
||||||
// These paths do not actually exist on the filesystem
|
|
||||||
let path1 = temp_dir.path().join("non_existent_dir");
|
let path1 = temp_dir.path().join("non_existent_dir");
|
||||||
let path2 = temp_dir.path().join("another_missing/path");
|
let path2 = temp_dir.path().join("another_missing/path");
|
||||||
|
|
||||||
|
@ -687,11 +667,9 @@ mod tests {
|
||||||
let valid_path = temp_dir.path().join("valid_dir");
|
let valid_path = temp_dir.path().join("valid_dir");
|
||||||
fs::create_dir(&valid_path).unwrap();
|
fs::create_dir(&valid_path).unwrap();
|
||||||
|
|
||||||
// Create a PathBuf from invalid UTF-8 bytes
|
|
||||||
let invalid_bytes = b"invalid_\xff_utf8";
|
let invalid_bytes = b"invalid_\xff_utf8";
|
||||||
let os_str = OsStr::from_bytes(invalid_bytes);
|
let os_str = OsStr::from_bytes(invalid_bytes);
|
||||||
let non_utf8_path = PathBuf::from(os_str);
|
let non_utf8_path = PathBuf::from(os_str);
|
||||||
// Sanity check: ensure this path *cannot* be converted to str
|
|
||||||
assert!(
|
assert!(
|
||||||
non_utf8_path.to_str().is_none(),
|
non_utf8_path.to_str().is_none(),
|
||||||
"Path should not be convertible to UTF-8 str"
|
"Path should not be convertible to UTF-8 str"
|
||||||
|
@ -708,7 +686,6 @@ mod tests {
|
||||||
test_env.activate(py)?;
|
test_env.activate(py)?;
|
||||||
|
|
||||||
let final_sys_path = get_sys_path(py)?;
|
let final_sys_path = get_sys_path(py)?;
|
||||||
// Should have added only the valid path
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
final_sys_path.len(),
|
final_sys_path.len(),
|
||||||
initial_len + 1,
|
initial_len + 1,
|
||||||
|
@ -719,7 +696,6 @@ mod tests {
|
||||||
valid_path.to_str().unwrap()
|
valid_path.to_str().unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check that the invalid path string representation is NOT present
|
|
||||||
let invalid_path_lossy = non_utf8_path.to_string_lossy();
|
let invalid_path_lossy = non_utf8_path.to_string_lossy();
|
||||||
assert!(
|
assert!(
|
||||||
!final_sys_path
|
!final_sys_path
|
||||||
|
@ -733,17 +709,14 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(windows)] // Test specific behavior for invalid UTF-16/WTF-8 on Windows
|
#[cfg(windows)]
|
||||||
fn test_activate_skips_non_utf8_paths_windows() -> PyResult<()> {
|
fn test_activate_skips_non_utf8_paths_windows() -> PyResult<()> {
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::os::windows::ffi::OsStringExt;
|
use std::os::windows::ffi::OsStringExt;
|
||||||
|
|
||||||
let temp_dir = tempdir().unwrap();
|
let temp_dir = tempdir().unwrap();
|
||||||
let valid_path = temp_dir.path().join("valid_dir");
|
let valid_path = temp_dir.path().join("valid_dir");
|
||||||
// No need to create dir, just need the PathBuf
|
|
||||||
|
|
||||||
// Create an OsString from invalid UTF-16 (a lone surrogate)
|
|
||||||
// D800 is a high surrogate, not valid unless paired with a low surrogate.
|
|
||||||
let invalid_wide: Vec<u16> = vec![
|
let invalid_wide: Vec<u16> = vec![
|
||||||
'i' as u16, 'n' as u16, 'v' as u16, 'a' as u16, 'l' as u16, 'i' as u16, 'd' as u16,
|
'i' as u16, 'n' as u16, 'v' as u16, 'a' as u16, 'l' as u16, 'i' as u16, 'd' as u16,
|
||||||
'_' as u16, 0xD800, '_' as u16, 'w' as u16, 'i' as u16, 'd' as u16, 'e' as u16,
|
'_' as u16, 0xD800, '_' as u16, 'w' as u16, 'i' as u16, 'd' as u16, 'e' as u16,
|
||||||
|
@ -751,7 +724,6 @@ mod tests {
|
||||||
let os_string = OsString::from_wide(&invalid_wide);
|
let os_string = OsString::from_wide(&invalid_wide);
|
||||||
let non_utf8_path = PathBuf::from(os_string);
|
let non_utf8_path = PathBuf::from(os_string);
|
||||||
|
|
||||||
// Sanity check: ensure this path *cannot* be converted to a valid UTF-8 str
|
|
||||||
assert!(
|
assert!(
|
||||||
non_utf8_path.to_str().is_none(),
|
non_utf8_path.to_str().is_none(),
|
||||||
"Path with lone surrogate should not be convertible to UTF-8 str"
|
"Path with lone surrogate should not be convertible to UTF-8 str"
|
||||||
|
@ -768,7 +740,6 @@ mod tests {
|
||||||
test_env.activate(py)?;
|
test_env.activate(py)?;
|
||||||
|
|
||||||
let final_sys_path = get_sys_path(py)?;
|
let final_sys_path = get_sys_path(py)?;
|
||||||
// Should have added only the valid path
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
final_sys_path.len(),
|
final_sys_path.len(),
|
||||||
initial_len + 1,
|
initial_len + 1,
|
||||||
|
@ -779,7 +750,6 @@ mod tests {
|
||||||
valid_path.to_str().unwrap()
|
valid_path.to_str().unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check that the invalid path string representation is NOT present
|
|
||||||
let invalid_path_lossy = non_utf8_path.to_string_lossy();
|
let invalid_path_lossy = non_utf8_path.to_string_lossy();
|
||||||
assert!(
|
assert!(
|
||||||
!final_sys_path
|
!final_sys_path
|
||||||
|
|
196
crates/djls-project/src/system.rs
Normal file
196
crates/djls-project/src/system.rs
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
use std::env::VarError;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use which::Error as WhichError;
|
||||||
|
|
||||||
|
pub fn find_executable(name: &str) -> Result<PathBuf, WhichError> {
|
||||||
|
#[cfg(not(test))]
|
||||||
|
{
|
||||||
|
which::which(name)
|
||||||
|
}
|
||||||
|
#[cfg(test)]
|
||||||
|
{
|
||||||
|
mock::find_executable_mocked(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn env_var(key: &str) -> Result<String, VarError> {
|
||||||
|
#[cfg(not(test))]
|
||||||
|
{
|
||||||
|
std::env::var(key)
|
||||||
|
}
|
||||||
|
#[cfg(test)]
|
||||||
|
{
|
||||||
|
mock::env_var_mocked(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod mock {
|
||||||
|
use super::*;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::thread_local;
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static MOCK_EXEC_RESULTS: RefCell<HashMap<String, Result<PathBuf, WhichError>>> = RefCell::new(HashMap::new());
|
||||||
|
static MOCK_ENV_RESULTS: RefCell<HashMap<String, Result<String, VarError>>> = RefCell::new(HashMap::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn find_executable_mocked(name: &str) -> Result<PathBuf, WhichError> {
|
||||||
|
MOCK_EXEC_RESULTS.with(|mocks| {
|
||||||
|
mocks
|
||||||
|
.borrow()
|
||||||
|
.get(name)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or(Err(WhichError::CannotFindBinaryPath))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn env_var_mocked(key: &str) -> Result<String, VarError> {
|
||||||
|
MOCK_ENV_RESULTS.with(|mocks| {
|
||||||
|
mocks
|
||||||
|
.borrow()
|
||||||
|
.get(key)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or(Err(VarError::NotPresent))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RAII guard to clear all mocks automatically after each test.
|
||||||
|
pub struct MockGuard;
|
||||||
|
impl Drop for MockGuard {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
MOCK_EXEC_RESULTS.with(|mocks| mocks.borrow_mut().clear());
|
||||||
|
MOCK_ENV_RESULTS.with(|mocks| mocks.borrow_mut().clear());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_exec_path(name: &str, path: PathBuf) {
|
||||||
|
MOCK_EXEC_RESULTS.with(|mocks| {
|
||||||
|
mocks.borrow_mut().insert(name.to_string(), Ok(path));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_exec_error(name: &str, error: WhichError) {
|
||||||
|
MOCK_EXEC_RESULTS.with(|mocks| {
|
||||||
|
mocks.borrow_mut().insert(name.to_string(), Err(error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_env_var(key: &str, value: String) {
|
||||||
|
MOCK_ENV_RESULTS.with(|mocks| {
|
||||||
|
mocks.borrow_mut().insert(key.to_string(), Ok(value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simulates VarError::NotPresent
|
||||||
|
pub fn remove_env_var(key: &str) {
|
||||||
|
MOCK_ENV_RESULTS.with(|mocks| {
|
||||||
|
mocks
|
||||||
|
.borrow_mut()
|
||||||
|
.insert(key.to_string(), Err(VarError::NotPresent));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::mock::{self as sys_mock, MockGuard};
|
||||||
|
use super::*;
|
||||||
|
use std::env::VarError;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use which::Error as WhichError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_exec_mock_path_retrieval() {
|
||||||
|
let _guard = MockGuard;
|
||||||
|
let expected_path = PathBuf::from("/mock/path/to/python");
|
||||||
|
sys_mock::set_exec_path("python", expected_path.clone());
|
||||||
|
let result = find_executable("python");
|
||||||
|
assert_eq!(result.unwrap(), expected_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_exec_mock_error_retrieval() {
|
||||||
|
let _guard = MockGuard;
|
||||||
|
sys_mock::set_exec_error("cargo", WhichError::CannotFindBinaryPath);
|
||||||
|
let result = find_executable("cargo");
|
||||||
|
assert!(matches!(result, Err(WhichError::CannotFindBinaryPath)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_exec_mock_default_error_if_unmocked() {
|
||||||
|
let _guard = MockGuard;
|
||||||
|
let result = find_executable("git"); // Not mocked
|
||||||
|
assert!(matches!(result, Err(WhichError::CannotFindBinaryPath)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_env_mock_set_var_retrieval() {
|
||||||
|
let _guard = MockGuard;
|
||||||
|
sys_mock::set_env_var("MY_VAR", "my_value".to_string());
|
||||||
|
let result = env_var("MY_VAR");
|
||||||
|
assert_eq!(result.unwrap(), "my_value");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_env_mock_remove_var_retrieval() {
|
||||||
|
let _guard = MockGuard;
|
||||||
|
// Set it first, then remove it via mock
|
||||||
|
sys_mock::set_env_var("TEMP_VAR", "temp_value".to_string());
|
||||||
|
sys_mock::remove_env_var("TEMP_VAR");
|
||||||
|
let result = env_var("TEMP_VAR");
|
||||||
|
assert!(matches!(result, Err(VarError::NotPresent)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_env_mock_default_error_if_unmocked() {
|
||||||
|
let _guard = MockGuard;
|
||||||
|
let result = env_var("UNMOCKED_VAR"); // Not mocked
|
||||||
|
assert!(matches!(result, Err(VarError::NotPresent)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mock_guard_clears_all_mocks() {
|
||||||
|
let expected_exec_path = PathBuf::from("/tmp/myprog");
|
||||||
|
let expected_env_val = "test_value".to_string();
|
||||||
|
|
||||||
|
{
|
||||||
|
let _guard = MockGuard;
|
||||||
|
sys_mock::set_exec_path("myprog", expected_exec_path.clone());
|
||||||
|
sys_mock::set_env_var("MY_TEST_ENV", expected_env_val.clone());
|
||||||
|
// Guard drops here, clearing both mocks
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify mocks were cleared
|
||||||
|
let _guard = MockGuard;
|
||||||
|
let result_exec = find_executable("myprog");
|
||||||
|
assert!(matches!(result_exec, Err(WhichError::CannotFindBinaryPath)));
|
||||||
|
let result_env = env_var("MY_TEST_ENV");
|
||||||
|
assert!(matches!(result_env, Err(VarError::NotPresent)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mocks_are_separate_between_tests() {
|
||||||
|
let _guard = MockGuard; // Ensures clean state
|
||||||
|
|
||||||
|
// Check state from previous tests (should be cleared)
|
||||||
|
let result_python = find_executable("python");
|
||||||
|
assert!(matches!(
|
||||||
|
result_python,
|
||||||
|
Err(WhichError::CannotFindBinaryPath)
|
||||||
|
));
|
||||||
|
let result_myvar = env_var("MY_VAR");
|
||||||
|
assert!(matches!(result_myvar, Err(VarError::NotPresent)));
|
||||||
|
|
||||||
|
// Set mocks specific to this test
|
||||||
|
let expected_path_node = PathBuf::from("/usr/bin/node");
|
||||||
|
sys_mock::set_exec_path("node", expected_path_node.clone());
|
||||||
|
sys_mock::set_env_var("NODE_ENV", "production".to_string());
|
||||||
|
|
||||||
|
let result_node = find_executable("node");
|
||||||
|
assert_eq!(result_node.unwrap(), expected_path_node);
|
||||||
|
let result_node_env = env_var("NODE_ENV");
|
||||||
|
assert_eq!(result_node_env.unwrap(), "production");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue