Skip to content

Commit 87925dd

Browse files
authored
Merge branch 'main' into add-resolver-api-endpoints
2 parents 95884c1 + 2b7932b commit 87925dd

File tree

4 files changed

+158
-13
lines changed

4 files changed

+158
-13
lines changed

.github/workflows/lint-fix.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343
run: pip install pre-commit==3.7.0
4444
- name: Fix python lint issues
4545
run: |
46-
pre-commit run --files openhands/**/* evaluation/**/* tests/**/* --config ./dev_config/python/.pre-commit-config.yaml --all-files
46+
pre-commit run --files openhands/**/* evaluation/**/* tests/**/* --config ./dev_config/python/.pre-commit-config.yaml
4747
4848
# Commit and push changes if any
4949
- name: Check for changes

openhands/resolver/issue_definitions.py

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,21 @@ def _extract_image_urls(self, issue_body: str) -> list[str]:
8383
return re.findall(image_pattern, issue_body)
8484

8585
def _extract_issue_references(self, body: str) -> list[int]:
86-
pattern = r'#(\d+)'
86+
# First, remove code blocks as they may contain false positives
87+
body = re.sub(r'```.*?```', '', body, flags=re.DOTALL)
88+
89+
# Remove inline code
90+
body = re.sub(r'`[^`]*`', '', body)
91+
92+
# Remove URLs that contain hash symbols
93+
body = re.sub(r'https?://[^\s)]*#\d+[^\s)]*', '', body)
94+
95+
# Now extract issue numbers, making sure they're not part of other text
96+
# The pattern matches #number that:
97+
# 1. Is at the start of text or after whitespace/punctuation
98+
# 2. Is followed by whitespace, punctuation, or end of text
99+
# 3. Is not part of a URL
100+
pattern = r'(?:^|[\s\[({]|[^\w#])#(\d+)(?=[\s,.\])}]|$)'
87101
return [int(match) for match in re.findall(pattern, body)]
88102

