Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/python | |
| 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 | |
| 4 # found in the LICENSE file. | |
| 5 | |
| 6 """Helper script to update the test error expectations based on actual results. | |
| 7 | |
| 8 This is useful for regenerating test expectations after making changes to the | |
| 9 error format. | |
| 10 | |
| 11 To use this run the affected tests, and then pass the input to this script. For | |
| 12 instance: | |
| 13 | |
| 14 $ ./out/Release/net_unittests --gtest_filter="*VerifyCertificateChain*" \ | |
| 15 &> results.txt | |
| 16 $ python net/data/verify_certificate_chain_unittest/rebase-errors.py \ | |
| 17 results.txt | |
| 18 | |
| 19 The script will search the unit-test (results.txt in above example) and look | |
| 20 for failure lines and the corresponding actual error string. | |
| 21 | |
| 22 It will then go and update the corresponding .pem and .py file. | |
| 23 """ | |
| 24 | |
| 25 import common | |
| 26 import glob | |
| 27 import os | |
| 28 import sys | |
| 29 import re | |
| 30 | |
| 31 | |
| 32 def read_file_to_string(path): | |
| 33 """Reads a file entirely to a string""" | |
| 34 with open(path, 'r') as f: | |
| 35 return f.read() | |
| 36 | |
| 37 | |
| 38 def write_string_to_file(data, path): | |
| 39 """Writes a string to a file""" | |
| 40 print "Writing file %s ..." % (path) | |
| 41 with open(path, "w") as f: | |
| 42 f.write(data) | |
| 43 | |
| 44 | |
| 45 def get_file_paths_for_test(test_name): | |
| 46 """Returns the file paths (as a tuple) that define a particular unit test. | |
| 47 For instance given test name 'IntermediateLacksBasicConstraints' it would | |
| 48 return the paths to: | |
| 49 | |
| 50 * intermediate-lacks-basic-constraints.pem, | |
| 51 * generate-intermediate-lacks-basic-constraints.py | |
| 52 """ | |
| 53 # The directory that this python script is stored in. | |
| 54 base_dir = os.path.dirname(os.path.realpath(__file__)) | |
| 55 | |
| 56 # The C++ test name is just a camel case verson of the file name. Rather than | |
| 57 # converting directly from camel case to a file name, it is simpler to just | |
| 58 # scan the file list and see which matches. (Not efficient but good enough). | |
| 59 paths = glob.glob(os.path.join(base_dir, '*.pem')) | |
| 60 | |
| 61 for pem_path in paths: | |
| 62 file_name = os.path.basename(pem_path) | |
| 63 file_name_no_extension = os.path.splitext(file_name)[0] | |
| 64 | |
| 65 # Strip the hyphens in file name to bring it closer to the camel case. | |
| 66 transformed = file_name_no_extension.replace('-', '') | |
| 67 | |
| 68 # Now all that differs is the case. | |
| 69 if transformed.lower() == test_name.lower(): | |
| 70 py_file_name = 'generate-' + file_name_no_extension + '.py' | |
| 71 py_path = os.path.join(base_dir, py_file_name) | |
| 72 return (pem_path, py_path) | |
| 73 | |
| 74 return None | |
| 75 | |
| 76 | |
| 77 def replace_string(original, start, end, replacement): | |
| 78 """Replaces the specified range of |original| with |replacement|""" | |
| 79 return original[0:start] + replacement + original[end:] | |
| 80 | |
| 81 | |
| 82 def fixup_pem_file(path, actual_errors): | |
| 83 """Updates the ERRORS block in the test .pem file""" | |
| 84 contents = read_file_to_string(path) | |
| 85 | |
| 86 # This assumes that ERRORS is the last thing in file, and comes after the | |
| 87 # VERIFY_RESULT block. | |
| 88 kEndVerifyResult = '-----END VERIFY_RESULT-----' | |
| 89 contents = contents[0:contents.index(kEndVerifyResult)] | |
| 90 contents += kEndVerifyResult | |
| 91 contents += '\n' | |
| 92 contents += '\n' | |
| 93 contents += common.text_data_to_pem('ERRORS', actual_errors) | |
| 94 | |
| 95 # Update the file. | |
| 96 write_string_to_file(contents, path) | |
| 97 | |
| 98 | |
| 99 def fixup_py_file(path, actual_errors): | |
| 100 """Replaces the 'errors = XXX' section of the test's python script""" | |
| 101 contents = read_file_to_string(path) | |
| 102 | |
| 103 # This assumes that the errors variable uses triple quotes. | |
| 104 prog = re.compile(r'^errors = """(.*)"""', re.MULTILINE | re.DOTALL) | |
| 105 result = prog.search(contents) | |
| 106 | |
| 107 # Replace the stuff in between the triple quotes with the actual errors. | |
| 108 contents = replace_string(contents, result.start(1), result.end(1), | |
|
mattm
2016/09/09 22:18:54
could use re.sub instead of re.search + replace_st
eroman
2016/09/09 23:16:25
My concern with re.sub() is that it processes back
mattm
2016/09/09 23:22:21
ah, I forgot that. I guess there is the 2nd form o
eroman
2016/09/10 00:22:39
Left unchanged.
| |
| 109 actual_errors) | |
| 110 | |
| 111 # Update the file. | |
| 112 write_string_to_file(contents, path) | |
| 113 | |
| 114 | |
| 115 def fixup_test(test_name, actual_errors): | |
| 116 """Updates the test files used by |test_name|, setting the expected error to | |
| 117 |actual_errors|""" | |
| 118 | |
| 119 # Determine the paths for the corresponding *.pem file and generate-*.py | |
| 120 pem_path, py_path = get_file_paths_for_test(test_name) | |
| 121 | |
| 122 fixup_pem_file(pem_path, actual_errors) | |
| 123 fixup_py_file(py_path, actual_errors) | |
| 124 | |
| 125 | |
| 126 kTestNamePattern = (r'^\[ RUN \] VerifyCertificateChain/' | |
| 127 'VerifyCertificateChainSingleRootTest/0\.(.*)$') | |
| 128 kValueOfLine = 'Value of: errors.ToDebugString()' | |
| 129 kActualPattern = '^ Actual: "(.*)"$' | |
| 130 | |
| 131 | |
| 132 def main(): | |
| 133 if len(sys.argv) != 2: | |
| 134 print 'Usage: %s <path-to-unittest-stdout>' % (sys.argv[0]) | |
| 135 sys.exit(1) | |
| 136 | |
| 137 test_stdout = read_file_to_string(sys.argv[1]) | |
|
mattm
2016/09/09 22:18:54
what do you think about reading from sys.stdin ins
eroman
2016/09/09 23:16:26
Sure, will do.
eroman
2016/09/10 00:22:39
Done.
| |
| 138 lines = test_stdout.split('\n') | |
| 139 | |
| 140 # Iterate over each line of the unit test stdout. | |
| 141 for i in range(len(lines) - 3): | |
| 142 # Figure out the name of the test. | |
| 143 m = re.search(kTestNamePattern, lines[i]) | |
| 144 if not m: | |
| 145 continue | |
| 146 test_name = m.group(1) | |
| 147 | |
| 148 # Confirm that it is a failure having to do with the errors. | |
| 149 if lines[i + 2] != kValueOfLine: | |
| 150 continue | |
| 151 | |
| 152 # Get the actual error text (which in gtest output is escaped). | |
| 153 m = re.search(kActualPattern, lines[i + 3]) | |
| 154 if not m: | |
| 155 continue | |
| 156 actual = m.group(1) | |
| 157 actual = actual.decode('string-escape') | |
| 158 | |
| 159 fixup_test(test_name, actual) | |
| 160 | |
| 161 | |
| 162 if __name__ == "__main__": | |
| 163 main() | |
| OLD | NEW |