[AZ-152] Add test infrastructure: pytest conftest, fixtures, constants patching

Made-with: Cursor
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-03-26 23:09:42 +02:00
parent 8db19cc60a
commit 66fe1cc918
7 changed files with 238 additions and 0 deletions
@@ -0,0 +1,28 @@
# Batch Report
**Batch**: 1
**Tasks**: AZ-152 (test_infrastructure)
**Date**: 2026-03-26
## Task Results
| Task | Status | Files Modified | Tests | Issues |
|------|--------|---------------|-------|--------|
| AZ-152_test_infrastructure | Done | 6 files | 12/12 passed | None |
## Files Created
| File | Lines | Purpose |
|------|-------|---------|
| tests/conftest.py | 149 | Session/function fixtures, constants patching, collect_ignore for legacy scripts |
| tests/__init__.py | 0 | Package init |
| tests/performance/conftest.py | 0 | Performance conftest (empty, inherits parent) |
| tests/performance/__init__.py | 0 | Package init |
| tests/test_infrastructure.py | 59 | Smoke tests for all fixtures and constants patching |
| tests/performance/test_placeholder.py | 2 | Placeholder so performance test collection succeeds |
## Code Review Verdict: PASS
## Auto-Fix Attempts: 0
## Stuck Agents: None
## Next Batch: AZ-153, AZ-154, AZ-155, AZ-156, AZ-157, AZ-158, AZ-159, AZ-160, AZ-161, AZ-162, AZ-163
View File
+149
View File
@@ -0,0 +1,149 @@
import shutil
from pathlib import Path
import pytest
_PROJECT_ROOT = Path(__file__).resolve().parent.parent
_DATASET_IMAGES = _PROJECT_ROOT / "_docs/00_problem/input_data/dataset/images"
_DATASET_LABELS = _PROJECT_ROOT / "_docs/00_problem/input_data/dataset/labels"
_ONNX_MODEL = _PROJECT_ROOT / "_docs/00_problem/input_data/azaion.onnx"
_CLASSES_JSON = _PROJECT_ROOT / "classes.json"
collect_ignore = ["security_test.py", "imagelabel_visualize_test.py"]
def apply_constants_patch(monkeypatch, base: Path):
import constants as c
from os import path
root = str(base.resolve())
azaion = path.join(root, "azaion")
monkeypatch.setattr(c, "azaion", azaion)
data_dir = path.join(azaion, "data")
monkeypatch.setattr(c, "data_dir", data_dir)
monkeypatch.setattr(c, "data_images_dir", path.join(data_dir, c.images))
monkeypatch.setattr(c, "data_labels_dir", path.join(data_dir, c.labels))
processed_dir = path.join(azaion, "data-processed")
monkeypatch.setattr(c, "processed_dir", processed_dir)
monkeypatch.setattr(c, "processed_images_dir", path.join(processed_dir, c.images))
monkeypatch.setattr(c, "processed_labels_dir", path.join(processed_dir, c.labels))
corrupted_dir = path.join(azaion, "data-corrupted")
monkeypatch.setattr(c, "corrupted_dir", corrupted_dir)
monkeypatch.setattr(c, "corrupted_images_dir", path.join(corrupted_dir, c.images))
monkeypatch.setattr(c, "corrupted_labels_dir", path.join(corrupted_dir, c.labels))
monkeypatch.setattr(c, "sample_dir", path.join(azaion, "data-sample"))
monkeypatch.setattr(c, "datasets_dir", path.join(azaion, "datasets"))
models_dir = path.join(azaion, "models")
monkeypatch.setattr(c, "models_dir", models_dir)
monkeypatch.setattr(c, "CURRENT_PT_MODEL", path.join(models_dir, f"{c.prefix[:-1]}.pt"))
monkeypatch.setattr(c, "CURRENT_ONNX_MODEL", path.join(models_dir, f"{c.prefix[:-1]}.onnx"))
@pytest.fixture(scope="session")
def fixture_images_dir():
p = _DATASET_IMAGES
if not p.is_dir():
pytest.skip(f"missing dataset images: {p}")
return p
@pytest.fixture(scope="session")
def fixture_labels_dir():
p = _DATASET_LABELS
if not p.is_dir():
pytest.skip(f"missing dataset labels: {p}")
return p
@pytest.fixture(scope="session")
def fixture_onnx_model():
p = _ONNX_MODEL
if not p.is_file():
pytest.skip(f"missing onnx model: {p}")
return p.read_bytes()
@pytest.fixture(scope="session")
def fixture_classes_json():
p = _CLASSES_JSON
if not p.is_file():
pytest.skip(f"missing classes.json: {p}")
return p
@pytest.fixture
def constants_patch(monkeypatch):
def _apply(base: Path):
apply_constants_patch(monkeypatch, base)
return _apply
@pytest.fixture
def work_dir(tmp_path):
w = tmp_path / "work"
(w / "images").mkdir(parents=True)
(w / "labels").mkdir(parents=True)
return w
@pytest.fixture
def sample_image_label(fixture_images_dir, fixture_labels_dir, tmp_path):
imgs = sorted(fixture_images_dir.glob("*.jpg"))
if not imgs:
raise RuntimeError("no images in fixture_images_dir")
stem = imgs[0].stem
src_img = fixture_images_dir / f"{stem}.jpg"
src_lbl = fixture_labels_dir / f"{stem}.txt"
dst_img = tmp_path / "images" / f"{stem}.jpg"
dst_lbl = tmp_path / "labels" / f"{stem}.txt"
dst_img.parent.mkdir(parents=True, exist_ok=True)
dst_lbl.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(src_img, dst_img)
shutil.copy2(src_lbl, dst_lbl)
return dst_img, dst_lbl
@pytest.fixture
def sample_images_labels(fixture_images_dir, fixture_labels_dir, tmp_path):
def _factory(count: int):
if count < 1:
raise ValueError("count must be >= 1")
imgs = sorted(fixture_images_dir.glob("*.jpg"))
if count > len(imgs):
raise ValueError("count exceeds available images")
out_img = tmp_path / "images"
out_lbl = tmp_path / "labels"
out_img.mkdir(parents=True, exist_ok=True)
out_lbl.mkdir(parents=True, exist_ok=True)
for p in imgs[:count]:
stem = p.stem
shutil.copy2(fixture_images_dir / f"{stem}.jpg", out_img / f"{stem}.jpg")
shutil.copy2(fixture_labels_dir / f"{stem}.txt", out_lbl / f"{stem}.txt")
return out_img, out_lbl
return _factory
@pytest.fixture
def corrupted_label(tmp_path):
p = tmp_path / "labels" / "corrupted.txt"
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text("0 1.5 0.5 0.1 0.1\n", encoding="utf-8")
return p
@pytest.fixture
def edge_bbox_label(tmp_path):
p = tmp_path / "labels" / "edge.txt"
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text("0 0.01 0.5 0.02 0.3\n", encoding="utf-8")
return p
@pytest.fixture
def empty_label(tmp_path):
p = tmp_path / "labels" / "empty.txt"
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text("", encoding="utf-8")
return p
View File
View File
+2
View File
@@ -0,0 +1,2 @@
def test_performance_package_collects():
assert True
+59
View File
@@ -0,0 +1,59 @@
import constants as c
def test_fixture_images_dir_has_jpegs(fixture_images_dir):
jpgs = list(fixture_images_dir.glob("*.jpg"))
assert len(jpgs) == 100
def test_fixture_labels_dir_has_yolo_labels(fixture_labels_dir):
txts = list(fixture_labels_dir.glob("*.txt"))
assert len(txts) == 100
def test_fixture_onnx_model_bytes(fixture_onnx_model):
assert len(fixture_onnx_model) > 0
def test_fixture_classes_json_exists(fixture_classes_json):
assert fixture_classes_json.is_file()
assert fixture_classes_json.name == "classes.json"
def test_work_dir_layout(work_dir):
assert (work_dir / "images").is_dir()
assert (work_dir / "labels").is_dir()
def test_sample_image_label_paths(sample_image_label):
img, lbl = sample_image_label
assert img.suffix.lower() == ".jpg"
assert lbl.suffix == ".txt"
assert img.exists() and lbl.exists()
assert img.stem == lbl.stem
def test_sample_images_labels_factory(sample_images_labels):
img_dir, lbl_dir = sample_images_labels(3)
assert len(list(img_dir.glob("*.jpg"))) == 3
assert len(list(lbl_dir.glob("*.txt"))) == 3
def test_corrupted_label_content(corrupted_label):
text = corrupted_label.read_text(encoding="utf-8")
assert "1.5" in text
def test_edge_bbox_label_exists(edge_bbox_label):
assert edge_bbox_label.read_text(encoding="utf-8").strip()
def test_empty_label_file(empty_label):
assert empty_label.read_text(encoding="utf-8") == ""
def test_constants_patch_uses_tmp(constants_patch, tmp_path):
constants_patch(tmp_path)
assert c.azaion.startswith(str(tmp_path))
assert c.data_dir.startswith(str(tmp_path))
assert c.CURRENT_ONNX_MODEL.startswith(str(tmp_path))