Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions tests/shapes/circles/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Test data_morph.shapes.circles subpackage."""
37 changes: 37 additions & 0 deletions tests/shapes/circles/bases.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Base test class for circle shapes."""

from __future__ import annotations

import re
from typing import TYPE_CHECKING

import pytest

if TYPE_CHECKING:
from numbers import Number

CIRCLE_REPR = r'<Circle center=\((\d+\.*\d*), (\d+\.*\d*)\) radius=(\d+\.*\d*)>'


class CirclesModuleTestBase:
"""Base for testing circle shapes."""

shape_name: str
distance_test_cases: tuple[tuple[tuple[Number], float]]
repr_regex: str

@pytest.fixture(scope='class')
def shape(self, shape_factory):
"""Fixture to get the shape for testing."""
return shape_factory.generate_shape(self.shape_name)

def test_distance(self, shape, test_point, expected_distance):
"""
Test the distance() method parametrized by distance_test_cases
(see conftest.py).
"""
assert pytest.approx(shape.distance(*test_point)) == expected_distance

def test_repr(self, shape):
"""Test that the __repr__() method is working."""
assert re.match(self.repr_regex, repr(shape)) is not None
29 changes: 29 additions & 0 deletions tests/shapes/circles/test_bullseye.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Test the bullseye module."""

import numpy as np
import pytest

from .bases import CIRCLE_REPR, CirclesModuleTestBase

pytestmark = [pytest.mark.shapes, pytest.mark.circles]


class TestBullseye(CirclesModuleTestBase):
"""Test the Bullseye class."""

shape_name = 'bullseye'
distance_test_cases = (((20, 50), 3.660254), ((10, 25), 9.08004))
repr_regex = (
r'^<Bullseye>\n'
r' circles=\n'
r' ' + CIRCLE_REPR + '\n'
r' ' + CIRCLE_REPR + '$'
)

def test_init(self, shape):
"""Test that the Bullseye contains two concentric circles."""
assert len(shape.circles) == 2

a, b = shape.circles
assert np.array_equal(a.center, b.center)
assert a.radius != b.radius
26 changes: 26 additions & 0 deletions tests/shapes/circles/test_circle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Test the circle module."""

import numpy as np
import pytest

from .bases import CIRCLE_REPR, CirclesModuleTestBase

pytestmark = [pytest.mark.shapes, pytest.mark.circles]


class TestCircle(CirclesModuleTestBase):
"""Test the Circle class."""

shape_name = 'circle'
distance_test_cases = (((20, 50), 10.490381), ((10, 25), 15.910168))
repr_regex = '^' + CIRCLE_REPR + '$'

def test_is_circle(self, shape):
"""Test that the Circle is a valid circle (mathematically)."""
angles = np.arange(0, 361, 45)
cx, cy = shape.center
for x, y in zip(
cx + shape.radius * np.cos(angles),
cy + shape.radius * np.sin(angles),
):
assert pytest.approx(shape.distance(x, y)) == 0
45 changes: 45 additions & 0 deletions tests/shapes/circles/test_rings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""Test the rings module."""

import numpy as np
import pytest

from .bases import CIRCLE_REPR, CirclesModuleTestBase

pytestmark = [pytest.mark.shapes, pytest.mark.circles]


class TestRings(CirclesModuleTestBase):
"""Test the Rings class."""

shape_name = 'rings'
distance_test_cases = (((20, 50), 3.16987), ((10, 25), 9.08004))
repr_regex = (
r'^<Rings>\n'
r' circles=\n'
r' ' + CIRCLE_REPR + '\n'
r' ' + CIRCLE_REPR + '\n'
r' ' + CIRCLE_REPR + '\n'
r' ' + CIRCLE_REPR + '$'
)

@pytest.mark.parametrize('num_rings', [3, 5])
def test_init(self, shape_factory, num_rings):
"""Test that the Rings contains multiple concentric circles."""
shape = shape_factory.generate_shape(self.shape_name, num_rings=num_rings)

assert len(shape.circles) == num_rings
assert all(
np.array_equal(circle.center, shape.circles[0].center)
for circle in shape.circles[1:]
)
assert len({circle.radius for circle in shape.circles}) == num_rings

@pytest.mark.parametrize('num_rings', ['3', -5, 1, True])
def test_num_rings_is_valid(self, shape_factory, num_rings):
"""Test that num_rings input validation is working."""
if isinstance(num_rings, int):
with pytest.raises(ValueError, match='num_rings must be greater than 1'):
_ = shape_factory.generate_shape(self.shape_name, num_rings=num_rings)
else:
with pytest.raises(TypeError, match='num_rings must be an integer'):
_ = shape_factory.generate_shape(self.shape_name, num_rings=num_rings)
1 change: 1 addition & 0 deletions tests/shapes/lines/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Test data_morph.shapes.lines subpackage."""
99 changes: 99 additions & 0 deletions tests/shapes/lines/bases.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
"""Base test classes for line shapes."""

