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 """Compare the artifacts from two builds.""" | 6 """Compare the artifacts from two builds.""" |
6 | 7 |
| 8 import difflib |
7 import json | 9 import json |
8 import optparse | 10 import optparse |
9 import os | 11 import os |
10 import sys | 12 import sys |
11 | 13 |
12 | 14 |
13 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) | 15 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) |
14 | 16 |
15 | 17 |
16 def get_files_to_compare(build_dir): | 18 def get_files_to_compare(build_dir): |
17 """Get the list of files to compare.""" | 19 """Get the list of files to compare.""" |
18 allowed = frozenset( | 20 allowed = frozenset( |
19 ('', '.app', '.dll', '.dylib', '.exe', '.nexe', '.so')) | 21 ('', '.app', '.dll', '.dylib', '.exe', '.nexe', '.so')) |
20 | 22 |
21 def check(f): | 23 def check(f): |
22 if not os.path.isfile(f): | 24 if not os.path.isfile(f): |
23 return False | 25 return False |
24 if os.path.basename(f).startswith('.'): | 26 if os.path.basename(f).startswith('.'): |
25 return False | 27 return False |
26 ext = os.path.splitext(f)[1] | 28 ext = os.path.splitext(f)[1] |
27 if ext == '.isolated': | 29 if ext == '.isolated': |
28 return True | 30 return True |
29 return ext in allowed and os.access(f, os.X_OK) | 31 return ext in allowed and os.access(f, os.X_OK) |
30 | 32 |
31 return set(f for f in os.listdir(build_dir) if | 33 return set(f for f in os.listdir(build_dir) if |
32 check(os.path.join(build_dir, f))) | 34 check(os.path.join(build_dir, f))) |
33 | 35 |
34 | 36 |
35 def compare_files(first_filepath, second_filepath): | 37 def compare_files(first_filepath, second_filepath): |
36 """Compare two binaries and return the number of differences between them. | 38 """Compares two binaries and return the number of differences between them. |
37 | 39 |
38 Returns -1 if the files have a different size. | 40 Returns None if the files are equal, a string otherwise. |
39 """ | 41 """ |
40 if os.stat(first_filepath).st_size != os.stat(second_filepath).st_size: | 42 file_len = os.stat(first_filepath).st_size |
41 return -1 | 43 if file_len != os.stat(second_filepath).st_size: |
| 44 return 'different size: %d != %d' % ( |
| 45 file_len, os.stat(second_filepath).st_size) |
42 | 46 |
43 # Read the files by chunks of 1MB. | |
44 chunk_size = 1024 * 1024 | 47 chunk_size = 1024 * 1024 |
45 diffs = 0 | 48 diffs = 0 |
46 with open(first_filepath, 'rb') as lhs: | 49 with open(first_filepath, 'rb') as lhs: |
47 with open(second_filepath, 'rb') as rhs: | 50 with open(second_filepath, 'rb') as rhs: |
48 while True: | 51 while True: |
49 lhs_data = lhs.read(chunk_size) | 52 lhs_data = lhs.read(chunk_size) |
50 rhs_data = rhs.read(chunk_size) | 53 rhs_data = rhs.read(chunk_size) |
51 if not lhs_data: | 54 if not lhs_data: |
52 break | 55 break |
53 diffs += sum(l != r for l, r in zip(lhs_data, rhs_data)) | 56 diffs += sum(l != r for l, r in zip(lhs_data, rhs_data)) |
| 57 if not diffs: |
| 58 return None |
54 | 59 |
55 return diffs | 60 result = '%d out of %d bytes are different (%.2f%%)' % ( |
| 61 diffs, file_len, 100.0 * diffs / file_len) |
| 62 |
| 63 if diffs and first_filepath.endswith('.isolated'): |
| 64 # Unpack the files. |
| 65 with open(first_filepath, 'rb') as f: |
| 66 lhs = json.dumps( |
| 67 json.load(f), indent=2, sort_keys=True, |
| 68 separators=(',', ': ')).splitlines() |
| 69 with open(second_filepath, 'rb') as f: |
| 70 rhs = json.dumps( |
| 71 json.load(f), indent=2, sort_keys=True, |
| 72 separators=(',', ': ')).splitlines() |
| 73 |
| 74 result += '\n' + '\n'.join( |
| 75 line for line in difflib.unified_diff(lhs, rhs) |
| 76 if not line.startswith(('---', '+++'))) |
| 77 return result |
56 | 78 |
57 | 79 |
58 def compare_build_artifacts(first_dir, second_dir): | 80 def compare_build_artifacts(first_dir, second_dir): |
59 """Compare the artifacts from two distinct builds.""" | 81 """Compare the artifacts from two distinct builds.""" |
60 if not os.path.isdir(first_dir): | 82 if not os.path.isdir(first_dir): |
61 print >> sys.stderr, '%s isn\'t a valid directory.' % first_dir | 83 print >> sys.stderr, '%s isn\'t a valid directory.' % first_dir |
62 return 1 | 84 return 1 |
63 if not os.path.isdir(second_dir): | 85 if not os.path.isdir(second_dir): |
64 print >> sys.stderr, '%s isn\'t a valid directory.' % second_dir | 86 print >> sys.stderr, '%s isn\'t a valid directory.' % second_dir |
65 return 1 | 87 return 1 |
66 | 88 |
67 with open(os.path.join(BASE_DIR, 'deterministic_build_blacklist.json')) as f: | 89 with open(os.path.join(BASE_DIR, 'deterministic_build_blacklist.json')) as f: |
68 blacklist = frozenset(json.load(f)) | 90 blacklist = frozenset(json.load(f)) |
69 | 91 |
70 res = 0 | 92 res = 0 |
71 first_list = get_files_to_compare(first_dir) - blacklist | 93 first_list = get_files_to_compare(first_dir) - blacklist |
72 second_list = get_files_to_compare(second_dir) - blacklist | 94 second_list = get_files_to_compare(second_dir) - blacklist |
73 | 95 |
74 diff = first_list.symmetric_difference(second_list) | 96 diff = first_list.symmetric_difference(second_list) |
75 if diff: | 97 if diff: |
76 print >> sys.stderr, 'Different list of files in both directories' | 98 print >> sys.stderr, 'Different list of files in both directories' |
77 print >> sys.stderr, '\n'.join(' ' + i for i in sorted(diff)) | 99 print >> sys.stderr, '\n'.join(' ' + i for i in sorted(diff)) |
78 res += len(diff) | 100 res += len(diff) |
79 | 101 |
80 max_filepath_len = max(len(n) for n in first_list & second_list) | 102 max_filepath_len = max(len(n) for n in first_list & second_list) |
81 for f in sorted(first_list & second_list): | 103 for f in sorted(first_list & second_list): |
82 first_file = os.path.join(first_dir, f) | 104 first_file = os.path.join(first_dir, f) |
83 second_file = os.path.join(second_dir, f) | 105 second_file = os.path.join(second_dir, f) |
84 files_diffs = compare_files(first_file, second_file) | 106 result = compare_files(first_file, second_file) |
85 if not files_diffs: | 107 if not result: |
86 result = 'equal' | 108 result = 'equal' |
87 else: | 109 else: |
88 file_len = os.stat(first_file).st_size | 110 result = 'DIFFERENT: %s' % result |
89 difference = '' | |
90 if files_diffs == -1: | |
91 difference = 'different size: %d != %d' % (file_len, | |
92 os.stat(second_file).st_size) | |
93 else: | |
94 difference = '%d out of %d bytes are different (%.2f%%)' % ( | |
95 files_diffs, file_len, 100.0 * files_diffs / file_len) | |
96 result = 'DIFFERENT: %s' % difference | |
97 res += 1 | 111 res += 1 |
98 print('%-*s: %s' % (max_filepath_len, f, result)) | 112 print('%-*s: %s' % (max_filepath_len, f, result)) |
99 | 113 |
100 print '%d files are equal, %d are different.' % ( | 114 print '%d files are equal, %d are different.' % ( |
101 len(first_list & second_list) - res, res) | 115 len(first_list & second_list) - res, res) |
102 | 116 |
103 return 0 if res == 0 else 1 | 117 return 0 if res == 0 else 1 |
104 | 118 |
105 | 119 |
106 def main(): | 120 def main(): |
107 parser = optparse.OptionParser(usage='%prog [options]') | 121 parser = optparse.OptionParser(usage='%prog [options]') |
108 parser.add_option('--first-build-dir', help='The first build directory.') | 122 parser.add_option( |
109 parser.add_option('--second-build-dir', help='The second build directory.') | 123 '-f', '--first-build-dir', help='The first build directory.') |
| 124 parser.add_option( |
| 125 '-s', '--second-build-dir', help='The second build directory.') |
110 options, _ = parser.parse_args() | 126 options, _ = parser.parse_args() |
111 | 127 |
112 if not options.first_build_dir: | 128 if not options.first_build_dir: |
113 parser.error('--first-build-dir is required') | 129 parser.error('--first-build-dir is required') |
114 if not options.second_build_dir: | 130 if not options.second_build_dir: |
115 parser.error('--second-build-dir is required') | 131 parser.error('--second-build-dir is required') |
116 | 132 |
117 return compare_build_artifacts(options.first_build_dir, | 133 return compare_build_artifacts(options.first_build_dir, |
118 options.second_build_dir) | 134 options.second_build_dir) |
119 | 135 |
120 | 136 |
121 if __name__ == '__main__': | 137 if __name__ == '__main__': |
122 sys.exit(main()) | 138 sys.exit(main()) |
OLD | NEW |