Files
wehub-resource-sync 9194ef5abd
Docs/Test Workflow / Test docs build (push) Failing after 0s
Check links & references / links-check (push) Failing after 1s
Pytest/Test Workflow / Import Test and Pytest Run (ubuntu-latest, 3.10) (push) Failing after 0s
Pytest/Test Workflow / Import Test and Pytest Run (ubuntu-latest, 3.11) (push) Failing after 0s
PR Conflict Labeler / main (push) Failing after 2s
Pytest/Test Workflow / Import Test and Pytest Run (ubuntu-latest, 3.12) (push) Failing after 2s
Pytest/Test Workflow / Import Test and Pytest Run (ubuntu-latest, 3.13) (push) Failing after 0s
Pytest/Test Workflow / Build this Package (push) Failing after 5s
Pytest/Test Workflow / Import Test and Pytest Run (macos-latest, 3.10) (push) Has been cancelled
Pytest/Test Workflow / Import Test and Pytest Run (macos-latest, 3.11) (push) Has been cancelled
Pytest/Test Workflow / Import Test and Pytest Run (macos-latest, 3.12) (push) Has been cancelled
Pytest/Test Workflow / Import Test and Pytest Run (macos-latest, 3.13) (push) Has been cancelled
Pytest/Test Workflow / Import Test and Pytest Run (windows-latest, 3.10) (push) Has been cancelled
Pytest/Test Workflow / Import Test and Pytest Run (windows-latest, 3.11) (push) Has been cancelled
Pytest/Test Workflow / Import Test and Pytest Run (windows-latest, 3.12) (push) Has been cancelled
Pytest/Test Workflow / Import Test and Pytest Run (windows-latest, 3.13) (push) Has been cancelled
Pytest/Test Workflow / testing-guardian (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:06:10 +08:00

324 lines
11 KiB
Python

import random
from contextlib import ExitStack as DoesNotRaise
from pathlib import Path
from typing import TypeVar
import numpy as np
import pytest
import supervision.dataset.utils as dataset_utils
from supervision import Detections
from supervision.dataset.utils import (
approximate_mask_with_polygons,
build_class_index_mapping,
check_no_basename_collisions,
map_detections_class_id,
merge_class_lists,
train_test_split,
)
from tests.helpers import _create_detections
T = TypeVar("T")
@pytest.mark.parametrize(
("data", "train_ratio", "random_state", "shuffle", "expected_result", "exception"),
[
([], 0.5, None, False, ([], []), DoesNotRaise()), # empty data
(
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
0.5,
None,
False,
([0, 1, 2, 3, 4], [5, 6, 7, 8, 9]),
DoesNotRaise(),
), # data with 10 numbers and 50% train split
(
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
1.0,
None,
False,
([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], []),
DoesNotRaise(),
), # data with 10 numbers and 100% train split
(
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
0.0,
None,
False,
([], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
DoesNotRaise(),
), # data with 10 numbers and 0% train split
(
["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"],
0.5,
None,
False,
(["a", "b", "c", "d", "e"], ["f", "g", "h", "i", "j"]),
DoesNotRaise(),
), # data with 10 chars and 50% train split
(
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
0.5,
23,
True,
([7, 8, 5, 6, 3], [2, 9, 0, 1, 4]),
DoesNotRaise(),
), # data with 10 numbers and 50% train split with 23 random seed
(
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
0.5,
32,
True,
([4, 6, 0, 8, 9], [5, 7, 2, 3, 1]),
DoesNotRaise(),
), # data with 10 numbers and 50% train split with 23 random seed
],
)
def test_train_test_split(
data: list[T],
train_ratio: float,
random_state: int,
shuffle: bool,
expected_result: tuple[list[T], list[T]] | None,
exception: Exception,
) -> None:
with exception:
result = train_test_split(
data=data,
train_ratio=train_ratio,
random_state=random_state,
shuffle=shuffle,
)
assert result == expected_result
def test_approximate_mask_with_polygons_default_preserves_polygon(
monkeypatch,
) -> None:
"""Default mask polygon conversion forwards zero simplification."""
percentages: list[float] = []
def fake_approximate_polygon(polygon: np.ndarray, percentage: float) -> np.ndarray:
"""Capture simplification percentage while preserving the polygon."""
percentages.append(percentage)
return polygon
monkeypatch.setattr(dataset_utils, "approximate_polygon", fake_approximate_polygon)
approximate_mask_with_polygons(np.ones((3, 3), dtype=bool))
assert percentages == [0.0]
@pytest.mark.parametrize(
("class_lists", "expected_result", "exception"),
[
([], [], DoesNotRaise()), # empty class lists
(
[["dog", "person"]],
["dog", "person"],
DoesNotRaise(),
), # single class list; already alphabetically sorted
(
[["person", "dog"]],
["dog", "person"],
DoesNotRaise(),
), # single class list; not alphabetically sorted
(
[["dog", "person"], ["dog", "person"]],
["dog", "person"],
DoesNotRaise(),
), # two class lists; the same classes; already alphabetically sorted
(
[["dog", "person"], ["cat"]],
["cat", "dog", "person"],
DoesNotRaise(),
), # two class lists; different classes; already alphabetically sorted
],
)
def test_merge_class_maps(
class_lists: list[list[str]], expected_result: list[str], exception: Exception
) -> None:
with exception:
result = merge_class_lists(class_lists=class_lists)
assert result == expected_result
@pytest.mark.parametrize(
("source_classes", "target_classes", "expected_result", "exception"),
[
([], [], {}, DoesNotRaise()), # empty class lists
([], ["dog", "person"], {}, DoesNotRaise()), # empty source class list
(
["dog", "person"],
[],
None,
pytest.raises(ValueError, match="Class dog not found"),
), # empty target class list
(
["dog", "person"],
["dog", "person"],
{0: 0, 1: 1},
DoesNotRaise(),
), # same class lists
(
["dog", "person"],
["person", "dog"],
{0: 1, 1: 0},
DoesNotRaise(),
), # same class lists but not alphabetically sorted
(
["dog", "person"],
["cat", "dog", "person"],
{0: 1, 1: 2},
DoesNotRaise(),
), # source class list is a subset of target class list
(
["dog", "person"],
["cat", "dog"],
None,
pytest.raises(ValueError, match="Class person not found"),
), # source class list is not a subset of target class list
],
)
def test_build_class_index_mapping(
source_classes: list[str],
target_classes: list[str],
expected_result: dict[int, int] | None,
exception: Exception,
) -> None:
with exception:
result = build_class_index_mapping(
source_classes=source_classes, target_classes=target_classes
)
assert result == expected_result
@pytest.mark.parametrize(
("source_to_target_mapping", "detections", "expected_result", "exception"),
[
(
{},
_create_detections(xyxy=[[0, 0, 10, 10]], class_id=[0]),
None,
pytest.raises(ValueError, match="subset of source_to_target_mapping"),
), # empty mapping
(
{0: 1},
_create_detections(xyxy=[[0, 0, 10, 10]], class_id=[0]),
_create_detections(xyxy=[[0, 0, 10, 10]], class_id=[1]),
DoesNotRaise(),
), # single mapping
(
{0: 1, 1: 2},
Detections.empty(),
Detections.empty(),
DoesNotRaise(),
), # empty detections
(
{0: 1, 1: 2},
_create_detections(xyxy=[[0, 0, 10, 10]], class_id=[0]),
_create_detections(xyxy=[[0, 0, 10, 10]], class_id=[1]),
DoesNotRaise(),
), # multiple mappings
(
{0: 1, 1: 2},
_create_detections(xyxy=[[0, 0, 10, 10], [0, 0, 10, 10]], class_id=[0, 1]),
_create_detections(xyxy=[[0, 0, 10, 10], [0, 0, 10, 10]], class_id=[1, 2]),
DoesNotRaise(),
), # multiple mappings
(
{0: 1, 1: 2},
_create_detections(xyxy=[[0, 0, 10, 10]], class_id=[2]),
None,
pytest.raises(ValueError, match="source_to_target_mapping keys"),
), # class_id not in mapping
(
{0: 1, 1: 2},
_create_detections(xyxy=[[0, 0, 10, 10]], class_id=[0], confidence=[0.5]),
_create_detections(xyxy=[[0, 0, 10, 10]], class_id=[1], confidence=[0.5]),
DoesNotRaise(),
), # confidence is not None
],
)
def test_map_detections_class_id(
source_to_target_mapping: dict[int, int],
detections: Detections,
expected_result: Detections | None,
exception: Exception,
) -> None:
with exception:
result = map_detections_class_id(
source_to_target_mapping=source_to_target_mapping, detections=detections
)
assert result == expected_result
class TestTrainTestSplitRngIsolation:
"""Regression tests for train_test_split RNG isolation (DAT-02)."""
def test_does_not_mutate_input_list(self) -> None:
"""split() must not reorder the caller's list in place."""
data = list(range(10))
original = data.copy()
train_test_split(data=data, train_ratio=0.5, random_state=42, shuffle=True)
assert data == original
def test_does_not_pollute_global_rng(self) -> None:
"""split() must not disturb the process-global random state."""
state_before = random.getstate()
train_test_split(
data=list(range(10)), train_ratio=0.5, random_state=42, shuffle=True
)
assert random.getstate() == state_before
def test_result_independent_of_global_rng(self) -> None:
"""A fixed random_state yields the same split regardless of global RNG."""
first = train_test_split(
data=list(range(10)), train_ratio=0.5, random_state=42, shuffle=True
)
for _ in range(5):
random.random() # noqa: S311 — perturb global RNG; split must ignore it
second = train_test_split(
data=list(range(10)), train_ratio=0.5, random_state=42, shuffle=True
)
assert first == second
class TestCheckNoBasenameCollisions:
"""Regression tests for export basename collision detection (DAT-04)."""
def test_raises_on_colliding_output_names(self) -> None:
"""Two source paths mapping to one output name must raise ValueError."""
with pytest.raises(ValueError, match="both map to image file"):
check_no_basename_collisions(
image_paths=["a/img.jpg", "b/img.jpg"],
key=lambda image_path: Path(image_path).name,
output_kind="image",
)
def test_passes_on_unique_output_names(self) -> None:
"""Distinct output names must not raise."""
check_no_basename_collisions(
image_paths=["a/img1.jpg", "b/img2.jpg"],
key=lambda image_path: Path(image_path).name,
output_kind="image",
)
def test_passes_on_empty_image_paths(self) -> None:
"""Empty list must not raise (vacuously no collision)."""
check_no_basename_collisions(
image_paths=[],
key=lambda image_path: Path(image_path).name,
output_kind="image",
)
def test_passes_on_single_image_path(self) -> None:
"""Single element list cannot collide with itself."""
check_no_basename_collisions(
image_paths=["dir/only.jpg"],
key=lambda image_path: Path(image_path).name,
output_kind="image",
)