378 lines
11 KiB
Rust
378 lines
11 KiB
Rust
//! Integration tests for Python virtual environment dependency isolation
|
|
//!
|
|
//! Tests the end-to-end flow of creating isolated Python environments
|
|
//! for packs with dependencies.
|
|
|
|
use attune_worker::runtime::{
|
|
DependencyManager, DependencyManagerRegistry, DependencySpec, PythonVenvManager,
|
|
};
|
|
use tempfile::TempDir;
|
|
|
|
#[tokio::test]
|
|
async fn test_python_venv_creation() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let manager = PythonVenvManager::new(temp_dir.path().to_path_buf());
|
|
|
|
let spec = DependencySpec::new("python").with_dependency("requests==2.28.0");
|
|
|
|
let env_info = manager
|
|
.ensure_environment("test_pack", &spec)
|
|
.await
|
|
.expect("Failed to create environment");
|
|
|
|
assert_eq!(env_info.runtime, "python");
|
|
assert!(env_info.is_valid);
|
|
assert!(env_info.path.exists());
|
|
assert!(env_info.executable_path.exists());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_venv_idempotency() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let manager = PythonVenvManager::new(temp_dir.path().to_path_buf());
|
|
|
|
let spec = DependencySpec::new("python").with_dependency("requests==2.28.0");
|
|
|
|
// Create environment first time
|
|
let env_info1 = manager
|
|
.ensure_environment("test_pack", &spec)
|
|
.await
|
|
.expect("Failed to create environment");
|
|
|
|
let created_at1 = env_info1.created_at;
|
|
|
|
// Call ensure_environment again with same dependencies
|
|
let env_info2 = manager
|
|
.ensure_environment("test_pack", &spec)
|
|
.await
|
|
.expect("Failed to ensure environment");
|
|
|
|
// Should return existing environment (same created_at)
|
|
assert_eq!(env_info1.created_at, env_info2.created_at);
|
|
assert_eq!(created_at1, env_info2.created_at);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_venv_update_on_dependency_change() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let manager = PythonVenvManager::new(temp_dir.path().to_path_buf());
|
|
|
|
let spec1 = DependencySpec::new("python").with_dependency("requests==2.28.0");
|
|
|
|
// Create environment with first set of dependencies
|
|
let env_info1 = manager
|
|
.ensure_environment("test_pack", &spec1)
|
|
.await
|
|
.expect("Failed to create environment");
|
|
|
|
let created_at1 = env_info1.created_at;
|
|
|
|
// Give it a moment to ensure timestamp difference
|
|
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
|
|
|
|
// Change dependencies
|
|
let spec2 = DependencySpec::new("python").with_dependency("requests==2.29.0");
|
|
|
|
// Should recreate environment
|
|
let env_info2 = manager
|
|
.ensure_environment("test_pack", &spec2)
|
|
.await
|
|
.expect("Failed to update environment");
|
|
|
|
// Updated timestamp should be newer
|
|
assert!(env_info2.updated_at >= created_at1);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_multiple_pack_isolation() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let manager = PythonVenvManager::new(temp_dir.path().to_path_buf());
|
|
|
|
let spec1 = DependencySpec::new("python").with_dependency("requests==2.28.0");
|
|
let spec2 = DependencySpec::new("python").with_dependency("flask==2.3.0");
|
|
|
|
// Create environments for two different packs
|
|
let env1 = manager
|
|
.ensure_environment("pack_a", &spec1)
|
|
.await
|
|
.expect("Failed to create environment for pack_a");
|
|
|
|
let env2 = manager
|
|
.ensure_environment("pack_b", &spec2)
|
|
.await
|
|
.expect("Failed to create environment for pack_b");
|
|
|
|
// Should have different paths
|
|
assert_ne!(env1.path, env2.path);
|
|
assert_ne!(env1.executable_path, env2.executable_path);
|
|
|
|
// Both should be valid
|
|
assert!(env1.is_valid);
|
|
assert!(env2.is_valid);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_get_executable_path() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let manager = PythonVenvManager::new(temp_dir.path().to_path_buf());
|
|
|
|
let spec = DependencySpec::new("python");
|
|
|
|
manager
|
|
.ensure_environment("test_pack", &spec)
|
|
.await
|
|
.expect("Failed to create environment");
|
|
|
|
let python_path = manager
|
|
.get_executable_path("test_pack")
|
|
.await
|
|
.expect("Failed to get executable path");
|
|
|
|
assert!(python_path.exists());
|
|
assert!(python_path.to_string_lossy().contains("test_pack"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_validate_environment() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let manager = PythonVenvManager::new(temp_dir.path().to_path_buf());
|
|
|
|
// Non-existent environment should not be valid
|
|
let is_valid = manager
|
|
.validate_environment("nonexistent")
|
|
.await
|
|
.expect("Validation check failed");
|
|
assert!(!is_valid);
|
|
|
|
// Create environment
|
|
let spec = DependencySpec::new("python");
|
|
manager
|
|
.ensure_environment("test_pack", &spec)
|
|
.await
|
|
.expect("Failed to create environment");
|
|
|
|
// Should now be valid
|
|
let is_valid = manager
|
|
.validate_environment("test_pack")
|
|
.await
|
|
.expect("Validation check failed");
|
|
assert!(is_valid);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_remove_environment() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let manager = PythonVenvManager::new(temp_dir.path().to_path_buf());
|
|
|
|
let spec = DependencySpec::new("python");
|
|
|
|
// Create environment
|
|
let env_info = manager
|
|
.ensure_environment("test_pack", &spec)
|
|
.await
|
|
.expect("Failed to create environment");
|
|
|
|
let path = env_info.path.clone();
|
|
assert!(path.exists());
|
|
|
|
// Remove environment
|
|
manager
|
|
.remove_environment("test_pack")
|
|
.await
|
|
.expect("Failed to remove environment");
|
|
|
|
assert!(!path.exists());
|
|
|
|
// Get environment should return None
|
|
let env = manager
|
|
.get_environment("test_pack")
|
|
.await
|
|
.expect("Failed to get environment");
|
|
assert!(env.is_none());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_list_environments() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let manager = PythonVenvManager::new(temp_dir.path().to_path_buf());
|
|
|
|
let spec = DependencySpec::new("python");
|
|
|
|
// Create multiple environments
|
|
manager
|
|
.ensure_environment("pack_a", &spec)
|
|
.await
|
|
.expect("Failed to create pack_a");
|
|
|
|
manager
|
|
.ensure_environment("pack_b", &spec)
|
|
.await
|
|
.expect("Failed to create pack_b");
|
|
|
|
manager
|
|
.ensure_environment("pack_c", &spec)
|
|
.await
|
|
.expect("Failed to create pack_c");
|
|
|
|
// List should return all three
|
|
let environments = manager
|
|
.list_environments()
|
|
.await
|
|
.expect("Failed to list environments");
|
|
|
|
assert_eq!(environments.len(), 3);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_dependency_manager_registry() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let mut registry = DependencyManagerRegistry::new();
|
|
|
|
let python_manager = PythonVenvManager::new(temp_dir.path().to_path_buf());
|
|
registry.register(Box::new(python_manager));
|
|
|
|
// Should support python
|
|
assert!(registry.supports("python"));
|
|
assert!(!registry.supports("nodejs"));
|
|
|
|
// Should be able to get manager
|
|
let manager = registry.get("python");
|
|
assert!(manager.is_some());
|
|
assert_eq!(manager.unwrap().runtime_type(), "python");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_dependency_spec_builder() {
|
|
let spec = DependencySpec::new("python")
|
|
.with_dependency("requests==2.28.0")
|
|
.with_dependency("flask>=2.0.0")
|
|
.with_version_range(Some("3.8".to_string()), Some("3.11".to_string()));
|
|
|
|
assert_eq!(spec.runtime, "python");
|
|
assert_eq!(spec.dependencies.len(), 2);
|
|
assert!(spec.has_dependencies());
|
|
assert_eq!(spec.min_version, Some("3.8".to_string()));
|
|
assert_eq!(spec.max_version, Some("3.11".to_string()));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_requirements_file_content() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let manager = PythonVenvManager::new(temp_dir.path().to_path_buf());
|
|
|
|
let requirements = "requests==2.28.0\nflask==2.3.0\npydantic>=2.0.0";
|
|
let spec = DependencySpec::new("python").with_requirements_file(requirements.to_string());
|
|
|
|
let env_info = manager
|
|
.ensure_environment("test_pack", &spec)
|
|
.await
|
|
.expect("Failed to create environment with requirements file");
|
|
|
|
assert!(env_info.is_valid);
|
|
assert!(env_info.installed_dependencies.len() > 0);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_pack_ref_sanitization() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let manager = PythonVenvManager::new(temp_dir.path().to_path_buf());
|
|
|
|
let spec = DependencySpec::new("python");
|
|
|
|
// Pack refs with special characters should be sanitized
|
|
let env_info = manager
|
|
.ensure_environment("core.http", &spec)
|
|
.await
|
|
.expect("Failed to create environment");
|
|
|
|
// Path should not contain dots
|
|
let path_str = env_info.path.to_string_lossy();
|
|
assert!(path_str.contains("core_http"));
|
|
assert!(!path_str.contains("core.http"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_needs_update_detection() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let manager = PythonVenvManager::new(temp_dir.path().to_path_buf());
|
|
|
|
let spec1 = DependencySpec::new("python").with_dependency("requests==2.28.0");
|
|
|
|
// Non-existent environment needs update
|
|
let needs_update = manager
|
|
.needs_update("test_pack", &spec1)
|
|
.await
|
|
.expect("Failed to check update status");
|
|
assert!(needs_update);
|
|
|
|
// Create environment
|
|
manager
|
|
.ensure_environment("test_pack", &spec1)
|
|
.await
|
|
.expect("Failed to create environment");
|
|
|
|
// Same spec should not need update
|
|
let needs_update = manager
|
|
.needs_update("test_pack", &spec1)
|
|
.await
|
|
.expect("Failed to check update status");
|
|
assert!(!needs_update);
|
|
|
|
// Different spec should need update
|
|
let spec2 = DependencySpec::new("python").with_dependency("requests==2.29.0");
|
|
let needs_update = manager
|
|
.needs_update("test_pack", &spec2)
|
|
.await
|
|
.expect("Failed to check update status");
|
|
assert!(needs_update);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_empty_dependencies() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let manager = PythonVenvManager::new(temp_dir.path().to_path_buf());
|
|
|
|
// Pack with no dependencies should still create venv
|
|
let spec = DependencySpec::new("python");
|
|
assert!(!spec.has_dependencies());
|
|
|
|
let env_info = manager
|
|
.ensure_environment("test_pack", &spec)
|
|
.await
|
|
.expect("Failed to create environment without dependencies");
|
|
|
|
assert!(env_info.is_valid);
|
|
assert!(env_info.path.exists());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_get_environment_caching() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let manager = PythonVenvManager::new(temp_dir.path().to_path_buf());
|
|
|
|
let spec = DependencySpec::new("python");
|
|
|
|
// Create environment
|
|
manager
|
|
.ensure_environment("test_pack", &spec)
|
|
.await
|
|
.expect("Failed to create environment");
|
|
|
|
// First get_environment should read from disk
|
|
let env1 = manager
|
|
.get_environment("test_pack")
|
|
.await
|
|
.expect("Failed to get environment")
|
|
.expect("Environment not found");
|
|
|
|
// Second get_environment should use cache
|
|
let env2 = manager
|
|
.get_environment("test_pack")
|
|
.await
|
|
.expect("Failed to get environment")
|
|
.expect("Environment not found");
|
|
|
|
assert_eq!(env1.id, env2.id);
|
|
assert_eq!(env1.path, env2.path);
|
|
}
|