b09f90bfcc
CI / lint-and-test (pull_request) Failing after 9s
Prevent worker/web clobbering of meta variants via flock and merge-by-id, make filter timeouts thread-local, harden job-id/stem sanitization, migrate TemplateResponse API, and remove the compare-bg experiment. Co-authored-by: Cursor <cursoragent@cursor.com>
135 lines
3.8 KiB
Python
135 lines
3.8 KiB
Python
"""Tests for pipeline pure helpers and meta I/O."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from app import pipeline
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("raw", "expected"),
|
|
[
|
|
("photo.jpg", "photo"),
|
|
("../evil", "evil"),
|
|
("a/b", "a_b"),
|
|
("", "photo"),
|
|
(" spaced ", "spaced"),
|
|
("foo@bar", "foo_bar"),
|
|
],
|
|
)
|
|
def test_sanitize_stem(raw: str, expected: str) -> None:
|
|
assert pipeline.sanitize_stem(raw) == expected
|
|
|
|
|
|
def test_variant_filename() -> None:
|
|
assert pipeline.variant_filename("photo", "v1") == "photo_v1.png"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("command", "expected_tail"),
|
|
[
|
|
("fx_blur 3", ["fx_blur", "3"]),
|
|
("normalize", ["normalize"]),
|
|
],
|
|
)
|
|
def test_gmic_filter_args(tmp_path: Path, command: str, expected_tail: list[str]) -> None:
|
|
image = tmp_path / "in.png"
|
|
image.touch()
|
|
args = pipeline.gmic_filter_args(image, command)
|
|
assert args[0] == str(image)
|
|
assert args[1:] == expected_tail
|
|
|
|
|
|
def test_next_variant_id() -> None:
|
|
manifest = {"variants": [{"id": "v1"}, {"id": "v2"}]}
|
|
assert pipeline.next_variant_id(manifest) == "v3"
|
|
|
|
|
|
def test_next_variant_id_after_gap() -> None:
|
|
manifest = {"variants": [{"id": "v1"}, {"id": "v3"}]}
|
|
assert pipeline.next_variant_id(manifest) == "v4"
|
|
|
|
|
|
def test_strip_ansi() -> None:
|
|
assert pipeline.strip_ansi("\x1b[31merror\x1b[0m") == "error"
|
|
|
|
|
|
def test_read_write_meta_roundtrip(data_dir: Path) -> None:
|
|
paths = pipeline.job_paths("event01")
|
|
pipeline.write_status(paths, "processing", source_file="event01.jpg")
|
|
meta = pipeline.read_meta("event01")
|
|
assert meta is not None
|
|
assert meta["status"] == "processing"
|
|
assert meta["source_file"] == "event01.jpg"
|
|
|
|
status = pipeline.read_status("event01")
|
|
assert status is not None
|
|
assert status["status"] == "processing"
|
|
|
|
manifest = pipeline.read_manifest("event01")
|
|
assert manifest is not None
|
|
assert manifest["variants"] == []
|
|
|
|
|
|
def test_list_job_ids(data_dir: Path) -> None:
|
|
paths = pipeline.job_paths("a")
|
|
pipeline.write_status(paths, "done")
|
|
paths = pipeline.job_paths("b")
|
|
pipeline.write_status(paths, "processing")
|
|
assert sorted(pipeline.list_job_ids()) == ["a", "b"]
|
|
|
|
|
|
def test_parallel_manifest_merge_keeps_all_variants(data_dir: Path) -> None:
|
|
"""Concurrent write_manifest callers must not clobber each other's variants."""
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
|
|
paths = pipeline.job_paths("race")
|
|
pipeline.write_status(paths, "processing")
|
|
pipeline.write_manifest(
|
|
paths,
|
|
{
|
|
"source_stem": "race",
|
|
"original_file": "race_original.jpg",
|
|
"rembg_file": "race_rembg.png",
|
|
"created_at": pipeline.now_iso(),
|
|
"variants": [],
|
|
},
|
|
)
|
|
|
|
def write_one(i: int) -> None:
|
|
pipeline.write_manifest(
|
|
paths,
|
|
{
|
|
"variants": [{"id": f"v{i}", "file": f"race_v{i}.png"}],
|
|
},
|
|
)
|
|
|
|
with ThreadPoolExecutor(max_workers=8) as pool:
|
|
list(pool.map(write_one, range(20)))
|
|
|
|
meta = pipeline.read_meta("race")
|
|
assert meta is not None
|
|
ids = {v["id"] for v in meta["variants"]}
|
|
assert ids == {f"v{i}" for i in range(20)}
|
|
assert meta["original_file"] == "race_original.jpg"
|
|
|
|
|
|
def test_filter_timeout_is_thread_local() -> None:
|
|
import threading
|
|
|
|
results: dict[str, int] = {}
|
|
|
|
def worker(name: str, value: int) -> None:
|
|
with pipeline.filter_timeout(value):
|
|
results[name] = pipeline._active_filter_timeout()
|
|
|
|
t1 = threading.Thread(target=worker, args=("a", 11))
|
|
t2 = threading.Thread(target=worker, args=("b", 22))
|
|
t1.start()
|
|
t2.start()
|
|
t1.join()
|
|
t2.join()
|
|
assert results == {"a": 11, "b": 22}
|