diff options
author | Campbell Barton <ideasman42@gmail.com> | 2014-06-18 15:44:40 +0400 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2014-06-18 16:03:46 +0400 |
commit | 0eb060c7b4fb3a85b14c9efca85a7a361640a95e (patch) | |
tree | 87e50d4913eb210f7d8aa6ab2f62136e42aa6c11 /tests/python/bl_run_operators.py | |
parent | 7259ac821ea73bdde3add0390dfc1137f619bc9a (diff) |
Move tests into tests/ top-level dir
Diffstat (limited to 'tests/python/bl_run_operators.py')
-rw-r--r-- | tests/python/bl_run_operators.py | 490 |
1 files changed, 490 insertions, 0 deletions
diff --git a/tests/python/bl_run_operators.py b/tests/python/bl_run_operators.py new file mode 100644 index 00000000000..e14b0ce6d32 --- /dev/null +++ b/tests/python/bl_run_operators.py @@ -0,0 +1,490 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# <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. +# +# only error checked for here is a segfault. + +import bpy +import sys + +USE_ATTRSET = False +USE_FILES = "" # "/mango/" +USE_RANDOM = False +USE_RANDOM_SCREEN = False +RANDOM_SEED = [1] # so we can redo crashes +RANDOM_RESET = 0.1 # 10% chance of resetting on each new operator +RANDOM_MULTIPLY = 10 + + +op_blacklist = ( + "script.reload", + "export*.*", + "import*.*", + "*.save_*", + "*.read_*", + "*.open_*", + "*.link_append", + "render.render", + "render.play_rendered_anim", + "sound.bake_animation", # OK but slow + "sound.mixdown", # OK but slow + "object.bake_image", # OK but slow + "object.paths_calculate", # OK but slow + "object.paths_update", # OK but slow + "ptcache.bake_all", # OK but slow + "nla.bake", # OK but slow + "*.*_export", + "*.*_import", + "ed.undo", + "ed.undo_push", + "script.autoexec_warn_clear", + "screen.delete", # already used for random screens + "wm.blenderplayer_start", + "wm.recover_auto_save", + "wm.quit_blender", + "wm.url_open", + "wm.doc_view", + "wm.doc_edit", + "wm.doc_view_manual", + "wm.path_open", + "wm.theme_install", + "wm.context_*", + "wm.properties_add", + "wm.properties_remove", + "wm.properties_edit", + "wm.properties_context_change", + "wm.operator_cheat_sheet", + "wm.interface_theme_*", + "wm.appconfig_*", # just annoying - but harmless + "wm.keyitem_add", # just annoying - but harmless + "wm.keyconfig_activate", # just annoying - but harmless + "wm.keyconfig_preset_add", # just annoying - but harmless + "wm.keyconfig_test", # just annoying - but harmless + "wm.memory_statistics", # another annoying one + "wm.dependency_relations", # another annoying one + "wm.keymap_restore", # another annoying one + "wm.addon_*", # harmless, but dont change state + "console.*", # just annoying - but harmless + ) + + +def blend_list(mainpath): + import os + from os.path import join, splitext + + def file_list(path, filename_check=None): + for dirpath, dirnames, filenames in os.walk(path): + + # skip '.svn' + if dirpath.startswith("."): + continue + + for filename in filenames: + filepath = join(dirpath, filename) + if filename_check is None or filename_check(filepath): + yield filepath + + def is_blend(filename): + ext = splitext(filename)[1] + return (ext in {".blend", }) + + return list(sorted(file_list(mainpath, is_blend))) + +if USE_FILES: + USE_FILES_LS = blend_list(USE_FILES) + # print(USE_FILES_LS) + + +def filter_op_list(operators): + from fnmatch import fnmatchcase + + def is_op_ok(op): + for op_match in op_blacklist: + if fnmatchcase(op, op_match): + print(" skipping: %s (%s)" % (op, op_match)) + return False + return True + + operators[:] = [op for op in operators if is_op_ok(op[0])] + + +def reset_blend(): + bpy.ops.wm.read_factory_settings() + for scene in bpy.data.scenes: + # reduce range so any bake action doesnt take too long + scene.frame_start = 1 + scene.frame_end = 5 + + if USE_RANDOM_SCREEN: + import random + for i in range(random.randint(0, len(bpy.data.screens))): + bpy.ops.screen.delete() + print("Scree IS", bpy.context.screen) + + +def reset_file(): + import random + f = USE_FILES_LS[random.randint(0, len(USE_FILES_LS) - 1)] + bpy.ops.wm.open_mainfile(filepath=f) + + +if USE_ATTRSET: + def build_property_typemap(skip_classes): + + property_typemap = {} + + for attr in dir(bpy.types): + cls = getattr(bpy.types, attr) + if issubclass(cls, skip_classes): + continue + + ## to support skip-save we cant get all props + # properties = cls.bl_rna.properties.keys() + properties = [] + for prop_id, prop in cls.bl_rna.properties.items(): + if not prop.is_skip_save: + properties.append(prop_id) + + properties.remove("rna_type") + property_typemap[attr] = properties + + return property_typemap + CLS_BLACKLIST = ( + bpy.types.BrushTextureSlot, + bpy.types.Brush, + ) + property_typemap = build_property_typemap(CLS_BLACKLIST) + bpy_struct_type = bpy.types.Struct.__base__ + + def id_walk(value, parent): + value_type = type(value) + value_type_name = value_type.__name__ + + value_id = getattr(value, "id_data", Ellipsis) + value_props = property_typemap.get(value_type_name, ()) + + for prop in value_props: + subvalue = getattr(value, prop) + + if subvalue == parent: + continue + # grr, recursive! + if prop == "point_caches": + continue + subvalue_type = type(subvalue) + yield value, prop, subvalue_type + subvalue_id = getattr(subvalue, "id_data", Ellipsis) + + if value_id == subvalue_id: + if subvalue_type == float: + pass + elif subvalue_type == int: + pass + elif subvalue_type == bool: + pass + elif subvalue_type == str: + pass + elif hasattr(subvalue, "__len__"): + for sub_item in subvalue[:]: + if isinstance(sub_item, bpy_struct_type): + subitem_id = getattr(sub_item, "id_data", Ellipsis) + if subitem_id == subvalue_id: + yield from id_walk(sub_item, value) + + if subvalue_type.__name__ in property_typemap: + yield from id_walk(subvalue, value) + + # main function + _random_values = ( + None, object, type, + 1, 0.1, -1, # float("nan"), + "", "test", b"", b"test", + (), [], {}, + (10,), (10, 20), (0, 0, 0), + {0: "", 1: "hello", 2: "test"}, {"": 0, "hello": 1, "test": 2}, + set(), {"", "test", "."}, {None, ..., type}, + range(10), (" " * i for i in range(10)), + ) + + def attrset_data(): + for attr in dir(bpy.data): + if attr == "window_managers": + continue + seq = getattr(bpy.data, attr) + if seq.__class__.__name__ == 'bpy_prop_collection': + for id_data in seq: + for val, prop, tp in id_walk(id_data, bpy.data): + # print(id_data) + for val_rnd in _random_values: + try: + setattr(val, prop, val_rnd) + except: + pass + + +def run_ops(operators, setup_func=None, reset=True): + print("\ncontext:", setup_func.__name__) + + # first invoke + for op_id, op in operators: + if op.poll(): + print(" operator:", op_id) + sys.stdout.flush() # in case of crash + + # disable will get blender in a bad state and crash easy! + if reset: + reset_test = True + if USE_RANDOM: + import random + if random.random() < (1.0 - RANDOM_RESET): + reset_test = False + + if reset_test: + if USE_FILES: + reset_file() + else: + reset_blend() + del reset_test + + if USE_RANDOM: + # we can't be sure it will work + try: + setup_func() + except: + pass + else: + setup_func() + + for mode in {'EXEC_DEFAULT', 'INVOKE_DEFAULT'}: + try: + op(mode) + except: + #import traceback + #traceback.print_exc() + pass + + if USE_ATTRSET: + attrset_data() + + if not operators: + # run test + if reset: + reset_blend() + if USE_RANDOM: + # we can't be sure it will work + try: + setup_func() + except: + pass + else: + setup_func() + + +# contexts +def ctx_clear_scene(): # copied from batch_import.py + unique_obs = set() + for scene in bpy.data.scenes: + for obj in scene.objects[:]: + scene.objects.unlink(obj) + unique_obs.add(obj) + + # remove obdata, for now only worry about the startup scene + for bpy_data_iter in (bpy.data.objects, bpy.data.meshes, bpy.data.lamps, bpy.data.cameras): + for id_data in bpy_data_iter: + bpy_data_iter.remove(id_data) + + +def ctx_editmode_mesh(): + bpy.ops.object.mode_set(mode='EDIT') + + +def ctx_editmode_mesh_extra(): + bpy.ops.object.vertex_group_add() + 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') + + +def ctx_editmode_mesh_empty(): + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.delete() + + +def ctx_editmode_curves(): + bpy.ops.curve.primitive_nurbs_circle_add() + bpy.ops.object.mode_set(mode='EDIT') + + +def ctx_editmode_curves_empty(): + bpy.ops.curve.primitive_nurbs_circle_add() + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.curve.select_all(action='SELECT') + bpy.ops.curve.delete(type='VERT') + + +def ctx_editmode_surface(): + bpy.ops.surface.primitive_nurbs_surface_torus_add() + bpy.ops.object.mode_set(mode='EDIT') + + +def ctx_editmode_mball(): + bpy.ops.object.metaball_add() + bpy.ops.object.mode_set(mode='EDIT') + + +def ctx_editmode_text(): + bpy.ops.object.text_add() + bpy.ops.object.mode_set(mode='EDIT') + + +def ctx_editmode_armature(): + bpy.ops.object.armature_add() + bpy.ops.object.mode_set(mode='EDIT') + + +def ctx_editmode_armature_empty(): + bpy.ops.object.armature_add() + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.armature.select_all(action='SELECT') + bpy.ops.armature.delete() + + +def ctx_editmode_lattice(): + bpy.ops.object.add(type='LATTICE') + bpy.ops.object.mode_set(mode='EDIT') + # bpy.ops.object.vertex_group_add() + + +def ctx_object_empty(): + bpy.ops.object.add(type='EMPTY') + + +def ctx_object_pose(): + bpy.ops.object.armature_add() + bpy.ops.object.mode_set(mode='POSE') + bpy.ops.pose.select_all(action='SELECT') + + +def ctx_object_paint_weight(): + bpy.ops.object.mode_set(mode='WEIGHT_PAINT') + + +def ctx_object_paint_vertex(): + bpy.ops.object.mode_set(mode='VERTEX_PAINT') + + +def ctx_object_paint_sculpt(): + bpy.ops.object.mode_set(mode='SCULPT') + + +def ctx_object_paint_texture(): + bpy.ops.object.mode_set(mode='TEXTURE_PAINT') + + +def bpy_check_type_duplicates(): + # non essential sanity check + bl_types = dir(bpy.types) + bl_types_unique = set(bl_types) + + if len(bl_types) != len(bl_types_unique): + print("Error, found duplicates in 'bpy.types'") + for t in sorted(bl_types_unique): + tot = bl_types.count(t) + if tot > 1: + print(" '%s', %d" % (t, tot)) + import sys + sys.exit(1) + + +def main(): + + bpy_check_type_duplicates() + + # reset_blend() + import bpy + operators = [] + for mod_name in dir(bpy.ops): + mod = getattr(bpy.ops, mod_name) + for submod_name in dir(mod): + op = getattr(mod, submod_name) + operators.append(("%s.%s" % (mod_name, submod_name), op)) + + operators.sort(key=lambda op: op[0]) + + filter_op_list(operators) + + # for testing, mix the list up. + #operators.reverse() + + if USE_RANDOM: + import random + random.seed(RANDOM_SEED[0]) + operators = operators * RANDOM_MULTIPLY + random.shuffle(operators) + + # 2 passes, first just run setup_func to make sure they are ok + for operators_test in ((), operators): + # Run the operator tests in different contexts + run_ops(operators_test, setup_func=lambda: None) + + if USE_FILES: + continue + + run_ops(operators_test, setup_func=ctx_clear_scene) + # object modes + run_ops(operators_test, setup_func=ctx_object_empty) + run_ops(operators_test, setup_func=ctx_object_pose) + run_ops(operators_test, setup_func=ctx_object_paint_weight) + run_ops(operators_test, setup_func=ctx_object_paint_vertex) + run_ops(operators_test, setup_func=ctx_object_paint_sculpt) + run_ops(operators_test, setup_func=ctx_object_paint_texture) + # mesh + run_ops(operators_test, setup_func=ctx_editmode_mesh) + run_ops(operators_test, setup_func=ctx_editmode_mesh_extra) + run_ops(operators_test, setup_func=ctx_editmode_mesh_empty) + # armature + run_ops(operators_test, setup_func=ctx_editmode_armature) + run_ops(operators_test, setup_func=ctx_editmode_armature_empty) + # curves + run_ops(operators_test, setup_func=ctx_editmode_curves) + run_ops(operators_test, setup_func=ctx_editmode_curves_empty) + run_ops(operators_test, setup_func=ctx_editmode_surface) + # other + run_ops(operators_test, setup_func=ctx_editmode_mball) + run_ops(operators_test, setup_func=ctx_editmode_text) + run_ops(operators_test, setup_func=ctx_editmode_lattice) + + if not operators_test: + print("All setup functions run fine!") + + print("Finished %r" % __file__) + +if __name__ == "__main__": + #~ for i in range(200): + #~ RANDOM_SEED[0] += 1 + #~ main() + main() |