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