Files
wildminder-ComfyUI-Chatterbox/tests/test_hf_progress.py
T
WildAi dc29843483 test: add mock-based suite
Vendored tree mocked at collection, so the suite runs
green without perth, diffusers, s3tokenizer or conformer.
Frozen widget snapshot pins node ids and widget order.
2026-10-06 18:36:08 +03:00

472 lines
17 KiB
Python

"""S4.1 -- the vendored ``src/hf_progress`` package.
The package is lifted from ``ComfyUI-VoxCPM`` and is deliberately reusable, so
what is asserted here is the contract a consumer relies on, not the internals:
the serialisable event shape, the cancel hook injection, the download chunk
patch, and the re-export surface named in the plan.
"""
from __future__ import annotations
import queue
import os
import pytest
from ComfyUI_Chatterbox.src import hf_progress
from ComfyUI_Chatterbox.src.hf_progress import (
DownloadProgressTqdm,
EventType,
HfProgressTracker,
ProgressEvent,
ProgressPhase,
TokenError,
TransferCancelledError,
TransferDirection,
TransferError,
TransferProgressError,
TransferResult,
XetCredentials,
XetTokenManager,
download_file,
download_snapshot,
generate_transfer_id,
is_xet_available,
patch_download_chunk_size,
tqdm_upload_patcher,
)
from ComfyUI_Chatterbox.src.hf_progress.callbacks import state_manager
SRC_HF_PROGRESS = os.path.dirname(os.path.abspath(hf_progress.__file__))
# Names the plan's §"public contract" section requires the package to re-export.
REQUIRED_REEXPORTS = (
"HfProgressTracker",
"ProgressEvent",
"EventType",
"TransferDirection",
"ProgressPhase",
"TransferError",
"TransferResult",
"TransferCancelledError",
"TransferProgressError",
"TokenError",
"generate_transfer_id",
"patch_download_chunk_size",
"download_file",
"download_snapshot",
"DownloadProgressTqdm",
"tqdm_upload_patcher",
"XetCredentials",
"XetTokenManager",
"is_xet_available",
)
class TestReExportSurface:
@pytest.mark.parametrize("name", REQUIRED_REEXPORTS)
def test_name_is_exported(self, name: str) -> None:
assert name in hf_progress.__all__, f"{name} is missing from __all__"
assert hasattr(hf_progress, name), f"{name} is listed but not defined"
def test_all_is_importable_as_a_whole(self) -> None:
"""Every advertised name resolves -- a stale __all__ is a real bug."""
for name in hf_progress.__all__:
assert getattr(hf_progress, name, None) is not None, name
def test_package_has_a_version(self) -> None:
assert hf_progress.__version__
class TestRelativeImportsOnly:
"""Plan §3.1.5: no absolute self-references.
The borrowed VoxCPM tree mixed ``from src.hf_progress import ...`` with
relative imports and depended on a ``sys.path.append`` in the node's
``__init__.py``. A vendored library that names itself absolutely only works
when the host puts its own root on ``sys.path`` first.
"""
@pytest.mark.parametrize(
"filename",
sorted(
name
for name in os.listdir(SRC_HF_PROGRESS)
if name.endswith(".py")
),
)
def test_no_absolute_self_reference(self, filename: str) -> None:
with open(os.path.join(SRC_HF_PROGRESS, filename), "r", encoding="utf-8") as handle:
source = handle.read()
assert "from src.hf_progress" not in source
assert "import src.hf_progress" not in source
assert "from src." not in source
@pytest.mark.parametrize("filename", ["__init__.py", "tracker.py", "callbacks.py"])
def test_module_does_not_touch_sys_path(self, filename: str) -> None:
"""Checked on the AST: the string may appear in prose, but never as
an attribute access or an assignment."""
import ast
with open(os.path.join(SRC_HF_PROGRESS, filename), "r", encoding="utf-8") as handle:
tree = ast.parse(handle.read(), filename=filename)
for node in ast.walk(tree):
if isinstance(node, ast.Attribute) and node.attr == "path":
value = node.value
assert not (isinstance(value, ast.Name) and value.id == "sys"), (
f"{filename} touches sys.path"
)
class TestProgressEventShape:
"""``ProgressEvent.to_dict()`` is the JSON a websocket carries."""
@pytest.fixture()
def event(self) -> ProgressEvent:
return ProgressEvent(
event_type=EventType.PROGRESS,
transfer_id="t-1",
direction=TransferDirection.DOWNLOAD,
filename="ve.safetensors",
phase=ProgressPhase.DOWNLOADING,
bytes_completed=512,
total_bytes=1024,
percentage=50.0,
speed=256.0,
)
def test_always_present_keys(self, event: ProgressEvent) -> None:
payload = event.to_dict()
for key in (
"event_type",
"transfer_id",
"direction",
"filename",
"phase",
"bytes_completed",
"total_bytes",
"percentage",
"speed",
"file_index",
"total_files",
"timestamp",
):
assert key in payload, f"{key} must always be serialisable"
def test_values_are_plain_json_types(self, event: ProgressEvent) -> None:
payload = event.to_dict()
assert payload["event_type"] == "progress"
assert payload["direction"] == "download"
assert payload["phase"] == "downloading"
assert payload["bytes_completed"] == 512
assert payload["total_bytes"] == 1024
assert payload["file_index"] == 0
assert payload["total_files"] == 1
assert isinstance(payload["timestamp"], float)
def test_percentage_is_rounded_to_two_places(self) -> None:
event = ProgressEvent(
event_type=EventType.PROGRESS,
transfer_id="t",
direction=TransferDirection.DOWNLOAD,
filename="f",
phase=ProgressPhase.DOWNLOADING,
percentage=33.333333,
)
assert event.to_dict()["percentage"] == 33.33
def test_xet_only_fields_are_omitted_when_zero(self, event: ProgressEvent) -> None:
payload = event.to_dict()
assert "transfer_bytes_completed" not in payload
assert "dedup_saved_bytes" not in payload
assert "error" not in payload
assert "extra" not in payload
def test_xet_only_fields_appear_once_set(self, event: ProgressEvent) -> None:
event.transfer_bytes_completed = 100
event.transfer_bytes_total = 200
event.transfer_speed = 3.0
event.dedup_saved_bytes = 7
payload = event.to_dict()
assert payload["transfer_bytes_completed"] == 100
assert payload["transfer_bytes_total"] == 200
assert payload["transfer_speed"] == 3.0
assert payload["dedup_saved_bytes"] == 7
def test_error_is_serialised_as_a_dict(self) -> None:
event = ProgressEvent.error_event(
transfer_id="t",
direction=TransferDirection.DOWNLOAD,
filename="f",
error=TransferError(message="boom", error_type="OSError", retryable=True),
)
payload = event.to_dict()
assert payload["event_type"] == "error"
assert payload["error"] == {
"message": "boom",
"error_type": "OSError",
"retryable": True,
}
def test_round_trips_through_from_dict(self, event: ProgressEvent) -> None:
event.extra = {"note": "x"}
restored = ProgressEvent.from_dict(event.to_dict())
assert restored.transfer_id == event.transfer_id
assert restored.event_type is EventType.PROGRESS
assert restored.direction is TransferDirection.DOWNLOAD
assert restored.extra == {"note": "x"}
def test_complete_event_forces_one_hundred_percent(self) -> None:
event = ProgressEvent.complete(
transfer_id="t",
direction=TransferDirection.DOWNLOAD,
filename="f",
bytes_completed=10,
total_bytes=10,
)
assert event.to_dict()["percentage"] == 100.0
assert event.to_dict()["event_type"] == "complete"
def test_cancelled_event_uses_its_own_type(self) -> None:
event = ProgressEvent.cancelled_event(
transfer_id="t",
direction=TransferDirection.DOWNLOAD,
filename="f",
)
assert event.event_type is EventType.CANCELLED
assert event.to_dict()["event_type"] == "cancelled"
def test_transfer_error_is_json_safe(self) -> None:
assert TransferError.from_dict(TransferError(message="m").to_dict()).message == "m"
assert str(TransferError(message="m")) == "m"
class TestDownloadProgressTqdmBind:
"""``bind()`` is the trick the whole HTTP download path rests on."""
def test_injects_the_cancel_hook(self) -> None:
calls = []
hook = lambda: bool(calls) or calls.append(1) is None and False # noqa: E731
events: queue.Queue = queue.Queue()
bound = DownloadProgressTqdm.bind(
event_queue=events,
transfer_id="t-9",
filename="ve.safetensors",
is_cancelled=hook,
)
bar = bound(total=10, unit="B", disable=True)
try:
assert bar._is_cancelled is hook
assert bar._transfer_id == "t-9"
assert bar._filename == "ve.safetensors"
assert bar._event_queue is events
finally:
bar.close()
def test_a_true_cancel_hook_raises_on_update(self) -> None:
bound = DownloadProgressTqdm.bind(
event_queue=queue.Queue(),
transfer_id="t-10",
filename="f",
is_cancelled=lambda: True,
)
bar = bound(total=10, unit="B", disable=True)
try:
with pytest.raises(TransferCancelledError):
bar.update(1)
finally:
bar.close()
state_manager.clear_state("t-10")
def test_a_false_cancel_hook_lets_the_bar_advance(self) -> None:
bound = DownloadProgressTqdm.bind(
event_queue=queue.Queue(),
transfer_id="t-11",
filename="f",
is_cancelled=lambda: False,
)
bar = bound(total=10, unit="B", disable=False)
try:
bar.update(1)
assert bar.n == 1
finally:
bar.close()
state_manager.clear_state("t-11")
def test_bound_class_is_named_after_its_transfer(self) -> None:
bound = DownloadProgressTqdm.bind(
event_queue=queue.Queue(),
transfer_id="abcdef123456",
filename="f",
)
assert bound.__name__ == "BoundDownloadTqdm_abcdef12"
def test_two_binds_do_not_share_state(self) -> None:
first = DownloadProgressTqdm.bind(
event_queue=queue.Queue(), transfer_id="a-1", filename="a"
)
second = DownloadProgressTqdm.bind(
event_queue=queue.Queue(), transfer_id="b-1", filename="b"
)
bar_a = first(total=4, unit="B", disable=True)
bar_b = second(total=4, unit="B", disable=True)
try:
assert bar_a._transfer_id == "a-1"
assert bar_b._transfer_id == "b-1"
finally:
bar_a.close()
bar_b.close()
state_manager.clear_state("a-1")
state_manager.clear_state("b-1")
class TestPatchDownloadChunkSize:
def test_sets_and_restores_the_hub_constant(self) -> None:
from huggingface_hub import constants as hf_constants
original = hf_constants.DOWNLOAD_CHUNK_SIZE
with patch_download_chunk_size(1234):
assert hf_constants.DOWNLOAD_CHUNK_SIZE == 1234
assert hf_constants.DOWNLOAD_CHUNK_SIZE == original
def test_restores_even_when_the_body_raises(self) -> None:
from huggingface_hub import constants as hf_constants
original = hf_constants.DOWNLOAD_CHUNK_SIZE
with pytest.raises(RuntimeError):
with patch_download_chunk_size(4321):
assert hf_constants.DOWNLOAD_CHUNK_SIZE == 4321
raise RuntimeError("boom")
assert hf_constants.DOWNLOAD_CHUNK_SIZE == original
def test_default_chunk_size_is_smaller_than_the_hub_default(self) -> None:
"""A 256 KiB window reports progress often enough for a live bar."""
from huggingface_hub import constants as hf_constants
assert 256 * 1024 < hf_constants.DOWNLOAD_CHUNK_SIZE
class TestHfProgressTracker:
def test_cancel_registry_round_trips(self) -> None:
tracker = HfProgressTracker(token=None)
assert tracker.is_cancelled("x") is False
tracker.cancel(transfer_id="x")
assert tracker.is_cancelled("x") is True
tracker.cleanup_transfer("x")
assert tracker.is_cancelled("x") is False
def test_event_queue_starts_empty(self) -> None:
tracker = HfProgressTracker(token=None)
assert tracker.event_queue.empty()
def test_generate_transfer_id_is_unique(self) -> None:
assert generate_transfer_id() != generate_transfer_id()
class TestExceptionsAndTokens:
@pytest.mark.parametrize(
"exc_type",
[TransferCancelledError, TransferProgressError, TokenError],
)
def test_exceptions_are_distinct_types(self, exc_type: type) -> None:
assert issubclass(exc_type, Exception)
def test_transfer_result_defaults(self) -> None:
result = TransferResult(
success=True, transfer_id="t", filename="f",
direction=TransferDirection.DOWNLOAD,
)
assert result.direction is TransferDirection.DOWNLOAD
assert result.url is None
assert result.file_size == 0
def test_is_xet_available_is_a_bool(self) -> None:
assert isinstance(is_xet_available(), bool)
def test_xet_credentials_holds_the_fields_the_tracker_reads(self) -> None:
credentials = XetCredentials(
endpoint="https://cas-server.xethub.hf.co",
token_info=("token", 1_700_000_000),
)
assert credentials.endpoint == "https://cas-server.xethub.hf.co"
assert credentials.token_info == ("token", 1_700_000_000)
assert credentials.token_refresher is None
def test_token_manager_can_be_built_without_a_token(self) -> None:
assert XetTokenManager(None) is not None
def test_tqdm_upload_patcher_is_a_context_manager(self) -> None:
patcher = tqdm_upload_patcher(
event_queue=queue.Queue(), transfer_id="u-1", filename="f", total_bytes=1
)
with patcher:
pass
state_manager.clear_state("u-1")
class TestIntegrationsSubpackage:
"""``hf_progress.integrations`` re-exports its SSE factories.
The subpackage shipped with an empty ``__init__``: importing
``hf_progress.integrations`` and finding nothing in it is a trap, and a
static reachability scan sees ``sse.py`` as an orphan. Both are fixed by the
re-export.
"""
def test_the_sse_factories_are_reachable_from_the_subpackage(self) -> None:
from ComfyUI_Chatterbox.src.hf_progress import integrations
assert integrations.__all__ == [
"create_progress_router",
"create_raw_sse_stream",
]
assert callable(integrations.create_progress_router)
assert callable(integrations.create_raw_sse_stream)
def test_the_subpackage_does_not_import_a_web_framework(self) -> None:
"""It must stay importable with no aiohttp/FastAPI installed."""
with open(
os.path.join(SRC_HF_PROGRESS, "integrations", "sse.py"),
"r",
encoding="utf-8",
) as handle:
source = handle.read()
assert "import aiohttp" not in source
assert "from aiohttp" not in source
assert "import fastapi" not in source
class TestModuleFunctionsAreCallable:
"""``download_file`` / ``download_snapshot`` must not import the network
stack until they are called, so importing this module stays cheap."""
def test_download_helpers_defer_their_hub_import(self, monkeypatch) -> None:
import huggingface_hub.file_download as file_download
calls = []
def fake_snapshot_download(**kwargs):
calls.append(kwargs)
return kwargs.get("local_dir", "/tmp/fake")
# The real snapshot_download is imported *inside* the helper, so
# replacing the attribute on the module object is enough.
monkeypatch.setattr(
file_download, "snapshot_download", fake_snapshot_download, raising=False
)
import huggingface_hub
monkeypatch.setattr(
huggingface_hub, "snapshot_download", fake_snapshot_download, raising=False
)
result = download_snapshot(
repo_id="owner/repo",
token=None,
event_queue=queue.Queue(),
transfer_id="s-1",
local_dir="/tmp/fake",
)
assert result == "/tmp/fake"
assert calls, "the download helper never reached huggingface_hub"
assert calls[0]["repo_id"] == "owner/repo"