Spaces:
Build error
Build error
| from unittest.mock import AsyncMock, MagicMock, Mock, patch | |
| import pytest | |
| from openhands.cli.tui import ( | |
| CustomDiffLexer, | |
| UsageMetrics, | |
| UserCancelledError, | |
| display_banner, | |
| display_command, | |
| display_event, | |
| display_message, | |
| display_runtime_initialization_message, | |
| display_shutdown_message, | |
| display_status, | |
| display_usage_metrics, | |
| display_welcome_message, | |
| get_session_duration, | |
| read_confirmation_input, | |
| ) | |
| from openhands.core.config import OpenHandsConfig | |
| from openhands.events import EventSource | |
| from openhands.events.action import ( | |
| Action, | |
| ActionConfirmationStatus, | |
| CmdRunAction, | |
| MessageAction, | |
| ) | |
| from openhands.events.observation import ( | |
| CmdOutputObservation, | |
| FileEditObservation, | |
| FileReadObservation, | |
| ) | |
| from openhands.llm.metrics import Metrics | |
| class TestDisplayFunctions: | |
| def test_display_runtime_initialization_message_local(self, mock_print): | |
| display_runtime_initialization_message('local') | |
| assert mock_print.call_count == 3 | |
| # Check the second call has the local runtime message | |
| args, kwargs = mock_print.call_args_list[1] | |
| assert 'Starting local runtime' in str(args[0]) | |
| def test_display_runtime_initialization_message_docker(self, mock_print): | |
| display_runtime_initialization_message('docker') | |
| assert mock_print.call_count == 3 | |
| # Check the second call has the docker runtime message | |
| args, kwargs = mock_print.call_args_list[1] | |
| assert 'Starting Docker runtime' in str(args[0]) | |
| def test_display_banner(self, mock_print): | |
| session_id = 'test-session-id' | |
| display_banner(session_id) | |
| # Verify banner calls | |
| assert mock_print.call_count >= 3 | |
| # Check the last call has the session ID | |
| args, kwargs = mock_print.call_args_list[-2] | |
| assert session_id in str(args[0]) | |
| assert 'Initialized conversation' in str(args[0]) | |
| def test_display_welcome_message(self, mock_print): | |
| display_welcome_message() | |
| assert mock_print.call_count == 2 | |
| # Check the first call contains the welcome message | |
| args, kwargs = mock_print.call_args_list[0] | |
| assert "Let's start building" in str(args[0]) | |
| def test_display_event_message_action(self, mock_display_message): | |
| config = MagicMock(spec=OpenHandsConfig) | |
| message = MessageAction(content='Test message') | |
| message._source = EventSource.AGENT | |
| display_event(message, config) | |
| mock_display_message.assert_called_once_with('Test message') | |
| def test_display_event_cmd_action(self, mock_display_command): | |
| config = MagicMock(spec=OpenHandsConfig) | |
| cmd_action = CmdRunAction(command='echo test') | |
| display_event(cmd_action, config) | |
| mock_display_command.assert_called_once_with(cmd_action) | |
| def test_display_event_cmd_output(self, mock_display_output): | |
| config = MagicMock(spec=OpenHandsConfig) | |
| cmd_output = CmdOutputObservation(content='Test output', command='echo test') | |
| display_event(cmd_output, config) | |
| mock_display_output.assert_called_once_with('Test output') | |
| def test_display_event_file_edit_observation(self, mock_display_file_edit): | |
| config = MagicMock(spec=OpenHandsConfig) | |
| file_edit_obs = FileEditObservation(path='test.py', content="print('hello')") | |
| display_event(file_edit_obs, config) | |
| mock_display_file_edit.assert_called_once_with(file_edit_obs) | |
| def test_display_event_file_read(self, mock_display_file_read): | |
| config = MagicMock(spec=OpenHandsConfig) | |
| file_read = FileReadObservation(path='test.py', content="print('hello')") | |
| display_event(file_read, config) | |
| mock_display_file_read.assert_called_once_with(file_read) | |
| def test_display_event_thought(self, mock_display_message): | |
| config = MagicMock(spec=OpenHandsConfig) | |
| action = Action() | |
| action.thought = 'Thinking about this...' | |
| display_event(action, config) | |
| mock_display_message.assert_called_once_with('Thinking about this...') | |
| def test_display_message(self, mock_print): | |
| message = 'Test message' | |
| display_message(message) | |
| mock_print.assert_called_once() | |
| args, kwargs = mock_print.call_args | |
| assert message in str(args[0]) | |
| def test_display_command_awaiting_confirmation(self, mock_print_container): | |
| cmd_action = CmdRunAction(command='echo test') | |
| cmd_action.confirmation_state = ActionConfirmationStatus.AWAITING_CONFIRMATION | |
| display_command(cmd_action) | |
| mock_print_container.assert_called_once() | |
| container = mock_print_container.call_args[0][0] | |
| assert 'echo test' in container.body.text | |
| class TestInteractiveCommandFunctions: | |
| def test_display_usage_metrics(self, mock_print_container): | |
| metrics = UsageMetrics() | |
| metrics.total_cost = 1.25 | |
| metrics.total_input_tokens = 1000 | |
| metrics.total_output_tokens = 2000 | |
| display_usage_metrics(metrics) | |
| mock_print_container.assert_called_once() | |
| def test_get_session_duration(self): | |
| import time | |
| current_time = time.time() | |
| one_hour_ago = current_time - 3600 | |
| # Test for a 1-hour session | |
| duration = get_session_duration(one_hour_ago) | |
| assert '1h' in duration | |
| assert '0m' in duration | |
| assert '0s' in duration | |
| def test_display_shutdown_message(self, mock_get_duration, mock_print): | |
| mock_get_duration.return_value = '1 hour 5 minutes' | |
| metrics = UsageMetrics() | |
| metrics.total_cost = 1.25 | |
| session_id = 'test-session-id' | |
| display_shutdown_message(metrics, session_id) | |
| assert mock_print.call_count >= 3 # At least 3 print calls | |
| assert mock_get_duration.call_count == 1 | |
| def test_display_status(self, mock_display_metrics): | |
| metrics = UsageMetrics() | |
| session_id = 'test-session-id' | |
| display_status(metrics, session_id) | |
| mock_display_metrics.assert_called_once_with(metrics) | |
| class TestCustomDiffLexer: | |
| def test_custom_diff_lexer_plus_line(self): | |
| lexer = CustomDiffLexer() | |
| document = Mock() | |
| document.lines = ['+added line'] | |
| line_style = lexer.lex_document(document)(0) | |
| assert line_style[0][0] == 'ansigreen' # Green for added lines | |
| assert line_style[0][1] == '+added line' | |
| def test_custom_diff_lexer_minus_line(self): | |
| lexer = CustomDiffLexer() | |
| document = Mock() | |
| document.lines = ['-removed line'] | |
| line_style = lexer.lex_document(document)(0) | |
| assert line_style[0][0] == 'ansired' # Red for removed lines | |
| assert line_style[0][1] == '-removed line' | |
| def test_custom_diff_lexer_metadata_line(self): | |
| lexer = CustomDiffLexer() | |
| document = Mock() | |
| document.lines = ['[Existing file]'] | |
| line_style = lexer.lex_document(document)(0) | |
| assert line_style[0][0] == 'bold' # Bold for metadata lines | |
| assert line_style[0][1] == '[Existing file]' | |
| def test_custom_diff_lexer_normal_line(self): | |
| lexer = CustomDiffLexer() | |
| document = Mock() | |
| document.lines = ['normal line'] | |
| line_style = lexer.lex_document(document)(0) | |
| assert line_style[0][0] == '' # Default style for other lines | |
| assert line_style[0][1] == 'normal line' | |
| class TestUsageMetrics: | |
| def test_usage_metrics_initialization(self): | |
| metrics = UsageMetrics() | |
| # Only test the attributes that are actually initialized | |
| assert isinstance(metrics.metrics, Metrics) | |
| assert metrics.session_init_time > 0 # Should have a valid timestamp | |
| class TestUserCancelledError: | |
| def test_user_cancelled_error(self): | |
| error = UserCancelledError() | |
| assert isinstance(error, Exception) | |
| class TestReadConfirmationInput: | |
| async def test_read_confirmation_input_yes(self, mock_create_session): | |
| mock_session = AsyncMock() | |
| mock_session.prompt_async.return_value = 'y' | |
| mock_create_session.return_value = mock_session | |
| result = await read_confirmation_input() | |
| assert result == 'yes' | |
| async def test_read_confirmation_input_yes_full(self, mock_create_session): | |
| mock_session = AsyncMock() | |
| mock_session.prompt_async.return_value = 'yes' | |
| mock_create_session.return_value = mock_session | |
| result = await read_confirmation_input() | |
| assert result == 'yes' | |
| async def test_read_confirmation_input_no(self, mock_create_session): | |
| mock_session = AsyncMock() | |
| mock_session.prompt_async.return_value = 'n' | |
| mock_create_session.return_value = mock_session | |
| result = await read_confirmation_input() | |
| assert result == 'no' | |
| async def test_read_confirmation_input_no_full(self, mock_create_session): | |
| mock_session = AsyncMock() | |
| mock_session.prompt_async.return_value = 'no' | |
| mock_create_session.return_value = mock_session | |
| result = await read_confirmation_input() | |
| assert result == 'no' | |
| async def test_read_confirmation_input_always(self, mock_create_session): | |
| mock_session = AsyncMock() | |
| mock_session.prompt_async.return_value = 'a' | |
| mock_create_session.return_value = mock_session | |
| result = await read_confirmation_input() | |
| assert result == 'always' | |
| async def test_read_confirmation_input_always_full(self, mock_create_session): | |
| mock_session = AsyncMock() | |
| mock_session.prompt_async.return_value = 'always' | |
| mock_create_session.return_value = mock_session | |
| result = await read_confirmation_input() | |
| assert result == 'always' | |
| async def test_read_confirmation_input_invalid(self, mock_create_session): | |
| mock_session = AsyncMock() | |
| mock_session.prompt_async.return_value = 'invalid' | |
| mock_create_session.return_value = mock_session | |
| result = await read_confirmation_input() | |
| assert result == 'no' | |
| async def test_read_confirmation_input_empty(self, mock_create_session): | |
| mock_session = AsyncMock() | |
| mock_session.prompt_async.return_value = '' | |
| mock_create_session.return_value = mock_session | |
| result = await read_confirmation_input() | |
| assert result == 'no' | |
| async def test_read_confirmation_input_none(self, mock_create_session): | |
| mock_session = AsyncMock() | |
| mock_session.prompt_async.return_value = None | |
| mock_create_session.return_value = mock_session | |
| result = await read_confirmation_input() | |
| assert result == 'no' | |
| async def test_read_confirmation_input_keyboard_interrupt( | |
| self, mock_create_session | |
| ): | |
| mock_session = AsyncMock() | |
| mock_session.prompt_async.side_effect = KeyboardInterrupt | |
| mock_create_session.return_value = mock_session | |
| result = await read_confirmation_input() | |
| assert result == 'no' | |
| async def test_read_confirmation_input_eof_error(self, mock_create_session): | |
| mock_session = AsyncMock() | |
| mock_session.prompt_async.side_effect = EOFError | |
| mock_create_session.return_value = mock_session | |
| result = await read_confirmation_input() | |
| assert result == 'no' | |