Skip to content

Commit cb3edc0

Browse files
committed
move out new tests
1 parent 5ce3830 commit cb3edc0

File tree

4 files changed

+2
-732
lines changed

4 files changed

+2
-732
lines changed

tests/unit/test_agent_controller.py

+1-37
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import asyncio
2-
from unittest.mock import ANY, AsyncMock, MagicMock, patch
2+
from unittest.mock import ANY, AsyncMock, MagicMock
33
from uuid import uuid4
44

55
import pytest
@@ -833,39 +833,3 @@ def on_event_memory(event: Event):
833833

834834
# Check that the context window exceeded error was raised during the run
835835
assert step_state.has_errored
836-
837-
838-
@pytest.mark.asyncio
839-
async def test_run_controller_with_memory_error(test_event_stream):
840-
config = AppConfig()
841-
event_stream = test_event_stream
842-
843-
agent = MagicMock(spec=Agent)
844-
agent.llm = MagicMock(spec=LLM)
845-
agent.llm.metrics = Metrics()
846-
agent.llm.config = config.get_llm_config()
847-
848-
runtime = MagicMock(spec=Runtime)
849-
runtime.event_stream = event_stream
850-
851-
# Create a real Memory instance
852-
memory = Memory(event_stream=event_stream, sid='test-memory')
853-
854-
# Patch the _on_recall_action method to raise our test exception
855-
def mock_on_recall_action(*args, **kwargs):
856-
raise RuntimeError('Test memory error')
857-
858-
with patch.object(memory, '_on_recall_action', side_effect=mock_on_recall_action):
859-
state = await run_controller(
860-
config=config,
861-
initial_user_action=MessageAction(content='Test message'),
862-
runtime=runtime,
863-
sid='test',
864-
agent=agent,
865-
fake_user_response_fn=lambda _: 'repeat',
866-
memory=memory,
867-
)
868-
869-
assert state.iteration == 0
870-
assert state.agent_state == AgentState.ERROR
871-
assert state.last_error == 'Error: RuntimeError'

tests/unit/test_conversation_memory.py

