mirror of
https://github.com/blender/blender
synced 2026-09-29 04:37:17 +03:00
Tests: add tests for mathutils index access and slicing
Test coverage in this area was incomplete, hiding various issues, extend the tests in preparation for fixes and more complete coverage. Ref !158183
This commit is contained in:
parent
34f08cea0b
commit
6b6d06d806
1 changed files with 184 additions and 0 deletions
|
|
@ -69,6 +69,49 @@ def _test_flat_buffer_protocol(self, ty, n):
|
|||
view[0] = 1
|
||||
|
||||
|
||||
class GenericSliceMixIn:
|
||||
"""
|
||||
Slice get/set tests parameterized by ``generic_len``
|
||||
(the length of the slice-target sequence).
|
||||
Sub-classes must:
|
||||
- Set ``generic_len``.
|
||||
- Implement ``generic_make(values)`` returning a fresh instance whose
|
||||
contents equal ``values``. Required because some types (e.g. ``MatrixAccess``) can't be constructed by type.
|
||||
- Inherit ``unittest.TestCase``.
|
||||
"""
|
||||
generic_len = 0
|
||||
|
||||
def generic_make(self, values):
|
||||
raise NotImplementedError
|
||||
|
||||
def test_slice_set_self_aliased_full(self):
|
||||
# `obj[:] = obj` writes original values back; effectively a no-op.
|
||||
base = tuple(float(i + 1) for i in range(self.generic_len))
|
||||
obj = self.generic_make(base)
|
||||
obj[:] = obj
|
||||
self.assertEqual(tuple(obj), base)
|
||||
|
||||
def test_slice_set_type_error_atomic(self):
|
||||
# A bad element in seq must raise TypeError without partially mutating the object.
|
||||
# Catches a future "optimization" that drops atomicity.
|
||||
base = tuple(float(i + 1) for i in range(self.generic_len))
|
||||
obj = self.generic_make(base)
|
||||
# `obj[-2:]` is `slice_length=2` for any `generic_len >= 2`,
|
||||
# so the parse failure is uniformly on the bad element rather
|
||||
# than depending on whether a length-mismatch check fires first.
|
||||
with self.assertRaises(TypeError):
|
||||
obj[-2:] = [99.0, "not_a_float"]
|
||||
self.assertEqual(tuple(obj), base)
|
||||
|
||||
def test_slice_set_identity_preserved(self):
|
||||
# In-place slice assignment must not replace the underlying object.
|
||||
base = tuple(float(i + 1) for i in range(self.generic_len))
|
||||
obj = self.generic_make(base)
|
||||
original_id = id(obj)
|
||||
obj[:] = base
|
||||
self.assertEqual(id(obj), original_id)
|
||||
|
||||
|
||||
class MatrixTesting(unittest.TestCase):
|
||||
|
||||
def assertAlmostEqualMatrix(self, first, second, size, *, places=6, msg=None, delta=None):
|
||||
|
|
@ -309,6 +352,98 @@ class MatrixTesting(unittest.TestCase):
|
|||
self.assertEqual(view.tolist(), expected)
|
||||
|
||||
|
||||
class MatrixSliceMixIn:
|
||||
"""
|
||||
Slice get/set tests for the ``Matrix`` row sequence.
|
||||
Items are row ``Vector`` of length ``matrix_size``.
|
||||
|
||||
``matrix_access_attr`` selects which sequence is being sliced:
|
||||
``""`` for the matrix itself (``mat[i:j]``), or ``"row"`` / ``"col"``
|
||||
for the ``MatrixAccess`` wrappers (``mat.row[i:j]`` / ``mat.col[i:j]``).
|
||||
"""
|
||||
matrix_size = 0
|
||||
matrix_access_attr = ""
|
||||
|
||||
def _make_value(self, seed):
|
||||
"""
|
||||
A distinct ``Vector`` value for the given integer seed.
|
||||
"""
|
||||
return Vector(tuple(float(seed * 100 + j) for j in range(self.matrix_size)))
|
||||
|
||||
def _make_obj(self, values):
|
||||
"""
|
||||
Build a `Matrix` whose rows or columns equal ``values``,
|
||||
and return ``(mat, accessor)`` where ``accessor`` is the
|
||||
sequence being sliced (the matrix itself, or ``mat.row`` / ``mat.col``).
|
||||
"""
|
||||
n = self.matrix_size
|
||||
if self.matrix_access_attr == "col":
|
||||
rows = tuple(
|
||||
tuple(values[c][r] for c in range(n))
|
||||
for r in range(n)
|
||||
)
|
||||
else:
|
||||
assert self.matrix_access_attr in {"", "row"}
|
||||
rows = tuple(tuple(v) for v in values)
|
||||
mat = Matrix(rows)
|
||||
accessor = mat if self.matrix_access_attr == "" else getattr(mat, self.matrix_access_attr)
|
||||
return mat, accessor
|
||||
|
||||
def test_slice_set_type_error_atomic(self):
|
||||
# A non-sequence element must raise TypeError without mutating the matrix.
|
||||
# `99.0` isn't a row/col-sequence so the parse fails on it.
|
||||
n = self.matrix_size
|
||||
base = tuple(self._make_value(i + 1) for i in range(n))
|
||||
_mat, obj = self._make_obj(base)
|
||||
# `obj[-2:]` is `slice_length=2` for any `generic_len >= 2`,
|
||||
# so the parse failure is uniformly on the bad element rather
|
||||
# than depending on whether a length-mismatch check fires first.
|
||||
with self.assertRaises(TypeError):
|
||||
obj[-2:] = [99.0, "not_a_float"]
|
||||
self.assertEqual(tuple(obj), base)
|
||||
|
||||
def test_slice_get(self):
|
||||
n = self.matrix_size
|
||||
base = tuple(self._make_value(i + 1) for i in range(n))
|
||||
_mat, obj = self._make_obj(base)
|
||||
self.assertIsInstance(obj[:], tuple)
|
||||
self.assertEqual(obj[:], base)
|
||||
self.assertEqual(obj[1:], base[1:])
|
||||
self.assertEqual(obj[:-1], base[:-1])
|
||||
self.assertEqual(obj[-1:], base[-1:])
|
||||
self.assertEqual(obj[1:1], ())
|
||||
|
||||
|
||||
class Matrix3x3TestingSlice(MatrixSliceMixIn, unittest.TestCase):
|
||||
matrix_size = 3
|
||||
matrix_access_attr = ""
|
||||
|
||||
|
||||
class Matrix4x4TestingSlice(MatrixSliceMixIn, unittest.TestCase):
|
||||
matrix_size = 4
|
||||
matrix_access_attr = ""
|
||||
|
||||
|
||||
class MatrixAccess3x3RowTestingSlice(MatrixSliceMixIn, unittest.TestCase):
|
||||
matrix_size = 3
|
||||
matrix_access_attr = "row"
|
||||
|
||||
|
||||
class MatrixAccess3x3ColTestingSlice(MatrixSliceMixIn, unittest.TestCase):
|
||||
matrix_size = 3
|
||||
matrix_access_attr = "col"
|
||||
|
||||
|
||||
class MatrixAccess4x4RowTestingSlice(MatrixSliceMixIn, unittest.TestCase):
|
||||
matrix_size = 4
|
||||
matrix_access_attr = "row"
|
||||
|
||||
|
||||
class MatrixAccess4x4ColTestingSlice(MatrixSliceMixIn, unittest.TestCase):
|
||||
matrix_size = 4
|
||||
matrix_access_attr = "col"
|
||||
|
||||
|
||||
class VectorTesting(unittest.TestCase):
|
||||
|
||||
def test_orthogonal(self):
|
||||
|
|
@ -373,6 +508,34 @@ class VectorTesting(unittest.TestCase):
|
|||
_test_flat_buffer_protocol(self, Vector, 10)
|
||||
|
||||
|
||||
class VectorTestingSlice2(GenericSliceMixIn, unittest.TestCase):
|
||||
generic_len = 2
|
||||
|
||||
def generic_make(self, values):
|
||||
return Vector(values)
|
||||
|
||||
|
||||
class VectorTestingSlice3(GenericSliceMixIn, unittest.TestCase):
|
||||
generic_len = 3
|
||||
|
||||
def generic_make(self, values):
|
||||
return Vector(values)
|
||||
|
||||
|
||||
class VectorTestingSlice4(GenericSliceMixIn, unittest.TestCase):
|
||||
generic_len = 4
|
||||
|
||||
def generic_make(self, values):
|
||||
return Vector(values)
|
||||
|
||||
|
||||
class VectorTestingSlice10(GenericSliceMixIn, unittest.TestCase):
|
||||
generic_len = 10
|
||||
|
||||
def generic_make(self, values):
|
||||
return Vector(values)
|
||||
|
||||
|
||||
class QuaternionTesting(unittest.TestCase):
|
||||
|
||||
def test_to_expmap(self):
|
||||
|
|
@ -405,18 +568,39 @@ class QuaternionTesting(unittest.TestCase):
|
|||
_test_flat_buffer_protocol(self, Quaternion, 4)
|
||||
|
||||
|
||||
class QuaternionTestingSlice(GenericSliceMixIn, unittest.TestCase):
|
||||
generic_len = 4
|
||||
|
||||
def generic_make(self, values):
|
||||
return Quaternion(values)
|
||||
|
||||
|
||||
class EulerTesting(unittest.TestCase):
|
||||
|
||||
def test_buffer_protocol(self):
|
||||
_test_flat_buffer_protocol(self, Euler, 3)
|
||||
|
||||
|
||||
class EulerTestingSlice(GenericSliceMixIn, unittest.TestCase):
|
||||
generic_len = 3
|
||||
|
||||
def generic_make(self, values):
|
||||
return Euler(values)
|
||||
|
||||
|
||||
class ColorTesting(unittest.TestCase):
|
||||
|
||||
def test_buffer_protocol(self):
|
||||
_test_flat_buffer_protocol(self, Color, 3)
|
||||
|
||||
|
||||
class ColorTestingSlice(GenericSliceMixIn, unittest.TestCase):
|
||||
generic_len = 3
|
||||
|
||||
def generic_make(self, values):
|
||||
return Color(values)
|
||||
|
||||
|
||||
# Test features of `mathutils` types.
|
||||
class TypeTesting(unittest.TestCase):
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue