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.

test/test_cli.py ADDED
@@ -0,0 +1,353 @@
1
+ #!/usr/bin/env python3
2
+ import pytest
3
+ import json
4
+ import sys
5
+ import logging
6
+ from unittest.mock import Mock, patch, mock_open
7
+
8
+ from skylos.cli import Colors, CleanFormatter, setup_logger, remove_unused_import, remove_unused_function, interactive_selection, print_badge, main
9
+
10
+ class TestColors:
11
+ def test_colors_defined(self):
12
+ """Test that all color constants are defined."""
13
+ assert hasattr(Colors, 'RED')
14
+ assert hasattr(Colors, 'GREEN')
15
+ assert hasattr(Colors, 'RESET')
16
+ assert hasattr(Colors, 'BOLD')
17
+
18
+ assert Colors.RED.startswith('\033[')
19
+ assert Colors.RESET == '\033[0m'
20
+
21
+ class TestCleanFormatter:
22
+ def test_clean_formatter_removes_metadata(self):
23
+ """Test that CleanFormatter only returns the message."""
24
+ formatter = CleanFormatter()
25
+
26
+ record = Mock()
27
+ record.getMessage.return_value = "Test message"
28
+
29
+ result = formatter.format(record)
30
+ assert result == "Test message"
31
+
32
+ record.getMessage.assert_called_once()
33
+
34
+
35
+ class TestSetupLogger:
36
+
37
+ @patch('skylos.cli.logging.FileHandler')
38
+ @patch('skylos.cli.logging.StreamHandler')
39
+ def test_setup_logger_console_only(self, mock_stream_handler, mock_file_handler):
40
+ """Test logger setup without output file."""
41
+ mock_handler = Mock()
42
+ mock_stream_handler.return_value = mock_handler
43
+
44
+ logger = setup_logger()
45
+
46
+ assert logger.name == 'skylos'
47
+ assert logger.level == logging.INFO
48
+ assert not logger.propagate
49
+
50
+ mock_stream_handler.assert_called_once_with(sys.stdout)
51
+ mock_file_handler.assert_not_called()
52
+
53
+ @patch('skylos.cli.logging.FileHandler')
54
+ @patch('skylos.cli.logging.StreamHandler')
55
+ def test_setup_logger_with_output_file(self, mock_stream_handler, mock_file_handler):
56
+ """Test logger setup with output file."""
57
+ mock_stream_handler.return_value = Mock()
58
+ mock_file_handler.return_value = Mock()
59
+
60
+ logger = setup_logger("output.log")
61
+
62
+ mock_stream_handler.assert_called_once_with(sys.stdout)
63
+ mock_file_handler.assert_called_once_with("output.log")
64
+
65
+
66
+ class TestRemoveUnusedImport:
67
+ """Test unused import removal functionality."""
68
+
69
+ def test_remove_simple_import(self):
70
+ """Test removing a simple import statement."""
71
+ content = """import os
72
+ import sys
73
+ import json
74
+
75
+ def main():
76
+ print(sys.version)
77
+ """
78
+
79
+ with patch('builtins.open', mock_open(read_data=content)) as mock_file:
80
+ result = remove_unused_import("test.py", "os", 1)
81
+
82
+ assert result is True
83
+ handle = mock_file()
84
+ written_content = ''.join(call.args[0] for call in handle.write.call_args_list)
85
+ assert "import os" not in written_content
86
+
87
+ def test_remove_from_multi_import(self):
88
+ content = "import os, sys, json\n"
89
+
90
+ with patch('builtins.open', mock_open(read_data=content)) as mock_file:
91
+ result = remove_unused_import("test.py", "os", 1)
92
+
93
+ assert result is True
94
+
95
+ def test_remove_from_import_statement(self):
96
+ content = "from collections import defaultdict, Counter\n"
97
+
98
+ with patch('builtins.open', mock_open(read_data=content)) as mock_file:
99
+ result = remove_unused_import("test.py", "Counter", 1)
100
+
101
+ assert result is True
102
+ handle = mock_file()
103
+ written_lines = handle.writelines.call_args[0][0]
104
+ written_content = ''.join(written_lines)
105
+ assert "defaultdict" in written_content
106
+ assert "Counter" not in written_content
107
+
108
+ def test_remove_entire_from_import(self):
109
+ content = "from collections import defaultdict\n"
110
+
111
+ with patch('builtins.open', mock_open(read_data=content)) as mock_file:
112
+ result = remove_unused_import("test.py", "defaultdict", 1)
113
+
114
+ assert result is True
115
+ handle = mock_file()
116
+ written_content = ''.join(call.args[0] for call in handle.write.call_args_list)
117
+ # line should be empty
118
+ assert written_content.strip() == ""
119
+
120
+ def test_remove_import_file_error(self):
121
+ """handling file errors when removing imports."""
122
+ with patch('builtins.open', side_effect=FileNotFoundError("File not found")):
123
+ result = remove_unused_import("nonexistent.py", "os", 1)
124
+ assert result is False
125
+
126
+ class TestRemoveUnusedFunction:
127
+ def test_remove_simple_function(self):
128
+ """test remove a simple function."""
129
+ content = """def used_function():
130
+ return "used"
131
+
132
+ def unused_function():
133
+ return "unused"
134
+
135
+ def another_function():
136
+ return "another"
137
+ """
138
+
139
+ with patch('skylos.cli.ast.parse') as mock_parse, \
140
+ patch('builtins.open', mock_open(read_data=content)) as mock_file:
141
+
142
+ mock_func_node = Mock()
143
+ mock_func_node.name = "unused_function"
144
+ mock_func_node.lineno = 4
145
+ mock_func_node.end_lineno = 5
146
+ mock_func_node.decorator_list = []
147
+
148
+ with patch('skylos.cli.ast.walk', return_value=[mock_func_node]):
149
+ with patch('skylos.cli.ast.FunctionDef', mock_func_node.__class__):
150
+ result = remove_unused_function("test.py", "unused_function", 4)
151
+
152
+ assert result is True
153
+
154
+ def test_remove_function_with_decorators(self):
155
+ """removing function with decorators."""
156
+ content = """@property
157
+ @decorator
158
+ def unused_function():
159
+ return "unused"
160
+ """
161
+
162
+ with patch('skylos.cli.ast.parse') as mock_parse, \
163
+ patch('builtins.open', mock_open(read_data=content)) as mock_file:
164
+
165
+ mock_decorator = Mock()
166
+ mock_decorator.lineno = 1
167
+
168
+ mock_func_node = Mock()
169
+ mock_func_node.name = "unused_function"
170
+ mock_func_node.lineno = 3
171
+ mock_func_node.end_lineno = 4
172
+ mock_func_node.decorator_list = [mock_decorator]
173
+
174
+ with patch('skylos.cli.ast.walk', return_value=[mock_func_node]):
175
+ with patch('skylos.cli.ast.FunctionDef', mock_func_node.__class__):
176
+ result = remove_unused_function("test.py", "unused_function", 3)
177
+
178
+ assert result is True
179
+
180
+ def test_remove_function_file_error(self):
181
+ with patch('builtins.open', side_effect=FileNotFoundError("File not found")):
182
+ result = remove_unused_function("nonexistent.py", "func", 1)
183
+ assert result is False
184
+
185
+ def test_remove_function_parse_error(self):
186
+ with patch('builtins.open', mock_open(read_data="invalid python code")), \
187
+ patch('skylos.cli.ast.parse', side_effect=SyntaxError("Invalid syntax")):
188
+ result = remove_unused_function("test.py", "func", 1)
189
+ assert result is False
190
+
191
+ class TestInteractiveSelection:
192
+ @pytest.fixture
193
+ def mock_logger(self):
194
+ return Mock()
195
+
196
+ @pytest.fixture
197
+ def sample_unused_items(self):
198
+ """create fake sample unused items for testing"""
199
+ functions = [
200
+ {"name": "unused_func1", "file": "test1.py", "line": 10},
201
+ {"name": "unused_func2", "file": "test2.py", "line": 20}
202
+ ]
203
+ imports = [
204
+ {"name": "unused_import1", "file": "test1.py", "line": 1},
205
+ {"name": "unused_import2", "file": "test2.py", "line": 2}
206
+ ]
207
+ return functions, imports
208
+
209
+ def test_interactive_selection_unavailable(self, mock_logger, sample_unused_items):
210
+ """interactive selection when inquirer is not available."""
211
+ functions, imports = sample_unused_items
212
+
213
+ with patch('skylos.cli.INTERACTIVE_AVAILABLE', False):
214
+ selected_functions, selected_imports = interactive_selection(
215
+ mock_logger, functions, imports
216
+ )
217
+
218
+ assert selected_functions == []
219
+ assert selected_imports == []
220
+ mock_logger.error.assert_called_once()
221
+
222
+ @patch('skylos.cli.inquirer')
223
+ def test_interactive_selection_with_selections(self, mock_inquirer, mock_logger, sample_unused_items):
224
+ functions, imports = sample_unused_items
225
+
226
+ mock_inquirer.prompt.side_effect = [
227
+ {'functions': [functions[0]]},
228
+ {'imports': [imports[1]]}
229
+ ]
230
+
231
+ with patch('skylos.cli.INTERACTIVE_AVAILABLE', True):
232
+ selected_functions, selected_imports = interactive_selection(
233
+ mock_logger, functions, imports
234
+ )
235
+
236
+ assert selected_functions == [functions[0]]
237
+ assert selected_imports == [imports[1]]
238
+ assert mock_inquirer.prompt.call_count == 2
239
+
240
+ @patch('skylos.cli.inquirer')
241
+ def test_interactive_selection_no_selections(self, mock_inquirer, mock_logger, sample_unused_items):
242
+ functions, imports = sample_unused_items
243
+
244
+ mock_inquirer.prompt.return_value = None
245
+
246
+ with patch('skylos.cli.INTERACTIVE_AVAILABLE', True):
247
+ selected_functions, selected_imports = interactive_selection(
248
+ mock_logger, functions, imports
249
+ )
250
+
251
+ assert selected_functions == []
252
+ assert selected_imports == []
253
+
254
+ def test_interactive_selection_empty_lists(self, mock_logger):
255
+ selected_functions, selected_imports = interactive_selection(
256
+ mock_logger, [], []
257
+ )
258
+
259
+ assert selected_functions == []
260
+ assert selected_imports == []
261
+
262
+
263
+ class TestPrintBadge:
264
+ @pytest.fixture
265
+ def mock_logger(self):
266
+ return Mock()
267
+
268
+ def test_print_badge_zero_dead_code(self, mock_logger):
269
+ """Test badge printing with zero dead code."""
270
+ print_badge(0, mock_logger)
271
+
272
+ calls = [call.args[0] for call in mock_logger.info.call_args_list]
273
+ badge_call = next((call for call in calls if "Dead_Code-Free" in call), None)
274
+ assert badge_call is not None
275
+ assert "brightgreen" in badge_call
276
+
277
+ def test_print_badge_with_dead_code(self, mock_logger):
278
+ print_badge(5, mock_logger)
279
+
280
+ calls = [call.args[0] for call in mock_logger.info.call_args_list]
281
+ badge_call = next((call for call in calls if "Dead_Code-5" in call), None)
282
+ assert badge_call is not None
283
+ assert "orange" in badge_call
284
+
285
+ class TestMainFunction:
286
+ @pytest.fixture
287
+ def mock_skylos_result(self):
288
+ return {
289
+ "unused_functions": [
290
+ {"name": "unused_func", "file": "test.py", "line": 10}
291
+ ],
292
+ "unused_imports": [
293
+ {"name": "unused_import", "file": "test.py", "line": 1}
294
+ ],
295
+ "unused_parameters": [],
296
+ "unused_variables": [],
297
+ "analysis_summary": {
298
+ "total_files": 2,
299
+ "excluded_folders": []
300
+ }
301
+ }
302
+
303
+ def test_main_json_output(self, mock_skylos_result):
304
+ """testing main function with JSON output"""
305
+ test_args = ["cli.py", "test_path", "--json"]
306
+
307
+ with patch('sys.argv', test_args), \
308
+ patch('skylos.cli.skylos.analyze') as mock_analyze, \
309
+ patch('skylos.cli.setup_logger') as mock_setup_logger:
310
+
311
+ mock_logger = Mock()
312
+ mock_setup_logger.return_value = mock_logger
313
+ mock_analyze.return_value = json.dumps(mock_skylos_result)
314
+
315
+ main()
316
+
317
+ mock_analyze.assert_called_once()
318
+ mock_logger.info.assert_called_with(json.dumps(mock_skylos_result))
319
+
320
+ def test_main_verbose_output(self, mock_skylos_result):
321
+ """ with verbose"""
322
+ test_args = ["cli.py", "test_path", "--verbose"]
323
+
324
+ with patch('sys.argv', test_args), \
325
+ patch('skylos.cli.skylos.analyze') as mock_analyze, \
326
+ patch('skylos.cli.setup_logger') as mock_setup_logger:
327
+
328
+ mock_logger = Mock()
329
+ mock_setup_logger.return_value = mock_logger
330
+ mock_analyze.return_value = json.dumps(mock_skylos_result)
331
+
332
+ main()
333
+
334
+ mock_logger.setLevel.assert_called_with(logging.DEBUG)
335
+
336
+ def test_main_analysis_error(self):
337
+ test_args = ["cli.py", "test_path"]
338
+
339
+ with patch('sys.argv', test_args), \
340
+ patch('skylos.cli.skylos.analyze', side_effect=Exception("Analysis failed")), \
341
+ patch('skylos.cli.setup_logger') as mock_setup_logger, \
342
+ patch('skylos.cli.parse_exclude_folders', return_value=set()):
343
+
344
+ mock_logger = Mock()
345
+ mock_setup_logger.return_value = mock_logger
346
+
347
+ with pytest.raises(SystemExit):
348
+ main()
349
+
350
+ mock_logger.error.assert_called_with("Error during analysis: Analysis failed")
351
+
352
+ if __name__ == "__main__":
353
+ pytest.main([__file__, "-v"])
@@ -0,0 +1,320 @@
1
+ import pytest
2
+ import json
3
+ import tempfile
4
+ import os
5
+ from pathlib import Path
6
+ from textwrap import dedent
7
+
8
+ import skylos
9
+ from skylos.analyzer import Skylos
10
+ from skylos.analyzer import DEFAULT_EXCLUDE_FOLDERS
11
+
12
+ class TestSkylosIntegration:
13
+
14
+ @pytest.fixture
15
+ def temp_project(self):
16
+ with tempfile.TemporaryDirectory() as temp_dir:
17
+ project_path = Path(temp_dir)
18
+
19
+ main_py = project_path / "main.py"
20
+ main_py.write_text(dedent("""
21
+ import os # unused import
22
+ import sys
23
+ from typing import List # unused import
24
+ from collections import defaultdict
25
+ from utils import UsedClass, exported_function
26
+
27
+ def used_function(x):
28
+ '''This function is called and should not be flagged'''
29
+ return x * 2
30
+
31
+ def unused_function(a, b):
32
+ '''This function is never called'''
33
+ unused_var = a + b # unused variable
34
+ return unused_var
35
+
36
+ def main():
37
+ result = used_function(5)
38
+ print(result, file=sys.stderr)
39
+ data = defaultdict(list)
40
+
41
+ obj = UsedClass()
42
+ obj.get_value()
43
+ exported_function()
44
+
45
+ return data
46
+
47
+ if __name__ == "__main__":
48
+ main()
49
+ """))
50
+
51
+ utils_py = project_path / "utils.py"
52
+ utils_py.write_text(dedent("""
53
+ import json # unused import
54
+
55
+ class UsedClass:
56
+ '''This class is imported and used'''
57
+ def __init__(self):
58
+ self.value = 42
59
+
60
+ def get_value(self):
61
+ return self.value
62
+
63
+ class UnusedClass:
64
+ '''This class is never used'''
65
+ def __init__(self):
66
+ self.data = {}
67
+
68
+ def unused_method(self, param): # unused parameter
69
+ return "never called"
70
+
71
+ def exported_function():
72
+ '''This function is used by main.py'''
73
+ return "exported"
74
+ """))
75
+
76
+ package_dir = project_path / "mypackage"
77
+ package_dir.mkdir()
78
+
79
+ init_py = package_dir / "__init__.py"
80
+ init_py.write_text(dedent("""
81
+ from .submodule import PublicClass
82
+
83
+ __all__ = ['PublicClass', 'public_function']
84
+
85
+ def public_function():
86
+ return "public"
87
+
88
+ def _private_function():
89
+ return "private"
90
+ """))
91
+
92
+ sub_py = package_dir / "submodule.py"
93
+ sub_py.write_text(dedent("""
94
+ class PublicClass:
95
+ '''Exported via __init__.py'''
96
+ def method(self):
97
+ return "method"
98
+
99
+ class InternalClass:
100
+ '''Not exported, should be flagged'''
101
+ pass
102
+ """))
103
+
104
+ test_py = project_path / "test_example.py"
105
+ test_py.write_text(dedent("""
106
+ import unittest
107
+ from main import used_function
108
+
109
+ class TestExample(unittest.TestCase):
110
+ def test_used_function(self):
111
+ result = used_function(3)
112
+ self.assertEqual(result, 6)
113
+
114
+ def test_unused_method(self):
115
+ pass
116
+ """))
117
+
118
+ yield project_path
119
+
120
+ def test_basic_analysis(self, temp_project):
121
+ """Test basic unused code detection"""
122
+ result_json = skylos.analyze(str(temp_project), exclude_folders=list(DEFAULT_EXCLUDE_FOLDERS))
123
+ result = json.loads(result_json)
124
+
125
+ assert "unused_functions" in result
126
+ assert "unused_imports" in result
127
+ assert "unused_variables" in result
128
+ assert "unused_parameters" in result
129
+ assert "unused_classes" in result
130
+
131
+ assert len(result["unused_functions"]) > 0
132
+ assert len(result["unused_imports"]) > 0
133
+
134
+ unused_function_names = [f["name"] for f in result["unused_functions"]]
135
+ assert "unused_function" in unused_function_names
136
+
137
+ assert "used_function" not in unused_function_names
138
+ assert "main" not in unused_function_names
139
+
140
+ def test_unused_imports_detection(self, temp_project):
141
+ """Test detection of unused imports"""
142
+ result_json = skylos.analyze(str(temp_project))
143
+ result = json.loads(result_json)
144
+
145
+ unused_imports = result["unused_imports"]
146
+ import_names = [imp["name"] for imp in unused_imports]
147
+
148
+ assert any("os" in name for name in import_names)
149
+ assert any("typing.List" in name or "List" in name for name in import_names)
150
+
151
+ used_imports = ["sys", "defaultdict"]
152
+ for used_import in used_imports:
153
+ assert not any(used_import in name for name in import_names)
154
+
155
+ def test_class_detection(self, temp_project):
156
+ """test detection of unused classes"""
157
+ result_json = skylos.analyze(str(temp_project))
158
+ result = json.loads(result_json)
159
+
160
+ unused_classes = result["unused_classes"]
161
+ class_names = [cls["name"] for cls in unused_classes]
162
+
163
+
164
+ assert any("UnusedClass" in name for name in class_names), f"UnusedClass not found in {class_names}"
165
+
166
+ used_classes_flagged = [name for name in class_names if "UsedClass" in name]
167
+ assert len(used_classes_flagged) == 0, f"UsedClass was incorrectly flagged as unused: {used_classes_flagged}"
168
+
169
+ # publicclass should not be flagged because it's exported via __init__.py
170
+ public_classes_flagged = [name for name in class_names if "PublicClass" in name]
171
+ assert len(public_classes_flagged) == 0, f"PublicClass was incorrectly flagged as unused: {public_classes_flagged}"
172
+
173
+ def test_exclude_folders(self, temp_project):
174
+ result1 = skylos.analyze(str(temp_project))
175
+
176
+ # exclude the mypackage folder
177
+ result2 = skylos.analyze(str(temp_project), exclude_folders=["mypackage"])
178
+
179
+ # it'll find fewer items when excluding a folder
180
+ data1 = json.loads(result1)
181
+ data2 = json.loads(result2)
182
+
183
+ total1 = sum(len(items) for items in data1.values() if isinstance(items, list))
184
+ total2 = sum(len(items) for items in data2.values() if isinstance(items, list))
185
+
186
+ assert total2 <= total1
187
+
188
+ def test_custom_exclude_folders(self, temp_project):
189
+ custom_dir = temp_project / "custom_exclude"
190
+ custom_dir.mkdir()
191
+
192
+ custom_file = custom_dir / "custom.py"
193
+ custom_file.write_text(dedent("""
194
+ def custom_function():
195
+ return "should be excluded"
196
+ """))
197
+
198
+ result_json = skylos.analyze(str(temp_project), exclude_folders=["custom_exclude"])
199
+ result = json.loads(result_json)
200
+
201
+ all_files = []
202
+ for category in ["unused_functions", "unused_imports", "unused_classes", "unused_variables"]:
203
+ for item in result[category]:
204
+ all_files.append(item["file"])
205
+
206
+ excluded_files = [f for f in all_files if "custom_exclude" in f]
207
+ assert len(excluded_files) == 0, f"Found excluded files: {excluded_files}"
208
+
209
+ def test_magic_methods_excluded(self, temp_project):
210
+ magic_py = temp_project / "magic.py"
211
+ magic_py.write_text(dedent("""
212
+ class MagicClass:
213
+ def __init__(self):
214
+ self.value = 0
215
+
216
+ def __str__(self):
217
+ return str(self.value)
218
+
219
+ def __len__(self):
220
+ return self.value
221
+
222
+ def regular_method(self):
223
+ return "regular"
224
+ """))
225
+
226
+ result_json = skylos.analyze(str(temp_project))
227
+ result = json.loads(result_json)
228
+
229
+ unused_functions = result["unused_functions"]
230
+ function_names = [f["name"] for f in unused_functions]
231
+
232
+ magic_methods = ["__init__", "__str__", "__len__"]
233
+ for magic_method in magic_methods:
234
+ assert not any(magic_method in name for name in function_names)
235
+
236
+ def test_test_methods_excluded(self, temp_project):
237
+ """Test that test methods are not flagged as unused"""
238
+ result_json = skylos.analyze(str(temp_project))
239
+ result = json.loads(result_json)
240
+
241
+ unused_functions = result["unused_functions"]
242
+ function_names = [f["name"] for f in unused_functions]
243
+
244
+ test_methods = ["test_used_function", "test_unused_method"]
245
+ for test_method in test_methods:
246
+ assert not any(test_method in name for name in function_names)
247
+
248
+ def test_exported_functions_excluded(self, temp_project):
249
+ result_json = skylos.analyze(str(temp_project))
250
+ result = json.loads(result_json)
251
+
252
+ unused_functions = result["unused_functions"]
253
+ function_names = [f["name"] for f in unused_functions]
254
+
255
+ assert not any("public_function" in name for name in function_names)
256
+
257
+ def test_single_file_analysis(self, temp_project):
258
+ main_file = temp_project / "main.py"
259
+
260
+ result_json = skylos.analyze(str(main_file))
261
+ result = json.loads(result_json)
262
+
263
+ # should still detect unused items in the single file
264
+ assert len(result["unused_functions"]) > 0
265
+ assert len(result["unused_imports"]) > 0
266
+
267
+ all_files = set()
268
+ for category in ["unused_functions", "unused_imports", "unused_variables"]:
269
+ for item in result[category]:
270
+ all_files.add(Path(item["file"]).name)
271
+
272
+ assert "main.py" in all_files
273
+ assert "utils.py" not in all_files
274
+
275
+ def test_confidence_levels(self, temp_project):
276
+ result_json = skylos.analyze(str(temp_project))
277
+ result = json.loads(result_json)
278
+
279
+ for category in ["unused_functions", "unused_imports", "unused_variables"]:
280
+ for item in result[category]:
281
+ assert "confidence" in item
282
+ assert isinstance(item["confidence"], (int, float))
283
+ assert 0 <= item["confidence"] <= 100
284
+
285
+ def test_analyzer_class_direct(self, temp_project):
286
+ analyzer = Skylos()
287
+ result_json = analyzer.analyze(str(temp_project), thr=50)
288
+ result = json.loads(result_json)
289
+
290
+ expected_keys = [
291
+ "unused_functions", "unused_imports", "unused_classes",
292
+ "unused_variables", "unused_parameters", "analysis_summary"
293
+ ]
294
+
295
+ for key in expected_keys:
296
+ assert key in result
297
+
298
+ def test_empty_project(self):
299
+ with tempfile.TemporaryDirectory() as temp_dir:
300
+ result_json = skylos.analyze(temp_dir)
301
+ result = json.loads(result_json)
302
+
303
+ assert result["unused_functions"] == []
304
+ assert result["unused_imports"] == []
305
+ assert result["analysis_summary"]["total_files"] == 0
306
+
307
+ def test_threshold_filtering(self, temp_project):
308
+ """Test that confidence threshold filtering works"""
309
+ # high threshold = find fewer items
310
+ result_high = json.loads(skylos.analyze(str(temp_project), conf=95))
311
+
312
+ # low threshold = should find more items
313
+ result_low = json.loads(skylos.analyze(str(temp_project), conf=10))
314
+
315
+ assert len(result_high["unused_functions"]) <= len(result_low["unused_functions"])
316
+ assert len(result_high["unused_imports"]) <= len(result_low["unused_imports"])
317
+
318
+
319
+ if __name__ == "__main__":
320
+ pytest.main([__file__, "-v"])