Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright (C) 2011 Google Inc. All rights reserved. | 1 # Copyright (C) 2011 Google Inc. All rights reserved. |
| 2 # | 2 # |
| 3 # Redistribution and use in source and binary forms, with or without | 3 # Redistribution and use in source and binary forms, with or without |
| 4 # modification, are permitted provided that the following conditions | 4 # modification, are permitted provided that the following conditions |
| 5 # are met: | 5 # are met: |
| 6 # 1. Redistributions of source code must retain the above copyright | 6 # 1. Redistributions of source code must retain the above copyright |
| 7 # notice, this list of conditions and the following disclaimer. | 7 # notice, this list of conditions and the following disclaimer. |
| 8 # 2. Redistributions in binary form must reproduce the above copyright | 8 # 2. Redistributions in binary form must reproduce the above copyright |
| 9 # notice, this list of conditions and the following disclaimer in the | 9 # notice, this list of conditions and the following disclaimer in the |
| 10 # documentation and/or other materials provided with the distribution. | 10 # documentation and/or other materials provided with the distribution. |
| 11 # | 11 # |
| 12 # THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY | 12 # THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY |
| 13 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 13 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 14 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | 14 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 15 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR | 15 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR |
| 16 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | 16 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| 17 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | 17 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| 18 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | 18 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| 19 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | 19 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| 20 # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 20 # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 21 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 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. | 22 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 23 # | 23 # |
| 24 | 24 |
| 25 import filecmp | |
| 25 import fnmatch | 26 import fnmatch |
| 26 import os | 27 import os |
| 27 import shutil | 28 import shutil |
| 28 import sys | 29 import sys |
| 29 import tempfile | 30 import tempfile |
| 30 | 31 |
| 31 from webkitpy.common.checkout.scm.detection import detect_scm_system | 32 from webkitpy.common.checkout.scm.detection import detect_scm_system |
| 32 from webkitpy.common.system import executive | 33 from webkitpy.common.system import executive |
| 33 from webkitpy.common.system.executive import ScriptError | |
| 34 | 34 |
| 35 # Add Source path to PYTHONPATH to support function calls to bindings/scripts | 35 # Add Source path to PYTHONPATH to support function calls to bindings/scripts |
| 36 # for compute_interfaces_info and idl_compiler | 36 # for compute_interfaces_info and idl_compiler |
| 37 module_path = os.path.dirname(__file__) | 37 module_path = os.path.dirname(__file__) |
| 38 source_path = os.path.normpath(os.path.join(module_path, os.pardir, os.pardir, | 38 source_path = os.path.normpath(os.path.join(module_path, os.pardir, os.pardir, |
| 39 os.pardir, os.pardir, 'Source')) | 39 os.pardir, os.pardir, 'Source')) |
| 40 sys.path.append(source_path) | 40 sys.path.append(source_path) |
| 41 | 41 |
| 42 from bindings.scripts.compute_interfaces_info import compute_interfaces_info, in terfaces_info | 42 from bindings.scripts.compute_interfaces_info import compute_interfaces_info, in terfaces_info |
| 43 from bindings.scripts.idl_compiler import IdlCompilerV8 | 43 from bindings.scripts.idl_compiler import IdlCompilerV8 |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 64 ]) | 64 ]) |
| 65 | 65 |
| 66 | 66 |
| 67 EXTENDED_ATTRIBUTES_FILE = 'bindings/IDLExtendedAttributes.txt' | 67 EXTENDED_ATTRIBUTES_FILE = 'bindings/IDLExtendedAttributes.txt' |
| 68 | 68 |
| 69 all_input_directory = '.' # Relative to Source/ | 69 all_input_directory = '.' # Relative to Source/ |
| 70 test_input_directory = os.path.join('bindings', 'tests', 'idls') | 70 test_input_directory = os.path.join('bindings', 'tests', 'idls') |
| 71 reference_directory = os.path.join('bindings', 'tests', 'results') | 71 reference_directory = os.path.join('bindings', 'tests', 'results') |
| 72 | 72 |
| 73 | 73 |
| 74 class ScopedTempFileProvider(object): | 74 class ScopedTempDir(object): |
| 75 """Wrapper for tempfile.mkdtemp() so it's usable with 'with' statement.""" | |
| 75 def __init__(self): | 76 def __init__(self): |
| 76 self.file_handles = [] | 77 self.dir_path = tempfile.mkdtemp() |
| 77 self.file_paths = [] | |
| 78 self.dir_paths = [] | |
| 79 | 78 |
| 80 def __enter__(self): | 79 def __enter__(self): |
| 81 return self | 80 return self.dir_path |
| 82 | 81 |
| 83 def __exit__(self, exc_type, exc_value, traceback): | 82 def __exit__(self, exc_type, exc_value, traceback): |
| 84 for file_handle in self.file_handles: | 83 # The temporary directory is used as an output directory, so it |
| 85 os.close(file_handle) | 84 # contains unknown files (it isn't aren't empty), hence use rmtree |
|
haraken
2014/03/28 10:33:18
isn't aren't => isn't
Nils Barth (inactive)
2014/03/28 10:47:25
Done.
| |
| 86 for file_path in self.file_paths: | 85 shutil.rmtree(self.dir_path) |
| 87 os.remove(file_path) | |
| 88 for dir_path in self.dir_paths: | |
| 89 # Temporary directories are used as output directories, so they | |
| 90 # contains unknown files (they aren't empty), hence use rmtree | |
| 91 shutil.rmtree(dir_path) | |
| 92 | 86 |
| 93 def new_temp_file(self): | |
| 94 file_handle, file_path = tempfile.mkstemp() | |
| 95 self.file_handles.append(file_handle) | |
| 96 self.file_paths.append(file_path) | |
| 97 return file_handle, file_path | |
| 98 | |
| 99 def new_temp_dir(self): | |
| 100 dir_path = tempfile.mkdtemp() | |
| 101 self.dir_paths.append(dir_path) | |
| 102 return dir_path | |
| 103 | 87 |
| 104 | 88 |
| 105 class BindingsTests(object): | 89 class BindingsTests(object): |
| 106 def __init__(self, reset_results, verbose, provider): | 90 def __init__(self, output_directory, verbose): |
| 107 self.reset_results = reset_results | |
| 108 self.verbose = verbose | 91 self.verbose = verbose |
| 109 self.executive = executive.Executive() | 92 self.executive = executive.Executive() |
| 110 self.provider = provider | |
| 111 self.idl_compiler = None | 93 self.idl_compiler = None |
| 112 # Generate output into the reference directory if resetting results, or | 94 self.output_directory = output_directory |
| 113 # a temp directory if not. | |
| 114 if reset_results: | |
| 115 self.output_directory = reference_directory | |
| 116 else: | |
| 117 self.output_directory = provider.new_temp_dir() | |
| 118 | 95 |
| 119 def run_command(self, cmd): | 96 def diff(self, filename1, filename2): |
| 120 output = self.executive.run_command(cmd) | 97 # difflib is too slow, especially on long output, so run diff |
|
haraken
2014/03/28 10:33:18
'difflib' and 'diff' are confusing. Shall we use '
Nils Barth (inactive)
2014/03/28 10:47:25
Good point.
I've clarified the comment
(as "Python
| |
| 121 if output: | 98 cmd = ['diff', |
| 122 print output | 99 '-u', |
| 100 '-N', | |
| 101 filename1, | |
| 102 filename2] | |
| 103 # Return output and don't raise exception, even though diff has | |
| 104 # non-zero exit if files differ. | |
| 105 return self.executive.run_command(cmd, error_handler=lambda x: None) | |
|
haraken
2014/03/28 10:33:18
Does this invoke Python's built-in diff?
Nils Barth (inactive)
2014/03/28 10:47:25
No, it invokes an external command.
Python's built
| |
| 123 | 106 |
| 124 def generate_from_idl(self, idl_file): | 107 def generate_from_idl(self, idl_file): |
| 125 idl_file_fullpath = os.path.realpath(idl_file) | 108 idl_file_fullpath = os.path.realpath(idl_file) |
| 126 try: | 109 self.idl_compiler.compile_file(idl_file_fullpath) |
| 127 self.idl_compiler.compile_file(idl_file_fullpath) | |
| 128 except Exception as err: | |
| 129 print 'ERROR: idl_compiler.py: ' + os.path.basename(idl_file) | |
| 130 print err | |
| 131 return 1 | |
| 132 | |
| 133 return 0 | |
| 134 | 110 |
| 135 def generate_interface_dependencies(self): | 111 def generate_interface_dependencies(self): |
| 136 def idl_paths(directory): | 112 def idl_paths(directory): |
| 137 return [os.path.join(directory, input_file) | 113 return [os.path.join(directory, input_file) |
| 138 for input_file in os.listdir(directory) | 114 for input_file in os.listdir(directory) |
| 139 if input_file.endswith('.idl')] | 115 if input_file.endswith('.idl')] |
| 140 | 116 |
| 141 def idl_paths_recursive(directory): | 117 def idl_paths_recursive(directory): |
| 142 idl_paths = [] | 118 idl_paths = [] |
| 143 for dirpath, _, files in os.walk(directory): | 119 for dirpath, _, files in os.walk(directory): |
| 144 idl_paths.extend(os.path.join(dirpath, filename) | 120 idl_paths.extend(os.path.join(dirpath, filename) |
| 145 for filename in fnmatch.filter(files, '*.idl')) | 121 for filename in fnmatch.filter(files, '*.idl')) |
| 146 return idl_paths | 122 return idl_paths |
| 147 | 123 |
| 148 def write_list_file(idl_paths): | |
| 149 list_file, list_filename = self.provider.new_temp_file() | |
| 150 list_contents = ''.join(idl_path + '\n' | |
| 151 for idl_path in idl_paths) | |
| 152 os.write(list_file, list_contents) | |
| 153 return list_filename | |
| 154 | |
| 155 # We compute interfaces info for *all* IDL files, not just test IDL | 124 # We compute interfaces info for *all* IDL files, not just test IDL |
| 156 # files, as code generator output depends on inheritance (both ancestor | 125 # files, as code generator output depends on inheritance (both ancestor |
| 157 # chain and inherited extended attributes), and some real interfaces | 126 # chain and inherited extended attributes), and some real interfaces |
| 158 # are special-cased, such as Node. | 127 # are special-cased, such as Node. |
| 159 # | 128 # |
| 160 # For example, when testing the behavior of interfaces that inherit | 129 # For example, when testing the behavior of interfaces that inherit |
| 161 # from Node, we also need to know that these inherit from EventTarget, | 130 # from Node, we also need to know that these inherit from EventTarget, |
| 162 # since this is also special-cased and Node inherits from EventTarget, | 131 # since this is also special-cased and Node inherits from EventTarget, |
| 163 # but this inheritance information requires computing dependencies for | 132 # but this inheritance information requires computing dependencies for |
| 164 # the real Node.idl file. | 133 # the real Node.idl file. |
| 165 try: | 134 compute_interfaces_info(idl_paths_recursive(all_input_directory)) |
| 166 compute_interfaces_info(idl_paths_recursive(all_input_directory)) | |
| 167 except Exception as err: | |
| 168 print 'ERROR: compute_interfaces_info.py' | |
| 169 print err | |
| 170 return 1 | |
| 171 | |
| 172 return 0 | |
| 173 | 135 |
| 174 def delete_cache_files(self): | 136 def delete_cache_files(self): |
| 175 # FIXME: Instead of deleting cache files, don't generate them. | 137 # FIXME: Instead of deleting cache files, don't generate them. |
| 176 cache_files = [os.path.join(self.output_directory, output_file) | 138 cache_files = [os.path.join(self.output_directory, output_file) |
| 177 for output_file in os.listdir(self.output_directory) | 139 for output_file in os.listdir(self.output_directory) |
| 178 if (output_file in ('lextab.py', # PLY lex | 140 if (output_file in ('lextab.py', # PLY lex |
| 179 'lextab.pyc', | 141 'lextab.pyc', |
| 180 'parsetab.pickle') or # PLY yacc | 142 'parsetab.pickle') or # PLY yacc |
| 181 output_file.endswith('.cache'))] # Jinja | 143 output_file.endswith('.cache'))] # Jinja |
| 182 for cache_file in cache_files: | 144 for cache_file in cache_files: |
| 183 os.remove(cache_file) | 145 os.remove(cache_file) |
| 184 | 146 |
| 185 def identical_file(self, reference_filename, output_filename): | 147 def identical_file(self, reference_filename, output_filename): |
| 186 reference_basename = os.path.basename(reference_filename) | 148 reference_basename = os.path.basename(reference_filename) |
| 187 cmd = ['diff', | 149 |
| 188 '-u', | 150 if not filecmp.cmp(reference_filename, output_filename): |
| 189 '-N', | |
| 190 reference_filename, | |
| 191 output_filename] | |
| 192 try: | |
| 193 self.run_command(cmd) | |
| 194 except ScriptError as err: | |
| 195 # run_command throws an exception on diff (b/c non-zero exit code) | |
| 196 print 'FAIL: %s' % reference_basename | 151 print 'FAIL: %s' % reference_basename |
| 197 print err.output | 152 print self.diff(reference_filename, output_filename) |
| 198 return False | 153 return False |
| 199 | 154 |
| 200 if self.verbose: | 155 if self.verbose: |
| 201 print 'PASS: %s' % reference_basename | 156 print 'PASS: %s' % reference_basename |
| 202 return True | 157 return True |
| 203 | 158 |
| 204 def identical_output_files(self): | 159 def identical_output_files(self): |
| 205 file_pairs = [(os.path.join(reference_directory, output_file), | 160 file_pairs = [(os.path.join(reference_directory, output_file), |
| 206 os.path.join(self.output_directory, output_file)) | 161 os.path.join(self.output_directory, output_file)) |
| 207 for output_file in os.listdir(self.output_directory)] | 162 for output_file in os.listdir(self.output_directory)] |
| 208 return all([self.identical_file(reference_filename, output_filename) | 163 return all([self.identical_file(reference_filename, output_filename) |
| 209 for (reference_filename, output_filename) in file_pairs]) | 164 for (reference_filename, output_filename) in file_pairs]) |
| 210 | 165 |
| 211 def no_excess_files(self): | 166 def no_excess_files(self): |
| 212 generated_files = set(os.listdir(self.output_directory)) | 167 generated_files = set(os.listdir(self.output_directory)) |
| 213 generated_files.add('.svn') # Subversion working copy directory | 168 generated_files.add('.svn') # Subversion working copy directory |
| 214 excess_files = [output_file | 169 excess_files = [output_file |
| 215 for output_file in os.listdir(reference_directory) | 170 for output_file in os.listdir(reference_directory) |
| 216 if output_file not in generated_files] | 171 if output_file not in generated_files] |
| 217 if excess_files: | 172 if excess_files: |
| 218 print ('Excess reference files! ' | 173 print ('Excess reference files! ' |
| 219 '(probably cruft from renaming or deleting):\n' + | 174 '(probably cruft from renaming or deleting):\n' + |
| 220 '\n'.join(excess_files)) | 175 '\n'.join(excess_files)) |
| 221 return False | 176 return False |
| 222 return True | 177 return True |
| 223 | 178 |
| 224 def run_tests(self): | 179 def run_tests(self): |
| 225 # Generate output, immediately dying on failure | 180 self.generate_interface_dependencies() |
| 226 if self.generate_interface_dependencies(): | |
| 227 return False | |
| 228 | |
| 229 self.idl_compiler = IdlCompilerV8(self.output_directory, | 181 self.idl_compiler = IdlCompilerV8(self.output_directory, |
| 230 EXTENDED_ATTRIBUTES_FILE, | 182 EXTENDED_ATTRIBUTES_FILE, |
| 231 interfaces_info=interfaces_info, | 183 interfaces_info=interfaces_info, |
| 232 only_if_changed=True) | 184 only_if_changed=True) |
| 233 | 185 |
| 234 for input_filename in os.listdir(test_input_directory): | 186 for input_filename in os.listdir(test_input_directory): |
| 235 if not input_filename.endswith('.idl'): | 187 if not input_filename.endswith('.idl'): |
| 236 continue | 188 continue |
| 237 if input_filename in DEPENDENCY_IDL_FILES: | 189 if input_filename in DEPENDENCY_IDL_FILES: |
| 238 # Dependencies aren't built (they are used by the dependent) | 190 # Dependencies aren't built (they are used by the dependent) |
| 239 if self.verbose: | 191 if self.verbose: |
| 240 print 'DEPENDENCY: %s' % input_filename | 192 print 'DEPENDENCY: %s' % input_filename |
| 241 continue | 193 continue |
| 242 | 194 |
| 243 idl_path = os.path.join(test_input_directory, input_filename) | 195 idl_path = os.path.join(test_input_directory, input_filename) |
| 244 if self.generate_from_idl(idl_path): | 196 self.generate_from_idl(idl_path) |
| 245 return False | 197 if self.verbose: |
| 246 if self.reset_results and self.verbose: | 198 print 'Compiled: %s' % input_filename |
| 247 print 'Reset results: %s' % input_filename | |
| 248 | 199 |
| 249 self.delete_cache_files() | 200 self.delete_cache_files() |
| 250 | 201 |
| 251 # Detect all changes | 202 # Detect all changes |
| 252 passed = self.identical_output_files() | 203 passed = self.identical_output_files() |
| 253 passed &= self.no_excess_files() | 204 passed &= self.no_excess_files() |
| 254 return passed | 205 return passed |
| 255 | 206 |
| 256 def main(self): | 207 def main(self): |
| 257 current_scm = detect_scm_system(os.curdir) | 208 current_scm = detect_scm_system(os.curdir) |
| 258 os.chdir(os.path.join(current_scm.checkout_root, 'Source')) | 209 os.chdir(os.path.join(current_scm.checkout_root, 'Source')) |
| 259 | 210 |
| 260 all_tests_passed = self.run_tests() | 211 all_tests_passed = self.run_tests() |
| 261 if all_tests_passed: | 212 if all_tests_passed: |
| 262 if self.verbose: | 213 if self.verbose: |
| 263 print | 214 print |
| 264 print PASS_MESSAGE | 215 print PASS_MESSAGE |
| 265 return 0 | 216 return 0 |
| 266 print | 217 print |
| 267 print FAIL_MESSAGE | 218 print FAIL_MESSAGE |
| 268 return 1 | 219 return 1 |
| 269 | 220 |
| 270 | 221 |
| 271 def run_bindings_tests(reset_results, verbose): | 222 def run_bindings_tests(reset_results, verbose): |
| 272 with ScopedTempFileProvider() as provider: | 223 # Generate output into the reference directory if resetting results, or |
| 273 return BindingsTests(reset_results, verbose, provider).main() | 224 # a temp directory if not. |
| 225 if reset_results: | |
| 226 print 'Resetting results' | |
| 227 return BindingsTests(reference_directory, verbose).main() | |
| 228 with ScopedTempDir() as temp_dir: | |
| 229 return BindingsTests(temp_dir, verbose).main() | |
| OLD | NEW |