from __future__ import annotations

from numbers import Number

import numpy as np
import pytest


class LinesModuleTestBase:
"""Base for testing line-based shapes."""

shape_name: str
distance_test_cases: tuple[tuple[tuple[Number], float]]
expected_line_count: int
expected_slopes: tuple[Number] | Number

@pytest.fixture(scope='class')
def shape(self, shape_factory):
"""Fixture to get the shape for testing."""
return shape_factory.generate_shape(self.shape_name)

@pytest.fixture(scope='class')
def slopes(self, shape):
"""Fixture to get the slopes of the lines."""
xs, ys = np.array(shape.lines).T
runs = np.diff(xs, axis=0)
rises = np.diff(ys, axis=0)
slopes = rises / np.ma.masked_array(runs, mask=runs == 0)
return slopes.filled(np.inf)

def test_init(self, shape):
"""Test that the shape consists of the correct number of distinct lines."""
num_unique_lines, *_ = np.unique(shape.lines, axis=0).shape
assert num_unique_lines == self.expected_line_count

def test_distance(self, shape, test_point, expected_distance):
"""
Test the distance() method parametrized by distance_test_cases
(see conftest.py).
"""
assert pytest.approx(shape.distance(*test_point)) == expected_distance

def test_slopes(self, slopes):
"""Test that the slopes are as expected."""
expected = (
[self.expected_slopes]
if isinstance(self.expected_slopes, Number)
else self.expected_slopes
)
assert np.array_equal(np.unique(slopes), expected)


class ParallelLinesModuleTestBase(LinesModuleTestBase):
"""Base for testing parallel line-based shapes."""

def test_lines_are_parallel(self, slopes):
"""Test that the lines are parallel (slopes are equal)."""
assert np.unique(slopes).size == 1


class PolygonsLineModuleTestBase:
"""Base for testing polygon shapes."""

shape_name: str
distance_test_cases: tuple[tuple[tuple[Number], float]]
expected_line_count: int

@pytest.fixture(scope='class')
def shape(self, shape_factory):
"""Fixture to get the shape for testing."""
return shape_factory.generate_shape(self.shape_name)

@pytest.fixture(scope='class')
def slopes(self, shape):
"""Fixture to get the slopes of the lines."""
xs, ys = np.array(shape.lines).T
runs = np.diff(xs, axis=0)
rises = np.diff(ys, axis=0)
slopes = rises / np.ma.masked_array(runs, mask=runs == 0)
return slopes.filled(np.inf)

def test_init(self, shape):
"""Test that the shape consists of the correct number of distinct lines."""
num_unique_lines, *_ = np.unique(shape.lines, axis=0).shape
assert num_unique_lines == self.expected_line_count

def test_distance(self, shape, test_point, expected_distance):
"""
Test the distance() method parametrized by distance_test_cases
(see conftest.py).
"""
assert pytest.approx(shape.distance(*test_point)) == expected_distance

def test_lines_form_polygon(self, shape):
"""Test that the lines form a polygon."""
endpoints = np.array(shape.lines).reshape(-1, 2)
assert np.unique(endpoints, axis=0).shape[0] == self.expected_line_count
20 changes: 20 additions & 0 deletions tests/shapes/lines/test_diamond.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""Test the diamond module."""

import numpy as np
import pytest

from .bases import PolygonsLineModuleTestBase

pytestmark = [pytest.mark.shapes, pytest.mark.lines, pytest.mark.polygons]


class TestDiamond(PolygonsLineModuleTestBase):
"""Test the Diamond class."""

shape_name = 'diamond'
distance_test_cases = (((20, 50), 0.0), ((30, 60), 2.773501))
expected_line_count = 4

def test_slopes(self, slopes):
"""Test that the slopes are as expected."""
np.testing.assert_array_equal(np.sort(slopes).flatten(), [-1.5, -1.5, 1.5, 1.5])
16 changes: 16 additions & 0 deletions tests/shapes/lines/test_high_lines.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""Test the high_lines module."""

