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 25import os 26import shutil 27import tempfile 28from webkitpy.common.checkout.scm.detection import detect_scm_system 29from webkitpy.common.system.executive import ScriptError 30 31PASS_MESSAGE = 'All tests PASS!' 32FAIL_MESSAGE = """Some tests FAIL! 33To update the reference files, execute: 34 run-bindings-tests --reset-results 35 36If the failures are not due to your changes, test results may be out of sync; 37please rebaseline them in a separate CL, after checking that tests fail in ToT. 38In CL, please set: 39NOTRY=true 40TBR=(someone in Source/bindings/OWNERS or WATCHLISTS:bindings) 41""" 42 43# Python compiler is incomplete; skip IDLs with unimplemented features 44SKIP_PYTHON = set([ 45 'TestCustomAccessors.idl', 46 'TestEventTarget.idl', 47 'TestException.idl', 48 'TestImplements.idl', 49 'TestInterface.idl', 50 'TestInterfaceImplementedAs.idl', 51 'TestNamedConstructor.idl', 52 'TestNode.idl', 53 'TestObject.idl', 54 'TestOverloadedConstructors.idl', 55 'TestPartialInterface.idl', 56 'TestTypedefs.idl', 57]) 58 59input_directory = os.path.join('bindings', 'tests', 'idls') 60support_input_directory = os.path.join('bindings', 'tests', 'idls', 'testing') 61reference_directory = os.path.join('bindings', 'tests', 'results') 62reference_event_names_filename = os.path.join(reference_directory, 'EventInterfaces.in') 63 64 65class ScopedTempFileProvider(object): 66 def __init__(self): 67 self.files = [] 68 self.directories = [] 69 70 def __del__(self): 71 for filename in self.files: 72 os.remove(filename) 73 for directory in self.directories: 74 shutil.rmtree(directory) 75 76 def newtempfile(self): 77 file_handle, path = tempfile.mkstemp() 78 self.files.append(path) 79 return file_handle, path 80 81 def newtempdir(self): 82 path = tempfile.mkdtemp() 83 self.directories.append(path) 84 return path 85 86provider = ScopedTempFileProvider() 87 88 89class BindingsTests(object): 90 def __init__(self, reset_results, test_python, verbose, executive): 91 self.reset_results = reset_results 92 self.test_python = test_python 93 self.verbose = verbose 94 self.executive = executive 95 _, self.interface_dependencies_filename = provider.newtempfile() 96 _, self.derived_sources_list_filename = provider.newtempfile() 97 # Generate output into the reference directory if resetting results, or 98 # a temp directory if not. 99 if reset_results: 100 self.output_directory = reference_directory 101 else: 102 self.output_directory = provider.newtempdir() 103 self.output_directory_py = provider.newtempdir() 104 self.event_names_filename = os.path.join(self.output_directory, 'EventInterfaces.in') 105 106 def run_command(self, cmd): 107 output = self.executive.run_command(cmd) 108 if output: 109 print output 110 111 def generate_from_idl_pl(self, idl_file): 112 cmd = ['perl', '-w', 113 '-Ibindings/scripts', 114 '-Ibuild/scripts', 115 '-Icore/scripts', 116 '-I../../JSON/out/lib/perl5', 117 'bindings/scripts/generate_bindings.pl', 118 # idl include directories (path relative to generate-bindings.pl) 119 '--include', '.', 120 '--outputDir', self.output_directory, 121 '--interfaceDependenciesFile', self.interface_dependencies_filename, 122 '--idlAttributesFile', 'bindings/IDLExtendedAttributes.txt', 123 idl_file] 124 try: 125 self.run_command(cmd) 126 except ScriptError, e: 127 print 'ERROR: generate_bindings.pl: ' + os.path.basename(idl_file) 128 print e.output 129 return e.exit_code 130 return 0 131 132 def generate_from_idl_py(self, idl_file): 133 cmd = ['python', 134 'bindings/scripts/unstable/idl_compiler.py', 135 '--output-dir', self.output_directory_py, 136 '--idl-attributes-file', 'bindings/IDLExtendedAttributes.txt', 137 '--include', '.', 138 '--interface-dependencies-file', 139 self.interface_dependencies_filename, 140 idl_file] 141 try: 142 self.run_command(cmd) 143 except ScriptError, e: 144 print 'ERROR: idl_compiler.py: ' + os.path.basename(idl_file) 145 print e.output 146 return e.exit_code 147 return 0 148 149 def generate_interface_dependencies(self): 150 idl_files_list_file, main_idl_files_list_filename = provider.newtempfile() 151 idl_paths = [os.path.join(input_directory, input_file) 152 for input_file in os.listdir(input_directory) 153 if input_file.endswith('.idl')] 154 idl_files_list_contents = ''.join(idl_path + '\n' 155 for idl_path in idl_paths) 156 os.write(idl_files_list_file, idl_files_list_contents) 157 support_idl_files_list_file, support_idl_files_list_filename = provider.newtempfile() 158 support_idl_paths = [os.path.join(support_input_directory, input_file) 159 for input_file in os.listdir(support_input_directory) 160 if input_file.endswith('.idl')] 161 support_idl_files_list_contents = ''.join(idl_path + '\n' 162 for idl_path in support_idl_paths) 163 os.write(support_idl_files_list_file, support_idl_files_list_contents) 164 165 # Dummy files, required by compute_dependencies but not checked 166 _, window_constructors_file = provider.newtempfile() 167 _, workerglobalscope_constructors_file = provider.newtempfile() 168 _, sharedworkerglobalscope_constructors_file = provider.newtempfile() 169 _, dedicatedworkerglobalscope_constructors_file = provider.newtempfile() 170 _, serviceworkersglobalscope_constructors_file = provider.newtempfile() 171 cmd = ['python', 172 'bindings/scripts/compute_dependencies.py', 173 '--main-idl-files-list', main_idl_files_list_filename, 174 '--support-idl-files-list', support_idl_files_list_filename, 175 '--interface-dependencies-file', self.interface_dependencies_filename, 176 '--bindings-derived-sources-file', self.derived_sources_list_filename, 177 '--window-constructors-file', window_constructors_file, 178 '--workerglobalscope-constructors-file', workerglobalscope_constructors_file, 179 '--sharedworkerglobalscope-constructors-file', sharedworkerglobalscope_constructors_file, 180 '--dedicatedworkerglobalscope-constructors-file', dedicatedworkerglobalscope_constructors_file, 181 '--serviceworkerglobalscope-constructors-file', serviceworkersglobalscope_constructors_file, 182 '--event-names-file', self.event_names_filename, 183 '--write-file-only-if-changed', '0'] 184 185 if self.reset_results and self.verbose: 186 print 'Reset results: EventInterfaces.in' 187 try: 188 self.run_command(cmd) 189 except ScriptError, e: 190 print 'ERROR: compute_dependencies.py' 191 print e.output 192 return e.exit_code 193 return 0 194 195 def identical_file(self, reference_filename, output_filename): 196 reference_basename = os.path.basename(reference_filename) 197 cmd = ['diff', 198 '-u', 199 '-N', 200 reference_filename, 201 output_filename] 202 try: 203 self.run_command(cmd) 204 except ScriptError, e: 205 # run_command throws an exception on diff (b/c non-zero exit code) 206 print 'FAIL: %s' % reference_basename 207 print e.output 208 return False 209 210 if self.verbose: 211 print 'PASS: %s' % reference_basename 212 return True 213 214 def identical_output_files(self, output_directory): 215 file_pairs = [(os.path.join(reference_directory, output_file), 216 os.path.join(output_directory, output_file)) 217 for output_file in os.listdir(output_directory) 218 # FIXME: add option to compiler to not generate tables 219 if output_file != 'parsetab.py'] 220 return all([self.identical_file(reference_filename, output_filename) 221 for (reference_filename, output_filename) in file_pairs]) 222 223 def no_excess_files(self): 224 generated_files = set(os.listdir(self.output_directory)) 225 generated_files.add('.svn') # Subversion working copy directory 226 excess_files = [output_file 227 for output_file in os.listdir(reference_directory) 228 if output_file not in generated_files] 229 if excess_files: 230 print ('Excess reference files! ' 231 '(probably cruft from renaming or deleting):\n' + 232 '\n'.join(excess_files)) 233 return False 234 return True 235 236 def run_tests(self): 237 # Generate output, immediately dying on failure 238 if self.generate_interface_dependencies(): 239 return False 240 241 for directory in [input_directory, support_input_directory]: 242 for input_filename in os.listdir(directory): 243 if not input_filename.endswith('.idl'): 244 continue 245 idl_path = os.path.join(directory, input_filename) 246 if self.generate_from_idl_pl(idl_path): 247 return False 248 if self.reset_results and self.verbose: 249 print 'Reset results: %s' % input_filename 250 if not self.test_python: 251 continue 252 if (input_filename in SKIP_PYTHON or 253 directory == support_input_directory): 254 if self.verbose: 255 print 'SKIP: %s' % input_filename 256 continue 257 if self.generate_from_idl_py(idl_path): 258 return False 259 260 # Detect all changes 261 passed = self.identical_file(reference_event_names_filename, 262 self.event_names_filename) 263 passed &= self.identical_output_files(self.output_directory) 264 if self.test_python: 265 if self.verbose: 266 print 267 print 'Python:' 268 passed &= self.identical_output_files(self.output_directory_py) 269 passed &= self.no_excess_files() 270 return passed 271 272 def main(self): 273 current_scm = detect_scm_system(os.curdir) 274 os.chdir(os.path.join(current_scm.checkout_root, 'Source')) 275 276 all_tests_passed = self.run_tests() 277 if all_tests_passed: 278 if self.verbose: 279 print 280 print PASS_MESSAGE 281 return 0 282 print 283 print FAIL_MESSAGE 284 return -1 285