Spaces:
Runtime error
Runtime error
| from __future__ import annotations | |
| from pathlib import Path | |
| from types import SimpleNamespace | |
| from PIL import Image | |
| import pytest | |
| import kneiff.utils.image.caption.blip as caption_blip | |
| import kneiff.utils.image.caption.server as caption_server | |
| from kneiff.progress import ProgressUpdate | |
| from kneiff.utils.image.caption.batch import ( | |
| CaptionBatchItemResult, | |
| plan_caption_batch, | |
| ) | |
| from kneiff.utils.image.caption.io import CaptionSidecarIO | |
| def test_caption_batch_plan_excludes_existing_sidecars(tmp_path: Path) -> None: | |
| first = tmp_path / "first.png" | |
| second = tmp_path / "second.png" | |
| Image.new("RGB", (2, 2), color="red").save(first) | |
| Image.new("RGB", (2, 2), color="blue").save(second) | |
| first.with_suffix(".cap.txt").write_text("existing", encoding="utf-8") | |
| jobs = plan_caption_batch(tmp_path, CaptionSidecarIO()) | |
| assert [job.image_path for job in jobs] == [second] | |
| def test_server_caption_batch_reports_written_and_caught_failures( | |
| monkeypatch: pytest.MonkeyPatch, | |
| tmp_path: Path, | |
| ) -> None: | |
| first = tmp_path / "first.png" | |
| second = tmp_path / "second.png" | |
| Image.new("RGB", (2, 2), color="red").save(first) | |
| Image.new("RGB", (2, 2), color="blue").save(second) | |
| sidecar_io = CaptionSidecarIO() | |
| jobs = plan_caption_batch(tmp_path, sidecar_io) | |
| calls = 0 | |
| monkeypatch.setattr( | |
| caption_server, | |
| "load_openai_backend_config", | |
| lambda **kwargs: SimpleNamespace(model="test-model"), | |
| ) | |
| monkeypatch.setattr(caption_server, "build_openai_client", lambda cfg: object()) | |
| monkeypatch.setattr( | |
| caption_server, | |
| "image_to_data_url", | |
| lambda *args, **kwargs: "data:image/jpeg;base64,test", | |
| ) | |
| def fake_caption_one(*args: object, **kwargs: object) -> str: | |
| nonlocal calls | |
| calls += 1 | |
| if calls == 2: | |
| raise RuntimeError("server unavailable") | |
| return "A concise caption." | |
| monkeypatch.setattr(caption_server, "caption_one", fake_caption_one) | |
| updates: list[ProgressUpdate] = [] | |
| items: list[CaptionBatchItemResult] = [] | |
| instructions: list[str] = [] | |
| result = caption_server.run_caption_batch_with_server( | |
| jobs, | |
| sidecar_io=sidecar_io, | |
| model_name="test-model", | |
| progress_callback=updates.append, | |
| instruction_callback=instructions.append, | |
| item_callback=items.append, | |
| ) | |
| assert result.count("written") == 1 | |
| assert result.count("failed") == 1 | |
| assert first.with_suffix(".cap.txt").is_file() | |
| assert not second.with_suffix(".cap.txt").exists() | |
| assert len(instructions) == 2 | |
| assert [item.status for item in items] == ["written", "failed"] | |
| assert sum(update.advance for update in updates) == 2 | |
| class _FakeCaptioner: | |
| def caption(self, image: Image.Image, instruction: str, gen: object) -> str: | |
| del image, instruction, gen | |
| return "A local caption." | |
| def test_blip_caption_batch_advances_for_image_open_skip( | |
| monkeypatch: pytest.MonkeyPatch, | |
| tmp_path: Path, | |
| ) -> None: | |
| invalid = tmp_path / "invalid.png" | |
| valid = tmp_path / "valid.png" | |
| invalid.write_bytes(b"not an image") | |
| Image.new("RGB", (2, 2), color="green").save(valid) | |
| sidecar_io = CaptionSidecarIO() | |
| jobs = plan_caption_batch(tmp_path, sidecar_io) | |
| monkeypatch.setattr( | |
| caption_blip, | |
| "build_captioner", | |
| lambda model_name, backend: _FakeCaptioner(), | |
| ) | |
| updates: list[ProgressUpdate] = [] | |
| result = caption_blip.run_caption_batch_with_blip( | |
| jobs, | |
| sidecar_io=sidecar_io, | |
| progress_callback=updates.append, | |
| ) | |
| assert result.count("skipped") == 1 | |
| assert result.count("written") == 1 | |
| assert valid.with_suffix(".cap.txt").is_file() | |
| assert sum(update.advance for update in updates) == 2 | |
| def test_blip_inference_failure_propagates_with_incomplete_progress( | |
| monkeypatch: pytest.MonkeyPatch, | |
| tmp_path: Path, | |
| ) -> None: | |
| source = tmp_path / "source.png" | |
| Image.new("RGB", (2, 2), color="green").save(source) | |
| sidecar_io = CaptionSidecarIO() | |
| jobs = plan_caption_batch(tmp_path, sidecar_io) | |
| class FailingCaptioner: | |
| def caption(self, image: Image.Image, instruction: str, gen: object) -> str: | |
| del image, instruction, gen | |
| raise RuntimeError("inference failed") | |
| monkeypatch.setattr( | |
| caption_blip, | |
| "build_captioner", | |
| lambda model_name, backend: FailingCaptioner(), | |
| ) | |
| updates: list[ProgressUpdate] = [] | |
| with pytest.raises(RuntimeError, match="inference failed"): | |
| caption_blip.run_caption_batch_with_blip( | |
| jobs, | |
| sidecar_io=sidecar_io, | |
| progress_callback=updates.append, | |
| ) | |
| assert updates == [] | |
| assert not source.with_suffix(".cap.txt").exists() | |