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 |