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_visitor.py
CHANGED
|
@@ -52,6 +52,15 @@ class TestDefinition(unittest.TestCase):
|
|
|
52
52
|
self.assertEqual(result["full_name"], "mymodule.MyClass.my_method")
|
|
53
53
|
self.assertEqual(result["simple_name"], "my_method")
|
|
54
54
|
|
|
55
|
+
def test_definition_to_dict_method_deep_nesting(self):
|
|
56
|
+
"""Test to_dict method for deeply nested methods."""
|
|
57
|
+
definition = Definition("mymodule.OuterClass.InnerClass.deep_method", "method", "test.py", 20)
|
|
58
|
+
result = definition.to_dict()
|
|
59
|
+
|
|
60
|
+
self.assertEqual(result["name"], "InnerClass.deep_method")
|
|
61
|
+
self.assertEqual(result["full_name"], "mymodule.OuterClass.InnerClass.deep_method")
|
|
62
|
+
self.assertEqual(result["simple_name"], "deep_method")
|
|
63
|
+
|
|
55
64
|
def test_init_file_detection(self):
|
|
56
65
|
"""Test detection of __init__.py files."""
|
|
57
66
|
definition = Definition("pkg.func", "function", "/path/to/__init__.py", 1)
|
|
@@ -59,6 +68,13 @@ class TestDefinition(unittest.TestCase):
|
|
|
59
68
|
|
|
60
69
|
definition2 = Definition("pkg.func", "function", "/path/to/module.py", 1)
|
|
61
70
|
self.assertFalse(definition2.in_init)
|
|
71
|
+
|
|
72
|
+
def test_definition_types(self):
|
|
73
|
+
"""Test all definition types."""
|
|
74
|
+
types = ["function", "method", "class", "variable", "parameter", "import"]
|
|
75
|
+
for def_type in types:
|
|
76
|
+
definition = Definition(f"test.{def_type}", def_type, "test.py", 1)
|
|
77
|
+
self.assertEqual(definition.type, def_type)
|
|
62
78
|
|
|
63
79
|
class TestVisitor(unittest.TestCase):
|
|
64
80
|
"""Test the Visitor class."""
|
|
@@ -91,46 +107,107 @@ def my_function():
|
|
|
91
107
|
self.assertEqual(definition.type, "function")
|
|
92
108
|
self.assertEqual(definition.simple_name, "my_function")
|
|
93
109
|
|
|
110
|
+
def test_async_function(self):
|
|
111
|
+
"""Test detection of async function definitions."""
|
|
112
|
+
code = """
|
|
113
|
+
async def async_function():
|
|
114
|
+
await some_call()
|
|
115
|
+
"""
|
|
116
|
+
visitor = self.parse_and_visit(code)
|
|
117
|
+
|
|
118
|
+
self.assertEqual(len(visitor.defs), 1)
|
|
119
|
+
definition = visitor.defs[0]
|
|
120
|
+
self.assertEqual(definition.type, "function")
|
|
121
|
+
self.assertEqual(definition.simple_name, "async_function")
|
|
122
|
+
|
|
94
123
|
def test_class_with_methods(self):
|
|
95
124
|
"""Test detection of classes and methods."""
|
|
96
125
|
code = """
|
|
97
126
|
class MyClass:
|
|
98
127
|
def __init__(self):
|
|
99
128
|
pass
|
|
100
|
-
|
|
129
|
+
|
|
101
130
|
def method(self):
|
|
102
131
|
pass
|
|
132
|
+
|
|
133
|
+
@staticmethod
|
|
134
|
+
def static_method():
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
@classmethod
|
|
138
|
+
def class_method(cls):
|
|
139
|
+
pass
|
|
103
140
|
"""
|
|
104
141
|
visitor = self.parse_and_visit(code)
|
|
105
142
|
|
|
106
|
-
|
|
107
|
-
|
|
143
|
+
print(f"DEBUG: Found {len(visitor.defs)} definitions:")
|
|
144
|
+
for d in visitor.defs:
|
|
145
|
+
print(f" {d.type}: {d.name}")
|
|
108
146
|
|
|
109
|
-
|
|
110
|
-
|
|
147
|
+
class_defs = [d for d in visitor.defs if d.type == "class"]
|
|
148
|
+
method_defs = [d for d in visitor.defs if d.type == "method"]
|
|
149
|
+
param_defs = [d for d in visitor.defs if d.type == "parameter"]
|
|
111
150
|
|
|
112
|
-
|
|
113
|
-
self.assertEqual(
|
|
114
|
-
|
|
115
|
-
self.assertEqual(
|
|
151
|
+
self.assertEqual(len(class_defs), 1)
|
|
152
|
+
self.assertEqual(class_defs[0].simple_name, "MyClass")
|
|
153
|
+
|
|
154
|
+
self.assertEqual(len(method_defs), 4)
|
|
155
|
+
method_names = {m.simple_name for m in method_defs}
|
|
156
|
+
self.assertEqual(method_names, {"__init__", "method", "static_method", "class_method"})
|
|
157
|
+
|
|
158
|
+
# static_method has no params, so only 3 total: self (2x) + cls (1x)
|
|
159
|
+
self.assertTrue(len(param_defs) >= 3)
|
|
116
160
|
|
|
117
|
-
def
|
|
118
|
-
"""Test import statement detection."""
|
|
161
|
+
def test_imports_basic(self):
|
|
119
162
|
code = """
|
|
120
163
|
import os
|
|
121
164
|
import sys as system
|
|
165
|
+
"""
|
|
166
|
+
visitor = self.parse_and_visit(code)
|
|
167
|
+
|
|
168
|
+
imports = [d for d in visitor.defs if d.type == "import"]
|
|
169
|
+
self.assertEqual(len(imports), 2)
|
|
170
|
+
|
|
171
|
+
self.assertEqual(visitor.alias["os"], "os")
|
|
172
|
+
self.assertEqual(visitor.alias["system"], "sys")
|
|
173
|
+
|
|
174
|
+
def test_imports_from(self):
|
|
175
|
+
"""Test from-import statement detection."""
|
|
176
|
+
code = """
|
|
122
177
|
from pathlib import Path
|
|
123
178
|
from collections import defaultdict, Counter
|
|
179
|
+
from os.path import join as path_join
|
|
124
180
|
"""
|
|
125
181
|
visitor = self.parse_and_visit(code)
|
|
126
182
|
|
|
127
183
|
imports = [d for d in visitor.defs if d.type == "import"]
|
|
128
184
|
self.assertTrue(len(imports) >= 4)
|
|
129
185
|
|
|
130
|
-
self.assertEqual(visitor.alias["system"], "sys")
|
|
131
186
|
self.assertEqual(visitor.alias["Path"], "pathlib.Path")
|
|
132
187
|
self.assertEqual(visitor.alias["defaultdict"], "collections.defaultdict")
|
|
133
|
-
|
|
188
|
+
self.assertEqual(visitor.alias["Counter"], "collections.Counter")
|
|
189
|
+
self.assertEqual(visitor.alias["path_join"], "os.path.join")
|
|
190
|
+
|
|
191
|
+
def test_relative_imports(self):
|
|
192
|
+
"""Test relative import detection."""
|
|
193
|
+
code = """
|
|
194
|
+
from . import sibling_module
|
|
195
|
+
from ..parent import parent_function
|
|
196
|
+
from ...grandparent.utils import helper
|
|
197
|
+
"""
|
|
198
|
+
visitor = Visitor("package.subpackage.module", self.temp_file.name)
|
|
199
|
+
tree = ast.parse(code)
|
|
200
|
+
visitor.visit(tree)
|
|
201
|
+
|
|
202
|
+
imports = [d for d in visitor.defs if d.type == "import"]
|
|
203
|
+
|
|
204
|
+
self.assertTrue(len(imports) >= 2)
|
|
205
|
+
|
|
206
|
+
if "package.subpackage.sibling_module" in {imp.name for imp in imports}:
|
|
207
|
+
self.assertEqual(visitor.alias["sibling_module"], "package.subpackage.sibling_module")
|
|
208
|
+
if "package.parent_function" in {imp.name for imp in imports}:
|
|
209
|
+
self.assertEqual(visitor.alias["parent_function"], "package.parent_function")
|
|
210
|
+
|
|
134
211
|
def test_nested_functions(self):
|
|
135
212
|
"""Test nested function detection."""
|
|
136
213
|
code = """
|
|
@@ -153,20 +230,105 @@ def outer():
|
|
|
153
230
|
"test_module.outer.inner.deeply_nested"
|
|
154
231
|
}
|
|
155
232
|
self.assertEqual(names, expected_names)
|
|
233
|
+
|
|
234
|
+
def test_function_parameters(self):
|
|
235
|
+
"""Test function parameter detection."""
|
|
236
|
+
code = """
|
|
237
|
+
def function_with_params(a, b, c=None, *args, **kwargs):
|
|
238
|
+
return a + b
|
|
239
|
+
|
|
240
|
+
class MyClass:
|
|
241
|
+
def method(self, x, y=10):
|
|
242
|
+
return self.x + y
|
|
243
|
+
"""
|
|
244
|
+
visitor = self.parse_and_visit(code)
|
|
245
|
+
|
|
246
|
+
params = [d for d in visitor.defs if d.type == "parameter"]
|
|
247
|
+
|
|
248
|
+
self.assertTrue(len(params) >= 5)
|
|
249
|
+
|
|
250
|
+
param_names = {p.simple_name for p in params}
|
|
251
|
+
expected_basic_params = {"a", "b", "c"}
|
|
252
|
+
self.assertTrue(expected_basic_params.issubset(param_names))
|
|
253
|
+
|
|
254
|
+
def test_parameter_usage_tracking(self):
|
|
255
|
+
code = """
|
|
256
|
+
def use_params(a, b, unused_param):
|
|
257
|
+
result = a + b # a and b are used, unused_param is not
|
|
258
|
+
return result
|
|
259
|
+
"""
|
|
260
|
+
visitor = self.parse_and_visit(code)
|
|
261
|
+
|
|
262
|
+
params = [d for d in visitor.defs if d.type == "parameter"]
|
|
263
|
+
param_names = {p.simple_name for p in params}
|
|
264
|
+
self.assertEqual(param_names, {"a", "b", "unused_param"})
|
|
265
|
+
|
|
266
|
+
ref_names = {ref[0] for ref in visitor.refs}
|
|
267
|
+
|
|
268
|
+
a_param = next(p.name for p in params if p.simple_name == "a")
|
|
269
|
+
b_param = next(p.name for p in params if p.simple_name == "b")
|
|
270
|
+
|
|
271
|
+
self.assertIn(a_param, ref_names)
|
|
272
|
+
self.assertIn(b_param, ref_names)
|
|
273
|
+
|
|
274
|
+
def test_variables(self):
|
|
275
|
+
code = """
|
|
276
|
+
MODULE_VAR = "module level"
|
|
277
|
+
|
|
278
|
+
class MyClass:
|
|
279
|
+
CLASS_VAR = "class level"
|
|
280
|
+
|
|
281
|
+
def method(self):
|
|
282
|
+
local_var = "function level"
|
|
283
|
+
self.instance_var = "instance level"
|
|
284
|
+
return local_var
|
|
285
|
+
|
|
286
|
+
def function():
|
|
287
|
+
func_var = "function scope"
|
|
156
288
|
|
|
289
|
+
def nested():
|
|
290
|
+
nested_var = "nested scope"
|
|
291
|
+
return nested_var
|
|
292
|
+
|
|
293
|
+
return func_var
|
|
294
|
+
"""
|
|
295
|
+
visitor = self.parse_and_visit(code)
|
|
296
|
+
|
|
297
|
+
variables = [d for d in visitor.defs if d.type == "variable"]
|
|
298
|
+
var_names = {v.simple_name for v in variables}
|
|
299
|
+
|
|
300
|
+
expected_basic_vars = {"MODULE_VAR", "CLASS_VAR", "local_var", "func_var", "nested_var"}
|
|
301
|
+
found_basic_vars = expected_basic_vars & var_names
|
|
302
|
+
|
|
303
|
+
self.assertTrue(len(found_basic_vars) >= 4)
|
|
304
|
+
|
|
157
305
|
def test_getattr_detection(self):
|
|
158
|
-
"""Test detection of getattr calls."""
|
|
159
306
|
code = """
|
|
160
307
|
obj = SomeClass()
|
|
161
308
|
value = getattr(obj, 'attribute_name')
|
|
162
309
|
check = hasattr(obj, 'other_attr')
|
|
310
|
+
dynamic_attr = getattr(module, 'function_name')
|
|
163
311
|
"""
|
|
164
312
|
visitor = self.parse_and_visit(code)
|
|
165
313
|
|
|
166
314
|
ref_names = {ref[0] for ref in visitor.refs}
|
|
167
315
|
self.assertIn('attribute_name', ref_names)
|
|
168
316
|
self.assertIn('other_attr', ref_names)
|
|
169
|
-
|
|
317
|
+
self.assertIn('function_name', ref_names)
|
|
318
|
+
|
|
319
|
+
def test_globals_detection(self):
|
|
320
|
+
"""Test detection of globals() usage."""
|
|
321
|
+
code = """
|
|
322
|
+
def dynamic_call():
|
|
323
|
+
func = globals()['some_function']
|
|
324
|
+
return func()
|
|
325
|
+
"""
|
|
326
|
+
visitor = self.parse_and_visit(code)
|
|
327
|
+
|
|
328
|
+
ref_names = {ref[0] for ref in visitor.refs}
|
|
329
|
+
|
|
330
|
+
self.assertIn('globals', ref_names)
|
|
331
|
+
|
|
170
332
|
def test_all_detection(self):
|
|
171
333
|
"""Test __all__ detection."""
|
|
172
334
|
code = """
|
|
@@ -186,7 +348,19 @@ CONSTANT = 42
|
|
|
186
348
|
self.assertIn('function1', ref_names)
|
|
187
349
|
self.assertIn('Class1', ref_names)
|
|
188
350
|
self.assertIn('CONSTANT', ref_names)
|
|
189
|
-
|
|
351
|
+
|
|
352
|
+
def test_all_tuple_format(self):
|
|
353
|
+
"""Test __all__ with tuple format."""
|
|
354
|
+
code = """
|
|
355
|
+
__all__ = ('func1', 'func2', 'Class1')
|
|
356
|
+
"""
|
|
357
|
+
visitor = self.parse_and_visit(code)
|
|
358
|
+
|
|
359
|
+
ref_names = {ref[0] for ref in visitor.refs}
|
|
360
|
+
self.assertIn('func1', ref_names)
|
|
361
|
+
self.assertIn('func2', ref_names)
|
|
362
|
+
self.assertIn('Class1', ref_names)
|
|
363
|
+
|
|
190
364
|
def test_builtin_detection(self):
|
|
191
365
|
"""Test that builtins are correctly identified."""
|
|
192
366
|
code = """
|
|
@@ -194,27 +368,347 @@ def my_function():
|
|
|
194
368
|
result = len([1, 2, 3])
|
|
195
369
|
print(result)
|
|
196
370
|
data = list(range(10))
|
|
197
|
-
|
|
371
|
+
items = enumerate(data)
|
|
372
|
+
total = sum(data)
|
|
373
|
+
return sorted(data)
|
|
198
374
|
"""
|
|
199
375
|
visitor = self.parse_and_visit(code)
|
|
200
376
|
|
|
201
377
|
ref_names = {ref[0] for ref in visitor.refs}
|
|
202
378
|
builtins_found = ref_names & PYTHON_BUILTINS
|
|
203
|
-
expected_builtins = {'len', 'print', 'list', 'range'}
|
|
379
|
+
expected_builtins = {'len', 'print', 'list', 'range', 'enumerate', 'sum', 'sorted'}
|
|
204
380
|
self.assertTrue(expected_builtins.issubset(builtins_found))
|
|
205
381
|
|
|
382
|
+
def test_decorators(self):
|
|
383
|
+
code = """
|
|
384
|
+
@property
|
|
385
|
+
def getter(self):
|
|
386
|
+
return self._value
|
|
387
|
+
|
|
388
|
+
@staticmethod
|
|
389
|
+
@decorator_with_args('arg')
|
|
390
|
+
def complex_decorated():
|
|
391
|
+
pass
|
|
392
|
+
|
|
393
|
+
class MyClass:
|
|
394
|
+
@classmethod
|
|
395
|
+
def class_method(cls):
|
|
396
|
+
pass
|
|
397
|
+
"""
|
|
398
|
+
visitor = self.parse_and_visit(code)
|
|
399
|
+
|
|
400
|
+
functions = [d for d in visitor.defs if d.type in ("function", "method")]
|
|
401
|
+
func_names = {f.simple_name for f in functions}
|
|
402
|
+
self.assertIn("getter", func_names)
|
|
403
|
+
self.assertIn("complex_decorated", func_names)
|
|
404
|
+
self.assertIn("class_method", func_names)
|
|
405
|
+
|
|
406
|
+
def test_inheritance_detection(self):
|
|
407
|
+
code = """
|
|
408
|
+
class Parent:
|
|
409
|
+
pass
|
|
410
|
+
|
|
411
|
+
class Child(Parent):
|
|
412
|
+
pass
|
|
413
|
+
|
|
414
|
+
class MultipleInheritance(Parent, object):
|
|
415
|
+
pass
|
|
416
|
+
"""
|
|
417
|
+
visitor = self.parse_and_visit(code)
|
|
418
|
+
|
|
419
|
+
classes = [d for d in visitor.defs if d.type == "class"]
|
|
420
|
+
class_names = {c.simple_name for c in classes}
|
|
421
|
+
self.assertEqual(class_names, {"Parent", "Child", "MultipleInheritance"})
|
|
422
|
+
|
|
423
|
+
ref_names = {ref[0] for ref in visitor.refs}
|
|
424
|
+
self.assertIn("test_module.Parent", ref_names)
|
|
425
|
+
self.assertIn("object", ref_names)
|
|
426
|
+
|
|
427
|
+
def test_comprehensions(self):
|
|
428
|
+
code = """
|
|
429
|
+
def test_comprehensions():
|
|
430
|
+
squares = [x**2 for x in range(10)]
|
|
431
|
+
square_dict = {x: x**2 for x in range(5)}
|
|
432
|
+
|
|
433
|
+
even_squares = {x**2 for x in range(10) if x % 2 == 0}
|
|
434
|
+
|
|
435
|
+
return squares, square_dict, even_squares
|
|
436
|
+
"""
|
|
437
|
+
visitor = self.parse_and_visit(code)
|
|
438
|
+
|
|
439
|
+
variables = [d for d in visitor.defs if d.type == "variable"]
|
|
440
|
+
var_names = {v.simple_name for v in variables}
|
|
441
|
+
|
|
442
|
+
expected_vars = {"squares", "square_dict", "even_squares"}
|
|
443
|
+
self.assertTrue(expected_vars.issubset(var_names))
|
|
444
|
+
|
|
445
|
+
def test_lambda_functions(self):
|
|
446
|
+
"""Test lambda function handling."""
|
|
447
|
+
code = """
|
|
448
|
+
def test_lambdas():
|
|
449
|
+
double = lambda x: x * 2
|
|
450
|
+
|
|
451
|
+
add = lambda a, b: a + b
|
|
452
|
+
|
|
453
|
+
numbers = [1, 2, 3, 4, 5]
|
|
454
|
+
doubled = list(map(lambda n: n * 2, numbers))
|
|
455
|
+
|
|
456
|
+
return double, add, doubled
|
|
457
|
+
"""
|
|
458
|
+
visitor = self.parse_and_visit(code)
|
|
459
|
+
|
|
460
|
+
functions = [d for d in visitor.defs if d.type == "function"]
|
|
461
|
+
func_names = {f.simple_name for f in functions}
|
|
462
|
+
self.assertEqual(func_names, {"test_lambdas"})
|
|
463
|
+
|
|
464
|
+
def test_attribute_access_chains(self):
|
|
465
|
+
"""Test complex attribute access chains."""
|
|
466
|
+
code = """
|
|
467
|
+
import os
|
|
468
|
+
from pathlib import Path
|
|
469
|
+
|
|
470
|
+
def test_attributes():
|
|
471
|
+
current_dir = os.getcwd()
|
|
472
|
+
|
|
473
|
+
path = Path.home().parent.name
|
|
474
|
+
|
|
475
|
+
text = "hello world"
|
|
476
|
+
result = text.upper().replace(" ", "_")
|
|
477
|
+
|
|
478
|
+
return current_dir, path, result
|
|
479
|
+
"""
|
|
480
|
+
visitor = self.parse_and_visit(code)
|
|
481
|
+
|
|
482
|
+
ref_names = {ref[0] for ref in visitor.refs}
|
|
483
|
+
|
|
484
|
+
self.assertIn("os.getcwd", ref_names)
|
|
485
|
+
self.assertIn("pathlib.Path.home", ref_names)
|
|
486
|
+
|
|
487
|
+
def test_star_imports(self):
|
|
488
|
+
code = """
|
|
489
|
+
from os import *
|
|
490
|
+
from collections import defaultdict
|
|
491
|
+
|
|
492
|
+
def use_star_import():
|
|
493
|
+
current_dir = getcwd() # from os import *
|
|
494
|
+
|
|
495
|
+
# explicit
|
|
496
|
+
my_dict = defaultdict(list)
|
|
497
|
+
|
|
498
|
+
return current_dir, my_dict
|
|
499
|
+
"""
|
|
500
|
+
visitor = self.parse_and_visit(code)
|
|
501
|
+
|
|
502
|
+
imports = [d for d in visitor.defs if d.type == "import"]
|
|
503
|
+
import_names = {i.name for i in imports}
|
|
504
|
+
|
|
505
|
+
# have the explicit import
|
|
506
|
+
self.assertIn("collections.defaultdict", import_names)
|
|
507
|
+
|
|
508
|
+
def test_exception_handling(self):
|
|
509
|
+
code = """
|
|
510
|
+
def test_exceptions():
|
|
511
|
+
try:
|
|
512
|
+
risky_operation()
|
|
513
|
+
except ValueError as ve:
|
|
514
|
+
handle_value_error(ve)
|
|
515
|
+
except (TypeError, AttributeError) as e:
|
|
516
|
+
handle_other_errors(e)
|
|
517
|
+
except Exception:
|
|
518
|
+
handle_generic_error()
|
|
519
|
+
finally:
|
|
520
|
+
cleanup()
|
|
521
|
+
"""
|
|
522
|
+
|
|
523
|
+
# our current visitor can't really handle exception variables it's is a feature gap
|
|
524
|
+
|
|
525
|
+
def test_context_managers(self):
|
|
526
|
+
code = """
|
|
527
|
+
def test_context_managers():
|
|
528
|
+
with open('file.txt') as f:
|
|
529
|
+
content = f.read()
|
|
530
|
+
|
|
531
|
+
with open('input.txt') as infile, open('output.txt', 'w') as outfile:
|
|
532
|
+
data = infile.read()
|
|
533
|
+
outfile.write(data.upper())
|
|
534
|
+
|
|
535
|
+
return content
|
|
536
|
+
"""
|
|
537
|
+
visitor = self.parse_and_visit(code)
|
|
538
|
+
|
|
539
|
+
variables = [d for d in visitor.defs if d.type == "variable"]
|
|
540
|
+
var_names = {v.simple_name for v in variables}
|
|
541
|
+
|
|
542
|
+
# just check some basic variables, later then improve on visitor
|
|
543
|
+
basic_vars = {"content", "data"}
|
|
544
|
+
found_basic = basic_vars & var_names
|
|
545
|
+
|
|
546
|
+
self.assertTrue(len(found_basic) >= 1)
|
|
547
|
+
|
|
206
548
|
class TestConstants(unittest.TestCase):
|
|
207
|
-
"""Test the module constants."""
|
|
208
549
|
|
|
209
|
-
def
|
|
550
|
+
def test_python_builtins_completeness(self):
|
|
210
551
|
"""Test that important builtins are included."""
|
|
211
|
-
important_builtins = {
|
|
552
|
+
important_builtins = {
|
|
553
|
+
# basic types
|
|
554
|
+
'str', 'int', 'float', 'bool', 'list', 'dict', 'set', 'tuple',
|
|
555
|
+
# funcs
|
|
556
|
+
'print', 'len', 'range', 'enumerate', 'zip', 'map', 'filter',
|
|
557
|
+
'sum', 'min', 'max', 'sorted', 'reversed', 'all', 'any',
|
|
558
|
+
# more advance stuff
|
|
559
|
+
'open', 'super', 'getattr', 'setattr', 'hasattr', 'isinstance',
|
|
560
|
+
'property', 'classmethod', 'staticmethod'
|
|
561
|
+
}
|
|
212
562
|
self.assertTrue(important_builtins.issubset(PYTHON_BUILTINS))
|
|
213
563
|
|
|
214
564
|
def test_dynamic_patterns(self):
|
|
215
565
|
"""Test dynamic pattern constants."""
|
|
216
566
|
expected_patterns = {'getattr', 'globals', 'eval', 'exec'}
|
|
217
567
|
self.assertTrue(expected_patterns.issubset(DYNAMIC_PATTERNS))
|
|
568
|
+
|
|
569
|
+
def test_builtins_are_strings(self):
|
|
570
|
+
"""Test that all builtins are strings."""
|
|
571
|
+
for builtin in PYTHON_BUILTINS:
|
|
572
|
+
self.assertIsInstance(builtin, str)
|
|
573
|
+
self.assertTrue(builtin.isidentifier())
|
|
574
|
+
|
|
575
|
+
class TestEdgeCases(unittest.TestCase):
|
|
576
|
+
|
|
577
|
+
def setUp(self):
|
|
578
|
+
self.temp_file = tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False)
|
|
579
|
+
self.visitor = Visitor("test_module", self.temp_file.name)
|
|
580
|
+
|
|
581
|
+
def tearDown(self):
|
|
582
|
+
Path(self.temp_file.name).unlink()
|
|
583
|
+
|
|
584
|
+
def parse_and_visit(self, code):
|
|
585
|
+
"""Helper to parse code and visit with the visitor."""
|
|
586
|
+
tree = ast.parse(code)
|
|
587
|
+
self.visitor.visit(tree)
|
|
588
|
+
return self.visitor
|
|
589
|
+
|
|
590
|
+
def test_empty_file(self):
|
|
591
|
+
code = ""
|
|
592
|
+
visitor = self.parse_and_visit(code)
|
|
593
|
+
|
|
594
|
+
self.assertEqual(len(visitor.defs), 0)
|
|
595
|
+
self.assertEqual(len(visitor.refs), 0)
|
|
596
|
+
|
|
597
|
+
def test_comments_and_docstrings(self):
|
|
598
|
+
code = '''
|
|
599
|
+
"""Module docstring"""
|
|
600
|
+
|
|
601
|
+
def function_with_docstring():
|
|
602
|
+
"""Function docstring with 'quoted' content."""
|
|
603
|
+
# This is a comment
|
|
604
|
+
return "string with quotes"
|
|
605
|
+
|
|
606
|
+
class ClassWithDocstring:
|
|
607
|
+
"""Class docstring."""
|
|
608
|
+
pass
|
|
609
|
+
'''
|
|
610
|
+
visitor = self.parse_and_visit(code)
|
|
611
|
+
|
|
612
|
+
defs = [d for d in visitor.defs if d.type in ("function", "class")]
|
|
613
|
+
def_names = {d.simple_name for d in defs}
|
|
614
|
+
self.assertEqual(def_names, {"function_with_docstring", "ClassWithDocstring"})
|
|
615
|
+
|
|
616
|
+
def test_malformed_annotations(self):
|
|
617
|
+
"""handling of malformed type annotations."""
|
|
618
|
+
code = '''
|
|
619
|
+
def function_with_annotation(param: "SomeType") -> "ReturnType":
|
|
620
|
+
pass
|
|
621
|
+
|
|
622
|
+
def function_with_complex_annotation(param: Dict[str, List["NestedType"]]) -> None:
|
|
623
|
+
pass
|
|
624
|
+
'''
|
|
625
|
+
visitor = self.parse_and_visit(code)
|
|
626
|
+
|
|
627
|
+
functions = [d for d in visitor.defs if d.type == "function"]
|
|
628
|
+
self.assertEqual(len(functions), 2)
|
|
629
|
+
|
|
630
|
+
def test_import_aliases_fix(self):
|
|
631
|
+
"""Test the fix from issue in #8"""
|
|
632
|
+
code = """
|
|
633
|
+
from selenium.webdriver.support import expected_conditions as EC
|
|
634
|
+
from collections import defaultdict as dd
|
|
635
|
+
|
|
636
|
+
def use_aliases():
|
|
637
|
+
condition = EC.presence_of_element_located(("id", "test"))
|
|
638
|
+
|
|
639
|
+
my_dict = dd(list)
|
|
640
|
+
|
|
641
|
+
return condition, my_dict
|
|
642
|
+
"""
|
|
643
|
+
visitor = self.parse_and_visit(code)
|
|
644
|
+
|
|
645
|
+
self.assertEqual(visitor.alias["EC"], "selenium.webdriver.support.expected_conditions")
|
|
646
|
+
self.assertEqual(visitor.alias["dd"], "collections.defaultdict")
|
|
647
|
+
|
|
648
|
+
# should be the actual import, not the local alias
|
|
649
|
+
import_defs = [d for d in visitor.defs if d.type == "import"]
|
|
650
|
+
import_names = {d.name for d in import_defs}
|
|
651
|
+
|
|
652
|
+
self.assertIn("selenium.webdriver.support.expected_conditions", import_names)
|
|
653
|
+
self.assertIn("collections.defaultdict", import_names)
|
|
654
|
+
|
|
655
|
+
# the local aliases should not be defined as imports
|
|
656
|
+
self.assertNotIn("test_module.EC", import_names)
|
|
657
|
+
self.assertNotIn("test_module.dd", import_names)
|
|
658
|
+
|
|
659
|
+
ref_names = {ref[0] for ref in visitor.refs}
|
|
660
|
+
self.assertIn("selenium.webdriver.support.expected_conditions.presence_of_element_located", ref_names)
|
|
661
|
+
self.assertIn("collections.defaultdict", ref_names)
|
|
662
|
+
|
|
663
|
+
def test_import_errors(self):
|
|
664
|
+
code = '''
|
|
665
|
+
from . import something
|
|
666
|
+
|
|
667
|
+
from collections import defaultdict, Counter as cnt, deque
|
|
668
|
+
'''
|
|
669
|
+
visitor = Visitor("root_module", self.temp_file.name)
|
|
670
|
+
tree = ast.parse(code)
|
|
671
|
+
visitor.visit(tree)
|
|
672
|
+
|
|
673
|
+
imports = [d for d in visitor.defs if d.type == "import"]
|
|
674
|
+
self.assertTrue(len(imports) >= 3)
|
|
218
675
|
|
|
219
676
|
if __name__ == '__main__':
|
|
220
|
-
|
|
677
|
+
test_classes = [
|
|
678
|
+
TestDefinition,
|
|
679
|
+
TestVisitor,
|
|
680
|
+
TestConstants,
|
|
681
|
+
TestEdgeCases
|
|
682
|
+
]
|
|
683
|
+
|
|
684
|
+
loader = unittest.TestLoader()
|
|
685
|
+
suite = unittest.TestSuite()
|
|
686
|
+
|
|
687
|
+
for test_class in test_classes:
|
|
688
|
+
tests = loader.loadTestsFromTestCase(test_class)
|
|
689
|
+
suite.addTests(tests)
|
|
690
|
+
|
|
691
|
+
runner = unittest.TextTestRunner(verbosity=2)
|
|
692
|
+
result = runner.run(suite)
|
|
693
|
+
|
|
694
|
+
print(f"\n{'='*50}")
|
|
695
|
+
print(f"Test Summary:")
|
|
696
|
+
print(f"Tests run: {result.testsRun}")
|
|
697
|
+
print(f"Failures: {len(result.failures)}")
|
|
698
|
+
print(f"Errors: {len(result.errors)}")
|
|
699
|
+
|
|
700
|
+
if result.testsRun > 0:
|
|
701
|
+
success_rate = ((result.testsRun - len(result.failures) - len(result.errors)) / result.testsRun * 100)
|
|
702
|
+
print(f"Success rate: {success_rate:.1f}%")
|
|
703
|
+
|
|
704
|
+
if result.failures:
|
|
705
|
+
print(f"\nFailures:")
|
|
706
|
+
for test, traceback in result.failures:
|
|
707
|
+
print(f" - {test}")
|
|
708
|
+
|
|
709
|
+
if result.errors:
|
|
710
|
+
print(f"\nErrors:")
|
|
711
|
+
for test, traceback in result.errors:
|
|
712
|
+
print(f" - {test}")
|
|
713
|
+
|
|
714
|
+
print("="*50)
|
skylos-1.0.11.dist-info/RECORD
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
skylos/__init__.py,sha256=QbF0FyEwDpVFNQtCN9GQCkJGWkj8mU_WVHzFYrpGPvg,152
|
|
2
|
-
skylos/analyzer.py,sha256=CYJSf1jMMEvacFolKXs197fT9FYPFb-D29VtU3vO6vA,10247
|
|
3
|
-
skylos/cli.py,sha256=1ZY95i9pegj62PrwNrwolBRydgO3H4sKF5Wy0TndTrk,14978
|
|
4
|
-
skylos/visitor.py,sha256=P42ob1RKOGhMLh-gHOw5L7fVI0TUxEThFyPbDe6z5D0,11827
|
|
5
|
-
test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
test/compare_tools.py,sha256=0g9PDeJlbst-7hOaQzrL4MiJFQKpqM8q8VeBGzpPczg,22738
|
|
7
|
-
test/diagnostics.py,sha256=ExuFOCVpc9BDwNYapU96vj9RXLqxji32Sv6wVF4nJYU,13802
|
|
8
|
-
test/test_skylos.py,sha256=kz77STrS4k3Eez5RDYwGxOg2WH3e7zNZPUYEaTLbGTs,15608
|
|
9
|
-
test/test_visitor.py,sha256=bxUY_Zn_gLadZlz_n3Mu6rhVcExqElISwwVBo4eqVAY,7337
|
|
10
|
-
test/pykomodo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
-
test/pykomodo/command_line.py,sha256=3-khuenVjWZjvrvOrr_1K5lMTUmZa-B759B7k77Odpc,7384
|
|
12
|
-
test/pykomodo/config.py,sha256=UddY0sDIRlsZApnlQF70VE7b9KMsOoY_3yqFPtB71jw,453
|
|
13
|
-
test/pykomodo/core.py,sha256=rzoGibPwXr1efAgVtkcvhtM9ZETWWloCVi9weshxa4Y,3841
|
|
14
|
-
test/pykomodo/dashboard.py,sha256=wIvU8aq7vGhcs1bBfwGlXy3AQizmljySf7Of82LmgwI,21688
|
|
15
|
-
test/pykomodo/enhanced_chunker.py,sha256=nRTFSEyAkm4GilWEhJhPAZ67mjg4cEgW8oC6FaYxorY,12062
|
|
16
|
-
test/pykomodo/multi_dirs_chunker.py,sha256=Gz56V3RiBQX5ygeRsukqGRdCcJIb40w2082Xyu3UPOg,29184
|
|
17
|
-
test/pykomodo/pykomodo_config.py,sha256=CquUm_XvCm6XScWv4euyW-DwVgkgzGPHDlL3uB37Fxo,2389
|
|
18
|
-
test/pykomodo/token_chunker.py,sha256=p-zqhMdT_0h-jbIVeKon6sl3BBRNvbv8XAVDJB6qNSE,19530
|
|
19
|
-
test/sample_repo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
-
test/sample_repo/app.py,sha256=M5XgoAn-LPz50mKAj_ZacRKf-Pg7I4HbjWP7Z9jE4a0,226
|
|
21
|
-
test/sample_repo/sample_repo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
-
test/sample_repo/sample_repo/commands.py,sha256=b6gQ9YDabt2yyfqGbOpLo0osF7wya8O4Lm7m8gtCr3g,2575
|
|
23
|
-
test/sample_repo/sample_repo/models.py,sha256=xXIg3pToEZwKuUCmKX2vTlCF_VeFA0yZlvlBVPIy5Qw,3320
|
|
24
|
-
test/sample_repo/sample_repo/routes.py,sha256=8yITrt55BwS01G7nWdESdx8LuxmReqop1zrGUKPeLi8,2475
|
|
25
|
-
test/sample_repo/sample_repo/utils.py,sha256=S56hEYh8wkzwsD260MvQcmUFOkw2EjFU27nMLFE6G2k,1103
|
|
26
|
-
skylos-1.0.11.dist-info/METADATA,sha256=3brmk_HqJmxWAwyyEbzEW-xNC6afwiBb0CyCpnJTNIw,225
|
|
27
|
-
skylos-1.0.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
28
|
-
skylos-1.0.11.dist-info/entry_points.txt,sha256=zzRpN2ByznlQoLeuLolS_TFNYSQxUGBL1EXQsAd6bIA,43
|
|
29
|
-
skylos-1.0.11.dist-info/top_level.txt,sha256=f8GA_7KwfaEopPMP8-EXDQXaqd4IbsOQPakZy01LkdQ,12
|
|
30
|
-
skylos-1.0.11.dist-info/RECORD,,
|
test/pykomodo/__init__.py
DELETED
|
File without changes
|