89103
def _get_issue_comments(
@@ -455,17 +469,20 @@ def __get_context_from_external_issues_references(
455469
)
456470

457471
for issue_number in unique_issue_references:
458-
url = f'https://api.github.com/repos/{self.owner}/{self.repo}/issues/{issue_number}'
459-
headers = {
460-
'Authorization': f'Bearer {self.token}',
461-
'Accept': 'application/vnd.github.v3+json',
462-
}
463-
response = requests.get(url, headers=headers)
464-
response.raise_for_status()
465-
issue_data = response.json()
466-
issue_body = issue_data.get('body', '')
467-
if issue_body:
468-
closing_issues.append(issue_body)
472+
try:
473+
url = f'https://api.github.com/repos/{self.owner}/{self.repo}/issues/{issue_number}'
474+
headers = {
475+
'Authorization': f'Bearer {self.token}',
476+
'Accept': 'application/vnd.github.v3+json',
477+
}
478+
response = requests.get(url, headers=headers)
479+
response.raise_for_status()
480+
issue_data = response.json()
481+
issue_body = issue_data.get('body', '')
482+
if issue_body:
483+
closing_issues.append(issue_body)
484+
except requests.exceptions.RequestException as e:
485+
logger.warning(f'Failed to fetch issue {issue_number}: {str(e)}')
469486

470487
return closing_issues
471488

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import pytest
2+
import requests
3+
from unittest.mock import patch, MagicMock
4+
5+
from openhands.resolver.issue_definitions import PRHandler
6+
from openhands.resolver.github_issue import ReviewThread
7+
8+
9+
def test_handle_nonexistent_issue_reference():
10+
handler = PRHandler("test-owner", "test-repo", "test-token")
11+
12+
# Mock the requests.get to simulate a 404 error
13+
mock_response = MagicMock()
14+
mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError("404 Client Error: Not Found")
15+
16+
with patch('requests.get', return_value=mock_response):
17+
# Call the method with a non-existent issue reference
18+
result = handler._PRHandler__get_context_from_external_issues_references(
19+
closing_issues=[],
20+
closing_issue_numbers=[],
21+
issue_body="This references #999999", # Non-existent issue
22+
review_comments=[],
23+
review_threads=[],
24+
thread_comments=None
25+
)
26+
27+
# The method should return an empty list since the referenced issue couldn't be fetched
28+
assert result == []
29+
30+
31+
def test_handle_rate_limit_error():
32+
handler = PRHandler("test-owner", "test-repo", "test-token")
33+
34+
# Mock the requests.get to simulate a rate limit error
35+
mock_response = MagicMock()
36+
mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError(
37+
"403 Client Error: Rate Limit Exceeded"
38+
)
39+
40+
with patch('requests.get', return_value=mock_response):
41+
# Call the method with an issue reference
42+
result = handler._PRHandler__get_context_from_external_issues_references(
43+
closing_issues=[],
44+
closing_issue_numbers=[],
45+
issue_body="This references #123",
46+
review_comments=[],
47+
review_threads=[],
48+
thread_comments=None
49+
)
50+
51+
# The method should return an empty list since the request was rate limited
52+
assert result == []
53+
54+
55+
def test_handle_network_error():
56+
handler = PRHandler("test-owner", "test-repo", "test-token")
57+
58+
# Mock the requests.get to simulate a network error
59+
with patch('requests.get', side_effect=requests.exceptions.ConnectionError("Network Error")):
60+
# Call the method with an issue reference
61+
result = handler._PRHandler__get_context_from_external_issues_references(
62+
closing_issues=[],
63+
closing_issue_numbers=[],
64+
issue_body="This references #123",
65+
review_comments=[],
66+
review_threads=[],
67+
thread_comments=None
68+
)
69+
70+
# The method should return an empty list since the network request failed
71+
assert result == []
72+
73+
74+
def test_successful_issue_reference():
75+
handler = PRHandler("test-owner", "test-repo", "test-token")
76+
77+
# Mock a successful response
78+
mock_response = MagicMock()
79+
mock_response.raise_for_status.return_value = None
80+
mock_response.json.return_value = {"body": "This is the referenced issue body"}
81+
82+
with patch('requests.get', return_value=mock_response):
83+
# Call the method with an issue reference
84+
result = handler._PRHandler__get_context_from_external_issues_references(
85+
closing_issues=[],
86+
closing_issue_numbers=[],
87+
issue_body="This references #123",
88+
review_comments=[],
89+
review_threads=[],
90+
thread_comments=None
91+
)
92+
93+
# The method should return a list with the referenced issue body
94+
assert result == ["This is the referenced issue body"]
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from openhands.resolver.issue_definitions import IssueHandler
2+
3+
4+
def test_extract_issue_references():
5+
handler = IssueHandler("test-owner", "test-repo", "test-token")
6+
7+
# Test basic issue reference
8+
assert handler._extract_issue_references("Fixes #123") == [123]
9+
10+
# Test multiple issue references
11+
assert handler._extract_issue_references("Fixes #123, #456") == [123, 456]
12+
13+
# Test issue references in code blocks should be ignored
14+
assert handler._extract_issue_references("""
15+
Here's a code block:
16+
```python
17+
# This is a comment with #123
18+
def func():
19+
pass # Another #456
20+
```
21+
But this #789 should be extracted
22+
""") == [789]
23+
24+
# Test issue references in inline code should be ignored
25+
assert handler._extract_issue_references("This `#123` should be ignored but #456 should be extracted") == [456]
26+
27+
# Test issue references in URLs should be ignored
28+
assert handler._extract_issue_references("Check http://example.com/#123 but #456 should be extracted") == [456]
29+
30+
# Test issue references in markdown links should be extracted
31+
assert handler._extract_issue_references("[Link to #123](http://example.com) and #456") == [123, 456]
32+
33+
# Test issue references with text around them
34+
assert handler._extract_issue_references("Issue #123 is fixed and #456 is pending") == [123, 456]

0 commit comments

Comments
 (0)