Upstream version 10.39.225.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 import bindings.scripts.compute_interfaces_info_individual
44 from bindings.scripts.compute_interfaces_info_individual import compute_info_individual, info_individual
45 import bindings.scripts.compute_interfaces_info_overall
46 from bindings.scripts.compute_interfaces_info_overall import compute_interfaces_info_overall, interfaces_info
47 from bindings.scripts.idl_compiler import IdlCompilerDictionaryImpl, IdlCompilerV8
48
49
50 PASS_MESSAGE = 'All tests PASS!'
51 FAIL_MESSAGE = """Some tests FAIL!
52 To update the reference files, execute:
53     run-bindings-tests --reset-results
54
55 If the failures are not due to your changes, test results may be out of sync;
56 please rebaseline them in a separate CL, after checking that tests fail in ToT.
57 In CL, please set:
58 NOTRY=true
59 TBR=(someone in Source/bindings/OWNERS or WATCHLISTS:bindings)
60 """
61
62 DEPENDENCY_IDL_FILES = frozenset([
63     'TestImplements.idl',
64     'TestImplements2.idl',
65     'TestImplements3.idl',
66     'TestPartialInterface.idl',
67     'TestPartialInterface2.idl',
68     'TestPartialInterface3.idl',
69 ])
70
71 COMPONENT_DIRECTORY = frozenset(['core', 'modules'])
72
73 test_input_directory = os.path.join(source_path, 'bindings', 'tests', 'idls')
74 reference_directory = os.path.join(source_path, 'bindings', 'tests', 'results')
75
76 PLY_LEX_YACC_FILES = frozenset([
77     'lextab.py',  # PLY lex
78     'lextab.pyc',
79     'parsetab.pickle',  # PLY yacc
80 ])
81
82 @contextmanager
83 def TemporaryDirectory():
84     """Wrapper for tempfile.mkdtemp() so it's usable with 'with' statement.
85
86     Simple backport of tempfile.TemporaryDirectory from Python 3.2.
87     """
88     name = tempfile.mkdtemp()
89     try:
90         yield name
91     finally:
92         shutil.rmtree(name)
93
94
95 def generate_interface_dependencies():
96     def idl_paths_recursive(directory):
97         # This is slow, especially on Windows, due to os.walk making
98         # excess stat() calls. Faster versions may appear in Python 3.5 or
99         # later:
100         # https://github.com/benhoyt/scandir
101         # http://bugs.python.org/issue11406
102         idl_paths = []
103         for dirpath, _, files in os.walk(directory):
104             idl_paths.extend(os.path.join(dirpath, filename)
105                              for filename in fnmatch.filter(files, '*.idl'))
106         return idl_paths
107
108     # We compute interfaces info for *all* IDL files, not just test IDL
109     # files, as code generator output depends on inheritance (both ancestor
110     # chain and inherited extended attributes), and some real interfaces
111     # are special-cased, such as Node.
112     #
113     # For example, when testing the behavior of interfaces that inherit
114     # from Node, we also need to know that these inherit from EventTarget,
115     # since this is also special-cased and Node inherits from EventTarget,
116     # but this inheritance information requires computing dependencies for
117     # the real Node.idl file.
118
119     # 2-stage computation: individual, then overall
120     #
121     # Properly should compute separately by component (currently test
122     # includes are invalid), but that's brittle (would need to update this file
123     # for each new component) and doesn't test the code generator any better
124     # than using a single component.
125     for idl_filename in idl_paths_recursive(source_path):
126         compute_info_individual(idl_filename)
127     info_individuals = [info_individual()]
128     # TestDictionary.{h,cpp} are placed under Source/bindings/tests/idls/core.
129     # However, IdlCompiler generates TestDictionary.{h,cpp} by using relative_dir.
130     # So the files will be generated under output_dir/core/bindings/tests/idls/core.
131     # To avoid this issue, we need to clear relative_dir here.
132     for info in info_individuals:
133         for value in info['interfaces_info'].itervalues():
134             value['relative_dir'] = ''
135     compute_interfaces_info_overall(info_individuals)
136
137
138 def bindings_tests(output_directory, verbose):
139     executive = Executive()
140
141     def list_files(directory):
142         files = []
143         for component in os.listdir(directory):
144             if component not in COMPONENT_DIRECTORY:
145                 continue
146             directory_with_component = os.path.join(directory, component)
147             for filename in os.listdir(directory_with_component):
148                 files.append(os.path.join(directory_with_component, filename))
149         return files
150
151     def diff(filename1, filename2):
152         # Python's difflib module is too slow, especially on long output, so
153         # run external diff(1) command
154         cmd = ['diff',
155                '-u',  # unified format
156                '-N',  # treat absent files as empty
157                filename1,
158                filename2]
159         # Return output and don't raise exception, even though diff(1) has
160         # non-zero exit if files differ.
161         return executive.run_command(cmd, error_handler=lambda x: None)
162
163     def is_cache_file(filename):
164         if filename in PLY_LEX_YACC_FILES:
165             return True
166         if filename.endswith('.cache'):  # Jinja
167             return True
168         return False
169
170     def delete_cache_files():
171         # FIXME: Instead of deleting cache files, don't generate them.
172         cache_files = [path for path in list_files(output_directory)
173                        if is_cache_file(os.path.basename(path))]
174         for cache_file in cache_files:
175             os.remove(cache_file)
176
177     def identical_file(reference_filename, output_filename):
178         reference_basename = os.path.basename(reference_filename)
179
180         if not os.path.isfile(reference_filename):
181             print 'Missing reference file!'
182             print '(if adding new test, update reference files)'
183             print reference_basename
184             print
185             return False
186
187         if not filecmp.cmp(reference_filename, output_filename):
188             # cmp is much faster than diff, and usual case is "no differance",
189             # so only run diff if cmp detects a difference
190             print 'FAIL: %s' % reference_basename
191             print diff(reference_filename, output_filename)
192             return False
193
194         if verbose:
195             print 'PASS: %s' % reference_basename
196         return True
197
198     def identical_output_files(output_files):
199         reference_files = [os.path.join(reference_directory,
200                                         os.path.relpath(path, output_directory))
201                            for path in output_files]
202         return all([identical_file(reference_filename, output_filename)
203                     for (reference_filename, output_filename) in zip(reference_files, output_files)])
204
205     def no_excess_files(output_files):
206         generated_files = set([os.path.relpath(path, output_directory)
207                                for path in output_files])
208         # Add subversion working copy directories in core and modules.
209         for component in COMPONENT_DIRECTORY:
210             generated_files.add(os.path.join(component, '.svn'))
211
212         excess_files = []
213         for path in list_files(reference_directory):
214             relpath = os.path.relpath(path, reference_directory)
215             if relpath not in generated_files:
216                 excess_files.append(relpath)
217         if excess_files:
218             print ('Excess reference files! '
219                   '(probably cruft from renaming or deleting):\n' +
220                   '\n'.join(excess_files))
221             return False
222         return True
223
224     try:
225         generate_interface_dependencies()
226         for component in COMPONENT_DIRECTORY:
227             output_dir = os.path.join(output_directory, component)
228             if not os.path.exists(output_dir):
229                 os.makedirs(output_dir)
230
231             idl_compiler = IdlCompilerV8(output_dir,
232                                          interfaces_info=interfaces_info,
233                                          only_if_changed=True)
234             dictionary_impl_compiler = IdlCompilerDictionaryImpl(
235                 output_dir, interfaces_info=interfaces_info,
236                 only_if_changed=True)
237
238             idl_filenames = []
239             input_directory = os.path.join(test_input_directory, component)
240             for filename in os.listdir(input_directory):
241                 if (filename.endswith('.idl') and
242                     # Dependencies aren't built
243                     # (they are used by the dependent)
244                     filename not in DEPENDENCY_IDL_FILES):
245                     idl_filenames.append(
246                         os.path.realpath(
247                             os.path.join(input_directory, filename)))
248             for idl_path in idl_filenames:
249                 idl_basename = os.path.basename(idl_path)
250                 idl_compiler.compile_file(idl_path)
251                 definition_name, _ = os.path.splitext(idl_basename)
252                 if (definition_name in interfaces_info and interfaces_info[definition_name]['is_dictionary']):
253                     dictionary_impl_compiler.compile_file(idl_path)
254                 if verbose:
255                     print 'Compiled: %s' % idl_path
256     finally:
257         delete_cache_files()
258
259     # Detect all changes
260     output_files = list_files(output_directory)
261     passed = identical_output_files(output_files)
262     passed &= no_excess_files(output_files)
263
264     if passed:
265         if verbose:
266             print
267             print PASS_MESSAGE
268         return 0
269     print
270     print FAIL_MESSAGE
271     return 1
272
273
274 def run_bindings_tests(reset_results, verbose):
275     # Generate output into the reference directory if resetting results, or
276     # a temp directory if not.
277     if reset_results:
278         print 'Resetting results'
279         return bindings_tests(reference_directory, verbose)
280     with TemporaryDirectory() as temp_dir:
281         return bindings_tests(temp_dir, verbose)