1#!/usr/bin/env python3 2# Copyright 2016 The Chromium Authors 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# https://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16"""Helper script to update the test error expectations based on actual results. 17 18This is useful for regenerating test expectations after making changes to the 19error format. 20 21To use this run the affected tests, and then pass the input to this script 22(either via stdin, or as the first argument). For instance: 23 24 $ ./build/pki_test --gtest_filter="*ParseCertificate*:*ParsedCertificate*:" | \ 25 pki/testdata/parse_certificate_unittest/rebase-errors.py 26 27The script works by scanning the stdout looking for gtest failures having a 28particular format. The C++ test side should have been instrumented to dump 29out the test file's path on mismatch. 30 31This script will then update the corresponding .pem file 32""" 33 34import base64 35import os 36import re 37import sys 38 39# Regular expression to find the failed errors in test stdout. 40# * Group 1 of the match is file path (relative to //src) where the 41# expected errors were read from. 42# * Group 2 of the match is the actual error text 43failed_test_regex = re.compile(r""" 44Cert errors don't match expectations \((.+?)\) 45 46EXPECTED: 47 48(?:.|\n)*? 49ACTUAL: 50 51((?:.|\n)*?) 52===> Use pki/testdata/parse_certificate_unittest/rebase-errors.py to rebaseline. 53""", re.MULTILINE) 54 55 56# Regular expression to find the ERRORS block (and any text above it) in a PEM 57# file. The assumption is that ERRORS is not the very first block in the file 58# (since it looks for an -----END to precede it). 59# * Group 1 of the match is the ERRORS block content and any comments 60# immediately above it. 61errors_block_regex = re.compile(r""".* 62-----END .*?----- 63(.*? 64-----BEGIN ERRORS----- 65.*? 66-----END ERRORS-----)""", re.MULTILINE | re.DOTALL) 67 68 69def read_file_to_string(path): 70 """Reads a file entirely to a string""" 71 with open(path, 'r') as f: 72 return f.read() 73 74 75def write_string_to_file(data, path): 76 """Writes a string to a file""" 77 print("Writing file %s ..." % (path)) 78 with open(path, "w") as f: 79 f.write(data) 80 81 82def replace_string(original, start, end, replacement): 83 """Replaces the specified range of |original| with |replacement|""" 84 return original[0:start] + replacement + original[end:] 85 86 87def text_data_to_pem(block_header, text_data): 88 # b64encode takes in bytes and returns bytes. 89 pem_data = base64.b64encode(text_data.encode('utf8')).decode('utf8') 90 return '%s\n-----BEGIN %s-----\n%s\n-----END %s-----\n' % ( 91 text_data, block_header, pem_data, block_header) 92 93 94def fixup_pem_file(path, actual_errors): 95 """Updates the ERRORS block in the test .pem file""" 96 contents = read_file_to_string(path) 97 98 errors_block_text = '\n' + text_data_to_pem('ERRORS', actual_errors) 99 # Strip the trailing newline. 100 errors_block_text = errors_block_text[:-1] 101 102 m = errors_block_regex.search(contents) 103 104 if not m: 105 contents += errors_block_text 106 else: 107 contents = replace_string(contents, m.start(1), m.end(1), 108 errors_block_text) 109 110 # Update the file. 111 write_string_to_file(contents, path) 112 113 114def get_src_root(): 115 """Returns the path to BoringSSL source tree. This assumes the 116 current script is inside the source tree.""" 117 cur_dir = os.path.dirname(os.path.realpath(__file__)) 118 119 while True: 120 # Check if it looks like the BoringSSL root. 121 if os.path.isdir(os.path.join(cur_dir, "crypto")) and \ 122 os.path.isdir(os.path.join(cur_dir, "pki")) and \ 123 os.path.isdir(os.path.join(cur_dir, "ssl")): 124 return cur_dir 125 parent_dir, _ = os.path.split(cur_dir) 126 if not parent_dir or parent_dir == cur_dir: 127 break 128 cur_dir = parent_dir 129 130 print("Couldn't find src dir") 131 sys.exit(1) 132 133 134def get_abs_path(rel_path): 135 """Converts |rel_path| (relative to src) to a full path""" 136 return os.path.join(get_src_root(), rel_path) 137 138 139def main(): 140 if len(sys.argv) > 2: 141 print('Usage: %s [path-to-unittest-stdout]' % (sys.argv[0])) 142 sys.exit(1) 143 144 # Read the input either from a file, or from stdin. 145 test_stdout = None 146 if len(sys.argv) == 2: 147 test_stdout = read_file_to_string(sys.argv[1]) 148 else: 149 print('Reading input from stdin...') 150 test_stdout = sys.stdin.read() 151 152 for m in failed_test_regex.finditer(test_stdout): 153 src_relative_errors_path = "pki/" + m.group(1) 154 errors_path = get_abs_path(src_relative_errors_path) 155 actual_errors = m.group(2) 156 157 fixup_pem_file(errors_path, actual_errors) 158 159 160if __name__ == "__main__": 161 main() 162