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
147 lines
5.2 KiB
Python
147 lines
5.2 KiB
Python
# ------------------------------------------------------------------------
|
|
# RF-DETR
|
|
# Copyright (c) 2025 Roboflow. All Rights Reserved.
|
|
# Licensed under the Apache License, Version 2.0 [see LICENSE for details]
|
|
# ------------------------------------------------------------------------
|
|
|
|
|
|
import os
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from rfdetr.assets import ModelWeightAsset, ModelWeights, ModelWeightsBase
|
|
from rfdetr.assets.model_weights import get_model_cache_dir
|
|
|
|
|
|
class TestGetModelCacheDir:
|
|
"""Verify get_model_cache_dir reads RF_HOME with correct default."""
|
|
|
|
def test_default_when_rf_home_not_set(self, monkeypatch):
|
|
"""Returns ~/.roboflow/models when RF_HOME env var is absent."""
|
|
monkeypatch.delenv("RF_HOME", raising=False)
|
|
expected = os.path.normpath(os.path.expanduser("~/.roboflow/models"))
|
|
assert get_model_cache_dir() == expected
|
|
|
|
def test_custom_rf_home_absolute_path(self, monkeypatch, tmp_path):
|
|
"""Returns exact RF_HOME value when set to an absolute path."""
|
|
monkeypatch.setenv("RF_HOME", str(tmp_path))
|
|
assert get_model_cache_dir() == str(tmp_path)
|
|
|
|
def test_tilde_in_rf_home_is_expanded(self, monkeypatch):
|
|
"""Tilde in RF_HOME is expanded; result contains no literal tilde."""
|
|
monkeypatch.setenv("RF_HOME", "~/custom_rfdetr_cache")
|
|
result = get_model_cache_dir()
|
|
assert result == os.path.normpath(os.path.expanduser("~/custom_rfdetr_cache"))
|
|
assert "~" not in result
|
|
|
|
def test_returns_string(self, monkeypatch):
|
|
"""Return value is always a str, not a Path."""
|
|
monkeypatch.delenv("RF_HOME", raising=False)
|
|
assert isinstance(get_model_cache_dir(), str)
|
|
|
|
|
|
def test_from_filename_found():
|
|
"""Test from_filename with valid filename."""
|
|
asset = ModelWeights.from_filename("rf-detr-base.pth")
|
|
|
|
assert asset is not None
|
|
assert isinstance(asset, ModelWeightAsset)
|
|
assert asset.filename == "rf-detr-base.pth"
|
|
assert asset.url.startswith("http")
|
|
assert "rf-detr-base-coco.pth" in asset.url
|
|
|
|
|
|
def test_from_filename_not_found():
|
|
"""Test from_filename with invalid filename."""
|
|
asset = ModelWeights.from_filename("nonexistent-model.pth")
|
|
assert asset is None
|
|
|
|
|
|
def test_get_url():
|
|
"""Test get_url class method."""
|
|
url = ModelWeights.get_url("rf-detr-base.pth")
|
|
|
|
assert url is not None
|
|
assert isinstance(url, str)
|
|
assert url.startswith("http")
|
|
assert "rf-detr-base-coco.pth" in url
|
|
|
|
|
|
def test_get_url_not_found():
|
|
"""Test get_url with invalid filename."""
|
|
url = ModelWeights.get_url("nonexistent-model.pth")
|
|
assert url is None
|
|
|
|
|
|
def test_get_md5():
|
|
"""Test get_md5 class method."""
|
|
md5 = ModelWeights.get_md5("rf-detr-base.pth")
|
|
|
|
# MD5 may be None if not yet computed
|
|
assert md5 is None or isinstance(md5, str)
|
|
|
|
# If MD5 exists, verify format
|
|
if md5 is not None:
|
|
assert len(md5) == 32
|
|
assert all(c in "0123456789abcdef" for c in md5.lower())
|
|
|
|
|
|
def test_list_models():
|
|
"""Test list_models returns all model filenames."""
|
|
models = ModelWeights.list_models()
|
|
|
|
assert isinstance(models, list)
|
|
assert len(models) > 0
|
|
assert "rf-detr-base.pth" in models
|
|
assert "rf-detr-large.pth" in models
|
|
|
|
# All entries should be strings
|
|
assert all(isinstance(m, str) for m in models)
|
|
|
|
|
|
@pytest.mark.parametrize("asset", [pytest.param(a, id=a.filename) for a in ModelWeights])
|
|
def test_all_assets_have_valid_urls(asset: ModelWeightAsset) -> None:
|
|
"""Test that all assets have valid URLs."""
|
|
assert asset.url.startswith("http")
|
|
assert len(asset.url) > 20 # Reasonable minimum URL length
|
|
|
|
|
|
@pytest.mark.parametrize("asset", [pytest.param(a, id=a.filename) for a in ModelWeights])
|
|
def test_all_assets_have_valid_filenames(asset: ModelWeightAsset) -> None:
|
|
"""Test that all assets have valid filenames."""
|
|
assert len(asset.filename) > 0
|
|
assert asset.filename.endswith((".pth", ".pt"))
|
|
|
|
|
|
def test_filenames_are_unique():
|
|
"""Test that all filenames are unique (prevent accidental duplicates)."""
|
|
filenames = [asset.filename for asset in ModelWeights]
|
|
assert len(filenames) == len(set(filenames)), "Duplicate filenames detected"
|
|
|
|
|
|
def test_model_weight_asset_optional_md5():
|
|
"""Test that MD5 hash is optional (important for new models)."""
|
|
asset = ModelWeightAsset(filename="test-model.pth", url="https://example.com/test-model.pth")
|
|
|
|
assert asset.md5_hash is None, "MD5 hash should be optional"
|
|
|
|
|
|
def test_model_weights_inherits_from_base():
|
|
"""Test inheritance for compile-time safety contract."""
|
|
assert issubclass(ModelWeights, ModelWeightsBase), (
|
|
"ModelWeights must inherit from ModelWeightsBase for compatibility"
|
|
)
|
|
|
|
|
|
def test_rfdetr_large_deprecated_emits_future_warning() -> None:
|
|
"""RFDETRLargeDeprecated emits FutureWarning on instantiation via the pyDeprecate class decorator."""
|
|
from rfdetr.variants import RFDETRLargeDeprecated
|
|
|
|
# The proxy uses num_warns=1; reset counter so this test is order-independent
|
|
# regardless of whether another test already triggered the warning in this session.
|
|
RFDETRLargeDeprecated._cfg.warned = 0
|
|
with patch("rfdetr.detr.RFDETR.__init__", return_value=None):
|
|
with pytest.warns(FutureWarning):
|
|
RFDETRLargeDeprecated()
|