mod execution_tests { use goose::execution::manager::AgentManager; use goose::execution::SessionExecutionMode; use serial_test::serial; use std::sync::Arc; #[test] fn test_execution_mode_constructors() { assert_eq!( SessionExecutionMode::chat(), SessionExecutionMode::Interactive ); assert_eq!( SessionExecutionMode::scheduled(), SessionExecutionMode::Background ); let parent = "parent-123".to_string(); assert_eq!( SessionExecutionMode::task(parent.clone()), SessionExecutionMode::SubTask { parent_session: parent } ); } #[tokio::test] #[serial] async fn test_session_isolation() { AgentManager::reset_for_test(); let manager = AgentManager::instance().await.unwrap(); let session1 = uuid::Uuid::new_v4().to_string(); let session2 = uuid::Uuid::new_v4().to_string(); let agent1 = manager.get_or_create_agent(session1.clone()).await.unwrap(); let agent2 = manager.get_or_create_agent(session2.clone()).await.unwrap(); // Different sessions should have different agents assert!(!Arc::ptr_eq(&agent1, &agent2)); // Getting the same session should return the same agent let agent1_again = manager.get_or_create_agent(session1).await.unwrap(); assert!(Arc::ptr_eq(&agent1, &agent1_again)); AgentManager::reset_for_test(); } #[tokio::test] #[serial] async fn test_session_limit() { AgentManager::reset_for_test(); let manager = AgentManager::instance().await.unwrap(); let sessions: Vec<_> = (0..100).map(|i| format!("session-{}", i)).collect(); for session in &sessions { manager.get_or_create_agent(session.clone()).await.unwrap(); } // Create a new session after cleanup let new_session = "new-session".to_string(); let _new_agent = manager.get_or_create_agent(new_session).await.unwrap(); assert_eq!(manager.session_count().await, 100); } #[tokio::test] #[serial] async fn test_remove_session() { AgentManager::reset_for_test(); let manager = AgentManager::instance().await.unwrap(); let session = String::from("remove-test"); manager.get_or_create_agent(session.clone()).await.unwrap(); assert!(manager.has_session(&session).await); manager.remove_session(&session).await.unwrap(); assert!(!manager.has_session(&session).await); assert!(manager.remove_session(&session).await.is_err()); } #[tokio::test] #[serial] async fn test_concurrent_access() { AgentManager::reset_for_test(); let manager = AgentManager::instance().await.unwrap(); let session = String::from("concurrent-test"); let mut handles = vec![]; for _ in 0..10 { let mgr = Arc::clone(&manager); let sess = session.clone(); handles.push(tokio::spawn(async move { mgr.get_or_create_agent(sess).await.unwrap() })); } let agents: Vec<_> = futures::future::join_all(handles) .await .into_iter() .map(|r| r.unwrap()) .collect(); for agent in &agents[1..] { assert!(Arc::ptr_eq(&agents[0], agent)); } assert_eq!(manager.session_count().await, 1); } #[tokio::test] #[serial] async fn test_concurrent_session_creation_race_condition() { // Test that concurrent attempts to create the same new session ID // result in only one agent being created (tests double-check pattern) AgentManager::reset_for_test(); let manager = AgentManager::instance().await.unwrap(); let session_id = String::from("race-condition-test"); // Spawn multiple tasks trying to create the same NEW session simultaneously let mut handles = vec![]; for _ in 0..20 { let sess = session_id.clone(); let mgr_clone = Arc::clone(&manager); handles.push(tokio::spawn(async move { mgr_clone.get_or_create_agent(sess).await.unwrap() })); } // Collect all agents let agents: Vec<_> = futures::future::join_all(handles) .await .into_iter() .map(|r| r.unwrap()) .collect(); for agent in &agents[1..] { assert!( Arc::ptr_eq(&agents[0], agent), "All concurrent requests should get the same agent" ); } assert_eq!(manager.session_count().await, 1); } #[tokio::test] #[serial] async fn test_configure_default_provider() { use std::env; AgentManager::reset_for_test(); let original_provider = env::var("GOOSE_DEFAULT_PROVIDER").ok(); let original_model = env::var("GOOSE_DEFAULT_MODEL").ok(); env::set_var("GOOSE_DEFAULT_PROVIDER", "openai"); env::set_var("GOOSE_DEFAULT_MODEL", "gpt-4o-mini"); let manager = AgentManager::instance().await.unwrap(); let result = manager.configure_default_provider().await; assert!(result.is_ok()); // Restore original env vars if let Some(val) = original_provider { env::set_var("GOOSE_DEFAULT_PROVIDER", val); } else { env::remove_var("GOOSE_DEFAULT_PROVIDER"); } if let Some(val) = original_model { env::set_var("GOOSE_DEFAULT_MODEL", val); } else { env::remove_var("GOOSE_DEFAULT_MODEL"); } } #[tokio::test] #[serial] async fn test_set_default_provider() { use goose::providers::testprovider::TestProvider; use std::sync::Arc; AgentManager::reset_for_test(); let manager = AgentManager::instance().await.unwrap(); // Create a test provider for replaying (doesn't need inner provider) let temp_file = format!( "{}/test_provider_{}.json", std::env::temp_dir().display(), std::process::id() ); // Create an empty test provider (will fail on actual use but that's ok for this test) let test_provider = TestProvider::new_replaying(&temp_file) .unwrap_or_else(|_| TestProvider::new_replaying("/tmp/dummy.json").unwrap()); manager.set_default_provider(Arc::new(test_provider)).await; let session = String::from("provider-test"); let _agent = manager.get_or_create_agent(session.clone()).await.unwrap(); assert!(manager.has_session(&session).await); } #[tokio::test] #[serial] async fn test_eviction_updates_last_used() { AgentManager::reset_for_test(); // Test that accessing a session updates its last_used timestamp // and affects eviction order let manager = AgentManager::instance().await.unwrap(); let sessions: Vec<_> = (0..100).map(|i| format!("session-{}", i)).collect(); for session in &sessions { manager.get_or_create_agent(session.clone()).await.unwrap(); // Small delay to ensure different timestamps tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; } // Access the first session again to update its last_used tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; manager .get_or_create_agent(sessions[0].clone()) .await .unwrap(); // Now create a 101st session - should evict session2 (least recently used) let session101 = String::from("session-101"); manager .get_or_create_agent(session101.clone()) .await .unwrap(); assert!(manager.has_session(&sessions[0]).await); assert!(!manager.has_session(&sessions[1]).await); assert!(manager.has_session(&session101).await); } #[tokio::test] #[serial] async fn test_remove_nonexistent_session_error() { // Test that removing a non-existent session returns an error AgentManager::reset_for_test(); let manager = AgentManager::instance().await.unwrap(); let session = String::from("never-created"); let result = manager.remove_session(&session).await; assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("not found")); } }