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