roma-debug 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- roma_debug/__init__.py +3 -0
- roma_debug/config.py +79 -0
- roma_debug/core/__init__.py +5 -0
- roma_debug/core/engine.py +423 -0
- roma_debug/core/models.py +313 -0
- roma_debug/main.py +753 -0
- roma_debug/parsers/__init__.py +21 -0
- roma_debug/parsers/base.py +189 -0
- roma_debug/parsers/python_ast_parser.py +268 -0
- roma_debug/parsers/registry.py +196 -0
- roma_debug/parsers/traceback_patterns.py +314 -0
- roma_debug/parsers/treesitter_parser.py +598 -0
- roma_debug/prompts.py +153 -0
- roma_debug/server.py +247 -0
- roma_debug/tracing/__init__.py +28 -0
- roma_debug/tracing/call_chain.py +278 -0
- roma_debug/tracing/context_builder.py +672 -0
- roma_debug/tracing/dependency_graph.py +298 -0
- roma_debug/tracing/error_analyzer.py +399 -0
- roma_debug/tracing/import_resolver.py +315 -0
- roma_debug/tracing/project_scanner.py +569 -0
- roma_debug/utils/__init__.py +5 -0
- roma_debug/utils/context.py +422 -0
- roma_debug-0.1.0.dist-info/METADATA +34 -0
- roma_debug-0.1.0.dist-info/RECORD +36 -0
- roma_debug-0.1.0.dist-info/WHEEL +5 -0
- roma_debug-0.1.0.dist-info/entry_points.txt +2 -0
- roma_debug-0.1.0.dist-info/licenses/LICENSE +201 -0
- roma_debug-0.1.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +1 -0
- tests/test_context.py +208 -0
- tests/test_engine.py +296 -0
- tests/test_parsers.py +534 -0
- tests/test_project_scanner.py +275 -0
- tests/test_traceback_patterns.py +222 -0
- tests/test_tracing.py +296 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"""Tests for the project scanner and error analyzer."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import tempfile
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from roma_debug.core.models import Language
|
|
8
|
+
from roma_debug.tracing.project_scanner import ProjectScanner, ProjectInfo, ProjectFile
|
|
9
|
+
from roma_debug.tracing.error_analyzer import ErrorAnalyzer, ErrorAnalysis
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestProjectScanner:
|
|
13
|
+
"""Tests for ProjectScanner."""
|
|
14
|
+
|
|
15
|
+
def test_scan_python_project(self):
|
|
16
|
+
"""Test scanning a Python project."""
|
|
17
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
18
|
+
# Create a simple Python project
|
|
19
|
+
os.makedirs(f"{tmpdir}/src")
|
|
20
|
+
|
|
21
|
+
with open(f"{tmpdir}/app.py", "w") as f:
|
|
22
|
+
f.write("""
|
|
23
|
+
from flask import Flask
|
|
24
|
+
app = Flask(__name__)
|
|
25
|
+
|
|
26
|
+
@app.route('/')
|
|
27
|
+
def index():
|
|
28
|
+
return 'Hello'
|
|
29
|
+
""")
|
|
30
|
+
|
|
31
|
+
with open(f"{tmpdir}/src/utils.py", "w") as f:
|
|
32
|
+
f.write("""
|
|
33
|
+
def helper():
|
|
34
|
+
return 42
|
|
35
|
+
""")
|
|
36
|
+
|
|
37
|
+
with open(f"{tmpdir}/requirements.txt", "w") as f:
|
|
38
|
+
f.write("flask\n")
|
|
39
|
+
|
|
40
|
+
scanner = ProjectScanner(tmpdir)
|
|
41
|
+
info = scanner.scan()
|
|
42
|
+
|
|
43
|
+
assert info.project_type == "flask"
|
|
44
|
+
assert info.primary_language == Language.PYTHON
|
|
45
|
+
assert "flask" in info.frameworks_detected
|
|
46
|
+
assert len(info.entry_points) >= 1
|
|
47
|
+
assert any("app.py" in ep.path for ep in info.entry_points)
|
|
48
|
+
assert len(info.config_files) >= 1
|
|
49
|
+
|
|
50
|
+
def test_scan_javascript_project(self):
|
|
51
|
+
"""Test scanning a JavaScript project."""
|
|
52
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
53
|
+
with open(f"{tmpdir}/index.js", "w") as f:
|
|
54
|
+
f.write("""
|
|
55
|
+
const express = require('express');
|
|
56
|
+
const app = express();
|
|
57
|
+
|
|
58
|
+
app.get('/', (req, res) => {
|
|
59
|
+
res.send('Hello');
|
|
60
|
+
});
|
|
61
|
+
""")
|
|
62
|
+
|
|
63
|
+
with open(f"{tmpdir}/package.json", "w") as f:
|
|
64
|
+
f.write('{"name": "test", "dependencies": {"express": "^4.0.0"}}')
|
|
65
|
+
|
|
66
|
+
scanner = ProjectScanner(tmpdir)
|
|
67
|
+
info = scanner.scan()
|
|
68
|
+
|
|
69
|
+
assert info.project_type == "express"
|
|
70
|
+
assert info.primary_language == Language.JAVASCRIPT
|
|
71
|
+
assert "express" in info.frameworks_detected
|
|
72
|
+
assert len(info.entry_points) >= 1
|
|
73
|
+
|
|
74
|
+
def test_find_relevant_files_http_error(self):
|
|
75
|
+
"""Test finding relevant files from HTTP error."""
|
|
76
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
77
|
+
with open(f"{tmpdir}/app.py", "w") as f:
|
|
78
|
+
f.write("""
|
|
79
|
+
from flask import Flask
|
|
80
|
+
app = Flask(__name__)
|
|
81
|
+
""")
|
|
82
|
+
|
|
83
|
+
with open(f"{tmpdir}/routes.py", "w") as f:
|
|
84
|
+
f.write("""
|
|
85
|
+
from app import app
|
|
86
|
+
|
|
87
|
+
@app.route('/api/users')
|
|
88
|
+
def users():
|
|
89
|
+
return []
|
|
90
|
+
""")
|
|
91
|
+
|
|
92
|
+
scanner = ProjectScanner(tmpdir)
|
|
93
|
+
scanner.scan()
|
|
94
|
+
|
|
95
|
+
relevant = scanner.find_relevant_files("Cannot GET /index.html")
|
|
96
|
+
|
|
97
|
+
# Should find app.py and routes.py as relevant
|
|
98
|
+
paths = [f.path for f in relevant]
|
|
99
|
+
assert any("app" in p for p in paths) or any("route" in p for p in paths)
|
|
100
|
+
|
|
101
|
+
def test_project_summary(self):
|
|
102
|
+
"""Test project summary generation."""
|
|
103
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
104
|
+
with open(f"{tmpdir}/main.py", "w") as f:
|
|
105
|
+
f.write("print('hello')")
|
|
106
|
+
|
|
107
|
+
scanner = ProjectScanner(tmpdir)
|
|
108
|
+
info = scanner.scan()
|
|
109
|
+
|
|
110
|
+
summary = info.to_summary()
|
|
111
|
+
assert "Project Type:" in summary
|
|
112
|
+
assert "Primary Language:" in summary
|
|
113
|
+
|
|
114
|
+
def test_skip_directories(self):
|
|
115
|
+
"""Test that node_modules and similar are skipped."""
|
|
116
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
117
|
+
os.makedirs(f"{tmpdir}/node_modules/lodash")
|
|
118
|
+
os.makedirs(f"{tmpdir}/src")
|
|
119
|
+
|
|
120
|
+
with open(f"{tmpdir}/src/app.js", "w") as f:
|
|
121
|
+
f.write("console.log('app');")
|
|
122
|
+
|
|
123
|
+
with open(f"{tmpdir}/node_modules/lodash/index.js", "w") as f:
|
|
124
|
+
f.write("module.exports = {};")
|
|
125
|
+
|
|
126
|
+
scanner = ProjectScanner(tmpdir)
|
|
127
|
+
info = scanner.scan()
|
|
128
|
+
|
|
129
|
+
# Should not include node_modules files
|
|
130
|
+
paths = [f.path for f in info.source_files]
|
|
131
|
+
assert not any("node_modules" in p for p in paths)
|
|
132
|
+
assert any("app.js" in p for p in paths)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class TestErrorAnalyzer:
|
|
136
|
+
"""Tests for ErrorAnalyzer."""
|
|
137
|
+
|
|
138
|
+
def test_analyze_http_404_error(self):
|
|
139
|
+
"""Test analyzing HTTP 404 error."""
|
|
140
|
+
analyzer = ErrorAnalyzer()
|
|
141
|
+
analysis = analyzer.analyze("Cannot GET /index.html")
|
|
142
|
+
|
|
143
|
+
assert analysis.error_type == "http"
|
|
144
|
+
assert analysis.error_category == "http_404"
|
|
145
|
+
assert "/index.html" in analysis.affected_routes
|
|
146
|
+
|
|
147
|
+
def test_analyze_python_import_error(self):
|
|
148
|
+
"""Test analyzing Python import error."""
|
|
149
|
+
analyzer = ErrorAnalyzer()
|
|
150
|
+
analysis = analyzer.analyze("ModuleNotFoundError: No module named 'flask'")
|
|
151
|
+
|
|
152
|
+
assert analysis.error_type == "import"
|
|
153
|
+
assert analysis.error_category == "python_import"
|
|
154
|
+
assert analysis.suggested_language == Language.PYTHON
|
|
155
|
+
|
|
156
|
+
def test_analyze_javascript_error(self):
|
|
157
|
+
"""Test analyzing JavaScript error."""
|
|
158
|
+
analyzer = ErrorAnalyzer()
|
|
159
|
+
# Use a more distinctly JavaScript error message
|
|
160
|
+
analysis = analyzer.analyze("ReferenceError: myVariable is not defined\n at Object.<anonymous> (/app/index.js:10:5)")
|
|
161
|
+
|
|
162
|
+
assert analysis.error_type == "runtime"
|
|
163
|
+
assert analysis.error_category == "js_reference"
|
|
164
|
+
assert analysis.suggested_language == Language.JAVASCRIPT
|
|
165
|
+
|
|
166
|
+
def test_analyze_config_error(self):
|
|
167
|
+
"""Test analyzing configuration error."""
|
|
168
|
+
analyzer = ErrorAnalyzer()
|
|
169
|
+
analysis = analyzer.analyze("API key not valid. Please check your API key.")
|
|
170
|
+
|
|
171
|
+
assert analysis.error_type == "config"
|
|
172
|
+
assert analysis.error_category == "config"
|
|
173
|
+
|
|
174
|
+
def test_extract_routes(self):
|
|
175
|
+
"""Test route extraction from error."""
|
|
176
|
+
analyzer = ErrorAnalyzer()
|
|
177
|
+
analysis = analyzer.analyze("Cannot GET /api/users/123")
|
|
178
|
+
|
|
179
|
+
assert "/api/users/123" in analysis.affected_routes
|
|
180
|
+
|
|
181
|
+
def test_with_project_scanner(self):
|
|
182
|
+
"""Test error analyzer with project scanner."""
|
|
183
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
184
|
+
with open(f"{tmpdir}/server.py", "w") as f:
|
|
185
|
+
f.write("""
|
|
186
|
+
from flask import Flask, send_from_directory
|
|
187
|
+
app = Flask(__name__, static_folder='static')
|
|
188
|
+
""")
|
|
189
|
+
|
|
190
|
+
scanner = ProjectScanner(tmpdir)
|
|
191
|
+
analyzer = ErrorAnalyzer(scanner)
|
|
192
|
+
|
|
193
|
+
analysis = analyzer.analyze("Cannot GET /index.html")
|
|
194
|
+
|
|
195
|
+
# Should find server.py as relevant
|
|
196
|
+
assert len(analysis.relevant_files) > 0
|
|
197
|
+
|
|
198
|
+
def test_get_fix_context(self):
|
|
199
|
+
"""Test getting comprehensive fix context."""
|
|
200
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
201
|
+
with open(f"{tmpdir}/app.py", "w") as f:
|
|
202
|
+
f.write("""
|
|
203
|
+
from flask import Flask
|
|
204
|
+
app = Flask(__name__)
|
|
205
|
+
""")
|
|
206
|
+
|
|
207
|
+
scanner = ProjectScanner(tmpdir)
|
|
208
|
+
analyzer = ErrorAnalyzer(scanner)
|
|
209
|
+
|
|
210
|
+
context = analyzer.get_fix_context(
|
|
211
|
+
"Cannot GET /index.html",
|
|
212
|
+
include_project_structure=True,
|
|
213
|
+
include_file_contents=True,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
assert "ERROR ANALYSIS" in context
|
|
217
|
+
assert "PROJECT STRUCTURE" in context
|
|
218
|
+
|
|
219
|
+
def test_error_confidence(self):
|
|
220
|
+
"""Test error detection confidence."""
|
|
221
|
+
analyzer = ErrorAnalyzer()
|
|
222
|
+
|
|
223
|
+
# High confidence for specific error
|
|
224
|
+
analysis = analyzer.analyze("ModuleNotFoundError: No module named 'flask'")
|
|
225
|
+
assert analysis.confidence >= 0.9
|
|
226
|
+
|
|
227
|
+
# Lower confidence for vague error
|
|
228
|
+
analysis = analyzer.analyze("Something went wrong")
|
|
229
|
+
assert analysis.confidence < 0.5
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class TestIntegration:
|
|
233
|
+
"""Integration tests for project scanner + error analyzer."""
|
|
234
|
+
|
|
235
|
+
def test_flask_static_file_error(self):
|
|
236
|
+
"""Test analyzing Flask static file error with project context."""
|
|
237
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
238
|
+
# Create a Flask project structure
|
|
239
|
+
os.makedirs(f"{tmpdir}/static")
|
|
240
|
+
os.makedirs(f"{tmpdir}/templates")
|
|
241
|
+
|
|
242
|
+
with open(f"{tmpdir}/app.py", "w") as f:
|
|
243
|
+
f.write("""
|
|
244
|
+
from flask import Flask, render_template, send_from_directory
|
|
245
|
+
import os
|
|
246
|
+
|
|
247
|
+
app = Flask(__name__)
|
|
248
|
+
|
|
249
|
+
@app.route('/')
|
|
250
|
+
def index():
|
|
251
|
+
return render_template('index.html')
|
|
252
|
+
|
|
253
|
+
if __name__ == '__main__':
|
|
254
|
+
app.run(debug=True)
|
|
255
|
+
""")
|
|
256
|
+
|
|
257
|
+
with open(f"{tmpdir}/templates/index.html", "w") as f:
|
|
258
|
+
f.write("<html><body>Hello</body></html>")
|
|
259
|
+
|
|
260
|
+
scanner = ProjectScanner(tmpdir)
|
|
261
|
+
analyzer = ErrorAnalyzer(scanner)
|
|
262
|
+
|
|
263
|
+
# Analyze a static file error
|
|
264
|
+
analysis = analyzer.analyze("Cannot GET /index.html")
|
|
265
|
+
|
|
266
|
+
# Should identify as HTTP error
|
|
267
|
+
assert analysis.error_type == "http"
|
|
268
|
+
|
|
269
|
+
# Should find app.py as relevant
|
|
270
|
+
relevant_paths = [f.path for f in analysis.relevant_files]
|
|
271
|
+
assert any("app" in p for p in relevant_paths)
|
|
272
|
+
|
|
273
|
+
# Context should include file contents
|
|
274
|
+
context = analyzer.get_fix_context("Cannot GET /index.html")
|
|
275
|
+
assert "flask" in context.lower() or "Flask" in context
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""Tests for multi-language traceback pattern matching."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from roma_debug.core.models import Language
|
|
5
|
+
from roma_debug.parsers.traceback_patterns import (
|
|
6
|
+
detect_traceback_language,
|
|
7
|
+
parse_traceback,
|
|
8
|
+
extract_frames,
|
|
9
|
+
extract_file_line_pairs,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestLanguageDetection:
|
|
14
|
+
"""Tests for traceback language detection."""
|
|
15
|
+
|
|
16
|
+
def test_detect_python_traceback(self):
|
|
17
|
+
traceback = '''
|
|
18
|
+
Traceback (most recent call last):
|
|
19
|
+
File "/app/main.py", line 10, in main
|
|
20
|
+
result = process()
|
|
21
|
+
File "/app/utils.py", line 25, in process
|
|
22
|
+
return compute(data)
|
|
23
|
+
ValueError: invalid literal for int()
|
|
24
|
+
'''
|
|
25
|
+
assert detect_traceback_language(traceback) == Language.PYTHON
|
|
26
|
+
|
|
27
|
+
def test_detect_javascript_stacktrace(self):
|
|
28
|
+
traceback = '''
|
|
29
|
+
Error: Cannot read property 'x' of undefined
|
|
30
|
+
at processData (/app/src/utils.js:15:23)
|
|
31
|
+
at main (/app/src/index.js:42:10)
|
|
32
|
+
at Object.<anonymous> (/app/src/index.js:50:1)
|
|
33
|
+
'''
|
|
34
|
+
assert detect_traceback_language(traceback) == Language.JAVASCRIPT
|
|
35
|
+
|
|
36
|
+
def test_detect_go_panic(self):
|
|
37
|
+
traceback = '''
|
|
38
|
+
panic: runtime error: invalid memory address or nil pointer dereference
|
|
39
|
+
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x49c2ef]
|
|
40
|
+
|
|
41
|
+
goroutine 1 [running]:
|
|
42
|
+
main.processData(0x0, 0x0)
|
|
43
|
+
/app/main.go:25 +0x1f
|
|
44
|
+
main.main()
|
|
45
|
+
/app/main.go:15 +0x3a
|
|
46
|
+
'''
|
|
47
|
+
assert detect_traceback_language(traceback) == Language.GO
|
|
48
|
+
|
|
49
|
+
def test_detect_rust_panic(self):
|
|
50
|
+
traceback = '''
|
|
51
|
+
thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src/main.rs:15:10
|
|
52
|
+
stack backtrace:
|
|
53
|
+
0: std::panicking::begin_panic
|
|
54
|
+
1: core::option::Option<T>::unwrap
|
|
55
|
+
at /rustc/xxx/library/core/src/option.rs:777:21
|
|
56
|
+
2: myapp::process_data
|
|
57
|
+
at ./src/main.rs:15:10
|
|
58
|
+
'''
|
|
59
|
+
assert detect_traceback_language(traceback) == Language.RUST
|
|
60
|
+
|
|
61
|
+
def test_detect_java_exception(self):
|
|
62
|
+
traceback = '''
|
|
63
|
+
Exception in thread "main" java.lang.NullPointerException
|
|
64
|
+
at com.example.MyClass.processData(MyClass.java:25)
|
|
65
|
+
at com.example.Main.main(Main.java:15)
|
|
66
|
+
Caused by: java.lang.IllegalArgumentException: Invalid input
|
|
67
|
+
at com.example.Utils.validate(Utils.java:42)
|
|
68
|
+
'''
|
|
69
|
+
assert detect_traceback_language(traceback) == Language.JAVA
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class TestPythonTraceback:
|
|
73
|
+
"""Tests for Python traceback parsing."""
|
|
74
|
+
|
|
75
|
+
def test_simple_traceback(self):
|
|
76
|
+
traceback = '''
|
|
77
|
+
Traceback (most recent call last):
|
|
78
|
+
File "/app/main.py", line 10, in main
|
|
79
|
+
result = process()
|
|
80
|
+
ValueError: invalid value
|
|
81
|
+
'''
|
|
82
|
+
result = parse_traceback(traceback)
|
|
83
|
+
|
|
84
|
+
assert result.language == Language.PYTHON
|
|
85
|
+
assert len(result.frames) >= 1
|
|
86
|
+
assert result.frames[0].filepath == "/app/main.py"
|
|
87
|
+
assert result.frames[0].line_number == 10
|
|
88
|
+
assert result.frames[0].function_name == "main"
|
|
89
|
+
|
|
90
|
+
def test_multi_file_traceback(self):
|
|
91
|
+
traceback = '''
|
|
92
|
+
Traceback (most recent call last):
|
|
93
|
+
File "/app/main.py", line 10, in main
|
|
94
|
+
result = process()
|
|
95
|
+
File "/app/utils.py", line 25, in process
|
|
96
|
+
return compute(data)
|
|
97
|
+
File "/app/compute.py", line 5, in compute
|
|
98
|
+
return int(value)
|
|
99
|
+
ValueError: invalid literal
|
|
100
|
+
'''
|
|
101
|
+
result = parse_traceback(traceback)
|
|
102
|
+
|
|
103
|
+
assert len(result.frames) == 3
|
|
104
|
+
assert result.frames[0].filepath == "/app/main.py"
|
|
105
|
+
assert result.frames[1].filepath == "/app/utils.py"
|
|
106
|
+
assert result.frames[2].filepath == "/app/compute.py"
|
|
107
|
+
|
|
108
|
+
def test_error_extraction(self):
|
|
109
|
+
traceback = '''
|
|
110
|
+
Traceback (most recent call last):
|
|
111
|
+
File "/app/main.py", line 10, in main
|
|
112
|
+
result = int("abc")
|
|
113
|
+
ValueError: invalid literal for int() with base 10: 'abc'
|
|
114
|
+
'''
|
|
115
|
+
result = parse_traceback(traceback)
|
|
116
|
+
|
|
117
|
+
assert result.error_type == "ValueError"
|
|
118
|
+
assert "invalid literal" in result.error_message
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class TestJavaScriptStackTrace:
|
|
122
|
+
"""Tests for JavaScript/Node.js stack trace parsing."""
|
|
123
|
+
|
|
124
|
+
def test_node_stacktrace(self):
|
|
125
|
+
traceback = '''
|
|
126
|
+
TypeError: Cannot read property 'name' of undefined
|
|
127
|
+
at processUser (/app/src/users.js:25:15)
|
|
128
|
+
at main (/app/src/index.js:10:5)
|
|
129
|
+
'''
|
|
130
|
+
result = parse_traceback(traceback, Language.JAVASCRIPT)
|
|
131
|
+
|
|
132
|
+
assert result.language == Language.JAVASCRIPT
|
|
133
|
+
assert len(result.frames) >= 1
|
|
134
|
+
assert "/app/src/users.js" in result.frames[0].filepath
|
|
135
|
+
assert result.frames[0].line_number == 25
|
|
136
|
+
|
|
137
|
+
def test_anonymous_function(self):
|
|
138
|
+
traceback = '''
|
|
139
|
+
Error: Something went wrong
|
|
140
|
+
at /app/src/index.js:42:10
|
|
141
|
+
at Object.<anonymous> (/app/src/index.js:50:1)
|
|
142
|
+
'''
|
|
143
|
+
result = parse_traceback(traceback, Language.JAVASCRIPT)
|
|
144
|
+
|
|
145
|
+
assert len(result.frames) >= 1
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class TestGoTraceback:
|
|
149
|
+
"""Tests for Go panic/stacktrace parsing."""
|
|
150
|
+
|
|
151
|
+
def test_go_panic(self):
|
|
152
|
+
traceback = '''
|
|
153
|
+
panic: runtime error: index out of range
|
|
154
|
+
|
|
155
|
+
goroutine 1 [running]:
|
|
156
|
+
main.processData()
|
|
157
|
+
/app/main.go:25 +0x1f
|
|
158
|
+
main.main()
|
|
159
|
+
/app/main.go:15 +0x3a
|
|
160
|
+
'''
|
|
161
|
+
result = parse_traceback(traceback, Language.GO)
|
|
162
|
+
|
|
163
|
+
assert result.language == Language.GO
|
|
164
|
+
assert len(result.frames) >= 1
|
|
165
|
+
# Should find main.go references
|
|
166
|
+
assert any("main.go" in f.filepath for f in result.frames)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class TestRustTraceback:
|
|
170
|
+
"""Tests for Rust panic parsing."""
|
|
171
|
+
|
|
172
|
+
def test_rust_panic(self):
|
|
173
|
+
traceback = '''
|
|
174
|
+
thread 'main' panicked at 'index out of bounds', src/main.rs:15:10
|
|
175
|
+
'''
|
|
176
|
+
result = parse_traceback(traceback, Language.RUST)
|
|
177
|
+
|
|
178
|
+
assert result.language == Language.RUST
|
|
179
|
+
assert len(result.frames) >= 1
|
|
180
|
+
assert "main.rs" in result.frames[0].filepath
|
|
181
|
+
assert result.frames[0].line_number == 15
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class TestJavaTraceback:
|
|
185
|
+
"""Tests for Java exception parsing."""
|
|
186
|
+
|
|
187
|
+
def test_java_exception(self):
|
|
188
|
+
traceback = '''
|
|
189
|
+
java.lang.NullPointerException
|
|
190
|
+
at com.example.MyClass.process(MyClass.java:25)
|
|
191
|
+
at com.example.Main.main(Main.java:10)
|
|
192
|
+
'''
|
|
193
|
+
result = parse_traceback(traceback, Language.JAVA)
|
|
194
|
+
|
|
195
|
+
assert result.language == Language.JAVA
|
|
196
|
+
assert len(result.frames) >= 1
|
|
197
|
+
assert "MyClass.java" in result.frames[0].filepath
|
|
198
|
+
assert result.frames[0].line_number == 25
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class TestExtractFilePairs:
|
|
202
|
+
"""Tests for simple file/line extraction."""
|
|
203
|
+
|
|
204
|
+
def test_extract_python_pairs(self):
|
|
205
|
+
traceback = '''
|
|
206
|
+
File "/app/main.py", line 10
|
|
207
|
+
File "/app/utils.py", line 25
|
|
208
|
+
'''
|
|
209
|
+
pairs = extract_file_line_pairs(traceback)
|
|
210
|
+
|
|
211
|
+
assert len(pairs) == 2
|
|
212
|
+
assert ("/app/main.py", 10) in pairs
|
|
213
|
+
assert ("/app/utils.py", 25) in pairs
|
|
214
|
+
|
|
215
|
+
def test_extract_with_language_hint(self):
|
|
216
|
+
traceback = '''
|
|
217
|
+
at com.example.Main.main(Main.java:10)
|
|
218
|
+
'''
|
|
219
|
+
pairs = extract_file_line_pairs(traceback, Language.JAVA)
|
|
220
|
+
|
|
221
|
+
assert len(pairs) >= 1
|
|
222
|
+
assert any(p[0].endswith("Main.java") for p in pairs)
|