PawTrace / backend /tests /test_prepare_data.py
Elliott Duke
HomingPet: lost-dog reunification (FastAPI + React) with Render deploy
de1e3fc
Raw
History Blame Contribute Delete
3.91 kB
"""Tests for scripts/prepare_test_data.py — split ratios, reproducibility, CSV format, pairs."""
import csv
from pathlib import Path
from scripts.make_sample_images import make_image
from scripts.prepare_test_data import prepare
def _make_dataset_dir(tmp: Path, layout: dict[str, int]) -> Path:
"""layout: {folder_name: image_count}. Writes real JPEGs."""
root = tmp / "input"
for folder, count in layout.items():
d = root / folder
d.mkdir(parents=True)
for i in range(count):
(d / f"img{i}.jpg").write_bytes(make_image(hash((folder, i)) % 1000))
return root
def _read(path: Path) -> list[dict]:
with path.open(newline="", encoding="utf-8") as fh:
return list(csv.DictReader(fh))
def test_default_holdout_split(tmp_path):
root = _make_dataset_dir(tmp_path, {"dogA": 3, "dogB": 6})
out = tmp_path / "out"
prepare(root, out, holdout=None, seed=1)
known = _read(out / "known_dogs.csv")
found = _read(out / "found_dogs.csv")
by_folder = lambda rows, f: [r for r in rows if r["folder"] == f] # noqa: E731
# dogA has <5 images -> 1 holdout; dogB has 5+ -> 2 holdout.
assert len(by_folder(found, "dogA")) == 1
assert len(by_folder(known, "dogA")) == 2
assert len(by_folder(found, "dogB")) == 2
assert len(by_folder(known, "dogB")) == 4
def test_explicit_holdout(tmp_path):
root = _make_dataset_dir(tmp_path, {"dogB": 6})
out = tmp_path / "out"
prepare(root, out, holdout=1, seed=1)
assert len(_read(out / "found_dogs.csv")) == 1
assert len(_read(out / "known_dogs.csv")) == 5
def test_reproducible_with_same_seed(tmp_path):
root = _make_dataset_dir(tmp_path, {"dogA": 4, "dogB": 5})
out1, out2 = tmp_path / "o1", tmp_path / "o2"
prepare(root, out1, holdout=None, seed=7)
prepare(root, out2, holdout=None, seed=7)
for fname in ("known_dogs.csv", "found_dogs.csv", "pairs.csv"):
assert (out1 / fname).read_text() == (out2 / fname).read_text()
def test_csv_columns_match_loader_contract(tmp_path):
root = _make_dataset_dir(tmp_path, {"dogA": 2})
out = tmp_path / "out"
prepare(root, out, holdout=1, seed=1)
known_cols = _read(out / "known_dogs.csv")[0].keys()
found_cols = _read(out / "found_dogs.csv")[0].keys()
assert set(known_cols) == {
"folder", "image_file", "dog_name", "color", "size", "description",
"zip", "owner_name", "owner_email", "owner_phone",
}
assert set(found_cols) == {
"folder", "image_file", "description", "color", "size", "found_zip",
"current_location", "finder_name", "finder_email", "finder_phone",
}
def test_found_zip_equals_known_zip(tmp_path):
root = _make_dataset_dir(tmp_path, {"dogA": 3})
out = tmp_path / "out"
prepare(root, out, holdout=1, seed=3)
known_zip = _read(out / "known_dogs.csv")[0]["zip"]
found_zip = _read(out / "found_dogs.csv")[0]["found_zip"]
assert known_zip == found_zip # so default-radius matching works
def test_pairs_link_known_and_found(tmp_path):
root = _make_dataset_dir(tmp_path, {"dogA": 4})
out = tmp_path / "out"
prepare(root, out, holdout=1, seed=5)
pairs = _read(out / "pairs.csv")
assert len(pairs) == 1
row = pairs[0]
known_imgs = set(row["known_images"].split(";"))
found_imgs = set(row["found_images"].split(";"))
assert len(found_imgs) == 1
assert len(known_imgs) == 3
assert known_imgs.isdisjoint(found_imgs) # an image is either registration or holdout
assert known_imgs | found_imgs == {"img0.jpg", "img1.jpg", "img2.jpg", "img3.jpg"}
def test_skips_empty_folder(tmp_path):
root = _make_dataset_dir(tmp_path, {"dogA": 2})
(root / "empty_dog").mkdir()
out = tmp_path / "out"
result = prepare(root, out, holdout=1, seed=1)
assert "empty_dog" in result["skipped_folders"]
assert result["dogs"] == 1