kneifftools / tests /test_jtp3_wrapper.py
kneiff's picture
chore(history)!: publish anonymous repository root
2857cf3
Raw
History Blame Contribute Delete
11.8 kB
from __future__ import annotations
import os
import subprocess
import sys
from pathlib import Path
import pytest
import kneiff.utils.image.tag_jtp3 as tag_jtp3
UPSTREAM_TAGS = r"from side, artist \(name\)"
def _write_current_hydra_snapshot(snapshot: Path) -> None:
snapshot.mkdir()
(snapshot / "inference.py").write_text(
'group.add_argument("-b", "--batch-size", type=int, default=1)\n',
encoding="utf-8",
)
def _patch_jtp3_subprocess(
tmp_path: Path,
monkeypatch,
) -> dict[str, object]:
snapshot = tmp_path / "snapshot"
calls: dict[str, object] = {}
def fake_snapshot_download(**kwargs: object) -> str:
calls["snapshot_kwargs"] = kwargs
return str(snapshot)
def fake_run(
command: list[str],
*,
cwd: Path,
env: dict[str, str],
check: bool,
text: bool,
) -> subprocess.CompletedProcess[str]:
calls["run"] = (command, cwd, env, check, text)
for value in command:
path = Path(value)
if path.suffix.lower() in {".png", ".jpg", ".jpeg", ".webp"}:
path.with_suffix(".txt").write_text(
f"{UPSTREAM_TAGS}\n", encoding="utf-8"
)
return subprocess.CompletedProcess(command, 0)
monkeypatch.setattr(tag_jtp3, "snapshot_download", fake_snapshot_download)
monkeypatch.setattr(tag_jtp3.subprocess, "run", fake_run)
return calls
def test_build_jtp3_command_uses_stdout_csv_and_snapshot_cwd(tmp_path: Path) -> None:
snapshot = tmp_path / "snapshot"
config = tag_jtp3.Jtp3RunConfig(
paths=[Path("images")],
recursive=True,
threshold=0.35,
device="cpu",
batch_size=4,
workers=2,
seqlen=2048,
prefix="knf-",
csv_stdout=True,
)
command = tag_jtp3.build_jtp3_command(snapshot, config)
assert command[:2] == [sys.executable, str(snapshot / "inference.py")]
assert "--output" in command
assert "-" in command
assert "--recursive" in command
assert ["--device", "cpu"] == command[
command.index("--device") : command.index("--device") + 2
]
def test_build_jtp3_command_uses_current_hydra_cli_shape(tmp_path: Path) -> None:
snapshot = tmp_path / "snapshot"
_write_current_hydra_snapshot(snapshot)
config = tag_jtp3.Jtp3RunConfig(
paths=[Path("image.png")],
recursive=True,
device="cpu",
batch_size=4,
workers=2,
seqlen=2048,
prefix="knf-",
csv_stdout=True,
)
command = tag_jtp3.build_jtp3_command(snapshot, config)
assert command[:4] == [
sys.executable,
"-m",
"kneiff.utils.image.jtp3_upstream_runner",
str(snapshot / "inference.py"),
]
assert "--threshold" not in command
assert "--batch" not in command
assert ["--model", tag_jtp3.CURRENT_HYDRA_JTP3_MODEL] == command[
command.index("--model") : command.index("--model") + 2
]
assert ["--batch-size", "4"] == command[
command.index("--batch-size") : command.index("--batch-size") + 2
]
assert ["--seqlen", "2048"] == command[
command.index("--seqlen") : command.index("--seqlen") + 2
]
assert ["--workers", "2"] == command[
command.index("--workers") : command.index("--workers") + 2
]
assert ["--prefix", "knf-"] == command[
command.index("--prefix") : command.index("--prefix") + 2
]
assert ["--device", "cpu"] == command[
command.index("--device") : command.index("--device") + 2
]
assert "--recursive" in command
assert "--output" in command
assert "-" in command
assert command[-1] == "image.png"
def test_build_jtp3_command_rejects_legacy_threshold_for_current_hydra(
tmp_path: Path,
) -> None:
snapshot = tmp_path / "snapshot"
_write_current_hydra_snapshot(snapshot)
config = tag_jtp3.Jtp3RunConfig(
paths=[Path("image.png")],
threshold=0.35,
)
with pytest.raises(ValueError, match="--threshold"):
tag_jtp3.build_jtp3_command(snapshot, config)
def test_build_jtp3_environment_prepends_snapshot_to_pythonpath(
tmp_path: Path,
monkeypatch,
) -> None:
snapshot = tmp_path / "snapshot"
monkeypatch.setenv("PYTHONPATH", "existing")
env = tag_jtp3.build_jtp3_environment(snapshot)
assert env["PYTHONPATH"] == os.pathsep.join([str(snapshot), "existing"])
def test_parse_jtp3_sidecar_tags_ignores_empty_tokens() -> None:
assert tag_jtp3.parse_jtp3_sidecar_tags(" alpha, beta ,, gamma\n") == [
"alpha",
"beta",
"gamma",
]
def test_format_jtp3_tags_defaults_to_e621_whitespace_output() -> None:
assert (
tag_jtp3.format_jtp3_tags(
[r"from side", r"artist \(name\)"],
comma_separated=False,
)
== "from_side artist_(name)"
)
def test_format_jtp3_tags_can_keep_legacy_commas() -> None:
assert (
tag_jtp3.format_jtp3_tags(
[r"from side", r"artist \(name\)"],
comma_separated=True,
)
== r"from side, artist \(name\)"
)
def test_run_jtp3_defaults_to_stdout_without_touching_original_sidecar(
tmp_path: Path,
monkeypatch,
capsys,
) -> None:
image_path = tmp_path / "image.png"
image_path.write_bytes(b"image")
sidecar_path = image_path.with_suffix(".txt")
sidecar_path.write_text("keep me\n", encoding="utf-8")
calls = _patch_jtp3_subprocess(tmp_path, monkeypatch)
tag_jtp3.run_jtp3(tag_jtp3.Jtp3RunConfig(paths=[image_path]))
assert capsys.readouterr().out == "from_side artist_(name)\n"
assert sidecar_path.read_text(encoding="utf-8") == "keep me\n"
command, _cwd, _env, _check, _text = calls["run"]
assert isinstance(command, list)
assert str(image_path) not in command
def test_run_jtp3_prints_paths_for_multiple_stdout_images(
tmp_path: Path,
monkeypatch,
capsys,
) -> None:
first_path = tmp_path / "a.png"
second_path = tmp_path / "b.png"
first_path.write_bytes(b"image")
second_path.write_bytes(b"image")
_patch_jtp3_subprocess(tmp_path, monkeypatch)
tag_jtp3.run_jtp3(tag_jtp3.Jtp3RunConfig(paths=[first_path, second_path]))
assert capsys.readouterr().out == (
f"{first_path}\tfrom_side artist_(name)\n"
f"{second_path}\tfrom_side artist_(name)\n"
)
def test_run_jtp3_stdout_can_use_legacy_commas(
tmp_path: Path,
monkeypatch,
capsys,
) -> None:
image_path = tmp_path / "image.png"
image_path.write_bytes(b"image")
_patch_jtp3_subprocess(tmp_path, monkeypatch)
tag_jtp3.run_jtp3(tag_jtp3.Jtp3RunConfig(paths=[image_path], comma_separated=True))
assert capsys.readouterr().out == f"{UPSTREAM_TAGS}\n"
def test_run_jtp3_txt_postprocesses_original_sidecar(
tmp_path: Path,
monkeypatch,
) -> None:
image_path = tmp_path / "image.png"
image_path.write_bytes(b"image")
_patch_jtp3_subprocess(tmp_path, monkeypatch)
tag_jtp3.run_jtp3(tag_jtp3.Jtp3RunConfig(paths=[image_path], write_txt=True))
assert image_path.with_suffix(".txt").read_text(encoding="utf-8") == (
"from_side artist_(name)\n"
)
def test_run_jtp3_txt_comma_preserves_legacy_sidecar(
tmp_path: Path,
monkeypatch,
) -> None:
image_path = tmp_path / "image.png"
image_path.write_bytes(b"image")
_patch_jtp3_subprocess(tmp_path, monkeypatch)
tag_jtp3.run_jtp3(
tag_jtp3.Jtp3RunConfig(
paths=[image_path],
write_txt=True,
comma_separated=True,
)
)
assert image_path.with_suffix(".txt").read_text(encoding="utf-8") == (
f"{UPSTREAM_TAGS}\n"
)
def test_run_jtp3_rejects_csv_stdout_text_modes() -> None:
with pytest.raises(ValueError, match="--csv-stdout"):
tag_jtp3.run_jtp3(
tag_jtp3.Jtp3RunConfig(
paths=[Path("image.png")],
csv_stdout=True,
write_txt=True,
)
)
def test_parse_args_accepts_txt_and_comma_modes() -> None:
args = tag_jtp3.parse_args(["--txt", "--comma", "image.png"])
short_args = tag_jtp3.parse_args(["-c", "image.png"])
assert args.txt is True
assert args.comma_separated is True
assert short_args.comma_separated is True
def test_ensure_jtp3_runtime_dependencies_reports_missing_pyvips(
tmp_path: Path,
monkeypatch,
) -> None:
snapshot = tmp_path / "snapshot"
snapshot.mkdir()
(snapshot / "image.py").write_text("import pyvips\n", encoding="utf-8")
def fake_import_module(name: str, package: str | None = None) -> object:
raise ModuleNotFoundError("No module named 'pyvips'", name="pyvips")
monkeypatch.setattr(tag_jtp3.importlib, "import_module", fake_import_module)
with pytest.raises(tag_jtp3.Jtp3DependencyError, match="pyvips"):
tag_jtp3.ensure_jtp3_runtime_dependencies(snapshot)
def test_ensure_jtp3_runtime_dependencies_reports_missing_libvips(
tmp_path: Path,
monkeypatch,
) -> None:
snapshot = tmp_path / "snapshot"
snapshot.mkdir()
(snapshot / "image.py").write_text("import pyvips\n", encoding="utf-8")
def fake_import_module(name: str, package: str | None = None) -> object:
raise OSError("cannot load library 'vips-42'")
monkeypatch.setattr(tag_jtp3.importlib, "import_module", fake_import_module)
with pytest.raises(tag_jtp3.Jtp3DependencyError, match="libvips"):
tag_jtp3.ensure_jtp3_runtime_dependencies(snapshot)
def test_ensure_jtp3_runtime_dependencies_skips_old_pillow_snapshot(
tmp_path: Path,
monkeypatch,
) -> None:
snapshot = tmp_path / "snapshot"
snapshot.mkdir()
(snapshot / "image.py").write_text("from PIL import Image\n", encoding="utf-8")
import_attempts: list[str] = []
def fake_import_module(name: str, package: str | None = None) -> object:
import_attempts.append(name)
raise AssertionError("old snapshots should not import pyvips")
monkeypatch.setattr(tag_jtp3.importlib, "import_module", fake_import_module)
tag_jtp3.ensure_jtp3_runtime_dependencies(snapshot)
assert import_attempts == []
def test_run_jtp3_downloads_limited_snapshot_and_runs_in_snapshot(
tmp_path: Path,
monkeypatch,
) -> None:
snapshot = tmp_path / "snapshot"
calls: dict[str, object] = {}
def fake_snapshot_download(**kwargs: object) -> str:
calls["snapshot_kwargs"] = kwargs
return str(snapshot)
def fake_run(
command: list[str],
*,
cwd: Path,
env: dict[str, str],
check: bool,
text: bool,
) -> subprocess.CompletedProcess[str]:
calls["run"] = (command, cwd, env, check, text)
return subprocess.CompletedProcess(command, 0)
monkeypatch.setattr(tag_jtp3, "snapshot_download", fake_snapshot_download)
monkeypatch.setattr(tag_jtp3.subprocess, "run", fake_run)
result = tag_jtp3.run_jtp3(
tag_jtp3.Jtp3RunConfig(paths=[Path("image.png")], csv_stdout=True),
tag_jtp3.Jtp3SnapshotConfig(revision="abc123"),
)
assert result.returncode == 0
snapshot_kwargs = calls["snapshot_kwargs"]
assert isinstance(snapshot_kwargs, dict)
assert snapshot_kwargs["repo_id"] == tag_jtp3.DEFAULT_REPO_ID
assert snapshot_kwargs["revision"] == "abc123"
assert snapshot_kwargs["allow_patterns"] == tag_jtp3.DEFAULT_ALLOW_PATTERNS
command, cwd, env, check, text = calls["run"]
assert isinstance(command, list)
assert "--output" in command
assert "-" in command
assert cwd == snapshot
assert isinstance(env, dict)
assert env["PYTHONPATH"].split(os.pathsep)[0] == str(snapshot)
assert check is True
assert text is True