1 | #!/usr/bin/env python
|
---|
2 | #
|
---|
3 | # Copyright 2008, Google Inc.
|
---|
4 | # All rights reserved.
|
---|
5 | #
|
---|
6 | # Redistribution and use in source and binary forms, with or without
|
---|
7 | # modification, are permitted provided that the following conditions are
|
---|
8 | # met:
|
---|
9 | #
|
---|
10 | # * Redistributions of source code must retain the above copyright
|
---|
11 | # notice, this list of conditions and the following disclaimer.
|
---|
12 | # * Redistributions in binary form must reproduce the above
|
---|
13 | # copyright notice, this list of conditions and the following disclaimer
|
---|
14 | # in the documentation and/or other materials provided with the
|
---|
15 | # distribution.
|
---|
16 | # * Neither the name of Google Inc. nor the names of its
|
---|
17 | # contributors may be used to endorse or promote products derived from
|
---|
18 | # this software without specific prior written permission.
|
---|
19 | #
|
---|
20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
---|
21 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
---|
22 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
---|
23 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
---|
24 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
---|
25 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
---|
26 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
---|
27 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
---|
28 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
---|
29 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
---|
30 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
---|
31 |
|
---|
32 | """Tests the text output of Google C++ Testing Framework.
|
---|
33 |
|
---|
34 | SYNOPSIS
|
---|
35 | gtest_output_test.py --build_dir=BUILD/DIR --gengolden
|
---|
36 | # where BUILD/DIR contains the built gtest_output_test_ file.
|
---|
37 | gtest_output_test.py --gengolden
|
---|
38 | gtest_output_test.py
|
---|
39 | """
|
---|
40 |
|
---|
41 | __author__ = 'wan@google.com (Zhanyong Wan)'
|
---|
42 |
|
---|
43 | import os
|
---|
44 | import re
|
---|
45 | import sys
|
---|
46 | import gtest_test_utils
|
---|
47 |
|
---|
48 |
|
---|
49 | # The flag for generating the golden file
|
---|
50 | GENGOLDEN_FLAG = '--gengolden'
|
---|
51 | CATCH_EXCEPTIONS_ENV_VAR_NAME = 'GTEST_CATCH_EXCEPTIONS'
|
---|
52 |
|
---|
53 | IS_WINDOWS = os.name == 'nt'
|
---|
54 |
|
---|
55 | # TODO(vladl@google.com): remove the _lin suffix.
|
---|
56 | GOLDEN_NAME = 'gtest_output_test_golden_lin.txt'
|
---|
57 |
|
---|
58 | PROGRAM_PATH = gtest_test_utils.GetTestExecutablePath('gtest_output_test_')
|
---|
59 |
|
---|
60 | # At least one command we exercise must not have the
|
---|
61 | # --gtest_internal_skip_environment_and_ad_hoc_tests flag.
|
---|
62 | COMMAND_LIST_TESTS = ({}, [PROGRAM_PATH, '--gtest_list_tests'])
|
---|
63 | COMMAND_WITH_COLOR = ({}, [PROGRAM_PATH, '--gtest_color=yes'])
|
---|
64 | COMMAND_WITH_TIME = ({}, [PROGRAM_PATH,
|
---|
65 | '--gtest_print_time',
|
---|
66 | '--gtest_internal_skip_environment_and_ad_hoc_tests',
|
---|
67 | '--gtest_filter=FatalFailureTest.*:LoggingTest.*'])
|
---|
68 | COMMAND_WITH_DISABLED = (
|
---|
69 | {}, [PROGRAM_PATH,
|
---|
70 | '--gtest_also_run_disabled_tests',
|
---|
71 | '--gtest_internal_skip_environment_and_ad_hoc_tests',
|
---|
72 | '--gtest_filter=*DISABLED_*'])
|
---|
73 | COMMAND_WITH_SHARDING = (
|
---|
74 | {'GTEST_SHARD_INDEX': '1', 'GTEST_TOTAL_SHARDS': '2'},
|
---|
75 | [PROGRAM_PATH,
|
---|
76 | '--gtest_internal_skip_environment_and_ad_hoc_tests',
|
---|
77 | '--gtest_filter=PassingTest.*'])
|
---|
78 |
|
---|
79 | GOLDEN_PATH = os.path.join(gtest_test_utils.GetSourceDir(), GOLDEN_NAME)
|
---|
80 |
|
---|
81 |
|
---|
82 | def ToUnixLineEnding(s):
|
---|
83 | """Changes all Windows/Mac line endings in s to UNIX line endings."""
|
---|
84 |
|
---|
85 | return s.replace('\r\n', '\n').replace('\r', '\n')
|
---|
86 |
|
---|
87 |
|
---|
88 | def RemoveLocations(test_output):
|
---|
89 | """Removes all file location info from a Google Test program's output.
|
---|
90 |
|
---|
91 | Args:
|
---|
92 | test_output: the output of a Google Test program.
|
---|
93 |
|
---|
94 | Returns:
|
---|
95 | output with all file location info (in the form of
|
---|
96 | 'DIRECTORY/FILE_NAME:LINE_NUMBER: 'or
|
---|
97 | 'DIRECTORY\\FILE_NAME(LINE_NUMBER): ') replaced by
|
---|
98 | 'FILE_NAME:#: '.
|
---|
99 | """
|
---|
100 |
|
---|
101 | return re.sub(r'.*[/\\](.+)(\:\d+|\(\d+\))\: ', r'\1:#: ', test_output)
|
---|
102 |
|
---|
103 |
|
---|
104 | def RemoveStackTraceDetails(output):
|
---|
105 | """Removes all stack traces from a Google Test program's output."""
|
---|
106 |
|
---|
107 | # *? means "find the shortest string that matches".
|
---|
108 | return re.sub(r'Stack trace:(.|\n)*?\n\n',
|
---|
109 | 'Stack trace: (omitted)\n\n', output)
|
---|
110 |
|
---|
111 |
|
---|
112 | def RemoveStackTraces(output):
|
---|
113 | """Removes all traces of stack traces from a Google Test program's output."""
|
---|
114 |
|
---|
115 | # *? means "find the shortest string that matches".
|
---|
116 | return re.sub(r'Stack trace:(.|\n)*?\n\n', '', output)
|
---|
117 |
|
---|
118 |
|
---|
119 | def RemoveTime(output):
|
---|
120 | """Removes all time information from a Google Test program's output."""
|
---|
121 |
|
---|
122 | return re.sub(r'\(\d+ ms', '(? ms', output)
|
---|
123 |
|
---|
124 |
|
---|
125 | def RemoveTypeInfoDetails(test_output):
|
---|
126 | """Removes compiler-specific type info from Google Test program's output.
|
---|
127 |
|
---|
128 | Args:
|
---|
129 | test_output: the output of a Google Test program.
|
---|
130 |
|
---|
131 | Returns:
|
---|
132 | output with type information normalized to canonical form.
|
---|
133 | """
|
---|
134 |
|
---|
135 | # some compilers output the name of type 'unsigned int' as 'unsigned'
|
---|
136 | return re.sub(r'unsigned int', 'unsigned', test_output)
|
---|
137 |
|
---|
138 |
|
---|
139 | def NormalizeToCurrentPlatform(test_output):
|
---|
140 | """Normalizes platform specific output details for easier comparison."""
|
---|
141 |
|
---|
142 | if IS_WINDOWS:
|
---|
143 | # Removes the color information that is not present on Windows.
|
---|
144 | test_output = re.sub('\x1b\\[(0;3\d)?m', '', test_output)
|
---|
145 | # Changes failure message headers into the Windows format.
|
---|
146 | test_output = re.sub(r': Failure\n', r': error: ', test_output)
|
---|
147 | # Changes file(line_number) to file:line_number.
|
---|
148 | test_output = re.sub(r'((\w|\.)+)\((\d+)\):', r'\1:\3:', test_output)
|
---|
149 |
|
---|
150 | return test_output
|
---|
151 |
|
---|
152 |
|
---|
153 | def RemoveTestCounts(output):
|
---|
154 | """Removes test counts from a Google Test program's output."""
|
---|
155 |
|
---|
156 | output = re.sub(r'\d+ tests?, listed below',
|
---|
157 | '? tests, listed below', output)
|
---|
158 | output = re.sub(r'\d+ FAILED TESTS',
|
---|
159 | '? FAILED TESTS', output)
|
---|
160 | output = re.sub(r'\d+ tests? from \d+ test cases?',
|
---|
161 | '? tests from ? test cases', output)
|
---|
162 | output = re.sub(r'\d+ tests? from ([a-zA-Z_])',
|
---|
163 | r'? tests from \1', output)
|
---|
164 | return re.sub(r'\d+ tests?\.', '? tests.', output)
|
---|
165 |
|
---|
166 |
|
---|
167 | def RemoveMatchingTests(test_output, pattern):
|
---|
168 | """Removes output of specified tests from a Google Test program's output.
|
---|
169 |
|
---|
170 | This function strips not only the beginning and the end of a test but also
|
---|
171 | all output in between.
|
---|
172 |
|
---|
173 | Args:
|
---|
174 | test_output: A string containing the test output.
|
---|
175 | pattern: A regex string that matches names of test cases or
|
---|
176 | tests to remove.
|
---|
177 |
|
---|
178 | Returns:
|
---|
179 | Contents of test_output with tests whose names match pattern removed.
|
---|
180 | """
|
---|
181 |
|
---|
182 | test_output = re.sub(
|
---|
183 | r'.*\[ RUN \] .*%s(.|\n)*?\[( FAILED | OK )\] .*%s.*\n' % (
|
---|
184 | pattern, pattern),
|
---|
185 | '',
|
---|
186 | test_output)
|
---|
187 | return re.sub(r'.*%s.*\n' % pattern, '', test_output)
|
---|
188 |
|
---|
189 |
|
---|
190 | def NormalizeOutput(output):
|
---|
191 | """Normalizes output (the output of gtest_output_test_.exe)."""
|
---|
192 |
|
---|
193 | output = ToUnixLineEnding(output)
|
---|
194 | output = RemoveLocations(output)
|
---|
195 | output = RemoveStackTraceDetails(output)
|
---|
196 | output = RemoveTime(output)
|
---|
197 | return output
|
---|
198 |
|
---|
199 |
|
---|
200 | def GetShellCommandOutput(env_cmd):
|
---|
201 | """Runs a command in a sub-process, and returns its output in a string.
|
---|
202 |
|
---|
203 | Args:
|
---|
204 | env_cmd: The shell command. A 2-tuple where element 0 is a dict of extra
|
---|
205 | environment variables to set, and element 1 is a string with
|
---|
206 | the command and any flags.
|
---|
207 |
|
---|
208 | Returns:
|
---|
209 | A string with the command's combined standard and diagnostic output.
|
---|
210 | """
|
---|
211 |
|
---|
212 | # Spawns cmd in a sub-process, and gets its standard I/O file objects.
|
---|
213 | # Set and save the environment properly.
|
---|
214 | environ = os.environ.copy()
|
---|
215 | environ.update(env_cmd[0])
|
---|
216 | p = gtest_test_utils.Subprocess(env_cmd[1], env=environ)
|
---|
217 |
|
---|
218 | return p.output
|
---|
219 |
|
---|
220 |
|
---|
221 | def GetCommandOutput(env_cmd):
|
---|
222 | """Runs a command and returns its output with all file location
|
---|
223 | info stripped off.
|
---|
224 |
|
---|
225 | Args:
|
---|
226 | env_cmd: The shell command. A 2-tuple where element 0 is a dict of extra
|
---|
227 | environment variables to set, and element 1 is a string with
|
---|
228 | the command and any flags.
|
---|
229 | """
|
---|
230 |
|
---|
231 | # Disables exception pop-ups on Windows.
|
---|
232 | environ, cmdline = env_cmd
|
---|
233 | environ = dict(environ) # Ensures we are modifying a copy.
|
---|
234 | environ[CATCH_EXCEPTIONS_ENV_VAR_NAME] = '1'
|
---|
235 | return NormalizeOutput(GetShellCommandOutput((environ, cmdline)))
|
---|
236 |
|
---|
237 |
|
---|
238 | def GetOutputOfAllCommands():
|
---|
239 | """Returns concatenated output from several representative commands."""
|
---|
240 |
|
---|
241 | return (GetCommandOutput(COMMAND_WITH_COLOR) +
|
---|
242 | GetCommandOutput(COMMAND_WITH_TIME) +
|
---|
243 | GetCommandOutput(COMMAND_WITH_DISABLED) +
|
---|
244 | GetCommandOutput(COMMAND_WITH_SHARDING))
|
---|
245 |
|
---|
246 |
|
---|
247 | test_list = GetShellCommandOutput(COMMAND_LIST_TESTS)
|
---|
248 | SUPPORTS_DEATH_TESTS = 'DeathTest' in test_list
|
---|
249 | SUPPORTS_TYPED_TESTS = 'TypedTest' in test_list
|
---|
250 | SUPPORTS_THREADS = 'ExpectFailureWithThreadsTest' in test_list
|
---|
251 | SUPPORTS_STACK_TRACES = False
|
---|
252 |
|
---|
253 | CAN_GENERATE_GOLDEN_FILE = (SUPPORTS_DEATH_TESTS and
|
---|
254 | SUPPORTS_TYPED_TESTS and
|
---|
255 | SUPPORTS_THREADS)
|
---|
256 |
|
---|
257 |
|
---|
258 | class GTestOutputTest(gtest_test_utils.TestCase):
|
---|
259 | def RemoveUnsupportedTests(self, test_output):
|
---|
260 | if not SUPPORTS_DEATH_TESTS:
|
---|
261 | test_output = RemoveMatchingTests(test_output, 'DeathTest')
|
---|
262 | if not SUPPORTS_TYPED_TESTS:
|
---|
263 | test_output = RemoveMatchingTests(test_output, 'TypedTest')
|
---|
264 | test_output = RemoveMatchingTests(test_output, 'TypedDeathTest')
|
---|
265 | test_output = RemoveMatchingTests(test_output, 'TypeParamDeathTest')
|
---|
266 | if not SUPPORTS_THREADS:
|
---|
267 | test_output = RemoveMatchingTests(test_output,
|
---|
268 | 'ExpectFailureWithThreadsTest')
|
---|
269 | test_output = RemoveMatchingTests(test_output,
|
---|
270 | 'ScopedFakeTestPartResultReporterTest')
|
---|
271 | test_output = RemoveMatchingTests(test_output,
|
---|
272 | 'WorksConcurrently')
|
---|
273 | if not SUPPORTS_STACK_TRACES:
|
---|
274 | test_output = RemoveStackTraces(test_output)
|
---|
275 |
|
---|
276 | return test_output
|
---|
277 |
|
---|
278 | def testOutput(self):
|
---|
279 | output = GetOutputOfAllCommands()
|
---|
280 |
|
---|
281 | golden_file = open(GOLDEN_PATH, 'rb')
|
---|
282 | # A mis-configured source control system can cause \r appear in EOL
|
---|
283 | # sequences when we read the golden file irrespective of an operating
|
---|
284 | # system used. Therefore, we need to strip those \r's from newlines
|
---|
285 | # unconditionally.
|
---|
286 | golden = ToUnixLineEnding(golden_file.read())
|
---|
287 | golden_file.close()
|
---|
288 |
|
---|
289 | # We want the test to pass regardless of certain features being
|
---|
290 | # supported or not.
|
---|
291 |
|
---|
292 | # We still have to remove type name specifics in all cases.
|
---|
293 | normalized_actual = RemoveTypeInfoDetails(output)
|
---|
294 | normalized_golden = RemoveTypeInfoDetails(golden)
|
---|
295 |
|
---|
296 | if CAN_GENERATE_GOLDEN_FILE:
|
---|
297 | self.assertEqual(normalized_golden, normalized_actual)
|
---|
298 | else:
|
---|
299 | normalized_actual = NormalizeToCurrentPlatform(
|
---|
300 | RemoveTestCounts(normalized_actual))
|
---|
301 | normalized_golden = NormalizeToCurrentPlatform(
|
---|
302 | RemoveTestCounts(self.RemoveUnsupportedTests(normalized_golden)))
|
---|
303 |
|
---|
304 | # This code is very handy when debugging golden file differences:
|
---|
305 | if os.getenv('DEBUG_GTEST_OUTPUT_TEST'):
|
---|
306 | open(os.path.join(
|
---|
307 | gtest_test_utils.GetSourceDir(),
|
---|
308 | '_gtest_output_test_normalized_actual.txt'), 'wb').write(
|
---|
309 | normalized_actual)
|
---|
310 | open(os.path.join(
|
---|
311 | gtest_test_utils.GetSourceDir(),
|
---|
312 | '_gtest_output_test_normalized_golden.txt'), 'wb').write(
|
---|
313 | normalized_golden)
|
---|
314 |
|
---|
315 | self.assertEqual(normalized_golden, normalized_actual)
|
---|
316 |
|
---|
317 |
|
---|
318 | if __name__ == '__main__':
|
---|
319 | if sys.argv[1:] == [GENGOLDEN_FLAG]:
|
---|
320 | if CAN_GENERATE_GOLDEN_FILE:
|
---|
321 | output = GetOutputOfAllCommands()
|
---|
322 | golden_file = open(GOLDEN_PATH, 'wb')
|
---|
323 | golden_file.write(output)
|
---|
324 | golden_file.close()
|
---|
325 | else:
|
---|
326 | message = (
|
---|
327 | """Unable to write a golden file when compiled in an environment
|
---|
328 | that does not support all the required features (death tests, typed tests,
|
---|
329 | and multiple threads). Please generate the golden file using a binary built
|
---|
330 | with those features enabled.""")
|
---|
331 |
|
---|
332 | sys.stderr.write(message)
|
---|
333 | sys.exit(1)
|
---|
334 | else:
|
---|
335 | gtest_test_utils.Main()
|
---|