chore: import upstream snapshot with attribution
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
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
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
# ------------------------------------------------------------------------
|
||||
# RF-DETR
|
||||
# Copyright (c) 2025 Roboflow. All Rights Reserved.
|
||||
# Licensed under the Apache License, Version 2.0 [see LICENSE for details]
|
||||
# ------------------------------------------------------------------------
|
||||
@@ -0,0 +1,143 @@
|
||||
# ------------------------------------------------------------------------
|
||||
# RF-DETR
|
||||
# Copyright (c) 2025 Roboflow. All Rights Reserved.
|
||||
# Licensed under the Apache License, Version 2.0 [see LICENSE for details]
|
||||
# ------------------------------------------------------------------------
|
||||
"""Tests for YAML config files in configs/ — PTL Ch4/T6.
|
||||
|
||||
Verifies that every example YAML config file:
|
||||
- exists on disk,
|
||||
- parses as valid YAML,
|
||||
- contains a ``model`` section with ``model_config`` and ``train_config``,
|
||||
- references the expected model class_path, and
|
||||
- segmentation configs use SegmentationTrainConfig.
|
||||
"""
|
||||
|
||||
import pathlib
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
CONFIGS_DIR = pathlib.Path(__file__).parent.parent.parent / "configs"
|
||||
|
||||
DETECTION_CONFIGS = [
|
||||
"rfdetr_nano",
|
||||
"rfdetr_small",
|
||||
"rfdetr_medium",
|
||||
"rfdetr_base",
|
||||
"rfdetr_large",
|
||||
]
|
||||
|
||||
SEGMENTATION_CONFIGS = [
|
||||
"rfdetr_seg_nano",
|
||||
"rfdetr_seg_small",
|
||||
"rfdetr_seg_medium",
|
||||
"rfdetr_seg_large",
|
||||
"rfdetr_seg_xlarge",
|
||||
"rfdetr_seg_2xlarge",
|
||||
]
|
||||
|
||||
ALL_CONFIGS = DETECTION_CONFIGS + SEGMENTATION_CONFIGS
|
||||
|
||||
# Maps filename stem → expected model_config class_path.
|
||||
EXPECTED_MODEL_CLASS = {
|
||||
"rfdetr_nano": "rfdetr.config.RFDETRNanoConfig",
|
||||
"rfdetr_small": "rfdetr.config.RFDETRSmallConfig",
|
||||
"rfdetr_medium": "rfdetr.config.RFDETRMediumConfig",
|
||||
"rfdetr_base": "rfdetr.config.RFDETRBaseConfig",
|
||||
"rfdetr_large": "rfdetr.config.RFDETRLargeConfig",
|
||||
"rfdetr_seg_nano": "rfdetr.config.RFDETRSegNanoConfig",
|
||||
"rfdetr_seg_small": "rfdetr.config.RFDETRSegSmallConfig",
|
||||
"rfdetr_seg_medium": "rfdetr.config.RFDETRSegMediumConfig",
|
||||
"rfdetr_seg_large": "rfdetr.config.RFDETRSegLargeConfig",
|
||||
"rfdetr_seg_xlarge": "rfdetr.config.RFDETRSegXLargeConfig",
|
||||
"rfdetr_seg_2xlarge": "rfdetr.config.RFDETRSeg2XLargeConfig",
|
||||
}
|
||||
|
||||
|
||||
def _load(name: str) -> dict:
|
||||
"""Parse a config file by stem name and return its dict."""
|
||||
return yaml.safe_load((CONFIGS_DIR / f"{name}.yaml").read_text())
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# File existence
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestConfigFilesExist:
|
||||
"""Every expected YAML config file must be present on disk."""
|
||||
|
||||
@pytest.mark.parametrize("name", ALL_CONFIGS)
|
||||
def test_config_file_exists(self, name):
|
||||
"""configs/{name}.yaml must exist."""
|
||||
assert (CONFIGS_DIR / f"{name}.yaml").exists(), f"Missing config file: {name}.yaml"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# YAML validity
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestConfigFilesValidYAML:
|
||||
"""Each file must be parseable as YAML and produce a mapping."""
|
||||
|
||||
@pytest.mark.parametrize("name", ALL_CONFIGS)
|
||||
def test_config_is_valid_yaml(self, name):
|
||||
"""yaml.safe_load must succeed and return a dict."""
|
||||
data = _load(name)
|
||||
assert isinstance(data, dict), f"{name}.yaml did not parse to a dict"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Structure
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestConfigStructure:
|
||||
"""Each YAML must have a model section with model_config and train_config."""
|
||||
|
||||
@pytest.mark.parametrize("name", ALL_CONFIGS)
|
||||
def test_has_model_section(self, name):
|
||||
"""Top-level 'model' key must be present."""
|
||||
assert "model" in _load(name), f"{name}.yaml missing 'model' section"
|
||||
|
||||
@pytest.mark.parametrize("name", ALL_CONFIGS)
|
||||
def test_has_model_config(self, name):
|
||||
"""model.model_config must be present."""
|
||||
assert "model_config" in _load(name)["model"], f"{name}.yaml missing model.model_config"
|
||||
|
||||
@pytest.mark.parametrize("name", ALL_CONFIGS)
|
||||
def test_has_train_config(self, name):
|
||||
"""model.train_config must be present."""
|
||||
assert "train_config" in _load(name)["model"], f"{name}.yaml missing model.train_config"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Class paths
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestConfigClassPaths:
|
||||
"""model_config class_path must match the expected model variant."""
|
||||
|
||||
@pytest.mark.parametrize("name", ALL_CONFIGS)
|
||||
def test_model_config_class_path(self, name):
|
||||
"""model.model_config.class_path must match the variant."""
|
||||
got = _load(name)["model"]["model_config"]["class_path"]
|
||||
want = EXPECTED_MODEL_CLASS[name]
|
||||
assert got == want, f"{name}.yaml: expected class_path {want!r}, got {got!r}"
|
||||
|
||||
@pytest.mark.parametrize("name", SEGMENTATION_CONFIGS)
|
||||
def test_seg_uses_segmentation_train_config(self, name):
|
||||
"""Segmentation configs must use SegmentationTrainConfig."""
|
||||
got = _load(name)["model"]["train_config"]["class_path"]
|
||||
assert got == "rfdetr.config.SegmentationTrainConfig", (
|
||||
f"{name}.yaml: train_config must use SegmentationTrainConfig, got {got!r}"
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("name", DETECTION_CONFIGS)
|
||||
def test_det_uses_train_config(self, name):
|
||||
"""Detection configs must use TrainConfig (not a subclass)."""
|
||||
got = _load(name)["model"]["train_config"]["class_path"]
|
||||
assert got == "rfdetr.config.TrainConfig", f"{name}.yaml: train_config must use TrainConfig, got {got!r}"
|
||||
@@ -0,0 +1,29 @@
|
||||
# ------------------------------------------------------------------------
|
||||
# RF-DETR
|
||||
# Copyright (c) 2025 Roboflow. All Rights Reserved.
|
||||
# Licensed under the Apache License, Version 2.0 [see LICENSE for details]
|
||||
# ------------------------------------------------------------------------
|
||||
"""Tests for the CLI entry point configuration."""
|
||||
|
||||
import pathlib
|
||||
import re
|
||||
|
||||
|
||||
class TestEntryPoint:
|
||||
"""[project.scripts] in pyproject.toml uses the correct CLI entry point."""
|
||||
|
||||
def _read_entry_point(self) -> str:
|
||||
"""Return the rfdetr console_scripts value from pyproject.toml."""
|
||||
root = pathlib.Path(__file__).parent.parent.parent
|
||||
content = (root / "pyproject.toml").read_text()
|
||||
m = re.search(r"\[project\.scripts\].*?rfdetr\s*=\s*\"([^\"]+)\"", content, re.DOTALL)
|
||||
assert m, "rfdetr entry not found in [project.scripts]"
|
||||
return m.group(1)
|
||||
|
||||
def test_entry_point_value(self):
|
||||
"""Rfdetr entry point must be rfdetr.cli:main."""
|
||||
assert self._read_entry_point() == "rfdetr.cli:main"
|
||||
|
||||
def test_entry_point_not_legacy(self):
|
||||
"""Entry point must no longer reference rfdetr.cli.main:trainer."""
|
||||
assert self._read_entry_point() != "rfdetr.cli.main:trainer"
|
||||
@@ -0,0 +1,195 @@
|
||||
# ------------------------------------------------------------------------
|
||||
# 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}"
|
||||
Reference in New Issue
Block a user