1 # Copyright (C) 2011 Google Inc. All rights reserved.
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions
6 # 1. Redistributions of source code must retain the above copyright
7 # notice, this list of conditions and the following disclaimer.
8 # 2. Redistributions in binary form must reproduce the above copyright
9 # notice, this list of conditions and the following disclaimer in the
10 # documentation and/or other materials provided with the distribution.
12 # THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
13 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
14 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
15 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
16 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
17 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
18 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
19 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
20 # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
22 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 from contextlib import contextmanager
34 from webkitpy.common.system.executive import Executive
36 # Source/ path is needed both to find input IDL files, and to import other
38 module_path = os.path.dirname(__file__)
39 source_path = os.path.normpath(os.path.join(module_path, os.pardir, os.pardir,
40 os.pardir, os.pardir, 'Source'))
41 sys.path.append(source_path) # for Source/bindings imports
43 from bindings.scripts.code_generator_v8 import CodeGeneratorUnionType
44 import bindings.scripts.compute_interfaces_info_individual
45 from bindings.scripts.compute_interfaces_info_individual import InterfaceInfoCollector
46 import bindings.scripts.compute_interfaces_info_overall
47 from bindings.scripts.compute_interfaces_info_overall import compute_interfaces_info_overall, interfaces_info
48 from bindings.scripts.idl_compiler import IdlCompilerDictionaryImpl, IdlCompilerV8
49 from bindings.scripts.idl_reader import IdlReader
50 from bindings.scripts.utilities import idl_filename_to_component, write_file
53 PASS_MESSAGE = 'All tests PASS!'
54 FAIL_MESSAGE = """Some tests FAIL!
55 To update the reference files, execute:
56 run-bindings-tests --reset-results
58 If the failures are not due to your changes, test results may be out of sync;
59 please rebaseline them in a separate CL, after checking that tests fail in ToT.
62 TBR=(someone in Source/bindings/OWNERS or WATCHLISTS:bindings)
65 DEPENDENCY_IDL_FILES = frozenset([
67 'TestImplements2.idl',
68 'TestImplements3.idl',
69 'TestPartialInterface.idl',
70 'TestPartialInterface2.idl',
71 'TestPartialInterface3.idl',
74 # core/inspector/InspectorInstrumentation.idl is not a valid Blink IDL.
75 NON_BLINK_IDL_FILES = frozenset([
76 'InspectorInstrumentation.idl',
79 COMPONENT_DIRECTORY = frozenset(['core', 'modules'])
81 test_input_directory = os.path.join(source_path, 'bindings', 'tests', 'idls')
82 reference_directory = os.path.join(source_path, 'bindings', 'tests', 'results')
84 # component -> set of union types
88 def TemporaryDirectory():
89 """Wrapper for tempfile.mkdtemp() so it's usable with 'with' statement.
91 Simple backport of tempfile.TemporaryDirectory from Python 3.2.
93 name = tempfile.mkdtemp()
100 def generate_interface_dependencies(output_directory):
101 def idl_paths_recursive(directory):
102 # This is slow, especially on Windows, due to os.walk making
103 # excess stat() calls. Faster versions may appear in Python 3.5 or
105 # https://github.com/benhoyt/scandir
106 # http://bugs.python.org/issue11406
108 for dirpath, _, files in os.walk(directory):
109 idl_paths.extend(os.path.join(dirpath, filename)
110 for filename in fnmatch.filter(files, '*.idl'))
113 def collect_blink_idl_paths():
114 """Returns IDL file paths which blink actually uses."""
116 for component in COMPONENT_DIRECTORY:
117 directory = os.path.join(source_path, component)
118 idl_paths.extend(idl_paths_recursive(directory))
121 def collect_interfaces_info(idl_path_list):
122 info_collector = InterfaceInfoCollector()
123 for idl_path in idl_path_list:
124 if os.path.basename(idl_path) in NON_BLINK_IDL_FILES:
126 info_collector.collect_info(idl_path)
127 info = info_collector.get_info_as_dict()
128 # TestDictionary.{h,cpp} are placed under
129 # Source/bindings/tests/idls/core. However, IdlCompiler generates
130 # TestDictionary.{h,cpp} by using relative_dir.
131 # So the files will be generated under
132 # output_dir/core/bindings/tests/idls/core.
133 # To avoid this issue, we need to clear relative_dir here.
134 for value in info['interfaces_info'].itervalues():
135 value['relative_dir'] = ''
136 # Merge component-wide information.
137 component_info = info_collector.get_component_info_as_dict()
138 info.update(component_info)
141 # We compute interfaces info for *all* IDL files, not just test IDL
142 # files, as code generator output depends on inheritance (both ancestor
143 # chain and inherited extended attributes), and some real interfaces
144 # are special-cased, such as Node.
146 # For example, when testing the behavior of interfaces that inherit
147 # from Node, we also need to know that these inherit from EventTarget,
148 # since this is also special-cased and Node inherits from EventTarget,
149 # but this inheritance information requires computing dependencies for
150 # the real Node.idl file.
151 non_test_idl_paths = collect_blink_idl_paths()
152 # For bindings test IDL files, we collect interfaces info for each
153 # component so that we can generate union type containers separately.
155 for component in COMPONENT_DIRECTORY:
156 test_idl_paths[component] = idl_paths_recursive(
157 os.path.join(test_input_directory, component))
158 # 2nd-stage computation: individual, then overall
160 # Properly should compute separately by component (currently test
161 # includes are invalid), but that's brittle (would need to update this file
162 # for each new component) and doesn't test the code generator any better
163 # than using a single component.
164 non_test_interfaces_info = collect_interfaces_info(non_test_idl_paths)
165 test_interfaces_info = {}
166 for component, paths in test_idl_paths.iteritems():
167 test_interfaces_info[component] = collect_interfaces_info(paths)
168 # In order to allow test IDL files to override the production IDL files if
169 # they have the same interface name, process the test IDL files after the
170 # non-test IDL files.
171 info_individuals = [non_test_interfaces_info] + test_interfaces_info.values()
172 compute_interfaces_info_overall(info_individuals)
173 # 3rd-stage: union types
174 # We only process union types which are defined under
175 # Source/bindings/tests/idls. Otherwise, the result of union type
176 # container classes will be affected by non-test IDL files.
177 for component, interfaces_info in test_interfaces_info.iteritems():
178 union_types[component] = interfaces_info['union_types']
181 def bindings_tests(output_directory, verbose):
182 executive = Executive()
184 def list_files(directory):
186 for component in os.listdir(directory):
187 if component not in COMPONENT_DIRECTORY:
189 directory_with_component = os.path.join(directory, component)
190 for filename in os.listdir(directory_with_component):
191 files.append(os.path.join(directory_with_component, filename))
194 def diff(filename1, filename2):
195 # Python's difflib module is too slow, especially on long output, so
196 # run external diff(1) command
198 '-u', # unified format
199 '-N', # treat absent files as empty
202 # Return output and don't raise exception, even though diff(1) has
203 # non-zero exit if files differ.
204 return executive.run_command(cmd, error_handler=lambda x: None)
206 def is_cache_file(filename):
207 return filename.endswith('.cache')
209 def delete_cache_files():
210 # FIXME: Instead of deleting cache files, don't generate them.
211 cache_files = [path for path in list_files(output_directory)
212 if is_cache_file(os.path.basename(path))]
213 for cache_file in cache_files:
214 os.remove(cache_file)
216 def identical_file(reference_filename, output_filename):
217 reference_basename = os.path.basename(reference_filename)
219 if not os.path.isfile(reference_filename):
220 print 'Missing reference file!'
221 print '(if adding new test, update reference files)'
222 print reference_basename
226 if not filecmp.cmp(reference_filename, output_filename):
227 # cmp is much faster than diff, and usual case is "no differance",
228 # so only run diff if cmp detects a difference
229 print 'FAIL: %s' % reference_basename
230 print diff(reference_filename, output_filename)
234 print 'PASS: %s' % reference_basename
237 def identical_output_files(output_files):
238 reference_files = [os.path.join(reference_directory,
239 os.path.relpath(path, output_directory))
240 for path in output_files]
241 return all([identical_file(reference_filename, output_filename)
242 for (reference_filename, output_filename) in zip(reference_files, output_files)])
244 def no_excess_files(output_files):
245 generated_files = set([os.path.relpath(path, output_directory)
246 for path in output_files])
247 # Add subversion working copy directories in core and modules.
248 for component in COMPONENT_DIRECTORY:
249 generated_files.add(os.path.join(component, '.svn'))
252 for path in list_files(reference_directory):
253 relpath = os.path.relpath(path, reference_directory)
254 if relpath not in generated_files:
255 excess_files.append(relpath)
257 print ('Excess reference files! '
258 '(probably cruft from renaming or deleting):\n' +
259 '\n'.join(excess_files))
263 def generate_union_type_containers(output_directory, component):
264 generator = CodeGeneratorUnionType(
265 interfaces_info, cache_dir=None, output_dir=output_directory,
266 target_component=component)
267 outputs = generator.generate_code(union_types[component])
268 for output_path, output_code in outputs:
269 write_file(output_code, output_path, only_if_changed=True)
272 generate_interface_dependencies(output_directory)
273 for component in COMPONENT_DIRECTORY:
274 output_dir = os.path.join(output_directory, component)
275 if not os.path.exists(output_dir):
276 os.makedirs(output_dir)
278 generate_union_type_containers(output_dir, component)
280 idl_compiler = IdlCompilerV8(output_dir,
281 interfaces_info=interfaces_info,
282 only_if_changed=True)
283 if component == 'core':
284 partial_interface_output_dir = os.path.join(output_directory,
286 if not os.path.exists(partial_interface_output_dir):
287 os.makedirs(partial_interface_output_dir)
288 idl_partial_interface_compiler = IdlCompilerV8(
289 partial_interface_output_dir,
290 interfaces_info=interfaces_info,
291 only_if_changed=True,
292 target_component='modules')
294 idl_partial_interface_compiler = None
296 dictionary_impl_compiler = IdlCompilerDictionaryImpl(
297 output_dir, interfaces_info=interfaces_info,
298 only_if_changed=True)
301 input_directory = os.path.join(test_input_directory, component)
302 for filename in os.listdir(input_directory):
303 if (filename.endswith('.idl') and
304 # Dependencies aren't built
305 # (they are used by the dependent)
306 filename not in DEPENDENCY_IDL_FILES):
307 idl_filenames.append(
309 os.path.join(input_directory, filename)))
310 for idl_path in idl_filenames:
311 idl_basename = os.path.basename(idl_path)
312 idl_compiler.compile_file(idl_path)
313 definition_name, _ = os.path.splitext(idl_basename)
314 if definition_name in interfaces_info:
315 interface_info = interfaces_info[definition_name]
316 if interface_info['is_dictionary']:
317 dictionary_impl_compiler.compile_file(idl_path)
318 if component == 'core' and interface_info['dependencies_other_component_full_paths']:
319 idl_partial_interface_compiler.compile_file(idl_path)
321 print 'Compiled: %s' % idl_path
326 output_files = list_files(output_directory)
327 passed = identical_output_files(output_files)
328 passed &= no_excess_files(output_files)
340 def run_bindings_tests(reset_results, verbose):
341 # Generate output into the reference directory if resetting results, or
342 # a temp directory if not.
344 print 'Resetting results'
345 return bindings_tests(reference_directory, verbose)
346 with TemporaryDirectory() as temp_dir:
347 return bindings_tests(temp_dir, verbose)