xref: /aosp_15_r20/external/cronet/net/data/parse_certificate_unittest/rebase-errors.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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