Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(75)

Side by Side Diff: Tools/Scripts/webkitpy/bindings/main.py

Issue 216753002: Cleanup run-bindings-tests and use filecmp (23% faster r-b-t) (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: Revised Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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 empty), hence use rmtree
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 # Python's difflib module is too slow, especially on long output, so
121 if output: 98 # run external diff(1) command
122 print output 99 cmd = ['diff',
100 '-u',
101 '-N',
102 filename1,
103 filename2]
104 # Return output and don't raise exception, even though diff(1) has
105 # non-zero exit if files differ.
106 return self.executive.run_command(cmd, error_handler=lambda x: None)
123 107
124 def generate_from_idl(self, idl_file): 108 def generate_from_idl(self, idl_file):
125 idl_file_fullpath = os.path.realpath(idl_file) 109 idl_file_fullpath = os.path.realpath(idl_file)
126 try: 110 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 111
135 def generate_interface_dependencies(self): 112 def generate_interface_dependencies(self):
136 def idl_paths(directory):
137 return [os.path.join(directory, input_file)
138 for input_file in os.listdir(directory)
139 if input_file.endswith('.idl')]
140
141 def idl_paths_recursive(directory): 113 def idl_paths_recursive(directory):
142 idl_paths = [] 114 idl_paths = []
143 for dirpath, _, files in os.walk(directory): 115 for dirpath, _, files in os.walk(directory):
144 idl_paths.extend(os.path.join(dirpath, filename) 116 idl_paths.extend(os.path.join(dirpath, filename)
145 for filename in fnmatch.filter(files, '*.idl')) 117 for filename in fnmatch.filter(files, '*.idl'))
146 return idl_paths 118 return idl_paths
147 119
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 120 # We compute interfaces info for *all* IDL files, not just test IDL
156 # files, as code generator output depends on inheritance (both ancestor 121 # files, as code generator output depends on inheritance (both ancestor
157 # chain and inherited extended attributes), and some real interfaces 122 # chain and inherited extended attributes), and some real interfaces
158 # are special-cased, such as Node. 123 # are special-cased, such as Node.
159 # 124 #
160 # For example, when testing the behavior of interfaces that inherit 125 # For example, when testing the behavior of interfaces that inherit
161 # from Node, we also need to know that these inherit from EventTarget, 126 # from Node, we also need to know that these inherit from EventTarget,
162 # since this is also special-cased and Node inherits from EventTarget, 127 # since this is also special-cased and Node inherits from EventTarget,
163 # but this inheritance information requires computing dependencies for 128 # but this inheritance information requires computing dependencies for
164 # the real Node.idl file. 129 # the real Node.idl file.
165 try: 130 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 131
174 def delete_cache_files(self): 132 def delete_cache_files(self):
175 # FIXME: Instead of deleting cache files, don't generate them. 133 # FIXME: Instead of deleting cache files, don't generate them.
176 cache_files = [os.path.join(self.output_directory, output_file) 134 cache_files = [os.path.join(self.output_directory, output_file)
177 for output_file in os.listdir(self.output_directory) 135 for output_file in os.listdir(self.output_directory)
178 if (output_file in ('lextab.py', # PLY lex 136 if (output_file in ('lextab.py', # PLY lex
179 'lextab.pyc', 137 'lextab.pyc',
180 'parsetab.pickle') or # PLY yacc 138 'parsetab.pickle') or # PLY yacc
181 output_file.endswith('.cache'))] # Jinja 139 output_file.endswith('.cache'))] # Jinja
182 for cache_file in cache_files: 140 for cache_file in cache_files:
183 os.remove(cache_file) 141 os.remove(cache_file)
184 142
185 def identical_file(self, reference_filename, output_filename): 143 def identical_file(self, reference_filename, output_filename):
186 reference_basename = os.path.basename(reference_filename) 144 reference_basename = os.path.basename(reference_filename)
187 cmd = ['diff', 145
188 '-u', 146 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 147 print 'FAIL: %s' % reference_basename
197 print err.output 148 print self.diff(reference_filename, output_filename)
198 return False 149 return False
199 150
200 if self.verbose: 151 if self.verbose:
201 print 'PASS: %s' % reference_basename 152 print 'PASS: %s' % reference_basename
202 return True 153 return True
203 154
204 def identical_output_files(self): 155 def identical_output_files(self):
205 file_pairs = [(os.path.join(reference_directory, output_file), 156 file_pairs = [(os.path.join(reference_directory, output_file),
206 os.path.join(self.output_directory, output_file)) 157 os.path.join(self.output_directory, output_file))
207 for output_file in os.listdir(self.output_directory)] 158 for output_file in os.listdir(self.output_directory)]
208 return all([self.identical_file(reference_filename, output_filename) 159 return all([self.identical_file(reference_filename, output_filename)
209 for (reference_filename, output_filename) in file_pairs]) 160 for (reference_filename, output_filename) in file_pairs])
210 161
211 def no_excess_files(self): 162 def no_excess_files(self):
212 generated_files = set(os.listdir(self.output_directory)) 163 generated_files = set(os.listdir(self.output_directory))
213 generated_files.add('.svn') # Subversion working copy directory 164 generated_files.add('.svn') # Subversion working copy directory
214 excess_files = [output_file 165 excess_files = [output_file
215 for output_file in os.listdir(reference_directory) 166 for output_file in os.listdir(reference_directory)
216 if output_file not in generated_files] 167 if output_file not in generated_files]
217 if excess_files: 168 if excess_files:
218 print ('Excess reference files! ' 169 print ('Excess reference files! '
219 '(probably cruft from renaming or deleting):\n' + 170 '(probably cruft from renaming or deleting):\n' +
220 '\n'.join(excess_files)) 171 '\n'.join(excess_files))
221 return False 172 return False
222 return True 173 return True
223 174
224 def run_tests(self): 175 def run_tests(self):
225 # Generate output, immediately dying on failure 176 self.generate_interface_dependencies()
226 if self.generate_interface_dependencies():
227 return False
228
229 self.idl_compiler = IdlCompilerV8(self.output_directory, 177 self.idl_compiler = IdlCompilerV8(self.output_directory,
230 EXTENDED_ATTRIBUTES_FILE, 178 EXTENDED_ATTRIBUTES_FILE,
231 interfaces_info=interfaces_info, 179 interfaces_info=interfaces_info,
232 only_if_changed=True) 180 only_if_changed=True)
233 181
234 for input_filename in os.listdir(test_input_directory): 182 for input_filename in os.listdir(test_input_directory):
235 if not input_filename.endswith('.idl'): 183 if not input_filename.endswith('.idl'):
236 continue 184 continue
237 if input_filename in DEPENDENCY_IDL_FILES: 185 if input_filename in DEPENDENCY_IDL_FILES:
238 # Dependencies aren't built (they are used by the dependent) 186 # Dependencies aren't built (they are used by the dependent)
239 if self.verbose: 187 if self.verbose:
240 print 'DEPENDENCY: %s' % input_filename 188 print 'DEPENDENCY: %s' % input_filename
241 continue 189 continue
242 190
243 idl_path = os.path.join(test_input_directory, input_filename) 191 idl_path = os.path.join(test_input_directory, input_filename)
244 if self.generate_from_idl(idl_path): 192 self.generate_from_idl(idl_path)
245 return False 193 if self.verbose:
246 if self.reset_results and self.verbose: 194 print 'Compiled: %s' % input_filename
247 print 'Reset results: %s' % input_filename
248 195
249 self.delete_cache_files() 196 self.delete_cache_files()
250 197
251 # Detect all changes 198 # Detect all changes
252 passed = self.identical_output_files() 199 passed = self.identical_output_files()
253 passed &= self.no_excess_files() 200 passed &= self.no_excess_files()
254 return passed 201 return passed
255 202
256 def main(self): 203 def main(self):
257 current_scm = detect_scm_system(os.curdir) 204 current_scm = detect_scm_system(os.curdir)
258 os.chdir(os.path.join(current_scm.checkout_root, 'Source')) 205 os.chdir(os.path.join(current_scm.checkout_root, 'Source'))
259 206
260 all_tests_passed = self.run_tests() 207 all_tests_passed = self.run_tests()
261 if all_tests_passed: 208 if all_tests_passed:
262 if self.verbose: 209 if self.verbose:
263 print 210 print
264 print PASS_MESSAGE 211 print PASS_MESSAGE
265 return 0 212 return 0
266 print 213 print
267 print FAIL_MESSAGE 214 print FAIL_MESSAGE
268 return 1 215 return 1
269 216
270 217
271 def run_bindings_tests(reset_results, verbose): 218 def run_bindings_tests(reset_results, verbose):
272 with ScopedTempFileProvider() as provider: 219 # Generate output into the reference directory if resetting results, or
273 return BindingsTests(reset_results, verbose, provider).main() 220 # a temp directory if not.
221 if reset_results:
222 print 'Resetting results'
223 return BindingsTests(reference_directory, verbose).main()
224 with ScopedTempDir() as temp_dir:
225 return BindingsTests(temp_dir, verbose).main()
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698