1#!/usr/bin/env python3 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="*ParseCertificate*" | \ 15 net/data/parse_certificate_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 19out the test file's path on mismatch. 20 21This script will then update the corresponding .pem file 22""" 23 24import sys 25import os 26 27script_dir = os.path.dirname(os.path.realpath(__file__)) 28sys.path += [os.path.join(script_dir, '..')] 29 30import gencerts 31 32import os 33import sys 34import re 35 36# Regular expression to find the failed errors in test stdout. 37# * Group 1 of the match is file path (relative to //src) where the 38# expected errors were read from. 39# * Group 2 of the match is the actual error text 40failed_test_regex = re.compile(r""" 41Cert errors don't match expectations \((.+?)\) 42 43EXPECTED: 44 45(?:.|\n)*? 46ACTUAL: 47 48((?:.|\n)*?) 49===> Use net/data/parse_certificate_unittest/rebase-errors.py to rebaseline. 50""", re.MULTILINE) 51 52 53# Regular expression to find the ERRORS block (and any text above it) in a PEM 54# file. The assumption is that ERRORS is not the very first block in the file 55# (since it looks for an -----END to precede it). 56# * Group 1 of the match is the ERRORS block content and any comments 57# immediately above it. 58errors_block_regex = re.compile(r""".* 59-----END .*?----- 60(.*? 61-----BEGIN ERRORS----- 62.*? 63-----END ERRORS-----)""", re.MULTILINE | re.DOTALL) 64 65 66def read_file_to_string(path): 67 """Reads a file entirely to a string""" 68 with open(path, 'r') as f: 69 return f.read() 70 71 72def write_string_to_file(data, path): 73 """Writes a string to a file""" 74 print("Writing file %s ..." % (path)) 75 with open(path, "w") as f: 76 f.write(data) 77 78 79def replace_string(original, start, end, replacement): 80 """Replaces the specified range of |original| with |replacement|""" 81 return original[0:start] + replacement + original[end:] 82 83 84def fixup_pem_file(path, actual_errors): 85 """Updates the ERRORS block in the test .pem file""" 86 contents = read_file_to_string(path) 87 88 errors_block_text = '\n' + gencerts.text_data_to_pem('ERRORS', actual_errors) 89 # Strip the trailing newline. 90 errors_block_text = errors_block_text[:-1] 91 92 m = errors_block_regex.search(contents) 93 94 if not m: 95 contents += errors_block_text 96 else: 97 contents = replace_string(contents, m.start(1), m.end(1), 98 errors_block_text) 99 100 # Update the file. 101 write_string_to_file(contents, path) 102 103 104def get_src_root(): 105 """Returns the path to the enclosing //src directory. This assumes the 106 current script is inside the source tree.""" 107 cur_dir = os.path.dirname(os.path.realpath(__file__)) 108 109 while True: 110 parent_dir, dirname = os.path.split(cur_dir) 111 # Check if it looks like the src/ root. 112 if dirname == "src" and os.path.isdir(os.path.join(cur_dir, "net")): 113 return cur_dir 114 if not parent_dir or parent_dir == cur_dir: 115 break 116 cur_dir = parent_dir 117 118 print("Couldn't find src dir") 119 sys.exit(1) 120 121 122def get_abs_path(rel_path): 123 """Converts |rel_path| (relative to src) to a full path""" 124 return os.path.join(get_src_root(), rel_path) 125 126 127def main(): 128 if len(sys.argv) > 2: 129 print('Usage: %s [path-to-unittest-stdout]' % (sys.argv[0])) 130 sys.exit(1) 131 132 # Read the input either from a file, or from stdin. 133 test_stdout = None 134 if len(sys.argv) == 2: 135 test_stdout = read_file_to_string(sys.argv[1]) 136 else: 137 print('Reading input from stdin...') 138 test_stdout = sys.stdin.read() 139 140 for m in failed_test_regex.finditer(test_stdout): 141 src_relative_errors_path = m.group(1) 142 errors_path = get_abs_path(src_relative_errors_path) 143 actual_errors = m.group(2) 144 145 fixup_pem_file(errors_path, actual_errors) 146 147 148if __name__ == "__main__": 149 main() 150