woolly 0.4.0__tar.gz → 0.5.0__tar.gz
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.
- {woolly-0.4.0 → woolly-0.5.0}/PKG-INFO +1 -1
- {woolly-0.4.0 → woolly-0.5.0}/tests/conftest.py +49 -0
- {woolly-0.4.0 → woolly-0.5.0}/tests/unit/test_commands/test_check.py +99 -64
- {woolly-0.4.0 → woolly-0.5.0}/tests/unit/test_languages/test_base.py +312 -0
- {woolly-0.4.0 → woolly-0.5.0}/tests/unit/test_languages/test_python.py +266 -9
- {woolly-0.4.0 → woolly-0.5.0}/tests/unit/test_languages/test_rust.py +119 -10
- {woolly-0.4.0 → woolly-0.5.0}/tests/unit/test_optional_dependencies.py +36 -45
- {woolly-0.4.0 → woolly-0.5.0}/tests/unit/test_reporters/test_json.py +35 -0
- woolly-0.5.0/tests/unit/test_reporters/test_stdout.py +187 -0
- {woolly-0.4.0 → woolly-0.5.0}/woolly/cache.py +21 -6
- {woolly-0.4.0 → woolly-0.5.0}/woolly/commands/check.py +197 -83
- woolly-0.5.0/woolly/http.py +53 -0
- {woolly-0.4.0 → woolly-0.5.0}/woolly/languages/base.py +187 -10
- woolly-0.5.0/woolly/languages/python.py +413 -0
- {woolly-0.4.0 → woolly-0.5.0}/woolly/languages/rust.py +59 -1
- {woolly-0.4.0 → woolly-0.5.0}/woolly/reporters/base.py +22 -0
- {woolly-0.4.0 → woolly-0.5.0}/woolly/reporters/json.py +96 -2
- {woolly-0.4.0 → woolly-0.5.0}/woolly/reporters/markdown.py +71 -0
- woolly-0.5.0/woolly/reporters/stdout.py +208 -0
- {woolly-0.4.0 → woolly-0.5.0}/woolly/reporters/template.py +30 -0
- woolly-0.4.0/tests/unit/test_reporters/test_stdout.py +0 -133
- woolly-0.4.0/woolly/http.py +0 -34
- woolly-0.4.0/woolly/languages/python.py +0 -200
- woolly-0.4.0/woolly/reporters/stdout.py +0 -76
- {woolly-0.4.0 → woolly-0.5.0}/.coverage +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/.github/workflows/lint.yml +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/.github/workflows/publish.yml +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/.github/workflows/tests.yml +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/.gitignore +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/.python-version +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/LICENSE +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/README.md +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/examples/template/example_template.md +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/pyproject.toml +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/tests/__init__.py +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/tests/functional/__init__.py +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/tests/functional/test_cli.py +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/tests/functional/test_optional_flag.py +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/tests/unit/__init__.py +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/tests/unit/test_cache.py +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/tests/unit/test_commands/__init__.py +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/tests/unit/test_commands/test_other_commands.py +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/tests/unit/test_debug.py +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/tests/unit/test_languages/__init__.py +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/tests/unit/test_languages/test_registry.py +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/tests/unit/test_progress.py +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/tests/unit/test_reporters/__init__.py +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/tests/unit/test_reporters/test_base.py +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/tests/unit/test_reporters/test_markdown.py +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/tests/unit/test_reporters/test_registry.py +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/tests/unit/test_reporters/test_template.py +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/uv.lock +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/woolly/__init__.py +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/woolly/__main__.py +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/woolly/commands/__init__.py +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/woolly/commands/clear_cache.py +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/woolly/commands/list_formats.py +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/woolly/commands/list_languages.py +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/woolly/debug.py +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/woolly/languages/__init__.py +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/woolly/progress.py +0 -0
- {woolly-0.4.0 → woolly-0.5.0}/woolly/reporters/__init__.py +0 -0
|
@@ -24,6 +24,10 @@ def temp_cache_dir(tmp_path, monkeypatch):
|
|
|
24
24
|
cache_dir = tmp_path / "cache"
|
|
25
25
|
cache_dir.mkdir()
|
|
26
26
|
monkeypatch.setattr("woolly.cache.CACHE_DIR", cache_dir)
|
|
27
|
+
# Reset the ensured-namespaces set so that the new CACHE_DIR is used
|
|
28
|
+
import woolly.cache as _cache_mod
|
|
29
|
+
|
|
30
|
+
_cache_mod._ensured_namespaces.clear()
|
|
27
31
|
return cache_dir
|
|
28
32
|
|
|
29
33
|
|
|
@@ -119,6 +123,7 @@ def mock_crates_io_response():
|
|
|
119
123
|
"description": "A serialization framework for Rust",
|
|
120
124
|
"homepage": "https://serde.rs",
|
|
121
125
|
"repository": "https://github.com/serde-rs/serde",
|
|
126
|
+
"license": "MIT OR Apache-2.0",
|
|
122
127
|
}
|
|
123
128
|
}
|
|
124
129
|
|
|
@@ -154,12 +159,55 @@ def mock_pypi_response():
|
|
|
154
159
|
"summary": "Python HTTP for Humans",
|
|
155
160
|
"home_page": "https://requests.readthedocs.io",
|
|
156
161
|
"project_url": "https://github.com/psf/requests",
|
|
162
|
+
"license": "Apache-2.0",
|
|
163
|
+
"license_expression": None,
|
|
164
|
+
"provides_extra": ["socks"],
|
|
165
|
+
"requires_dist": [
|
|
166
|
+
"charset-normalizer<4,>=2",
|
|
167
|
+
"idna<4,>=2.5",
|
|
168
|
+
"urllib3<3,>=1.21.1",
|
|
169
|
+
"certifi>=2017.4.17",
|
|
170
|
+
"PySocks!=1.5.7,>=1.5.6; extra == 'socks'",
|
|
171
|
+
],
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@pytest.fixture
|
|
177
|
+
def mock_crates_io_version_response():
|
|
178
|
+
"""Create a mock crates.io version API response (for features)."""
|
|
179
|
+
return {
|
|
180
|
+
"version": {
|
|
181
|
+
"num": "1.0.200",
|
|
182
|
+
"features": {
|
|
183
|
+
"default": ["std", "derive"],
|
|
184
|
+
"std": [],
|
|
185
|
+
"derive": ["serde_derive"],
|
|
186
|
+
"alloc": [],
|
|
187
|
+
},
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@pytest.fixture
|
|
193
|
+
def mock_pypi_version_response():
|
|
194
|
+
"""Create a mock PyPI version API response (for extras/features)."""
|
|
195
|
+
return {
|
|
196
|
+
"info": {
|
|
197
|
+
"name": "requests",
|
|
198
|
+
"version": "2.31.0",
|
|
199
|
+
"summary": "Python HTTP for Humans",
|
|
200
|
+
"license": "Apache-2.0",
|
|
201
|
+
"license_expression": None,
|
|
202
|
+
"provides_extra": ["socks", "security"],
|
|
157
203
|
"requires_dist": [
|
|
158
204
|
"charset-normalizer<4,>=2",
|
|
159
205
|
"idna<4,>=2.5",
|
|
160
206
|
"urllib3<3,>=1.21.1",
|
|
161
207
|
"certifi>=2017.4.17",
|
|
162
208
|
"PySocks!=1.5.7,>=1.5.6; extra == 'socks'",
|
|
209
|
+
"pyOpenSSL>=0.14; extra == 'security'",
|
|
210
|
+
"cryptography>=1.3.4; extra == 'security'",
|
|
163
211
|
],
|
|
164
212
|
}
|
|
165
213
|
}
|
|
@@ -174,6 +222,7 @@ def mock_pypi_response():
|
|
|
174
222
|
def mock_console():
|
|
175
223
|
"""Create a mock Rich console."""
|
|
176
224
|
console = MagicMock(spec=Console)
|
|
225
|
+
console.width = 120 # Wide enough for side-by-side layout
|
|
177
226
|
return console
|
|
178
227
|
|
|
179
228
|
|
|
@@ -12,7 +12,7 @@ from unittest.mock import MagicMock
|
|
|
12
12
|
import pytest
|
|
13
13
|
from rich.tree import Tree
|
|
14
14
|
|
|
15
|
-
from woolly.commands.check import TreeStats,
|
|
15
|
+
from woolly.commands.check import TreeStats, _compute_stats_from_visited, build_tree
|
|
16
16
|
from woolly.languages.base import (
|
|
17
17
|
Dependency,
|
|
18
18
|
FedoraPackageStatus,
|
|
@@ -41,6 +41,9 @@ class MockProvider(LanguageProvider):
|
|
|
41
41
|
def fetch_dependencies(self, package_name: str, version: str):
|
|
42
42
|
return self.dependencies.get(f"{package_name}:{version}", [])
|
|
43
43
|
|
|
44
|
+
def fetch_features(self, package_name: str, version: str):
|
|
45
|
+
return []
|
|
46
|
+
|
|
44
47
|
def check_fedora_packaging(self, package_name: str):
|
|
45
48
|
return self.fedora_status.get(
|
|
46
49
|
package_name, FedoraPackageStatus(is_packaged=False)
|
|
@@ -106,7 +109,7 @@ class TestBuildTree:
|
|
|
106
109
|
@pytest.mark.unit
|
|
107
110
|
def test_returns_visited_marker_for_duplicate(self, provider):
|
|
108
111
|
"""Critical path: returns visited marker for already-visited packages."""
|
|
109
|
-
visited = {"root": (True, "1.0.0")}
|
|
112
|
+
visited = {"root": (True, "1.0.0", False)}
|
|
110
113
|
|
|
111
114
|
result = build_tree(provider, "root", visited=visited)
|
|
112
115
|
|
|
@@ -157,6 +160,32 @@ class TestBuildTree:
|
|
|
157
160
|
label = str(tree.label)
|
|
158
161
|
assert "1.0.0" in label
|
|
159
162
|
|
|
163
|
+
@pytest.mark.unit
|
|
164
|
+
def test_includes_license_in_label(self, provider):
|
|
165
|
+
"""Good path: license is shown in tree label."""
|
|
166
|
+
provider.packages["licensed-pkg"] = PackageInfo(
|
|
167
|
+
name="licensed-pkg", latest_version="1.0.0", license="MIT"
|
|
168
|
+
)
|
|
169
|
+
provider.fedora_status["licensed-pkg"] = FedoraPackageStatus(
|
|
170
|
+
is_packaged=True, versions=["1.0.0"]
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
tree = build_tree(provider, "licensed-pkg")
|
|
174
|
+
|
|
175
|
+
assert isinstance(tree, Tree)
|
|
176
|
+
label = str(tree.label)
|
|
177
|
+
assert "MIT" in label
|
|
178
|
+
|
|
179
|
+
@pytest.mark.unit
|
|
180
|
+
def test_no_license_marker_when_no_license(self, provider):
|
|
181
|
+
"""Good path: no license marker when package has no license."""
|
|
182
|
+
tree = build_tree(provider, "root")
|
|
183
|
+
|
|
184
|
+
assert isinstance(tree, Tree)
|
|
185
|
+
label = str(tree.label)
|
|
186
|
+
# License info from mock_package_info is None, so no license marker
|
|
187
|
+
assert "(None)" not in label
|
|
188
|
+
|
|
160
189
|
@pytest.mark.unit
|
|
161
190
|
def test_updates_progress_tracker(self, provider):
|
|
162
191
|
"""Good path: updates progress tracker when provided."""
|
|
@@ -291,115 +320,121 @@ class TestBuildTree:
|
|
|
291
320
|
assert len(tree.children) == 1
|
|
292
321
|
|
|
293
322
|
|
|
294
|
-
class
|
|
295
|
-
"""Tests for
|
|
323
|
+
class TestComputeStatsFromVisited:
|
|
324
|
+
"""Tests for _compute_stats_from_visited function."""
|
|
296
325
|
|
|
297
326
|
@pytest.mark.unit
|
|
298
327
|
def test_returns_tree_stats_model(self):
|
|
299
328
|
"""Good path: returns TreeStats model."""
|
|
300
|
-
|
|
329
|
+
visited = {"root": (True, "1.0.0", False)}
|
|
301
330
|
|
|
302
|
-
stats =
|
|
331
|
+
stats = _compute_stats_from_visited(visited)
|
|
303
332
|
|
|
304
333
|
assert isinstance(stats, TreeStats)
|
|
305
334
|
|
|
306
335
|
@pytest.mark.unit
|
|
307
336
|
def test_counts_packaged(self):
|
|
308
337
|
"""Good path: counts packaged packages."""
|
|
309
|
-
|
|
338
|
+
visited = {"root": (True, "1.0.0", False)}
|
|
310
339
|
|
|
311
|
-
stats =
|
|
340
|
+
stats = _compute_stats_from_visited(visited)
|
|
312
341
|
|
|
313
|
-
assert stats.packaged
|
|
342
|
+
assert stats.packaged == 1
|
|
314
343
|
|
|
315
344
|
@pytest.mark.unit
|
|
316
345
|
def test_counts_missing(self):
|
|
317
346
|
"""Good path: counts missing packages."""
|
|
318
|
-
|
|
347
|
+
visited = {"root": (False, "1.0.0", False)}
|
|
319
348
|
|
|
320
|
-
stats =
|
|
349
|
+
stats = _compute_stats_from_visited(visited)
|
|
321
350
|
|
|
322
|
-
assert stats.missing
|
|
351
|
+
assert stats.missing == 1
|
|
323
352
|
|
|
324
353
|
@pytest.mark.unit
|
|
325
354
|
def test_collects_missing_list(self):
|
|
326
355
|
"""Good path: collects list of missing packages."""
|
|
327
|
-
|
|
356
|
+
visited = {"missing-pkg": (False, "1.0.0", False)}
|
|
328
357
|
|
|
329
|
-
stats =
|
|
358
|
+
stats = _compute_stats_from_visited(visited)
|
|
330
359
|
|
|
331
|
-
assert len(stats.missing_list)
|
|
360
|
+
assert len(stats.missing_list) == 1
|
|
361
|
+
assert "missing-pkg" in stats.missing_list
|
|
332
362
|
|
|
333
363
|
@pytest.mark.unit
|
|
334
364
|
def test_collects_packaged_list(self):
|
|
335
365
|
"""Good path: collects list of packaged packages."""
|
|
336
|
-
|
|
366
|
+
visited = {"pkg": (True, "1.0.0", False)}
|
|
337
367
|
|
|
338
|
-
stats =
|
|
368
|
+
stats = _compute_stats_from_visited(visited)
|
|
339
369
|
|
|
340
|
-
assert len(stats.packaged_list)
|
|
370
|
+
assert len(stats.packaged_list) == 1
|
|
371
|
+
assert "pkg" in stats.packaged_list
|
|
341
372
|
|
|
342
373
|
@pytest.mark.unit
|
|
343
|
-
def
|
|
344
|
-
"""Good path:
|
|
345
|
-
|
|
346
|
-
tree.add("[dim]child[/dim] • [green]✓[/green] (already visited)")
|
|
374
|
+
def test_counts_not_found_as_missing(self):
|
|
375
|
+
"""Good path: counts not-found packages (version=None) as missing."""
|
|
376
|
+
visited = {"unknown": (False, None, False)}
|
|
347
377
|
|
|
348
|
-
stats =
|
|
378
|
+
stats = _compute_stats_from_visited(visited)
|
|
349
379
|
|
|
350
|
-
assert stats.
|
|
380
|
+
assert stats.missing == 1
|
|
381
|
+
assert "unknown" in stats.missing_list
|
|
351
382
|
|
|
352
383
|
@pytest.mark.unit
|
|
353
|
-
def
|
|
354
|
-
"""
|
|
355
|
-
|
|
384
|
+
def test_multiple_packages(self):
|
|
385
|
+
"""Critical path: counts all packages from visited dict."""
|
|
386
|
+
visited = {
|
|
387
|
+
"root": (True, "1.0.0", False),
|
|
388
|
+
"child1": (True, "1.0.0", False),
|
|
389
|
+
"child2": (False, "1.0.0", False),
|
|
390
|
+
}
|
|
356
391
|
|
|
357
|
-
stats =
|
|
392
|
+
stats = _compute_stats_from_visited(visited)
|
|
358
393
|
|
|
359
|
-
assert stats.
|
|
394
|
+
assert stats.total == 3
|
|
395
|
+
assert stats.packaged == 2
|
|
396
|
+
assert stats.missing == 1
|
|
360
397
|
|
|
361
398
|
@pytest.mark.unit
|
|
362
|
-
def
|
|
363
|
-
"""
|
|
364
|
-
|
|
365
|
-
tree = Tree("[bold]root[/bold] v1.0.0 • [green]✓ packaged[/green]")
|
|
366
|
-
# Add a child that represents a package not found on the registry (string format)
|
|
367
|
-
tree.add(
|
|
368
|
-
"[bold red]nonexistent-pkg[/bold red] • [red]not found on crates.io[/red]"
|
|
369
|
-
)
|
|
370
|
-
|
|
371
|
-
stats = collect_stats(tree)
|
|
399
|
+
def test_empty_visited(self):
|
|
400
|
+
"""Good path: handles empty visited dict."""
|
|
401
|
+
stats = _compute_stats_from_visited({})
|
|
372
402
|
|
|
373
|
-
assert stats.
|
|
374
|
-
assert
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
403
|
+
assert stats.total == 0
|
|
404
|
+
assert stats.packaged == 0
|
|
405
|
+
assert stats.missing == 0
|
|
406
|
+
assert stats.missing_list == []
|
|
407
|
+
assert stats.packaged_list == []
|
|
378
408
|
|
|
379
409
|
@pytest.mark.unit
|
|
380
|
-
def
|
|
381
|
-
"""
|
|
382
|
-
|
|
383
|
-
child1 = Tree("[bold]child1[/bold] v1.0.0 • [green]✓ packaged[/green]")
|
|
384
|
-
child2 = Tree("[bold]child2[/bold] v1.0.0 • [red]✗ not packaged[/red]")
|
|
385
|
-
root.children.append(child1)
|
|
386
|
-
root.children.append(child2)
|
|
410
|
+
def test_has_dev_build_stats(self):
|
|
411
|
+
"""Good path: stats model has dev/build dependency stats (zeroed by default)."""
|
|
412
|
+
visited = {"pkg": (True, "1.0.0", False)}
|
|
387
413
|
|
|
388
|
-
stats =
|
|
414
|
+
stats = _compute_stats_from_visited(visited)
|
|
389
415
|
|
|
390
|
-
assert stats
|
|
391
|
-
assert stats
|
|
392
|
-
assert stats
|
|
416
|
+
assert hasattr(stats, "dev_total")
|
|
417
|
+
assert hasattr(stats, "dev_packaged")
|
|
418
|
+
assert hasattr(stats, "dev_missing")
|
|
419
|
+
assert hasattr(stats, "build_total")
|
|
420
|
+
assert hasattr(stats, "build_packaged")
|
|
421
|
+
assert hasattr(stats, "build_missing")
|
|
422
|
+
assert stats.dev_total == 0
|
|
423
|
+
assert stats.build_total == 0
|
|
393
424
|
|
|
394
425
|
@pytest.mark.unit
|
|
395
|
-
def
|
|
396
|
-
"""Good path:
|
|
397
|
-
|
|
426
|
+
def test_optional_dependency_tracking(self):
|
|
427
|
+
"""Good path: tracks optional dependency statistics."""
|
|
428
|
+
visited = {
|
|
429
|
+
"required": (True, "1.0.0", False),
|
|
430
|
+
"opt-packaged": (True, "2.0.0", True),
|
|
431
|
+
"opt-missing": (False, "3.0.0", True),
|
|
432
|
+
}
|
|
398
433
|
|
|
399
|
-
stats =
|
|
434
|
+
stats = _compute_stats_from_visited(visited)
|
|
400
435
|
|
|
401
|
-
assert
|
|
402
|
-
assert
|
|
403
|
-
assert
|
|
404
|
-
assert
|
|
405
|
-
assert
|
|
436
|
+
assert stats.total == 3
|
|
437
|
+
assert stats.optional_total == 2
|
|
438
|
+
assert stats.optional_packaged == 1
|
|
439
|
+
assert stats.optional_missing == 1
|
|
440
|
+
assert "opt-missing" in stats.optional_missing_list
|