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="*VerifyCertificateChain*" | \
25     pki/testdata/verify_certificate_chain_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 out
29the test file's path on mismatch.
30
31This script will then update the corresponding test/error file that contains the
32error expectation.
33"""
34
35import os
36import sys
37import re
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 path errors don't match expectations \((.+?)\)
45
46EXPECTED:
47
48(?:.|\n)*?
49ACTUAL:
50
51((?:.|\n)*?)
52===> Use pki/testdata/verify_certificate_chain_unittest/rebase-errors.py to rebaseline.
53""", re.MULTILINE)
54
55
56def read_file_to_string(path):
57  """Reads a file entirely to a string"""
58  with open(path, 'r') as f:
59    return f.read()
60
61
62def write_string_to_file(data, path):
63  """Writes a string to a file"""
64  print("Writing file %s ..." % (path))
65  with open(path, "w") as f:
66    f.write(data)
67
68
69def get_src_root():
70  """Returns the path to BoringSSL source tree. This assumes the
71  current script is inside the source tree."""
72  cur_dir = os.path.dirname(os.path.realpath(__file__))
73
74  while True:
75    # Check if it looks like the BoringSSL root.
76    if os.path.isdir(os.path.join(cur_dir, "crypto")) and \
77       os.path.isdir(os.path.join(cur_dir, "pki")) and \
78       os.path.isdir(os.path.join(cur_dir, "ssl")):
79      return cur_dir
80    parent_dir, _ = os.path.split(cur_dir)
81    if not parent_dir or parent_dir == cur_dir:
82      break
83    cur_dir = parent_dir
84
85  print("Couldn't find src dir")
86  sys.exit(1)
87
88
89def get_abs_path(rel_path):
90  """Converts |rel_path| (relative to src) to a full path"""
91  return os.path.join(get_src_root(), rel_path)
92
93
94def fixup_errors_for_file(actual_errors, test_file_path):
95  """Updates the errors in |test_file_path| to match |actual_errors|"""
96  contents = read_file_to_string(test_file_path)
97
98  header = "\nexpected_errors:\n"
99  index = contents.find(header)
100  if index < 0:
101    print("Couldn't find expected_errors")
102    sys.exit(1)
103
104  # The rest of the file contains the errors (overwrite).
105  contents = contents[0:index] + header + actual_errors
106
107  write_string_to_file(contents, test_file_path)
108
109
110def main():
111  if len(sys.argv) > 2:
112    print('Usage: %s [path-to-unittest-stdout]' % (sys.argv[0]))
113    sys.exit(1)
114
115  # Read the input either from a file, or from stdin.
116  test_stdout = None
117  if len(sys.argv) == 2:
118    test_stdout = read_file_to_string(sys.argv[1])
119  else:
120    print('Reading input from stdin...')
121    test_stdout = sys.stdin.read()
122
123  for m in failed_test_regex.finditer(test_stdout):
124    src_relative_errors_path = "pki/" + m.group(1)
125    errors_path = get_abs_path(src_relative_errors_path)
126    actual_errors = m.group(2)
127
128    if errors_path.endswith(".test"):
129      fixup_errors_for_file(actual_errors, errors_path)
130    elif errors_path.endswith(".txt"):
131      write_string_to_file(actual_errors, errors_path)
132    else:
133      print('Unknown file extension')
134      sys.exit(1)
135
136
137
138if __name__ == "__main__":
139  main()
140