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
33 from webkitpy.common.system.executive import Executive
35 # Source/ path is needed both to find input IDL files, and to import other
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
42 from bindings.scripts.compute_interfaces_info import compute_interfaces_info, interfaces_info
43 from bindings.scripts.idl_compiler import IdlCompilerV8
46 PASS_MESSAGE = 'All tests PASS!'
47 FAIL_MESSAGE = """Some tests FAIL!
48 To update the reference files, execute:
49 run-bindings-tests --reset-results
51 If the failures are not due to your changes, test results may be out of sync;
52 please rebaseline them in a separate CL, after checking that tests fail in ToT.
55 TBR=(someone in Source/bindings/OWNERS or WATCHLISTS:bindings)
58 DEPENDENCY_IDL_FILES = frozenset([
60 'TestImplements2.idl',
61 'TestImplements3.idl',
62 'TestPartialInterface.idl',
63 'TestPartialInterface2.idl',
67 test_input_directory = os.path.join(source_path, 'bindings', 'tests', 'idls')
68 reference_directory = os.path.join(source_path, 'bindings', 'tests', 'results')
72 def TemporaryDirectory():
73 """Wrapper for tempfile.mkdtemp() so it's usable with 'with' statement.
75 Simple backport of tempfile.TemporaryDirectory from Python 3.2.
77 name = tempfile.mkdtemp()
84 def generate_interface_dependencies():
85 def idl_paths_recursive(directory):
86 # This is slow, especially on Windows, due to os.walk making
87 # excess stat() calls. Faster versions may appear in Python 3.5 or
89 # https://github.com/benhoyt/scandir
90 # http://bugs.python.org/issue11406
92 for dirpath, _, files in os.walk(directory):
93 idl_paths.extend(os.path.join(dirpath, filename)
94 for filename in fnmatch.filter(files, '*.idl'))
97 # We compute interfaces info for *all* IDL files, not just test IDL
98 # files, as code generator output depends on inheritance (both ancestor
99 # chain and inherited extended attributes), and some real interfaces
100 # are special-cased, such as Node.
102 # For example, when testing the behavior of interfaces that inherit
103 # from Node, we also need to know that these inherit from EventTarget,
104 # since this is also special-cased and Node inherits from EventTarget,
105 # but this inheritance information requires computing dependencies for
106 # the real Node.idl file.
107 compute_interfaces_info(idl_paths_recursive(source_path))
110 def bindings_tests(output_directory, verbose):
111 executive = Executive()
113 def diff(filename1, filename2):
114 # Python's difflib module is too slow, especially on long output, so
115 # run external diff(1) command
117 '-u', # unified format
118 '-N', # treat absent files as empty
121 # Return output and don't raise exception, even though diff(1) has
122 # non-zero exit if files differ.
123 return executive.run_command(cmd, error_handler=lambda x: None)
125 def delete_cache_files():
126 # FIXME: Instead of deleting cache files, don't generate them.
127 cache_files = [os.path.join(output_directory, output_file)
128 for output_file in os.listdir(output_directory)
129 if (output_file in ('lextab.py', # PLY lex
131 'parsetab.pickle') or # PLY yacc
132 output_file.endswith('.cache'))] # Jinja
133 for cache_file in cache_files:
134 os.remove(cache_file)
136 def identical_file(reference_filename, output_filename):
137 reference_basename = os.path.basename(reference_filename)
139 if not os.path.isfile(reference_filename):
140 print 'Missing reference file!'
141 print '(if adding new test, update reference files)'
142 print reference_basename
146 if not filecmp.cmp(reference_filename, output_filename):
147 # cmp is much faster than diff, and usual case is "no differance",
148 # so only run diff if cmp detects a difference
149 print 'FAIL: %s' % reference_basename
150 print diff(reference_filename, output_filename)
154 print 'PASS: %s' % reference_basename
157 def identical_output_files():
158 file_pairs = [(os.path.join(reference_directory, output_file),
159 os.path.join(output_directory, output_file))
160 for output_file in os.listdir(output_directory)]
161 return all([identical_file(reference_filename, output_filename)
162 for (reference_filename, output_filename) in file_pairs])
164 def no_excess_files():
165 generated_files = set(os.listdir(output_directory))
166 generated_files.add('.svn') # Subversion working copy directory
167 excess_files = [output_file
168 for output_file in os.listdir(reference_directory)
169 if output_file not in generated_files]
171 print ('Excess reference files! '
172 '(probably cruft from renaming or deleting):\n' +
173 '\n'.join(excess_files))
178 generate_interface_dependencies()
179 idl_compiler = IdlCompilerV8(output_directory,
180 interfaces_info=interfaces_info,
181 only_if_changed=True)
183 idl_basenames = [filename
184 for filename in os.listdir(test_input_directory)
185 if (filename.endswith('.idl') and
186 # Dependencies aren't built
187 # (they are used by the dependent)
188 filename not in DEPENDENCY_IDL_FILES)]
189 for idl_basename in idl_basenames:
190 idl_path = os.path.realpath(
191 os.path.join(test_input_directory, idl_basename))
192 idl_compiler.compile_file(idl_path)
194 print 'Compiled: %s' % filename
199 passed = identical_output_files()
200 passed &= no_excess_files()
212 def run_bindings_tests(reset_results, verbose):
213 # Generate output into the reference directory if resetting results, or
214 # a temp directory if not.
216 print 'Resetting results'
217 return bindings_tests(reference_directory, verbose)
218 with TemporaryDirectory() as temp_dir:
219 return bindings_tests(temp_dir, verbose)