| 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. |
| (...skipping 11 matching lines...) Expand all Loading... |
| 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 filecmp |
| 26 import fnmatch | 26 import fnmatch |
| 27 import os | 27 import os |
| 28 import shutil | 28 import shutil |
| 29 import sys | 29 import sys |
| 30 import tempfile | 30 import tempfile |
| 31 | 31 |
| 32 from webkitpy.common.checkout.scm.detection import detect_scm_system | 32 from webkitpy.common.system.executive import Executive |
| 33 from webkitpy.common.system import executive | |
| 34 | 33 |
| 35 # Add Source path to PYTHONPATH to support function calls to bindings/scripts | 34 # Add Source path to PYTHONPATH to support function calls to bindings/scripts |
| 36 # for compute_interfaces_info and idl_compiler | 35 # for compute_interfaces_info and idl_compiler |
| 37 module_path = os.path.dirname(__file__) | 36 module_path = os.path.dirname(__file__) |
| 38 source_path = os.path.normpath(os.path.join(module_path, os.pardir, os.pardir, | 37 source_path = os.path.normpath(os.path.join(module_path, os.pardir, os.pardir, |
| 39 os.pardir, os.pardir, 'Source')) | 38 os.pardir, os.pardir, 'Source')) |
| 40 sys.path.append(source_path) | 39 sys.path.append(source_path) |
| 41 | 40 |
| 42 from bindings.scripts.compute_interfaces_info import compute_interfaces_info, in
terfaces_info | 41 from bindings.scripts.compute_interfaces_info import compute_interfaces_info, in
terfaces_info |
| 43 from bindings.scripts.idl_compiler import IdlCompilerV8 | 42 from bindings.scripts.idl_compiler import IdlCompilerV8 |
| (...skipping 13 matching lines...) Expand all Loading... |
| 57 | 56 |
| 58 DEPENDENCY_IDL_FILES = frozenset([ | 57 DEPENDENCY_IDL_FILES = frozenset([ |
| 59 'TestImplements.idl', | 58 'TestImplements.idl', |
| 60 'TestImplements2.idl', | 59 'TestImplements2.idl', |
| 61 'TestImplements3.idl', | 60 'TestImplements3.idl', |
| 62 'TestPartialInterface.idl', | 61 'TestPartialInterface.idl', |
| 63 'TestPartialInterface2.idl', | 62 'TestPartialInterface2.idl', |
| 64 ]) | 63 ]) |
| 65 | 64 |
| 66 | 65 |
| 67 EXTENDED_ATTRIBUTES_FILE = 'bindings/IDLExtendedAttributes.txt' | 66 EXTENDED_ATTRIBUTES_FILE = os.path.join(source_path, |
| 67 'bindings/IDLExtendedAttributes.txt') |
| 68 | 68 |
| 69 all_input_directory = '.' # Relative to Source/ | 69 test_input_directory = os.path.join(source_path, 'bindings', 'tests', 'idls') |
| 70 test_input_directory = os.path.join('bindings', 'tests', 'idls') | 70 reference_directory = os.path.join(source_path, 'bindings', 'tests', 'results') |
| 71 reference_directory = os.path.join('bindings', 'tests', 'results') | |
| 72 | 71 |
| 73 | 72 |
| 74 class ScopedTempDir(object): | 73 class ScopedTempDir(object): |
| 75 """Wrapper for tempfile.mkdtemp() so it's usable with 'with' statement.""" | 74 """Wrapper for tempfile.mkdtemp() so it's usable with 'with' statement.""" |
| 76 def __init__(self): | 75 def __init__(self): |
| 77 self.dir_path = tempfile.mkdtemp() | 76 self.dir_path = tempfile.mkdtemp() |
| 78 | 77 |
| 79 def __enter__(self): | 78 def __enter__(self): |
| 80 return self.dir_path | 79 return self.dir_path |
| 81 | 80 |
| 82 def __exit__(self, exc_type, exc_value, traceback): | 81 def __exit__(self, exc_type, exc_value, traceback): |
| 83 # The temporary directory is used as an output directory, so it | 82 # The temporary directory is used as an output directory, so it |
| 84 # contains unknown files (it isn't empty), hence use rmtree | 83 # contains unknown files (it isn't empty), hence use rmtree |
| 85 shutil.rmtree(self.dir_path) | 84 shutil.rmtree(self.dir_path) |
| 86 | 85 |
| 87 | 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 future |
| 91 # versions of Python: |
| 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 |
| 88 | 99 |
| 89 class BindingsTests(object): | 100 # We compute interfaces info for *all* IDL files, not just test IDL |
| 90 def __init__(self, output_directory, verbose): | 101 # files, as code generator output depends on inheritance (both ancestor |
| 91 self.verbose = verbose | 102 # chain and inherited extended attributes), and some real interfaces |
| 92 self.executive = executive.Executive() | 103 # are special-cased, such as Node. |
| 93 self.idl_compiler = None | 104 # |
| 94 self.output_directory = output_directory | 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 compute_interfaces_info(idl_paths_recursive(source_path)) |
| 95 | 111 |
| 96 def diff(self, filename1, filename2): | 112 |
| 113 def bindings_tests(output_directory, verbose): |
| 114 executive = Executive() |
| 115 |
| 116 def diff(filename1, filename2): |
| 97 # Python's difflib module is too slow, especially on long output, so | 117 # Python's difflib module is too slow, especially on long output, so |
| 98 # run external diff(1) command | 118 # run external diff(1) command |
| 99 cmd = ['diff', | 119 cmd = ['diff', |
| 100 '-u', | 120 '-u', # unified format |
| 101 '-N', | 121 '-N', # treat absent files as empty |
| 102 filename1, | 122 filename1, |
| 103 filename2] | 123 filename2] |
| 104 # Return output and don't raise exception, even though diff(1) has | 124 # Return output and don't raise exception, even though diff(1) has |
| 105 # non-zero exit if files differ. | 125 # non-zero exit if files differ. |
| 106 return self.executive.run_command(cmd, error_handler=lambda x: None) | 126 return executive.run_command(cmd, error_handler=lambda x: None) |
| 107 | 127 |
| 108 def generate_from_idl(self, idl_file): | 128 def delete_cache_files(): |
| 109 idl_file_fullpath = os.path.realpath(idl_file) | |
| 110 self.idl_compiler.compile_file(idl_file_fullpath) | |
| 111 | |
| 112 def generate_interface_dependencies(self): | |
| 113 def idl_paths_recursive(directory): | |
| 114 idl_paths = [] | |
| 115 for dirpath, _, files in os.walk(directory): | |
| 116 idl_paths.extend(os.path.join(dirpath, filename) | |
| 117 for filename in fnmatch.filter(files, '*.idl')) | |
| 118 return idl_paths | |
| 119 | |
| 120 # We compute interfaces info for *all* IDL files, not just test IDL | |
| 121 # files, as code generator output depends on inheritance (both ancestor | |
| 122 # chain and inherited extended attributes), and some real interfaces | |
| 123 # are special-cased, such as Node. | |
| 124 # | |
| 125 # For example, when testing the behavior of interfaces that inherit | |
| 126 # from Node, we also need to know that these inherit from EventTarget, | |
| 127 # since this is also special-cased and Node inherits from EventTarget, | |
| 128 # but this inheritance information requires computing dependencies for | |
| 129 # the real Node.idl file. | |
| 130 compute_interfaces_info(idl_paths_recursive(all_input_directory)) | |
| 131 | |
| 132 def delete_cache_files(self): | |
| 133 # FIXME: Instead of deleting cache files, don't generate them. | 129 # FIXME: Instead of deleting cache files, don't generate them. |
| 134 cache_files = [os.path.join(self.output_directory, output_file) | 130 cache_files = [os.path.join(output_directory, output_file) |
| 135 for output_file in os.listdir(self.output_directory) | 131 for output_file in os.listdir(output_directory) |
| 136 if (output_file in ('lextab.py', # PLY lex | 132 if (output_file in ('lextab.py', # PLY lex |
| 137 'lextab.pyc', | 133 'lextab.pyc', |
| 138 'parsetab.pickle') or # PLY yacc | 134 'parsetab.pickle') or # PLY yacc |
| 139 output_file.endswith('.cache'))] # Jinja | 135 output_file.endswith('.cache'))] # Jinja |
| 140 for cache_file in cache_files: | 136 for cache_file in cache_files: |
| 141 os.remove(cache_file) | 137 os.remove(cache_file) |
| 142 | 138 |
| 143 def identical_file(self, reference_filename, output_filename): | 139 def identical_file(reference_filename, output_filename): |
| 144 reference_basename = os.path.basename(reference_filename) | 140 reference_basename = os.path.basename(reference_filename) |
| 145 | 141 |
| 146 if not filecmp.cmp(reference_filename, output_filename): | 142 if not filecmp.cmp(reference_filename, output_filename): |
| 147 print 'FAIL: %s' % reference_basename | 143 print 'FAIL: %s' % reference_basename |
| 148 print self.diff(reference_filename, output_filename) | 144 print diff(reference_filename, output_filename) |
| 149 return False | 145 return False |
| 150 | 146 |
| 151 if self.verbose: | 147 if verbose: |
| 152 print 'PASS: %s' % reference_basename | 148 print 'PASS: %s' % reference_basename |
| 153 return True | 149 return True |
| 154 | 150 |
| 155 def identical_output_files(self): | 151 def identical_output_files(): |
| 156 file_pairs = [(os.path.join(reference_directory, output_file), | 152 file_pairs = [(os.path.join(reference_directory, output_file), |
| 157 os.path.join(self.output_directory, output_file)) | 153 os.path.join(output_directory, output_file)) |
| 158 for output_file in os.listdir(self.output_directory)] | 154 for output_file in os.listdir(output_directory)] |
| 159 return all([self.identical_file(reference_filename, output_filename) | 155 return all([identical_file(reference_filename, output_filename) |
| 160 for (reference_filename, output_filename) in file_pairs]) | 156 for (reference_filename, output_filename) in file_pairs]) |
| 161 | 157 |
| 162 def no_excess_files(self): | 158 def no_excess_files(): |
| 163 generated_files = set(os.listdir(self.output_directory)) | 159 generated_files = set(os.listdir(output_directory)) |
| 164 generated_files.add('.svn') # Subversion working copy directory | 160 generated_files.add('.svn') # Subversion working copy directory |
| 165 excess_files = [output_file | 161 excess_files = [output_file |
| 166 for output_file in os.listdir(reference_directory) | 162 for output_file in os.listdir(reference_directory) |
| 167 if output_file not in generated_files] | 163 if output_file not in generated_files] |
| 168 if excess_files: | 164 if excess_files: |
| 169 print ('Excess reference files! ' | 165 print ('Excess reference files! ' |
| 170 '(probably cruft from renaming or deleting):\n' + | 166 '(probably cruft from renaming or deleting):\n' + |
| 171 '\n'.join(excess_files)) | 167 '\n'.join(excess_files)) |
| 172 return False | 168 return False |
| 173 return True | 169 return True |
| 174 | 170 |
| 175 def run_tests(self): | 171 generate_interface_dependencies() |
| 176 self.generate_interface_dependencies() | 172 idl_compiler = IdlCompilerV8(output_directory, |
| 177 self.idl_compiler = IdlCompilerV8(self.output_directory, | 173 EXTENDED_ATTRIBUTES_FILE, |
| 178 EXTENDED_ATTRIBUTES_FILE, | 174 interfaces_info=interfaces_info, |
| 179 interfaces_info=interfaces_info, | 175 only_if_changed=True) |
| 180 only_if_changed=True) | |
| 181 | 176 |
| 182 for input_filename in os.listdir(test_input_directory): | 177 idl_basenames = [filename |
| 183 if not input_filename.endswith('.idl'): | 178 for filename in os.listdir(test_input_directory) |
| 184 continue | 179 if (filename.endswith('.idl') and |
| 185 if input_filename in DEPENDENCY_IDL_FILES: | 180 # Dependencies aren't built |
| 186 # Dependencies aren't built (they are used by the dependent) | 181 # (they are used by the dependent) |
| 187 if self.verbose: | 182 filename not in DEPENDENCY_IDL_FILES)] |
| 188 print 'DEPENDENCY: %s' % input_filename | 183 for idl_basename in idl_basenames: |
| 189 continue | 184 idl_path = os.path.realpath( |
| 185 os.path.join(test_input_directory, idl_basename)) |
| 186 idl_compiler.compile_file(idl_path) |
| 187 if verbose: |
| 188 print 'Compiled: %s' % filename |
| 190 | 189 |
| 191 idl_path = os.path.join(test_input_directory, input_filename) | 190 delete_cache_files() |
| 192 self.generate_from_idl(idl_path) | |
| 193 if self.verbose: | |
| 194 print 'Compiled: %s' % input_filename | |
| 195 | 191 |
| 196 self.delete_cache_files() | 192 # Detect all changes |
| 193 passed = identical_output_files() |
| 194 passed &= no_excess_files() |
| 197 | 195 |
| 198 # Detect all changes | 196 if passed: |
| 199 passed = self.identical_output_files() | 197 if verbose: |
| 200 passed &= self.no_excess_files() | 198 print |
| 201 return passed | 199 print PASS_MESSAGE |
| 202 | 200 return 0 |
| 203 def main(self): | 201 print |
| 204 current_scm = detect_scm_system(os.curdir) | 202 print FAIL_MESSAGE |
| 205 os.chdir(os.path.join(current_scm.checkout_root, 'Source')) | 203 return 1 |
| 206 | |
| 207 all_tests_passed = self.run_tests() | |
| 208 if all_tests_passed: | |
| 209 if self.verbose: | |
| 210 print | |
| 211 print PASS_MESSAGE | |
| 212 return 0 | |
| 213 print | |
| 214 print FAIL_MESSAGE | |
| 215 return 1 | |
| 216 | 204 |
| 217 | 205 |
| 218 def run_bindings_tests(reset_results, verbose): | 206 def run_bindings_tests(reset_results, verbose): |
| 219 # Generate output into the reference directory if resetting results, or | 207 # Generate output into the reference directory if resetting results, or |
| 220 # a temp directory if not. | 208 # a temp directory if not. |
| 221 if reset_results: | 209 if reset_results: |
| 222 print 'Resetting results' | 210 print 'Resetting results' |
| 223 return BindingsTests(reference_directory, verbose).main() | 211 return bindings_tests(reference_directory, verbose) |
| 224 with ScopedTempDir() as temp_dir: | 212 with ScopedTempDir() as temp_dir: |
| 225 return BindingsTests(temp_dir, verbose).main() | 213 return bindings_tests(temp_dir, verbose) |
| OLD | NEW |