OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 # Copyright (c) 2016 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2016 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 """Helper script to update the test error expectations based on actual results. | 6 """Helper script to update the test error expectations based on actual results. |
7 | 7 |
8 This is useful for regenerating test expectations after making changes to the | 8 This is useful for regenerating test expectations after making changes to the |
9 error format. | 9 error format. |
10 | 10 |
11 To use this run the affected tests, and then pass the input to this script | 11 To use this run the affected tests, and then pass the input to this script |
12 (either via stdin, or as the first argument). For instance: | 12 (either via stdin, or as the first argument). For instance: |
13 | 13 |
14 $ ./out/Release/net_unittests --gtest_filter="*VerifyCertificateChain*" | \ | 14 $ ./out/Release/net_unittests --gtest_filter="*VerifyCertificateChain*" | \ |
15 net/data/verify_certificate_chain_unittest/rebase-errors.py | 15 net/data/verify_certificate_chain_unittest/rebase-errors.py |
16 | 16 |
17 The script works by scanning the stdout looking for gtest failures when | 17 The script works by scanning the stdout looking for gtest failures when |
18 comparing "errors.ToDebugString(chain)". The C++ test side should have been | 18 comparing "errors.ToDebugString(chain)". The C++ test side should have been |
19 instrumented to dump out the test file's path on mismatch. | 19 instrumented to dump out the test file's path on mismatch. |
20 | 20 |
21 This script will then update the corresponding file(s) -- a .pem file, and | 21 This script will then update the corresponding .test file that contains the |
22 possibly an accompanying .py file. | 22 error expectation. |
23 """ | 23 """ |
24 | 24 |
25 import common | |
26 import os | 25 import os |
27 import sys | 26 import sys |
28 import re | 27 import re |
29 | 28 |
30 | |
31 # Regular expression to find the failed errors in test stdout. | 29 # Regular expression to find the failed errors in test stdout. |
32 # * Group 1 of the match is the actual error text (backslash-escaped) | 30 # * Group 1 of the match is the actual error text (backslash-escaped) |
33 # * Group 2 of the match is file path (relative to //src) where the expected | 31 # * Group 2 of the match is file path (relative to //src) where the expected |
34 # errors were read from. | 32 # errors were read from. |
35 failed_test_regex = re.compile(r""" | 33 failed_test_regex = re.compile(r""" |
36 Value of: errors.ToDebugString\((?:test.chain)?\) | 34 Value of: errors.ToDebugString\((?:test.chain)?\) |
37 Actual: "(.*)" | 35 Actual: "(.*)" |
38 (?:.|\n)+? | 36 (?:.|\n)+? |
39 Test file: (.*) | 37 Test file: (.*[.]test) |
40 """, re.MULTILINE) | 38 """, re.MULTILINE) |
41 | 39 |
42 | 40 |
43 # Regular expression to find the ERRORS block (and any text above it) in a PEM | |
44 # file. The assumption is that ERRORS is not the very first block in the file | |
45 # (since it looks for an -----END to precede it). | |
46 # * Group 1 of the match is the ERRORS block content and any comments | |
47 # immediately above it. | |
48 errors_block_regex = re.compile(r""".* | |
49 -----END .*?----- | |
50 | |
51 (.*? | |
52 -----BEGIN ERRORS----- | |
53 .*? | |
54 -----END ERRORS----- | |
55 )""", re.MULTILINE | re.DOTALL) | |
56 | |
57 | |
58 def read_file_to_string(path): | 41 def read_file_to_string(path): |
59 """Reads a file entirely to a string""" | 42 """Reads a file entirely to a string""" |
60 with open(path, 'r') as f: | 43 with open(path, 'r') as f: |
61 return f.read() | 44 return f.read() |
62 | 45 |
63 | 46 |
64 def write_string_to_file(data, path): | 47 def write_string_to_file(data, path): |
65 """Writes a string to a file""" | 48 """Writes a string to a file""" |
66 print "Writing file %s ..." % (path) | 49 print "Writing file %s ..." % (path) |
67 with open(path, "w") as f: | 50 with open(path, "w") as f: |
68 f.write(data) | 51 f.write(data) |
69 | 52 |
70 | 53 |
71 def get_py_path(pem_path): | |
72 """Returns the .py filepath used to generate the given .pem path, which may | |
73 or may not exist. | |
74 | |
75 Some test files (notably those in verify_certificate_chain_unittest/ have a | |
76 "generate-XXX.py" script that builds the "XXX.pem" file. Build the path to | |
77 the corresponding "generate-XXX.py" (which may or may not exist).""" | |
78 file_name = os.path.basename(pem_path) | |
79 file_name_no_extension = os.path.splitext(file_name)[0] | |
80 py_file_name = 'generate-' + file_name_no_extension + '.py' | |
81 return os.path.join(os.path.dirname(pem_path), py_file_name) | |
82 | |
83 | |
84 def replace_string(original, start, end, replacement): | |
85 """Replaces the specified range of |original| with |replacement|""" | |
86 return original[0:start] + replacement + original[end:] | |
87 | |
88 | |
89 def fixup_pem_file(path, actual_errors): | |
90 """Updates the ERRORS block in the test .pem file""" | |
91 contents = read_file_to_string(path) | |
92 | |
93 m = errors_block_regex.search(contents) | |
94 | |
95 if not m: | |
96 contents += '\n' + common.text_data_to_pem('ERRORS', actual_errors) | |
97 else: | |
98 contents = replace_string(contents, m.start(1), m.end(1), | |
99 common.text_data_to_pem('ERRORS', actual_errors)) | |
100 | |
101 # Update the file. | |
102 write_string_to_file(contents, path) | |
103 | |
104 | |
105 def fixup_py_file(path, actual_errors): | |
106 """Replaces the 'errors = XXX' section of the test's python script""" | |
107 contents = read_file_to_string(path) | |
108 | |
109 # This assumes that the errors variable uses triple quotes. | |
110 prog = re.compile(r'^errors = (""".*?"""|None)', re.MULTILINE | re.DOTALL) | |
111 result = prog.search(contents) | |
112 | |
113 # Replace the stuff in between the triple quotes with the actual errors. | |
114 contents = replace_string(contents, result.start(1), result.end(1), | |
115 '"""' + actual_errors + '"""') | |
116 | |
117 # Update the file. | |
118 write_string_to_file(contents, path) | |
119 | |
120 | |
121 def get_src_root(): | 54 def get_src_root(): |
122 """Returns the path to the enclosing //src directory. This assumes the | 55 """Returns the path to the enclosing //src directory. This assumes the |
123 current script is inside the source tree.""" | 56 current script is inside the source tree.""" |
124 cur_dir = os.path.dirname(os.path.realpath(__file__)) | 57 cur_dir = os.path.dirname(os.path.realpath(__file__)) |
125 | 58 |
126 while True: | 59 while True: |
127 parent_dir, dirname = os.path.split(cur_dir) | 60 parent_dir, dirname = os.path.split(cur_dir) |
128 # Check if it looks like the src/ root. | 61 # Check if it looks like the src/ root. |
129 if dirname == "src" and os.path.isdir(os.path.join(cur_dir, "net")): | 62 if dirname == "src" and os.path.isdir(os.path.join(cur_dir, "net")): |
130 return cur_dir | 63 return cur_dir |
131 if not parent_dir or parent_dir == cur_dir: | 64 if not parent_dir or parent_dir == cur_dir: |
132 break | 65 break |
133 cur_dir = parent_dir | 66 cur_dir = parent_dir |
134 | 67 |
135 print "Couldn't find src dir" | 68 print "Couldn't find src dir" |
136 sys.exit(1) | 69 sys.exit(1) |
137 | 70 |
138 | 71 |
139 def get_abs_path(rel_path): | 72 def get_abs_path(rel_path): |
140 """Converts |rel_path| (relative to src) to a full path""" | 73 """Converts |rel_path| (relative to src) to a full path""" |
141 return os.path.join(get_src_root(), rel_path) | 74 return os.path.join(get_src_root(), rel_path) |
142 | 75 |
143 | 76 |
144 def fixup_errors_for_file(actual_errors, pem_path): | 77 def fixup_errors_for_file(actual_errors, test_file_path): |
145 """Updates the errors in |test_file_path| (.pem file) to match | 78 """Updates the errors in |test_file_path| to match |actual_errors|""" |
146 |actual_errors|""" | 79 contents = read_file_to_string(test_file_path) |
147 | 80 |
148 fixup_pem_file(pem_path, actual_errors) | 81 header = "\nexpected_errors:\n" |
| 82 index = contents.find(header) |
| 83 if index < 0: |
| 84 print "Couldn't find expected_errors" |
| 85 sys.exit(1) |
149 | 86 |
150 # If the test has a generator script update it too. | 87 # The rest of the file contains the errors (overwrite). |
151 py_path = get_py_path(pem_path) | 88 contents = contents[0:index] + header + actual_errors |
152 if os.path.isfile(py_path): | 89 |
153 fixup_py_file(py_path, actual_errors) | 90 write_string_to_file(contents, test_file_path) |
154 | 91 |
155 | 92 |
156 def main(): | 93 def main(): |
157 if len(sys.argv) > 2: | 94 if len(sys.argv) > 2: |
158 print 'Usage: %s [path-to-unittest-stdout]' % (sys.argv[0]) | 95 print 'Usage: %s [path-to-unittest-stdout]' % (sys.argv[0]) |
159 sys.exit(1) | 96 sys.exit(1) |
160 | 97 |
161 # Read the input either from a file, or from stdin. | 98 # Read the input either from a file, or from stdin. |
162 test_stdout = None | 99 test_stdout = None |
163 if len(sys.argv) == 2: | 100 if len(sys.argv) == 2: |
164 test_stdout = read_file_to_string(sys.argv[1]) | 101 test_stdout = read_file_to_string(sys.argv[1]) |
165 else: | 102 else: |
166 print 'Reading input from stdin...' | 103 print 'Reading input from stdin...' |
167 test_stdout = sys.stdin.read() | 104 test_stdout = sys.stdin.read() |
168 | 105 |
169 for m in failed_test_regex.finditer(test_stdout): | 106 for m in failed_test_regex.finditer(test_stdout): |
170 actual_errors = m.group(1) | 107 actual_errors = m.group(1) |
171 actual_errors = actual_errors.decode('string-escape') | 108 actual_errors = actual_errors.decode('string-escape') |
172 relative_test_path = m.group(2) | 109 relative_test_path = m.group(2) |
173 fixup_errors_for_file(actual_errors, get_abs_path(relative_test_path)) | 110 fixup_errors_for_file(actual_errors, get_abs_path(relative_test_path)) |
174 | 111 |
175 | 112 |
176 if __name__ == "__main__": | 113 if __name__ == "__main__": |
177 main() | 114 main() |
OLD | NEW |