OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright 2014 The Chromium Authors. All rights reserved. | 2 # Copyright 2014 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Compare the artifacts from two builds.""" | 6 """Compare the artifacts from two builds.""" |
7 | 7 |
8 import difflib | 8 import difflib |
9 import json | 9 import json |
10 import optparse | 10 import optparse |
11 import os | 11 import os |
12 import struct | 12 import struct |
13 import sys | 13 import sys |
14 import time | 14 import time |
15 | 15 |
16 | 16 |
17 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) | 17 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) |
18 | 18 |
19 | 19 |
| 20 # List of files that are known to be non deterministic. This is a temporary |
| 21 # workaround to find regression on the deterministic builders. |
| 22 # TODO(sebmarchand): Remove this once all the files are deterministic. |
| 23 WHITELIST = { |
| 24 'win': { |
| 25 'base_unittests.exe', |
| 26 'base_unittests.isolated', |
| 27 'browser_tests.exe', |
| 28 'browser_tests.isolated', |
| 29 'chrome.dll', |
| 30 'chrome.exe', |
| 31 'chrome_child.dll', |
| 32 'chrome_watcher.dll', |
| 33 'clearkeycdm.dll', |
| 34 'content_browsertests.exe', |
| 35 'content_browsertests.isolated', |
| 36 'content_unittests.exe', |
| 37 'content_unittests.isolated', |
| 38 'd8.exe', |
| 39 'delegate_execute.exe', |
| 40 'delegate_execute_unittests.exe', |
| 41 'interactive_ui_tests.exe', |
| 42 'interactive_ui_tests.isolated', |
| 43 'metro_driver.dll', |
| 44 'mock_nacl_gdb.exe', |
| 45 'net_unittests.exe', |
| 46 'net_unittests.isolated', |
| 47 'npapi_test_plugin.dll', |
| 48 'pdf.dll', |
| 49 'peerconnection_server.exe', |
| 50 'ppapi_nacl_tests_pnacl_newlib_x32.nexe', |
| 51 'ppapi_nacl_tests_pnacl_newlib_x64.nexe', |
| 52 'sync_integration_tests.exe', |
| 53 'sync_integration_tests.isolated', |
| 54 'test_registrar.exe', |
| 55 'unit_tests.exe', |
| 56 'unit_tests.isolated', |
| 57 }, |
| 58 'linux': { |
| 59 'browser_tests.isolated', |
| 60 'nacl_helper_nonsfi', |
| 61 'nacl_irt_x86_64.nexe', |
| 62 'ppapi_nacl_tests_glibc_x64.nexe', |
| 63 'ppapi_nacl_tests_newlib_x64.nexe', |
| 64 'ppapi_nacl_tests_pnacl_newlib_x64.nexe', |
| 65 'unit_tests.isolated', |
| 66 }, |
| 67 'mac': { }, |
| 68 'android': { }, |
| 69 'ios': { }, |
| 70 } |
| 71 |
20 def get_files_to_compare(build_dir, recursive=False): | 72 def get_files_to_compare(build_dir, recursive=False): |
21 """Get the list of files to compare.""" | 73 """Get the list of files to compare.""" |
22 allowed = frozenset( | 74 allowed = frozenset( |
23 ('', '.apk', '.app', '.dll', '.dylib', '.exe', '.nexe', '.so')) | 75 ('', '.apk', '.app', '.dll', '.dylib', '.exe', '.nexe', '.so')) |
24 non_x_ok_exts = frozenset(('.apk', '.isolated')) | 76 non_x_ok_exts = frozenset(('.apk', '.isolated')) |
25 def check(f): | 77 def check(f): |
26 if not os.path.isfile(f): | 78 if not os.path.isfile(f): |
27 return False | 79 return False |
28 if os.path.basename(f).startswith('.'): | 80 if os.path.basename(f).startswith('.'): |
29 return False | 81 return False |
(...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
117 # else, falls through binary comparison, it must be binary equal too. | 169 # else, falls through binary comparison, it must be binary equal too. |
118 | 170 |
119 file_len = os.stat(first_filepath).st_size | 171 file_len = os.stat(first_filepath).st_size |
120 if file_len != os.stat(second_filepath).st_size: | 172 if file_len != os.stat(second_filepath).st_size: |
121 return 'different size: %d != %d' % ( | 173 return 'different size: %d != %d' % ( |
122 file_len, os.stat(second_filepath).st_size) | 174 file_len, os.stat(second_filepath).st_size) |
123 | 175 |
124 return diff_binary(first_filepath, second_filepath, file_len) | 176 return diff_binary(first_filepath, second_filepath, file_len) |
125 | 177 |
126 | 178 |
127 def compare_build_artifacts(first_dir, second_dir, recursive=False): | 179 def compare_build_artifacts(first_dir, second_dir, target_platform, |
| 180 recursive=False): |
128 """Compare the artifacts from two distinct builds.""" | 181 """Compare the artifacts from two distinct builds.""" |
129 if not os.path.isdir(first_dir): | 182 if not os.path.isdir(first_dir): |
130 print >> sys.stderr, '%s isn\'t a valid directory.' % first_dir | 183 print >> sys.stderr, '%s isn\'t a valid directory.' % first_dir |
131 return 1 | 184 return 1 |
132 if not os.path.isdir(second_dir): | 185 if not os.path.isdir(second_dir): |
133 print >> sys.stderr, '%s isn\'t a valid directory.' % second_dir | 186 print >> sys.stderr, '%s isn\'t a valid directory.' % second_dir |
134 return 1 | 187 return 1 |
135 | 188 |
136 with open(os.path.join(BASE_DIR, 'deterministic_build_blacklist.json')) as f: | 189 with open(os.path.join(BASE_DIR, 'deterministic_build_blacklist.json')) as f: |
137 blacklist = frozenset(json.load(f)) | 190 blacklist = frozenset(json.load(f)) |
138 | 191 |
139 res = 0 | 192 whitelist = WHITELIST[target_platform] |
| 193 |
| 194 unexpected_failures = 0 |
| 195 expected_failures = 0 |
140 first_list = get_files_to_compare(first_dir, recursive) - blacklist | 196 first_list = get_files_to_compare(first_dir, recursive) - blacklist |
141 second_list = get_files_to_compare(second_dir, recursive) - blacklist | 197 second_list = get_files_to_compare(second_dir, recursive) - blacklist |
142 | 198 |
143 diff = first_list.symmetric_difference(second_list) | 199 diff = first_list.symmetric_difference(second_list) |
144 if diff: | 200 if diff: |
145 print >> sys.stderr, 'Different list of files in both directories' | 201 print >> sys.stderr, 'Different list of files in both directories' |
146 print >> sys.stderr, '\n'.join(' ' + i for i in sorted(diff)) | 202 print >> sys.stderr, '\n'.join(' ' + i for i in sorted(diff)) |
147 res += len(diff) | 203 unexpected_failures += len(diff) |
148 | 204 |
149 epoch_hex = struct.pack('<I', int(time.time())).encode('hex') | 205 epoch_hex = struct.pack('<I', int(time.time())).encode('hex') |
150 print('Epoch: %s' % | 206 print('Epoch: %s' % |
151 ' '.join(epoch_hex[i:i+2] for i in xrange(0, len(epoch_hex), 2))) | 207 ' '.join(epoch_hex[i:i+2] for i in xrange(0, len(epoch_hex), 2))) |
152 max_filepath_len = max(len(n) for n in first_list & second_list) | 208 max_filepath_len = max(len(n) for n in first_list & second_list) |
153 for f in sorted(first_list & second_list): | 209 for f in sorted(first_list & second_list): |
154 first_file = os.path.join(first_dir, f) | 210 first_file = os.path.join(first_dir, f) |
155 second_file = os.path.join(second_dir, f) | 211 second_file = os.path.join(second_dir, f) |
156 result = compare_files(first_file, second_file) | 212 result = compare_files(first_file, second_file) |
157 if not result: | 213 if not result: |
158 result = 'equal' | 214 result = 'equal' |
159 else: | 215 else: |
160 result = 'DIFFERENT: %s' % result | 216 expected = 'unexpected' |
161 res += 1 | 217 if f in whitelist: |
| 218 expected_failures += 1 |
| 219 expected = 'expected' |
| 220 else: |
| 221 unexpected_failures += 1 |
| 222 result = 'DIFFERENT (%s): %s' % (expected, result) |
162 print('%-*s: %s' % (max_filepath_len, f, result)) | 223 print('%-*s: %s' % (max_filepath_len, f, result)) |
163 | 224 |
164 print '%d files are equal, %d are different.' % ( | 225 print '%d files are equal, %d are different.' % ( |
165 len(first_list & second_list) - res, res) | 226 len(first_list & second_list) - expected_failures - unexpected_failures, |
| 227 expected_failures + unexpected_failures) |
166 | 228 |
167 return 0 if res == 0 else 1 | 229 return 0 if unexpected_failures == 0 else 1 |
168 | 230 |
169 | 231 |
170 def main(): | 232 def main(): |
171 parser = optparse.OptionParser(usage='%prog [options]') | 233 parser = optparse.OptionParser(usage='%prog [options]') |
172 parser.add_option( | 234 parser.add_option( |
173 '-f', '--first-build-dir', help='The first build directory.') | 235 '-f', '--first-build-dir', help='The first build directory.') |
174 parser.add_option( | 236 parser.add_option( |
175 '-s', '--second-build-dir', help='The second build directory.') | 237 '-s', '--second-build-dir', help='The second build directory.') |
176 parser.add_option('-r', '--recursive', action='store_true', default=False, | 238 parser.add_option('-r', '--recursive', action='store_true', default=False, |
177 help='Indicates if the comparison should be recursive.') | 239 help='Indicates if the comparison should be recursive.') |
| 240 parser.add_option('-t', '--target-platform', help='The target platform.') |
178 options, _ = parser.parse_args() | 241 options, _ = parser.parse_args() |
179 | 242 |
180 if not options.first_build_dir: | 243 if not options.first_build_dir: |
181 parser.error('--first-build-dir is required') | 244 parser.error('--first-build-dir is required') |
182 if not options.second_build_dir: | 245 if not options.second_build_dir: |
183 parser.error('--second-build-dir is required') | 246 parser.error('--second-build-dir is required') |
| 247 if not options.target_platform: |
| 248 parser.error('--target-platform is required') |
184 | 249 |
185 return compare_build_artifacts(os.path.abspath(options.first_build_dir), | 250 return compare_build_artifacts(os.path.abspath(options.first_build_dir), |
186 os.path.abspath(options.second_build_dir), | 251 os.path.abspath(options.second_build_dir), |
| 252 options.target_platform, |
187 options.recursive) | 253 options.recursive) |
188 | 254 |
189 | 255 |
190 if __name__ == '__main__': | 256 if __name__ == '__main__': |
191 sys.exit(main()) | 257 sys.exit(main()) |
OLD | NEW |