Cleanup: pep8
[blender.git] / tests / python / bl_load_py_modules.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 # simple script to enable all addons, and disable
22
23 """
24 ./blender.bin --background -noaudio --factory-startup --python tests/python/bl_load_py_modules.py
25 """
26
27 import bpy
28 import addon_utils
29
30 import sys
31 import os
32
33 BLACKLIST = {
34     "bl_i18n_utils",
35     "bl_previews_utils",
36     "cycles",
37     "io_export_dxf",  # TODO, check on why this fails
38     'io_import_dxf',  # Because of cydxfentity.so dependency
39
40     # The unpacked wheel is only loaded when actually used, not directly on import:
41     os.path.join("io_blend_utils", "blender_bam-unpacked.whl"),
42 }
43
44 # Some modules need to add to the `sys.path`.
45 MODULE_SYS_PATHS = {
46     # Runs in a Python subprocess, so its expected its basedir can be imported.
47     "io_blend_utils.blendfile_pack": ".",
48 }
49
50 if not bpy.app.build_options.freestyle:
51     BLACKLIST.add("render_freestyle_svg")
52
53 BLACKLIST_DIRS = (
54     os.path.join(bpy.utils.resource_path('USER'), "scripts"),
55 ) + tuple(addon_utils.paths()[1:])
56
57
58 def module_names_recursive(mod_dir, *, parent=None):
59     """
60     a version of bpy.path.module_names that includes non-packages
61     """
62
63     is_package = os.path.exists(os.path.join(mod_dir, "__init__.py"))
64
65     for n in os.listdir(mod_dir):
66         if not n.startswith((".", "_")):
67             submod_full = os.path.join(mod_dir, n)
68             if os.path.isdir(submod_full):
69                 if not parent:
70                     subparent = n
71                 else:
72                     subparent = parent + "." + n
73                 yield from module_names_recursive(submod_full, parent=subparent)
74             elif n.endswith(".py") and is_package is False:
75                 submod = n[:-3]
76                 if parent:
77                     submod = parent + "." + submod
78                 yield submod, submod_full
79
80
81 def module_names_all(mod_dir):
82     yield from bpy.path.module_names(mod_dir)
83     yield from module_names_recursive(mod_dir)
84
85
86 def addon_modules_sorted():
87     modules = addon_utils.modules({})
88     modules[:] = [mod for mod in modules if not mod.__file__.startswith(BLACKLIST_DIRS)]
89     modules.sort(key=lambda mod: mod.__name__)
90     return modules
91
92
93 def source_list(path, filename_check=None):
94     from os.path import join
95     for dirpath, dirnames, filenames in os.walk(path):
96         # skip '.git'
97         dirnames[:] = [d for d in dirnames if not d.startswith(".")]
98
99         for filename in filenames:
100             filepath = join(dirpath, filename)
101             if filename_check is None or filename_check(filepath):
102                 yield filepath
103
104
105 def load_addons():
106     modules = addon_modules_sorted()
107     addons = bpy.context.user_preferences.addons
108
109     # first disable all
110     for mod_name in list(addons.keys()):
111         addon_utils.disable(mod_name, default_set=True)
112
113     assert(bool(addons) is False)
114
115     for mod in modules:
116         mod_name = mod.__name__
117         if mod_name in BLACKLIST:
118             continue
119         addon_utils.enable(mod_name, default_set=True)
120         if not (mod_name in addons):
121             raise Exception("'addon_utils.enable(%r)' call failed" % mod_name)
122
123
124 def load_modules():
125     VERBOSE = os.environ.get("BLENDER_VERBOSE") is not None
126
127     modules = []
128     module_paths = []
129
130     # paths blender stores scripts in.
131     paths = bpy.utils.script_paths()
132
133     print("Paths:")
134     for script_path in paths:
135         print("\t'%s'" % script_path)
136
137     #
138     # find all sys.path we added
139     for script_path in paths:
140         for mod_dir in sys.path:
141             if mod_dir.startswith(script_path):
142                 if not mod_dir.startswith(BLACKLIST_DIRS):
143                     if mod_dir not in module_paths:
144                         if os.path.exists(mod_dir):
145                             module_paths.append(mod_dir)
146
147     #
148     # collect modules from our paths.
149     module_names = {}
150     for mod_dir in module_paths:
151         # print("mod_dir", mod_dir)
152         for mod, mod_full in bpy.path.module_names(mod_dir):
153             if mod in BLACKLIST:
154                 continue
155             if mod in module_names:
156                 mod_dir_prev, mod_full_prev = module_names[mod]
157                 raise Exception("Module found twice %r.\n    (%r -> %r, %r -> %r)" %
158                                 (mod, mod_dir, mod_full, mod_dir_prev, mod_full_prev))
159
160             modules.append(__import__(mod))
161
162             module_names[mod] = mod_dir, mod_full
163     del module_names
164
165     #
166     # test we tested all files except for presets and templates
167     ignore_paths = [
168         os.sep + "presets" + os.sep,
169         os.sep + "templates" + os.sep,
170     ] + ([(os.sep + f + os.sep) for f in BLACKLIST] +
171          [(os.sep + f + ".py") for f in BLACKLIST])
172
173     #
174     # now submodules
175     for m in modules:
176         filepath = m.__file__
177         if os.path.basename(filepath).startswith("__init__."):
178             mod_dir = os.path.dirname(filepath)
179             for submod, submod_full in module_names_all(mod_dir):
180                 # fromlist is ignored, ugh.
181                 mod_name_full = m.__name__ + "." + submod
182
183                 sys_path_back = sys.path[:]
184
185                 sys.path.extend([
186                     os.path.normpath(os.path.join(mod_dir, f))
187                     for f in MODULE_SYS_PATHS.get(mod_name_full, ())
188                 ])
189
190                 try:
191                     __import__(mod_name_full)
192                     mod_imp = sys.modules[mod_name_full]
193
194                     sys.path[:] = sys_path_back
195
196                     # check we load what we ask for.
197                     assert(os.path.samefile(mod_imp.__file__, submod_full))
198
199                     modules.append(mod_imp)
200                 except Exception as e:
201                     import traceback
202                     # Module might fail to import, but we don't want whole test to fail here.
203                     # Reasoning:
204                     # - This module might be in ignored list (for example, preset or template),
205                     #   so failing here will cause false-positive test failure.
206                     # - If this is module which should not be ignored, it is not added to list
207                     #   of successfully loaded modules, meaning the test will catch this
208                     #   import failure.
209                     # - We want to catch all failures of this script instead of stopping on
210                     #   a first big failure.
211                     do_print = True
212                     if not VERBOSE:
213                         for ignore in ignore_paths:
214                             if ignore in submod_full:
215                                 do_print = False
216                                 break
217                     if do_print:
218                         traceback.print_exc()
219
220     #
221     # check which filepaths we didn't load
222     source_files = []
223     for mod_dir in module_paths:
224         source_files.extend(source_list(mod_dir, filename_check=lambda f: f.endswith(".py")))
225
226     source_files = list(set(source_files))
227     source_files.sort()
228
229     #
230     # remove loaded files
231     loaded_files = list({m.__file__ for m in modules})
232     loaded_files.sort()
233
234     for f in loaded_files:
235         source_files.remove(f)
236
237     for f in source_files:
238         for ignore in ignore_paths:
239             if ignore in f:
240                 break
241         else:
242             raise Exception("Source file %r not loaded in test" % f)
243
244     print("loaded %d modules" % len(loaded_files))
245
246
247 def main():
248     load_addons()
249     load_modules()
250
251
252 if __name__ == "__main__":
253     # So a python error exits(1)
254     try:
255         main()
256     except:
257         import traceback
258         traceback.print_exc()
259         sys.exit(1)