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.
@@ -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)