skylos 1.0.11__py3-none-any.whl → 1.1.11__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/__init__.py +1 -1
- skylos/analyzer.py +108 -9
- skylos/cli.py +63 -4
- skylos/visitor.py +5 -7
- {skylos-1.0.11.dist-info → skylos-1.1.11.dist-info}/METADATA +1 -1
- skylos-1.1.11.dist-info/RECORD +25 -0
- test/conftest.py +212 -0
- test/test_analyzer.py +584 -0
- test/test_cli.py +353 -0
- test/test_integration.py +320 -0
- test/test_visitor.py +516 -22
- skylos-1.0.11.dist-info/RECORD +0 -30
- test/pykomodo/__init__.py +0 -0
- test/pykomodo/command_line.py +0 -176
- test/pykomodo/config.py +0 -20
- test/pykomodo/core.py +0 -121
- test/pykomodo/dashboard.py +0 -608
- test/pykomodo/enhanced_chunker.py +0 -304
- test/pykomodo/multi_dirs_chunker.py +0 -783
- test/pykomodo/pykomodo_config.py +0 -68
- test/pykomodo/token_chunker.py +0 -470
- {skylos-1.0.11.dist-info → skylos-1.1.11.dist-info}/WHEEL +0 -0
- {skylos-1.0.11.dist-info → skylos-1.1.11.dist-info}/entry_points.txt +0 -0
- {skylos-1.0.11.dist-info → skylos-1.1.11.dist-info}/top_level.txt +0 -0
test/test_analyzer.py
ADDED
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import pytest
|
|
3
|
+
import json
|
|
4
|
+
import tempfile
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from unittest.mock import Mock, patch
|
|
7
|
+
from collections import defaultdict
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
from skylos.analyzer import (
|
|
11
|
+
Skylos,
|
|
12
|
+
parse_exclude_folders,
|
|
13
|
+
proc_file,
|
|
14
|
+
analyze,
|
|
15
|
+
DEFAULT_EXCLUDE_FOLDERS,
|
|
16
|
+
AUTO_CALLED,
|
|
17
|
+
MAGIC_METHODS,
|
|
18
|
+
TEST_METHOD_PATTERN
|
|
19
|
+
)
|
|
20
|
+
except ImportError:
|
|
21
|
+
import sys
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
24
|
+
from skylos.analyzer import (
|
|
25
|
+
Skylos,
|
|
26
|
+
parse_exclude_folders,
|
|
27
|
+
proc_file,
|
|
28
|
+
analyze,
|
|
29
|
+
DEFAULT_EXCLUDE_FOLDERS,
|
|
30
|
+
AUTO_CALLED,
|
|
31
|
+
MAGIC_METHODS,
|
|
32
|
+
TEST_METHOD_PATTERN
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
class TestParseExcludeFolders:
|
|
36
|
+
|
|
37
|
+
def test_default_exclude_folders_included(self):
|
|
38
|
+
"""default folders are included by default."""
|
|
39
|
+
result = parse_exclude_folders(None, use_defaults=True)
|
|
40
|
+
assert DEFAULT_EXCLUDE_FOLDERS.issubset(result)
|
|
41
|
+
|
|
42
|
+
def test_default_exclude_folders_disabled(self):
|
|
43
|
+
"""default folders can be disabled."""
|
|
44
|
+
result = parse_exclude_folders(None, use_defaults=False)
|
|
45
|
+
assert not DEFAULT_EXCLUDE_FOLDERS.intersection(result)
|
|
46
|
+
|
|
47
|
+
def test_user_exclude_folders_added(self):
|
|
48
|
+
"""user-specified folders are added."""
|
|
49
|
+
user_folders = {"custom_folder", "another_folder"}
|
|
50
|
+
result = parse_exclude_folders(user_folders, use_defaults=True)
|
|
51
|
+
assert user_folders.issubset(result)
|
|
52
|
+
assert DEFAULT_EXCLUDE_FOLDERS.issubset(result)
|
|
53
|
+
|
|
54
|
+
def test_include_folders_override_defaults(self):
|
|
55
|
+
"""include_folders can override defaults."""
|
|
56
|
+
include_folders = {"__pycache__", ".git"}
|
|
57
|
+
result = parse_exclude_folders(None, use_defaults=True, include_folders=include_folders)
|
|
58
|
+
for folder in include_folders:
|
|
59
|
+
assert folder not in result
|
|
60
|
+
|
|
61
|
+
def test_include_folders_override_user_excludes(self):
|
|
62
|
+
"""include_folders can override user excludes."""
|
|
63
|
+
user_excludes = {"custom_folder", "another_folder"}
|
|
64
|
+
include_folders = {"custom_folder"}
|
|
65
|
+
result = parse_exclude_folders(user_excludes, use_defaults=False, include_folders=include_folders)
|
|
66
|
+
assert "custom_folder" not in result
|
|
67
|
+
assert "another_folder" in result
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class TestSkylos:
|
|
71
|
+
|
|
72
|
+
@pytest.fixture
|
|
73
|
+
def skylos(self):
|
|
74
|
+
return Skylos()
|
|
75
|
+
|
|
76
|
+
def test_init(self, skylos):
|
|
77
|
+
assert skylos.defs == {}
|
|
78
|
+
assert skylos.refs == []
|
|
79
|
+
assert skylos.dynamic == set()
|
|
80
|
+
assert isinstance(skylos.exports, defaultdict)
|
|
81
|
+
|
|
82
|
+
def test_module_name_generation(self, skylos):
|
|
83
|
+
"""Test module name generation from file paths."""
|
|
84
|
+
root = Path("/project")
|
|
85
|
+
|
|
86
|
+
# test a regular Python file
|
|
87
|
+
file_path = Path("/project/src/module.py")
|
|
88
|
+
result = skylos._module(root, file_path)
|
|
89
|
+
assert result == "src.module"
|
|
90
|
+
|
|
91
|
+
# test __init__.py file
|
|
92
|
+
file_path = Path("/project/src/__init__.py")
|
|
93
|
+
result = skylos._module(root, file_path)
|
|
94
|
+
assert result == "src"
|
|
95
|
+
|
|
96
|
+
# nested module
|
|
97
|
+
file_path = Path("/project/src/package/submodule.py")
|
|
98
|
+
result = skylos._module(root, file_path)
|
|
99
|
+
assert result == "src.package.submodule"
|
|
100
|
+
|
|
101
|
+
# root level file
|
|
102
|
+
file_path = Path("/project/main.py")
|
|
103
|
+
result = skylos._module(root, file_path)
|
|
104
|
+
assert result == "main"
|
|
105
|
+
|
|
106
|
+
def test_should_exclude_file(self, skylos):
|
|
107
|
+
"""
|
|
108
|
+
should exclude pycache, build, egg-info and whatever is in exclude_folders
|
|
109
|
+
"""
|
|
110
|
+
root = Path("/project")
|
|
111
|
+
exclude_folders = {"__pycache__", "build", "*.egg-info"}
|
|
112
|
+
|
|
113
|
+
file_path = Path("/project/src/__pycache__/module.pyc")
|
|
114
|
+
assert skylos._should_exclude_file(file_path, root, exclude_folders)
|
|
115
|
+
|
|
116
|
+
file_path = Path("/project/build/lib/module.py")
|
|
117
|
+
assert skylos._should_exclude_file(file_path, root, exclude_folders)
|
|
118
|
+
|
|
119
|
+
file_path = Path("/project/mypackage.egg-info/PKG-INFO")
|
|
120
|
+
assert skylos._should_exclude_file(file_path, root, exclude_folders)
|
|
121
|
+
|
|
122
|
+
file_path = Path("/project/src/module.py")
|
|
123
|
+
assert not skylos._should_exclude_file(file_path, root, exclude_folders)
|
|
124
|
+
|
|
125
|
+
assert not skylos._should_exclude_file(file_path, root, None)
|
|
126
|
+
|
|
127
|
+
@patch('skylos.analyzer.Path')
|
|
128
|
+
def test_get_python_files_single_file(self, mock_path, skylos):
|
|
129
|
+
mock_file = Mock()
|
|
130
|
+
mock_file.is_file.return_value = True
|
|
131
|
+
mock_file.parent = Path("/project")
|
|
132
|
+
mock_path.return_value.resolve.return_value = mock_file
|
|
133
|
+
|
|
134
|
+
files, root = skylos._get_python_files("/project/test.py")
|
|
135
|
+
assert files == [mock_file]
|
|
136
|
+
assert root == Path("/project")
|
|
137
|
+
|
|
138
|
+
@patch('skylos.analyzer.Path')
|
|
139
|
+
def test_get_python_files_directory(self, mock_path, skylos):
|
|
140
|
+
mock_dir = Mock()
|
|
141
|
+
mock_dir.is_file.return_value = False
|
|
142
|
+
mock_files = [Path("/project/file1.py"), Path("/project/file2.py")]
|
|
143
|
+
mock_dir.glob.return_value = mock_files
|
|
144
|
+
mock_path.return_value.resolve.return_value = mock_dir
|
|
145
|
+
|
|
146
|
+
files, root = skylos._get_python_files("/project")
|
|
147
|
+
assert files == mock_files
|
|
148
|
+
assert root == mock_dir
|
|
149
|
+
|
|
150
|
+
def test_mark_exports_in_init(self, skylos):
|
|
151
|
+
mock_def1 = Mock()
|
|
152
|
+
mock_def1.in_init = True
|
|
153
|
+
mock_def1.simple_name = "public_function"
|
|
154
|
+
mock_def1.is_exported = False
|
|
155
|
+
|
|
156
|
+
mock_def2 = Mock()
|
|
157
|
+
mock_def2.in_init = True
|
|
158
|
+
mock_def2.simple_name = "_private_function"
|
|
159
|
+
mock_def2.is_exported = False
|
|
160
|
+
|
|
161
|
+
skylos.defs = {
|
|
162
|
+
"module.public_function": mock_def1,
|
|
163
|
+
"module._private_function": mock_def2
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
skylos._mark_exports()
|
|
167
|
+
|
|
168
|
+
assert mock_def1.is_exported == True
|
|
169
|
+
assert mock_def2.is_exported == False
|
|
170
|
+
|
|
171
|
+
def test_mark_exports_explicit_exports(self, skylos):
|
|
172
|
+
mock_def = Mock()
|
|
173
|
+
mock_def.simple_name = "my_function"
|
|
174
|
+
mock_def.type = "function"
|
|
175
|
+
mock_def.is_exported = False
|
|
176
|
+
|
|
177
|
+
skylos.defs = {"module.my_function": mock_def}
|
|
178
|
+
skylos.exports = {"module": {"my_function"}}
|
|
179
|
+
|
|
180
|
+
skylos._mark_exports()
|
|
181
|
+
|
|
182
|
+
assert mock_def.is_exported == True
|
|
183
|
+
|
|
184
|
+
def test_mark_refs_direct_reference(self, skylos):
|
|
185
|
+
mock_def = Mock()
|
|
186
|
+
mock_def.references = 0
|
|
187
|
+
|
|
188
|
+
skylos.defs = {"module.function": mock_def}
|
|
189
|
+
skylos.refs = [("module.function", None)]
|
|
190
|
+
|
|
191
|
+
skylos._mark_refs()
|
|
192
|
+
|
|
193
|
+
assert mock_def.references == 1
|
|
194
|
+
|
|
195
|
+
def test_mark_refs_import_reference(self, skylos):
|
|
196
|
+
mock_import = Mock()
|
|
197
|
+
mock_import.type = "import"
|
|
198
|
+
mock_import.simple_name = "imported_func"
|
|
199
|
+
mock_import.references = 0
|
|
200
|
+
|
|
201
|
+
mock_original = Mock()
|
|
202
|
+
mock_original.type = "function"
|
|
203
|
+
mock_original.simple_name = "imported_func"
|
|
204
|
+
mock_original.references = 0
|
|
205
|
+
|
|
206
|
+
skylos.defs = {
|
|
207
|
+
"module.imported_func": mock_import,
|
|
208
|
+
"other_module.imported_func": mock_original
|
|
209
|
+
}
|
|
210
|
+
skylos.refs = [("module.imported_func", None)]
|
|
211
|
+
|
|
212
|
+
skylos._mark_refs()
|
|
213
|
+
|
|
214
|
+
assert mock_import.references == 1
|
|
215
|
+
assert mock_original.references == 1
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class TestHeuristics:
|
|
219
|
+
|
|
220
|
+
@pytest.fixture
|
|
221
|
+
def skylos_with_class_methods(self, mock_definition):
|
|
222
|
+
skylos = Skylos()
|
|
223
|
+
|
|
224
|
+
mock_class = mock_definition(
|
|
225
|
+
name="MyClass",
|
|
226
|
+
simple_name="MyClass",
|
|
227
|
+
type="class",
|
|
228
|
+
references=1
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
mock_init = mock_definition(
|
|
232
|
+
name="MyClass.__init__",
|
|
233
|
+
simple_name="__init__",
|
|
234
|
+
type="method",
|
|
235
|
+
references=0
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
mock_enter = mock_definition(
|
|
239
|
+
name="MyClass.__enter__",
|
|
240
|
+
simple_name="__enter__",
|
|
241
|
+
type="method",
|
|
242
|
+
references=0
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
skylos.defs = {
|
|
246
|
+
"MyClass": mock_class,
|
|
247
|
+
"MyClass.__init__": mock_init,
|
|
248
|
+
"MyClass.__enter__": mock_enter
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return skylos, mock_class, mock_init, mock_enter
|
|
252
|
+
|
|
253
|
+
def test_auto_called_methods_get_references(self, skylos_with_class_methods):
|
|
254
|
+
"""auto-called methods get reference counts when class is used."""
|
|
255
|
+
skylos, mock_class, mock_init, mock_enter = skylos_with_class_methods
|
|
256
|
+
|
|
257
|
+
skylos._apply_heuristics()
|
|
258
|
+
|
|
259
|
+
assert mock_init.references == 1
|
|
260
|
+
assert mock_enter.references == 1
|
|
261
|
+
|
|
262
|
+
def test_magic_methods_confidence_zero(self, mock_definition):
|
|
263
|
+
"""magic methods get confidence of 0."""
|
|
264
|
+
skylos = Skylos()
|
|
265
|
+
|
|
266
|
+
mock_magic = mock_definition(
|
|
267
|
+
name="MyClass.__str__",
|
|
268
|
+
simple_name="__str__",
|
|
269
|
+
type="method",
|
|
270
|
+
confidence=100
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
skylos.defs = {"MyClass.__str__": mock_magic}
|
|
274
|
+
skylos._apply_heuristics()
|
|
275
|
+
|
|
276
|
+
assert mock_magic.confidence == 0
|
|
277
|
+
|
|
278
|
+
def test_self_cls_parameters_confidence_zero(self, mock_definition):
|
|
279
|
+
"""self/cls parameters get confidence of 0"""
|
|
280
|
+
skylos = Skylos()
|
|
281
|
+
|
|
282
|
+
mock_self = mock_definition(
|
|
283
|
+
name="self",
|
|
284
|
+
simple_name="self",
|
|
285
|
+
type="parameter",
|
|
286
|
+
confidence=100
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
mock_cls = mock_definition(
|
|
290
|
+
name="cls",
|
|
291
|
+
simple_name="cls",
|
|
292
|
+
type="parameter",
|
|
293
|
+
confidence=100
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
skylos.defs = {"self": mock_self, "cls": mock_cls}
|
|
297
|
+
skylos._apply_heuristics()
|
|
298
|
+
|
|
299
|
+
assert mock_self.confidence == 0
|
|
300
|
+
assert mock_cls.confidence == 0
|
|
301
|
+
|
|
302
|
+
def test_test_methods_confidence_zero(self, mock_definition):
|
|
303
|
+
"""test methods in test classes get confidence of 0"""
|
|
304
|
+
skylos = Skylos()
|
|
305
|
+
|
|
306
|
+
mock_test_method = mock_definition(
|
|
307
|
+
name="TestMyClass.test_something",
|
|
308
|
+
simple_name="test_something",
|
|
309
|
+
type="method",
|
|
310
|
+
confidence=100
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
skylos.defs = {"TestMyClass.test_something": mock_test_method}
|
|
314
|
+
skylos._apply_heuristics()
|
|
315
|
+
|
|
316
|
+
assert mock_test_method.confidence == 0
|
|
317
|
+
|
|
318
|
+
def test_underscore_variable_confidence_zero(self, mock_definition):
|
|
319
|
+
"""underscore variables get confidence of 0."""
|
|
320
|
+
skylos = Skylos()
|
|
321
|
+
|
|
322
|
+
mock_underscore = mock_definition(
|
|
323
|
+
name="_",
|
|
324
|
+
simple_name="_",
|
|
325
|
+
type="variable",
|
|
326
|
+
confidence=100
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
skylos.defs = {"_": mock_underscore}
|
|
330
|
+
skylos._apply_heuristics()
|
|
331
|
+
|
|
332
|
+
assert mock_underscore.confidence == 0
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class TestAnalyze:
|
|
336
|
+
|
|
337
|
+
@pytest.fixture
|
|
338
|
+
def temp_python_project(self):
|
|
339
|
+
"""Create a temp Python project for testing"""
|
|
340
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
341
|
+
temp_path = Path(temp_dir)
|
|
342
|
+
|
|
343
|
+
main_py = temp_path / "main.py"
|
|
344
|
+
main_py.write_text("""
|
|
345
|
+
def used_function():
|
|
346
|
+
return "used"
|
|
347
|
+
|
|
348
|
+
def unused_function():
|
|
349
|
+
return "unused"
|
|
350
|
+
|
|
351
|
+
class UsedClass:
|
|
352
|
+
def method(self):
|
|
353
|
+
pass
|
|
354
|
+
|
|
355
|
+
class UnusedClass:
|
|
356
|
+
def method(self):
|
|
357
|
+
pass
|
|
358
|
+
|
|
359
|
+
result = used_function()
|
|
360
|
+
instance = UsedClass()
|
|
361
|
+
""")
|
|
362
|
+
|
|
363
|
+
package_dir = temp_path / "mypackage"
|
|
364
|
+
package_dir.mkdir()
|
|
365
|
+
|
|
366
|
+
init_py = package_dir / "__init__.py"
|
|
367
|
+
init_py.write_text("""
|
|
368
|
+
from .module import exported_function
|
|
369
|
+
|
|
370
|
+
def internal_function():
|
|
371
|
+
pass
|
|
372
|
+
""")
|
|
373
|
+
|
|
374
|
+
module_py = package_dir / "module.py"
|
|
375
|
+
module_py.write_text("""
|
|
376
|
+
def exported_function():
|
|
377
|
+
return "exported"
|
|
378
|
+
|
|
379
|
+
def internal_function():
|
|
380
|
+
return "internal"
|
|
381
|
+
""")
|
|
382
|
+
|
|
383
|
+
test_dir = temp_path / "__pycache__"
|
|
384
|
+
test_dir.mkdir()
|
|
385
|
+
|
|
386
|
+
test_file = test_dir / "cached.pyc"
|
|
387
|
+
test_file.write_text("# This should be excluded")
|
|
388
|
+
|
|
389
|
+
yield temp_path
|
|
390
|
+
|
|
391
|
+
@patch('skylos.analyzer.proc_file')
|
|
392
|
+
def test_analyze_basic(self, mock_proc_file, temp_python_project):
|
|
393
|
+
mock_def = Mock()
|
|
394
|
+
mock_def.name = "test.unused_function"
|
|
395
|
+
mock_def.references = 0
|
|
396
|
+
mock_def.is_exported = False
|
|
397
|
+
mock_def.confidence = 80
|
|
398
|
+
mock_def.type = "function"
|
|
399
|
+
mock_def.to_dict.return_value = {
|
|
400
|
+
"name": "test.unused_function",
|
|
401
|
+
"type": "function",
|
|
402
|
+
"file": "test.py",
|
|
403
|
+
"line": 1
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
mock_proc_file.return_value = ([mock_def], [], set(), set())
|
|
407
|
+
|
|
408
|
+
result_json = analyze(str(temp_python_project), conf=60)
|
|
409
|
+
result = json.loads(result_json)
|
|
410
|
+
|
|
411
|
+
assert "unused_functions" in result
|
|
412
|
+
assert "unused_imports" in result
|
|
413
|
+
assert "unused_classes" in result
|
|
414
|
+
assert "unused_variables" in result
|
|
415
|
+
assert "unused_parameters" in result
|
|
416
|
+
assert "analysis_summary" in result
|
|
417
|
+
|
|
418
|
+
def test_analyze_with_exclusions(self, temp_python_project):
|
|
419
|
+
"""analyze with folder exclusions."""
|
|
420
|
+
exclude_dir = temp_python_project / "build"
|
|
421
|
+
exclude_dir.mkdir()
|
|
422
|
+
exclude_file = exclude_dir / "generated.py"
|
|
423
|
+
exclude_file.write_text("def generated_function(): pass")
|
|
424
|
+
|
|
425
|
+
result_json = analyze(str(temp_python_project), exclude_folders=["build"]) # Use list instead of set
|
|
426
|
+
result = json.loads(result_json)
|
|
427
|
+
|
|
428
|
+
assert result["analysis_summary"]["excluded_folders"] == ["build"]
|
|
429
|
+
|
|
430
|
+
def test_analyze_empty_directory(self):
|
|
431
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
432
|
+
result_json = analyze(temp_dir, conf=60)
|
|
433
|
+
result = json.loads(result_json)
|
|
434
|
+
|
|
435
|
+
assert result["analysis_summary"]["total_files"] == 0
|
|
436
|
+
assert all(len(result[key]) == 0 for key in [
|
|
437
|
+
"unused_functions", "unused_imports", "unused_classes",
|
|
438
|
+
"unused_variables", "unused_parameters"
|
|
439
|
+
])
|
|
440
|
+
|
|
441
|
+
def test_confidence_threshold_filtering(self, mock_definition):
|
|
442
|
+
"""confidence threshold properly filters results."""
|
|
443
|
+
skylos = Skylos()
|
|
444
|
+
|
|
445
|
+
high_conf = mock_definition(
|
|
446
|
+
name="high_conf",
|
|
447
|
+
simple_name="high_conf",
|
|
448
|
+
type="function",
|
|
449
|
+
references=0,
|
|
450
|
+
is_exported=False,
|
|
451
|
+
confidence=80
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
low_conf = mock_definition(
|
|
455
|
+
name="low_conf",
|
|
456
|
+
simple_name="low_conf",
|
|
457
|
+
type="function",
|
|
458
|
+
references=0,
|
|
459
|
+
is_exported=False,
|
|
460
|
+
confidence=40
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
skylos.defs = {"high_conf": high_conf, "low_conf": low_conf}
|
|
464
|
+
|
|
465
|
+
with patch.object(skylos, '_get_python_files') as mock_get_files:
|
|
466
|
+
mock_get_files.return_value = ([Path("/fake/file.py")], Path("/"))
|
|
467
|
+
|
|
468
|
+
with patch('skylos.analyzer.proc_file') as mock_proc_file:
|
|
469
|
+
mock_proc_file.return_value = ([], [], set(), set())
|
|
470
|
+
|
|
471
|
+
result_json = skylos.analyze("/fake/path", thr=60)
|
|
472
|
+
result = json.loads(result_json)
|
|
473
|
+
|
|
474
|
+
# include only high confidence
|
|
475
|
+
assert len(result["unused_functions"]) == 1
|
|
476
|
+
assert result["unused_functions"][0]["name"] == "high_conf"
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
class TestProcFile:
|
|
480
|
+
|
|
481
|
+
def test_proc_file_with_valid_python(self):
|
|
482
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
|
|
483
|
+
f.write("""
|
|
484
|
+
def test_function():
|
|
485
|
+
pass
|
|
486
|
+
|
|
487
|
+
class TestClass:
|
|
488
|
+
def method(self):
|
|
489
|
+
pass
|
|
490
|
+
""")
|
|
491
|
+
f.flush()
|
|
492
|
+
|
|
493
|
+
try:
|
|
494
|
+
with patch('skylos.analyzer.Visitor') as mock_visitor_class:
|
|
495
|
+
mock_visitor = Mock()
|
|
496
|
+
mock_visitor.defs = []
|
|
497
|
+
mock_visitor.refs = []
|
|
498
|
+
mock_visitor.dyn = set()
|
|
499
|
+
mock_visitor.exports = set()
|
|
500
|
+
mock_visitor_class.return_value = mock_visitor
|
|
501
|
+
|
|
502
|
+
defs, refs, dyn, exports = proc_file(f.name, "test_module")
|
|
503
|
+
|
|
504
|
+
mock_visitor_class.assert_called_once_with("test_module", f.name)
|
|
505
|
+
mock_visitor.visit.assert_called_once()
|
|
506
|
+
|
|
507
|
+
assert defs == []
|
|
508
|
+
assert refs == []
|
|
509
|
+
assert dyn == set()
|
|
510
|
+
assert exports == set()
|
|
511
|
+
finally:
|
|
512
|
+
Path(f.name).unlink()
|
|
513
|
+
|
|
514
|
+
def test_proc_file_with_invalid_python(self):
|
|
515
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
|
|
516
|
+
f.write("def invalid_syntax(:\npass")
|
|
517
|
+
f.flush()
|
|
518
|
+
|
|
519
|
+
try:
|
|
520
|
+
defs, refs, dyn, exports = proc_file(f.name, "test_module")
|
|
521
|
+
|
|
522
|
+
assert defs == []
|
|
523
|
+
assert refs == []
|
|
524
|
+
assert dyn == set()
|
|
525
|
+
assert exports == set()
|
|
526
|
+
finally:
|
|
527
|
+
Path(f.name).unlink()
|
|
528
|
+
|
|
529
|
+
def test_proc_file_with_tuple_args(self):
|
|
530
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
|
|
531
|
+
f.write("def test(): pass")
|
|
532
|
+
f.flush()
|
|
533
|
+
|
|
534
|
+
try:
|
|
535
|
+
with patch('skylos.analyzer.Visitor') as mock_visitor_class:
|
|
536
|
+
mock_visitor = Mock()
|
|
537
|
+
mock_visitor.defs = []
|
|
538
|
+
mock_visitor.refs = []
|
|
539
|
+
mock_visitor.dyn = set()
|
|
540
|
+
mock_visitor.exports = set()
|
|
541
|
+
mock_visitor_class.return_value = mock_visitor
|
|
542
|
+
|
|
543
|
+
defs, refs, dyn, exports = proc_file((f.name, "test_module"))
|
|
544
|
+
|
|
545
|
+
mock_visitor_class.assert_called_once_with("test_module", f.name)
|
|
546
|
+
finally:
|
|
547
|
+
Path(f.name).unlink()
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
class TestConstants:
|
|
551
|
+
|
|
552
|
+
def test_auto_called_contains_expected_methods(self):
|
|
553
|
+
""" AUTO_CALLED contains expected magic methods."""
|
|
554
|
+
assert "__init__" in AUTO_CALLED
|
|
555
|
+
assert "__enter__" in AUTO_CALLED
|
|
556
|
+
assert "__exit__" in AUTO_CALLED
|
|
557
|
+
|
|
558
|
+
def test_magic_methods_contains_common_methods(self):
|
|
559
|
+
""" MAGIC_METHODS contains common magic methods."""
|
|
560
|
+
assert "__str__" in MAGIC_METHODS
|
|
561
|
+
assert "__repr__" in MAGIC_METHODS
|
|
562
|
+
assert "__eq__" in MAGIC_METHODS
|
|
563
|
+
assert "__len__" in MAGIC_METHODS
|
|
564
|
+
|
|
565
|
+
def test_test_method_pattern_matches_correctly(self):
|
|
566
|
+
""" TEST_METHOD_PATTERN matches test methods correctly."""
|
|
567
|
+
assert TEST_METHOD_PATTERN.match("test_something")
|
|
568
|
+
assert TEST_METHOD_PATTERN.match("test_another_thing")
|
|
569
|
+
assert TEST_METHOD_PATTERN.match("test_123")
|
|
570
|
+
assert not TEST_METHOD_PATTERN.match("not_a_test")
|
|
571
|
+
assert not TEST_METHOD_PATTERN.match("test") # no underscore
|
|
572
|
+
assert not TEST_METHOD_PATTERN.match("testing_something") # doesnt start with test_
|
|
573
|
+
|
|
574
|
+
def test_default_exclude_folders_contains_expected(self):
|
|
575
|
+
""" DEFAULT_EXCLUDE_FOLDERS contains expected directories."""
|
|
576
|
+
expected_folders = {
|
|
577
|
+
"__pycache__", ".git", ".pytest_cache", ".mypy_cache",
|
|
578
|
+
".tox", "htmlcov", ".coverage", "build", "dist",
|
|
579
|
+
"*.egg-info", "venv", ".venv"
|
|
580
|
+
}
|
|
581
|
+
assert expected_folders.issubset(DEFAULT_EXCLUDE_FOLDERS)
|
|
582
|
+
|
|
583
|
+
if __name__ == "__main__":
|
|
584
|
+
pytest.main([__file__, "-v"])
|