1# Copyright (c) 2011-present, Facebook, Inc. All rights reserved. 2# This source code is licensed under both the GPLv2 (found in the 3# COPYING file in the root directory) and Apache 2.0 License 4# (found in the LICENSE.Apache file in the root directory). 5 6'''Filter for error messages in test output: 7 - Receives merged stdout/stderr from test on stdin 8 - Finds patterns of known error messages for test name (first argument) 9 - Prints those error messages to stdout 10''' 11 12from __future__ import absolute_import 13from __future__ import division 14from __future__ import print_function 15from __future__ import unicode_literals 16 17import re 18import sys 19 20 21class ErrorParserBase(object): 22 def parse_error(self, line): 23 '''Parses a line of test output. If it contains an error, returns a 24 formatted message describing the error; otherwise, returns None. 25 Subclasses must override this method. 26 ''' 27 raise NotImplementedError 28 29 30class GTestErrorParser(ErrorParserBase): 31 '''A parser that remembers the last test that began running so it can print 32 that test's name upon detecting failure. 33 ''' 34 _GTEST_NAME_PATTERN = re.compile(r'\[ RUN \] (\S+)$') 35 # format: '<filename or "unknown file">:<line #>: Failure' 36 _GTEST_FAIL_PATTERN = re.compile(r'(unknown file|\S+:\d+): Failure$') 37 38 def __init__(self): 39 self._last_gtest_name = 'Unknown test' 40 41 def parse_error(self, line): 42 gtest_name_match = self._GTEST_NAME_PATTERN.match(line) 43 if gtest_name_match: 44 self._last_gtest_name = gtest_name_match.group(1) 45 return None 46 gtest_fail_match = self._GTEST_FAIL_PATTERN.match(line) 47 if gtest_fail_match: 48 return '%s failed: %s' % ( 49 self._last_gtest_name, gtest_fail_match.group(1)) 50 return None 51 52 53class MatchErrorParser(ErrorParserBase): 54 '''A simple parser that returns the whole line if it matches the pattern. 55 ''' 56 def __init__(self, pattern): 57 self._pattern = re.compile(pattern) 58 59 def parse_error(self, line): 60 if self._pattern.match(line): 61 return line 62 return None 63 64 65class CompilerErrorParser(MatchErrorParser): 66 def __init__(self): 67 # format (compile error): 68 # '<filename>:<line #>:<column #>: error: <error msg>' 69 # format (link error): 70 # '<filename>:<line #>: error: <error msg>' 71 # The below regex catches both 72 super(CompilerErrorParser, self).__init__(r'\S+:\d+: error:') 73 74 75class ScanBuildErrorParser(MatchErrorParser): 76 def __init__(self): 77 super(ScanBuildErrorParser, self).__init__( 78 r'scan-build: \d+ bugs found.$') 79 80 81class DbCrashErrorParser(MatchErrorParser): 82 def __init__(self): 83 super(DbCrashErrorParser, self).__init__(r'\*\*\*.*\^$|TEST FAILED.') 84 85 86class WriteStressErrorParser(MatchErrorParser): 87 def __init__(self): 88 super(WriteStressErrorParser, self).__init__( 89 r'ERROR: write_stress died with exitcode=\d+') 90 91 92class AsanErrorParser(MatchErrorParser): 93 def __init__(self): 94 super(AsanErrorParser, self).__init__( 95 r'==\d+==ERROR: AddressSanitizer:') 96 97 98class UbsanErrorParser(MatchErrorParser): 99 def __init__(self): 100 # format: '<filename>:<line #>:<column #>: runtime error: <error msg>' 101 super(UbsanErrorParser, self).__init__(r'\S+:\d+:\d+: runtime error:') 102 103 104class ValgrindErrorParser(MatchErrorParser): 105 def __init__(self): 106 # just grab the summary, valgrind doesn't clearly distinguish errors 107 # from other log messages. 108 super(ValgrindErrorParser, self).__init__(r'==\d+== ERROR SUMMARY:') 109 110 111class CompatErrorParser(MatchErrorParser): 112 def __init__(self): 113 super(CompatErrorParser, self).__init__(r'==== .*[Ee]rror.* ====$') 114 115 116class TsanErrorParser(MatchErrorParser): 117 def __init__(self): 118 super(TsanErrorParser, self).__init__(r'WARNING: ThreadSanitizer:') 119 120 121_TEST_NAME_TO_PARSERS = { 122 'punit': [CompilerErrorParser, GTestErrorParser], 123 'unit': [CompilerErrorParser, GTestErrorParser], 124 'release': [CompilerErrorParser, GTestErrorParser], 125 'unit_481': [CompilerErrorParser, GTestErrorParser], 126 'release_481': [CompilerErrorParser, GTestErrorParser], 127 'clang_unit': [CompilerErrorParser, GTestErrorParser], 128 'clang_release': [CompilerErrorParser, GTestErrorParser], 129 'clang_analyze': [CompilerErrorParser, ScanBuildErrorParser], 130 'code_cov': [CompilerErrorParser, GTestErrorParser], 131 'unity': [CompilerErrorParser, GTestErrorParser], 132 'lite': [CompilerErrorParser], 133 'lite_test': [CompilerErrorParser, GTestErrorParser], 134 'stress_crash': [CompilerErrorParser, DbCrashErrorParser], 135 'stress_crash_with_atomic_flush': [CompilerErrorParser, DbCrashErrorParser], 136 'stress_crash_with_txn': [CompilerErrorParser, DbCrashErrorParser], 137 'write_stress': [CompilerErrorParser, WriteStressErrorParser], 138 'asan': [CompilerErrorParser, GTestErrorParser, AsanErrorParser], 139 'asan_crash': [CompilerErrorParser, AsanErrorParser, DbCrashErrorParser], 140 'asan_crash_with_atomic_flush': [CompilerErrorParser, AsanErrorParser, DbCrashErrorParser], 141 'asan_crash_with_txn': [CompilerErrorParser, AsanErrorParser, DbCrashErrorParser], 142 'ubsan': [CompilerErrorParser, GTestErrorParser, UbsanErrorParser], 143 'ubsan_crash': [CompilerErrorParser, UbsanErrorParser, DbCrashErrorParser], 144 'ubsan_crash_with_atomic_flush': [CompilerErrorParser, UbsanErrorParser, DbCrashErrorParser], 145 'ubsan_crash_with_txn': [CompilerErrorParser, UbsanErrorParser, DbCrashErrorParser], 146 'valgrind': [CompilerErrorParser, GTestErrorParser, ValgrindErrorParser], 147 'tsan': [CompilerErrorParser, GTestErrorParser, TsanErrorParser], 148 'format_compatible': [CompilerErrorParser, CompatErrorParser], 149 'run_format_compatible': [CompilerErrorParser, CompatErrorParser], 150 'no_compression': [CompilerErrorParser, GTestErrorParser], 151 'run_no_compression': [CompilerErrorParser, GTestErrorParser], 152 'regression': [CompilerErrorParser], 153 'run_regression': [CompilerErrorParser], 154} 155 156 157def main(): 158 if len(sys.argv) != 2: 159 return 'Usage: %s <test name>' % sys.argv[0] 160 test_name = sys.argv[1] 161 if test_name not in _TEST_NAME_TO_PARSERS: 162 return 'Unknown test name: %s' % test_name 163 164 error_parsers = [] 165 for parser_cls in _TEST_NAME_TO_PARSERS[test_name]: 166 error_parsers.append(parser_cls()) 167 168 for line in sys.stdin: 169 line = line.strip() 170 for error_parser in error_parsers: 171 error_msg = error_parser.parse_error(line) 172 if error_msg is not None: 173 print(error_msg) 174 175 176if __name__ == '__main__': 177 sys.exit(main()) 178