import pytest

from .bases import ParallelLinesModuleTestBase

pytestmark = [pytest.mark.shapes, pytest.mark.lines]


class TestHighLines(ParallelLinesModuleTestBase):
"""Test the HighLines class."""

shape_name = 'high_lines'
distance_test_cases = (((20, 50), 6.0), ((30, 60), 4.0))
expected_line_count = 2
expected_slopes = 0
16 changes: 16 additions & 0 deletions tests/shapes/lines/test_horizontal_lines.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""Test the horizontal_lines module."""

import pytest

from .bases import ParallelLinesModuleTestBase

pytestmark = [pytest.mark.shapes, pytest.mark.lines]


class TestHorizontalLines(ParallelLinesModuleTestBase):
"""Test the HorizontalLines class."""

shape_name = 'h_lines'
distance_test_cases = (((20, 50), 0.0), ((30, 60), 2.5))
expected_line_count = 5
expected_slopes = 0
20 changes: 20 additions & 0 deletions tests/shapes/lines/test_rectangle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""Test the rectangle module."""

import numpy as np
import pytest

from .bases import PolygonsLineModuleTestBase

pytestmark = [pytest.mark.shapes, pytest.mark.lines, pytest.mark.polygons]


class TestRectangle(PolygonsLineModuleTestBase):
"""Test the Rectangle class."""

shape_name = 'rectangle'
distance_test_cases = (((20, 50), 0.0), ((30, 60), 2.0))
expected_line_count = 4

def test_slopes(self, slopes):
"""Test that the slopes are as expected."""
np.testing.assert_array_equal(np.sort(slopes).flatten(), [0, 0, np.inf, np.inf])
16 changes: 16 additions & 0 deletions tests/shapes/lines/test_slant_down_lines.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""Test the slant_down module."""

import pytest

from .bases import ParallelLinesModuleTestBase

pytestmark = [pytest.mark.shapes, pytest.mark.lines]


class TestSlantDownLines(ParallelLinesModuleTestBase):
"""Test the SlantDownLines class."""

shape_name = 'slant_down'
distance_test_cases = (((20, 50), 1.664101), ((30, 60), 0.554700))
expected_line_count = 5
expected_slopes = -1.5
16 changes: 16 additions & 0 deletions tests/shapes/lines/test_slant_up_lines.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""Test the slant_down module."""

import pytest

from .bases import ParallelLinesModuleTestBase

pytestmark = [pytest.mark.shapes, pytest.mark.lines]


class TestSlantUpLines(ParallelLinesModuleTestBase):
"""Test the SlantUpLines class."""

shape_name = 'slant_up'
distance_test_cases = (((20, 50), 1.664101), ((30, 60), 1.109400))
expected_line_count = 5
expected_slopes = 1.5
15 changes: 15 additions & 0 deletions tests/shapes/lines/test_star.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Test the star module."""

import pytest

from .bases import PolygonsLineModuleTestBase

pytestmark = [pytest.mark.shapes, pytest.mark.lines, pytest.mark.polygons]


class TestStar(PolygonsLineModuleTestBase):
"""Test the Star class."""

shape_name = 'star'
distance_test_cases = (((20, 50), 5.856516), ((30, 60), 3.709127))
expected_line_count = 10
17 changes: 17 additions & 0 deletions tests/shapes/lines/test_vertical_lines.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Test the vertical_lines module."""

import numpy as np
import pytest

from .bases import ParallelLinesModuleTestBase

pytestmark = [pytest.mark.shapes, pytest.mark.lines]


class TestVerticalLines(ParallelLinesModuleTestBase):
"""Test the VerticalLines class."""

shape_name = 'v_lines'
distance_test_cases = (((35, 60), 5.0), ((30, 60), 0.0))
expected_line_count = 5
expected_slopes = np.inf
Loading