-
Notifications
You must be signed in to change notification settings - Fork 2.3k
make agent manager singleton #4880
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,8 +25,10 @@ mod execution_tests { | |
| } | ||
|
|
||
| #[tokio::test] | ||
| #[serial] | ||
| async fn test_session_isolation() { | ||
| let manager = AgentManager::new(None).await.unwrap(); | ||
| AgentManager::reset_for_test(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do we need to reset these? I'd imagine you only want to reset for testing for situations where you actually want to test that a new singleton does somethign different
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it is better to reset every time to keep the tests isolated from each other
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, no. you should isolate these tests from the rest of the system, but if you write tests for a singleton and then reset the singleton all the time, what are you testing?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you mean that we want to cover the singleton access crossing multiple test cases? Unit tests should only cover each simple functionalities of the methods, we have a test case to test the multiple access as the same time, and others for the main logic, to make all these tests isolated, we need such reset. |
||
| let manager = AgentManager::instance().await.unwrap(); | ||
|
|
||
| let session1 = uuid::Uuid::new_v4().to_string(); | ||
| let session2 = uuid::Uuid::new_v4().to_string(); | ||
|
|
@@ -51,35 +53,15 @@ mod execution_tests { | |
| .unwrap(); | ||
|
|
||
| assert!(Arc::ptr_eq(&agent1, &agent1_again)); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn test_session_limit() { | ||
| let manager = AgentManager::new(Some(3)).await.unwrap(); | ||
|
|
||
| let sessions: Vec<_> = (0..3).map(|i| format!("session-{}", i)).collect(); | ||
|
|
||
| for session in &sessions { | ||
| manager | ||
| .get_or_create_agent(session.clone(), SessionExecutionMode::chat()) | ||
| .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, SessionExecutionMode::chat()) | ||
| .await | ||
| .unwrap(); | ||
|
|
||
| assert_eq!(manager.session_count().await, 3); | ||
| assert!(!manager.has_session(&sessions[0]).await); | ||
| AgentManager::reset_for_test(); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| #[serial] | ||
| async fn test_remove_session() { | ||
| let manager = AgentManager::new(None).await.unwrap(); | ||
| AgentManager::reset_for_test(); | ||
| let manager = AgentManager::instance().await.unwrap(); | ||
| let session = String::from("remove-test"); | ||
|
|
||
| manager | ||
|
|
@@ -92,11 +74,15 @@ mod execution_tests { | |
| assert!(!manager.has_session(&session).await); | ||
|
|
||
| assert!(manager.remove_session(&session).await.is_err()); | ||
|
|
||
| AgentManager::reset_for_test(); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| #[serial] | ||
| async fn test_concurrent_access() { | ||
| let manager = Arc::new(AgentManager::new(None).await.unwrap()); | ||
| AgentManager::reset_for_test(); | ||
| let manager = AgentManager::instance().await.unwrap(); | ||
| let session = String::from("concurrent-test"); | ||
|
|
||
| let mut handles = vec![]; | ||
|
|
@@ -121,11 +107,15 @@ mod execution_tests { | |
| } | ||
|
|
||
| assert_eq!(manager.session_count().await, 1); | ||
|
|
||
| AgentManager::reset_for_test(); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| #[serial] | ||
| async fn test_different_modes_same_session() { | ||
| let manager = AgentManager::new(None).await.unwrap(); | ||
| AgentManager::reset_for_test(); | ||
| let manager = AgentManager::instance().await.unwrap(); | ||
| let session_id = String::from("mode-test"); | ||
|
|
||
| // Create initial agent | ||
|
|
@@ -142,13 +132,17 @@ mod execution_tests { | |
| .unwrap(); | ||
|
|
||
| assert!(Arc::ptr_eq(&agent1, &agent2)); | ||
|
|
||
| AgentManager::reset_for_test(); | ||
| } | ||
|
|
||
| #[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) | ||
| let manager = Arc::new(AgentManager::new(None).await.unwrap()); | ||
| 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 | ||
|
|
@@ -181,44 +175,24 @@ mod execution_tests { | |
|
|
||
| // Only one session should exist | ||
| assert_eq!(manager.session_count().await, 1); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn test_edge_case_max_sessions_one() { | ||
| let manager = AgentManager::new(Some(1)).await.unwrap(); | ||
|
|
||
| let session1 = String::from("only-session"); | ||
| manager | ||
| .get_or_create_agent(session1.clone(), SessionExecutionMode::Interactive) | ||
| .await | ||
| .unwrap(); | ||
|
|
||
| assert_eq!(manager.session_count().await, 1); | ||
|
|
||
| // Creating second session should evict the first | ||
| let session2 = String::from("new-session"); | ||
| manager | ||
| .get_or_create_agent(session2.clone(), SessionExecutionMode::Interactive) | ||
| .await | ||
| .unwrap(); | ||
|
|
||
| assert!(!manager.has_session(&session1).await); | ||
| assert!(manager.has_session(&session2).await); | ||
| assert_eq!(manager.session_count().await, 1); | ||
| AgentManager::reset_for_test(); | ||
| } | ||
|
|
||
| #[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::new(None).await.unwrap(); | ||
| let manager = AgentManager::instance().await.unwrap(); | ||
| let result = manager.configure_default_provider().await; | ||
|
|
||
| assert!(result.is_ok()); | ||
|
|
@@ -234,14 +208,18 @@ mod execution_tests { | |
| } else { | ||
| env::remove_var("GOOSE_DEFAULT_MODEL"); | ||
| } | ||
|
|
||
| AgentManager::reset_for_test(); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| #[serial] | ||
| async fn test_set_default_provider() { | ||
| use goose::providers::testprovider::TestProvider; | ||
| use std::sync::Arc; | ||
|
|
||
| let manager = AgentManager::new(None).await.unwrap(); | ||
| 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!( | ||
|
|
@@ -263,59 +241,65 @@ mod execution_tests { | |
| .unwrap(); | ||
|
|
||
| assert!(manager.has_session(&session).await); | ||
|
|
||
| AgentManager::reset_for_test(); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn test_eviction_updates_last_used() { | ||
| // Test that accessing a session updates its last_used timestamp | ||
| // and affects eviction order | ||
| let manager = AgentManager::new(Some(2)).await.unwrap(); | ||
| #[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 session1 = String::from("session-1"); | ||
| let session2 = String::from("session-2"); | ||
| let result = manager.remove_session(&session).await; | ||
| assert!(result.is_err()); | ||
| assert!(result.unwrap_err().to_string().contains("not found")); | ||
|
|
||
| manager | ||
| .get_or_create_agent(session1.clone(), SessionExecutionMode::Interactive) | ||
| .await | ||
| .unwrap(); | ||
| AgentManager::reset_for_test(); | ||
| } | ||
|
|
||
| // Small delay to ensure different timestamps | ||
| tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; | ||
| #[tokio::test] | ||
| #[serial] | ||
| async fn test_singleton_instance() { | ||
| AgentManager::reset_for_test(); | ||
|
|
||
| manager | ||
| .get_or_create_agent(session2.clone(), SessionExecutionMode::Interactive) | ||
| .await | ||
| .unwrap(); | ||
| let instance1 = AgentManager::instance().await.unwrap(); | ||
| let instance2 = AgentManager::instance().await.unwrap(); | ||
|
|
||
| // Access session1 again to update its last_used | ||
| tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; | ||
| manager | ||
| .get_or_create_agent(session1.clone(), SessionExecutionMode::Interactive) | ||
| .await | ||
| .unwrap(); | ||
| assert!(Arc::ptr_eq(&instance1, &instance2)); | ||
|
|
||
| // Now create a third session - should evict session2 (least recently used) | ||
| let session3 = String::from("session-3"); | ||
| manager | ||
| .get_or_create_agent(session3.clone(), SessionExecutionMode::Interactive) | ||
| let session_id = String::from("singleton-test"); | ||
| instance1 | ||
| .get_or_create_agent(session_id.clone(), SessionExecutionMode::Interactive) | ||
| .await | ||
| .unwrap(); | ||
|
|
||
| // session1 should still exist (recently accessed) | ||
| // session2 should be evicted (least recently used) | ||
| assert!(manager.has_session(&session1).await); | ||
| assert!(!manager.has_session(&session2).await); | ||
| assert!(manager.has_session(&session3).await); | ||
| assert!(instance2.has_session(&session_id).await); | ||
|
|
||
| AgentManager::reset_for_test(); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn test_remove_nonexistent_session_error() { | ||
| // Test that removing a non-existent session returns an error | ||
| let manager = AgentManager::new(None).await.unwrap(); | ||
| let session = String::from("never-created"); | ||
| #[serial] | ||
| async fn test_singleton_persistence_across_calls() { | ||
yingjiehe-xyz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| AgentManager::reset_for_test(); | ||
|
|
||
| let result = manager.remove_session(&session).await; | ||
| assert!(result.is_err()); | ||
| assert!(result.unwrap_err().to_string().contains("not found")); | ||
| let session_id = String::from("persistence-test"); | ||
| { | ||
| let manager = AgentManager::instance().await.unwrap(); | ||
| manager | ||
| .get_or_create_agent(session_id.clone(), SessionExecutionMode::Interactive) | ||
| .await | ||
| .unwrap(); | ||
| } | ||
|
|
||
| { | ||
| let manager = AgentManager::instance().await.unwrap(); | ||
| assert!(manager.has_session(&session_id).await); | ||
| } | ||
|
|
||
| AgentManager::reset_for_test(); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you do want to annotate this with test only though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it work here, we can use
#[cfg(test)]if the tests are agent class are in the same package, otherwise, tests cannot find thereset_for_testsee error: