Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Tools / Scripts / webkitpy / bindings / main.py
1 # Copyright (C) 2011 Google Inc.  All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions
5 # are met:
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.
11 #
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.
23 #
24
25 from contextlib import contextmanager
26 import filecmp
27 import fnmatch
28 import os
29 import re
30 import shutil
31 import sys
32 import tempfile
33
34 from webkitpy.common.system.executive import Executive
35
36 # Source/ path is needed both to find input IDL files, and to import other
37 # Python modules.
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
42
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
51
52
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
57
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.
60 In CL, please set:
61 NOTRY=true
62 TBR=(someone in Source/bindings/OWNERS or WATCHLISTS:bindings)
63 """
64
65 DEPENDENCY_IDL_FILES = frozenset([
66     'TestImplements.idl',
67     'TestImplements2.idl',
68     'TestImplements3.idl',
69     'TestPartialInterface.idl',
70     'TestPartialInterface2.idl',
71     'TestPartialInterface3.idl',
72 ])
73
74 # core/inspector/InspectorInstrumentation.idl is not a valid Blink IDL.
75 NON_BLINK_IDL_FILES = frozenset([
76     'InspectorInstrumentation.idl',
77 ])
78
79 COMPONENT_DIRECTORY = frozenset(['core', 'modules'])
80
81 test_input_directory = os.path.join(source_path, 'bindings', 'tests', 'idls')
82 reference_directory = os.path.join(source_path, 'bindings', 'tests', 'results')
83
84 # component -> set of union types
85 union_types = {}
86
87 @contextmanager
88 def TemporaryDirectory():
89     """Wrapper for tempfile.mkdtemp() so it's usable with 'with' statement.
90
91     Simple backport of tempfile.TemporaryDirectory from Python 3.2.
92     """
93     name = tempfile.mkdtemp()
94     try:
95         yield name
96     finally:
97         shutil.rmtree(name)
98
99
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
104         # later:
105         # https://github.com/benhoyt/scandir
106         # http://bugs.python.org/issue11406
107         idl_paths = []
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'))
111         return idl_paths
112
113     def collect_blink_idl_paths():
114         """Returns IDL file paths which blink actually uses."""
115         idl_paths = []
116         for component in COMPONENT_DIRECTORY:
117             directory = os.path.join(source_path, component)
118             idl_paths.extend(idl_paths_recursive(directory))
119         return idl_paths
120
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:
125                 continue
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)
139         return info
140
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.
145     #
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.
154     test_idl_paths = {}
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
159     #
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']
179
180
181 def bindings_tests(output_directory, verbose):
182     executive = Executive()
183
184     def list_files(directory):
185         files = []
186         for component in os.listdir(directory):
187             if component not in COMPONENT_DIRECTORY:
188                 continue
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))
192         return files
193
194     def diff(filename1, filename2):
195         # Python's difflib module is too slow, especially on long output, so
196         # run external diff(1) command
197         cmd = ['diff',
198                '-u',  # unified format
199                '-N',  # treat absent files as empty
200                filename1,
201                filename2]
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)
205
206     def is_cache_file(filename):
207         return filename.endswith('.cache')
208
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)
215
216     def identical_file(reference_filename, output_filename):
217         reference_basename = os.path.basename(reference_filename)
218
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
223             print
224             return False
225
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)
231             return False
232
233         if verbose:
234             print 'PASS: %s' % reference_basename
235         return True
236
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)])
243
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'))
250
251         excess_files = []
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)
256         if excess_files:
257             print ('Excess reference files! '
258                   '(probably cruft from renaming or deleting):\n' +
259                   '\n'.join(excess_files))
260             return False
261         return True
262
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)
270
271     try:
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)
277
278             generate_union_type_containers(output_dir, component)
279
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,
285                                                             'modules')
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')
293             else:
294                 idl_partial_interface_compiler = None
295
296             dictionary_impl_compiler = IdlCompilerDictionaryImpl(
297                 output_dir, interfaces_info=interfaces_info,
298                 only_if_changed=True)
299
300             idl_filenames = []
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(
308                         os.path.realpath(
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)
320                 if verbose:
321                     print 'Compiled: %s' % idl_path
322     finally:
323         delete_cache_files()
324
325     # Detect all changes
326     output_files = list_files(output_directory)
327     passed = identical_output_files(output_files)
328     passed &= no_excess_files(output_files)
329
330     if passed:
331         if verbose:
332             print
333             print PASS_MESSAGE
334         return 0
335     print
336     print FAIL_MESSAGE
337     return 1
338
339
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.
343     if reset_results:
344         print 'Resetting results'
345         return bindings_tests(reference_directory, verbose)
346     with TemporaryDirectory() as temp_dir:
347         return bindings_tests(temp_dir, verbose)