1#!/usr/bin/env python 2# Copyright 2016 The Chromium Authors 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 8This is useful for regenerating test expectations after making changes to the 9error format. 10 11To 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: 13 14 $ ./out/Release/net_unittests --gtest_filter="*VerifyCertificateChain*" | \ 15 net/data/verify_certificate_chain_unittest/rebase-errors.py 16 17The script works by scanning the stdout looking for gtest failures having a 18particular format. The C++ test side should have been instrumented to dump out 19the test file's path on mismatch. 20 21This script will then update the corresponding test/error file that contains the 22error expectation. 23""" 24 25import os 26import sys 27import re 28 29# Regular expression to find the failed errors in test stdout. 30# * Group 1 of the match is file path (relative to //src) where the 31# expected errors were read from. 32# * Group 2 of the match is the actual error text 33failed_test_regex = re.compile(r""" 34Cert path errors don't match expectations \((.+?)\) 35 36EXPECTED: 37 38(?:.|\n)*? 39ACTUAL: 40 41((?:.|\n)*?) 42===> Use net/data/verify_certificate_chain_unittest/rebase-errors.py to rebaseline. 43""", re.MULTILINE) 44 45 46def read_file_to_string(path): 47 """Reads a file entirely to a string""" 48 with open(path, 'r') as f: 49 return f.read() 50 51 52def write_string_to_file(data, path): 53 """Writes a string to a file""" 54 print("Writing file %s ..." % (path)) 55 with open(path, "w") as f: 56 f.write(data) 57 58 59def get_src_root(): 60 """Returns the path to the enclosing //src directory. This assumes the 61 current script is inside the source tree.""" 62 cur_dir = os.path.dirname(os.path.realpath(__file__)) 63 64 while True: 65 parent_dir, dirname = os.path.split(cur_dir) 66 # Check if it looks like the src/ root. 67 if dirname == "src" and os.path.isdir(os.path.join(cur_dir, "net")): 68 return cur_dir 69 if not parent_dir or parent_dir == cur_dir: 70 break 71 cur_dir = parent_dir 72 73 print("Couldn't find src dir") 74 sys.exit(1) 75 76 77def get_abs_path(rel_path): 78 """Converts |rel_path| (relative to src) to a full path""" 79 return os.path.join(get_src_root(), rel_path) 80 81 82def fixup_errors_for_file(actual_errors, test_file_path): 83 """Updates the errors in |test_file_path| to match |actual_errors|""" 84 contents = read_file_to_string(test_file_path) 85 86 header = "\nexpected_errors:\n" 87 index = contents.find(header) 88 if index < 0: 89 print("Couldn't find expected_errors") 90 sys.exit(1) 91 92 # The rest of the file contains the errors (overwrite). 93 contents = contents[0:index] + header + actual_errors 94 95 write_string_to_file(contents, test_file_path) 96 97 98def main(): 99 if len(sys.argv) > 2: 100 print('Usage: %s [path-to-unittest-stdout]' % (sys.argv[0])) 101 sys.exit(1) 102 103 # Read the input either from a file, or from stdin. 104 test_stdout = None 105 if len(sys.argv) == 2: 106 test_stdout = read_file_to_string(sys.argv[1]) 107 else: 108 print('Reading input from stdin...') 109 test_stdout = sys.stdin.read() 110 111 for m in failed_test_regex.finditer(test_stdout): 112 src_relative_errors_path = m.group(1) 113 errors_path = get_abs_path(src_relative_errors_path) 114 actual_errors = m.group(2) 115 116 if errors_path.endswith(".test"): 117 fixup_errors_for_file(actual_errors, errors_path) 118 elif errors_path.endswith(".txt"): 119 write_string_to_file(actual_errors, errors_path) 120 else: 121 print('Unknown file extension') 122 sys.exit(1) 123 124 125 126if __name__ == "__main__": 127 main() 128