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

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHimanshi Kalra <calra>2020-12-17 18:14:55 +0300
committerHimanshi Kalra <himanshikalra98@gmail.com>2020-12-17 18:28:20 +0300
commite7b698327cd91b371ff4fd43d1c117637224fded (patch)
treeee17716640f1b1133ae2ab4ca65cd92a63d09f15 /tests/python/modules
parentfed995ced5b0b963bb42de5aa3e93a3c5dc91aac (diff)
Updated and extended Regression Testing frameworks (Gsoc 2020)
This revision contains the following changes- - Updated the existing testing framework for Modifiers for Regression Testing. - Tests for Physics modifiers and remaining Generate and Deform modifiers are added. - The existing `ModifierSpec` is updated with backward compatibility to support Physics Modifiers. - Now there is support for frame number and giving nested parameters for attributes. - Some Deform modifiers required Object Operators, e.g. "Bind" in Mesh Deform, so a new class was added to support that functionality. - A separate class for holding Particles System, they are tested by converting all the particles to mesh and joining it to the mesh they were added. - Updated the format to add tests for Bevel, Boolean and Operators as well. Reviewed By: zazizizou, mont29, campbellbarton Differential Revision: https://developer.blender.org/D8507
Diffstat (limited to 'tests/python/modules')
-rw-r--r--tests/python/modules/mesh_test.py557
1 files changed, 342 insertions, 215 deletions
diff --git a/tests/python/modules/mesh_test.py b/tests/python/modules/mesh_test.py
index c85e7acf4e8..6671918a206 100644
--- a/tests/python/modules/mesh_test.py
+++ b/tests/python/modules/mesh_test.py
@@ -45,7 +45,6 @@ import functools
import inspect
import os
-
# Output from this module and from blender itself will occur during tests.
# We need to flush python so that the output is properly interleaved, otherwise
# blender's output for one test will end up showing in the middle of another test...
@@ -54,37 +53,39 @@ print = functools.partial(print, flush=True)
class ModifierSpec:
"""
- Holds one modifier and its parameters.
+ Holds a Generate or Deform or Physics modifier type and its parameters.
"""
- def __init__(self, modifier_name: str, modifier_type: str, modifier_parameters: dict):
+ def __init__(self, modifier_name: str, modifier_type: str, modifier_parameters: dict, frame_end=0):
"""
Constructs a modifier spec.
:param modifier_name: str - name of object modifier, e.g. "myFirstSubsurfModif"
:param modifier_type: str - type of object modifier, e.g. "SUBSURF"
:param modifier_parameters: dict - {name : val} dictionary giving modifier parameters, e.g. {"quality" : 4}
+ :param frame_end: int - frame at which simulation needs to be baked or modifier needs to be applied.
"""
self.modifier_name = modifier_name
self.modifier_type = modifier_type
self.modifier_parameters = modifier_parameters
+ self.frame_end = frame_end
def __str__(self):
return "Modifier: " + self.modifier_name + " of type " + self.modifier_type + \
" with parameters: " + str(self.modifier_parameters)
-class PhysicsSpec:
+class ParticleSystemSpec:
"""
- Holds one Physics modifier and its parameters.
+ Holds a Particle System modifier and its parameters.
"""
def __init__(self, modifier_name: str, modifier_type: str, modifier_parameters: dict, frame_end: int):
"""
- Constructs a physics spec.
- :param modifier_name: str - name of object modifier, e.g. "Cloth"
- :param modifier_type: str - type of object modifier, e.g. "CLOTH"
- :param modifier_parameters: dict - {name : val} dictionary giving modifier parameters, e.g. {"quality" : 4}
- :param frame_end:int - the last frame of the simulation at which it is baked
+ Constructs a particle system spec.
+ :param modifier_name: str - name of object modifier, e.g. "Particles"
+ :param modifier_type: str - type of object modifier, e.g. "PARTICLE_SYSTEM"
+ :param modifier_parameters: dict - {name : val} dictionary giving modifier parameters, e.g. {"seed" : 1}
+ :param frame_end: int - the last frame of the simulation at which the modifier is applied
"""
self.modifier_name = modifier_name
self.modifier_type = modifier_type
@@ -96,14 +97,14 @@ class PhysicsSpec:
" with parameters: " + str(self.modifier_parameters) + " with frame end: " + str(self.frame_end)
-class OperatorSpec:
+class OperatorSpecEditMode:
"""
Holds one operator and its parameters.
"""
def __init__(self, operator_name: str, operator_parameters: dict, select_mode: str, selection: set):
"""
- Constructs an operatorSpec. Raises ValueError if selec_mode is invalid.
+ Constructs an OperatorSpecEditMode. Raises ValueError if selec_mode is invalid.
:param operator_name: str - name of mesh operator from bpy.ops.mesh, e.g. "bevel" or "fill"
:param operator_parameters: dict - {name : val} dictionary containing operator parameters.
:param select_mode: str - mesh selection mode, must be either 'VERT', 'EDGE' or 'FACE'
@@ -121,6 +122,45 @@ class OperatorSpec:
" in selection mode: " + self.select_mode + ", selecting " + str(self.selection)
+class OperatorSpecObjectMode:
+ """
+ Holds an object operator and its parameters. Helper class for DeformModifierSpec.
+ Needed to support operations in Object Mode and not Edit Mode which is supported by OperatorSpecEditMode.
+ """
+
+ def __init__(self, operator_name: str, operator_parameters: dict):
+ """
+ :param operator_name: str - name of the object operator from bpy.ops.object, e.g. "shade_smooth" or "shape_keys"
+ :param operator_parameters: dict - contains operator parameters.
+ """
+ self.operator_name = operator_name
+ self.operator_parameters = operator_parameters
+
+ def __str__(self):
+ return "Operator: " + self.operator_name + " with parameters: " + str(self.operator_parameters)
+
+
+class DeformModifierSpec:
+ """
+ Holds a list of deform modifier and OperatorSpecObjectMode.
+ For deform modifiers which have an object operator
+ """
+
+ def __init__(self, frame_number: int, modifier_list: list, object_operator_spec: OperatorSpecObjectMode = None):
+ """
+ Constructs a Deform Modifier spec (for user input)
+ :param frame_number: int - the frame at which animated keyframe is inserted
+ :param modifier_list: ModifierSpec - contains modifiers
+ :param object_operator_spec: OperatorSpecObjectMode - contains object operators
+ """
+ self.frame_number = frame_number
+ self.modifier_list = modifier_list
+ self.object_operator_spec = object_operator_spec
+
+ def __str__(self):
+ return "Modifier: " + str(self.modifier_list) + " with object operator " + str(self.object_operator_spec)
+
+
class MeshTest:
"""
A mesh testing class targeted at testing modifiers and operators on a single object.
@@ -129,33 +169,45 @@ class MeshTest:
"""
def __init__(
- self,
- test_object_name: str,
- expected_object_name: str,
- operations_stack=None,
- apply_modifiers=False,
- threshold=None,
+ self,
+ test_name: str,
+ test_object_name: str,
+ expected_object_name: str,
+ operations_stack=None,
+ apply_modifiers=False,
+ do_compare=False,
+ threshold=None
):
"""
Constructs a MeshTest object. Raises a KeyError if objects with names expected_object_name
or test_object_name don't exist.
- :param test_object: str - Name of object of mesh type to run the operations on.
- :param expected_object: str - Name of object of mesh type that has the expected
+ :param test_name: str - unique test name identifier.
+ :param test_object_name: str - Name of object of mesh type to run the operations on.
+ :param expected_object_name: str - Name of object of mesh type that has the expected
geometry after running the operations.
:param operations_stack: list - stack holding operations to perform on the test_object.
- :param apply_modifier: bool - True if we want to apply the modifiers right after adding them to the object.
- This affects operations of type ModifierSpec only.
+ :param apply_modifiers: bool - True if we want to apply the modifiers right after adding them to the object.
+ - True if we want to apply the modifier to a list of modifiers, after some operation.
+ This affects operations of type ModifierSpec and DeformModifierSpec.
+ :param do_compare: bool - True if we want to compare the test and expected objects, False otherwise.
+ :param threshold : exponent: To allow variations and accept difference to a certain degree.
+
"""
if operations_stack is None:
operations_stack = []
for operation in operations_stack:
- if not (isinstance(operation, ModifierSpec) or isinstance(operation, OperatorSpec)):
- raise ValueError("Expected operation of type {} or {}. Got {}".
- format(type(ModifierSpec), type(OperatorSpec),
+ if not (isinstance(operation, ModifierSpec) or isinstance(operation, OperatorSpecEditMode)
+ or isinstance(operation, OperatorSpecObjectMode) or isinstance(operation, DeformModifierSpec)
+ or isinstance(operation, ParticleSystemSpec)):
+ raise ValueError("Expected operation of type {} or {} or {} or {}. Got {}".
+ format(type(ModifierSpec), type(OperatorSpecEditMode),
+ type(DeformModifierSpec), type(ParticleSystemSpec),
type(operation)))
self.operations_stack = operations_stack
self.apply_modifier = apply_modifiers
+ self.do_compare = do_compare
self.threshold = threshold
+ self.test_name = test_name
self.verbose = os.environ.get("BLENDER_VERBOSE") is not None
self.update = os.getenv('BLENDER_TEST_UPDATE') is not None
@@ -187,22 +239,6 @@ class MeshTest:
objects = bpy.data.objects
self.expected_object = objects[expected_object_name]
- def add_modifier(self, modifier_spec: ModifierSpec):
- """
- Add a modifier to the operations stack.
- :param modifier_spec: modifier to add to the operations stack
- """
- self.operations_stack.append(modifier_spec)
- if self.verbose:
- print("Added modififier {}".format(modifier_spec))
-
- def add_operator(self, operator_spec: OperatorSpec):
- """
- Adds an operator to the operations stack.
- :param operator_spec: OperatorSpec - operator to add to the operations stack.
- """
- self.operations_stack.append(operator_spec)
-
def _on_failed_test(self, compare_result, validation_success, evaluated_test_object):
if self.update and validation_success:
if self.verbose:
@@ -239,83 +275,164 @@ class MeshTest:
"""
return self._test_updated
- def _apply_modifier(self, test_object, modifier_spec: ModifierSpec):
+ def _set_parameters_impl(self, modifier, modifier_parameters, nested_settings_path, modifier_name):
"""
- Add modifier to object and apply (if modifier_spec.apply_modifier is True)
+ Doing a depth first traversal of the modifier parameters and setting their values.
+ :param: modifier: Of type modifier, its altered to become a setting in recursion.
+ :param: modifier_parameters : dict or sequence, a simple/nested dictionary of modifier parameters.
+ :param: nested_settings_path : list(stack): helps in tracing path to each node.
+ """
+ if not isinstance(modifier_parameters, dict):
+ param_setting = None
+ for i, setting in enumerate(nested_settings_path):
+
+ # We want to set the attribute only when we have reached the last setting.
+ # Applying of intermediate settings is meaningless.
+ if i == len(nested_settings_path) - 1:
+ setattr(modifier, setting, modifier_parameters)
+
+ elif hasattr(modifier, setting):
+ param_setting = getattr(modifier, setting)
+ # getattr doesn't accept canvas_surfaces["Surface"], but we need to pass it to setattr.
+ if setting == "canvas_surfaces":
+ modifier = param_setting.active
+ else:
+ modifier = param_setting
+ else:
+ # Clean up first
+ bpy.ops.object.delete()
+ raise Exception("Modifier '{}' has no parameter named '{}'".
+ format(modifier_name, setting))
+
+ # It pops the current node before moving on to its sibling.
+ nested_settings_path.pop()
+ return
+
+ for key in modifier_parameters:
+ nested_settings_path.append(key)
+ self._set_parameters_impl(modifier, modifier_parameters[key], nested_settings_path, modifier_name)
+
+ if nested_settings_path:
+ nested_settings_path.pop()
+
+ def set_parameters(self, modifier, modifier_parameters):
+ """
+ Wrapper for _set_parameters_util
+ """
+ settings = []
+ modifier_name = modifier.name
+ self._set_parameters_impl(modifier, modifier_parameters, settings, modifier_name)
+
+ def _add_modifier(self, test_object, modifier_spec: ModifierSpec):
+ """
+ Add modifier to object.
:param test_object: bpy.types.Object - Blender object to apply modifier on.
:param modifier_spec: ModifierSpec - ModifierSpec object with parameters
"""
+ bakers_list = ['CLOTH', 'SOFT_BODY', 'DYNAMIC_PAINT', 'FLUID']
+ scene = bpy.context.scene
+ scene.frame_set(1)
modifier = test_object.modifiers.new(modifier_spec.modifier_name,
modifier_spec.modifier_type)
+
+ if modifier is None:
+ raise Exception("This modifier type is already added on the Test Object, please remove it and try again.")
+
if self.verbose:
print("Created modifier '{}' of type '{}'.".
format(modifier_spec.modifier_name, modifier_spec.modifier_type))
- for param_name in modifier_spec.modifier_parameters:
- try:
- setattr(modifier, param_name, modifier_spec.modifier_parameters[param_name])
- if self.verbose:
- print("\t set parameter '{}' with value '{}'".
- format(param_name, modifier_spec.modifier_parameters[param_name]))
- except AttributeError:
- # Clean up first
- bpy.ops.object.delete()
- raise AttributeError("Modifier '{}' has no parameter named '{}'".
- format(modifier_spec.modifier_type, param_name))
+ # Special case for Dynamic Paint, need to toggle Canvas on.
+ if modifier.type == "DYNAMIC_PAINT":
+ bpy.ops.dpaint.type_toggle(type='CANVAS')
- if self.apply_modifier:
- bpy.ops.object.modifier_apply(modifier=modifier_spec.modifier_name)
+ self.set_parameters(modifier, modifier_spec.modifier_parameters)
+
+ if modifier.type in bakers_list:
+ self._bake_current_simulation(test_object, modifier.name, modifier_spec.frame_end)
+
+ scene.frame_set(modifier_spec.frame_end)
+
+ def _apply_modifier(self, test_object, modifier_name):
+ # Modifier automatically gets applied when converting from Curve to Mesh.
+ if test_object.type == 'CURVE':
+ bpy.ops.object.convert(target='MESH')
+ elif test_object.type == 'MESH':
+ bpy.ops.object.modifier_apply(modifier=modifier_name)
+ else:
+ raise Exception("This object type is not yet supported!")
+
+ def _bake_current_simulation(self, test_object, test_modifier_name, frame_end):
+ """
+ FLUID: Bakes the simulation
+ SOFT BODY, CLOTH, DYNAMIC PAINT: Overrides the point_cache context and then bakes.
+ """
- def _bake_current_simulation(self, obj, test_mod_type, test_mod_name, frame_end):
for scene in bpy.data.scenes:
- for modifier in obj.modifiers:
- if modifier.type == test_mod_type:
- obj.modifiers[test_mod_name].point_cache.frame_end = frame_end
- override = {'scene': scene, 'active_object': obj, 'point_cache': modifier.point_cache}
+ for modifier in test_object.modifiers:
+ if modifier.type == 'FLUID':
+ bpy.ops.fluid.bake_all()
+ break
+
+ elif modifier.type == 'CLOTH' or modifier.type == 'SOFT_BODY':
+ test_object.modifiers[test_modifier_name].point_cache.frame_end = frame_end
+ override_setting = modifier.point_cache
+ override = {'scene': scene, 'active_object': test_object, 'point_cache': override_setting}
+ bpy.ops.ptcache.bake(override, bake=True)
+ break
+
+ elif modifier.type == 'DYNAMIC_PAINT':
+ dynamic_paint_setting = modifier.canvas_settings.canvas_surfaces.active
+ override_setting = dynamic_paint_setting.point_cache
+ override = {'scene': scene, 'active_object': test_object, 'point_cache': override_setting}
bpy.ops.ptcache.bake(override, bake=True)
break
- def _apply_physics_settings(self, test_object, physics_spec: PhysicsSpec):
+ def _apply_particle_system(self, test_object, particle_sys_spec: ParticleSystemSpec):
"""
- Apply Physics settings to test objects.
+ Applies Particle System settings to test objects
"""
- scene = bpy.context.scene
- scene.frame_set(1)
- modifier = test_object.modifiers.new(physics_spec.modifier_name,
- physics_spec.modifier_type)
- physics_setting = modifier.settings
+ bpy.context.scene.frame_set(1)
+ bpy.ops.object.select_all(action='DESELECT')
+
+ test_object.modifiers.new(particle_sys_spec.modifier_name, particle_sys_spec.modifier_type)
+
+ settings_name = test_object.particle_systems.active.settings.name
+ particle_setting = bpy.data.particles[settings_name]
if self.verbose:
print("Created modifier '{}' of type '{}'.".
- format(physics_spec.modifier_name, physics_spec.modifier_type))
+ format(particle_sys_spec.modifier_name, particle_sys_spec.modifier_type))
- for param_name in physics_spec.modifier_parameters:
+ for param_name in particle_sys_spec.modifier_parameters:
try:
- setattr(physics_setting, param_name, physics_spec.modifier_parameters[param_name])
+ if param_name == "seed":
+ system_setting = test_object.particle_systems[particle_sys_spec.modifier_name]
+ setattr(system_setting, param_name, particle_sys_spec.modifier_parameters[param_name])
+ else:
+ setattr(particle_setting, param_name, particle_sys_spec.modifier_parameters[param_name])
+
if self.verbose:
print("\t set parameter '{}' with value '{}'".
- format(param_name, physics_spec.modifier_parameters[param_name]))
+ format(param_name, particle_sys_spec.modifier_parameters[param_name]))
except AttributeError:
# Clean up first
bpy.ops.object.delete()
raise AttributeError("Modifier '{}' has no parameter named '{}'".
- format(physics_spec.modifier_type, param_name))
-
- scene.frame_set(physics_spec.frame_end + 1)
+ format(particle_sys_spec.modifier_type, param_name))
- self._bake_current_simulation(
- test_object,
- physics_spec.modifier_type,
- physics_spec.modifier_name,
- physics_spec.frame_end,
- )
+ bpy.context.scene.frame_set(particle_sys_spec.frame_end)
+ test_object.select_set(True)
+ bpy.ops.object.duplicates_make_real()
+ test_object.select_set(True)
+ bpy.ops.object.join()
if self.apply_modifier:
- bpy.ops.object.modifier_apply(modifier=physics_spec.modifier_name)
+ self._apply_modifier(test_object, particle_sys_spec.modifier_name)
- def _apply_operator(self, test_object, operator: OperatorSpec):
+ def _apply_operator_edit_mode(self, test_object, operator: OperatorSpecEditMode):
"""
Apply operator on test object.
:param test_object: bpy.types.Object - Blender object to apply operator on.
- :param operator: OperatorSpec - OperatorSpec object with parameters.
+ :param operator: OperatorSpecEditMode - OperatorSpecEditMode object with parameters.
"""
mesh = test_object.data
bpy.ops.object.mode_set(mode='EDIT')
@@ -340,15 +457,64 @@ class MeshTest:
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type=operator.select_mode)
mesh_operator = getattr(bpy.ops.mesh, operator.operator_name)
- if not mesh_operator:
- raise AttributeError("No mesh operator {}".format(operator.operator_name))
- retval = mesh_operator(**operator.operator_parameters)
+
+ try:
+ retval = mesh_operator(**operator.operator_parameters)
+ except AttributeError:
+ raise AttributeError("bpy.ops.mesh has no attribute {}".format(operator.operator_name))
+ except TypeError as ex:
+ raise TypeError("Incorrect operator parameters {!r} raised {!r}".format(operator.operator_parameters, ex))
+
+ if retval != {'FINISHED'}:
+ raise RuntimeError("Unexpected operator return value: {}".format(retval))
+ if self.verbose:
+ print("Applied {}".format(operator))
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ def _apply_operator_object_mode(self, operator: OperatorSpecObjectMode):
+ """
+ Applies the object operator.
+ """
+ bpy.ops.object.mode_set(mode='OBJECT')
+ object_operator = getattr(bpy.ops.object, operator.operator_name)
+
+ try:
+ retval = object_operator(**operator.operator_parameters)
+ except AttributeError:
+ raise AttributeError("bpy.ops.mesh has no attribute {}".format(operator.operator_name))
+ except TypeError as ex:
+ raise TypeError("Incorrect operator parameters {!r} raised {!r}".format(operator.operator_parameters, ex))
+
if retval != {'FINISHED'}:
raise RuntimeError("Unexpected operator return value: {}".format(retval))
if self.verbose:
print("Applied operator {}".format(operator))
+ def _apply_deform_modifier(self, test_object, operation: list):
+ """
+ param: operation: list: List of modifiers or combination of modifier and object operator.
+ """
+
+ scene = bpy.context.scene
+ scene.frame_set(1)
bpy.ops.object.mode_set(mode='OBJECT')
+ modifier_operations_list = operation.modifier_list
+ modifier_names = []
+ object_operations = operation.object_operator_spec
+ for modifier_operations in modifier_operations_list:
+ if isinstance(modifier_operations, ModifierSpec):
+ self._add_modifier(test_object, modifier_operations)
+ modifier_names.append(modifier_operations.modifier_name)
+
+ if isinstance(object_operations, OperatorSpecObjectMode):
+ self._apply_operator_object_mode(object_operations)
+
+ scene.frame_set(operation.frame_number)
+
+ if self.apply_modifier:
+ for mod_name in modifier_names:
+ self._apply_modifier(test_object, mod_name)
def run_test(self):
"""
@@ -369,24 +535,40 @@ class MeshTest:
evaluated_test_object = bpy.context.active_object
evaluated_test_object.name = "evaluated_object"
if self.verbose:
+ print()
print(evaluated_test_object.name, "is set to active")
# Add modifiers and operators.
for operation in self.operations_stack:
if isinstance(operation, ModifierSpec):
- self._apply_modifier(evaluated_test_object, operation)
+ self._add_modifier(evaluated_test_object, operation)
+ if self.apply_modifier:
+ self._apply_modifier(evaluated_test_object, operation.modifier_name)
+
+ elif isinstance(operation, OperatorSpecEditMode):
+ self._apply_operator_edit_mode(evaluated_test_object, operation)
+
+ elif isinstance(operation, OperatorSpecObjectMode):
+ self._apply_operator_object_mode(operation)
- elif isinstance(operation, OperatorSpec):
- self._apply_operator(evaluated_test_object, operation)
+ elif isinstance(operation, DeformModifierSpec):
+ self._apply_deform_modifier(evaluated_test_object, operation)
+
+ elif isinstance(operation, ParticleSystemSpec):
+ self._apply_particle_system(evaluated_test_object, operation)
- elif isinstance(operation, PhysicsSpec):
- self._apply_physics_settings(evaluated_test_object, operation)
else:
- raise ValueError("Expected operation of type {} or {} or {}. Got {}".
- format(type(ModifierSpec), type(OperatorSpec), type(PhysicsSpec),
- type(operation)))
+ raise ValueError("Expected operation of type {} or {} or {} or {}. Got {}".
+ format(type(ModifierSpec), type(OperatorSpecEditMode),
+ type(OperatorSpecObjectMode), type(ParticleSystemSpec), type(operation)))
# Compare resulting mesh with expected one.
+ # Compare only when self.do_compare is set to True, it is set to False for run-test and returns.
+ if not self.do_compare:
+ print("Meshes/objects are not compared, compare evaluated and expected object in Blender for "
+ "visualization only.")
+ return False
+
if self.verbose:
print("Comparing expected mesh with resulting mesh...")
evaluated_test_mesh = evaluated_test_object.data
@@ -415,75 +597,80 @@ class MeshTest:
return self._on_failed_test(compare_result, validation_success, evaluated_test_object)
-class OperatorTest:
+class RunTest:
"""
- Helper class that stores and executes operator tests.
+ Helper class that stores and executes modifier tests.
Example usage:
+ >>> modifier_list = [
+ >>> ModifierSpec("firstSUBSURF", "SUBSURF", {"quality": 5}),
+ >>> ModifierSpec("firstSOLIDIFY", "SOLIDIFY", {"thickness_clamp": 0.9, "thickness": 1})
+ >>> ]
+ >>> operator_list = [
+ >>> OperatorSpecEditMode("delete_edgeloop", {}, "EDGE", MONKEY_LOOP_EDGE),
+ >>> ]
>>> tests = [
- >>> ['FACE', {0, 1, 2, 3, 4, 5}, 'Cubecube', 'Cubecube_result_1', 'intersect_boolean', {'operation': 'UNION'}],
- >>> ['FACE', {0, 1, 2, 3, 4, 5}, 'Cubecube', 'Cubecube_result_2', 'intersect_boolean', {'operation': 'INTERSECT'}],
+ >>> MeshTest("Test1", "testCube", "expectedCube", modifier_list),
+ >>> MeshTest("Test2", "testCube_2", "expectedCube_2", modifier_list),
+ >>> MeshTest("MonkeyDeleteEdge", "testMonkey","expectedMonkey", operator_list)
>>> ]
- >>> operator_test = OperatorTest(tests)
- >>> operator_test.run_all_tests()
+ >>> modifiers_test = RunTest(tests)
+ >>> modifiers_test.run_all_tests()
"""
- def __init__(self, operator_tests):
+ def __init__(self, tests, apply_modifiers=False, do_compare=False):
"""
- Constructs an operator test.
- :param operator_tests: list - list of operator test cases. Each element in the list must contain the following
+ Construct a modifier test.
+ :param tests: list - list of modifier or operator test cases. Each element in the list must contain the
+ following
in the correct order:
- 1) select_mode: str - mesh selection mode, must be either 'VERT', 'EDGE' or 'FACE'
- 2) selection: set - set of vertices/edges/faces indices to select, e.g. [0, 9, 10].
- 3) test_object_name: bpy.Types.Object - test object
- 4) expected_object_name: bpy.Types.Object - expected object
- 5) operator_name: str - name of mesh operator from bpy.ops.mesh, e.g. "bevel" or "fill"
- 6) operator_parameters: dict - {name : val} dictionary containing operator parameters.
- """
- self.operator_tests = operator_tests
+ 0) test_name: str - unique test name
+ 1) test_object_name: bpy.Types.Object - test object
+ 2) expected_object_name: bpy.Types.Object - expected object
+ 3) modifiers or operators: list - list of mesh_test.ModifierSpec objects or
+ mesh_test.OperatorSpecEditMode objects
+ """
+ self.tests = tests
+ self._ensure_unique_test_name_or_raise_error()
+ self.apply_modifiers = apply_modifiers
+ self.do_compare = do_compare
self.verbose = os.environ.get("BLENDER_VERBOSE") is not None
self._failed_tests_list = []
- def run_test(self, index: int):
+ def _ensure_unique_test_name_or_raise_error(self):
"""
- Run a single test from operator_tests list
- :param index: int - index of test
- :return: bool - True if test is successful. False otherwise.
+ Check if the test name is unique else raise an error.
"""
- case = self.operator_tests[index]
- if len(case) != 6:
- raise ValueError("Expected exactly 6 parameters for each test case, got {}".format(len(case)))
- select_mode = case[0]
- selection = case[1]
- test_object_name = case[2]
- expected_object_name = case[3]
- operator_name = case[4]
- operator_parameters = case[5]
-
- operator_spec = OperatorSpec(operator_name, operator_parameters, select_mode, selection)
+ all_test_names = []
+ for each_test in self.tests:
+ test_name = each_test.test_name
+ all_test_names.append(test_name)
- test = MeshTest(test_object_name, expected_object_name)
- test.add_operator(operator_spec)
-
- success = test.run_test()
- if test.is_test_updated():
- # Run the test again if the blend file has been updated.
- success = test.run_test()
- return success
+ seen_name = set()
+ for ele in all_test_names:
+ if ele in seen_name:
+ raise ValueError("{} is a duplicate, write a new unique name.".format(ele))
+ else:
+ seen_name.add(ele)
def run_all_tests(self):
- for index, _ in enumerate(self.operator_tests):
+ """
+ Run all tests in self.tests list. Raises an exception if one the tests fails.
+ """
+ for test_number, each_test in enumerate(self.tests):
+ test_name = each_test.test_name
if self.verbose:
print()
- print("Running test {}...".format(index))
- success = self.run_test(index)
+ print("Running test {}...".format(test_number))
+ print("Test name {}\n".format(test_name))
+ success = self.run_test(test_name)
if not success:
- self._failed_tests_list.append(index)
+ self._failed_tests_list.append(test_name)
if len(self._failed_tests_list) != 0:
- print("Following tests failed: {}".format(self._failed_tests_list))
+ print("\nFollowing tests failed: {}".format(self._failed_tests_list))
blender_path = bpy.app.binary_path
blend_path = bpy.data.filepath
@@ -493,63 +680,31 @@ class OperatorTest:
print("Run following command to open Blender and run the failing test:")
print("{} {} --python {} -- {} {}"
- .format(blender_path, blend_path, python_path, "--run-test", "<test_index>"))
+ .format(blender_path, blend_path, python_path, "--run-test", "<test_name>"))
raise Exception("Tests {} failed".format(self._failed_tests_list))
-
-class ModifierTest:
- """
- Helper class that stores and executes modifier tests.
-
- Example usage:
-
- >>> modifier_list = [
- >>> ModifierSpec("firstSUBSURF", "SUBSURF", {"quality": 5}),
- >>> ModifierSpec("firstSOLIDIFY", "SOLIDIFY", {"thickness_clamp": 0.9, "thickness": 1})
- >>> ]
- >>> tests = [
- >>> ["testCube", "expectedCube", modifier_list],
- >>> ["testCube_2", "expectedCube_2", modifier_list]
- >>> ]
- >>> modifiers_test = ModifierTest(tests)
- >>> modifiers_test.run_all_tests()
- """
-
- def __init__(self, modifier_tests: list, apply_modifiers=False, threshold=None):
+ def run_test(self, test_name: str):
"""
- Construct a modifier test.
- :param modifier_tests: list - list of modifier test cases. Each element in the list must contain the following
- in the correct order:
- 1) test_object_name: bpy.Types.Object - test object
- 2) expected_object_name: bpy.Types.Object - expected object
- 3) modifiers: list - list of mesh_test.ModifierSpec objects.
- """
- self.modifier_tests = modifier_tests
- self.apply_modifiers = apply_modifiers
- self.threshold = threshold
- self.verbose = os.environ.get("BLENDER_VERBOSE") is not None
- self._failed_tests_list = []
-
- def run_test(self, index: int):
- """
- Run a single test from self.modifier_tests list
- :param index: int - index of test
+ Run a single test from self.tests list
+ :param test_name: int - name of test
:return: bool - True if test passed, False otherwise.
"""
- case = self.modifier_tests[index]
- if len(case) != 3:
- raise ValueError("Expected exactly 3 parameters for each test case, got {}".format(len(case)))
- test_object_name = case[0]
- expected_object_name = case[1]
- spec_list = case[2]
+ case = None
+ for index, each_test in enumerate(self.tests):
+ if test_name == each_test.test_name:
+ case = self.tests[index]
+ break
+
+ if case is None:
+ raise Exception('No test called {} found!'.format(test_name))
- test = MeshTest(test_object_name, expected_object_name, threshold=self.threshold)
+ test = case
if self.apply_modifiers:
test.apply_modifier = True
- for modifier_spec in spec_list:
- test.add_modifier(modifier_spec)
+ if self.do_compare:
+ test.do_compare = True
success = test.run_test()
if test.is_test_updated():
@@ -557,31 +712,3 @@ class ModifierTest:
success = test.run_test()
return success
-
- def run_all_tests(self):
- """
- Run all tests in self.modifiers_tests list. Raises an exception if one the tests fails.
- """
- for index, _ in enumerate(self.modifier_tests):
- if self.verbose:
- print()
- print("Running test {}...\n".format(index))
- success = self.run_test(index)
-
- if not success:
- self._failed_tests_list.append(index)
-
- if len(self._failed_tests_list) != 0:
- print("Following tests failed: {}".format(self._failed_tests_list))
-
- blender_path = bpy.app.binary_path
- blend_path = bpy.data.filepath
- frame = inspect.stack()[1]
- module = inspect.getmodule(frame[0])
- python_path = module.__file__
-
- print("Run following command to open Blender and run the failing test:")
- print("{} {} --python {} -- {} {}"
- .format(blender_path, blend_path, python_path, "--run-test", "<test_index>"))
-
- raise Exception("Tests {} failed".format(self._failed_tests_list))