Spaces:
Build error
Build error
import json | |
from unittest.mock import MagicMock, patch | |
import pytest | |
from openhands.core.config import LLMConfig | |
from openhands.events.action.message import MessageAction | |
from openhands.llm.llm import LLM | |
from openhands.resolver.interfaces.github import GithubPRHandler | |
from openhands.resolver.interfaces.issue import Issue, ReviewThread | |
from openhands.resolver.interfaces.issue_definitions import ServiceContextPR | |
def pr_handler(): | |
llm_config = LLMConfig(model='test-model') | |
handler = ServiceContextPR( | |
GithubPRHandler('test-owner', 'test-repo', 'test-token'), llm_config | |
) | |
return handler | |
def mock_llm_success_response(): | |
return MagicMock( | |
choices=[ | |
MagicMock( | |
message=MagicMock( | |
content="""--- success | |
true | |
--- explanation | |
The changes look good""" | |
) | |
) | |
] | |
) | |
def test_guess_success_review_threads_litellm_call(): | |
"""Test that the completion() call for review threads contains the expected content.""" | |
# Create a PR handler instance | |
llm_config = LLMConfig(model='test', api_key='test') | |
handler = ServiceContextPR( | |
GithubPRHandler('test-owner', 'test-repo', 'test-token'), llm_config | |
) | |
# Create a mock issue with review threads | |
issue = Issue( | |
owner='test-owner', | |
repo='test-repo', | |
number=1, | |
title='Test PR', | |
body='Test Body', | |
thread_comments=None, | |
closing_issues=['Issue 1 description', 'Issue 2 description'], | |
review_comments=None, | |
review_threads=[ | |
ReviewThread( | |
comment='Please fix the formatting\n---\nlatest feedback:\nAdd docstrings', | |
files=['/src/file1.py', '/src/file2.py'], | |
), | |
ReviewThread( | |
comment='Add more tests\n---\nlatest feedback:\nAdd test cases', | |
files=['/tests/test_file.py'], | |
), | |
], | |
thread_ids=['1', '2'], | |
head_branch='test-branch', | |
) | |
# Create mock history with a detailed response | |
history = [ | |
MessageAction( | |
content="""I have made the following changes: | |
1. Fixed formatting in file1.py and file2.py | |
2. Added docstrings to all functions | |
3. Added test cases in test_file.py""" | |
) | |
] | |
# Create mock LLM config | |
llm_config = LLMConfig(model='test-model', api_key='test-key') | |
# Mock the LLM response | |
mock_response = MagicMock() | |
mock_response.choices = [ | |
MagicMock( | |
message=MagicMock( | |
content="""--- success | |
true | |
--- explanation | |
The changes successfully address the feedback.""" | |
) | |
) | |
] | |
# Test the guess_success method | |
with patch.object(LLM, 'completion') as mock_completion: | |
mock_completion.return_value = mock_response | |
success, success_list, explanation = handler.guess_success(issue, history) | |
# Verify the completion() calls | |
assert mock_completion.call_count == 2 # One call per review thread | |
# Check first call | |
first_call = mock_completion.call_args_list[0] | |
first_prompt = first_call[1]['messages'][0]['content'] | |
assert ( | |
'Issue descriptions:\n' | |
+ json.dumps(['Issue 1 description', 'Issue 2 description'], indent=4) | |
in first_prompt | |
) | |
assert ( | |
'Feedback:\nPlease fix the formatting\n---\nlatest feedback:\nAdd docstrings' | |
in first_prompt | |
) | |
assert ( | |
'Files locations:\n' | |
+ json.dumps(['/src/file1.py', '/src/file2.py'], indent=4) | |
in first_prompt | |
) | |
assert 'Last message from AI agent:\n' + history[0].content in first_prompt | |
# Check second call | |
second_call = mock_completion.call_args_list[1] | |
second_prompt = second_call[1]['messages'][0]['content'] | |
assert ( | |
'Issue descriptions:\n' | |
+ json.dumps(['Issue 1 description', 'Issue 2 description'], indent=4) | |
in second_prompt | |
) | |
assert ( | |
'Feedback:\nAdd more tests\n---\nlatest feedback:\nAdd test cases' | |
in second_prompt | |
) | |
assert ( | |
'Files locations:\n' + json.dumps(['/tests/test_file.py'], indent=4) | |
in second_prompt | |
) | |
assert 'Last message from AI agent:\n' + history[0].content in second_prompt | |
assert len(json.loads(explanation)) == 2 | |
def test_guess_success_thread_comments_litellm_call(): | |
"""Test that the completion() call for thread comments contains the expected content.""" | |
# Create a PR handler instance | |
llm_config = LLMConfig(model='test', api_key='test') | |
handler = ServiceContextPR( | |
GithubPRHandler('test-owner', 'test-repo', 'test-token'), llm_config | |
) | |
# Create a mock issue with thread comments | |
issue = Issue( | |
owner='test-owner', | |
repo='test-repo', | |
number=1, | |
title='Test PR', | |
body='Test Body', | |
thread_comments=[ | |
'Please improve error handling', | |
'Add input validation', | |
'latest feedback:\nHandle edge cases', | |
], | |
closing_issues=['Issue 1 description', 'Issue 2 description'], | |
review_comments=None, | |
thread_ids=None, | |
head_branch='test-branch', | |
) | |
# Create mock history with a detailed response | |
history = [ | |
MessageAction( | |
content="""I have made the following changes: | |
1. Added try/catch blocks for error handling | |
2. Added input validation checks | |
3. Added handling for edge cases""" | |
) | |
] | |
# Create mock LLM config | |
llm_config = LLMConfig(model='test-model', api_key='test-key') | |
# Mock the LLM response | |
mock_response = MagicMock() | |
mock_response.choices = [ | |
MagicMock( | |
message=MagicMock( | |
content="""--- success | |
true | |
--- explanation | |
The changes successfully address the feedback.""" | |
) | |
) | |
] | |
# Test the guess_success method | |
with patch.object(LLM, 'completion') as mock_completion: | |
mock_completion.return_value = mock_response | |
success, success_list, explanation = handler.guess_success(issue, history) | |
# Verify the completion() call | |
mock_completion.assert_called_once() | |
call_args = mock_completion.call_args | |
prompt = call_args[1]['messages'][0]['content'] | |
# Check prompt content | |
assert ( | |
'Issue descriptions:\n' | |
+ json.dumps(['Issue 1 description', 'Issue 2 description'], indent=4) | |
in prompt | |
) | |
assert 'PR Thread Comments:\n' + '\n---\n'.join(issue.thread_comments) in prompt | |
assert 'Last message from AI agent:\n' + history[0].content in prompt | |
assert len(json.loads(explanation)) == 1 | |
def test_check_feedback_with_llm(): | |
"""Test the _check_feedback_with_llm helper function.""" | |
# Create a PR handler instance | |
llm_config = LLMConfig(model='test', api_key='test') | |
handler = ServiceContextPR( | |
GithubPRHandler('test-owner', 'test-repo', 'test-token'), llm_config | |
) | |
# Test cases for different LLM responses | |
test_cases = [ | |
{ | |
'response': '--- success\ntrue\n--- explanation\nChanges look good', | |
'expected': (True, 'Changes look good'), | |
}, | |
{ | |
'response': '--- success\nfalse\n--- explanation\nNot all issues fixed', | |
'expected': (False, 'Not all issues fixed'), | |
}, | |
{ | |
'response': 'Invalid response format', | |
'expected': ( | |
False, | |
'Failed to decode answer from LLM response: Invalid response format', | |
), | |
}, | |
{ | |
'response': '--- success\ntrue\n--- explanation\nMultiline\nexplanation\nhere', | |
'expected': (True, 'Multiline\nexplanation\nhere'), | |
}, | |
] | |
for case in test_cases: | |
# Mock the LLM response | |
mock_response = MagicMock() | |
mock_response.choices = [MagicMock(message=MagicMock(content=case['response']))] | |
# Test the function | |
with patch.object(LLM, 'completion', return_value=mock_response): | |
success, explanation = handler._check_feedback_with_llm('test prompt') | |
assert (success, explanation) == case['expected'] | |
def test_check_review_thread_with_git_patch(): | |
"""Test that git patch from complete_runtime is included in the prompt.""" | |
# Create a PR handler instance | |
llm_config = LLMConfig(model='test', api_key='test') | |
handler = ServiceContextPR( | |
GithubPRHandler('test-owner', 'test-repo', 'test-token'), llm_config | |
) | |
# Create test data | |
review_thread = ReviewThread( | |
comment='Please fix the formatting\n---\nlatest feedback:\nAdd docstrings', | |
files=['/src/file1.py', '/src/file2.py'], | |
) | |
issues_context = json.dumps( | |
['Issue 1 description', 'Issue 2 description'], indent=4 | |
) | |
last_message = 'I have fixed the formatting and added docstrings' | |
git_patch = 'diff --git a/src/file1.py b/src/file1.py\n+"""Added docstring."""\n' | |
# Mock the LLM response | |
mock_response = MagicMock() | |
mock_response.choices = [ | |
MagicMock( | |
message=MagicMock( | |
content="""--- success | |
true | |
--- explanation | |
Changes look good""" | |
) | |
) | |
] | |
# Test the function | |
with patch.object(LLM, 'completion') as mock_completion: | |
mock_completion.return_value = mock_response | |
success, explanation = handler._check_review_thread( | |
review_thread, issues_context, last_message, git_patch | |
) | |
# Verify the completion() call | |
mock_completion.assert_called_once() | |
call_args = mock_completion.call_args | |
prompt = call_args[1]['messages'][0]['content'] | |
# Check prompt content | |
assert 'Issue descriptions:\n' + issues_context in prompt | |
assert 'Feedback:\n' + review_thread.comment in prompt | |
assert ( | |
'Files locations:\n' + json.dumps(review_thread.files, indent=4) in prompt | |
) | |
assert 'Last message from AI agent:\n' + last_message in prompt | |
assert 'Changes made (git patch):\n' + git_patch in prompt | |
# Check result | |
assert success is True | |
assert explanation == 'Changes look good' | |
def test_check_review_thread(): | |
"""Test the _check_review_thread helper function.""" | |
# Create a PR handler instance | |
llm_config = LLMConfig(model='test', api_key='test') | |
handler = ServiceContextPR( | |
GithubPRHandler('test-owner', 'test-repo', 'test-token'), llm_config | |
) | |
# Create test data | |
review_thread = ReviewThread( | |
comment='Please fix the formatting\n---\nlatest feedback:\nAdd docstrings', | |
files=['/src/file1.py', '/src/file2.py'], | |
) | |
issues_context = json.dumps( | |
['Issue 1 description', 'Issue 2 description'], indent=4 | |
) | |
last_message = 'I have fixed the formatting and added docstrings' | |
# Mock the LLM response | |
mock_response = MagicMock() | |
mock_response.choices = [ | |
MagicMock( | |
message=MagicMock( | |
content="""--- success | |
true | |
--- explanation | |
Changes look good""" | |
) | |
) | |
] | |
# Test the function | |
with patch.object(LLM, 'completion') as mock_completion: | |
mock_completion.return_value = mock_response | |
success, explanation = handler._check_review_thread( | |
review_thread, issues_context, last_message | |
) | |
# Verify the completion() call | |
mock_completion.assert_called_once() | |
call_args = mock_completion.call_args | |
prompt = call_args[1]['messages'][0]['content'] | |
# Check prompt content | |
assert 'Issue descriptions:\n' + issues_context in prompt | |
assert 'Feedback:\n' + review_thread.comment in prompt | |
assert ( | |
'Files locations:\n' + json.dumps(review_thread.files, indent=4) in prompt | |
) | |
assert 'Last message from AI agent:\n' + last_message in prompt | |
# Check result | |
assert success is True | |
assert explanation == 'Changes look good' | |
def test_check_thread_comments_with_git_patch(): | |
"""Test that git patch from complete_runtime is included in the prompt.""" | |
# Create a PR handler instance | |
llm_config = LLMConfig(model='test', api_key='test') | |
handler = ServiceContextPR( | |
GithubPRHandler('test-owner', 'test-repo', 'test-token'), llm_config | |
) | |
# Create test data | |
thread_comments = [ | |
'Please improve error handling', | |
'Add input validation', | |
'latest feedback:\nHandle edge cases', | |
] | |
issues_context = json.dumps( | |
['Issue 1 description', 'Issue 2 description'], indent=4 | |
) | |
last_message = 'I have added error handling and input validation' | |
git_patch = 'diff --git a/src/file1.py b/src/file1.py\n+try:\n+ validate_input()\n+except ValueError:\n+ handle_error()\n' | |
# Mock the LLM response | |
mock_response = MagicMock() | |
mock_response.choices = [ | |
MagicMock( | |
message=MagicMock( | |
content="""--- success | |
true | |
--- explanation | |
Changes look good""" | |
) | |
) | |
] | |
# Test the function | |
with patch.object(LLM, 'completion') as mock_completion: | |
mock_completion.return_value = mock_response | |
success, explanation = handler._check_thread_comments( | |
thread_comments, issues_context, last_message, git_patch | |
) | |
# Verify the completion() call | |
mock_completion.assert_called_once() | |
call_args = mock_completion.call_args | |
prompt = call_args[1]['messages'][0]['content'] | |
# Check prompt content | |
assert 'Issue descriptions:\n' + issues_context in prompt | |
assert 'PR Thread Comments:\n' + '\n---\n'.join(thread_comments) in prompt | |
assert 'Last message from AI agent:\n' + last_message in prompt | |
assert 'Changes made (git patch):\n' + git_patch in prompt | |
# Check result | |
assert success is True | |
assert explanation == 'Changes look good' | |
def test_check_thread_comments(): | |
"""Test the _check_thread_comments helper function.""" | |
# Create a PR handler instance | |
llm_config = LLMConfig(model='test', api_key='test') | |
handler = ServiceContextPR( | |
GithubPRHandler('test-owner', 'test-repo', 'test-token'), llm_config | |
) | |
# Create test data | |
thread_comments = [ | |
'Please improve error handling', | |
'Add input validation', | |
'latest feedback:\nHandle edge cases', | |
] | |
issues_context = json.dumps( | |
['Issue 1 description', 'Issue 2 description'], indent=4 | |
) | |
last_message = 'I have added error handling and input validation' | |
# Mock the LLM response | |
mock_response = MagicMock() | |
mock_response.choices = [ | |
MagicMock( | |
message=MagicMock( | |
content="""--- success | |
true | |
--- explanation | |
Changes look good""" | |
) | |
) | |
] | |
# Test the function | |
with patch.object(LLM, 'completion') as mock_completion: | |
mock_completion.return_value = mock_response | |
success, explanation = handler._check_thread_comments( | |
thread_comments, issues_context, last_message | |
) | |
# Verify the completion() call | |
mock_completion.assert_called_once() | |
call_args = mock_completion.call_args | |
prompt = call_args[1]['messages'][0]['content'] | |
# Check prompt content | |
assert 'Issue descriptions:\n' + issues_context in prompt | |
assert 'PR Thread Comments:\n' + '\n---\n'.join(thread_comments) in prompt | |
assert 'Last message from AI agent:\n' + last_message in prompt | |
# Check result | |
assert success is True | |
assert explanation == 'Changes look good' | |
def test_check_review_comments_with_git_patch(): | |
"""Test that git patch from complete_runtime is included in the prompt.""" | |
# Create a PR handler instance | |
llm_config = LLMConfig(model='test', api_key='test') | |
handler = ServiceContextPR( | |
GithubPRHandler('test-owner', 'test-repo', 'test-token'), llm_config | |
) | |
# Create test data | |
review_comments = [ | |
'Please fix the code style', | |
'Add more test cases', | |
'latest feedback:\nImprove documentation', | |
] | |
issues_context = json.dumps( | |
['Issue 1 description', 'Issue 2 description'], indent=4 | |
) | |
last_message = 'I have fixed the code style and added tests' | |
git_patch = 'diff --git a/src/file1.py b/src/file1.py\n+"""This module does X."""\n+def func():\n+ """Do Y."""\n' | |
# Mock the LLM response | |
mock_response = MagicMock() | |
mock_response.choices = [ | |
MagicMock( | |
message=MagicMock( | |
content="""--- success | |
true | |
--- explanation | |
Changes look good""" | |
) | |
) | |
] | |
# Test the function | |
with patch.object(LLM, 'completion') as mock_completion: | |
mock_completion.return_value = mock_response | |
success, explanation = handler._check_review_comments( | |
review_comments, issues_context, last_message, git_patch | |
) | |
# Verify the completion() call | |
mock_completion.assert_called_once() | |
call_args = mock_completion.call_args | |
prompt = call_args[1]['messages'][0]['content'] | |
# Check prompt content | |
assert 'Issue descriptions:\n' + issues_context in prompt | |
assert 'PR Review Comments:\n' + '\n---\n'.join(review_comments) in prompt | |
assert 'Last message from AI agent:\n' + last_message in prompt | |
assert 'Changes made (git patch):\n' + git_patch in prompt | |
# Check result | |
assert success is True | |
assert explanation == 'Changes look good' | |
def test_check_review_comments(): | |
"""Test the _check_review_comments helper function.""" | |
# Create a PR handler instance | |
llm_config = LLMConfig(model='test', api_key='test') | |
handler = ServiceContextPR( | |
GithubPRHandler('test-owner', 'test-repo', 'test-token'), llm_config | |
) | |
# Create test data | |
review_comments = [ | |
'Please improve code readability', | |
'Add comments to complex functions', | |
'Follow PEP 8 style guide', | |
] | |
issues_context = json.dumps( | |
['Issue 1 description', 'Issue 2 description'], indent=4 | |
) | |
last_message = 'I have improved code readability and added comments' | |
# Mock the LLM response | |
mock_response = MagicMock() | |
mock_response.choices = [ | |
MagicMock( | |
message=MagicMock( | |
content="""--- success | |
true | |
--- explanation | |
Changes look good""" | |
) | |
) | |
] | |
# Test the function | |
with patch.object(LLM, 'completion') as mock_completion: | |
mock_completion.return_value = mock_response | |
success, explanation = handler._check_review_comments( | |
review_comments, issues_context, last_message | |
) | |
# Verify the completion() call | |
mock_completion.assert_called_once() | |
call_args = mock_completion.call_args | |
prompt = call_args[1]['messages'][0]['content'] | |
# Check prompt content | |
assert 'Issue descriptions:\n' + issues_context in prompt | |
assert 'PR Review Comments:\n' + '\n---\n'.join(review_comments) in prompt | |
assert 'Last message from AI agent:\n' + last_message in prompt | |
# Check result | |
assert success is True | |
assert explanation == 'Changes look good' | |
def test_guess_success_review_comments_litellm_call(): | |
"""Test that the completion() call for review comments contains the expected content.""" | |
# Create a PR handler instance | |
llm_config = LLMConfig(model='test', api_key='test') | |
handler = ServiceContextPR( | |
GithubPRHandler('test-owner', 'test-repo', 'test-token'), llm_config | |
) | |
# Create a mock issue with review comments | |
issue = Issue( | |
owner='test-owner', | |
repo='test-repo', | |
number=1, | |
title='Test PR', | |
body='Test Body', | |
thread_comments=None, | |
closing_issues=['Issue 1 description', 'Issue 2 description'], | |
review_comments=[ | |
'Please improve code readability', | |
'Add comments to complex functions', | |
'Follow PEP 8 style guide', | |
], | |
thread_ids=None, | |
head_branch='test-branch', | |
) | |
# Create mock history with a detailed response | |
history = [ | |
MessageAction( | |
content="""I have made the following changes: | |
1. Improved code readability by breaking down complex functions | |
2. Added detailed comments to all complex functions | |
3. Fixed code style to follow PEP 8""" | |
) | |
] | |
# Mock the LLM response | |
mock_response = MagicMock() | |
mock_response.choices = [ | |
MagicMock( | |
message=MagicMock( | |
content="""--- success | |
true | |
--- explanation | |
The changes successfully address the feedback.""" | |
) | |
) | |
] | |
with patch.object(LLM, 'completion') as mock_completion: | |
mock_completion.return_value = mock_response | |
success, success_list, explanation = handler.guess_success(issue, history) | |
# Verify the completion() call | |
mock_completion.assert_called_once() | |
call_args = mock_completion.call_args | |
prompt = call_args[1]['messages'][0]['content'] | |
# Check prompt content | |
assert ( | |
'Issue descriptions:\n' | |
+ json.dumps(['Issue 1 description', 'Issue 2 description'], indent=4) | |
in prompt | |
) | |
assert 'PR Review Comments:\n' + '\n---\n'.join(issue.review_comments) in prompt | |
assert 'Last message from AI agent:\n' + history[0].content in prompt | |
assert len(json.loads(explanation)) == 1 | |