PyAPI: don't adjust prefs when an fails to load
[blender.git] / tests / python / bl_run_operators.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8 compliant>
20
21 # semi-useful script, runs all operators in a number of different
22 # contexts, cheap way to find misc small bugs but is in no way a complete test.
23 #
24 # only error checked for here is a segfault.
25
26 import bpy
27 import sys
28
29 USE_ATTRSET = False
30 USE_FILES = ""  # "/mango/"
31 USE_RANDOM = False
32 USE_RANDOM_SCREEN = False
33 RANDOM_SEED = [1]  # so we can redo crashes
34 RANDOM_RESET = 0.1  # 10% chance of resetting on each new operator
35 RANDOM_MULTIPLY = 10
36
37
38 op_blacklist = (
39     "script.reload",
40     "export*.*",
41     "import*.*",
42     "*.save_*",
43     "*.read_*",
44     "*.open_*",
45     "*.link_append",
46     "render.render",
47     "render.play_rendered_anim",
48     "sound.bake_animation",    # OK but slow
49     "sound.mixdown",           # OK but slow
50     "object.bake_image",       # OK but slow
51     "object.paths_calculate",  # OK but slow
52     "object.paths_update",     # OK but slow
53     "ptcache.bake_all",        # OK but slow
54     "nla.bake",                # OK but slow
55     "*.*_export",
56     "*.*_import",
57     "ed.undo",
58     "ed.undo_push",
59     "script.autoexec_warn_clear",
60     "screen.delete",           # already used for random screens
61     "wm.blenderplayer_start",
62     "wm.recover_auto_save",
63     "wm.quit_blender",
64     "wm.url_open",
65     "wm.doc_view",
66     "wm.doc_edit",
67     "wm.doc_view_manual",
68     "wm.path_open",
69     "wm.copy_prev_settings",
70     "wm.theme_install",
71     "wm.context_*",
72     "wm.properties_add",
73     "wm.properties_remove",
74     "wm.properties_edit",
75     "wm.properties_context_change",
76     "wm.operator_cheat_sheet",
77     "wm.interface_theme_*",
78     "wm.appconfig_*",           # just annoying - but harmless
79     "wm.keyitem_add",           # just annoying - but harmless
80     "wm.keyconfig_activate",    # just annoying - but harmless
81     "wm.keyconfig_preset_add",  # just annoying - but harmless
82     "wm.keyconfig_test",        # just annoying - but harmless
83     "wm.memory_statistics",     # another annoying one
84     "wm.dependency_relations",  # another annoying one
85     "wm.keymap_restore",        # another annoying one
86     "wm.addon_*",               # harmless, but dont change state
87     "console.*",                # just annoying - but harmless
88     )
89
90
91 def blend_list(mainpath):
92     import os
93     from os.path import join, splitext
94
95     def file_list(path, filename_check=None):
96         for dirpath, dirnames, filenames in os.walk(path):
97
98             # skip '.svn'
99             if dirpath.startswith("."):
100                 continue
101
102             for filename in filenames:
103                 filepath = join(dirpath, filename)
104                 if filename_check is None or filename_check(filepath):
105                     yield filepath
106
107     def is_blend(filename):
108         ext = splitext(filename)[1]
109         return (ext in {".blend", })
110
111     return list(sorted(file_list(mainpath, is_blend)))
112
113 if USE_FILES:
114     USE_FILES_LS = blend_list(USE_FILES)
115     # print(USE_FILES_LS)
116
117
118 def filter_op_list(operators):
119     from fnmatch import fnmatchcase
120
121     def is_op_ok(op):
122         for op_match in op_blacklist:
123             if fnmatchcase(op, op_match):
124                 print("    skipping: %s (%s)" % (op, op_match))
125                 return False
126         return True
127
128     operators[:] = [op for op in operators if is_op_ok(op[0])]
129
130
131 def reset_blend():
132     bpy.ops.wm.read_factory_settings()
133     for scene in bpy.data.scenes:
134         # reduce range so any bake action doesnt take too long
135         scene.frame_start = 1
136         scene.frame_end = 5
137
138     if USE_RANDOM_SCREEN:
139         import random
140         for i in range(random.randint(0, len(bpy.data.screens))):
141             bpy.ops.screen.delete()
142         print("Scree IS", bpy.context.screen)
143
144
145 def reset_file():
146     import random
147     f = USE_FILES_LS[random.randint(0, len(USE_FILES_LS) - 1)]
148     bpy.ops.wm.open_mainfile(filepath=f)
149
150
151 if USE_ATTRSET:
152     def build_property_typemap(skip_classes):
153
154         property_typemap = {}
155
156         for attr in dir(bpy.types):
157             cls = getattr(bpy.types, attr)
158             if issubclass(cls, skip_classes):
159                 continue
160
161             ## to support skip-save we cant get all props
162             # properties = cls.bl_rna.properties.keys()
163             properties = []
164             for prop_id, prop in cls.bl_rna.properties.items():
165                 if not prop.is_skip_save:
166                     properties.append(prop_id)
167
168             properties.remove("rna_type")
169             property_typemap[attr] = properties
170
171         return property_typemap
172     CLS_BLACKLIST = (
173         bpy.types.BrushTextureSlot,
174         bpy.types.Brush,
175         )
176     property_typemap = build_property_typemap(CLS_BLACKLIST)
177     bpy_struct_type = bpy.types.Struct.__base__
178
179     def id_walk(value, parent):
180         value_type = type(value)
181         value_type_name = value_type.__name__
182
183         value_id = getattr(value, "id_data", Ellipsis)
184         value_props = property_typemap.get(value_type_name, ())
185
186         for prop in value_props:
187             subvalue = getattr(value, prop)
188
189             if subvalue == parent:
190                 continue
191             # grr, recursive!
192             if prop == "point_caches":
193                 continue
194             subvalue_type = type(subvalue)
195             yield value, prop, subvalue_type
196             subvalue_id = getattr(subvalue, "id_data", Ellipsis)
197
198             if value_id == subvalue_id:
199                 if subvalue_type == float:
200                     pass
201                 elif subvalue_type == int:
202                     pass
203                 elif subvalue_type == bool:
204                     pass
205                 elif subvalue_type == str:
206                     pass
207                 elif hasattr(subvalue, "__len__"):
208                     for sub_item in subvalue[:]:
209                         if isinstance(sub_item, bpy_struct_type):
210                             subitem_id = getattr(sub_item, "id_data", Ellipsis)
211                             if subitem_id == subvalue_id:
212                                 yield from id_walk(sub_item, value)
213
214                 if subvalue_type.__name__ in property_typemap:
215                     yield from id_walk(subvalue, value)
216
217     # main function
218     _random_values = (
219         None, object, type,
220         1, 0.1, -1,  # float("nan"),
221         "", "test", b"", b"test",
222         (), [], {},
223         (10,), (10, 20), (0, 0, 0),
224         {0: "", 1: "hello", 2: "test"}, {"": 0, "hello": 1, "test": 2},
225         set(), {"", "test", "."}, {None, ..., type},
226         range(10), (" " * i for i in range(10)),
227         )
228
229     def attrset_data():
230         for attr in dir(bpy.data):
231             if attr == "window_managers":
232                 continue
233             seq = getattr(bpy.data, attr)
234             if seq.__class__.__name__ == 'bpy_prop_collection':
235                 for id_data in seq:
236                     for val, prop, tp in id_walk(id_data, bpy.data):
237                         # print(id_data)
238                         for val_rnd in _random_values:
239                             try:
240                                 setattr(val, prop, val_rnd)
241                             except:
242                                 pass
243
244
245 def run_ops(operators, setup_func=None, reset=True):
246     print("\ncontext:", setup_func.__name__)
247
248     # first invoke
249     for op_id, op in operators:
250         if op.poll():
251             print("    operator:", op_id)
252             sys.stdout.flush()  # in case of crash
253
254             # disable will get blender in a bad state and crash easy!
255             if reset:
256                 reset_test = True
257                 if USE_RANDOM:
258                     import random
259                     if random.random() < (1.0 - RANDOM_RESET):
260                         reset_test = False
261
262                 if reset_test:
263                     if USE_FILES:
264                         reset_file()
265                     else:
266                         reset_blend()
267                 del reset_test
268
269             if USE_RANDOM:
270                 # we can't be sure it will work
271                 try:
272                     setup_func()
273                 except:
274                     pass
275             else:
276                 setup_func()
277
278             for mode in {'EXEC_DEFAULT', 'INVOKE_DEFAULT'}:
279                 try:
280                     op(mode)
281                 except:
282                     #import traceback
283                     #traceback.print_exc()
284                     pass
285
286                 if USE_ATTRSET:
287                     attrset_data()
288
289     if not operators:
290         # run test
291         if reset:
292             reset_blend()
293         if USE_RANDOM:
294             # we can't be sure it will work
295             try:
296                 setup_func()
297             except:
298                 pass
299         else:
300             setup_func()
301
302
303 # contexts
304 def ctx_clear_scene():  # copied from batch_import.py
305     unique_obs = set()
306     for scene in bpy.data.scenes:
307         for obj in scene.objects[:]:
308             scene.objects.unlink(obj)
309             unique_obs.add(obj)
310
311     # remove obdata, for now only worry about the startup scene
312     for bpy_data_iter in (bpy.data.objects, bpy.data.meshes, bpy.data.lamps, bpy.data.cameras):
313         for id_data in bpy_data_iter:
314             bpy_data_iter.remove(id_data)
315
316
317 def ctx_editmode_mesh():
318     bpy.ops.object.mode_set(mode='EDIT')
319
320
321 def ctx_editmode_mesh_extra():
322     bpy.ops.object.vertex_group_add()
323     bpy.ops.object.shape_key_add(from_mix=False)
324     bpy.ops.object.shape_key_add(from_mix=True)
325     bpy.ops.mesh.uv_texture_add()
326     bpy.ops.mesh.vertex_color_add()
327     bpy.ops.object.material_slot_add()
328     # editmode last!
329     bpy.ops.object.mode_set(mode='EDIT')
330
331
332 def ctx_editmode_mesh_empty():
333     bpy.ops.object.mode_set(mode='EDIT')
334     bpy.ops.mesh.select_all(action='SELECT')
335     bpy.ops.mesh.delete()
336
337
338 def ctx_editmode_curves():
339     bpy.ops.curve.primitive_nurbs_circle_add()
340     bpy.ops.object.mode_set(mode='EDIT')
341
342
343 def ctx_editmode_curves_empty():
344     bpy.ops.curve.primitive_nurbs_circle_add()
345     bpy.ops.object.mode_set(mode='EDIT')
346     bpy.ops.curve.select_all(action='SELECT')
347     bpy.ops.curve.delete(type='VERT')
348
349
350 def ctx_editmode_surface():
351     bpy.ops.surface.primitive_nurbs_surface_torus_add()
352     bpy.ops.object.mode_set(mode='EDIT')
353
354
355 def ctx_editmode_mball():
356     bpy.ops.object.metaball_add()
357     bpy.ops.object.mode_set(mode='EDIT')
358
359
360 def ctx_editmode_text():
361     bpy.ops.object.text_add()
362     bpy.ops.object.mode_set(mode='EDIT')
363
364
365 def ctx_editmode_armature():
366     bpy.ops.object.armature_add()
367     bpy.ops.object.mode_set(mode='EDIT')
368
369
370 def ctx_editmode_armature_empty():
371     bpy.ops.object.armature_add()
372     bpy.ops.object.mode_set(mode='EDIT')
373     bpy.ops.armature.select_all(action='SELECT')
374     bpy.ops.armature.delete()
375
376
377 def ctx_editmode_lattice():
378     bpy.ops.object.add(type='LATTICE')
379     bpy.ops.object.mode_set(mode='EDIT')
380     # bpy.ops.object.vertex_group_add()
381
382
383 def ctx_object_empty():
384     bpy.ops.object.add(type='EMPTY')
385
386
387 def ctx_object_pose():
388     bpy.ops.object.armature_add()
389     bpy.ops.object.mode_set(mode='POSE')
390     bpy.ops.pose.select_all(action='SELECT')
391
392
393 def ctx_object_paint_weight():
394     bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
395
396
397 def ctx_object_paint_vertex():
398     bpy.ops.object.mode_set(mode='VERTEX_PAINT')
399
400
401 def ctx_object_paint_sculpt():
402     bpy.ops.object.mode_set(mode='SCULPT')
403
404
405 def ctx_object_paint_texture():
406     bpy.ops.object.mode_set(mode='TEXTURE_PAINT')
407
408
409 def bpy_check_type_duplicates():
410     # non essential sanity check
411     bl_types = dir(bpy.types)
412     bl_types_unique = set(bl_types)
413
414     if len(bl_types) != len(bl_types_unique):
415         print("Error, found duplicates in 'bpy.types'")
416         for t in sorted(bl_types_unique):
417             tot = bl_types.count(t)
418             if tot > 1:
419                 print("    '%s', %d" % (t, tot))
420         import sys
421         sys.exit(1)
422
423
424 def main():
425
426     bpy_check_type_duplicates()
427
428     # reset_blend()
429     import bpy
430     operators = []
431     for mod_name in dir(bpy.ops):
432         mod = getattr(bpy.ops, mod_name)
433         for submod_name in dir(mod):
434             op = getattr(mod, submod_name)
435             operators.append(("%s.%s" % (mod_name, submod_name), op))
436
437     operators.sort(key=lambda op: op[0])
438
439     filter_op_list(operators)
440
441     # for testing, mix the list up.
442     #operators.reverse()
443
444     if USE_RANDOM:
445         import random
446         random.seed(RANDOM_SEED[0])
447         operators = operators * RANDOM_MULTIPLY
448         random.shuffle(operators)
449
450     # 2 passes, first just run setup_func to make sure they are ok
451     for operators_test in ((), operators):
452         # Run the operator tests in different contexts
453         run_ops(operators_test, setup_func=lambda: None)
454
455         if USE_FILES:
456             continue
457
458         run_ops(operators_test, setup_func=ctx_clear_scene)
459         # object modes
460         run_ops(operators_test, setup_func=ctx_object_empty)
461         run_ops(operators_test, setup_func=ctx_object_pose)
462         run_ops(operators_test, setup_func=ctx_object_paint_weight)
463         run_ops(operators_test, setup_func=ctx_object_paint_vertex)
464         run_ops(operators_test, setup_func=ctx_object_paint_sculpt)
465         run_ops(operators_test, setup_func=ctx_object_paint_texture)
466         # mesh
467         run_ops(operators_test, setup_func=ctx_editmode_mesh)
468         run_ops(operators_test, setup_func=ctx_editmode_mesh_extra)
469         run_ops(operators_test, setup_func=ctx_editmode_mesh_empty)
470         # armature
471         run_ops(operators_test, setup_func=ctx_editmode_armature)
472         run_ops(operators_test, setup_func=ctx_editmode_armature_empty)
473         # curves
474         run_ops(operators_test, setup_func=ctx_editmode_curves)
475         run_ops(operators_test, setup_func=ctx_editmode_curves_empty)
476         run_ops(operators_test, setup_func=ctx_editmode_surface)
477         # other
478         run_ops(operators_test, setup_func=ctx_editmode_mball)
479         run_ops(operators_test, setup_func=ctx_editmode_text)
480         run_ops(operators_test, setup_func=ctx_editmode_lattice)
481
482         if not operators_test:
483             print("All setup functions run fine!")
484
485     print("Finished %r" % __file__)
486
487 if __name__ == "__main__":
488     #~ for i in range(200):
489         #~ RANDOM_SEED[0] += 1
490         #~ main()
491     main()