Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'tests/python')
-rw-r--r--tests/python/CMakeLists.txt11
-rw-r--r--tests/python/bl_blendfile_io.py4
-rw-r--r--tests/python/bl_blendfile_liblink.py264
-rw-r--r--tests/python/bl_blendfile_library_overrides.py198
-rw-r--r--tests/python/bl_keymap_validate.py2
-rw-r--r--tests/python/bl_load_addons.py6
-rw-r--r--tests/python/bl_load_py_modules.py2
-rw-r--r--tests/python/bl_mesh_modifiers.py6
-rw-r--r--tests/python/bl_pyapi_bpy_driver_secure_eval.py220
-rw-r--r--tests/python/bl_pyapi_idprop.py2
-rw-r--r--tests/python/bl_rigging_symmetrize.py26
-rw-r--r--tests/python/bl_rna_manual_reference.py8
-rw-r--r--tests/python/bl_run_operators.py1
-rw-r--r--tests/python/bl_run_operators_event_simulate.py2
-rw-r--r--tests/python/eevee_render_tests.py33
-rw-r--r--tests/python/gpu_info.py26
-rwxr-xr-xtests/python/modules/render_report.py19
-rw-r--r--tests/python/operators.py36
-rw-r--r--tests/python/workbench_render_tests.py7
19 files changed, 646 insertions, 227 deletions
diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt
index 38c3fc4389a..ca3070b60ad 100644
--- a/tests/python/CMakeLists.txt
+++ b/tests/python/CMakeLists.txt
@@ -102,6 +102,11 @@ add_blender_test(
)
add_blender_test(
+ script_pyapi_bpy_driver_secure_eval
+ --python ${CMAKE_CURRENT_LIST_DIR}/bl_pyapi_bpy_driver_secure_eval.py
+)
+
+add_blender_test(
script_pyapi_idprop
--python ${CMAKE_CURRENT_LIST_DIR}/bl_pyapi_idprop.py
)
@@ -630,8 +635,8 @@ if(WITH_CYCLES OR WITH_OPENGL_RENDER_TESTS)
MESSAGE(WARNING "Disabling render tests because OIIO idiff does not exist")
elseif(NOT EXISTS "${TEST_SRC_DIR}/render/shader")
MESSAGE(WARNING "Disabling render tests because tests folder does not exist at ${TEST_SRC_DIR}")
- elseif(NOT WITH_COMPOSITOR)
- MESSAGE(WARNING "Disabling render tests because WITH_COMPOSITOR is disabled")
+ elseif(NOT WITH_COMPOSITOR_CPU)
+ MESSAGE(WARNING "Disabling render tests because WITH_COMPOSITOR_CPU is disabled")
elseif(NOT WITH_OPENCOLORIO)
MESSAGE(WARNING "Disabling render tests because WITH_OPENCOLORIO is disabled")
else()
@@ -730,7 +735,7 @@ if(WITH_CYCLES OR WITH_OPENGL_RENDER_TESTS)
endif()
endif()
-if(WITH_COMPOSITOR)
+if(WITH_COMPOSITOR_CPU)
set(compositor_tests
color
converter
diff --git a/tests/python/bl_blendfile_io.py b/tests/python/bl_blendfile_io.py
index f79ee2a240b..fa63b789751 100644
--- a/tests/python/bl_blendfile_io.py
+++ b/tests/python/bl_blendfile_io.py
@@ -33,7 +33,7 @@ class TestBlendFileSaveLoadBasic(TestHelper):
read_data = self.blender_data_to_tuple(bpy.data, "read_data 1")
# We have orphaned data, which should be removed by file reading, so there should not be equality here.
- assert(orig_data != read_data)
+ assert orig_data != read_data
bpy.data.orphans_purge()
@@ -44,7 +44,7 @@ class TestBlendFileSaveLoadBasic(TestHelper):
read_data = self.blender_data_to_tuple(bpy.data, "read_data 2")
- assert(orig_data == read_data)
+ assert orig_data == read_data
TESTS = (
diff --git a/tests/python/bl_blendfile_liblink.py b/tests/python/bl_blendfile_liblink.py
index 120afba4911..a4ca845da4e 100644
--- a/tests/python/bl_blendfile_liblink.py
+++ b/tests/python/bl_blendfile_liblink.py
@@ -93,9 +93,9 @@ class TestBlendLibLinkSaveLoadBasic(TestBlendLibLinkHelper):
link_dir = os.path.join(output_lib_path, "Mesh")
bpy.ops.wm.link(directory=link_dir, filename="LibMesh", instance_object_data=False)
- assert(len(bpy.data.meshes) == 1)
- assert(len(bpy.data.objects) == 0)
- assert(len(bpy.data.collections) == 0) # Scene's master collection is not listed here
+ assert len(bpy.data.meshes) == 1
+ assert len(bpy.data.objects) == 0
+ assert len(bpy.data.collections) == 0 # Scene's master collection is not listed here
orig_data = self.blender_data_to_tuple(bpy.data, "orig_data")
@@ -106,8 +106,8 @@ class TestBlendLibLinkSaveLoadBasic(TestBlendLibLinkHelper):
read_data = self.blender_data_to_tuple(bpy.data, "read_data")
# Since there is no usage of linked mesh, it is lost during save/reload.
- assert(len(bpy.data.meshes) == 0)
- assert(orig_data != read_data)
+ assert len(bpy.data.meshes) == 0
+ assert orig_data != read_data
# Simple link of a single ObData with obdata instantiation.
self.reset_blender()
@@ -115,9 +115,9 @@ class TestBlendLibLinkSaveLoadBasic(TestBlendLibLinkHelper):
link_dir = os.path.join(output_lib_path, "Mesh")
bpy.ops.wm.link(directory=link_dir, filename="LibMesh", instance_object_data=True)
- assert(len(bpy.data.meshes) == 1)
- assert(len(bpy.data.objects) == 1) # Instance created for the mesh ObData.
- assert(len(bpy.data.collections) == 0) # Scene's master collection is not listed here
+ assert len(bpy.data.meshes) == 1
+ assert len(bpy.data.objects) == 1 # Instance created for the mesh ObData.
+ assert len(bpy.data.collections) == 0 # Scene's master collection is not listed here
orig_data = self.blender_data_to_tuple(bpy.data, "orig_data")
@@ -126,7 +126,7 @@ class TestBlendLibLinkSaveLoadBasic(TestBlendLibLinkHelper):
read_data = self.blender_data_to_tuple(bpy.data, "read_data")
- assert(orig_data == read_data)
+ assert orig_data == read_data
# Simple link of a single Object.
self.reset_blender()
@@ -134,9 +134,9 @@ class TestBlendLibLinkSaveLoadBasic(TestBlendLibLinkHelper):
link_dir = os.path.join(output_lib_path, "Object")
bpy.ops.wm.link(directory=link_dir, filename="LibMesh")
- assert(len(bpy.data.meshes) == 1)
- assert(len(bpy.data.objects) == 1)
- assert(len(bpy.data.collections) == 0) # Scene's master collection is not listed here
+ assert len(bpy.data.meshes) == 1
+ assert len(bpy.data.objects) == 1
+ assert len(bpy.data.collections) == 0 # Scene's master collection is not listed here
orig_data = self.blender_data_to_tuple(bpy.data, "orig_data")
@@ -145,7 +145,7 @@ class TestBlendLibLinkSaveLoadBasic(TestBlendLibLinkHelper):
read_data = self.blender_data_to_tuple(bpy.data, "read_data")
- assert(orig_data == read_data)
+ assert orig_data == read_data
# Simple link of a single Collection, with Empty-instantiation.
self.reset_blender()
@@ -153,9 +153,9 @@ class TestBlendLibLinkSaveLoadBasic(TestBlendLibLinkHelper):
link_dir = os.path.join(output_lib_path, "Collection")
bpy.ops.wm.link(directory=link_dir, filename="LibMesh", instance_collections=True)
- assert(len(bpy.data.meshes) == 1)
- assert(len(bpy.data.objects) == 2) # linked object and local empty instancing the collection
- assert(len(bpy.data.collections) == 1) # Scene's master collection is not listed here
+ assert len(bpy.data.meshes) == 1
+ assert len(bpy.data.objects) == 2 # linked object and local empty instancing the collection
+ assert len(bpy.data.collections) == 1 # Scene's master collection is not listed here
orig_data = self.blender_data_to_tuple(bpy.data, "orig_data")
@@ -164,7 +164,7 @@ class TestBlendLibLinkSaveLoadBasic(TestBlendLibLinkHelper):
read_data = self.blender_data_to_tuple(bpy.data, "read_data")
- assert(orig_data == read_data)
+ assert orig_data == read_data
# Simple link of a single Collection, with ViewLayer-instantiation.
self.reset_blender()
@@ -172,11 +172,11 @@ class TestBlendLibLinkSaveLoadBasic(TestBlendLibLinkHelper):
link_dir = os.path.join(output_lib_path, "Collection")
bpy.ops.wm.link(directory=link_dir, filename="LibMesh", instance_collections=False)
- assert(len(bpy.data.meshes) == 1)
- assert(len(bpy.data.objects) == 1)
- assert(len(bpy.data.collections) == 1) # Scene's master collection is not listed here
+ assert len(bpy.data.meshes) == 1
+ assert len(bpy.data.objects) == 1
+ assert len(bpy.data.collections) == 1 # Scene's master collection is not listed here
# Linked collection should have been added to the scene's master collection children.
- assert(bpy.data.collections[0] in set(bpy.data.scenes[0].collection.children))
+ assert bpy.data.collections[0] in set(bpy.data.scenes[0].collection.children)
orig_data = self.blender_data_to_tuple(bpy.data, "orig_data")
@@ -185,7 +185,7 @@ class TestBlendLibLinkSaveLoadBasic(TestBlendLibLinkHelper):
read_data = self.blender_data_to_tuple(bpy.data, "read_data")
- assert(orig_data == read_data)
+ assert orig_data == read_data
class TestBlendLibAppendBasic(TestBlendLibLinkHelper):
@@ -211,15 +211,15 @@ class TestBlendLibAppendBasic(TestBlendLibLinkHelper):
bpy.data.materials[0].use_fake_user,
)
- assert(len(bpy.data.materials) == 1)
- assert(bpy.data.materials[0].library is not None)
- assert(bpy.data.materials[0].users == 2) # Fake user is not cleared when linking.
- assert(len(bpy.data.meshes) == 1)
- assert(bpy.data.meshes[0].library is None)
- assert(bpy.data.meshes[0].use_fake_user is False)
- assert(bpy.data.meshes[0].users == 0)
- assert(len(bpy.data.objects) == 0)
- assert(len(bpy.data.collections) == 0) # Scene's master collection is not listed here
+ assert len(bpy.data.materials) == 1
+ assert bpy.data.materials[0].library is not None
+ assert bpy.data.materials[0].users == 2 # Fake user is not cleared when linking.
+ assert len(bpy.data.meshes) == 1
+ assert bpy.data.meshes[0].library is None
+ assert bpy.data.meshes[0].use_fake_user is False
+ assert bpy.data.meshes[0].users == 0
+ assert len(bpy.data.objects) == 0
+ assert len(bpy.data.collections) == 0 # Scene's master collection is not listed here
# Simple append of a single ObData with obdata instantiation.
self.reset_blender()
@@ -228,16 +228,16 @@ class TestBlendLibAppendBasic(TestBlendLibLinkHelper):
bpy.ops.wm.append(directory=link_dir, filename="LibMesh",
instance_object_data=True, set_fake=False, use_recursive=False, do_reuse_local_id=False)
- assert(len(bpy.data.materials) == 1)
- assert(bpy.data.materials[0].library is not None)
- assert(bpy.data.materials[0].users == 2) # Fake user is not cleared when linking.
- assert(len(bpy.data.meshes) == 1)
- assert(bpy.data.meshes[0].library is None)
- assert(bpy.data.meshes[0].use_fake_user is False)
- assert(bpy.data.meshes[0].users == 1)
- assert(len(bpy.data.objects) == 1) # Instance created for the mesh ObData.
- assert(bpy.data.objects[0].library is None)
- assert(len(bpy.data.collections) == 0) # Scene's master collection is not listed here
+ assert len(bpy.data.materials) == 1
+ assert bpy.data.materials[0].library is not None
+ assert bpy.data.materials[0].users == 2 # Fake user is not cleared when linking.
+ assert len(bpy.data.meshes) == 1
+ assert bpy.data.meshes[0].library is None
+ assert bpy.data.meshes[0].use_fake_user is False
+ assert bpy.data.meshes[0].users == 1
+ assert len(bpy.data.objects) == 1 # Instance created for the mesh ObData.
+ assert bpy.data.objects[0].library is None
+ assert len(bpy.data.collections) == 0 # Scene's master collection is not listed here
# Simple append of a single ObData with fake user.
self.reset_blender()
@@ -246,15 +246,15 @@ class TestBlendLibAppendBasic(TestBlendLibLinkHelper):
bpy.ops.wm.append(directory=link_dir, filename="LibMesh",
instance_object_data=False, set_fake=True, use_recursive=False, do_reuse_local_id=False)
- assert(len(bpy.data.materials) == 1)
- assert(bpy.data.materials[0].library is not None)
- assert(bpy.data.materials[0].users == 2) # Fake user is not cleared when linking.
- assert(len(bpy.data.meshes) == 1)
- assert(bpy.data.meshes[0].library is None)
- assert(bpy.data.meshes[0].use_fake_user is True)
- assert(bpy.data.meshes[0].users == 1)
- assert(len(bpy.data.objects) == 0)
- assert(len(bpy.data.collections) == 0) # Scene's master collection is not listed here
+ assert len(bpy.data.materials) == 1
+ assert bpy.data.materials[0].library is not None
+ assert bpy.data.materials[0].users == 2 # Fake user is not cleared when linking.
+ assert len(bpy.data.meshes) == 1
+ assert bpy.data.meshes[0].library is None
+ assert bpy.data.meshes[0].use_fake_user is True
+ assert bpy.data.meshes[0].users == 1
+ assert len(bpy.data.objects) == 0
+ assert len(bpy.data.collections) == 0 # Scene's master collection is not listed here
# Simple append of a single Object.
self.reset_blender()
@@ -263,16 +263,16 @@ class TestBlendLibAppendBasic(TestBlendLibLinkHelper):
bpy.ops.wm.append(directory=link_dir, filename="LibMesh",
instance_object_data=False, set_fake=False, use_recursive=False, do_reuse_local_id=False)
- assert(len(bpy.data.materials) == 1)
- assert(bpy.data.materials[0].library is not None)
- assert(bpy.data.materials[0].users == 2) # Fake user is not cleared when linking.
- assert(len(bpy.data.meshes) == 1)
- assert(bpy.data.meshes[0].library is None)
- assert(bpy.data.meshes[0].users == 1)
- assert(len(bpy.data.objects) == 1)
- assert(bpy.data.objects[0].library is None)
- assert(bpy.data.objects[0].users == 1)
- assert(len(bpy.data.collections) == 0) # Scene's master collection is not listed here
+ assert len(bpy.data.materials) == 1
+ assert bpy.data.materials[0].library is not None
+ assert bpy.data.materials[0].users == 2 # Fake user is not cleared when linking.
+ assert len(bpy.data.meshes) == 1
+ assert bpy.data.meshes[0].library is None
+ assert bpy.data.meshes[0].users == 1
+ assert len(bpy.data.objects) == 1
+ assert bpy.data.objects[0].library is None
+ assert bpy.data.objects[0].users == 1
+ assert len(bpy.data.collections) == 0 # Scene's master collection is not listed here
# Simple recursive append of a single Object.
self.reset_blender()
@@ -281,16 +281,16 @@ class TestBlendLibAppendBasic(TestBlendLibLinkHelper):
bpy.ops.wm.append(directory=link_dir, filename="LibMesh",
instance_object_data=False, set_fake=False, use_recursive=True, do_reuse_local_id=False)
- assert(len(bpy.data.materials) == 1)
- assert(bpy.data.materials[0].library is None)
- assert(bpy.data.materials[0].users == 1) # Fake user is cleared when appending.
- assert(len(bpy.data.meshes) == 1)
- assert(bpy.data.meshes[0].library is None)
- assert(bpy.data.meshes[0].users == 1)
- assert(len(bpy.data.objects) == 1)
- assert(bpy.data.objects[0].library is None)
- assert(bpy.data.objects[0].users == 1)
- assert(len(bpy.data.collections) == 0) # Scene's master collection is not listed here
+ assert len(bpy.data.materials) == 1
+ assert bpy.data.materials[0].library is None
+ assert bpy.data.materials[0].users == 1 # Fake user is cleared when appending.
+ assert len(bpy.data.meshes) == 1
+ assert bpy.data.meshes[0].library is None
+ assert bpy.data.meshes[0].users == 1
+ assert len(bpy.data.objects) == 1
+ assert bpy.data.objects[0].library is None
+ assert bpy.data.objects[0].users == 1
+ assert len(bpy.data.collections) == 0 # Scene's master collection is not listed here
# Simple recursive append of a single Collection.
self.reset_blender()
@@ -299,17 +299,17 @@ class TestBlendLibAppendBasic(TestBlendLibLinkHelper):
bpy.ops.wm.append(directory=link_dir, filename="LibMesh",
instance_object_data=False, set_fake=False, use_recursive=True, do_reuse_local_id=False)
- assert(len(bpy.data.materials) == 1)
- assert(bpy.data.materials[0].library is None)
- assert(bpy.data.materials[0].users == 1) # Fake user is cleared when appending.
- assert(bpy.data.meshes[0].library is None)
- assert(bpy.data.meshes[0].users == 1)
- assert(len(bpy.data.objects) == 1)
- assert(bpy.data.objects[0].library is None)
- assert(bpy.data.objects[0].users == 1)
- assert(len(bpy.data.collections) == 1) # Scene's master collection is not listed here
- assert(bpy.data.collections[0].library is None)
- assert(bpy.data.collections[0].users == 1)
+ assert len(bpy.data.materials) == 1
+ assert bpy.data.materials[0].library is None
+ assert bpy.data.materials[0].users == 1 # Fake user is cleared when appending.
+ assert bpy.data.meshes[0].library is None
+ assert bpy.data.meshes[0].users == 1
+ assert len(bpy.data.objects) == 1
+ assert bpy.data.objects[0].library is None
+ assert bpy.data.objects[0].users == 1
+ assert len(bpy.data.collections) == 1 # Scene's master collection is not listed here
+ assert bpy.data.collections[0].library is None
+ assert bpy.data.collections[0].users == 1
class TestBlendLibAppendReuseID(TestBlendLibLinkHelper):
@@ -328,51 +328,51 @@ class TestBlendLibAppendReuseID(TestBlendLibLinkHelper):
bpy.ops.wm.append(directory=link_dir, filename="LibMesh",
instance_object_data=False, set_fake=False, use_recursive=True, do_reuse_local_id=False)
- assert(len(bpy.data.meshes) == 1)
- assert(bpy.data.meshes[0].library is None)
- assert(bpy.data.meshes[0].use_fake_user is False)
- assert(bpy.data.meshes[0].users == 1)
- assert(bpy.data.meshes[0].library_weak_reference is not None)
- assert(bpy.data.meshes[0].library_weak_reference.filepath == output_lib_path)
- assert(bpy.data.meshes[0].library_weak_reference.id_name == "MELibMesh")
- assert(len(bpy.data.objects) == 1)
+ assert len(bpy.data.meshes) == 1
+ assert bpy.data.meshes[0].library is None
+ assert bpy.data.meshes[0].use_fake_user is False
+ assert bpy.data.meshes[0].users == 1
+ assert bpy.data.meshes[0].library_weak_reference is not None
+ assert bpy.data.meshes[0].library_weak_reference.filepath == output_lib_path
+ assert bpy.data.meshes[0].library_weak_reference.id_name == "MELibMesh"
+ assert len(bpy.data.objects) == 1
for ob in bpy.data.objects:
- assert(ob.library is None)
- assert(ob.library_weak_reference is None)
- assert(len(bpy.data.collections) == 0) # Scene's master collection is not listed here
+ assert ob.library is None
+ assert ob.library_weak_reference is None
+ assert len(bpy.data.collections) == 0 # Scene's master collection is not listed here
bpy.ops.wm.append(directory=link_dir, filename="LibMesh",
instance_object_data=False, set_fake=False, use_recursive=True, do_reuse_local_id=True)
- assert(len(bpy.data.meshes) == 1)
- assert(bpy.data.meshes[0].library is None)
- assert(bpy.data.meshes[0].use_fake_user is False)
- assert(bpy.data.meshes[0].users == 2)
- assert(bpy.data.meshes[0].library_weak_reference is not None)
- assert(bpy.data.meshes[0].library_weak_reference.filepath == output_lib_path)
- assert(bpy.data.meshes[0].library_weak_reference.id_name == "MELibMesh")
- assert(len(bpy.data.objects) == 2)
+ assert len(bpy.data.meshes) == 1
+ assert bpy.data.meshes[0].library is None
+ assert bpy.data.meshes[0].use_fake_user is False
+ assert bpy.data.meshes[0].users == 2
+ assert bpy.data.meshes[0].library_weak_reference is not None
+ assert bpy.data.meshes[0].library_weak_reference.filepath == output_lib_path
+ assert bpy.data.meshes[0].library_weak_reference.id_name == "MELibMesh"
+ assert len(bpy.data.objects) == 2
for ob in bpy.data.objects:
- assert(ob.library is None)
- assert(ob.library_weak_reference is None)
- assert(len(bpy.data.collections) == 0) # Scene's master collection is not listed here
+ assert ob.library is None
+ assert ob.library_weak_reference is None
+ assert len(bpy.data.collections) == 0 # Scene's master collection is not listed here
bpy.ops.wm.append(directory=link_dir, filename="LibMesh",
instance_object_data=False, set_fake=False, use_recursive=True, do_reuse_local_id=False)
- assert(len(bpy.data.meshes) == 2)
- assert(bpy.data.meshes[0].library_weak_reference is None)
- assert(bpy.data.meshes[1].library is None)
- assert(bpy.data.meshes[1].use_fake_user is False)
- assert(bpy.data.meshes[1].users == 1)
- assert(bpy.data.meshes[1].library_weak_reference is not None)
- assert(bpy.data.meshes[1].library_weak_reference.filepath == output_lib_path)
- assert(bpy.data.meshes[1].library_weak_reference.id_name == "MELibMesh")
- assert(len(bpy.data.objects) == 3)
+ assert len(bpy.data.meshes) == 2
+ assert bpy.data.meshes[0].library_weak_reference is None
+ assert bpy.data.meshes[1].library is None
+ assert bpy.data.meshes[1].use_fake_user is False
+ assert bpy.data.meshes[1].users == 1
+ assert bpy.data.meshes[1].library_weak_reference is not None
+ assert bpy.data.meshes[1].library_weak_reference.filepath == output_lib_path
+ assert bpy.data.meshes[1].library_weak_reference.id_name == "MELibMesh"
+ assert len(bpy.data.objects) == 3
for ob in bpy.data.objects:
- assert(ob.library is None)
- assert(ob.library_weak_reference is None)
- assert(len(bpy.data.collections) == 0) # Scene's master collection is not listed here
+ assert ob.library is None
+ assert ob.library_weak_reference is None
+ assert len(bpy.data.collections) == 0 # Scene's master collection is not listed here
class TestBlendLibLibraryReload(TestBlendLibLinkHelper):
@@ -390,9 +390,9 @@ class TestBlendLibLibraryReload(TestBlendLibLinkHelper):
link_dir = os.path.join(output_lib_path, "Object")
bpy.ops.wm.link(directory=link_dir, filename="LibMesh")
- assert(len(bpy.data.meshes) == 1)
- assert(len(bpy.data.objects) == 1)
- assert(len(bpy.data.collections) == 0) # Scene's master collection is not listed here
+ assert len(bpy.data.meshes) == 1
+ assert len(bpy.data.objects) == 1
+ assert len(bpy.data.collections) == 0 # Scene's master collection is not listed here
orig_data = self.blender_data_to_tuple(bpy.data, "orig_data")
@@ -402,7 +402,7 @@ class TestBlendLibLibraryReload(TestBlendLibLinkHelper):
print(orig_data)
print(reload_data)
- assert(orig_data == reload_data)
+ assert orig_data == reload_data
class TestBlendLibLibraryRelocate(TestBlendLibLinkHelper):
@@ -420,9 +420,9 @@ class TestBlendLibLibraryRelocate(TestBlendLibLinkHelper):
link_dir = os.path.join(output_lib_path, "Object")
bpy.ops.wm.link(directory=link_dir, filename="LibMesh")
- assert(len(bpy.data.meshes) == 1)
- assert(len(bpy.data.objects) == 1)
- assert(len(bpy.data.collections) == 0) # Scene's master collection is not listed here
+ assert len(bpy.data.meshes) == 1
+ assert len(bpy.data.objects) == 1
+ assert len(bpy.data.collections) == 0 # Scene's master collection is not listed here
orig_data = self.blender_data_to_tuple(bpy.data, "orig_data")
@@ -436,7 +436,7 @@ class TestBlendLibLibraryRelocate(TestBlendLibLinkHelper):
print(orig_data)
print(relocate_data)
- assert(orig_data == relocate_data)
+ assert orig_data == relocate_data
class TestBlendLibDataLibrariesLoad(TestBlendLibLinkHelper):
@@ -454,21 +454,21 @@ class TestBlendLibDataLibrariesLoad(TestBlendLibLinkHelper):
with bpy.data.libraries.load(filepath=output_lib_path) as lib_ctx:
lib_src, lib_link = lib_ctx
- assert(len(lib_src.meshes) == 1)
- assert(len(lib_src.objects) == 1)
- assert(len(lib_src.collections) == 1)
+ assert len(lib_src.meshes) == 1
+ assert len(lib_src.objects) == 1
+ assert len(lib_src.collections) == 1
- assert(len(lib_link.meshes) == 0)
- assert(len(lib_link.objects) == 0)
- assert(len(lib_link.collections) == 0)
+ assert len(lib_link.meshes) == 0
+ assert len(lib_link.objects) == 0
+ assert len(lib_link.collections) == 0
lib_link.collections.append(lib_src.collections[0])
# Linking happens when living the context manager.
- assert(len(bpy.data.meshes) == 1)
- assert(len(bpy.data.objects) == 1) # This code does no instantiation.
- assert(len(bpy.data.collections) == 1)
+ assert len(bpy.data.meshes) == 1
+ assert len(bpy.data.objects) == 1 # This code does no instantiation.
+ assert len(bpy.data.collections) == 1
TESTS = (
diff --git a/tests/python/bl_blendfile_library_overrides.py b/tests/python/bl_blendfile_library_overrides.py
index 1acc1e4d862..6890bb1e660 100644
--- a/tests/python/bl_blendfile_library_overrides.py
+++ b/tests/python/bl_blendfile_library_overrides.py
@@ -57,49 +57,49 @@ class TestLibraryOverrides(TestHelper, unittest.TestCase):
local_id = obj.override_create()
self.assertIsNotNone(local_id.override_library)
self.assertIsNone(local_id.data.override_library)
- assert(len(local_id.override_library.properties) == 0)
+ assert len(local_id.override_library.properties) == 0
# #### Generate an override property & operation automatically by editing the local override data.
local_id.location.y = 1.0
local_id.override_library.operations_update()
- assert(len(local_id.override_library.properties) == 1)
+ assert len(local_id.override_library.properties) == 1
override_prop = local_id.override_library.properties[0]
- assert(override_prop.rna_path == "location")
- assert(len(override_prop.operations) == 1)
+ assert override_prop.rna_path == "location"
+ assert len(override_prop.operations) == 1
override_operation = override_prop.operations[0]
- assert(override_operation.operation == 'REPLACE')
+ assert override_operation.operation == 'REPLACE'
# Setting location.y overrode all elements in the location array. -1 is a wildcard.
- assert(override_operation.subitem_local_index == -1)
+ assert override_operation.subitem_local_index == -1
# #### Reset the override to its linked reference data.
local_id.override_library.reset()
- assert(len(local_id.override_library.properties) == 0)
- assert(local_id.location == local_id.override_library.reference.location)
+ assert len(local_id.override_library.properties) == 0
+ assert local_id.location == local_id.override_library.reference.location
# #### Generate an override property & operation manually using the API.
override_property = local_id.override_library.properties.add(rna_path="location")
override_property.operations.add(operation='REPLACE')
- assert(len(local_id.override_library.properties) == 1)
+ assert len(local_id.override_library.properties) == 1
override_prop = local_id.override_library.properties[0]
- assert(override_prop.rna_path == "location")
- assert(len(override_prop.operations) == 1)
+ assert override_prop.rna_path == "location"
+ assert len(override_prop.operations) == 1
override_operation = override_prop.operations[0]
- assert(override_operation.operation == 'REPLACE')
+ assert override_operation.operation == 'REPLACE'
# Setting location.y overrode all elements in the location array. -1 is a wildcard.
- assert(override_operation.subitem_local_index == -1)
+ assert override_operation.subitem_local_index == -1
override_property = local_id.override_library.properties[0]
override_property.operations.remove(override_property.operations[0])
local_id.override_library.properties.remove(override_property)
- assert(len(local_id.override_library.properties) == 0)
+ assert len(local_id.override_library.properties) == 0
# #### Delete the override.
local_id_name = local_id.name
- assert(bpy.data.objects.get((local_id_name, None), None) == local_id)
+ assert bpy.data.objects.get((local_id_name, None), None) == local_id
local_id.override_library.destroy()
- assert(bpy.data.objects.get((local_id_name, None), None) is None)
+ assert bpy.data.objects.get((local_id_name, None), None) is None
def test_link_permissive(self):
"""
@@ -119,39 +119,39 @@ class TestLibraryOverrides(TestHelper, unittest.TestCase):
local_id = obj.override_create()
self.assertIsNotNone(local_id.override_library)
self.assertIsNone(local_id.data.override_library)
- assert(len(local_id.override_library.properties) == 1)
+ assert len(local_id.override_library.properties) == 1
override_prop = local_id.override_library.properties[0]
- assert(override_prop.rna_path == "scale")
- assert(len(override_prop.operations) == 1)
+ assert override_prop.rna_path == "scale"
+ assert len(override_prop.operations) == 1
override_operation = override_prop.operations[0]
- assert(override_operation.operation == 'NOOP')
- assert(override_operation.subitem_local_index == -1)
+ assert override_operation.operation == 'NOOP'
+ assert override_operation.subitem_local_index == -1
local_id.location.y = 1.0
local_id.scale.x = 0.5
# `scale.x` will apply, but will be reverted when the library overrides
# are updated. This is by design so python scripts can still alter the
# properties locally what is a typical usecase in productions.
- assert(local_id.scale.x == 0.5)
- assert(local_id.location.y == 1.0)
+ assert local_id.scale.x == 0.5
+ assert local_id.location.y == 1.0
local_id.override_library.operations_update()
- assert(local_id.scale.x == 1.0)
- assert(local_id.location.y == 1.0)
+ assert local_id.scale.x == 1.0
+ assert local_id.location.y == 1.0
- assert(len(local_id.override_library.properties) == 2)
+ assert len(local_id.override_library.properties) == 2
override_prop = local_id.override_library.properties[0]
- assert(override_prop.rna_path == "scale")
- assert(len(override_prop.operations) == 1)
+ assert override_prop.rna_path == "scale"
+ assert len(override_prop.operations) == 1
override_operation = override_prop.operations[0]
- assert(override_operation.operation == 'NOOP')
- assert(override_operation.subitem_local_index == -1)
+ assert override_operation.operation == 'NOOP'
+ assert override_operation.subitem_local_index == -1
override_prop = local_id.override_library.properties[1]
- assert(override_prop.rna_path == "location")
- assert(len(override_prop.operations) == 1)
+ assert override_prop.rna_path == "location"
+ assert len(override_prop.operations) == 1
override_operation = override_prop.operations[0]
- assert(override_operation.operation == 'REPLACE')
- assert (override_operation.subitem_local_index == -1)
+ assert override_operation.operation == 'REPLACE'
+ assert override_operation.subitem_local_index == -1
class TestLibraryTemplate(TestHelper, unittest.TestCase):
@@ -169,21 +169,137 @@ class TestLibraryTemplate(TestHelper, unittest.TestCase):
mesh = bpy.data.meshes.new(TestLibraryTemplate.MESH_LIBRARY_PERMISSIVE)
obj = bpy.data.objects.new(TestLibraryTemplate.OBJECT_LIBRARY_PERMISSIVE, object_data=mesh)
bpy.context.collection.objects.link(obj)
- assert(obj.override_library is None)
+ assert obj.override_library is None
obj.override_template_create()
- assert(obj.override_library is not None)
- assert(len(obj.override_library.properties) == 0)
+ assert obj.override_library is not None
+ assert len(obj.override_library.properties) == 0
prop = obj.override_library.properties.add(rna_path='scale')
- assert(len(obj.override_library.properties) == 1)
- assert(len(prop.operations) == 0)
+ assert len(obj.override_library.properties) == 1
+ assert len(prop.operations) == 0
operation = prop.operations.add(operation='NOOP')
- assert(len(prop.operations) == 1)
- assert(operation.operation == 'NOOP')
+ assert len(prop.operations) == 1
+ assert operation.operation == 'NOOP'
+
+
+class TestLibraryOverridesResync(TestHelper, unittest.TestCase):
+ DATA_NAME_CONTAINER = "LibCollection"
+ DATA_NAME_RIGGED = "LibRigged"
+ DATA_NAME_RIG = "LibRig"
+ DATA_NAME_CONTROLLER_1 = "LibController1"
+ DATA_NAME_CONTROLLER_2 = "LibController2"
+
+ def __init__(self, args):
+ self.args = args
+
+ output_dir = pathlib.Path(self.args.output_dir)
+ self.ensure_path(str(output_dir))
+ self.output_path = output_dir / "blendlib_overrides.blend"
+ self.test_output_path = output_dir / "blendlib_overrides_test.blend"
+
+ bpy.ops.wm.read_homefile(use_empty=True, use_factory_startup=True)
+
+ collection_container = bpy.data.collections.new(TestLibraryOverridesResync.DATA_NAME_CONTAINER)
+ bpy.context.collection.children.link(collection_container)
+
+ mesh = bpy.data.meshes.new(TestLibraryOverridesResync.DATA_NAME_RIGGED)
+ obj_child = bpy.data.objects.new(TestLibraryOverridesResync.DATA_NAME_RIGGED, object_data=mesh)
+ collection_container.objects.link(obj_child)
+ armature = bpy.data.armatures.new(TestLibraryOverridesResync.DATA_NAME_RIG)
+ obj_armature = bpy.data.objects.new(TestLibraryOverridesResync.DATA_NAME_RIG, object_data=armature)
+ obj_child.parent = obj_armature
+ collection_container.objects.link(obj_armature)
+
+ obj_child_modifier = obj_child.modifiers.new("", 'ARMATURE')
+ obj_child_modifier.object = obj_armature
+
+ obj_ctrl1 = bpy.data.objects.new(TestLibraryOverridesResync.DATA_NAME_CONTROLLER_1, object_data=None)
+ collection_container.objects.link(obj_ctrl1)
+
+ obj_armature_constraint = obj_armature.constraints.new('COPY_LOCATION')
+ obj_armature_constraint.target = obj_ctrl1
+
+ collection_sub = bpy.data.collections.new(TestLibraryOverridesResync.DATA_NAME_CONTROLLER_2)
+ collection_container.children.link(collection_sub)
+ obj_ctrl2 = bpy.data.objects.new(TestLibraryOverridesResync.DATA_NAME_CONTROLLER_2, object_data=None)
+ collection_sub.objects.link(obj_ctrl2)
+
+ bpy.ops.wm.save_as_mainfile(filepath=str(self.output_path), check_existing=False, compress=False)
+
+ def test_link_and_override_resync(self):
+ bpy.ops.wm.read_homefile(use_empty=True, use_factory_startup=True)
+ bpy.data.orphans_purge()
+
+ link_dir = self.output_path / "Collection"
+ bpy.ops.wm.link(
+ directory=str(link_dir),
+ filename=TestLibraryOverridesResync.DATA_NAME_CONTAINER,
+ instance_collections=False,
+ )
+
+ linked_collection_container = bpy.data.collections[TestLibraryOverridesResync.DATA_NAME_CONTAINER]
+ assert linked_collection_container.library is not None
+ assert linked_collection_container.override_library is None
+ assert len(bpy.data.collections) == 2
+ assert all(id_.library is not None for id_ in bpy.data.collections)
+ assert len(bpy.data.objects) == 4
+ assert all(id_.library is not None for id_ in bpy.data.objects)
+ assert len(bpy.data.meshes) == 1
+ assert all(id_.library is not None for id_ in bpy.data.meshes)
+ assert len(bpy.data.armatures) == 1
+ assert all(id_.library is not None for id_ in bpy.data.armatures)
+
+ override_collection_container = linked_collection_container.override_hierarchy_create(
+ bpy.context.scene,
+ bpy.context.view_layer,
+ )
+ assert override_collection_container.library is None
+ assert override_collection_container.override_library is not None
+ # Objects and collections are duplicated as overrides, but meshes and armatures remain only linked data.
+ assert len(bpy.data.collections) == 4
+ assert all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.collections[:2])
+ assert len(bpy.data.objects) == 8
+ assert all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.objects[:4])
+ assert len(bpy.data.meshes) == 1
+ assert len(bpy.data.armatures) == 1
+
+ bpy.ops.wm.save_as_mainfile(filepath=str(self.test_output_path), check_existing=False, compress=False)
+
+ # Re-open the lib file, and change its ID relationships.
+ bpy.ops.wm.open_mainfile(filepath=str(self.output_path))
+
+ obj_armature = bpy.data.objects[TestLibraryOverridesResync.DATA_NAME_RIG]
+ obj_armature_constraint = obj_armature.constraints[0]
+ obj_ctrl2 = bpy.data.objects[TestLibraryOverridesResync.DATA_NAME_CONTROLLER_2]
+ obj_armature_constraint.target = obj_ctrl2
+
+ bpy.ops.wm.save_as_mainfile(filepath=str(self.output_path), check_existing=False, compress=False)
+
+ # Re-open the main file, and check that automatic resync did its work correctly, remapping the target of the
+ # armature constraint to controller 2, without creating unexpected garbage IDs along the line.
+ bpy.ops.wm.open_mainfile(filepath=str(self.test_output_path))
+
+ override_collection_container = bpy.data.collections[TestLibraryOverridesResync.DATA_NAME_CONTAINER]
+ assert override_collection_container.library is None
+ assert override_collection_container.override_library is not None
+ # Objects and collections are duplicated as overrides, but meshes and armatures remain only linked data.
+ assert len(bpy.data.collections) == 4
+ assert all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.collections[:2])
+ assert len(bpy.data.objects) == 8
+ assert all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.objects[:4])
+ assert len(bpy.data.meshes) == 1
+ assert len(bpy.data.armatures) == 1
+
+ obj_armature = bpy.data.objects[TestLibraryOverridesResync.DATA_NAME_RIG]
+ obj_ctrl2 = bpy.data.objects[TestLibraryOverridesResync.DATA_NAME_CONTROLLER_2]
+ assert obj_armature.library is None and obj_armature.override_library is not None
+ assert obj_ctrl2.library is None and obj_ctrl2.override_library is not None
+ assert obj_armature.constraints[0].target == obj_ctrl2
TESTS = (
TestLibraryOverrides,
TestLibraryTemplate,
+ TestLibraryOverridesResync,
)
diff --git a/tests/python/bl_keymap_validate.py b/tests/python/bl_keymap_validate.py
index 83d41c8a9f6..11da4d562b0 100644
--- a/tests/python/bl_keymap_validate.py
+++ b/tests/python/bl_keymap_validate.py
@@ -228,7 +228,7 @@ def keyconfig_activate_and_extract_data(
bpy.ops.preferences.keyconfig_activate(filepath=filepath)
# If called multiple times, something strange is happening.
- assert(len(args_collected) == 1)
+ assert len(args_collected) == 1
args, _kw = args_collected[0]
# Ignore the type check as `temp_fn_argument_extractor` is a generic function
# which doesn't contain type information of the function being wrapped.
diff --git a/tests/python/bl_load_addons.py b/tests/python/bl_load_addons.py
index b94c56541af..b67bc22102c 100644
--- a/tests/python/bl_load_addons.py
+++ b/tests/python/bl_load_addons.py
@@ -57,7 +57,7 @@ def disable_addons():
addons = bpy.context.preferences.addons
for mod_name in list(addons.keys()):
addon_utils.disable(mod_name, default_set=True)
- assert(bool(addons) is False)
+ assert bool(addons) is False
def test_load_addons():
@@ -97,13 +97,13 @@ def reload_addons(do_reload=True, do_reverse=True):
mod_name = mod.__name__
print("\tenabling:", mod_name)
addon_utils.enable(mod_name, default_set=True)
- assert(mod_name in addons)
+ assert mod_name in addons
for mod in modules:
mod_name = mod.__name__
print("\tdisabling:", mod_name)
addon_utils.disable(mod_name, default_set=True)
- assert(not (mod_name in addons))
+ assert not (mod_name in addons)
# now test reloading
if do_reload:
diff --git a/tests/python/bl_load_py_modules.py b/tests/python/bl_load_py_modules.py
index 7ad5895ce86..784d8984935 100644
--- a/tests/python/bl_load_py_modules.py
+++ b/tests/python/bl_load_py_modules.py
@@ -161,7 +161,7 @@ def load_modules():
sys.path[:] = sys_path_back
# check we load what we ask for.
- assert(os.path.samefile(mod_imp.__file__, submod_full))
+ assert os.path.samefile(mod_imp.__file__, submod_full)
modules.append(mod_imp)
except Exception:
diff --git a/tests/python/bl_mesh_modifiers.py b/tests/python/bl_mesh_modifiers.py
index 640cf1c30f2..5498316b267 100644
--- a/tests/python/bl_mesh_modifiers.py
+++ b/tests/python/bl_mesh_modifiers.py
@@ -55,8 +55,8 @@ def render_gl(context, filepath, shade):
def render_gl_all_modes(context, obj, filepath=""):
- assert(obj is not None)
- assert(filepath != "")
+ assert obj is not None
+ assert filepath != ""
scene = context.scene
@@ -91,7 +91,7 @@ def render_gl_all_modes(context, obj, filepath=""):
render_gl(context, filepath + "_wp_wire", shade='WIREFRAME')
- assert(1)
+ assert 1
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
diff --git a/tests/python/bl_pyapi_bpy_driver_secure_eval.py b/tests/python/bl_pyapi_bpy_driver_secure_eval.py
new file mode 100644
index 00000000000..953dbcd5381
--- /dev/null
+++ b/tests/python/bl_pyapi_bpy_driver_secure_eval.py
@@ -0,0 +1,220 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+# ./blender.bin --background -noaudio --python tests/python/bl_pyapi_bpy_driver_secure_eval.py -- --verbose
+import bpy
+import unittest
+import builtins
+from types import ModuleType
+
+
+# -----------------------------------------------------------------------------
+# Mock Environment
+
+
+expect_unreachable_msg = "This function should _NEVER_ run!"
+# Internal check, to ensure this actually runs as expected.
+expect_unreachable_count = 0
+
+
+def expect_os_unreachable():
+ global expect_unreachable_count
+ expect_unreachable_count += 1
+ raise Exception(expect_unreachable_msg)
+
+
+__import__("os").expect_os_unreachable = expect_os_unreachable
+
+
+expect_open_unreachable_count = 0
+
+
+def open_expect_unreachable(*args, **kwargs):
+ global expect_open_unreachable_count
+ expect_open_unreachable_count += 1
+ raise Exception(expect_unreachable_msg)
+
+
+mock_builtins = {**builtins.__dict__, **{"open": open_expect_unreachable}}
+
+
+# -----------------------------------------------------------------------------
+# Utility Functions
+
+
+def is_expression_secure(expr_str, verbose):
+ """
+ Return (ok, code) where ok is true if expr_str is considered secure.
+ """
+ # Internal function only for testing (not part of the public API).
+ from _bpy import _driver_secure_code_test
+ expr_code = compile(expr_str, "<is_expression_secure>", 'eval')
+ ok = _driver_secure_code_test(expr_code, verbose=verbose)
+ return ok, expr_code
+
+
+# -----------------------------------------------------------------------------
+# Tests (Accept)
+
+
+class _TestExprMixIn:
+ """
+ Sub-classes must define:
+ - expressions_expect_secure: boolean, the expected secure state.
+ - expressions: A sequence of expressions that must evaluate in the driver name-space.
+
+ Optionally:
+ - expressions_expect_unreachable:
+ A boolean, when true, it's expected each expression should call
+ ``expect_os_unreachable`` or ``expect_open_unreachable``.
+ """
+
+ # Sub-class may override.
+ expressions_expect_unreachable = False
+
+ def assertSecure(self, expect_secure, expr_str):
+ is_secure, expr_code = is_expression_secure(
+ expr_str,
+ # Only verbose when secure as this is will result in an failure,
+ # in that case it's useful to know which op-codes caused the test to unexpectedly fail.
+ verbose=expect_secure,
+ )
+ if is_secure != expect_secure:
+ raise self.failureException(
+ "Expression \"%s\" was expected to be %s" %
+ (expr_str, "secure" if expect_secure else "insecure"))
+ # NOTE: executing is not essential, it's just better to ensure the expressions make sense.
+ try:
+ exec(
+ expr_code,
+ {"__builtins__": mock_builtins},
+ {**bpy.app.driver_namespace, **{"__builtins__": mock_builtins}},
+ )
+ # exec(expr_code, {}, bpy.app.driver_namespace)
+ ex = None
+ except BaseException as ex_test:
+ ex = ex_test
+
+ if self.expressions_expect_unreachable:
+ if ex and ex.args == (expect_unreachable_msg,):
+ ex = None
+ elif not ex:
+ raise self.failureException("Expression \"%s\" failed to run `os.expect_os_unreachable`" % (expr_str,))
+ else:
+ # An unknown exception was raised, use the exception below.
+ pass
+
+ if ex:
+ raise self.failureException("Expression \"%s\" failed to evaluate with error: %r" % (expr_str, ex))
+
+ def test_expr(self):
+ expect_secure = self.expressions_expect_secure
+ for expr_str in self.expressions:
+ self.assertSecure(expect_secure, expr_str)
+
+
+class TestExprMixIn_Accept(_TestExprMixIn):
+ expressions_expect_secure = True
+
+
+class TestExprMixIn_Reject(_TestExprMixIn):
+ expressions_expect_secure = False
+
+
+class TestAcceptLiteralNumbers(unittest.TestCase, TestExprMixIn_Accept):
+ expressions = ("1", "1_1", "1.1", "1j", "0x1", "0o1", "0b1")
+
+
+class TestAcceptLiteralStrings(unittest.TestCase, TestExprMixIn_Accept):
+ expressions = ("''", "'_'", "r''", "r'_'", "'''_'''")
+
+
+class TestAcceptSequencesEmpty(unittest.TestCase, TestExprMixIn_Accept):
+ expressions = ("()", "[]", "{}", "[[]]", "(())")
+
+
+class TestAcceptSequencesSimple(unittest.TestCase, TestExprMixIn_Accept):
+ expressions = ("('', '')", "['', '_']", "{'', '_'}", "{'': '_'}")
+
+
+class TestAcceptSequencesExpand(unittest.TestCase, TestExprMixIn_Accept):
+ expressions = ("(*('', '_'),)", "[*(), *[]]", "{*{1, 2}}")
+
+
+class TestAcceptSequencesComplex(unittest.TestCase, TestExprMixIn_Accept):
+ expressions = ("[1, 2, 3][-1:0:-1][0]", "1 in (1, 2)", "False if 1 in {1, 2} else True")
+
+
+class TestAcceptMathOperators(unittest.TestCase, TestExprMixIn_Accept):
+ expressions = ("4 / 4", "4 * 4", "4 // 4", "2 ** 2", "4 ^ -1", "4 & 1", "4 % 1")
+
+
+class TestAcceptMathFunctionsSimple(unittest.TestCase, TestExprMixIn_Accept):
+ expressions = ("sin(pi)", "degrees(pi / 2)", "clamp(4, 0, 1)")
+
+
+class TestAcceptMathFunctionsComplex(unittest.TestCase, TestExprMixIn_Accept):
+ expressions = ("-(sin(pi) ** 2) / 2", "floor(22 / 7)", "ceil(pi + 1)")
+
+
+# -----------------------------------------------------------------------------
+# Tests (Reject)
+
+class TestRejectLiteralFStrings(unittest.TestCase, TestExprMixIn_Reject):
+ # F-String's are not supported as `BUILD_STRING` op-code is disabled,
+ # while it may be safe to enable that needs to be double-checked.
+ # Further it doesn't seem useful for typical math expressions used in drivers.
+ expressions = ("f''", "f'{1}'", "f'{\"_\"}'")
+
+
+class TestRejectModuleAccess(unittest.TestCase, TestExprMixIn_Reject):
+ # Each of these commands _must_ run `expect_os_unreachable`,
+ # and must also be rejected as insecure - otherwise we have problems.
+ expressions_expect_unreachable = True
+ expressions = (
+ "__import__('os').expect_os_unreachable()",
+ "exec(\"__import__('os').expect_os_unreachable()\")",
+ "(globals().update(__import__('os').__dict__), expect_os_unreachable())",
+ )
+
+ # Ensure the functions are actually called.
+ def setUp(self):
+ self._count = expect_unreachable_count
+
+ def tearDown(self):
+ count_actual = expect_unreachable_count - self._count
+ count_expect = len(self.expressions)
+ if count_actual != count_expect:
+ raise Exception(
+ "Expected 'os.expect_os_unreachable' to be called %d times but was called %d times" %
+ (count_expect, count_actual),
+ )
+
+
+class TestRejectOpenAccess(unittest.TestCase, TestExprMixIn_Reject):
+ # Each of these commands _must_ run `expect_open_unreachable`,
+ # and must also be rejected as insecure - otherwise we have problems.
+ expressions_expect_unreachable = True
+ expressions = (
+ "open('file.txt', 'r')",
+ "exec(\"open('file.txt', 'r')\")",
+ "(globals().update({'fake_open': __builtins__['open']}), fake_open())",
+ )
+
+ # Ensure the functions are actually called.
+ def setUp(self):
+ self._count = expect_open_unreachable_count
+
+ def tearDown(self):
+ count_actual = expect_open_unreachable_count - self._count
+ count_expect = len(self.expressions)
+ if count_actual != count_expect:
+ raise Exception(
+ "Expected 'open' to be called %d times but was called %d times" %
+ (count_expect, count_actual),
+ )
+
+
+if __name__ == '__main__':
+ import sys
+ sys.argv = [__file__] + (sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [])
+ unittest.main()
diff --git a/tests/python/bl_pyapi_idprop.py b/tests/python/bl_pyapi_idprop.py
index ddb5be03594..ddce132dd5a 100644
--- a/tests/python/bl_pyapi_idprop.py
+++ b/tests/python/bl_pyapi_idprop.py
@@ -22,7 +22,7 @@ class TestHelper:
def setUp(self):
self._id = bpy.context.scene
self._id.pop("cycles", None)
- assert(len(self._id.keys()) == 0)
+ assert len(self._id.keys()) == 0
def tearDown(self):
for key in list(self._id.keys()):
diff --git a/tests/python/bl_rigging_symmetrize.py b/tests/python/bl_rigging_symmetrize.py
index 963be6d41d3..10ba99ac6e9 100644
--- a/tests/python/bl_rigging_symmetrize.py
+++ b/tests/python/bl_rigging_symmetrize.py
@@ -40,7 +40,7 @@ def check_loc_rot_scale(self, bone, exp_bone):
def check_parent(self, bone, exp_bone):
self.assertEqual(type(bone.parent), type(exp_bone.parent),
- "Missmatching types in pose.bones[%s].parent" % (bone.name))
+ "Mismatching types in pose.bones[%s].parent" % (bone.name))
self.assertTrue(bone.parent is None or bone.parent.name == exp_bone.parent.name,
"Bone parent does not match on bone %s" % (bone.name))
@@ -56,17 +56,17 @@ def check_bendy_bones(self, bone, exp_bone):
exp_value = getattr(exp_bone, var)
self.assertEqual(type(value), type(exp_value),
- "Missmatching types in pose.bones[%s].%s" % (bone.name, var))
+ "Mismatching types in pose.bones[%s].%s" % (bone.name, var))
if isinstance(value, str):
self.assertEqual(value, exp_value,
- "Missmatching value in pose.bones[%s].%s" % (bone.name, var))
+ "Mismatching value in pose.bones[%s].%s" % (bone.name, var))
elif hasattr(value, "name"):
self.assertEqual(value.name, exp_value.name,
- "Missmatching value in pose.bones[%s].%s" % (bone.name, var))
+ "Mismatching value in pose.bones[%s].%s" % (bone.name, var))
else:
self.assertAlmostEqual(value, exp_value,
- "Missmatching value in pose.bones[%s].%s" % (bone.name, var))
+ "Mismatching value in pose.bones[%s].%s" % (bone.name, var))
def check_ik(self, bone, exp_bone):
@@ -81,7 +81,7 @@ def check_ik(self, bone, exp_bone):
value = getattr(bone, var)
exp_value = getattr(exp_bone, var)
self.assertAlmostEqual(value, exp_value,
- "Missmatching value in pose.bones[%s].%s" % (bone.name, var))
+ "Mismatching value in pose.bones[%s].%s" % (bone.name, var))
def check_constraints(self, input_arm, expected_arm, bone, exp_bone):
@@ -89,7 +89,7 @@ def check_constraints(self, input_arm, expected_arm, bone, exp_bone):
expo_const_len = len(exp_bone.constraints)
self.assertEqual(const_len, expo_const_len,
- "Constraints missmatch on bone %s" % (bone.name))
+ "Constraints mismatch on bone %s" % (bone.name))
for exp_constraint in exp_bone.constraints:
const_name = exp_constraint.name
@@ -111,28 +111,28 @@ def check_constraints(self, input_arm, expected_arm, bone, exp_bone):
exp_value = getattr(exp_constraint, var)
self.assertEqual(type(value), type(exp_value),
- "Missmatching constraint value types in pose.bones[%s].constraints[%s].%s" % (
+ "Mismatching constraint value types in pose.bones[%s].constraints[%s].%s" % (
bone.name, const_name, var))
if isinstance(value, str):
self.assertEqual(value, exp_value,
- "Missmatching constraint value in pose.bones[%s].constraints[%s].%s" % (
+ "Mismatching constraint value in pose.bones[%s].constraints[%s].%s" % (
bone.name, const_name, var))
elif hasattr(value, "name"):
- # Some constraints targets the armature itself, so the armature name should missmatch.
+ # Some constraints targets the armature itself, so the armature name should mismatch.
if value.name == input_arm.name and exp_value.name == expected_arm.name:
continue
self.assertEqual(value.name, exp_value.name,
- "Missmatching constraint value in pose.bones[%s].constraints[%s].%s" % (
+ "Mismatching constraint value in pose.bones[%s].constraints[%s].%s" % (
bone.name, const_name, var))
elif isinstance(value, bool):
self.assertEqual(value, exp_value,
- "Missmatching constraint boolean in pose.bones[%s].constraints[%s].%s" % (
+ "Mismatching constraint boolean in pose.bones[%s].constraints[%s].%s" % (
bone.name, const_name, var))
else:
- msg = "Missmatching constraint value in pose.bones[%s].constraints[%s].%s" % (
+ msg = "Mismatching constraint value in pose.bones[%s].constraints[%s].%s" % (
bone.name, const_name, var)
self.assertAlmostEqual(value, exp_value, places=6, msg=msg)
diff --git a/tests/python/bl_rna_manual_reference.py b/tests/python/bl_rna_manual_reference.py
index 257c8b7601a..958cc46ae29 100644
--- a/tests/python/bl_rna_manual_reference.py
+++ b/tests/python/bl_rna_manual_reference.py
@@ -15,12 +15,12 @@ import bpy
def test_data():
import rna_manual_reference
- assert(isinstance(rna_manual_reference.url_manual_mapping, tuple))
+ assert isinstance(rna_manual_reference.url_manual_mapping, tuple)
for i, value in enumerate(rna_manual_reference.url_manual_mapping):
try:
- assert(len(value) == 2)
- assert(isinstance(value[0], str))
- assert(isinstance(value[1], str))
+ assert len(value) == 2
+ assert isinstance(value[0], str)
+ assert isinstance(value[1], str)
except:
print("Expected a tuple of 2 strings, instead item %d is a %s: %r" % (i, type(value), value))
import traceback
diff --git a/tests/python/bl_run_operators.py b/tests/python/bl_run_operators.py
index a2478bd7547..ccb0814e5eb 100644
--- a/tests/python/bl_run_operators.py
+++ b/tests/python/bl_run_operators.py
@@ -317,7 +317,6 @@ def ctx_editmode_mesh_extra():
bpy.ops.object.shape_key_add(from_mix=False)
bpy.ops.object.shape_key_add(from_mix=True)
bpy.ops.mesh.uv_texture_add()
- bpy.ops.mesh.vertex_color_add()
bpy.ops.object.material_slot_add()
# editmode last!
bpy.ops.object.mode_set(mode='EDIT')
diff --git a/tests/python/bl_run_operators_event_simulate.py b/tests/python/bl_run_operators_event_simulate.py
index d218e6b1bc0..e17eaef0480 100644
--- a/tests/python/bl_run_operators_event_simulate.py
+++ b/tests/python/bl_run_operators_event_simulate.py
@@ -461,7 +461,7 @@ class BlenderAction(argparse.Action):
except ArgumentTypeError as ex:
raise ArgumentTypeError("Invalid 'action' arguments \"%s\" at index %d, %s" % (value, index, str(ex)))
# Validation should never yield any events.
- assert(not dummy_result)
+ assert not dummy_result
return (op, args, kwargs)
diff --git a/tests/python/eevee_render_tests.py b/tests/python/eevee_render_tests.py
index 8c6f08ae76e..9ed850fcb52 100644
--- a/tests/python/eevee_render_tests.py
+++ b/tests/python/eevee_render_tests.py
@@ -3,10 +3,12 @@
import argparse
import os
+import pathlib
import shlex
import shutil
import subprocess
import sys
+from pathlib import Path
def setup():
@@ -98,6 +100,26 @@ if inside_blender:
sys.exit(1)
+def get_gpu_device_type(blender):
+ command = [
+ blender,
+ "-noaudio",
+ "--background"
+ "--factory-startup",
+ "--python",
+ str(pathlib.Path(__file__).parent / "gpu_info.py")
+ ]
+ try:
+ completed_process = subprocess.run(command, stdout=subprocess.PIPE)
+ for line in completed_process.stdout.read_text():
+ if line.startswith("GPU_DEVICE_TYPE:"):
+ vendor = line.split(':')[1]
+ return vendor
+ except BaseException as e:
+ return None
+ return None
+
+
def get_arguments(filepath, output_filepath):
return [
"--background",
@@ -133,11 +155,22 @@ def main():
idiff = args.idiff[0]
output_dir = args.outdir[0]
+ gpu_device_type = get_gpu_device_type(blender)
+ reference_override_dir = None
+ if gpu_device_type == "AMD":
+ reference_override_dir = "eevee_renders/amd"
+
from modules import render_report
report = render_report.Report("Eevee", output_dir, idiff)
report.set_pixelated(True)
report.set_reference_dir("eevee_renders")
+ report.set_reference_override_dir(reference_override_dir)
report.set_compare_engine('cycles', 'CPU')
+
+ test_dir_name = Path(test_dir).name
+ if test_dir_name.startswith('image'):
+ report.set_fail_threshold(0.051)
+
ok = report.run(test_dir, blender, get_arguments, batch=True)
sys.exit(not ok)
diff --git a/tests/python/gpu_info.py b/tests/python/gpu_info.py
new file mode 100644
index 00000000000..426ce29e85d
--- /dev/null
+++ b/tests/python/gpu_info.py
@@ -0,0 +1,26 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+"""
+Prints GPU back-end information to the console and exits.
+
+Use this script as `blender --background --python gpu_info.py`.
+"""
+import bpy
+import gpu
+import sys
+
+# Render with workbench to initialize the GPU backend otherwise it would fail when running in
+# background mode as the GPU backend won't be initialized.
+scene = bpy.context.scene
+scene.render.resolution_x = 1
+scene.render.resolution_y = 1
+scene.render.engine = "BLENDER_WORKBENCH"
+bpy.ops.render.render(animation=False, write_still=False)
+
+
+print('GPU_VENDOR:' + gpu.platform.vendor_get())
+print('GPU_RENDERER:' + gpu.platform.renderer_get())
+print('GPU_VERSION:' + gpu.platform.version_get())
+print('GPU_DEVICE_TYPE:' + gpu.platform.device_type_get())
+
+sys.exit(0)
diff --git a/tests/python/modules/render_report.py b/tests/python/modules/render_report.py
index 15441918800..15d46d6d127 100755
--- a/tests/python/modules/render_report.py
+++ b/tests/python/modules/render_report.py
@@ -78,12 +78,18 @@ def test_get_name(filepath):
return os.path.splitext(filename)[0]
-def test_get_images(output_dir, filepath, reference_dir):
+def test_get_images(output_dir, filepath, reference_dir, reference_override_dir):
testname = test_get_name(filepath)
dirpath = os.path.dirname(filepath)
old_dirpath = os.path.join(dirpath, reference_dir)
old_img = os.path.join(old_dirpath, testname + ".png")
+ if reference_override_dir:
+ override_dirpath = os.path.join(dirpath, reference_override_dir)
+ override_img = os.path.join(override_dirpath, testname + ".png")
+ if os.path.exists(override_img):
+ old_dirpath = override_dirpath
+ old_img = override_img
ref_dirpath = os.path.join(output_dir, os.path.basename(dirpath), "ref")
ref_img = os.path.join(ref_dirpath, testname + ".png")
@@ -108,6 +114,7 @@ class Report:
'output_dir',
'global_dir',
'reference_dir',
+ 'reference_override_dir',
'idiff',
'pixelated',
'fail_threshold',
@@ -127,6 +134,7 @@ class Report:
self.output_dir = output_dir
self.global_dir = os.path.dirname(output_dir)
self.reference_dir = 'reference_renders'
+ self.reference_override_dir = None
self.idiff = idiff
self.compare_engine = None
self.fail_threshold = 0.016
@@ -161,6 +169,9 @@ class Report:
def set_reference_dir(self, reference_dir):
self.reference_dir = reference_dir
+ def set_reference_override_dir(self, reference_override_dir):
+ self.reference_override_dir = reference_override_dir
+
def set_compare_engine(self, other_engine, other_device=None):
self.compare_engine = (other_engine, other_device)
@@ -343,7 +354,8 @@ class Report:
name = test_get_name(filepath)
name = name.replace('_', ' ')
- old_img, ref_img, new_img, diff_img = test_get_images(self.output_dir, filepath, self.reference_dir)
+ old_img, ref_img, new_img, diff_img = test_get_images(
+ self.output_dir, filepath, self.reference_dir, self.reference_override_dir)
status = error if error else ""
tr_style = """ class="table-danger" """ if error else ""
@@ -390,7 +402,8 @@ class Report:
self.compare_tests += test_html
def _diff_output(self, filepath, tmp_filepath):
- old_img, ref_img, new_img, diff_img = test_get_images(self.output_dir, filepath, self.reference_dir)
+ old_img, ref_img, new_img, diff_img = test_get_images(
+ self.output_dir, filepath, self.reference_dir, self.reference_override_dir)
# Create reference render directory.
old_dirpath = os.path.dirname(old_img)
diff --git a/tests/python/operators.py b/tests/python/operators.py
index 548a2b50b05..fc2e8e39d4f 100644
--- a/tests/python/operators.py
+++ b/tests/python/operators.py
@@ -115,6 +115,18 @@ def main():
[OperatorSpecEditMode("dissolve_faces", {}, "VERT", {5, 34, 47, 49, 83, 91, 95})],
),
+ # dissolve limited
+ SpecMeshTest(
+ "SphereDissolveLimited", "testSphereDissolveLimited", "expectedSphereDissolveLimited",
+ [OperatorSpecEditMode("dissolve_limited", {"angle_limit": 0.610865}, "FACE", {20, 23, 26, 29, 32})],
+ ),
+
+ # dissolve mode
+ SpecMeshTest(
+ "PlaneDissolveMode", "testPlaneDissolveMode", "expectedPlaneDissolveMode",
+ [OperatorSpecEditMode("dissolve_mode", {"use_verts": True}, "FACE", {0, 1, 2, 10, 12, 13})],
+ ),
+
# dissolve verts
SpecMeshTest(
"CubeDissolveVerts", "testCubeDissolveVerts", "expectedCubeDissolveVerts",
@@ -332,6 +344,12 @@ def main():
[OperatorSpecEditMode("mark_seam", {}, "EDGE", {1})],
),
+ # merge normals
+ SpecMeshTest(
+ "CubeMergeNormals", "testCubeMergeNormals", "expectedCubeMergeNormals",
+ [OperatorSpecEditMode("merge_normals", {}, "FACE", {3, 5})],
+ ),
+
# select all
SpecMeshTest(
"CircleSelectAll", "testCircleSelectAll", "expectedCircleSelectAll",
@@ -545,24 +563,6 @@ def main():
)],
),
- # Vertex Colors
- SpecMeshTest(
- "VertexColorAdd", "testCubeColorAdd", "expectedCubeColorAdd",
- [OperatorSpecEditMode("vertex_color_add", {}, "VERT", {})],
- ),
- SpecMeshTest(
- "VertexColorRemove", "testCubeColorRemove", "expectedCubeColorRemove",
- [OperatorSpecEditMode("vertex_color_remove", {}, "VERT", {})],
- ),
- SpecMeshTest(
- "VertexColorSculptAdd", "testCubeSculptAdd", "expectedCubeSculptAdd",
- [OperatorSpecEditMode("sculpt_vertex_color_add", {}, "VERT", {})],
- ),
- SpecMeshTest(
- "VertexColorSculptRemove", "testCubeSculptRemove", "expectedCubeSculptRemove",
- [OperatorSpecEditMode("sculpt_vertex_color_remove", {}, "VERT", {})],
- ),
-
# Laplacian Smooth
SpecMeshTest(
"LaplacianSmoothDefault", "testSphereLaplacianSmoothDefault", "expectedSphereLaplacianSmoothDefault",
diff --git a/tests/python/workbench_render_tests.py b/tests/python/workbench_render_tests.py
index 3ceb0fb3226..e182b2a41e2 100644
--- a/tests/python/workbench_render_tests.py
+++ b/tests/python/workbench_render_tests.py
@@ -3,10 +3,12 @@
import argparse
import os
+import platform
import shlex
import shutil
import subprocess
import sys
+from pathlib import Path
def setup():
@@ -73,6 +75,11 @@ def main():
report.set_pixelated(True)
report.set_reference_dir("workbench_renders")
report.set_compare_engine('eevee')
+
+ test_dir_name = Path(test_dir).name
+ if test_dir_name.startswith('hair') and platform.system() == "Darwin":
+ report.set_fail_threshold(0.050)
+
ok = report.run(test_dir, blender, get_arguments, batch=True)
sys.exit(not ok)