+1-333
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
)
1313
from openhands.events.event import Event, EventSource, FileEditSource, FileReadSource
1414
from openhands.events.observation import CmdOutputObservation
15-
from openhands.events.observation.agent import RecallObservation, RecallType
1615
from openhands.events.observation.browse import BrowserOutputObservation
1716
from openhands.events.observation.commands import (
1817
CmdOutputMetadata,
@@ -24,7 +23,7 @@
2423
from openhands.events.observation.reject import UserRejectObservation
2524
from openhands.events.tool import ToolCallMetadata
2625
from openhands.memory.conversation_memory import ConversationMemory
27-
from openhands.utils.prompt import PromptManager, RepositoryInfo, RuntimeInfo
26+
from openhands.utils.prompt import PromptManager
2827

2928

3029
@pytest.fixture
@@ -468,334 +467,3 @@ def test_apply_prompt_caching(conversation_memory):
468467
assert messages[1].content[0].cache_prompt is False
469468
assert messages[2].content[0].cache_prompt is False
470469
assert messages[3].content[0].cache_prompt is True
471-
472-
473-
def test_process_events_with_environment_info_recall_observation(conversation_memory):
474-
"""Test processing a RecallObservation with ENVIRONMENT_INFO type."""
475-
obs = RecallObservation(
476-
recall_type=RecallType.ENVIRONMENT_INFO,
477-
repo_name='test-repo',
478-
repo_directory='/path/to/repo',
479-
repo_instructions='# Test Repository\nThis is a test repository.',
480-
runtime_hosts={'localhost': 8080},
481-
content='Recalled environment info',
482-
)
483-
484-
initial_messages = [
485-
Message(role='system', content=[TextContent(text='System message')])
486-
]
487-
488-
messages = conversation_memory.process_events(
489-
condensed_history=[obs],
490-
initial_messages=initial_messages,
491-
max_message_chars=None,
492-
vision_is_active=False,
493-
)
494-
495-
assert len(messages) == 2
496-
result = messages[1]
497-
assert result.role == 'user'
498-
assert len(result.content) == 1
499-
assert isinstance(result.content[0], TextContent)
500-
assert result.content[0].text == 'Formatted repository and runtime info'
501-
502-
# Verify the prompt_manager was called with the correct parameters
503-
conversation_memory.prompt_manager.build_additional_info.assert_called_once()
504-
call_args = conversation_memory.prompt_manager.build_additional_info.call_args[1]
505-
assert isinstance(call_args['repository_info'], RepositoryInfo)
506-
assert call_args['repository_info'].repo_name == 'test-repo'
507-
assert call_args['repository_info'].repo_directory == '/path/to/repo'
508-
assert isinstance(call_args['runtime_info'], RuntimeInfo)
509-
assert call_args['runtime_info'].available_hosts == {'localhost': 8080}
510-
assert (
511-
call_args['repo_instructions']
512-
== '# Test Repository\nThis is a test repository.'
513-
)
514-
515-
516-
def test_process_events_with_knowledge_microagent_recall_observation(
517-
conversation_memory,
518-
):
519-
"""Test processing a RecallObservation with KNOWLEDGE_MICROAGENT type."""
520-
microagent_knowledge = [
521-
{
522-
'agent_name': 'test_agent',
523-
'trigger_word': 'test',
524-
'content': 'This is test agent content',
525-
},
526-
{
527-
'agent_name': 'another_agent',
528-
'trigger_word': 'another',
529-
'content': 'This is another agent content',
530-
},
531-
{
532-
'agent_name': 'disabled_agent',
533-
'trigger_word': 'disabled',
534-
'content': 'This is disabled agent content',
535-
},
536-
]
537-
538-
obs = RecallObservation(
539-
recall_type=RecallType.KNOWLEDGE_MICROAGENT,
540-
microagent_knowledge=microagent_knowledge,
541-
content='Recalled knowledge from microagents',
542-
)
543-
544-
initial_messages = [
545-
Message(role='system', content=[TextContent(text='System message')])
546-
]
547-
548-
messages = conversation_memory.process_events(
549-
condensed_history=[obs],
550-
initial_messages=initial_messages,
551-
max_message_chars=None,
552-
vision_is_active=False,
553-
)
554-
555-
assert len(messages) == 2
556-
result = messages[1]
557-
assert result.role == 'user'
558-
assert len(result.content) == 1
559-
assert isinstance(result.content[0], TextContent)
560-
# Verify that disabled_agent is filtered out and enabled agents are included
561-
assert 'This is test agent content' in result.content[0].text
562-
assert 'This is another agent content' in result.content[0].text
563-
assert 'This is disabled agent content' not in result.content[0].text
564-
565-
# Verify the prompt_manager was called with the correct parameters
566-
conversation_memory.prompt_manager.build_microagent_info.assert_called_once()
567-
call_args = conversation_memory.prompt_manager.build_microagent_info.call_args[1]
568-
569-
# Check that disabled_agent was filtered out
570-
triggered_agents = call_args['triggered_agents']
571-
assert len(triggered_agents) == 2
572-
agent_names = [agent['agent_name'] for agent in triggered_agents]
573-
assert 'test_agent' in agent_names
574-
assert 'another_agent' in agent_names
575-
assert 'disabled_agent' not in agent_names
576-
577-
578-
def test_process_events_with_recall_observation_extensions_disabled(
579-
agent_config, conversation_memory
580-
):
581-
"""Test processing a RecallObservation when prompt extensions are disabled."""
582-
# Modify the agent config to disable prompt extensions
583-
agent_config.enable_prompt_extensions = False
584-
585-
obs = RecallObservation(
586-
recall_type=RecallType.ENVIRONMENT_INFO,
587-
repo_name='test-repo',
588-
repo_directory='/path/to/repo',
589-
content='Recalled environment info',
590-
)
591-
592-
initial_messages = [
593-
Message(role='system', content=[TextContent(text='System message')])
594-
]
595-
596-
messages = conversation_memory.process_events(
597-
condensed_history=[obs],
598-
initial_messages=initial_messages,
599-
max_message_chars=None,
600-
vision_is_active=False,
601-
)
602-
603-
# When prompt extensions are disabled, the RecallObservation should be ignored
604-
assert len(messages) == 1 # Only the initial system message
605-
assert messages[0].role == 'system'
606-
607-
# Verify the prompt_manager was not called
608-
conversation_memory.prompt_manager.build_additional_info.assert_not_called()
609-
conversation_memory.prompt_manager.build_microagent_info.assert_not_called()
610-
611-
612-
def test_process_events_with_empty_microagent_knowledge(conversation_memory):
613-
"""Test processing a RecallObservation with empty microagent knowledge."""
614-
obs = RecallObservation(
615-
recall_type=RecallType.KNOWLEDGE_MICROAGENT,
616-
microagent_knowledge=[],
617-
content='Recalled knowledge from microagents',
618-
)
619-
620-
initial_messages = [
621-
Message(role='system', content=[TextContent(text='System message')])
622-
]
623-
624-
messages = conversation_memory.process_events(
625-
condensed_history=[obs],
626-
initial_messages=initial_messages,
627-
max_message_chars=None,
628-
vision_is_active=False,
629-
)
630-
631-
# The implementation returns an empty string but still creates a message
632-
assert len(messages) == 2
633-
assert messages[0].role == 'system'
634-
assert messages[1].role == 'user'
635-
assert len(messages[1].content) == 1
636-
assert isinstance(messages[1].content[0], TextContent)
637-
assert messages[1].content[0].text == ''
638-
639-
# When there are no triggered agents, build_microagent_info is not called
640-
conversation_memory.prompt_manager.build_microagent_info.assert_not_called()
641-
642-
643-
def test_process_events_with_recall_observation_deduplication(conversation_memory):
644-
"""Test that RecallObservations are properly deduplicated based on agent_name."""
645-
# Create a sequence of RecallObservations with overlapping agents
646-
obs1 = RecallObservation(
647-
recall_type=RecallType.KNOWLEDGE_MICROAGENT,
648-
microagent_knowledge=[
649-
{
650-
'agent_name': 'python_agent',
651-
'trigger_word': 'python',
652-
'content': 'Python best practices v1',
653-
},
654-
{
655-
'agent_name': 'git_agent',
656-
'trigger_word': 'git',
657-
'content': 'Git best practices v1',
658-
},
659-
{
660-
'agent_name': 'image_agent',
661-
'trigger_word': 'image',
662-
'content': 'Image best practices v1',
663-
},
664-
],
665-
content='First recall',
666-
)
667-
668-
obs2 = RecallObservation(
669-
recall_type=RecallType.KNOWLEDGE_MICROAGENT,
670-
microagent_knowledge=[
671-
{
672-
'agent_name': 'python_agent',
673-
'trigger_word': 'python',
674-
'content': 'Python best practices v2',
675-
},
676-
],
677-
content='Second recall',
678-
)
679-
680-
obs3 = RecallObservation(
681-
recall_type=RecallType.KNOWLEDGE_MICROAGENT,
682-
microagent_knowledge=[
683-
{
684-
'agent_name': 'git_agent',
685-
'trigger_word': 'git',
686-
'content': 'Git best practices v3',
687-
},
688-
],
689-
content='Third recall',
690-
)
691-
692-
initial_messages = [
693-
Message(role='system', content=[TextContent(text='System message')])
694-
]
695-
696-
messages = conversation_memory.process_events(
697-
condensed_history=[obs1, obs2, obs3],
698-
initial_messages=initial_messages,
699-
max_message_chars=None,
700-
vision_is_active=False,
701-
)
702-
703-
# Verify that only the most recent content for each agent is included
704-
assert len(messages) == 4 # system + 3 recalls
705-
recall_messages = messages[1:] # Skip system message
706-
707-
# First recall should only include image_agent (git_agent and python_agent appears later)
708-
assert 'Image best practices v1' in recall_messages[0].content[0].text
709-
assert 'Git best practices v1' not in recall_messages[0].content[0].text
710-
assert 'Python best practices v1' not in recall_messages[0].content[0].text
711-
712-
# Second recall should include python_agent (it's the most recent for that agent)
713-
assert 'Python best practices v2' in recall_messages[1].content[0].text
714-
715-
# Third recall should include git_agent (it's the most recent for that agent)
716-
assert 'Git best practices v3' in recall_messages[2].content[0].text
717-
718-
719-
def test_process_events_with_recall_observation_deduplication_disabled_agents(
720-
conversation_memory,
721-
):
722-
"""Test that disabled agents are filtered out after deduplication."""
723-
# Create a sequence of RecallObservations with disabled agents
724-
obs1 = RecallObservation(
725-
recall_type=RecallType.KNOWLEDGE_MICROAGENT,
726-
microagent_knowledge=[
727-
{
728-
'agent_name': 'disabled_agent',
729-
'trigger_word': 'disabled',
730-
'content': 'Disabled agent content',
731-
},
732-
{
733-
'agent_name': 'enabled_agent',
734-
'trigger_word': 'enabled',
735-
'content': 'Enabled agent content v1',
736-
},
737-
],
738-
content='First recall',
739-
)
740-
741-
obs2 = RecallObservation(
742-
recall_type=RecallType.KNOWLEDGE_MICROAGENT,
743-
microagent_knowledge=[
744-
{
745-
'agent_name': 'enabled_agent',
746-
'trigger_word': 'enabled',
747-
'content': 'Enabled agent content v2',
748-
},
749-
],
750-
content='Second recall',
751-
)
752-
753-
initial_messages = [
754-
Message(role='system', content=[TextContent(text='System message')])
755-
]
756-
757-
messages = conversation_memory.process_events(
758-
condensed_history=[obs1, obs2],
759-
initial_messages=initial_messages,
760-
max_message_chars=None,
761-
vision_is_active=False,
762-
)
763-
764-
# Verify that disabled agents are filtered out and only the most recent content for enabled agents is included
765-
assert len(messages) == 3 # system + 2 recalls
766-
recall_messages = messages[1:] # Skip system message
767-
768-
# First recall should not include disabled_agent
769-
assert 'Disabled agent content' not in recall_messages[0].content[0].text
770-
assert (
771-
'Enabled agent content v1' not in recall_messages[0].content[0].text
772-
) # Because it appears later
773-
774-
# Second recall should include enabled_agent (it's the most recent)
775-
assert 'Enabled agent content v2' in recall_messages[1].content[0].text
776-
777-
778-
def test_process_events_with_recall_observation_deduplication_empty(
779-
conversation_memory,
780-
):
781-
"""Test that empty RecallObservations are handled correctly."""
782-
obs = RecallObservation(
783-
recall_type=RecallType.KNOWLEDGE_MICROAGENT,
784-
microagent_knowledge=[],
785-
content='Empty recall',
786-
)
787-
788-
initial_messages = [
789-
Message(role='system', content=[TextContent(text='System message')])
790-
]
791-
792-
messages = conversation_memory.process_events(
793-
condensed_history=[obs],
794-
initial_messages=initial_messages,
795-
max_message_chars=None,
796-
vision_is_active=False,
797-
)
798-
799-
# Verify that empty RecallObservations are handled gracefully
800-
assert len(messages) == 2 # system + empty recall
801-
assert messages[1].content[0].text == '' # Empty string for empty recall

0 commit comments

Comments
 (0)