diff options
Diffstat (limited to 'tests/python')
-rw-r--r-- | tests/python/CMakeLists.txt | 11 | ||||
-rw-r--r-- | tests/python/bl_blendfile_io.py | 4 | ||||
-rw-r--r-- | tests/python/bl_blendfile_liblink.py | 264 | ||||
-rw-r--r-- | tests/python/bl_blendfile_library_overrides.py | 198 | ||||
-rw-r--r-- | tests/python/bl_keymap_validate.py | 2 | ||||
-rw-r--r-- | tests/python/bl_load_addons.py | 6 | ||||
-rw-r--r-- | tests/python/bl_load_py_modules.py | 2 | ||||
-rw-r--r-- | tests/python/bl_mesh_modifiers.py | 6 | ||||
-rw-r--r-- | tests/python/bl_pyapi_bpy_driver_secure_eval.py | 220 | ||||
-rw-r--r-- | tests/python/bl_pyapi_idprop.py | 2 | ||||
-rw-r--r-- | tests/python/bl_rigging_symmetrize.py | 26 | ||||
-rw-r--r-- | tests/python/bl_rna_manual_reference.py | 8 | ||||
-rw-r--r-- | tests/python/bl_run_operators.py | 1 | ||||
-rw-r--r-- | tests/python/bl_run_operators_event_simulate.py | 2 | ||||
-rw-r--r-- | tests/python/eevee_render_tests.py | 33 | ||||
-rw-r--r-- | tests/python/gpu_info.py | 26 | ||||
-rwxr-xr-x | tests/python/modules/render_report.py | 19 | ||||
-rw-r--r-- | tests/python/operators.py | 36 | ||||
-rw-r--r-- | tests/python/workbench_render_tests.py | 7 |
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) |