skylos 1.1.12__py3-none-any.whl → 1.2.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.

Potentially problematic release.


This version of skylos might be problematic. Click here for more details.

@@ -0,0 +1,372 @@
1
+ #!/usr/bin/env python3
2
+ import pytest
3
+ import ast
4
+ from pathlib import Path
5
+ from unittest.mock import Mock, patch
6
+ import fnmatch
7
+
8
+ try:
9
+ from skylos.framework_aware import (
10
+ FrameworkAwareVisitor,
11
+ detect_framework_usage,
12
+ FRAMEWORK_DECORATORS,
13
+ FRAMEWORK_FUNCTIONS,
14
+ FRAMEWORK_IMPORTS
15
+ )
16
+ except ImportError:
17
+ import sys
18
+ from pathlib import Path
19
+ sys.path.insert(0, str(Path(__file__).parent.parent))
20
+ from skylos.framework_aware import (
21
+ FrameworkAwareVisitor,
22
+ detect_framework_usage,
23
+ FRAMEWORK_DECORATORS,
24
+ FRAMEWORK_FUNCTIONS,
25
+ FRAMEWORK_IMPORTS
26
+ )
27
+
28
+ class TestFrameworkAwareVisitor:
29
+
30
+ def test_init_default(self):
31
+ """Test default initialization"""
32
+ visitor = FrameworkAwareVisitor()
33
+ assert visitor.is_framework_file == False
34
+ assert visitor.framework_decorated_lines == set()
35
+ assert visitor.detected_frameworks == set()
36
+
37
+ def test_flask_import_detection(self):
38
+ """Test Flask import detection"""
39
+ code = """
40
+ import flask
41
+ from flask import Flask, request
42
+ """
43
+ tree = ast.parse(code)
44
+ visitor = FrameworkAwareVisitor()
45
+ visitor.visit(tree)
46
+
47
+ assert visitor.is_framework_file == True
48
+ assert 'flask' in visitor.detected_frameworks
49
+
50
+ def test_fastapi_import_detection(self):
51
+ """Test FastAPI import detection"""
52
+ code = """
53
+ from fastapi import FastAPI
54
+ import fastapi
55
+ """
56
+ tree = ast.parse(code)
57
+ visitor = FrameworkAwareVisitor()
58
+ visitor.visit(tree)
59
+
60
+ assert visitor.is_framework_file == True
61
+ assert 'fastapi' in visitor.detected_frameworks
62
+
63
+ def test_django_import_detection(self):
64
+ """Test Django import detection"""
65
+ code = """
66
+ from django.http import HttpResponse
67
+ from django.views import View
68
+ """
69
+ tree = ast.parse(code)
70
+ visitor = FrameworkAwareVisitor()
71
+ visitor.visit(tree)
72
+
73
+ assert visitor.is_framework_file == True
74
+ assert 'django' in visitor.detected_frameworks
75
+
76
+ def test_flask_route_decorator_detection(self):
77
+ """Test Flask route decorator detection"""
78
+ code = """
79
+ @app.route('/api/users')
80
+ def get_users():
81
+ return []
82
+
83
+ @app.post('/api/users')
84
+ def create_user():
85
+ return {}
86
+ """
87
+ tree = ast.parse(code)
88
+ visitor = FrameworkAwareVisitor()
89
+ visitor.visit(tree)
90
+
91
+ assert visitor.is_framework_file == True
92
+ assert 3 in visitor.framework_decorated_lines # get_users function
93
+ assert 7 in visitor.framework_decorated_lines # create_user function
94
+
95
+ def test_fastapi_router_decorator_detection(self):
96
+ """Test FastAPI router decorator detection"""
97
+ code = """
98
+ @router.get('/items')
99
+ async def read_items():
100
+ return []
101
+
102
+ @router.post('/items')
103
+ async def create_item():
104
+ return {}
105
+ """
106
+ tree = ast.parse(code)
107
+ visitor = FrameworkAwareVisitor()
108
+ visitor.visit(tree)
109
+
110
+ assert visitor.is_framework_file == True
111
+ assert 3 in visitor.framework_decorated_lines
112
+ assert 7 in visitor.framework_decorated_lines
113
+
114
+ def test_django_decorator_detection(self):
115
+ """Test Django decorator detection"""
116
+ code = """
117
+ @login_required
118
+ def protected_view(request):
119
+ return HttpResponse("Protected")
120
+
121
+ @permission_required('auth.add_user')
122
+ def admin_view(request):
123
+ return HttpResponse("Admin")
124
+ """
125
+ tree = ast.parse(code)
126
+ visitor = FrameworkAwareVisitor()
127
+ visitor.visit(tree)
128
+
129
+ assert visitor.is_framework_file == True
130
+ assert 3 in visitor.framework_decorated_lines
131
+ assert 7 in visitor.framework_decorated_lines
132
+
133
+ def test_celery_task_decorator_detection(self):
134
+ """Test Celery task decorator detection"""
135
+ code = """
136
+ @task
137
+ def background_task():
138
+ return "done"
139
+
140
+ @shared_task
141
+ def shared_background_task():
142
+ return "shared done"
143
+ """
144
+ tree = ast.parse(code)
145
+ visitor = FrameworkAwareVisitor()
146
+ visitor.visit(tree)
147
+
148
+ assert visitor.is_framework_file == True
149
+ assert 3 in visitor.framework_decorated_lines
150
+ assert 7 in visitor.framework_decorated_lines
151
+
152
+ def test_django_view_class_detection(self):
153
+ """Test Django view class detection"""
154
+ code = """
155
+ from django import views
156
+
157
+ class UserView(View):
158
+ def get(self, request):
159
+ return HttpResponse("GET")
160
+
161
+ class UserViewSet(ViewSet):
162
+ def list(self, request):
163
+ return Response([])
164
+ """
165
+ tree = ast.parse(code)
166
+ visitor = FrameworkAwareVisitor()
167
+ visitor.visit(tree)
168
+
169
+ assert visitor.is_framework_file == True
170
+ assert 4 in visitor.framework_decorated_lines
171
+ assert 8 in visitor.framework_decorated_lines
172
+
173
+ def test_django_model_methods_in_framework_file(self):
174
+ code = """
175
+ from django.db import models
176
+
177
+ class User(models.Model):
178
+ name = models.CharField(max_length=100)
179
+
180
+ def save(self, *args, **kwargs):
181
+ # custom save logic
182
+ super().save(*args, **kwargs)
183
+
184
+ def delete(self, *args, **kwargs):
185
+ # custom delete logic
186
+ super().delete(*args, **kwargs)
187
+ """
188
+ tree = ast.parse(code)
189
+ visitor = FrameworkAwareVisitor()
190
+ visitor.visit(tree)
191
+
192
+ assert visitor.is_framework_file == True
193
+ assert 7 in visitor.framework_decorated_lines # save method
194
+ assert 11 in visitor.framework_decorated_lines # delete method
195
+
196
+ def test_framework_functions_not_detected_in_non_framework_file(self):
197
+ code = """
198
+ def save(self):
199
+ # regular save method, not Django
200
+ pass
201
+
202
+ def get(self):
203
+ # regular get method, not Django view
204
+ pass
205
+ """
206
+ tree = ast.parse(code)
207
+ visitor = FrameworkAwareVisitor()
208
+ visitor.visit(tree)
209
+
210
+ assert visitor.is_framework_file == False
211
+ assert visitor.framework_decorated_lines == set()
212
+
213
+ def test_multiple_decorators(self):
214
+ """Test function with multiple decorators"""
215
+ code = """
216
+ @app.route('/users')
217
+ @login_required
218
+ @cache.cached(timeout=60)
219
+ def get_users():
220
+ return []
221
+ """
222
+ tree = ast.parse(code)
223
+ visitor = FrameworkAwareVisitor()
224
+ visitor.visit(tree)
225
+
226
+ assert visitor.is_framework_file == True
227
+ assert 5 in visitor.framework_decorated_lines
228
+
229
+ def test_complex_decorator_patterns(self):
230
+ """Test complex decorator patterns"""
231
+ code = """
232
+ @app.route('/api/v1/users/<int:user_id>', methods=['GET', 'POST'])
233
+ def user_endpoint(user_id):
234
+ return {}
235
+
236
+ @router.get('/items/{item_id}')
237
+ async def get_item(item_id: int):
238
+ return {}
239
+ """
240
+ tree = ast.parse(code)
241
+ visitor = FrameworkAwareVisitor()
242
+ visitor.visit(tree)
243
+
244
+ assert visitor.is_framework_file == True
245
+ assert 3 in visitor.framework_decorated_lines
246
+ assert 7 in visitor.framework_decorated_lines
247
+
248
+ @patch('skylos.framework_aware.Path')
249
+ def test_file_content_framework_detection(self, mock_path):
250
+ """Test framework detection from file content"""
251
+ mock_file = Mock()
252
+ mock_file.read_text.return_value = "from flask import Flask\napp = Flask(__name__)"
253
+ mock_path.return_value = mock_file
254
+
255
+ visitor = FrameworkAwareVisitor(filename="test.py")
256
+
257
+ assert visitor.is_framework_file == True
258
+ assert 'flask' in visitor.detected_frameworks
259
+
260
+ def test_normalize_decorator_name(self):
261
+ visitor = FrameworkAwareVisitor()
262
+
263
+ node = ast.parse("@decorator\ndef func(): pass").body[0].decorator_list[0]
264
+ result = visitor._normalize_decorator(node)
265
+ assert result == "@decorator"
266
+
267
+ node = ast.parse("@app.route\ndef func(): pass").body[0].decorator_list[0]
268
+ result = visitor._normalize_decorator(node)
269
+ assert result == "@app.route"
270
+
271
+ class TestDetectFrameworkUsage:
272
+
273
+ def test_framework_decorated_function_low_confidence(self):
274
+ mock_def = Mock()
275
+ mock_def.line = 10
276
+ mock_def.simple_name = "get_users"
277
+ mock_def.type = "function"
278
+
279
+ mock_visitor = Mock()
280
+ mock_visitor.framework_decorated_lines = {10}
281
+ mock_visitor.is_framework_file = True
282
+
283
+ confidence = detect_framework_usage(mock_def, visitor=mock_visitor)
284
+ assert confidence == 20
285
+
286
+ def test_framework_file_function_medium_confidence(self):
287
+ mock_def = Mock()
288
+ mock_def.line = 15
289
+ mock_def.simple_name = "helper_function"
290
+ mock_def.type = "function"
291
+
292
+ mock_visitor = Mock()
293
+ mock_visitor.framework_decorated_lines = set()
294
+ mock_visitor.is_framework_file = True
295
+
296
+ confidence = detect_framework_usage(mock_def, visitor=mock_visitor)
297
+ assert confidence == 40
298
+
299
+ def test_private_function_in_framework_file_no_confidence(self):
300
+ mock_def = Mock()
301
+ mock_def.line = 20
302
+ mock_def.simple_name = "_private_function"
303
+ mock_def.type = "function"
304
+
305
+ mock_visitor = Mock()
306
+ mock_visitor.framework_decorated_lines = set()
307
+ mock_visitor.is_framework_file = True
308
+
309
+ confidence = detect_framework_usage(mock_def, visitor=mock_visitor)
310
+ assert confidence is None
311
+
312
+ def test_non_framework_file_no_confidence(self):
313
+ mock_def = Mock()
314
+ mock_def.line = 25
315
+ mock_def.simple_name = "regular_function"
316
+ mock_def.type = "function"
317
+
318
+ mock_visitor = Mock()
319
+ mock_visitor.framework_decorated_lines = set()
320
+ mock_visitor.is_framework_file = False
321
+
322
+ confidence = detect_framework_usage(mock_def, visitor=mock_visitor)
323
+ assert confidence is None
324
+
325
+ def test_no_visitor_returns_none(self):
326
+ mock_def = Mock()
327
+ confidence = detect_framework_usage(mock_def, visitor=None)
328
+ assert confidence is None
329
+
330
+ def test_non_function_in_framework_file_no_confidence(self):
331
+ mock_def = Mock()
332
+ mock_def.line = 30
333
+ mock_def.simple_name = "my_variable"
334
+ mock_def.type = "variable"
335
+
336
+ mock_visitor = Mock()
337
+ mock_visitor.framework_decorated_lines = set()
338
+ mock_visitor.is_framework_file = True
339
+
340
+ confidence = detect_framework_usage(mock_def, visitor=mock_visitor)
341
+ assert confidence is None
342
+
343
+
344
+ class TestFrameworkPatterns:
345
+
346
+ def test_framework_decorators_list(self):
347
+ """Test that FRAMEWORK_DECORATORS contains expected patterns"""
348
+ assert "@app.route" in FRAMEWORK_DECORATORS
349
+ assert "@router.get" in FRAMEWORK_DECORATORS
350
+ assert "@login_required" in FRAMEWORK_DECORATORS
351
+ assert "@task" in FRAMEWORK_DECORATORS
352
+ assert "@validator" in FRAMEWORK_DECORATORS
353
+
354
+ def test_framework_functions_list(self):
355
+ """Test that FRAMEWORK_FUNCTIONS contains expected patterns"""
356
+ assert "get" in FRAMEWORK_FUNCTIONS
357
+ assert "post" in FRAMEWORK_FUNCTIONS
358
+ assert "save" in FRAMEWORK_FUNCTIONS
359
+ assert "*_queryset" in FRAMEWORK_FUNCTIONS
360
+ assert "get_context_data" in FRAMEWORK_FUNCTIONS
361
+
362
+ def test_framework_imports_set(self):
363
+ """Test that FRAMEWORK_IMPORTS contains expected frameworks"""
364
+ assert 'flask' in FRAMEWORK_IMPORTS
365
+ assert 'django' in FRAMEWORK_IMPORTS
366
+ assert 'fastapi' in FRAMEWORK_IMPORTS
367
+ assert 'celery' in FRAMEWORK_IMPORTS
368
+ assert 'pydantic' in FRAMEWORK_IMPORTS
369
+
370
+
371
+ if __name__ == "__main__":
372
+ pytest.main([__file__, "-v"])
@@ -0,0 +1,328 @@
1
+ import pytest
2
+ import ast
3
+ from unittest.mock import patch
4
+ from skylos.test_aware import TestAwareVisitor
5
+
6
+ class TestTestAwareVisitor:
7
+
8
+ def test_init_without_filename(self):
9
+ visitor = TestAwareVisitor()
10
+ assert visitor.is_test_file == False
11
+ assert visitor.test_decorated_lines == set()
12
+
13
+ def test_init_with_non_test_filename(self):
14
+ visitor = TestAwareVisitor(filename="mymodule.py")
15
+ assert visitor.is_test_file == False
16
+ assert visitor.test_decorated_lines == set()
17
+
18
+ @patch('skylos.test_aware.TEST_FILE_RE')
19
+ def test_init_with_test_filename(self, mock_test_file_re):
20
+ mock_test_file_re.search.return_value = True
21
+
22
+ visitor = TestAwareVisitor(filename="test_module.py")
23
+ assert visitor.is_test_file == True
24
+ assert visitor.test_decorated_lines == set()
25
+ mock_test_file_re.search.assert_called_once_with("test_module.py")
26
+
27
+ def test_test_function_name_patterns(self):
28
+ code = """
29
+ def test_something():
30
+ pass
31
+
32
+ def another_test():
33
+ pass
34
+
35
+ def setup_method():
36
+ pass
37
+
38
+ def teardown_function():
39
+ pass
40
+
41
+ def setUp(self):
42
+ pass
43
+
44
+ def tearDown(self):
45
+ pass
46
+
47
+ def setUpClass(cls):
48
+ pass
49
+
50
+ def tearDownClass(cls):
51
+ pass
52
+
53
+ def setUpModule():
54
+ pass
55
+
56
+ def tearDownModule():
57
+ pass
58
+
59
+ def regular_function():
60
+ pass
61
+ """
62
+ tree = ast.parse(code)
63
+ visitor = TestAwareVisitor()
64
+ visitor.visit(tree)
65
+
66
+ assert 2 in visitor.test_decorated_lines
67
+ assert 5 in visitor.test_decorated_lines
68
+ assert 8 in visitor.test_decorated_lines
69
+ assert 11 in visitor.test_decorated_lines
70
+ assert 14 in visitor.test_decorated_lines
71
+ assert 17 in visitor.test_decorated_lines # tearDown
72
+ assert 20 in visitor.test_decorated_lines # setUpClass
73
+ assert 23 in visitor.test_decorated_lines # tearDownClass
74
+ assert 26 in visitor.test_decorated_lines # setUpModule
75
+ assert 29 in visitor.test_decorated_lines # tearDownModule
76
+ assert 32 not in visitor.test_decorated_lines # gd ol regular_function
77
+
78
+ @patch('skylos.test_aware.TEST_DECOR_RE')
79
+ def test_test_decorator_detection(self, mock_test_decor_re):
80
+ mock_test_decor_re.match.return_value = True
81
+
82
+ code = """
83
+ @pytest.mark.parametrize('a,b', [(1,2), (3,4)])
84
+ def test_with_parametrize():
85
+ pass
86
+
87
+ @mock.patch('module.function')
88
+ def test_with_mock():
89
+ pass
90
+
91
+ @unittest.skip("reason")
92
+ def test_with_skip():
93
+ pass
94
+ """
95
+ tree = ast.parse(code)
96
+ visitor = TestAwareVisitor()
97
+ visitor.visit(tree)
98
+
99
+ assert 3 in visitor.test_decorated_lines
100
+ assert 7 in visitor.test_decorated_lines
101
+ assert 11 in visitor.test_decorated_lines
102
+
103
+ def test_pytest_fixture_detection(self):
104
+ code = """
105
+ @pytest.fixture
106
+ def sample_data():
107
+ return {"key": "value"}
108
+
109
+ @pytest.fixture(scope="session")
110
+ def database():
111
+ return create_test_db()
112
+
113
+ @fixture
114
+ def simple_fixture():
115
+ return "test"
116
+ """
117
+ tree = ast.parse(code)
118
+ visitor = TestAwareVisitor()
119
+ visitor.visit(tree)
120
+
121
+ assert 3 in visitor.test_decorated_lines
122
+ assert 11 in visitor.test_decorated_lines
123
+
124
+ def test_async_test_function_detection(self):
125
+ code = """
126
+ async def test_async_function():
127
+ await some_async_operation()
128
+
129
+ @pytest.mark.asyncio
130
+ async def test_with_asyncio_decorator():
131
+ await another_operation()
132
+ """
133
+ tree = ast.parse(code)
134
+ visitor = TestAwareVisitor()
135
+ visitor.visit(tree)
136
+
137
+ assert 2 in visitor.test_decorated_lines
138
+ assert 6 in visitor.test_decorated_lines
139
+
140
+ def test_decorator_name_extraction(self):
141
+ visitor = TestAwareVisitor()
142
+
143
+ node = ast.parse("@decorator\ndef func(): pass").body[0].decorator_list[0]
144
+ result = visitor._decorator_name(node)
145
+ assert result == "decorator"
146
+
147
+ node = ast.parse("@pytest.mark.skip\ndef func(): pass").body[0].decorator_list[0]
148
+ result = visitor._decorator_name(node)
149
+ assert result == "pytest.mark.skip"
150
+
151
+ node = ast.parse("@fixture\ndef func(): pass").body[0].decorator_list[0]
152
+ result = visitor._decorator_name(node)
153
+ assert result == "fixture"
154
+
155
+ def test_multiple_decorators_on_function(self):
156
+ """testing function with couple of decorators"""
157
+ code = """
158
+ @pytest.mark.parametrize('x', [1, 2, 3])
159
+ @pytest.mark.slow
160
+ @mock.patch('module.function')
161
+ def test_multiple_decorators():
162
+ pass
163
+ """
164
+ tree = ast.parse(code)
165
+ visitor = TestAwareVisitor()
166
+ visitor.visit(tree)
167
+
168
+ # should be detected as test
169
+ assert 5 in visitor.test_decorated_lines
170
+
171
+ @patch('skylos.test_aware.TEST_IMPORT_RE')
172
+ def test_test_import_detection_when_test_file(self, mock_test_import_re):
173
+ mock_test_import_re.match.return_value = True
174
+
175
+ code = """
176
+ import pytest
177
+ import unittest
178
+ from mock import Mock
179
+ """
180
+ tree = ast.parse(code)
181
+ visitor = TestAwareVisitor()
182
+ visitor.is_test_file = True
183
+ visitor.visit(tree)
184
+
185
+ assert mock_test_import_re.match.call_count >= 2
186
+
187
+ @patch('skylos.test_aware.TEST_IMPORT_RE')
188
+ def test_test_import_detection_when_not_test_file(self, mock_test_import_re):
189
+ code = """
190
+ import pytest
191
+ import unittest
192
+ """
193
+ tree = ast.parse(code)
194
+ visitor = TestAwareVisitor()
195
+ visitor.is_test_file = False
196
+ visitor.visit(tree)
197
+
198
+ # no call regex match for imports
199
+ assert mock_test_import_re.match.call_count == 0
200
+
201
+ @patch('skylos.test_aware.TEST_IMPORT_RE')
202
+ def test_import_from_detection_when_test_file(self, mock_test_import_re):
203
+ mock_test_import_re.match.return_value = True
204
+
205
+ code = """
206
+ from pytest import fixture
207
+ from unittest.mock import Mock
208
+ """
209
+ tree = ast.parse(code)
210
+ visitor = TestAwareVisitor()
211
+ visitor.is_test_file = True
212
+ visitor.visit(tree)
213
+
214
+ # should call regex match from-imports
215
+ assert mock_test_import_re.match.call_count >= 2
216
+
217
+ def test_complex_test_class(self):
218
+ code = """
219
+ class TestUserModel:
220
+ def setUp(self):
221
+ self.user = User()
222
+
223
+ def test_user_creation(self):
224
+ assert self.user.name == "test"
225
+
226
+ def test_user_validation(self):
227
+ assert self.user.is_valid()
228
+
229
+ def tearDown(self):
230
+ self.user.delete()
231
+
232
+ def helper_method(self):
233
+ return "not a test"
234
+ """
235
+ tree = ast.parse(code)
236
+ visitor = TestAwareVisitor()
237
+ visitor.visit(tree)
238
+
239
+ assert 3 in visitor.test_decorated_lines # setUp
240
+ assert 6 in visitor.test_decorated_lines # test_user_creation
241
+ assert 9 in visitor.test_decorated_lines # test_user_validation
242
+ assert 12 in visitor.test_decorated_lines # tearDown
243
+ assert 15 not in visitor.test_decorated_lines # helper_method
244
+
245
+ def test_edge_case_function_names(self):
246
+ code = """
247
+ def test(): # 'test', no underscore
248
+ pass
249
+
250
+ def testing_function(): # 'test' but not test_
251
+ pass
252
+
253
+ def function_test(): # ending with _test
254
+ pass
255
+
256
+ def setuptools_install(): # 'setup' but starts with setup
257
+ pass
258
+ """
259
+ tree = ast.parse(code)
260
+ visitor = TestAwareVisitor()
261
+ visitor.visit(tree)
262
+
263
+ assert 2 not in visitor.test_decorated_lines
264
+ assert 5 not in visitor.test_decorated_lines
265
+ assert 8 in visitor.test_decorated_lines
266
+ assert 11 in visitor.test_decorated_lines
267
+
268
+ def test_nested_functions(self):
269
+ code = """
270
+ def outer_function():
271
+ def test_nested():
272
+ pass
273
+
274
+ def setup_nested():
275
+ pass
276
+
277
+ return test_nested
278
+ """
279
+ tree = ast.parse(code)
280
+ visitor = TestAwareVisitor()
281
+ visitor.visit(tree)
282
+
283
+ assert 3 in visitor.test_decorated_lines
284
+ assert 6 in visitor.test_decorated_lines
285
+ assert 2 not in visitor.test_decorated_lines
286
+
287
+ class TestTestAwareVisitorIntegration:
288
+
289
+ @patch('skylos.test_aware.TEST_FILE_RE')
290
+ def test_full_test_file_analysis(self, mock_test_file_re):
291
+ mock_test_file_re.search.return_value = True
292
+
293
+ code = """
294
+ import pytest
295
+ from unittest.mock import Mock
296
+
297
+ class TestCalculator:
298
+ @pytest.fixture
299
+ def calculator(self):
300
+ return Calculator()
301
+
302
+ def setUp(self):
303
+ self.mock_logger = Mock()
304
+
305
+ def test_addition(self, calculator):
306
+ result = calculator.add(2, 3)
307
+ assert result == 5
308
+
309
+ @pytest.mark.parametrize('a,b,expected', [(1,2,3), (4,5,9)])
310
+ def test_addition_parametrized(self, calculator, a, b, expected):
311
+ assert calculator.add(a, b) == expected
312
+
313
+ def helper_method(self):
314
+ return "utility function"
315
+ """
316
+ visitor = TestAwareVisitor(filename="test_calculator.py")
317
+ tree = ast.parse(code)
318
+ visitor.visit(tree)
319
+
320
+ assert visitor.is_test_file == True
321
+ assert 7 in visitor.test_decorated_lines
322
+ assert 10 in visitor.test_decorated_lines
323
+ assert 13 in visitor.test_decorated_lines
324
+ assert 18 in visitor.test_decorated_lines
325
+ assert 21 not in visitor.test_decorated_lines
326
+
327
+ if __name__ == "__main__":
328
+ pytest.main([__file__, "-v"])