Tests: bpy props: Add systematic tests for non-array types.
Fairly basic tests still, would need to be extended to check invalid cases handling too. Pull Request: https://projects.blender.org/blender/blender/pulls/145136
This commit is contained in:
committed by
Bastien Montagne
parent
f4421dfc8e
commit
8bb8bff4bf
@@ -260,6 +260,11 @@ add_blender_test(
|
||||
--python ${CMAKE_CURRENT_LIST_DIR}/bl_pyapi_idprop_datablock.py
|
||||
)
|
||||
|
||||
add_blender_test(
|
||||
script_pyapi_prop
|
||||
--python ${CMAKE_CURRENT_LIST_DIR}/bl_pyapi_prop.py
|
||||
)
|
||||
|
||||
add_blender_test(
|
||||
script_pyapi_prop_array
|
||||
--python ${CMAKE_CURRENT_LIST_DIR}/bl_pyapi_prop_array.py
|
||||
|
||||
@@ -254,6 +254,11 @@ class TestIdPropertyUIData(TestHelper, unittest.TestCase):
|
||||
self.assertEqual(self.id.path_resolve('["%s"]' % bpy.utils.escape_identifier(self.key_id)), 'A')
|
||||
|
||||
|
||||
# NOTE: the tests below are fairly deep checks on expected consistency in the py-defined dynamic RNA properties,
|
||||
# between their python representations (as RNA properties) and their underlying (IDProperty-based) storage.
|
||||
# See `bl_pyapi_prop.py` and `bl_pyapi_prop_array.py` for more basic but systematic testing
|
||||
# of all `bpy.props`-defined property types.
|
||||
|
||||
# Check statically typed underlying IDProperties storage for dynamic RNA properties.
|
||||
class TestIdPropertyDynamicRNA(TestHelper, unittest.TestCase):
|
||||
|
||||
|
||||
329
tests/python/bl_pyapi_prop.py
Normal file
329
tests/python/bl_pyapi_prop.py
Normal file
@@ -0,0 +1,329 @@
|
||||
# SPDX-FileCopyrightText: 2020-2023 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# NOTE: See also `bl_pyapi_prop_array.py` for the `Vector` bpy.props similar tests,
|
||||
# and `bl_pyapi_idprop.py` for some deeper testing of the consistency between
|
||||
# the underlying IDProperty storage, and the property data exposed in Python.
|
||||
|
||||
# ./blender.bin --background --python tests/python/bl_pyapi_prop.py -- --verbose
|
||||
import bpy
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
EnumProperty,
|
||||
StringProperty,
|
||||
PointerProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
import unittest
|
||||
import functools
|
||||
|
||||
id_inst = bpy.context.scene
|
||||
id_type = bpy.types.Scene
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Utility Types
|
||||
|
||||
class TestPropertyGroup(bpy.types.PropertyGroup):
|
||||
test_prop: IntProperty()
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Tests
|
||||
|
||||
class TestPropNumerical(unittest.TestCase):
|
||||
default_value = 0
|
||||
custom_value = 1
|
||||
|
||||
def setUp(self):
|
||||
id_type.test_bool = BoolProperty(default=bool(self.default_value))
|
||||
id_type.test_int = IntProperty(default=int(self.default_value))
|
||||
id_type.test_float = FloatProperty(default=float(self.default_value))
|
||||
|
||||
self.test_bool_storage = bool(self.custom_value)
|
||||
|
||||
def set_(s, v):
|
||||
self.test_bool_storage = v
|
||||
id_type.test_bool_getset = BoolProperty(
|
||||
default=bool(self.default_value),
|
||||
get=lambda s: self.test_bool_storage,
|
||||
set=set_,
|
||||
)
|
||||
|
||||
self.test_int_storage = int(self.custom_value)
|
||||
|
||||
def set_(s, v):
|
||||
self.test_int_storage = v
|
||||
id_type.test_int_getset = IntProperty(
|
||||
default=int(self.default_value),
|
||||
get=lambda s: self.test_int_storage,
|
||||
set=set_,
|
||||
)
|
||||
|
||||
self.test_float_storage = float(self.custom_value)
|
||||
|
||||
def set_(s, v):
|
||||
self.test_float_storage = v
|
||||
id_type.test_float_getset = FloatProperty(
|
||||
default=float(self.default_value),
|
||||
get=lambda s: self.test_float_storage,
|
||||
set=set_,
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
del id_type.test_float
|
||||
del id_type.test_int
|
||||
del id_type.test_bool
|
||||
|
||||
del id_type.test_float_getset
|
||||
del id_type.test_int_getset
|
||||
del id_type.test_bool_getset
|
||||
|
||||
def do_test_access(self, prop_name, py_type, expected_value):
|
||||
v = getattr(id_inst, prop_name)
|
||||
self.assertIsInstance(v, py_type)
|
||||
self.assertEqual(v, expected_value)
|
||||
setattr(id_inst, prop_name, v)
|
||||
|
||||
def test_access_bool(self):
|
||||
self.do_test_access("test_bool", bool, bool(self.default_value))
|
||||
|
||||
def test_access_int(self):
|
||||
self.do_test_access("test_int", int, int(self.default_value))
|
||||
|
||||
def test_access_float(self):
|
||||
self.do_test_access("test_float", float, float(self.default_value))
|
||||
|
||||
def test_access_bool_getset(self):
|
||||
self.do_test_access("test_bool_getset", bool, bool(self.custom_value))
|
||||
|
||||
def test_access_int_getset(self):
|
||||
self.do_test_access("test_int_getset", int, int(self.custom_value))
|
||||
|
||||
def test_access_float_getset(self):
|
||||
self.do_test_access("test_float_getset", float, float(self.custom_value))
|
||||
|
||||
# TODO: Add expected failure cases (e.g. handling of out-of range values).
|
||||
|
||||
|
||||
class TestPropString(unittest.TestCase):
|
||||
default_value = ""
|
||||
custom_value = "Blender"
|
||||
|
||||
def setUp(self):
|
||||
id_type.test_string = StringProperty(default=self.default_value)
|
||||
|
||||
self.test_string_storage = self.custom_value
|
||||
|
||||
def set_(s, v):
|
||||
self.test_string_storage = v
|
||||
id_type.test_string_getset = StringProperty(
|
||||
default=self.default_value,
|
||||
get=lambda s: self.test_string_storage,
|
||||
set=set_,
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
del id_type.test_string
|
||||
del id_type.test_string_getset
|
||||
|
||||
def do_test_access(self, prop_name, py_type, expected_value):
|
||||
v = getattr(id_inst, prop_name)
|
||||
self.assertIsInstance(v, py_type)
|
||||
self.assertEqual(v, expected_value)
|
||||
setattr(id_inst, prop_name, v)
|
||||
|
||||
def test_access_string(self):
|
||||
self.do_test_access("test_string", str, self.default_value)
|
||||
|
||||
def test_access_string_getset(self):
|
||||
self.do_test_access("test_string_getset", str, self.custom_value)
|
||||
|
||||
# TODO: Add expected failure cases (e.g. handling of too long values, invalid utf8 sequences, etc.).
|
||||
|
||||
|
||||
class TestPropEnum(unittest.TestCase):
|
||||
# FIXME: Auto-generated enum values do not play well with partially specifying some values.
|
||||
# This won't work, generating (1, 1, 2, 16):
|
||||
# enum_items = (("1", "1", "", 1), ("2", "2", ""), ("3", "3", ""), ("16", "16", "", 16),)
|
||||
enum_items = (("1", "1", ""), ("2", "2", ""), ("3", "3", ""), ("16", "16", "", 16),)
|
||||
enum_expected_values = {"1": 0, "2": 1, "3": 2, "16": 16}
|
||||
enum_expected_bitflag_values = {"1": 2**0, "2": 2**1, "3": 2**2, "16": 2**4}
|
||||
|
||||
default_value = "1"
|
||||
custom_value = "2"
|
||||
default_bitflag_value = {"1", "3"}
|
||||
custom_bitflag_value = {"16", "3"}
|
||||
|
||||
def setUp(self):
|
||||
id_type.test_enum = EnumProperty(items=self.enum_items, default=self.default_value)
|
||||
id_type.test_enum_bitflag = EnumProperty(
|
||||
items=self.enum_items,
|
||||
default=self.default_bitflag_value,
|
||||
options={"ENUM_FLAG"},
|
||||
)
|
||||
|
||||
self.test_enum_storage = self.enum_expected_values[self.custom_value]
|
||||
|
||||
def set_(s, v):
|
||||
self.test_enum_storage = v
|
||||
id_type.test_enum_getset = EnumProperty(
|
||||
items=self.enum_items,
|
||||
default=self.default_value,
|
||||
get=lambda s: self.test_enum_storage,
|
||||
set=set_,
|
||||
)
|
||||
|
||||
self.test_enum_bitflag_storage = functools.reduce(
|
||||
lambda a, b: a | b,
|
||||
(self.enum_expected_bitflag_values[bf] for bf in self.custom_bitflag_value))
|
||||
|
||||
def set_(s, v):
|
||||
self.test_enum_bitflag_storage = v
|
||||
id_type.test_enum_bitflag_getset = EnumProperty(
|
||||
items=self.enum_items,
|
||||
default=self.default_bitflag_value,
|
||||
options={"ENUM_FLAG"},
|
||||
get=lambda s: self.test_enum_bitflag_storage,
|
||||
set=set_,
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
del id_type.test_enum
|
||||
del id_type.test_enum_bitflag
|
||||
del id_type.test_enum_getset
|
||||
del id_type.test_enum_bitflag_getset
|
||||
|
||||
# Test expected generated values for enum items.
|
||||
def do_test_enum_values(self, prop_name, expected_item_values):
|
||||
enum_items = id_inst.bl_rna.properties[prop_name].enum_items
|
||||
self.assertEqual(len(expected_item_values), len(enum_items))
|
||||
for (expected_identifier, expected_value), item in zip(expected_item_values.items(), enum_items):
|
||||
self.assertEqual(expected_identifier, item.identifier)
|
||||
self.assertEqual(expected_value, item.value)
|
||||
|
||||
def test_enum_item_values(self):
|
||||
self.do_test_enum_values("test_enum", self.enum_expected_values)
|
||||
|
||||
def test_enum_bitflag_item_values(self):
|
||||
self.do_test_enum_values("test_enum_bitflag", self.enum_expected_bitflag_values)
|
||||
|
||||
# Test basic access to enum values.
|
||||
def do_test_access(self, prop_name, py_type, expected_value):
|
||||
v = getattr(id_inst, prop_name)
|
||||
self.assertIsInstance(v, py_type)
|
||||
self.assertEqual(v, expected_value)
|
||||
setattr(id_inst, prop_name, v)
|
||||
|
||||
def test_access_enum(self):
|
||||
self.do_test_access("test_enum", str, self.default_value)
|
||||
|
||||
def test_access_enum_bitflag(self):
|
||||
self.do_test_access("test_enum_bitflag", set, self.default_bitflag_value)
|
||||
|
||||
def test_access_enum_getset(self):
|
||||
self.do_test_access("test_enum_getset", str, self.custom_value)
|
||||
|
||||
def test_access_enum_bitflag_getset(self):
|
||||
self.do_test_access("test_enum_bitflag_getset", set, self.custom_bitflag_value)
|
||||
|
||||
# TODO: Add expected failure cases (e.g. handling of invalid items identifiers).
|
||||
|
||||
|
||||
class TestPropCollectionAndPointer(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
bpy.utils.register_class(TestPropertyGroup)
|
||||
|
||||
id_type.test_pointer = PointerProperty(type=TestPropertyGroup)
|
||||
id_type.test_pointer_ID = PointerProperty(type=bpy.types.ID)
|
||||
id_type.test_pointer_ID_poll = PointerProperty(type=bpy.types.ID, poll=lambda s, v: v.id_type == 'OBJECT')
|
||||
id_type.test_collection = CollectionProperty(type=TestPropertyGroup)
|
||||
|
||||
def tearDown(self):
|
||||
del id_type.test_pointer
|
||||
del id_type.test_pointer_ID
|
||||
del id_type.test_pointer_ID_poll
|
||||
del id_type.test_collection
|
||||
|
||||
bpy.utils.unregister_class(TestPropertyGroup)
|
||||
|
||||
def test_access_pointer(self):
|
||||
v = id_inst.test_pointer
|
||||
self.assertIsInstance(v, TestPropertyGroup)
|
||||
self.assertTrue(hasattr(v, "test_prop"))
|
||||
self.assertEqual(v.test_prop, 0)
|
||||
v.test_prop = 42
|
||||
self.assertEqual(id_inst.test_pointer.test_prop, 42)
|
||||
|
||||
def test_access_pointer_ID(self):
|
||||
self.assertEqual(id_inst.test_pointer_ID, None)
|
||||
|
||||
# Non-refcounting ID type
|
||||
win_man = bpy.data.window_managers[0]
|
||||
win_man_users = win_man.users
|
||||
id_inst.test_pointer_ID = win_man
|
||||
self.assertEqual(id_inst.test_pointer_ID, win_man)
|
||||
self.assertEqual(win_man.users, win_man_users)
|
||||
id_inst.test_pointer_ID = None
|
||||
self.assertEqual(id_inst.test_pointer_ID, None)
|
||||
self.assertEqual(win_man.users, win_man_users)
|
||||
|
||||
# Refcounting ID type
|
||||
ma = bpy.data.materials[0]
|
||||
ma_users = ma.users
|
||||
id_inst.test_pointer_ID = ma
|
||||
self.assertEqual(id_inst.test_pointer_ID, ma)
|
||||
self.assertEqual(win_man.users, ma_users + 1)
|
||||
id_inst.test_pointer_ID = None
|
||||
self.assertEqual(id_inst.test_pointer_ID, None)
|
||||
self.assertEqual(ma.users, ma_users)
|
||||
|
||||
def test_access_pointer_ID_poll(self):
|
||||
# Poll callback is only used for UI, in scripts it's still possible to assign an 'invalid' ID.
|
||||
self.assertEqual(id_inst.test_pointer_ID_poll, None)
|
||||
win_man = bpy.data.window_managers[0]
|
||||
id_inst.test_pointer_ID_poll = win_man
|
||||
self.assertEqual(id_inst.test_pointer_ID_poll, win_man)
|
||||
id_inst.test_pointer_ID_poll = None
|
||||
self.assertEqual(id_inst.test_pointer_ID_poll, None)
|
||||
|
||||
def test_access_collection(self):
|
||||
self.assertEqual(len(id_inst.test_collection), 0)
|
||||
|
||||
test_item = id_inst.test_collection.add()
|
||||
self.assertEqual(len(id_inst.test_collection), 1)
|
||||
self.assertIsInstance(test_item, TestPropertyGroup)
|
||||
self.assertTrue(hasattr(test_item, "test_prop"))
|
||||
self.assertEqual(test_item.test_prop, 0)
|
||||
self.assertEqual(id_inst.test_collection[0], test_item)
|
||||
test_item.test_prop = 42
|
||||
self.assertEqual(test_item.test_prop, 42)
|
||||
self.assertEqual(id_inst.test_collection[0].test_prop, 42)
|
||||
|
||||
test_item_2 = id_inst.test_collection.add()
|
||||
test_item_3 = id_inst.test_collection.add()
|
||||
test_item_3.test_prop = 24
|
||||
self.assertEqual(len(id_inst.test_collection), 3)
|
||||
self.assertEqual(id_inst.test_collection[0], test_item)
|
||||
self.assertEqual(id_inst.test_collection[1], test_item_2)
|
||||
self.assertEqual(id_inst.test_collection[2], test_item_3)
|
||||
|
||||
id_inst.test_collection.remove(1)
|
||||
self.assertEqual(len(id_inst.test_collection), 2)
|
||||
self.assertEqual(id_inst.test_collection[0], test_item)
|
||||
# Removing the second item re-allocates the third one, so no equality anymore.
|
||||
self.assertNotEqual(id_inst.test_collection[1], test_item_3)
|
||||
self.assertEqual(id_inst.test_collection[1].test_prop, 24)
|
||||
|
||||
# TODO: Add expected failure cases (e.g. assigning propertygroup to a Pointer property, etc.).
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
sys.argv = [__file__] + (sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [])
|
||||
unittest.main()
|
||||
@@ -2,6 +2,10 @@
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# NOTE: See also `bl_pyapi_prop.py` for the non-`Vector` bpy.props similar tests,
|
||||
# and `bl_pyapi_idprop.py` for some deeper testing of the consistency between
|
||||
# the underlying IDProperty storage, and the property data exposed in Python.
|
||||
|
||||
# ./blender.bin --background --python tests/python/bl_pyapi_prop_array.py -- --verbose
|
||||
import bpy
|
||||
from bpy.props import (
|
||||
|
||||
Reference in New Issue
Block a user