skylos 1.1.12__py3-none-any.whl → 1.2.1__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.
- skylos/analyzer.py +128 -133
- skylos/cli.py +20 -2
- skylos/constants.py +34 -27
- skylos/framework_aware.py +158 -0
- skylos/test_aware.py +66 -0
- skylos/visitor.py +28 -3
- {skylos-1.1.12.dist-info → skylos-1.2.1.dist-info}/METADATA +1 -1
- skylos-1.2.1.dist-info/RECORD +32 -0
- test/test_analyzer.py +253 -195
- test/test_changes_analyzer.py +149 -0
- test/test_constants.py +348 -0
- test/test_framework_aware.py +372 -0
- test/test_test_aware.py +328 -0
- test/test_visitor.py +0 -10
- skylos-1.1.12.dist-info/RECORD +0 -26
- {skylos-1.1.12.dist-info → skylos-1.2.1.dist-info}/WHEEL +0 -0
- {skylos-1.1.12.dist-info → skylos-1.2.1.dist-info}/entry_points.txt +0 -0
- {skylos-1.1.12.dist-info → skylos-1.2.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import tempfile
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from skylos.analyzer import Skylos
|
|
6
|
+
|
|
7
|
+
class TestChangeAnalyzer:
|
|
8
|
+
|
|
9
|
+
def test_future_annotations_not_flagged(self):
|
|
10
|
+
"""from __future__ import annotations should not be flagged as unused"""
|
|
11
|
+
code = '''
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
import ast # should be flagged
|
|
14
|
+
|
|
15
|
+
def func(x: int) -> int:
|
|
16
|
+
return x * 2
|
|
17
|
+
'''
|
|
18
|
+
result = self._analyze(code)
|
|
19
|
+
import_names = [i["simple_name"] for i in result["unused_imports"]]
|
|
20
|
+
|
|
21
|
+
assert "annotations" not in import_names, "__future__ annotations wrongly flagged"
|
|
22
|
+
assert "ast" in import_names, "Regular unused import should also be flagged"
|
|
23
|
+
|
|
24
|
+
def test_underscore_items_not_flagged(self):
|
|
25
|
+
"""items starting with _ should not be reported as unused"""
|
|
26
|
+
code = '''
|
|
27
|
+
_private_var = "private"
|
|
28
|
+
|
|
29
|
+
def _private_func():
|
|
30
|
+
return "private"
|
|
31
|
+
|
|
32
|
+
def regular_func(_private_param):
|
|
33
|
+
return "test"
|
|
34
|
+
|
|
35
|
+
class Example:
|
|
36
|
+
def _private_method(self):
|
|
37
|
+
return "private"
|
|
38
|
+
'''
|
|
39
|
+
result = self._analyze(code)
|
|
40
|
+
|
|
41
|
+
function_names = [f["name"] for f in result["unused_functions"]]
|
|
42
|
+
underscore_funcs = [name for name in function_names if name.startswith("_")]
|
|
43
|
+
assert len(underscore_funcs) == 0, f"Underscore functions incorrectly flagged: {underscore_funcs}"
|
|
44
|
+
|
|
45
|
+
variable_names = [v["name"] for v in result["unused_variables"]]
|
|
46
|
+
underscore_vars = [name for name in variable_names if name.startswith("_")]
|
|
47
|
+
assert len(underscore_vars) == 0, f"Underscore variables incorrectly flagged: {underscore_vars}"
|
|
48
|
+
|
|
49
|
+
param_names = [p["name"] for p in result["unused_parameters"]]
|
|
50
|
+
underscore_params = [name for name in param_names if name.startswith("_")]
|
|
51
|
+
assert len(underscore_params) == 0, f"Underscore parameters incorrectly flagged: {underscore_params}"
|
|
52
|
+
|
|
53
|
+
def test_unittest_magic_methods_not_flagged(self):
|
|
54
|
+
"""setUp, tearDown, setUpClass should not be flagged as unused"""
|
|
55
|
+
code = '''
|
|
56
|
+
import unittest
|
|
57
|
+
|
|
58
|
+
class TestCase(unittest.TestCase):
|
|
59
|
+
def setUp(self):
|
|
60
|
+
self.data = "test"
|
|
61
|
+
|
|
62
|
+
def tearDown(self):
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def setUpClass(cls):
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def tearDownClass(cls):
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
def test_example(self):
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
def setUpModule():
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
def tearDownModule():
|
|
80
|
+
pass
|
|
81
|
+
'''
|
|
82
|
+
result = self._analyze(code, "test_magic.py")
|
|
83
|
+
function_names = [f["name"] for f in result["unused_functions"]]
|
|
84
|
+
|
|
85
|
+
magic_methods = ["setUp", "tearDown", "setUpClass", "tearDownClass", "setUpModule", "tearDownModule"]
|
|
86
|
+
flagged_magic = [method for method in magic_methods if method in function_names]
|
|
87
|
+
|
|
88
|
+
assert len(flagged_magic) == 0, f"unittest/pytest methods incorrectly flagged: {flagged_magic}"
|
|
89
|
+
|
|
90
|
+
def test_all_edge_cases_together(self):
|
|
91
|
+
code = '''
|
|
92
|
+
from __future__ import annotations
|
|
93
|
+
import unused_import
|
|
94
|
+
|
|
95
|
+
_private_var = "private"
|
|
96
|
+
|
|
97
|
+
class TestExample:
|
|
98
|
+
def setUp(self):
|
|
99
|
+
self._data = "test"
|
|
100
|
+
|
|
101
|
+
def tearDown(self):
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
def test_something(self):
|
|
105
|
+
return self._data
|
|
106
|
+
|
|
107
|
+
def _helper_method(self):
|
|
108
|
+
return "helper"
|
|
109
|
+
|
|
110
|
+
def _private_func() -> str:
|
|
111
|
+
return "private"
|
|
112
|
+
|
|
113
|
+
def regular_func(_param: str):
|
|
114
|
+
return "test"
|
|
115
|
+
'''
|
|
116
|
+
result = self._analyze(code, "test_comprehensive.py")
|
|
117
|
+
|
|
118
|
+
# should not flag __future__ imports
|
|
119
|
+
import_names = [i["simple_name"] for i in result["unused_imports"]]
|
|
120
|
+
assert "annotations" not in import_names, "__future__ annotations flagged"
|
|
121
|
+
assert "unused_import" in import_names, "Regular unused import should be flagged"
|
|
122
|
+
|
|
123
|
+
# should not flag _ items
|
|
124
|
+
function_names = [f["name"] for f in result["unused_functions"]]
|
|
125
|
+
underscore_funcs = [name for name in function_names if name.startswith("_")]
|
|
126
|
+
assert len(underscore_funcs) == 0, f"Underscore functions flagged: {underscore_funcs}"
|
|
127
|
+
|
|
128
|
+
# should not flag test methods
|
|
129
|
+
magic_methods = ["setUp", "tearDown", "test_something"]
|
|
130
|
+
flagged_magic = [method for method in magic_methods if method in function_names]
|
|
131
|
+
assert len(flagged_magic) == 0, f"Test methods flagged: {flagged_magic}"
|
|
132
|
+
|
|
133
|
+
# should not flag _ param
|
|
134
|
+
param_names = [p["name"] for p in result["unused_parameters"]]
|
|
135
|
+
underscore_params = [name for name in param_names if name.startswith("_")]
|
|
136
|
+
assert len(underscore_params) == 0, f"Underscore parameters flagged: {underscore_params}"
|
|
137
|
+
|
|
138
|
+
def _analyze(self, code: str, filename: str = "example.py") -> dict:
|
|
139
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
140
|
+
file_path = Path(temp_dir) / filename
|
|
141
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
142
|
+
file_path.write_text(code)
|
|
143
|
+
|
|
144
|
+
skylos = Skylos()
|
|
145
|
+
result_json = skylos.analyze(str(temp_dir), thr=60)
|
|
146
|
+
return json.loads(result_json)
|
|
147
|
+
|
|
148
|
+
if __name__ == "__main__":
|
|
149
|
+
pytest.main([__file__, "-v"])
|
test/test_constants.py
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from skylos.constants import (
|
|
4
|
+
PENALTIES,
|
|
5
|
+
TEST_FILE_RE,
|
|
6
|
+
TEST_IMPORT_RE,
|
|
7
|
+
TEST_DECOR_RE,
|
|
8
|
+
AUTO_CALLED,
|
|
9
|
+
TEST_METHOD_PATTERN,
|
|
10
|
+
UNITTEST_LIFECYCLE_METHODS,
|
|
11
|
+
FRAMEWORK_FILE_RE,
|
|
12
|
+
DEFAULT_EXCLUDE_FOLDERS,
|
|
13
|
+
is_test_path, is_framework_path)
|
|
14
|
+
|
|
15
|
+
class TestTestFileRegex:
|
|
16
|
+
|
|
17
|
+
def test_test_file_patterns_match(self):
|
|
18
|
+
test_paths = [
|
|
19
|
+
"module_test.py", # ending with _test.py
|
|
20
|
+
"tests/test_something.py", # in the tests dir
|
|
21
|
+
"test/unit_tests.py", # in test dir
|
|
22
|
+
"/path/to/tests/helper.py", # in tests dir
|
|
23
|
+
"src/tests/integration/test_api.py", # also in tests dir
|
|
24
|
+
"Tests/TestCase.py", # test with captal T
|
|
25
|
+
"TESTS/MODULE_TEST.PY", # all caps tests dir
|
|
26
|
+
"/project/test/conftest.py",
|
|
27
|
+
"C:\\project\\tests\\test_file.py", # windows path
|
|
28
|
+
"project\\test\\helper.py",
|
|
29
|
+
"some_module_test.py"
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
for path in test_paths:
|
|
33
|
+
assert TEST_FILE_RE.search(path), f"Should match test path: {path}"
|
|
34
|
+
|
|
35
|
+
def test_non_test_file_patterns_dont_match(self):
|
|
36
|
+
"""Test that TEST_FILE_RE doesn't match non-test files"""
|
|
37
|
+
non_test_paths = [
|
|
38
|
+
"test_module.py",
|
|
39
|
+
"module.py",
|
|
40
|
+
"main.py",
|
|
41
|
+
"src/utils.py",
|
|
42
|
+
"app/models.py",
|
|
43
|
+
"testing_utils.py",
|
|
44
|
+
"contest.py",
|
|
45
|
+
"protest.py",
|
|
46
|
+
"/path/to/contest_module.py",
|
|
47
|
+
"mytests.py",
|
|
48
|
+
"src/testing/helper.py",
|
|
49
|
+
"testmodule.py"
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
for path in non_test_paths:
|
|
53
|
+
assert not TEST_FILE_RE.search(path), f"Should NOT match non-test path: {path}"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class TestTestImportRegex:
|
|
57
|
+
|
|
58
|
+
def test_test_import_patterns_match(self):
|
|
59
|
+
test_imports = [
|
|
60
|
+
"pytest",
|
|
61
|
+
"pytest.fixture",
|
|
62
|
+
"pytest.mark.parametrize",
|
|
63
|
+
"unittest",
|
|
64
|
+
"unittest.mock",
|
|
65
|
+
"unittest.TestCase",
|
|
66
|
+
"nose",
|
|
67
|
+
"nose.tools",
|
|
68
|
+
"mock",
|
|
69
|
+
"mock.patch",
|
|
70
|
+
"responses",
|
|
71
|
+
"responses.activate"
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
for import_name in test_imports:
|
|
75
|
+
assert TEST_IMPORT_RE.match(import_name), f"Should match test import: {import_name}"
|
|
76
|
+
|
|
77
|
+
def test_non_test_import_patterns_dont_match(self):
|
|
78
|
+
non_test_imports = [
|
|
79
|
+
"os",
|
|
80
|
+
"sys",
|
|
81
|
+
"json",
|
|
82
|
+
"requests",
|
|
83
|
+
"flask",
|
|
84
|
+
"django",
|
|
85
|
+
"numpy",
|
|
86
|
+
"pandas",
|
|
87
|
+
"pytest_plugin",
|
|
88
|
+
"unittest_extensions",
|
|
89
|
+
"mockery",
|
|
90
|
+
"response"
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
for import_name in non_test_imports:
|
|
94
|
+
assert not TEST_IMPORT_RE.match(import_name), f"Should NOT match non-test import: {import_name}"
|
|
95
|
+
|
|
96
|
+
class TestTestDecoratorRegex:
|
|
97
|
+
|
|
98
|
+
def test_test_decorator_patterns_match(self):
|
|
99
|
+
test_decorators = [
|
|
100
|
+
"pytest.fixture",
|
|
101
|
+
"pytest.mark",
|
|
102
|
+
"patch",
|
|
103
|
+
"responses.activate",
|
|
104
|
+
"freeze_time"
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
for decorator in test_decorators:
|
|
108
|
+
assert TEST_DECOR_RE.match(decorator), f"Should match test decorator: {decorator}"
|
|
109
|
+
|
|
110
|
+
def test_non_test_decorator_patterns_dont_match(self):
|
|
111
|
+
non_test_decorators = [
|
|
112
|
+
"property",
|
|
113
|
+
"staticmethod",
|
|
114
|
+
"classmethod",
|
|
115
|
+
"app.route",
|
|
116
|
+
"login_required",
|
|
117
|
+
"cache.cached",
|
|
118
|
+
"pytest_configure",
|
|
119
|
+
"patcher",
|
|
120
|
+
"response",
|
|
121
|
+
"freeze"
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
for decorator in non_test_decorators:
|
|
125
|
+
assert not TEST_DECOR_RE.match(decorator), f"Should not match non-test decorator: {decorator}"
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class TestTestMethodPattern:
|
|
129
|
+
|
|
130
|
+
def test_test_method_pattern_matches(self):
|
|
131
|
+
"""Test that TEST_METHOD_PATTERN matches test method names"""
|
|
132
|
+
test_methods = [
|
|
133
|
+
"test_something",
|
|
134
|
+
"test_user_creation",
|
|
135
|
+
"test_api_response",
|
|
136
|
+
"test_edge_case",
|
|
137
|
+
"test_123",
|
|
138
|
+
"test_with_numbers_123",
|
|
139
|
+
"test_UPPERCASE"
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
for method in test_methods:
|
|
143
|
+
assert TEST_METHOD_PATTERN.match(method), f"Should match test method: {method}"
|
|
144
|
+
|
|
145
|
+
def test_test_method_pattern_doesnt_match(self):
|
|
146
|
+
non_test_methods = [
|
|
147
|
+
"test", # No _
|
|
148
|
+
"test_",
|
|
149
|
+
"testing_something",
|
|
150
|
+
# dont start with test
|
|
151
|
+
"my_test_method",
|
|
152
|
+
"testSomething",
|
|
153
|
+
"Test_something",
|
|
154
|
+
"_test_something"
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
for method in non_test_methods:
|
|
158
|
+
assert not TEST_METHOD_PATTERN.match(method), f"Should NOT match non-test method: {method}"
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class TestFrameworkFileRegex:
|
|
162
|
+
|
|
163
|
+
def test_framework_file_patterns_match(self):
|
|
164
|
+
framework_files = [
|
|
165
|
+
"views.py",
|
|
166
|
+
"handlers.py",
|
|
167
|
+
"endpoints.py",
|
|
168
|
+
"routes.py",
|
|
169
|
+
"api.py",
|
|
170
|
+
"/path/to/views.py",
|
|
171
|
+
"app/views.py",
|
|
172
|
+
"VIEWS.PY",
|
|
173
|
+
"API.py",
|
|
174
|
+
"C:\\project\\app\\handlers.py",
|
|
175
|
+
"my_handlers.py",
|
|
176
|
+
"user_api.py",
|
|
177
|
+
"admin_views.py"
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
for file_path in framework_files:
|
|
181
|
+
assert FRAMEWORK_FILE_RE.search(file_path), f"Should match framework file: {file_path}"
|
|
182
|
+
|
|
183
|
+
def test_non_framework_file_patterns_dont_match(self):
|
|
184
|
+
"""Test that FRAMEWORK_FILE_RE doesn't match non-framework files"""
|
|
185
|
+
non_framework_files = [
|
|
186
|
+
"models.py",
|
|
187
|
+
"utils.py",
|
|
188
|
+
"config.py",
|
|
189
|
+
"main.py",
|
|
190
|
+
"views_helper.py",
|
|
191
|
+
"endpoint_utils.py",
|
|
192
|
+
"router.py",
|
|
193
|
+
"apis.py",
|
|
194
|
+
"handler.py"
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
for file_path in non_framework_files:
|
|
198
|
+
assert not FRAMEWORK_FILE_RE.search(file_path), f"Should NOT match non-framework file: {file_path}"
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class TestHelperFunctions:
|
|
202
|
+
|
|
203
|
+
def test_is_test_path_with_strings(self):
|
|
204
|
+
assert is_test_path("module_test.py") == True
|
|
205
|
+
assert is_test_path("tests/helper.py") == True
|
|
206
|
+
assert is_test_path("test/conftest.py") == True
|
|
207
|
+
assert is_test_path("test_module.py") == False
|
|
208
|
+
assert is_test_path("regular_module.py") == False
|
|
209
|
+
assert is_test_path("testing_utils.py") == False
|
|
210
|
+
|
|
211
|
+
def test_is_test_path_with_path_objects(self):
|
|
212
|
+
assert is_test_path(Path("module_test.py")) == True
|
|
213
|
+
assert is_test_path(Path("tests/helper.py")) == True
|
|
214
|
+
assert is_test_path(Path("test_module.py")) == False
|
|
215
|
+
assert is_test_path(Path("regular_module.py")) == False
|
|
216
|
+
|
|
217
|
+
def test_is_framework_path_with_strings(self):
|
|
218
|
+
assert is_framework_path("views.py") == True
|
|
219
|
+
assert is_framework_path("handlers.py") == True
|
|
220
|
+
assert is_framework_path("api.py") == True
|
|
221
|
+
assert is_framework_path("models.py") == False
|
|
222
|
+
assert is_framework_path("utils.py") == False
|
|
223
|
+
|
|
224
|
+
def test_is_framework_path_with_path_objects(self):
|
|
225
|
+
assert is_framework_path(Path("views.py")) == True
|
|
226
|
+
assert is_framework_path(Path("app/handlers.py")) == True
|
|
227
|
+
assert is_framework_path(Path("models.py")) == False
|
|
228
|
+
|
|
229
|
+
def test_path_functions_with_complex_paths(self):
|
|
230
|
+
complex_paths = [
|
|
231
|
+
("/project/src/tests/unit/test_models.py", True, False),
|
|
232
|
+
("/project/app/views.py", False, True),
|
|
233
|
+
("/project/src/utils/helpers.py", False, False),
|
|
234
|
+
("C:\\project\\tests\\integration\\test_api.py", True, True),
|
|
235
|
+
("C:\\project\\app\\api\\handlers.py", False, True),
|
|
236
|
+
("/very/deep/path/to/tests/conftest.py", True, False),
|
|
237
|
+
("/app/endpoints.py", False, True),
|
|
238
|
+
("/project/module_test.py", True, False)
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
for path, is_test, is_framework in complex_paths:
|
|
242
|
+
assert is_test_path(path) == is_test, f"is_test_path({path}) should be {is_test}"
|
|
243
|
+
assert is_framework_path(path) == is_framework, f"is_framework_path({path}) should be {is_framework}"
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class TestConstants:
|
|
247
|
+
|
|
248
|
+
def test_penalties_structure(self):
|
|
249
|
+
"""Test that penalities contains expected keys and reasonable values"""
|
|
250
|
+
expected_keys = {
|
|
251
|
+
"private_name", "dunder_or_magic", "underscored_var",
|
|
252
|
+
"in_init_file", "dynamic_module", "test_related", "framework_magic"
|
|
253
|
+
}
|
|
254
|
+
assert set(PENALTIES.keys()) == expected_keys
|
|
255
|
+
|
|
256
|
+
for key, value in PENALTIES.items():
|
|
257
|
+
assert isinstance(value, int), f"Penalty {key} should be int, got {type(value)}"
|
|
258
|
+
assert value > 0, f"Penalty {key} should be positive, got {value}"
|
|
259
|
+
assert value <= 100, f"Penalty {key} should be <= 100, got {value}"
|
|
260
|
+
|
|
261
|
+
def test_auto_called_methods(self):
|
|
262
|
+
expected_methods = {"__init__", "__enter__", "__exit__"}
|
|
263
|
+
assert AUTO_CALLED == expected_methods
|
|
264
|
+
|
|
265
|
+
for method in AUTO_CALLED:
|
|
266
|
+
assert method.startswith("__") and method.endswith("__"), f"{method} should be dunder method"
|
|
267
|
+
|
|
268
|
+
def test_unittest_lifecycle_methods(self):
|
|
269
|
+
expected_methods = {
|
|
270
|
+
'setUp', 'tearDown', 'setUpClass', 'tearDownClass',
|
|
271
|
+
'setUpModule', 'tearDownModule'
|
|
272
|
+
}
|
|
273
|
+
assert UNITTEST_LIFECYCLE_METHODS == expected_methods
|
|
274
|
+
|
|
275
|
+
setup_methods = {m for m in UNITTEST_LIFECYCLE_METHODS if 'setUp' in m or 'setup' in m.lower()}
|
|
276
|
+
teardown_methods = {m for m in UNITTEST_LIFECYCLE_METHODS if 'tearDown' in m or 'teardown' in m.lower()}
|
|
277
|
+
|
|
278
|
+
assert len(setup_methods) == 3, f"Should have 3 setup methods, got {setup_methods}"
|
|
279
|
+
assert len(teardown_methods) == 3, f"Should have 3 teardown methods, got {teardown_methods}"
|
|
280
|
+
|
|
281
|
+
def test_default_exclude_folders(self):
|
|
282
|
+
expected_folders = {
|
|
283
|
+
"__pycache__", ".git", ".pytest_cache", ".mypy_cache", ".tox",
|
|
284
|
+
"htmlcov", ".coverage", "build", "dist", "*.egg-info", "venv", ".venv"
|
|
285
|
+
}
|
|
286
|
+
assert expected_folders.issubset(DEFAULT_EXCLUDE_FOLDERS)
|
|
287
|
+
|
|
288
|
+
for folder in DEFAULT_EXCLUDE_FOLDERS:
|
|
289
|
+
assert isinstance(folder, str), f"Folder {folder} should be string"
|
|
290
|
+
assert len(folder) > 0, f"Folder name should not be empty"
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class TestRegexEdgeCases:
|
|
294
|
+
|
|
295
|
+
def test_test_file_regex_case_insensitivity(self):
|
|
296
|
+
assert TEST_FILE_RE.search("MODULE_TEST.PY")
|
|
297
|
+
assert TEST_FILE_RE.search("Tests/Helper.py")
|
|
298
|
+
assert TEST_FILE_RE.search("TEST/CONFTEST.PY")
|
|
299
|
+
assert TEST_FILE_RE.search("tests/MODULE.PY")
|
|
300
|
+
|
|
301
|
+
def test_test_file_regex_path_separators(self):
|
|
302
|
+
# unix
|
|
303
|
+
assert TEST_FILE_RE.search("tests/unit/test_module.py")
|
|
304
|
+
assert TEST_FILE_RE.search("/absolute/path/tests/test_file.py")
|
|
305
|
+
|
|
306
|
+
# windows
|
|
307
|
+
assert TEST_FILE_RE.search("tests\\unit\\test_module.py")
|
|
308
|
+
assert TEST_FILE_RE.search("C:\\project\\test\\test_file.py")
|
|
309
|
+
|
|
310
|
+
assert TEST_FILE_RE.search("project/tests\\test_file.py")
|
|
311
|
+
|
|
312
|
+
def test_framework_file_regex_case_insensitivity(self):
|
|
313
|
+
assert FRAMEWORK_FILE_RE.search("VIEWS.PY")
|
|
314
|
+
assert FRAMEWORK_FILE_RE.search("Handlers.py")
|
|
315
|
+
assert FRAMEWORK_FILE_RE.search("API.py")
|
|
316
|
+
assert FRAMEWORK_FILE_RE.search("Routes.PY")
|
|
317
|
+
|
|
318
|
+
def test_import_regex_with_deep_modules(self):
|
|
319
|
+
assert TEST_IMPORT_RE.match("pytest.mark.parametrize.something")
|
|
320
|
+
assert TEST_IMPORT_RE.match("unittest.mock.patch.object")
|
|
321
|
+
assert TEST_IMPORT_RE.match("nose.tools.assert_equal")
|
|
322
|
+
assert not TEST_IMPORT_RE.match("requests.auth.HTTPBasicAuth")
|
|
323
|
+
|
|
324
|
+
def test_decorator_regex_whitespace_handling(self):
|
|
325
|
+
working_decorators = [
|
|
326
|
+
"pytest.fixture",
|
|
327
|
+
"pytest.mark",
|
|
328
|
+
"patch",
|
|
329
|
+
"responses.activate",
|
|
330
|
+
"freeze_time"
|
|
331
|
+
]
|
|
332
|
+
|
|
333
|
+
for decorator in working_decorators:
|
|
334
|
+
assert TEST_DECOR_RE.match(decorator), f"Should match: {decorator}"
|
|
335
|
+
|
|
336
|
+
non_working = [
|
|
337
|
+
"pytest.mark.skip",
|
|
338
|
+
"pytest.mark.parametrize",
|
|
339
|
+
# may not work due to regex formatting
|
|
340
|
+
"patch.object"
|
|
341
|
+
]
|
|
342
|
+
|
|
343
|
+
for decorator in non_working:
|
|
344
|
+
assert not TEST_DECOR_RE.match(decorator), f"Currently doesn't match (regex issue): {decorator}"
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
if __name__ == "__main__":
|
|
348
|
+
pytest.main([__file__, "-v"])
|