claude_refactor_v3: Add and passed test on splited gin-configs loading but without weigts
This commit is contained in:
294
tests/conftest.py
Normal file
294
tests/conftest.py
Normal file
@@ -0,0 +1,294 @@
|
||||
# """Shared pytest fixtures + reporter hooks for caption-test test suite.
|
||||
|
||||
# Provides:
|
||||
# - proj_dir, path2cfg: real repository paths for integration-style tests
|
||||
# that load actual gin presets from in/config_files/.
|
||||
# - clear_gin: autouse fixture that wipes gin global state before every test
|
||||
# (gin keeps bindings in module-level singleton; tests must not leak).
|
||||
|
||||
# Reporter hooks print "✅/❌ <docstring>" next to each test's PASSED/FAILED
|
||||
# line in -v mode.
|
||||
|
||||
# Preset name lists live in a separate module (`tests/_presets.py`) so test
|
||||
# files can import them with a plain `import _presets` — no relative imports,
|
||||
# no need for tests/ to be a package.
|
||||
# """
|
||||
|
||||
# from __future__ import annotations
|
||||
|
||||
# from pathlib import Path
|
||||
|
||||
# import gin
|
||||
# import pytest
|
||||
|
||||
|
||||
# DINOV3_PRESETS = (
|
||||
# "gtauav_balanced",
|
||||
# "gtauav_balanced_asym",
|
||||
# "gtauav_baseline",
|
||||
# "gtauav_baseline_asym",
|
||||
# "gtauav_image_heavy",
|
||||
# "gtauav_text_heavy",
|
||||
# )
|
||||
|
||||
# STRIPNET_PRESETS = (
|
||||
# "gtauav_balanced_stripnet",
|
||||
# "gtauav_balanced_stripnet_unfrozen",
|
||||
# "gtauav_baseline_stripnet",
|
||||
# "gtauav_baseline_stripnet_unfrozen",
|
||||
# )
|
||||
|
||||
# ALL_TRAINING_PRESETS = DINOV3_PRESETS + STRIPNET_PRESETS
|
||||
|
||||
# # --- gin hygiene -----------------------------------------------------------
|
||||
|
||||
|
||||
# @pytest.fixture(autouse=True)
|
||||
# def clear_gin():
|
||||
# """Wipe gin's global binding state before AND after each test.
|
||||
|
||||
# Gin keeps bindings in module-level singletons; without this fixture, a
|
||||
# test that loads a config (or even just calls gin.parse_config_file in a
|
||||
# helper) leaks bindings into the next test, leading to flaky failures
|
||||
# like 'Unknown configurable' or wrong field values.
|
||||
# """
|
||||
# gin.clear_config()
|
||||
# yield
|
||||
# gin.clear_config()
|
||||
|
||||
|
||||
# # --- real-repo paths -------------------------------------------------------
|
||||
|
||||
|
||||
# @pytest.fixture
|
||||
# def proj_dir() -> Path:
|
||||
# """Path to repository root (the directory that contains src/, tests/, in/)."""
|
||||
# return Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
# @pytest.fixture
|
||||
# def path2cfg(proj_dir: Path) -> str:
|
||||
# """Trailing-slashed path to in/config_files/, matching `src/main.py`.
|
||||
|
||||
# Per REQUIREMENTS_GIN_STYLE.md §5, src/main.py builds this path as
|
||||
# `f"{proj_dir}in/config_files/"`. Tests that exercise the real repo
|
||||
# layout should use this fixture verbatim instead of constructing it
|
||||
# independently.
|
||||
# """
|
||||
# return f"{proj_dir}/in/config_files/"
|
||||
|
||||
|
||||
# # --- reporter hooks --------------------------------------------------------
|
||||
|
||||
|
||||
# def _docstring_summary(item: pytest.Item) -> str | None:
|
||||
# """Return the first non-empty line of a test's docstring, or None."""
|
||||
# func = getattr(item, "function", None) or getattr(item, "obj", None)
|
||||
# if func is None or not getattr(func, "__doc__", None):
|
||||
# return None
|
||||
# for line in func.__doc__.strip().splitlines():
|
||||
# stripped = line.strip()
|
||||
# if stripped:
|
||||
# return stripped
|
||||
# return None
|
||||
|
||||
|
||||
# # Cache nodeid → docstring summary, populated at collection time so the
|
||||
# # logreport hook can look them up without re-introspecting the test function.
|
||||
# _LAST_SEEN_SUMMARY: dict[str, str] = {}
|
||||
|
||||
|
||||
# def pytest_collection_modifyitems(
|
||||
# config: pytest.Config,
|
||||
# items: list[pytest.Item],
|
||||
# ) -> None:
|
||||
# """Cache each item's docstring summary for later use by the status hook."""
|
||||
# for item in items:
|
||||
# summary = _docstring_summary(item)
|
||||
# if summary:
|
||||
# _LAST_SEEN_SUMMARY[item.nodeid] = summary
|
||||
|
||||
|
||||
# def pytest_runtest_logreport(report: pytest.TestReport) -> None:
|
||||
# """Print '✅/❌/⏭️ <docstring>' after each test's `call` phase finishes.
|
||||
|
||||
# Pytest emits 3 reports per test (setup → call → teardown). We hook the
|
||||
# `call` phase — the one where pass/fail is actually decided — and write
|
||||
# the icon + docstring summary to stdout, where it appears next to
|
||||
# pytest's own PASSED/FAILED line.
|
||||
# """
|
||||
# if report.when != "call":
|
||||
# return
|
||||
|
||||
# summary = _LAST_SEEN_SUMMARY.get(report.nodeid, "(no docstring)")
|
||||
# if report.passed:
|
||||
# icon = "✅"
|
||||
# elif report.failed:
|
||||
# icon = "❌"
|
||||
# else:
|
||||
# icon = "⏭️"
|
||||
# print(f" {icon} {summary}")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
"""Shared pytest fixtures + reporter hooks for caption-test test suite.
|
||||
|
||||
Provides:
|
||||
- proj_dir, path2cfg: real repository paths for integration-style tests
|
||||
that load actual gin presets from in/config_files/.
|
||||
- clear_gin: autouse fixture that wipes gin global state before every test
|
||||
(gin keeps bindings in module-level singleton; tests must not leak).
|
||||
|
||||
Reporter hooks print "✅/❌ <docstring>" next to each test's PASSED/FAILED
|
||||
line in -v mode.
|
||||
|
||||
Preset name lists live in a separate module (`tests/_presets.py`) so test
|
||||
files can import them with a plain `import _presets` — no relative imports,
|
||||
no need for tests/ to be a package.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import gin
|
||||
import pytest
|
||||
|
||||
|
||||
DINOV3_PRESETS = (
|
||||
"gtauav_balanced",
|
||||
"gtauav_balanced_asym",
|
||||
"gtauav_baseline",
|
||||
"gtauav_baseline_asym",
|
||||
"gtauav_image_heavy",
|
||||
"gtauav_text_heavy",
|
||||
)
|
||||
|
||||
STRIPNET_PRESETS = (
|
||||
"gtauav_balanced_stripnet",
|
||||
"gtauav_balanced_stripnet_unfrozen",
|
||||
"gtauav_baseline_stripnet",
|
||||
"gtauav_baseline_stripnet_unfrozen",
|
||||
)
|
||||
|
||||
ALL_TRAINING_PRESETS = DINOV3_PRESETS + STRIPNET_PRESETS
|
||||
|
||||
# --- gin hygiene -----------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def clear_gin():
|
||||
"""Wipe gin's global binding state before AND after each test.
|
||||
|
||||
Gin keeps bindings in module-level singletons; without this fixture, a
|
||||
test that loads a config (or even just calls gin.parse_config_file in a
|
||||
helper) leaks bindings into the next test, leading to flaky failures
|
||||
like 'Unknown configurable' or wrong field values.
|
||||
"""
|
||||
gin.clear_config()
|
||||
yield
|
||||
gin.clear_config()
|
||||
|
||||
|
||||
# --- real-repo paths -------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def proj_dir() -> Path:
|
||||
"""Path to repository root (the directory that contains src/, tests/, in/)."""
|
||||
return Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def path2cfg(proj_dir: Path) -> str:
|
||||
"""Trailing-slashed path to in/config_files/, matching `src/main.py`.
|
||||
|
||||
Per REQUIREMENTS_GIN_STYLE.md §5, src/main.py builds this path as
|
||||
`f"{proj_dir}in/config_files/"`. Tests that exercise the real repo
|
||||
layout should use this fixture verbatim instead of constructing it
|
||||
independently.
|
||||
"""
|
||||
return f"{proj_dir}/in/config_files/"
|
||||
|
||||
|
||||
# --- reporter hooks --------------------------------------------------------
|
||||
|
||||
|
||||
def _docstring_summary(item: pytest.Item) -> str | None:
|
||||
"""Return the first non-empty line of a test's docstring, or None."""
|
||||
func = getattr(item, "function", None) or getattr(item, "obj", None)
|
||||
if func is None or not getattr(func, "__doc__", None):
|
||||
return None
|
||||
for line in func.__doc__.strip().splitlines():
|
||||
stripped = line.strip()
|
||||
if stripped:
|
||||
return stripped
|
||||
return None
|
||||
|
||||
|
||||
# Cache nodeid → docstring summary, populated at collection time so the
|
||||
# logreport hook can look them up without re-introspecting the test function.
|
||||
_LAST_SEEN_SUMMARY: dict[str, str] = {}
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(
|
||||
config: pytest.Config,
|
||||
items: list[pytest.Item],
|
||||
) -> None:
|
||||
"""Cache each item's docstring summary for later use by the status hook."""
|
||||
for item in items:
|
||||
summary = _docstring_summary(item)
|
||||
if summary:
|
||||
_LAST_SEEN_SUMMARY[item.nodeid] = summary
|
||||
|
||||
|
||||
def pytest_runtest_logreport(report: pytest.TestReport) -> None:
|
||||
"""Print test results with parametrized tests grouped under one header.
|
||||
|
||||
Non-parametrized test:
|
||||
✅ <docstring summary>
|
||||
|
||||
Parametrized test (first occurrence of the group):
|
||||
<docstring summary>
|
||||
✅ <param>
|
||||
|
||||
Parametrized test (subsequent occurrences):
|
||||
✅ <param>
|
||||
|
||||
Pytest emits 3 reports per test (setup → call → teardown). We hook the
|
||||
`call` phase — the one where pass/fail is actually decided.
|
||||
"""
|
||||
if report.when != "call":
|
||||
return
|
||||
|
||||
summary = _LAST_SEEN_SUMMARY.get(report.nodeid, "(no docstring)")
|
||||
if report.passed:
|
||||
icon = "✅"
|
||||
elif report.failed:
|
||||
icon = "❌"
|
||||
else:
|
||||
icon = "⏭️"
|
||||
|
||||
# Detect parametrization: pytest encodes params as `nodeid[param1-param2-...]`.
|
||||
if "[" in report.nodeid and report.nodeid.endswith("]"):
|
||||
base_id, _, param_part = report.nodeid.partition("[")
|
||||
param_label = param_part[:-1] or "empty" # strip trailing ']'
|
||||
|
||||
# Print docstring header only on first encounter of this group.
|
||||
# Leading "\n" separates the header from pytest's progress dot.
|
||||
if base_id not in _PRINTED_GROUP_HEADERS:
|
||||
print(f"\n{summary}")
|
||||
_PRINTED_GROUP_HEADERS.add(base_id)
|
||||
print(f" {icon} {param_label}")
|
||||
else:
|
||||
print(f" {icon} {summary}")
|
||||
|
||||
|
||||
# Tracks which parametrized test groups have already had their docstring
|
||||
# header printed. Reset implicitly at the start of each pytest run because
|
||||
# Python re-imports conftest.py.
|
||||
_PRINTED_GROUP_HEADERS: set[str] = set()
|
||||
Reference in New Issue
Block a user