From 63e0280d09e4da20441bb1aba77fdc5d627760fd Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Wed, 28 Jan 2026 18:42:51 -0800 Subject: [PATCH] Add tempfile as a dev-dependency and implement tests for architecture and virtual environment functionality --- Cargo.lock | 3 + crates/pet-core/Cargo.toml | 3 + crates/pet-core/src/arch.rs | 78 +++++++++++++ crates/pet-core/src/pyvenv_cfg.rs | 147 +++++++++++++++++++++++++ crates/pet-venv/Cargo.toml | 3 + crates/pet-venv/src/lib.rs | 116 ++++++++++++++++++++ crates/pet-virtualenv/Cargo.toml | 3 + crates/pet-virtualenv/src/lib.rs | 175 ++++++++++++++++++++++++++++++ 8 files changed, 528 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index c91c6a03..f313eac3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -480,6 +480,7 @@ dependencies = [ "regex", "serde", "serde_json", + "tempfile", ] [[package]] @@ -721,6 +722,7 @@ dependencies = [ "pet-core", "pet-python-utils", "pet-virtualenv", + "tempfile", ] [[package]] @@ -732,6 +734,7 @@ dependencies = [ "pet-core", "pet-fs", "pet-python-utils", + "tempfile", ] [[package]] diff --git a/crates/pet-core/Cargo.toml b/crates/pet-core/Cargo.toml index a1161fc5..961defce 100644 --- a/crates/pet-core/Cargo.toml +++ b/crates/pet-core/Cargo.toml @@ -15,3 +15,6 @@ lazy_static = "1.4.0" regex = "1.10.4" log = "0.4.21" serde_json = "1.0.93" + +[dev-dependencies] +tempfile = "3.10" diff --git a/crates/pet-core/src/arch.rs b/crates/pet-core/src/arch.rs index 960bba9a..f8371358 100644 --- a/crates/pet-core/src/arch.rs +++ b/crates/pet-core/src/arch.rs @@ -36,3 +36,81 @@ impl std::fmt::Display for Architecture { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_architecture_display_x64() { + let arch = Architecture::X64; + assert_eq!(format!("{}", arch), "x64"); + } + + #[test] + fn test_architecture_display_x86() { + let arch = Architecture::X86; + assert_eq!(format!("{}", arch), "x86"); + } + + #[test] + fn test_architecture_ordering() { + let x64 = Architecture::X64; + let x86 = Architecture::X86; + // X64 < X86 alphabetically + assert!(x64 < x86); + assert!(x86 > x64); + assert_eq!(x64.cmp(&x64), std::cmp::Ordering::Equal); + } + + #[test] + fn test_architecture_partial_ordering() { + let x64 = Architecture::X64; + let x86 = Architecture::X86; + assert_eq!(x64.partial_cmp(&x86), Some(std::cmp::Ordering::Less)); + assert_eq!(x86.partial_cmp(&x64), Some(std::cmp::Ordering::Greater)); + assert_eq!(x64.partial_cmp(&x64), Some(std::cmp::Ordering::Equal)); + } + + #[test] + fn test_architecture_equality() { + assert_eq!(Architecture::X64, Architecture::X64); + assert_eq!(Architecture::X86, Architecture::X86); + assert_ne!(Architecture::X64, Architecture::X86); + } + + #[test] + fn test_architecture_clone() { + let arch = Architecture::X64; + let cloned = arch.clone(); + assert_eq!(arch, cloned); + } + + #[test] + fn test_architecture_debug() { + let arch = Architecture::X64; + assert_eq!(format!("{:?}", arch), "X64"); + let arch = Architecture::X86; + assert_eq!(format!("{:?}", arch), "X86"); + } + + #[test] + fn test_architecture_serialize() { + let arch = Architecture::X64; + let json = serde_json::to_string(&arch).unwrap(); + assert_eq!(json, "\"x64\""); + + let arch = Architecture::X86; + let json = serde_json::to_string(&arch).unwrap(); + assert_eq!(json, "\"x86\""); + } + + #[test] + fn test_architecture_deserialize() { + let arch: Architecture = serde_json::from_str("\"x64\"").unwrap(); + assert_eq!(arch, Architecture::X64); + + let arch: Architecture = serde_json::from_str("\"x86\"").unwrap(); + assert_eq!(arch, Architecture::X86); + } +} diff --git a/crates/pet-core/src/pyvenv_cfg.rs b/crates/pet-core/src/pyvenv_cfg.rs index 28dbff54..42616158 100644 --- a/crates/pet-core/src/pyvenv_cfg.rs +++ b/crates/pet-core/src/pyvenv_cfg.rs @@ -186,3 +186,150 @@ fn parse_prompt(line: &str) -> Option { } None } + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Write; + use tempfile::tempdir; + + #[test] + fn test_parse_version_standard() { + let line = "version = 3.11.4"; + let result = parse_version(line, &VERSION); + assert!(result.is_some()); + let (ver, major, minor) = result.unwrap(); + assert_eq!(ver, "3.11.4"); + assert_eq!(major, 3); + assert_eq!(minor, 11); + } + + #[test] + fn test_parse_version_info() { + let line = "version_info = 3.12.0.final"; + let result = parse_version(line, &VERSION_INFO); + assert!(result.is_some()); + let (ver, major, minor) = result.unwrap(); + assert_eq!(ver, "3.12.0.final"); + assert_eq!(major, 3); + assert_eq!(minor, 12); + } + + #[test] + fn test_parse_version_no_match() { + let line = "home = /usr/bin/python"; + let result = parse_version(line, &VERSION); + assert!(result.is_none()); + } + + #[test] + fn test_parse_prompt_double_quotes() { + let line = r#"prompt = "my-env""#; + let result = parse_prompt(line); + assert_eq!(result, Some("my-env".to_string())); + } + + #[test] + fn test_parse_prompt_single_quotes() { + let line = "prompt = 'my-env'"; + let result = parse_prompt(line); + assert_eq!(result, Some("my-env".to_string())); + } + + #[test] + fn test_parse_prompt_no_quotes() { + let line = "prompt = my-venv"; + let result = parse_prompt(line); + assert_eq!(result, Some("my-venv".to_string())); + } + + #[test] + fn test_parse_prompt_with_spaces() { + let line = "prompt = my-venv "; + let result = parse_prompt(line); + assert_eq!(result, Some("my-venv".to_string())); + } + + #[test] + fn test_parse_prompt_empty_value() { + let line = "prompt = "; + let result = parse_prompt(line); + assert!(result.is_none()); + } + + #[test] + fn test_parse_prompt_not_prompt_line() { + let line = "home = /usr/bin/python"; + let result = parse_prompt(line); + assert!(result.is_none()); + } + + #[test] + fn test_pyvenv_cfg_find_in_directory() { + let dir = tempdir().unwrap(); + let cfg_path = dir.path().join("pyvenv.cfg"); + let mut file = fs::File::create(&cfg_path).unwrap(); + writeln!(file, "version = 3.11.4").unwrap(); + writeln!(file, "prompt = test-env").unwrap(); + + let result = PyVenvCfg::find(dir.path()); + assert!(result.is_some()); + let cfg = result.unwrap(); + assert_eq!(cfg.version, "3.11.4"); + assert_eq!(cfg.version_major, 3); + assert_eq!(cfg.version_minor, 11); + assert_eq!(cfg.prompt, Some("test-env".to_string())); + } + + #[test] + fn test_pyvenv_cfg_find_from_bin() { + let dir = tempdir().unwrap(); + let bin_dir = dir.path().join("bin"); + fs::create_dir_all(&bin_dir).unwrap(); + + let cfg_path = dir.path().join("pyvenv.cfg"); + let mut file = fs::File::create(&cfg_path).unwrap(); + writeln!(file, "version = 3.10.0").unwrap(); + + let result = PyVenvCfg::find(&bin_dir); + assert!(result.is_some()); + let cfg = result.unwrap(); + assert_eq!(cfg.version, "3.10.0"); + assert_eq!(cfg.version_major, 3); + assert_eq!(cfg.version_minor, 10); + } + + #[test] + fn test_pyvenv_cfg_not_found() { + let dir = tempdir().unwrap(); + let result = PyVenvCfg::find(dir.path()); + assert!(result.is_none()); + } + + #[test] + fn test_pyvenv_cfg_missing_version() { + let dir = tempdir().unwrap(); + let cfg_path = dir.path().join("pyvenv.cfg"); + let mut file = fs::File::create(&cfg_path).unwrap(); + writeln!(file, "home = /usr/bin/python").unwrap(); + writeln!(file, "prompt = my-env").unwrap(); + + let result = PyVenvCfg::find(dir.path()); + assert!(result.is_none()); // Version is required + } + + #[test] + fn test_pyvenv_cfg_version_info_format() { + let dir = tempdir().unwrap(); + let cfg_path = dir.path().join("pyvenv.cfg"); + let mut file = fs::File::create(&cfg_path).unwrap(); + writeln!(file, "version_info = 3.12.1.final.0").unwrap(); + + let result = PyVenvCfg::find(dir.path()); + assert!(result.is_some()); + let cfg = result.unwrap(); + assert_eq!(cfg.version, "3.12.1.final.0"); + assert_eq!(cfg.version_major, 3); + assert_eq!(cfg.version_minor, 12); + } +} diff --git a/crates/pet-venv/Cargo.toml b/crates/pet-venv/Cargo.toml index 411e1ad9..71217178 100644 --- a/crates/pet-venv/Cargo.toml +++ b/crates/pet-venv/Cargo.toml @@ -12,3 +12,6 @@ pet-core = { path = "../pet-core" } pet-virtualenv = { path = "../pet-virtualenv" } pet-python-utils = { path = "../pet-python-utils" } log = "0.4.21" + +[dev-dependencies] +tempfile = "3.10" diff --git a/crates/pet-venv/src/lib.rs b/crates/pet-venv/src/lib.rs index 5ca5575c..f6f76aa8 100644 --- a/crates/pet-venv/src/lib.rs +++ b/crates/pet-venv/src/lib.rs @@ -88,3 +88,119 @@ impl Locator for Venv { // We expect the user of this class to call `is_compatible` } } + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + use std::io::Write; + use tempfile::tempdir; + + #[test] + fn test_is_venv_dir_with_pyvenv_cfg() { + let dir = tempdir().unwrap(); + let cfg_path = dir.path().join("pyvenv.cfg"); + let mut file = fs::File::create(&cfg_path).unwrap(); + writeln!(file, "version = 3.11.4").unwrap(); + + assert!(is_venv_dir(dir.path())); + } + + #[test] + fn test_is_venv_dir_without_pyvenv_cfg() { + let dir = tempdir().unwrap(); + assert!(!is_venv_dir(dir.path())); + } + + #[test] + fn test_is_venv_with_pyvenv_cfg_in_parent() { + let dir = tempdir().unwrap(); + let bin_dir = dir.path().join("bin"); + fs::create_dir_all(&bin_dir).unwrap(); + + let cfg_path = dir.path().join("pyvenv.cfg"); + let mut file = fs::File::create(&cfg_path).unwrap(); + writeln!(file, "version = 3.11.4").unwrap(); + + // Create a fake python executable + let python_path = bin_dir.join("python"); + fs::File::create(&python_path).unwrap(); + + let env = PythonEnv::new(python_path, Some(dir.path().to_path_buf()), None); + assert!(is_venv(&env)); + } + + #[test] + fn test_is_venv_without_pyvenv_cfg() { + let dir = tempdir().unwrap(); + let bin_dir = dir.path().join("bin"); + fs::create_dir_all(&bin_dir).unwrap(); + + let python_path = bin_dir.join("python"); + fs::File::create(&python_path).unwrap(); + + let env = PythonEnv::new(python_path, Some(dir.path().to_path_buf()), None); + assert!(!is_venv(&env)); + } + + #[test] + fn test_venv_locator_kind() { + let venv = Venv::new(); + assert_eq!(venv.get_kind(), LocatorKind::Venv); + } + + #[test] + fn test_venv_supported_categories() { + let venv = Venv::new(); + let categories = venv.supported_categories(); + assert_eq!(categories.len(), 1); + assert_eq!(categories[0], PythonEnvironmentKind::Venv); + } + + #[test] + fn test_venv_default() { + let venv = Venv::default(); + assert_eq!(venv.get_kind(), LocatorKind::Venv); + } + + #[test] + fn test_venv_try_from_valid_venv() { + let dir = tempdir().unwrap(); + let bin_dir = dir.path().join("bin"); + fs::create_dir_all(&bin_dir).unwrap(); + + let cfg_path = dir.path().join("pyvenv.cfg"); + let mut file = fs::File::create(&cfg_path).unwrap(); + writeln!(file, "version = 3.11.4").unwrap(); + writeln!(file, "prompt = my-test-env").unwrap(); + + let python_path = bin_dir.join("python"); + fs::File::create(&python_path).unwrap(); + + let env = PythonEnv::new(python_path.clone(), Some(dir.path().to_path_buf()), None); + let venv = Venv::new(); + let result = venv.try_from(&env); + + assert!(result.is_some()); + let py_env = result.unwrap(); + assert_eq!(py_env.kind, Some(PythonEnvironmentKind::Venv)); + assert_eq!(py_env.name, Some("my-test-env".to_string())); + assert_eq!(py_env.executable, Some(python_path)); + } + + #[test] + fn test_venv_try_from_non_venv() { + let dir = tempdir().unwrap(); + let bin_dir = dir.path().join("bin"); + fs::create_dir_all(&bin_dir).unwrap(); + + let python_path = bin_dir.join("python"); + fs::File::create(&python_path).unwrap(); + + let env = PythonEnv::new(python_path, Some(dir.path().to_path_buf()), None); + let venv = Venv::new(); + let result = venv.try_from(&env); + + assert!(result.is_none()); + } +} diff --git a/crates/pet-virtualenv/Cargo.toml b/crates/pet-virtualenv/Cargo.toml index db1b5aab..8916048e 100644 --- a/crates/pet-virtualenv/Cargo.toml +++ b/crates/pet-virtualenv/Cargo.toml @@ -12,3 +12,6 @@ pet-core = { path = "../pet-core" } pet-fs = { path = "../pet-fs" } pet-python-utils = { path = "../pet-python-utils" } log = "0.4.21" + +[dev-dependencies] +tempfile = "3.10" diff --git a/crates/pet-virtualenv/src/lib.rs b/crates/pet-virtualenv/src/lib.rs index 4159c815..2a211517 100644 --- a/crates/pet-virtualenv/src/lib.rs +++ b/crates/pet-virtualenv/src/lib.rs @@ -141,3 +141,178 @@ impl Locator for VirtualEnv { // We expect the user of this class to call `is_compatible` } } + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + use tempfile::tempdir; + + #[test] + fn test_is_virtualenv_dir_with_activate() { + let dir = tempdir().unwrap(); + let bin_dir = dir.path().join("bin"); + fs::create_dir_all(&bin_dir).unwrap(); + fs::File::create(bin_dir.join("activate")).unwrap(); + + assert!(is_virtualenv_dir(dir.path())); + } + + #[test] + fn test_is_virtualenv_dir_with_activate_bat() { + let dir = tempdir().unwrap(); + let bin_dir = dir.path().join("bin"); + fs::create_dir_all(&bin_dir).unwrap(); + fs::File::create(bin_dir.join("activate.bat")).unwrap(); + + assert!(is_virtualenv_dir(dir.path())); + } + + #[test] + fn test_is_virtualenv_dir_with_activate_ps1() { + let dir = tempdir().unwrap(); + let bin_dir = dir.path().join("bin"); + fs::create_dir_all(&bin_dir).unwrap(); + fs::File::create(bin_dir.join("activate.ps1")).unwrap(); + + assert!(is_virtualenv_dir(dir.path())); + } + + #[test] + fn test_is_virtualenv_dir_from_bin() { + let dir = tempdir().unwrap(); + let bin_dir = dir.path().join("bin"); + fs::create_dir_all(&bin_dir).unwrap(); + fs::File::create(bin_dir.join("activate")).unwrap(); + + // Pass the bin directory itself + assert!(is_virtualenv_dir(&bin_dir)); + } + + #[test] + fn test_is_virtualenv_dir_without_activate() { + let dir = tempdir().unwrap(); + let bin_dir = dir.path().join("bin"); + fs::create_dir_all(&bin_dir).unwrap(); + + assert!(!is_virtualenv_dir(dir.path())); + } + + #[test] + fn test_is_virtualenv_dir_global_paths_excluded() { + // Global paths should not be considered virtualenvs + assert!(!is_virtualenv_dir(&PathBuf::from("/bin"))); + assert!(!is_virtualenv_dir(&PathBuf::from("/usr/bin"))); + assert!(!is_virtualenv_dir(&PathBuf::from("/usr/local/bin"))); + } + + #[test] + fn test_is_virtualenv_with_activate() { + let dir = tempdir().unwrap(); + let bin_dir = dir.path().join("bin"); + fs::create_dir_all(&bin_dir).unwrap(); + fs::File::create(bin_dir.join("activate")).unwrap(); + + let python_path = bin_dir.join("python"); + fs::File::create(&python_path).unwrap(); + + let env = PythonEnv::new(python_path, Some(dir.path().to_path_buf()), None); + assert!(is_virtualenv(&env)); + } + + #[test] + fn test_is_virtualenv_without_activate() { + let dir = tempdir().unwrap(); + let bin_dir = dir.path().join("bin"); + fs::create_dir_all(&bin_dir).unwrap(); + + let python_path = bin_dir.join("python"); + fs::File::create(&python_path).unwrap(); + + let env = PythonEnv::new(python_path, Some(dir.path().to_path_buf()), None); + assert!(!is_virtualenv(&env)); + } + + #[test] + fn test_is_virtualenv_without_prefix() { + let dir = tempdir().unwrap(); + let bin_dir = dir.path().join("bin"); + fs::create_dir_all(&bin_dir).unwrap(); + fs::File::create(bin_dir.join("activate")).unwrap(); + + let python_path = bin_dir.join("python"); + fs::File::create(&python_path).unwrap(); + + // No prefix provided + let env = PythonEnv::new(python_path, None, None); + assert!(is_virtualenv(&env)); + } + + #[test] + fn test_is_virtualenv_without_prefix_and_not_in_bin() { + let dir = tempdir().unwrap(); + // Not in bin or Scripts directory + let python_path = dir.path().join("python"); + fs::File::create(&python_path).unwrap(); + + let env = PythonEnv::new(python_path, None, None); + assert!(!is_virtualenv(&env)); + } + + #[test] + fn test_virtualenv_locator_kind() { + let venv = VirtualEnv::new(); + assert_eq!(venv.get_kind(), LocatorKind::VirtualEnv); + } + + #[test] + fn test_virtualenv_supported_categories() { + let venv = VirtualEnv::new(); + let categories = venv.supported_categories(); + assert_eq!(categories.len(), 1); + assert_eq!(categories[0], PythonEnvironmentKind::VirtualEnv); + } + + #[test] + fn test_virtualenv_default() { + let venv = VirtualEnv::default(); + assert_eq!(venv.get_kind(), LocatorKind::VirtualEnv); + } + + #[test] + fn test_virtualenv_try_from_valid() { + let dir = tempdir().unwrap(); + let bin_dir = dir.path().join("bin"); + fs::create_dir_all(&bin_dir).unwrap(); + fs::File::create(bin_dir.join("activate")).unwrap(); + + let python_path = bin_dir.join("python"); + fs::File::create(&python_path).unwrap(); + + let env = PythonEnv::new(python_path.clone(), Some(dir.path().to_path_buf()), None); + let venv = VirtualEnv::new(); + let result = venv.try_from(&env); + + assert!(result.is_some()); + let py_env = result.unwrap(); + assert_eq!(py_env.kind, Some(PythonEnvironmentKind::VirtualEnv)); + assert_eq!(py_env.executable, Some(python_path)); + assert_eq!(py_env.prefix, Some(dir.path().to_path_buf())); + } + + #[test] + fn test_virtualenv_try_from_non_virtualenv() { + let dir = tempdir().unwrap(); + let bin_dir = dir.path().join("bin"); + fs::create_dir_all(&bin_dir).unwrap(); + + let python_path = bin_dir.join("python"); + fs::File::create(&python_path).unwrap(); + + let env = PythonEnv::new(python_path, Some(dir.path().to_path_buf()), None); + let venv = VirtualEnv::new(); + let result = venv.try_from(&env); + + assert!(result.is_none()); + } +}