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
path: root/tests
diff options
context:
space:
mode:
authorJulian Eisel <julian@blender.org>2022-07-20 18:25:31 +0300
committerJulian Eisel <julian@blender.org>2022-07-20 18:25:31 +0300
commit9dbcefb10e53cc809eb2e99333376b2a881c0863 (patch)
tree9ea058c2877c472aba82650e24e5927e3f03eded /tests
parente1ced645fa208b3b77e07c99cb289cf7fa659ad3 (diff)
parent85f0b2ef5d5bfb67c245da0a52aeec44e63445fa (diff)
Merge branch 'asset-browser-grid-view' into file-browser-grid-view
Diffstat (limited to 'tests')
-rw-r--r--tests/performance/tests/eevee.py129
-rw-r--r--tests/python/CMakeLists.txt5
-rw-r--r--tests/python/alembic_export_tests.py2
-rw-r--r--tests/python/batch_import.py2
-rw-r--r--tests/python/bl_alembic_io_test.py2
-rw-r--r--tests/python/bl_animation_fcurves.py2
-rw-r--r--tests/python/bl_blendfile_library_overrides.py116
-rw-r--r--tests/python/bl_bundled_modules.py2
-rw-r--r--tests/python/bl_keymap_completeness.py2
-rw-r--r--tests/python/bl_keymap_validate.py2
-rw-r--r--tests/python/bl_load_addons.py2
-rw-r--r--tests/python/bl_load_py_modules.py2
-rw-r--r--tests/python/bl_mesh_modifiers.py2
-rw-r--r--tests/python/bl_mesh_validate.py2
-rw-r--r--tests/python/bl_pyapi_bpy_driver_secure_eval.py220
-rw-r--r--tests/python/bl_rigging_symmetrize.py28
-rw-r--r--tests/python/bl_rna_manual_reference.py2
-rw-r--r--tests/python/bl_rst_completeness.py2
-rw-r--r--tests/python/bl_run_operators.py2
-rw-r--r--tests/python/bl_run_operators_event_simulate.py2
-rw-r--r--tests/python/bl_test.py2
-rw-r--r--tests/python/bl_usd_import_test.py2
-rw-r--r--tests/python/boolean_operator.py2
-rw-r--r--tests/python/deform_modifiers.py2
-rw-r--r--tests/python/eevee_render_tests.py6
-rw-r--r--tests/python/ffmpeg_tests.py2
-rw-r--r--tests/python/geo_node_test.py2
-rw-r--r--tests/python/modifiers.py19
-rw-r--r--tests/python/modules/mesh_test.py2
-rwxr-xr-xtests/python/modules/render_report.py8
-rwxr-xr-xtests/python/modules/test_utils.py2
-rw-r--r--tests/python/operators.py2
-rw-r--r--tests/python/pep8.py21
-rw-r--r--tests/python/physics_cloth.py2
-rw-r--r--tests/python/physics_dynamic_paint.py2
-rw-r--r--tests/python/physics_ocean.py2
-rw-r--r--tests/python/physics_particle_instance.py2
-rw-r--r--tests/python/physics_particle_system.py2
-rw-r--r--tests/python/physics_softbody.py2
-rw-r--r--tests/python/rna_info_dump.py2
-rw-r--r--tests/python/rst_to_doctree_mini.py2
-rw-r--r--tests/python/workbench_render_tests.py7
42 files changed, 520 insertions, 103 deletions
diff --git a/tests/performance/tests/eevee.py b/tests/performance/tests/eevee.py
new file mode 100644
index 00000000000..df7cac695da
--- /dev/null
+++ b/tests/performance/tests/eevee.py
@@ -0,0 +1,129 @@
+# SPDX-License-Identifier: Apache-2.0
+
+import os
+import enum
+import time
+
+
+class RecordStage(enum.Enum):
+ INIT = 0,
+ WAIT_SHADERS = 1,
+ WARMUP = 2,
+ RECORD = 3,
+ FINISHED = 4
+
+
+WARMUP_SECONDS = 3
+WARMUP_FRAMES = 10
+SHADER_FALLBACK_SECONDS = 60
+RECORD_PLAYBACK_ITER = 3
+LOG_KEY = "ANIMATION_PERFORMANCE: "
+
+
+def _run(args):
+ import bpy
+
+ global record_stage
+ record_stage = RecordStage.INIT
+
+ bpy.app.handlers.frame_change_post.append(frame_change_handler)
+ bpy.ops.screen.animation_play()
+
+
+def frame_change_handler(scene):
+ import bpy
+
+ global record_stage
+ global start_time
+ global start_record_time
+ global start_warmup_time
+ global warmup_frame
+ global stop_record_time
+ global playback_iteration
+
+ if record_stage == RecordStage.INIT:
+ screen = bpy.context.window_manager.windows[0].screen
+ bpy.context.scene.sync_mode = 'NONE'
+
+ for area in screen.areas:
+ if area.type == 'VIEW_3D':
+ space = area.spaces[0]
+ space.shading.type = 'RENDERED'
+ space.overlay.show_overlays = False
+
+ start_time = time.perf_counter()
+ record_stage = RecordStage.WAIT_SHADERS
+
+ elif record_stage == RecordStage.WAIT_SHADERS:
+ shaders_compiled = False
+ if hasattr(bpy.app, 'is_job_running'):
+ shaders_compiled = not bpy.app.is_job_running("SHADER_COMPILATION")
+ else:
+ # Fallback when is_job_running doesn't exists by waiting for a time.
+ shaders_compiled = time.perf_counter() - start_time > SHADER_FALLBACK_SECONDS
+
+ if shaders_compiled:
+ start_warmup_time = time.perf_counter()
+ warmup_frame = 0
+ record_stage = RecordStage.WARMUP
+
+ elif record_stage == RecordStage.WARMUP:
+ warmup_frame += 1
+ if time.perf_counter() - start_warmup_time > WARMUP_SECONDS and warmup_frame > WARMUP_FRAMES:
+ start_record_time = time.perf_counter()
+ playback_iteration = 0
+ scene = bpy.context.scene
+ scene.frame_set(scene.frame_start)
+ record_stage = RecordStage.RECORD
+
+ elif record_stage == RecordStage.RECORD:
+ current_time = time.perf_counter()
+ scene = bpy.context.scene
+ if scene.frame_current == scene.frame_end:
+ playback_iteration += 1
+
+ if playback_iteration >= RECORD_PLAYBACK_ITER:
+ stop_record_time = current_time
+ record_stage = RecordStage.FINISHED
+
+ elif record_stage == RecordStage.FINISHED:
+ bpy.ops.screen.animation_cancel()
+ num_frames = RECORD_PLAYBACK_ITER * ((scene.frame_end - scene.frame_start) + 1)
+ elapse_seconds = stop_record_time - start_record_time
+ avg_frame_time = elapse_seconds / num_frames
+ fps = 1.0 / avg_frame_time
+ print(f"{LOG_KEY}{{'time': {avg_frame_time}, 'fps': {fps} }}")
+ bpy.app.handlers.frame_change_post.remove(frame_change_handler)
+ bpy.ops.wm.quit_blender()
+
+
+if __name__ == '__main__':
+ _run(None)
+
+else:
+ import api
+
+ class EeveeTest(api.Test):
+ def __init__(self, filepath):
+ self.filepath = filepath
+
+ def name(self):
+ return self.filepath.stem
+
+ def category(self):
+ return "eevee"
+
+ def run(self, env, device_id):
+ args = {}
+ _, log = env.run_in_blender(_run, args, [self.filepath], foreground=True)
+ for line in log:
+ if line.startswith(LOG_KEY):
+ result_str = line[len(LOG_KEY):]
+ result = eval(result_str)
+ return result
+
+ raise Exception("No playback performance result found in log.")
+
+ def generate(env):
+ filepaths = env.find_blend_files('eevee/*')
+ return [EeveeTest(filepath) for filepath in filepaths]
diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt
index 38c3fc4389a..d95f2cd2644 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
)
diff --git a/tests/python/alembic_export_tests.py b/tests/python/alembic_export_tests.py
index f6c05d955c9..95ae3ee9feb 100644
--- a/tests/python/alembic_export_tests.py
+++ b/tests/python/alembic_export_tests.py
@@ -1,8 +1,6 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
"""
Alembic Export Tests
diff --git a/tests/python/batch_import.py b/tests/python/batch_import.py
index a6b44bc7478..811b070b0ca 100644
--- a/tests/python/batch_import.py
+++ b/tests/python/batch_import.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
"""
Example Usage:
diff --git a/tests/python/bl_alembic_io_test.py b/tests/python/bl_alembic_io_test.py
index f3480380911..4cfda239bd1 100644
--- a/tests/python/bl_alembic_io_test.py
+++ b/tests/python/bl_alembic_io_test.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
"""
./blender.bin --background -noaudio --factory-startup --python tests/python/bl_alembic_io_test.py -- --testdir /path/to/lib/tests/alembic
"""
diff --git a/tests/python/bl_animation_fcurves.py b/tests/python/bl_animation_fcurves.py
index 449f17ebfec..931db3f2d22 100644
--- a/tests/python/bl_animation_fcurves.py
+++ b/tests/python/bl_animation_fcurves.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
"""
blender -b -noaudio --factory-startup --python tests/python/bl_animation_fcurves.py -- --testdir /path/to/lib/tests/animation
"""
diff --git a/tests/python/bl_blendfile_library_overrides.py b/tests/python/bl_blendfile_library_overrides.py
index 1acc1e4d862..3ba99bd61e4 100644
--- a/tests/python/bl_blendfile_library_overrides.py
+++ b/tests/python/bl_blendfile_library_overrides.py
@@ -181,9 +181,125 @@ class TestLibraryTemplate(TestHelper, unittest.TestCase):
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_bundled_modules.py b/tests/python/bl_bundled_modules.py
index 4a055960f83..7728a2deb47 100644
--- a/tests/python/bl_bundled_modules.py
+++ b/tests/python/bl_bundled_modules.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
# Test that modules we ship with our Python installation are available
import bz2
diff --git a/tests/python/bl_keymap_completeness.py b/tests/python/bl_keymap_completeness.py
index ee24a531f53..97335a94c01 100644
--- a/tests/python/bl_keymap_completeness.py
+++ b/tests/python/bl_keymap_completeness.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
# simple script to test 'bl_keymap_utils.keymap_hierarchy' contains correct values.
# Needed for 'bl_keymap_utils.keymap_hierarchy' which inspects tools.
diff --git a/tests/python/bl_keymap_validate.py b/tests/python/bl_keymap_validate.py
index b87eed0c0df..83d41c8a9f6 100644
--- a/tests/python/bl_keymap_validate.py
+++ b/tests/python/bl_keymap_validate.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
# ./blender.bin --background -noaudio --factory-startup --python tests/python/bl_keymap_validate.py
#
diff --git a/tests/python/bl_load_addons.py b/tests/python/bl_load_addons.py
index 8fee31f7a2b..b94c56541af 100644
--- a/tests/python/bl_load_addons.py
+++ b/tests/python/bl_load_addons.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
# simple script to enable all addons, and disable
"""
diff --git a/tests/python/bl_load_py_modules.py b/tests/python/bl_load_py_modules.py
index 4d4196d1f64..7ad5895ce86 100644
--- a/tests/python/bl_load_py_modules.py
+++ b/tests/python/bl_load_py_modules.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
# simple script to enable all addons, and disable
"""
diff --git a/tests/python/bl_mesh_modifiers.py b/tests/python/bl_mesh_modifiers.py
index 3b653402083..640cf1c30f2 100644
--- a/tests/python/bl_mesh_modifiers.py
+++ b/tests/python/bl_mesh_modifiers.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
# Currently this script only generates images from different modifier
# combinations and does not validate they work correctly,
# this is because we don't get 1:1 match with bmesh.
diff --git a/tests/python/bl_mesh_validate.py b/tests/python/bl_mesh_validate.py
index 9a9384ce777..9e093608406 100644
--- a/tests/python/bl_mesh_validate.py
+++ b/tests/python/bl_mesh_validate.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
# Simple script to check mash validate code.
# XXX Should be extended with many more "wrong cases"!
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_rigging_symmetrize.py b/tests/python/bl_rigging_symmetrize.py
index 6531b4df85f..10ba99ac6e9 100644
--- a/tests/python/bl_rigging_symmetrize.py
+++ b/tests/python/bl_rigging_symmetrize.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
"""
blender -b -noaudio --factory-startup --python tests/python/bl_rigging_symmetrize.py -- --testdir /path/to/lib/tests/animation
"""
@@ -42,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))
@@ -58,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):
@@ -83,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):
@@ -91,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
@@ -113,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 70f218f0bb2..257c8b7601a 100644
--- a/tests/python/bl_rna_manual_reference.py
+++ b/tests/python/bl_rna_manual_reference.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
# Use for validating our manual interlinking.
# ./blender.bin --background -noaudio --python tests/python/bl_rna_manual_reference.py
#
diff --git a/tests/python/bl_rst_completeness.py b/tests/python/bl_rst_completeness.py
index 59e532c433b..4846e65b78f 100644
--- a/tests/python/bl_rst_completeness.py
+++ b/tests/python/bl_rst_completeness.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
# run this script in the game engine.
# or on the command line with...
# ./blender.bin --background -noaudio --python tests/python/bl_rst_completeness.py
diff --git a/tests/python/bl_run_operators.py b/tests/python/bl_run_operators.py
index 7e73ec163a4..a2478bd7547 100644
--- a/tests/python/bl_run_operators.py
+++ b/tests/python/bl_run_operators.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
# semi-useful script, runs all operators in a number of different
# contexts, cheap way to find misc small bugs but is in no way a complete test.
#
diff --git a/tests/python/bl_run_operators_event_simulate.py b/tests/python/bl_run_operators_event_simulate.py
index 56f96847d0b..d218e6b1bc0 100644
--- a/tests/python/bl_run_operators_event_simulate.py
+++ b/tests/python/bl_run_operators_event_simulate.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
r"""
Overview
========
diff --git a/tests/python/bl_test.py b/tests/python/bl_test.py
index 7e79639a226..b71ebd2a7a7 100644
--- a/tests/python/bl_test.py
+++ b/tests/python/bl_test.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
import sys
import os
diff --git a/tests/python/bl_usd_import_test.py b/tests/python/bl_usd_import_test.py
index 1ba9b4f1edf..95b2328b2aa 100644
--- a/tests/python/bl_usd_import_test.py
+++ b/tests/python/bl_usd_import_test.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
import pathlib
import sys
import unittest
diff --git a/tests/python/boolean_operator.py b/tests/python/boolean_operator.py
index fed0b2bddfd..8b93226ab93 100644
--- a/tests/python/boolean_operator.py
+++ b/tests/python/boolean_operator.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
# To run all tests, use
# BLENDER_VERBOSE=1 blender path/to/bool_regression.blend --python path/to/boolean_operator.py -- --run-all-tests
# To run one test, use
diff --git a/tests/python/deform_modifiers.py b/tests/python/deform_modifiers.py
index 40cd9d4839c..e5be133a3ef 100644
--- a/tests/python/deform_modifiers.py
+++ b/tests/python/deform_modifiers.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
# To run the test type: blender -b /path/to/the/blend/file --python path/to/this/py/file -- --run-all-tests -- --verbose
# Type the above line in cmd/terminal, for example, look below
# blender -b c:\blender-lib\deform_modifiers.blend --python c:\deform_modifiers.py -- --run-all-tests -- --verbose
diff --git a/tests/python/eevee_render_tests.py b/tests/python/eevee_render_tests.py
index 8c6f08ae76e..68895291044 100644
--- a/tests/python/eevee_render_tests.py
+++ b/tests/python/eevee_render_tests.py
@@ -7,6 +7,7 @@ import shlex
import shutil
import subprocess
import sys
+from pathlib import Path
def setup():
@@ -138,6 +139,11 @@ def main():
report.set_pixelated(True)
report.set_reference_dir("eevee_renders")
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/ffmpeg_tests.py b/tests/python/ffmpeg_tests.py
index abbe38193b5..b40b8030f7e 100644
--- a/tests/python/ffmpeg_tests.py
+++ b/tests/python/ffmpeg_tests.py
@@ -1,8 +1,6 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
import argparse
import pathlib
import sys
diff --git a/tests/python/geo_node_test.py b/tests/python/geo_node_test.py
index 9d7c634db76..0842dd001da 100644
--- a/tests/python/geo_node_test.py
+++ b/tests/python/geo_node_test.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
import os
import sys
diff --git a/tests/python/modifiers.py b/tests/python/modifiers.py
index 8f8b5c6498c..11d696c3bed 100644
--- a/tests/python/modifiers.py
+++ b/tests/python/modifiers.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
import math
import os
import sys
@@ -215,7 +213,6 @@ def main():
SpecMeshTest("MergedNoneWeld", "testMergedNoneWeld", "expectedMergedNoneWeld",
[ModifierSpec("weld", 'WELD', {"merge_threshold": 0.019})]),
-
#############################################
# One 'Deform' modifier on primitive meshes
#############################################
@@ -327,6 +324,22 @@ def main():
]
+ boolean_basename = "CubeBooleanDiffBMeshObject"
+ tests.append(SpecMeshTest("BooleandDiffBMeshObject", "test" + boolean_basename, "expected" + boolean_basename,
+ [ModifierSpec("boolean", 'BOOLEAN',
+ {"solver": 'FAST', "operation": 'DIFFERENCE', "operand_type": 'OBJECT',
+ "object": bpy.data.objects["test" + boolean_basename + "Operand"]})]))
+ boolean_basename = "CubeBooleanDiffBMeshCollection"
+ tests.append(SpecMeshTest("BooleandDiffBMeshCollection",
+ "test" + boolean_basename,
+ "expected" + boolean_basename,
+ [ModifierSpec("boolean",
+ 'BOOLEAN',
+ {"solver": 'FAST',
+ "operation": 'DIFFERENCE',
+ "operand_type": 'COLLECTION',
+ "collection": bpy.data.collections["test" + boolean_basename + "Operands"]})]))
+
modifiers_test = RunTest(tests)
command = list(sys.argv)
diff --git a/tests/python/modules/mesh_test.py b/tests/python/modules/mesh_test.py
index 873ab779d65..5b01bfeee94 100644
--- a/tests/python/modules/mesh_test.py
+++ b/tests/python/modules/mesh_test.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
# A framework to run regression tests on mesh modifiers and operators based on howardt's mesh_ops_test.py
#
# General idea:
diff --git a/tests/python/modules/render_report.py b/tests/python/modules/render_report.py
index cae81af3144..15441918800 100755
--- a/tests/python/modules/render_report.py
+++ b/tests/python/modules/render_report.py
@@ -253,8 +253,11 @@ class Report:
failed = len(failed_tests) > 0
if failed:
message = """<div class="alert alert-danger" role="alert">"""
- message += """Run this command to update reference images for failed tests, or create images for new tests:<br>"""
- message += """<tt>BLENDER_TEST_UPDATE=1 ctest -R %s</tt>""" % self.title.lower()
+ message += """<p>Run this command to regenerate reference (ground truth) images:</p>"""
+ message += """<p><tt>BLENDER_TEST_UPDATE=1 ctest -R %s</tt></p>""" % self.title.lower()
+ message += """<p>This then happens for new and failing tests; reference images of """ \
+ """passing test cases will not be updated. Be sure to commit the new reference """ \
+ """images to the SVN repository afterwards.</p>"""
message += """</div>"""
else:
message = ""
@@ -294,6 +297,7 @@ class Report:
background-position:0 0, 25px 0, 25px -25px, 0px 25px;
}}
table td:first-child {{ width: 256px; }}
+ p {{ margin-bottom: 0.5rem; }}
</style>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
</head>
diff --git a/tests/python/modules/test_utils.py b/tests/python/modules/test_utils.py
index d5cd743cde9..6aba3a75263 100755
--- a/tests/python/modules/test_utils.py
+++ b/tests/python/modules/test_utils.py
@@ -1,8 +1,6 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
import functools
import shutil
import pathlib
diff --git a/tests/python/operators.py b/tests/python/operators.py
index 6ccc96dba5d..548a2b50b05 100644
--- a/tests/python/operators.py
+++ b/tests/python/operators.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
import bpy
import os
import sys
diff --git a/tests/python/pep8.py b/tests/python/pep8.py
index 9a2871c9ed5..2583bec8256 100644
--- a/tests/python/pep8.py
+++ b/tests/python/pep8.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8-80 compliant>
-
import os
import subprocess
import shutil
@@ -22,7 +20,6 @@ import shutil
# how many lines to read into the file, pep8 comment
# should be directly after the license header, ~20 in most cases
-PEP8_SEEK_COMMENT = 40
SKIP_PREFIX = "./tools", "./config", "./extern"
SKIP_ADDONS = True
FORCE_PEP8_ALL = False
@@ -39,22 +36,8 @@ def is_pep8(path):
print(path)
if open(path, 'rb').read(3) == b'\xef\xbb\xbf':
print("\nfile contains BOM, remove first 3 bytes: %r\n" % path)
-
- # templates don't have a header but should be pep8
- for d in ("presets", "templates_py", "examples"):
- if ("%s%s%s" % (os.sep, d, os.sep)) in path:
- return 1
-
- f = open(path, 'r', encoding="utf8")
- for _ in range(PEP8_SEEK_COMMENT):
- line = f.readline()
- if line.startswith("# <pep8"):
- if line.startswith("# <pep8 compliant>"):
- return 1
- elif line.startswith("# <pep8-80 compliant>"):
- return 2
- f.close()
- return 0
+ # Currently all scripts assumed to be pep8.
+ return 1
def check_files_flake8(files):
diff --git a/tests/python/physics_cloth.py b/tests/python/physics_cloth.py
index e453b4dd68b..fbd33392371 100644
--- a/tests/python/physics_cloth.py
+++ b/tests/python/physics_cloth.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
import os
import sys
diff --git a/tests/python/physics_dynamic_paint.py b/tests/python/physics_dynamic_paint.py
index 57b96ccffba..132c7b8c46d 100644
--- a/tests/python/physics_dynamic_paint.py
+++ b/tests/python/physics_dynamic_paint.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
import os
import sys
diff --git a/tests/python/physics_ocean.py b/tests/python/physics_ocean.py
index 20d563f782b..54cf8d65a9d 100644
--- a/tests/python/physics_ocean.py
+++ b/tests/python/physics_ocean.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
import os
import sys
diff --git a/tests/python/physics_particle_instance.py b/tests/python/physics_particle_instance.py
index 353c0d868c8..3cd52fd9fa0 100644
--- a/tests/python/physics_particle_instance.py
+++ b/tests/python/physics_particle_instance.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
import os
import sys
diff --git a/tests/python/physics_particle_system.py b/tests/python/physics_particle_system.py
index 37e3df781b0..51afae68a7b 100644
--- a/tests/python/physics_particle_system.py
+++ b/tests/python/physics_particle_system.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
import os
import sys
import bpy
diff --git a/tests/python/physics_softbody.py b/tests/python/physics_softbody.py
index 00d2a637cf7..ebb9fbb724f 100644
--- a/tests/python/physics_softbody.py
+++ b/tests/python/physics_softbody.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
import os
import sys
diff --git a/tests/python/rna_info_dump.py b/tests/python/rna_info_dump.py
index afedf670f2f..af00ef54de9 100644
--- a/tests/python/rna_info_dump.py
+++ b/tests/python/rna_info_dump.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
# Used for generating API diffs between releases
# ./blender.bin --background -noaudio --python tests/python/rna_info_dump.py
diff --git a/tests/python/rst_to_doctree_mini.py b/tests/python/rst_to_doctree_mini.py
index 3466c915aa0..43116922fe5 100644
--- a/tests/python/rst_to_doctree_mini.py
+++ b/tests/python/rst_to_doctree_mini.py
@@ -1,7 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-# <pep8 compliant>
-
# Module with function to extract a doctree from an reStructuredText file.
# Named 'Mini' because we only parse the minimum data needed to check
# Python classes, methods and attributes match up to those in existing modules.
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)