Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:26:24 +08:00

190 lines
8.3 KiB
Python

# ------------------------------------------------------------------------
# RF-DETR
# Copyright (c) 2025 Roboflow. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 [see LICENSE for details]
# ------------------------------------------------------------------------
"""Regression tests for _namespace_from_configs() config forwarding."""
import sys
from typing import Any
import pytest
from rfdetr._namespace import _namespace_from_configs
from rfdetr.config import RFDETRBaseConfig, RFDETRSegNanoConfig, SegmentationTrainConfig, TrainConfig
from rfdetr.models._types import BuilderArgs
class TestNamespaceForwarding:
"""Verify that _namespace_from_configs() forwards TrainConfig fields that were previously hardcoded to wrong
defaults."""
def _make_ns(self: "TestNamespaceForwarding", **tc_kwargs: Any) -> Any:
"""Build a namespace for tests with minimal default TrainConfig values."""
mc = RFDETRBaseConfig(num_classes=80)
tc_kwargs.setdefault("dataset_dir", "/tmp")
tc = TrainConfig(**tc_kwargs)
return _namespace_from_configs(mc, tc)
def test_aug_config_forwarded_when_set(self: "TestNamespaceForwarding") -> None:
aug = {"hsv_h": 0.015, "hsv_s": 0.7}
ns = self._make_ns(aug_config=aug)
assert ns.aug_config == aug
def test_aug_config_none_by_default(self: "TestNamespaceForwarding") -> None:
ns = self._make_ns()
assert ns.aug_config is None
def test_use_ema_forwarded_true(self: "TestNamespaceForwarding") -> None:
ns = self._make_ns(use_ema=True)
assert ns.use_ema is True
def test_use_ema_forwarded_false(self: "TestNamespaceForwarding") -> None:
ns = self._make_ns(use_ema=False)
assert ns.use_ema is False
def test_early_stopping_use_ema_forwarded_true(self: "TestNamespaceForwarding") -> None:
ns = self._make_ns(early_stopping_use_ema=True)
assert ns.early_stopping_use_ema is True
def test_early_stopping_use_ema_forwarded_false(self: "TestNamespaceForwarding") -> None:
ns = self._make_ns(early_stopping_use_ema=False)
assert ns.early_stopping_use_ema is False
class TestNamespaceProtocol:
"""_namespace_from_configs() output must satisfy the BuilderArgs Protocol."""
def _make_ns(self, mc=None, tc=None):
mc = mc or RFDETRBaseConfig(num_classes=80)
tc = tc or TrainConfig(dataset_dir="/tmp")
return _namespace_from_configs(mc, tc)
@pytest.mark.skipif(
sys.version_info < (3, 12),
reason="Runtime Protocol attribute checks require Python 3.12+",
)
def test_namespace_satisfies_builderargs_protocol_py312(self) -> None:
"""On Python 3.12+, isinstance() verifies data-attribute presence."""
ns = self._make_ns()
assert isinstance(ns, BuilderArgs)
def test_namespace_is_builderargs_instance(self) -> None:
"""Isinstance() check passes on all supported Python versions.
On Python 3.10/3.11 this is a structural no-op (no method members to check). On 3.12+ it verifies attribute
presence. The test documents the intent regardless of Python version.
"""
ns = self._make_ns()
assert isinstance(ns, BuilderArgs)
class TestNamespaceFieldOwnership:
"""Verify that the namespace reads each field from the authoritative owner."""
def _make_ns(self, mc=None, tc=None):
mc = mc or RFDETRBaseConfig(num_classes=80)
tc = tc or TrainConfig(dataset_dir="/tmp")
return _namespace_from_configs(mc, tc)
# --- cls_loss_coef must come from TrainConfig ---
def test_cls_loss_coef_from_train_config(self) -> None:
"""ns.cls_loss_coef must reflect TrainConfig.cls_loss_coef, not ModelConfig."""
mc = RFDETRBaseConfig(num_classes=80) # cls_loss_coef=1.0 (ModelConfig default)
tc = TrainConfig(dataset_dir="/tmp", cls_loss_coef=2.5)
ns = _namespace_from_configs(mc, tc)
assert ns.cls_loss_coef == pytest.approx(2.5)
def test_cls_loss_coef_segmentation_default_matches_pre_1_7_effective_value(self) -> None:
"""SegmentationTrainConfig default must preserve the pre-1.7 effective loss_ce weight."""
mc = RFDETRSegNanoConfig()
tc = SegmentationTrainConfig(dataset_dir="/tmp")
ns = _namespace_from_configs(mc, tc)
assert ns.cls_loss_coef == pytest.approx(1.0)
def test_cls_loss_coef_segmentation_explicit_train_config_value_wins(self) -> None:
"""Explicit SegmentationTrainConfig.cls_loss_coef values must propagate to namespace."""
mc = RFDETRSegNanoConfig()
tc = SegmentationTrainConfig(dataset_dir="/tmp", cls_loss_coef=5.0)
ns = _namespace_from_configs(mc, tc)
assert ns.cls_loss_coef == pytest.approx(5.0)
def test_cls_loss_coef_train_config_wins_over_explicit_model_config(self) -> None:
"""When both are explicitly set, TrainConfig.cls_loss_coef takes precedence."""
with pytest.warns(DeprecationWarning, match="ModelConfig\\.cls_loss_coef is deprecated"):
mc = RFDETRBaseConfig(num_classes=80, cls_loss_coef=0.5)
tc = TrainConfig(dataset_dir="/tmp", cls_loss_coef=3.0)
ns = _namespace_from_configs(mc, tc)
assert ns.cls_loss_coef == pytest.approx(3.0)
def test_cls_loss_coef_model_config_explicit_is_preserved_during_deprecation(self) -> None:
"""Explicit ModelConfig.cls_loss_coef remains effective until removal."""
with pytest.warns(DeprecationWarning, match="ModelConfig\\.cls_loss_coef is deprecated"):
mc = RFDETRBaseConfig(num_classes=80, cls_loss_coef=2.5)
tc = TrainConfig(dataset_dir="/tmp")
ns = _namespace_from_configs(mc, tc)
assert ns.cls_loss_coef == pytest.approx(2.5)
# --- num_select must come from ModelConfig unconditionally ---
def test_num_select_from_model_config(self) -> None:
"""ns.num_select must equal mc.num_select regardless of tc.num_select."""
mc = RFDETRSegNanoConfig() # num_select=100
tc = TrainConfig(dataset_dir="/tmp") # num_select=300 (default — was the bug)
ns = _namespace_from_configs(mc, tc)
assert ns.num_select == 100
@pytest.mark.parametrize(
"config_class, expected_num_select",
[
pytest.param(RFDETRSegNanoConfig, 100, id="seg_nano"),
pytest.param(RFDETRBaseConfig, 300, id="base"),
],
)
def test_num_select_matches_model_config_variant(self, config_class, expected_num_select) -> None:
"""ns.num_select must equal the model config's num_select for each variant."""
mc = config_class()
tc = TrainConfig(dataset_dir="/tmp")
ns = _namespace_from_configs(mc, tc)
assert ns.num_select == expected_num_select
class TestBuildNamespaceDeprecated:
"""build_namespace() is a deprecated shim — verify the warning fires."""
def test_emits_deprecation_warning(self, reset_build_namespace_warning_state) -> None:
"""Every call to build_namespace() must emit a DeprecationWarning."""
from rfdetr._namespace import build_namespace
mc = RFDETRBaseConfig(num_classes=80)
tc = TrainConfig(dataset_dir="/tmp")
with pytest.warns(FutureWarning, match="build_namespace"):
build_namespace(mc, tc)
def test_result_identical_to_namespace_from_configs(self, reset_build_namespace_warning_state) -> None:
"""build_namespace output must equal _namespace_from_configs output."""
from rfdetr._namespace import build_namespace
from rfdetr.models._defaults import MODEL_DEFAULTS
mc = RFDETRBaseConfig(num_classes=80)
tc = TrainConfig(dataset_dir="/tmp")
with pytest.warns(FutureWarning):
ns_legacy = build_namespace(mc, tc)
ns_new = _namespace_from_configs(mc, tc, MODEL_DEFAULTS)
legacy_attrs = vars(ns_legacy)
new_attrs = vars(ns_new)
assert set(legacy_attrs.keys()) == set(new_attrs.keys()), (
f"Key mismatch: "
f"legacy_only={set(legacy_attrs) - set(new_attrs)}, "
f"new_only={set(new_attrs) - set(legacy_attrs)}"
)
for key in sorted(legacy_attrs):
assert legacy_attrs[key] == new_attrs[key], (
f"Value mismatch for '{key}': legacy={legacy_attrs[key]!r}, new={new_attrs[key]!r}"
)