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.
27 import cPickle as pickle
28 from shutil import rmtree
30 from webkitpy.common.checkout.scm.detection import detect_scm_system
31 from webkitpy.common.system.executive import ScriptError
33 PASS_MESSAGE = 'All tests PASS!'
34 FAIL_MESSAGE = """Some tests FAIL!
35 To update the reference files, execute:
36 run-bindings-tests --reset-results
38 If the failures are not due to your changes, test results may be out of sync;
39 please rebaseline them in a separate CL, after checking that tests fail in ToT.
42 TBR=(someone in Source/bindings/OWNERS or WATCHLISTS:bindings)
45 DEPENDENCY_IDL_FILES = set([
46 'SupportTestPartialInterface.idl',
48 'TestImplements2.idl',
49 'TestImplements3.idl',
50 'TestPartialInterface.idl',
51 'TestPartialInterfacePython.idl',
52 'TestPartialInterfacePython2.idl',
55 SKIP_PYTHON = 'TestSVG.idl' # Not implementing SVG-specific hacks in Python
57 all_input_directory = '.' # Relative to Source/
58 test_input_directory = os.path.join('bindings', 'tests', 'idls')
59 reference_directory = os.path.join('bindings', 'tests', 'results')
60 reference_event_names_filename = os.path.join(reference_directory, 'EventInterfaces.in')
63 class ScopedTempFileProvider(object):
69 for filename in self.files:
71 for directory in self.directories:
74 def newtempfile(self):
75 file_handle, path = tempfile.mkstemp()
76 self.files.append(path)
77 return file_handle, path
80 path = tempfile.mkdtemp()
81 self.directories.append(path)
84 provider = ScopedTempFileProvider()
87 class BindingsTests(object):
88 def __init__(self, reset_results, test_python, verbose, executive):
89 self.reset_results = reset_results
90 self.test_python = test_python
91 self.verbose = verbose
92 self.executive = executive
93 _, self.interface_dependencies_filename = provider.newtempfile()
94 _, self.interfaces_info_filename = provider.newtempfile()
95 # Generate output into the reference directory if resetting results, or
96 # a temp directory if not.
98 self.output_directory = reference_directory
100 self.output_directory = provider.newtempdir()
101 self.output_directory_py = provider.newtempdir()
102 self.event_names_filename = os.path.join(self.output_directory, 'EventInterfaces.in')
104 def run_command(self, cmd):
105 output = self.executive.run_command(cmd)
109 def generate_from_idl_pl(self, idl_file):
111 '-Ibindings/scripts',
114 '-I../../JSON/out/lib/perl5',
115 'bindings/scripts/generate_bindings.pl',
116 # idl include directories (path relative to generate-bindings.pl)
118 '--outputDir', self.output_directory,
119 '--interfaceDependenciesFile', self.interface_dependencies_filename,
120 '--idlAttributesFile', 'bindings/IDLExtendedAttributes.txt',
123 self.run_command(cmd)
124 except ScriptError, e:
125 print 'ERROR: generate_bindings.pl: ' + os.path.basename(idl_file)
130 def generate_from_idl_py(self, idl_file):
132 'bindings/scripts/unstable/idl_compiler.py',
133 '--output-dir', self.output_directory_py,
134 '--idl-attributes-file', 'bindings/IDLExtendedAttributes.txt',
135 '--interfaces-info-file', self.interfaces_info_filename,
138 self.run_command(cmd)
139 except ScriptError, e:
140 print 'ERROR: idl_compiler.py: ' + os.path.basename(idl_file)
145 def generate_interface_dependencies(self):
146 def idl_paths(directory):
147 return [os.path.join(directory, input_file)
148 for input_file in os.listdir(directory)
149 if input_file.endswith('.idl')]
151 def idl_paths_recursive(directory):
153 for dirpath, _, files in os.walk(directory):
154 idl_paths.extend(os.path.join(dirpath, filename)
155 for filename in fnmatch.filter(files, '*.idl'))
158 def write_list_file(idl_paths):
159 list_file, list_filename = provider.newtempfile()
160 list_contents = ''.join(idl_path + '\n'
161 for idl_path in idl_paths)
162 os.write(list_file, list_contents)
165 def compute_dependencies(idl_files_list_filename,
166 event_names_filename):
167 # Dummy files, required by compute_dependencies but not checked
168 _, window_constructors_file = provider.newtempfile()
169 _, workerglobalscope_constructors_file = provider.newtempfile()
170 _, sharedworkerglobalscope_constructors_file = provider.newtempfile()
171 _, dedicatedworkerglobalscope_constructors_file = provider.newtempfile()
172 _, serviceworkersglobalscope_constructors_file = provider.newtempfile()
174 'bindings/scripts/compute_dependencies.py',
175 '--idl-files-list', idl_files_list_filename,
176 '--interface-dependencies-file', self.interface_dependencies_filename,
177 '--interfaces-info-file', self.interfaces_info_filename,
178 '--window-constructors-file', window_constructors_file,
179 '--workerglobalscope-constructors-file', workerglobalscope_constructors_file,
180 '--sharedworkerglobalscope-constructors-file', sharedworkerglobalscope_constructors_file,
181 '--dedicatedworkerglobalscope-constructors-file', dedicatedworkerglobalscope_constructors_file,
182 '--serviceworkerglobalscope-constructors-file', serviceworkersglobalscope_constructors_file,
183 '--event-names-file', event_names_filename,
184 '--write-file-only-if-changed', '0']
185 self.run_command(cmd)
187 test_idl_files_list_filename = write_list_file(idl_paths(test_input_directory))
188 all_idl_files_list_filename = write_list_file(idl_paths_recursive(all_input_directory))
190 if self.reset_results and self.verbose:
191 print 'Reset results: EventInterfaces.in'
193 # We first compute dependencies for testing files only,
194 # so we can compare EventInterfaces.in.
195 compute_dependencies(test_idl_files_list_filename,
196 self.event_names_filename)
198 # We then compute dependencies for all IDL files, as code generator
199 # output depends on inheritance (both ancestor chain and inherited
200 # extended attributes), and some real interfaces are special-cased,
202 # For example, when testing the behavior of interfaces that inherit
203 # from Node, we also need to know that these inherit from
204 # EventTarget, since this is also special-cased and Node inherits
205 # from EventTarget, but this inheritance information requires
206 # computing dependencies for the real Node.idl file.
208 # Don't overwrite the event names file generated for testing IDLs
209 _, dummy_event_names_filename = provider.newtempfile()
210 compute_dependencies(all_idl_files_list_filename,
211 dummy_event_names_filename)
212 except ScriptError, e:
213 print 'ERROR: compute_dependencies.py'
218 def identical_file(self, reference_filename, output_filename):
219 reference_basename = os.path.basename(reference_filename)
226 self.run_command(cmd)
227 except ScriptError, e:
228 # run_command throws an exception on diff (b/c non-zero exit code)
229 print 'FAIL: %s' % reference_basename
234 print 'PASS: %s' % reference_basename
237 def identical_output_files(self, output_directory):
238 file_pairs = [(os.path.join(reference_directory, output_file),
239 os.path.join(output_directory, output_file))
240 for output_file in os.listdir(output_directory)
241 # FIXME: add option to compiler to not generate tables
242 if output_file != 'parsetab.py']
243 return all([self.identical_file(reference_filename, output_filename)
244 for (reference_filename, output_filename) in file_pairs])
246 def no_excess_files(self):
247 generated_files = set(os.listdir(self.output_directory))
248 generated_files.add('.svn') # Subversion working copy directory
249 excess_files = [output_file
250 for output_file in os.listdir(reference_directory)
251 if output_file not in generated_files]
253 print ('Excess reference files! '
254 '(probably cruft from renaming or deleting):\n' +
255 '\n'.join(excess_files))
260 # Generate output, immediately dying on failure
261 if self.generate_interface_dependencies():
264 for input_filename in os.listdir(test_input_directory):
265 if not input_filename.endswith('.idl'):
267 if input_filename in DEPENDENCY_IDL_FILES:
268 # Dependencies aren't built (they are used by the dependent)
270 print 'DEPENDENCY: %s' % input_filename
273 idl_path = os.path.join(test_input_directory, input_filename)
274 if self.generate_from_idl_pl(idl_path):
276 if self.reset_results and self.verbose:
277 print 'Reset results: %s' % input_filename
278 if not self.test_python:
280 if input_filename == SKIP_PYTHON:
282 print 'SKIP: %s' % input_filename
284 if self.generate_from_idl_py(idl_path):
288 passed = self.identical_file(reference_event_names_filename,
289 self.event_names_filename)
290 passed &= self.identical_output_files(self.output_directory)
295 passed &= self.identical_output_files(self.output_directory_py)
296 passed &= self.no_excess_files()
300 current_scm = detect_scm_system(os.curdir)
301 os.chdir(os.path.join(current_scm.checkout_root, 'Source'))
303 all_tests_passed = self.run_tests()