16031aae96
CPU tests Workflow / Testing (ubuntu-latest, 3.12) (push) Failing after 1s
CPU tests Workflow / Testing (ubuntu-latest, 3.13) (push) Failing after 0s
Mypy Type Check / Type Check (push) Failing after 0s
Docs/Test WorkFlow / Test docs build (push) Failing after 1s
PR Conflict Labeler / labeling (push) Failing after 1s
Dependency resolution / Resolve [tflite] extra — Python 3.12 (push) Failing after 0s
Smoke Tests / try-all-models (ubuntu-latest, 3.10) (push) Failing after 0s
Smoke Tests / try-all-models (ubuntu-latest, 3.13) (push) Failing after 1s
CPU tests Workflow / build-pkg (push) Failing after 1s
CPU tests Workflow / Testing (ubuntu-latest, 3.10) (push) Failing after 0s
CPU tests Workflow / Testing (ubuntu-latest, 3.11) (push) Failing after 0s
Smoke Tests / try-all-models (macos-latest, 3.10) (push) Has been cancelled
Smoke Tests / try-all-models (macos-latest, 3.13) (push) Has been cancelled
Smoke Tests / try-all-models (windows-latest, 3.10) (push) Has been cancelled
Smoke Tests / try-all-models (windows-latest, 3.13) (push) Has been cancelled
CPU tests Workflow / Testing (macos-latest, 3.10) (push) Has been cancelled
CPU tests Workflow / Testing (macos-latest, 3.13) (push) Has been cancelled
CPU tests Workflow / Testing (windows-latest, 3.10) (push) Has been cancelled
CPU tests Workflow / Testing (windows-latest, 3.13) (push) Has been cancelled
CPU tests Workflow / testing-guardian (push) Has been cancelled
GPU tests Workflow / Testing (push) Has been cancelled
196 lines
6.8 KiB
Python
196 lines
6.8 KiB
Python
# ------------------------------------------------------------------------
|
|
# RF-DETR
|
|
# Copyright (c) 2025 Roboflow. All Rights Reserved.
|
|
# Licensed under the Apache License, Version 2.0 [see LICENSE for details]
|
|
# ------------------------------------------------------------------------
|
|
"""CLI smoke tests and YAML roundtrip tests — PTL Ch4/T7.
|
|
|
|
Smoke tests run RFDETRCli in-process with args=['--help'] / ['fit', '--help'] / ['validate', '--help'] and assert
|
|
SystemExit(0) — no subprocess needed.
|
|
|
|
YAML roundtrip tests load each example config with yaml.safe_load, import the class_path, construct the config object
|
|
with the YAML init_args, and verify every specified field survived the round-trip.
|
|
"""
|
|
|
|
import importlib
|
|
import pathlib
|
|
|
|
import pytest
|
|
import yaml
|
|
|
|
CONFIGS_DIR = pathlib.Path(__file__).parent.parent.parent / "configs"
|
|
|
|
ALL_CONFIGS = [
|
|
"rfdetr_nano",
|
|
"rfdetr_small",
|
|
"rfdetr_medium",
|
|
"rfdetr_base",
|
|
"rfdetr_large",
|
|
"rfdetr_seg_nano",
|
|
"rfdetr_seg_small",
|
|
"rfdetr_seg_medium",
|
|
"rfdetr_seg_large",
|
|
"rfdetr_seg_xlarge",
|
|
"rfdetr_seg_2xlarge",
|
|
]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _run_cli(*args: str) -> int:
|
|
"""Run RFDETRCli in-process with the given args; return the SystemExit code."""
|
|
from rfdetr.training.cli import RFDETRCli
|
|
from rfdetr.training.module_data import RFDETRDataModule
|
|
from rfdetr.training.module_model import RFDETRModelModule
|
|
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
RFDETRCli(RFDETRModelModule, RFDETRDataModule, args=list(args))
|
|
return exc_info.value.code
|
|
|
|
|
|
def _load(name: str) -> dict:
|
|
return yaml.safe_load((CONFIGS_DIR / f"{name}.yaml").read_text())
|
|
|
|
|
|
def _instantiate(class_path: str, init_args: dict) -> object:
|
|
"""Import class_path and construct an instance with init_args."""
|
|
module_path, class_name = class_path.rsplit(".", 1)
|
|
cls = getattr(importlib.import_module(module_path), class_name)
|
|
return cls(**init_args)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Smoke tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestCLIEntrypoint:
|
|
"""Module entrypoint and CLI import tests."""
|
|
|
|
def test_python_module_entrypoint_runs(self) -> None:
|
|
"""Python -m rfdetr --help exits 0 and mentions rfdetr."""
|
|
import subprocess
|
|
import sys
|
|
|
|
result = subprocess.run(
|
|
[sys.executable, "-m", "rfdetr", "--help"],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=30,
|
|
)
|
|
assert result.returncode == 0
|
|
assert "rfdetr" in result.stdout.lower() or "rfdetr" in result.stderr.lower()
|
|
|
|
def test_cli_main_is_importable(self) -> None:
|
|
"""rfdetr.cli module is importable and exposes a callable main."""
|
|
import importlib
|
|
|
|
mod = importlib.import_module("rfdetr.cli")
|
|
assert callable(getattr(mod, "main", None))
|
|
|
|
|
|
class TestCLIHelp:
|
|
"""Rfdetr --help and subcommand --help must exit 0."""
|
|
|
|
def test_top_level_help(self):
|
|
"""Rfdetr --help exits with code 0."""
|
|
assert _run_cli("--help") == 0
|
|
|
|
def test_fit_help(self):
|
|
"""Rfdetr fit --help exits with code 0."""
|
|
assert _run_cli("fit", "--help") == 0
|
|
|
|
def test_validate_help(self):
|
|
"""Rfdetr validate --help exits with code 0."""
|
|
assert _run_cli("validate", "--help") == 0
|
|
|
|
def test_fit_help_exposes_model_config(self):
|
|
"""Rfdetr fit --help output lists model.model_config arguments."""
|
|
import io
|
|
import sys
|
|
|
|
buf = io.StringIO()
|
|
old_stdout = sys.stdout
|
|
sys.stdout = buf
|
|
try:
|
|
_run_cli("fit", "--help")
|
|
finally:
|
|
sys.stdout = old_stdout
|
|
assert "model_config" in buf.getvalue()
|
|
|
|
def test_fit_help_exposes_train_config(self):
|
|
"""Rfdetr fit --help output lists model.train_config arguments."""
|
|
import io
|
|
import sys
|
|
|
|
buf = io.StringIO()
|
|
old_stdout = sys.stdout
|
|
sys.stdout = buf
|
|
try:
|
|
_run_cli("fit", "--help")
|
|
finally:
|
|
sys.stdout = old_stdout
|
|
assert "train_config" in buf.getvalue()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# YAML roundtrip — model_config
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestModelConfigRoundtrip:
|
|
"""model_config init_args from YAML construct a valid config with matching values."""
|
|
|
|
@pytest.mark.parametrize("name", ALL_CONFIGS)
|
|
def test_model_config_fields_survive_roundtrip(self, name):
|
|
"""Every field in model_config.init_args is preserved after instantiation."""
|
|
data = _load(name)
|
|
mc_section = data["model"]["model_config"]
|
|
class_path = mc_section["class_path"]
|
|
init_args = mc_section.get("init_args", {})
|
|
|
|
mc = _instantiate(class_path, init_args)
|
|
for field, value in init_args.items():
|
|
assert getattr(mc, field) == value, (
|
|
f"{name}.yaml: model_config.{field} expected {value!r}, got {getattr(mc, field)!r}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# YAML roundtrip — train_config
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestTrainConfigRoundtrip:
|
|
"""train_config init_args from YAML construct a valid config with matching values."""
|
|
|
|
@pytest.mark.parametrize("name", ALL_CONFIGS)
|
|
def test_train_config_fields_survive_roundtrip(self, name, tmp_path):
|
|
"""Every field in train_config.init_args is preserved after instantiation.
|
|
|
|
dataset_dir is rewritten to tmp_path so path expansion doesn't fail on the placeholder /data/coco value.
|
|
TrainConfig.expand_paths() converts relative paths containing separators to absolute, so both sides are
|
|
normalised with os.path.abspath before comparison.
|
|
"""
|
|
import os
|
|
|
|
data = _load(name)
|
|
tc_section = data["model"]["train_config"]
|
|
class_path = tc_section["class_path"]
|
|
init_args = dict(tc_section.get("init_args", {}))
|
|
|
|
# Substitute placeholder dataset_dir with a real tmp_path.
|
|
init_args["dataset_dir"] = str(tmp_path)
|
|
|
|
tc = _instantiate(class_path, init_args)
|
|
for field, value in init_args.items():
|
|
actual = getattr(tc, field)
|
|
# TrainConfig.expand_paths() resolves relative path strings to
|
|
# absolute, so normalise both sides for string path fields.
|
|
if isinstance(value, str) and (os.sep in value or "/" in value):
|
|
value = os.path.abspath(value)
|
|
assert actual == value, f"{name}.yaml: train_config.{field} expected {value!r}, got {actual!r}"
|