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.
- skylos/analyzer.py +93 -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.0.dist-info}/METADATA +1 -1
- skylos-1.2.0.dist-info/RECORD +32 -0
- test/test_analyzer.py +223 -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.0.dist-info}/WHEEL +0 -0
- {skylos-1.1.12.dist-info → skylos-1.2.0.dist-info}/entry_points.txt +0 -0
- {skylos-1.1.12.dist-info → skylos-1.2.0.dist-info}/top_level.txt +0 -0
skylos/visitor.py
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
import ast
|
|
3
3
|
from pathlib import Path
|
|
4
|
+
import os
|
|
5
|
+
import re, logging, traceback
|
|
6
|
+
DBG = bool(int(os.getenv("SKYLOS_DEBUG", "0")))
|
|
7
|
+
log = logging.getLogger("Skylos")
|
|
8
|
+
|
|
4
9
|
|
|
5
10
|
PYTHON_BUILTINS={"print","len","str","int","float","list","dict","set","tuple","range","open","super","object","type","enumerate","zip","map","filter","sorted","reversed","sum","min","max","all","any","next","iter","repr","chr","ord","bytes","bytearray","memoryview","format","round","abs","pow","divmod","complex","hash","id","bool","callable","getattr","setattr","delattr","hasattr","isinstance","issubclass","globals","locals","vars","dir","property","classmethod","staticmethod"}
|
|
6
11
|
DYNAMIC_PATTERNS={"getattr","globals","eval","exec"}
|
|
@@ -74,11 +79,23 @@ class Visitor(ast.NodeVisitor):
|
|
|
74
79
|
self.visit(node)
|
|
75
80
|
|
|
76
81
|
def visit_string_annotation(self, annotation_str):
|
|
82
|
+
if not isinstance(annotation_str, str):
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
if DBG:
|
|
86
|
+
log.debug(f"[visitor] parsing string annotation {annotation_str!r} "
|
|
87
|
+
f"in {self.filename}:{getattr(self, 'line', '?')}")
|
|
88
|
+
|
|
77
89
|
try:
|
|
78
|
-
parsed = ast.parse(annotation_str, mode=
|
|
90
|
+
parsed = ast.parse(annotation_str, mode="eval")
|
|
79
91
|
self.visit(parsed.body)
|
|
80
|
-
except:
|
|
81
|
-
|
|
92
|
+
except Exception:
|
|
93
|
+
if DBG:
|
|
94
|
+
log.debug("[visitor] inner-annotation parse failed:\n" +
|
|
95
|
+
traceback.format_exc())
|
|
96
|
+
# keep going but dont swallow symbol names:
|
|
97
|
+
for tok in re.findall(r"[A-Za-z_][A-Za-z0-9_]*", annotation_str):
|
|
98
|
+
self.add_ref(tok)
|
|
82
99
|
|
|
83
100
|
def visit_Import(self,node):
|
|
84
101
|
for a in node.names:
|
|
@@ -250,6 +267,11 @@ class Visitor(ast.NodeVisitor):
|
|
|
250
267
|
if module_name != "self":
|
|
251
268
|
qualified_name = f"{self.qual(module_name)}.{attr_name}"
|
|
252
269
|
self.add_ref(qualified_name)
|
|
270
|
+
|
|
271
|
+
elif isinstance(node.args[0], ast.Name):
|
|
272
|
+
target_name = node.args[0].id
|
|
273
|
+
if target_name != "self":
|
|
274
|
+
self.dyn.add(self.mod.split(".")[0] if self.mod else "")
|
|
253
275
|
|
|
254
276
|
elif isinstance(node.func, ast.Name) and node.func.id == "globals":
|
|
255
277
|
parent = getattr(node, 'parent', None)
|
|
@@ -259,6 +281,9 @@ class Visitor(ast.NodeVisitor):
|
|
|
259
281
|
func_name = parent.slice.value
|
|
260
282
|
self.add_ref(func_name)
|
|
261
283
|
self.add_ref(f"{self.mod}.{func_name}")
|
|
284
|
+
|
|
285
|
+
elif isinstance(node.func, ast.Name) and node.func.id in ("eval", "exec"):
|
|
286
|
+
self.dyn.add(self.mod.split(".")[0] if self.mod else "")
|
|
262
287
|
|
|
263
288
|
def visit_Name(self,node):
|
|
264
289
|
if isinstance(node.ctx,ast.Load):
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
skylos/__init__.py,sha256=ZZWhq0TZ3G-yDi9inbYvMn8OBes-pqo1aYB5EivuxFI,152
|
|
2
|
+
skylos/analyzer.py,sha256=MVO16T58bPpcISQKiHWtcQaeHG6r2awH6IuxHUmG56E,13858
|
|
3
|
+
skylos/cli.py,sha256=6udZY4vLU6PFzVMkaiCLCRcLXgquyHdmf4rIOAPosWc,18266
|
|
4
|
+
skylos/constants.py,sha256=F1kMjuTxfw2hJjd0SeOQcgex5WhHMUhXCzOlVmwuACs,1230
|
|
5
|
+
skylos/framework_aware.py,sha256=p7BGoFnzkpaLJoE3M5qgyIeZvXx17tOkdyXgeqGKmqU,5804
|
|
6
|
+
skylos/test_aware.py,sha256=cduaWMcFsuzIEQWAMFPC58xGk8NUU3SbUS0ChRPedv8,2372
|
|
7
|
+
skylos/visitor.py,sha256=MnWyzs0b2JOD2Nj1Iu7ZIZHCr7fRC92PY1bsT8bdgXg,12796
|
|
8
|
+
test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
test/compare_tools.py,sha256=0g9PDeJlbst-7hOaQzrL4MiJFQKpqM8q8VeBGzpPczg,22738
|
|
10
|
+
test/conftest.py,sha256=57sTF6vLL5U0CVKwGQFJcRs6n7t1dEHIriQoSluNmAI,6418
|
|
11
|
+
test/diagnostics.py,sha256=ExuFOCVpc9BDwNYapU96vj9RXLqxji32Sv6wVF4nJYU,13802
|
|
12
|
+
test/test_analyzer.py,sha256=c8gbZuUls3N8xGGDXdPzsaoma6AFR2fIDqp9cNJlg7c,22104
|
|
13
|
+
test/test_changes_analyzer.py,sha256=l1hspCFz-sF8gqOilJvntUDuGckwhYsVtzvSRB13ZWw,5085
|
|
14
|
+
test/test_cli.py,sha256=rtdKzanDRJT_F92jKkCQFdhvlfwVJxfXKO8Hrbn-mIg,13180
|
|
15
|
+
test/test_constants.py,sha256=pMuDy0UpC81zENMDCeK6Bqmm3BR_HHZQSlMG-9TgOm0,12602
|
|
16
|
+
test/test_framework_aware.py,sha256=tJ7bnhiGeSdsAvrWaGJO5ovTrw9n9BBk6o1HCw15yDA,11693
|
|
17
|
+
test/test_integration.py,sha256=bNKGUe-w0xEZEdnoQNHbssvKMGs9u9fmFQTOz1lX9_k,12398
|
|
18
|
+
test/test_skylos.py,sha256=kz77STrS4k3Eez5RDYwGxOg2WH3e7zNZPUYEaTLbGTs,15608
|
|
19
|
+
test/test_test_aware.py,sha256=VmbR_MQY0m941CAxxux8OxJHIr7l8crfWRouSeBMhIo,9390
|
|
20
|
+
test/test_visitor.py,sha256=xAbGv-XaozKm_0WJJhr0hMb6mLaJcbPz57G9-SWkxFU,22764
|
|
21
|
+
test/sample_repo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
+
test/sample_repo/app.py,sha256=M5XgoAn-LPz50mKAj_ZacRKf-Pg7I4HbjWP7Z9jE4a0,226
|
|
23
|
+
test/sample_repo/sample_repo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
+
test/sample_repo/sample_repo/commands.py,sha256=b6gQ9YDabt2yyfqGbOpLo0osF7wya8O4Lm7m8gtCr3g,2575
|
|
25
|
+
test/sample_repo/sample_repo/models.py,sha256=xXIg3pToEZwKuUCmKX2vTlCF_VeFA0yZlvlBVPIy5Qw,3320
|
|
26
|
+
test/sample_repo/sample_repo/routes.py,sha256=8yITrt55BwS01G7nWdESdx8LuxmReqop1zrGUKPeLi8,2475
|
|
27
|
+
test/sample_repo/sample_repo/utils.py,sha256=S56hEYh8wkzwsD260MvQcmUFOkw2EjFU27nMLFE6G2k,1103
|
|
28
|
+
skylos-1.2.0.dist-info/METADATA,sha256=FqeObdsFPYrqrKzpBngriBQbKZXnOJUnqDKJQSn-N4k,224
|
|
29
|
+
skylos-1.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
30
|
+
skylos-1.2.0.dist-info/entry_points.txt,sha256=zzRpN2ByznlQoLeuLolS_TFNYSQxUGBL1EXQsAd6bIA,43
|
|
31
|
+
skylos-1.2.0.dist-info/top_level.txt,sha256=f8GA_7KwfaEopPMP8-EXDQXaqd4IbsOQPakZy01LkdQ,12
|
|
32
|
+
skylos-1.2.0.dist-info/RECORD,,
|
test/test_analyzer.py
CHANGED
|
@@ -1,36 +1,101 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
1
|
import pytest
|
|
3
2
|
import json
|
|
4
3
|
import tempfile
|
|
5
4
|
from pathlib import Path
|
|
6
5
|
from unittest.mock import Mock, patch
|
|
7
6
|
from collections import defaultdict
|
|
7
|
+
from skylos.test_aware import TestAwareVisitor
|
|
8
|
+
from skylos.framework_aware import FrameworkAwareVisitor
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
10
|
+
from skylos.analyzer import (
|
|
11
|
+
Skylos,
|
|
12
|
+
parse_exclude_folders,
|
|
13
|
+
proc_file,
|
|
14
|
+
analyze,
|
|
15
|
+
DEFAULT_EXCLUDE_FOLDERS,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
@pytest.fixture
|
|
19
|
+
def mock_definition():
|
|
20
|
+
def _create_mock_def(name, simple_name, type, references=0, is_exported=False, confidence=100, in_init=False, line=1):
|
|
21
|
+
mock = Mock()
|
|
22
|
+
mock.name = name
|
|
23
|
+
mock.simple_name = simple_name
|
|
24
|
+
mock.type = type
|
|
25
|
+
mock.references = references
|
|
26
|
+
mock.is_exported = is_exported
|
|
27
|
+
mock.confidence = confidence
|
|
28
|
+
mock.in_init = in_init
|
|
29
|
+
mock.line = line
|
|
30
|
+
mock.to_dict.return_value = {
|
|
31
|
+
"name": name,
|
|
32
|
+
"type": type,
|
|
33
|
+
"file": "test.py",
|
|
34
|
+
"line": line
|
|
35
|
+
}
|
|
36
|
+
return mock
|
|
37
|
+
return _create_mock_def
|
|
38
|
+
|
|
39
|
+
@pytest.fixture
|
|
40
|
+
def mock_test_aware_visitor():
|
|
41
|
+
mock = Mock(spec=TestAwareVisitor)
|
|
42
|
+
mock.is_test_file = False
|
|
43
|
+
mock.test_decorated_lines = set()
|
|
44
|
+
return mock
|
|
45
|
+
|
|
46
|
+
@pytest.fixture
|
|
47
|
+
def mock_framework_aware_visitor():
|
|
48
|
+
mock = Mock(spec=FrameworkAwareVisitor)
|
|
49
|
+
mock.framework_decorated_lines = set()
|
|
50
|
+
return mock
|
|
51
|
+
|
|
52
|
+
@pytest.fixture
|
|
53
|
+
def temp_python_project():
|
|
54
|
+
"""Create a temp Python project for testing"""
|
|
55
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
56
|
+
temp_path = Path(temp_dir)
|
|
57
|
+
|
|
58
|
+
main_py = temp_path / "main.py"
|
|
59
|
+
main_py.write_text("""
|
|
60
|
+
def used_function():
|
|
61
|
+
return "used"
|
|
62
|
+
|
|
63
|
+
def unused_function():
|
|
64
|
+
return "unused"
|
|
65
|
+
|
|
66
|
+
class UsedClass:
|
|
67
|
+
def method(self):
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
class UnusedClass:
|
|
71
|
+
def method(self):
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
result = used_function()
|
|
75
|
+
instance = UsedClass()
|
|
76
|
+
""")
|
|
77
|
+
|
|
78
|
+
package_dir = temp_path / "mypackage"
|
|
79
|
+
package_dir.mkdir()
|
|
80
|
+
|
|
81
|
+
init_py = package_dir / "__init__.py"
|
|
82
|
+
init_py.write_text("""
|
|
83
|
+
from .module import exported_function
|
|
84
|
+
|
|
85
|
+
def internal_function():
|
|
86
|
+
pass
|
|
87
|
+
""")
|
|
88
|
+
|
|
89
|
+
module_py = package_dir / "module.py"
|
|
90
|
+
module_py.write_text("""
|
|
91
|
+
def exported_function():
|
|
92
|
+
return "exported"
|
|
93
|
+
|
|
94
|
+
def internal_function():
|
|
95
|
+
return "internal"
|
|
96
|
+
""")
|
|
97
|
+
|
|
98
|
+
yield temp_path
|
|
34
99
|
|
|
35
100
|
class TestParseExcludeFolders:
|
|
36
101
|
|
|
@@ -66,7 +131,6 @@ class TestParseExcludeFolders:
|
|
|
66
131
|
assert "custom_folder" not in result
|
|
67
132
|
assert "another_folder" in result
|
|
68
133
|
|
|
69
|
-
|
|
70
134
|
class TestSkylos:
|
|
71
135
|
|
|
72
136
|
@pytest.fixture
|
|
@@ -214,7 +278,6 @@ class TestSkylos:
|
|
|
214
278
|
assert mock_import.references == 1
|
|
215
279
|
assert mock_original.references == 1
|
|
216
280
|
|
|
217
|
-
|
|
218
281
|
class TestHeuristics:
|
|
219
282
|
|
|
220
283
|
@pytest.fixture
|
|
@@ -258,136 +321,9 @@ class TestHeuristics:
|
|
|
258
321
|
|
|
259
322
|
assert mock_init.references == 1
|
|
260
323
|
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
324
|
|
|
335
325
|
class TestAnalyze:
|
|
336
326
|
|
|
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
327
|
@patch('skylos.analyzer.proc_file')
|
|
392
328
|
def test_analyze_basic(self, mock_proc_file, temp_python_project):
|
|
393
329
|
mock_def = Mock()
|
|
@@ -398,18 +334,26 @@ def internal_function():
|
|
|
398
334
|
mock_def.type = "function"
|
|
399
335
|
mock_def.to_dict.return_value = {
|
|
400
336
|
"name": "test.unused_function",
|
|
401
|
-
"type": "function",
|
|
337
|
+
"type": "function",
|
|
402
338
|
"file": "test.py",
|
|
403
339
|
"line": 1
|
|
404
340
|
}
|
|
405
341
|
|
|
406
|
-
|
|
342
|
+
mock_test_visitor = Mock(spec=TestAwareVisitor)
|
|
343
|
+
mock_test_visitor.is_test_file = False
|
|
344
|
+
mock_test_visitor.test_decorated_lines = set()
|
|
345
|
+
|
|
346
|
+
mock_framework_visitor = Mock(spec=FrameworkAwareVisitor)
|
|
347
|
+
mock_framework_visitor.framework_decorated_lines = set()
|
|
348
|
+
mock_framework_visitor.is_framework_file = False
|
|
349
|
+
|
|
350
|
+
mock_proc_file.return_value = ([mock_def], [], set(), set(), mock_test_visitor, mock_framework_visitor)
|
|
407
351
|
|
|
408
352
|
result_json = analyze(str(temp_python_project), conf=60)
|
|
409
353
|
result = json.loads(result_json)
|
|
410
354
|
|
|
411
355
|
assert "unused_functions" in result
|
|
412
|
-
assert "unused_imports" in result
|
|
356
|
+
assert "unused_imports" in result
|
|
413
357
|
assert "unused_classes" in result
|
|
414
358
|
assert "unused_variables" in result
|
|
415
359
|
assert "unused_parameters" in result
|
|
@@ -422,7 +366,7 @@ def internal_function():
|
|
|
422
366
|
exclude_file = exclude_dir / "generated.py"
|
|
423
367
|
exclude_file.write_text("def generated_function(): pass")
|
|
424
368
|
|
|
425
|
-
result_json = analyze(str(temp_python_project), exclude_folders=["build"])
|
|
369
|
+
result_json = analyze(str(temp_python_project), exclude_folders=["build"])
|
|
426
370
|
result = json.loads(result_json)
|
|
427
371
|
|
|
428
372
|
assert result["analysis_summary"]["excluded_folders"] == ["build"]
|
|
@@ -466,7 +410,7 @@ def internal_function():
|
|
|
466
410
|
mock_get_files.return_value = ([Path("/fake/file.py")], Path("/"))
|
|
467
411
|
|
|
468
412
|
with patch('skylos.analyzer.proc_file') as mock_proc_file:
|
|
469
|
-
mock_proc_file.return_value = ([], [], set(), set())
|
|
413
|
+
mock_proc_file.return_value = ([], [], set(), set(), Mock(spec=TestAwareVisitor), Mock(spec=FrameworkAwareVisitor))
|
|
470
414
|
|
|
471
415
|
result_json = skylos.analyze("/fake/path", thr=60)
|
|
472
416
|
result = json.loads(result_json)
|
|
@@ -475,7 +419,6 @@ def internal_function():
|
|
|
475
419
|
assert len(result["unused_functions"]) == 1
|
|
476
420
|
assert result["unused_functions"][0]["name"] == "high_conf"
|
|
477
421
|
|
|
478
|
-
|
|
479
422
|
class TestProcFile:
|
|
480
423
|
|
|
481
424
|
def test_proc_file_with_valid_python(self):
|
|
@@ -491,7 +434,10 @@ class TestClass:
|
|
|
491
434
|
f.flush()
|
|
492
435
|
|
|
493
436
|
try:
|
|
494
|
-
with patch('skylos.analyzer.Visitor') as mock_visitor_class
|
|
437
|
+
with patch('skylos.analyzer.Visitor') as mock_visitor_class, \
|
|
438
|
+
patch('skylos.analyzer.TestAwareVisitor') as mock_test_visitor_class, \
|
|
439
|
+
patch('skylos.analyzer.FrameworkAwareVisitor') as mock_framework_visitor_class:
|
|
440
|
+
|
|
495
441
|
mock_visitor = Mock()
|
|
496
442
|
mock_visitor.defs = []
|
|
497
443
|
mock_visitor.refs = []
|
|
@@ -499,7 +445,13 @@ class TestClass:
|
|
|
499
445
|
mock_visitor.exports = set()
|
|
500
446
|
mock_visitor_class.return_value = mock_visitor
|
|
501
447
|
|
|
502
|
-
|
|
448
|
+
mock_test_visitor = Mock(spec=TestAwareVisitor)
|
|
449
|
+
mock_test_visitor_class.return_value = mock_test_visitor
|
|
450
|
+
|
|
451
|
+
mock_framework_visitor = Mock(spec=FrameworkAwareVisitor)
|
|
452
|
+
mock_framework_visitor_class.return_value = mock_framework_visitor
|
|
453
|
+
|
|
454
|
+
defs, refs, dyn, exports, test_flags, framework_flags = proc_file(f.name, "test_module")
|
|
503
455
|
|
|
504
456
|
mock_visitor_class.assert_called_once_with("test_module", f.name)
|
|
505
457
|
mock_visitor.visit.assert_called_once()
|
|
@@ -507,7 +459,10 @@ class TestClass:
|
|
|
507
459
|
assert defs == []
|
|
508
460
|
assert refs == []
|
|
509
461
|
assert dyn == set()
|
|
462
|
+
## added new test here for the new methods
|
|
510
463
|
assert exports == set()
|
|
464
|
+
assert test_flags == mock_test_visitor
|
|
465
|
+
assert framework_flags == mock_framework_visitor
|
|
511
466
|
finally:
|
|
512
467
|
Path(f.name).unlink()
|
|
513
468
|
|
|
@@ -517,12 +472,14 @@ class TestClass:
|
|
|
517
472
|
f.flush()
|
|
518
473
|
|
|
519
474
|
try:
|
|
520
|
-
defs, refs, dyn, exports = proc_file(f.name, "test_module")
|
|
475
|
+
defs, refs, dyn, exports, test_flags, framework_flags = proc_file(f.name, "test_module")
|
|
521
476
|
|
|
522
477
|
assert defs == []
|
|
523
478
|
assert refs == []
|
|
524
479
|
assert dyn == set()
|
|
525
480
|
assert exports == set()
|
|
481
|
+
assert isinstance(test_flags, TestAwareVisitor)
|
|
482
|
+
assert isinstance(framework_flags, FrameworkAwareVisitor)
|
|
526
483
|
finally:
|
|
527
484
|
Path(f.name).unlink()
|
|
528
485
|
|
|
@@ -532,7 +489,10 @@ class TestClass:
|
|
|
532
489
|
f.flush()
|
|
533
490
|
|
|
534
491
|
try:
|
|
535
|
-
with patch('skylos.analyzer.Visitor') as mock_visitor_class
|
|
492
|
+
with patch('skylos.analyzer.Visitor') as mock_visitor_class, \
|
|
493
|
+
patch('skylos.analyzer.TestAwareVisitor') as mock_test_visitor_class, \
|
|
494
|
+
patch('skylos.analyzer.FrameworkAwareVisitor') as mock_framework_visitor_class:
|
|
495
|
+
|
|
536
496
|
mock_visitor = Mock()
|
|
537
497
|
mock_visitor.defs = []
|
|
538
498
|
mock_visitor.refs = []
|
|
@@ -540,45 +500,113 @@ class TestClass:
|
|
|
540
500
|
mock_visitor.exports = set()
|
|
541
501
|
mock_visitor_class.return_value = mock_visitor
|
|
542
502
|
|
|
543
|
-
|
|
503
|
+
mock_test_visitor = Mock(spec=TestAwareVisitor)
|
|
504
|
+
mock_test_visitor_class.return_value = mock_test_visitor
|
|
505
|
+
|
|
506
|
+
mock_framework_visitor = Mock(spec=FrameworkAwareVisitor)
|
|
507
|
+
mock_framework_visitor_class.return_value = mock_framework_visitor
|
|
508
|
+
|
|
509
|
+
# defs, refs, dyn, exports, test_flags, framework_flags = proc_file((f.name, "test_module"))
|
|
544
510
|
|
|
545
511
|
mock_visitor_class.assert_called_once_with("test_module", f.name)
|
|
546
512
|
finally:
|
|
547
513
|
Path(f.name).unlink()
|
|
548
514
|
|
|
549
|
-
|
|
550
|
-
class TestConstants:
|
|
515
|
+
class TestApplyPenalties:
|
|
551
516
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
517
|
+
@patch('skylos.analyzer.detect_framework_usage')
|
|
518
|
+
def test_private_name_penalty(self, mock_detect_framework, mock_definition, mock_test_aware_visitor, mock_framework_aware_visitor):
|
|
519
|
+
"""private names get penalized."""
|
|
520
|
+
mock_detect_framework.return_value = None # or whatever confidence value here that can change later on
|
|
521
|
+
|
|
522
|
+
skylos = Skylos()
|
|
523
|
+
mock_def = mock_definition(
|
|
524
|
+
name="_private_func",
|
|
525
|
+
simple_name="_private_func",
|
|
526
|
+
type="function",
|
|
527
|
+
confidence=100
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
skylos._apply_penalties(mock_def, mock_test_aware_visitor, mock_framework_aware_visitor)
|
|
531
|
+
assert mock_def.confidence < 100
|
|
532
|
+
|
|
533
|
+
@patch('skylos.analyzer.detect_framework_usage')
|
|
534
|
+
def test_magic_methods_confidence_zero(self, mock_detect_framework, mock_definition, mock_test_aware_visitor, mock_framework_aware_visitor):
|
|
535
|
+
"""magic methods get confidence of 0."""
|
|
536
|
+
mock_detect_framework.return_value = None
|
|
537
|
+
skylos = Skylos()
|
|
538
|
+
mock_def = mock_definition(
|
|
539
|
+
name="MyClass.__str__",
|
|
540
|
+
simple_name="__str__",
|
|
541
|
+
type="method",
|
|
542
|
+
confidence=100
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
skylos._apply_penalties(mock_def, mock_test_aware_visitor, mock_framework_aware_visitor)
|
|
546
|
+
assert mock_def.confidence == 0
|
|
557
547
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
548
|
+
@patch('skylos.analyzer.detect_framework_usage')
|
|
549
|
+
def test_self_cls_parameters_confidence_zero(self, mock_detect_framework, mock_definition, mock_test_aware_visitor, mock_framework_aware_visitor):
|
|
550
|
+
mock_detect_framework.return_value = None
|
|
551
|
+
skylos = Skylos()
|
|
552
|
+
|
|
553
|
+
mock_self = mock_definition(
|
|
554
|
+
name="MyClass.method.self",
|
|
555
|
+
simple_name="self",
|
|
556
|
+
type="parameter",
|
|
557
|
+
confidence=100
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
mock_cls = mock_definition(
|
|
561
|
+
name="MyClass.classmethod.cls",
|
|
562
|
+
simple_name="cls",
|
|
563
|
+
type="parameter",
|
|
564
|
+
confidence=100
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
skylos._apply_penalties(mock_self, mock_test_aware_visitor, mock_framework_aware_visitor)
|
|
568
|
+
skylos._apply_penalties(mock_cls, mock_test_aware_visitor, mock_framework_aware_visitor)
|
|
569
|
+
|
|
570
|
+
assert mock_self.confidence == 0
|
|
571
|
+
assert mock_cls.confidence == 0
|
|
564
572
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
+
@patch('skylos.analyzer.detect_framework_usage')
|
|
574
|
+
def test_test_methods_confidence_zero(self, mock_detect_framework, mock_definition, mock_framework_aware_visitor):
|
|
575
|
+
"""test methods get confidence of 0"""
|
|
576
|
+
mock_detect_framework.return_value = None
|
|
577
|
+
|
|
578
|
+
skylos = Skylos()
|
|
579
|
+
|
|
580
|
+
test_visitor = Mock(spec=TestAwareVisitor)
|
|
581
|
+
test_visitor.is_test_file = True
|
|
582
|
+
test_visitor.test_decorated_lines = set()
|
|
583
|
+
|
|
584
|
+
mock_def = mock_definition(
|
|
585
|
+
name="TestMyClass.test_something",
|
|
586
|
+
simple_name="test_something",
|
|
587
|
+
type="method",
|
|
588
|
+
confidence=100
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
skylos._apply_penalties(mock_def, test_visitor, mock_framework_aware_visitor)
|
|
592
|
+
assert mock_def.confidence == 0
|
|
573
593
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
594
|
+
@patch('skylos.analyzer.detect_framework_usage')
|
|
595
|
+
def test_underscore_variable_confidence_zero(self, mock_detect_framework, mock_definition, mock_test_aware_visitor, mock_framework_aware_visitor):
|
|
596
|
+
"""underscore variables get confidence of 0."""
|
|
597
|
+
mock_detect_framework.return_value = None
|
|
598
|
+
|
|
599
|
+
skylos = Skylos()
|
|
600
|
+
|
|
601
|
+
mock_def = mock_definition(
|
|
602
|
+
name="_",
|
|
603
|
+
simple_name="_",
|
|
604
|
+
type="variable",
|
|
605
|
+
confidence=100
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
skylos._apply_penalties(mock_def, mock_test_aware_visitor, mock_framework_aware_visitor)
|
|
609
|
+
assert mock_def.confidence == 0
|
|
582
610
|
|
|
583
611
|
if __name__ == "__main__":
|
|
584
612
|
pytest.main([__file__, "-v"])
|