summaryrefslogtreecommitdiffstats
path: root/src/rocksdb/build_tools/error_filter.py
blob: c42df1f91e5e30fa83f84845d52d253f1f0bb229 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
#  Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
#  This source code is licensed under both the GPLv2 (found in the
#  COPYING file in the root directory) and Apache 2.0 License
#  (found in the LICENSE.Apache file in the root directory).

"""Filter for error messages in test output:
    - Receives merged stdout/stderr from test on stdin
    - Finds patterns of known error messages for test name (first argument)
    - Prints those error messages to stdout
"""

from __future__ import absolute_import, division, print_function, unicode_literals

import re
import sys


class ErrorParserBase(object):
    def parse_error(self, line):
        """Parses a line of test output. If it contains an error, returns a
        formatted message describing the error; otherwise, returns None.
        Subclasses must override this method.
        """
        raise NotImplementedError


class GTestErrorParser(ErrorParserBase):
    """A parser that remembers the last test that began running so it can print
    that test's name upon detecting failure.
    """

    _GTEST_NAME_PATTERN = re.compile(r"\[ RUN      \] (\S+)$")
    # format: '<filename or "unknown file">:<line #>: Failure'
    _GTEST_FAIL_PATTERN = re.compile(r"(unknown file|\S+:\d+): Failure$")

    def __init__(self):
        self._last_gtest_name = "Unknown test"

    def parse_error(self, line):
        gtest_name_match = self._GTEST_NAME_PATTERN.match(line)
        if gtest_name_match:
            self._last_gtest_name = gtest_name_match.group(1)
            return None
        gtest_fail_match = self._GTEST_FAIL_PATTERN.match(line)
        if gtest_fail_match:
            return "%s failed: %s" % (self._last_gtest_name, gtest_fail_match.group(1))
        return None


class MatchErrorParser(ErrorParserBase):
    """A simple parser that returns the whole line if it matches the pattern."""

    def __init__(self, pattern):
        self._pattern = re.compile(pattern)

    def parse_error(self, line):
        if self._pattern.match(line):
            return line
        return None


class CompilerErrorParser(MatchErrorParser):
    def __init__(self):
        # format (compile error):
        #   '<filename>:<line #>:<column #>: error: <error msg>'
        # format (link error):
        #   '<filename>:<line #>: error: <error msg>'
        # The below regex catches both
        super(CompilerErrorParser, self).__init__(r"\S+:\d+: error:")


class ScanBuildErrorParser(MatchErrorParser):
    def __init__(self):
        super(ScanBuildErrorParser, self).__init__(r"scan-build: \d+ bugs found.$")


class DbCrashErrorParser(MatchErrorParser):
    def __init__(self):
        super(DbCrashErrorParser, self).__init__(r"\*\*\*.*\^$|TEST FAILED.")


class WriteStressErrorParser(MatchErrorParser):
    def __init__(self):
        super(WriteStressErrorParser, self).__init__(
            r"ERROR: write_stress died with exitcode=\d+"
        )


class AsanErrorParser(MatchErrorParser):
    def __init__(self):
        super(AsanErrorParser, self).__init__(r"==\d+==ERROR: AddressSanitizer:")


class UbsanErrorParser(MatchErrorParser):
    def __init__(self):
        # format: '<filename>:<line #>:<column #>: runtime error: <error msg>'
        super(UbsanErrorParser, self).__init__(r"\S+:\d+:\d+: runtime error:")


class ValgrindErrorParser(MatchErrorParser):
    def __init__(self):
        # just grab the summary, valgrind doesn't clearly distinguish errors
        # from other log messages.
        super(ValgrindErrorParser, self).__init__(r"==\d+== ERROR SUMMARY:")


class CompatErrorParser(MatchErrorParser):
    def __init__(self):
        super(CompatErrorParser, self).__init__(r"==== .*[Ee]rror.* ====$")


class TsanErrorParser(MatchErrorParser):
    def __init__(self):
        super(TsanErrorParser, self).__init__(r"WARNING: ThreadSanitizer:")


_TEST_NAME_TO_PARSERS = {
    "punit": [CompilerErrorParser, GTestErrorParser],
    "unit": [CompilerErrorParser, GTestErrorParser],
    "release": [CompilerErrorParser, GTestErrorParser],
    "unit_481": [CompilerErrorParser, GTestErrorParser],
    "release_481": [CompilerErrorParser, GTestErrorParser],
    "clang_unit": [CompilerErrorParser, GTestErrorParser],
    "clang_release": [CompilerErrorParser, GTestErrorParser],
    "clang_analyze": [CompilerErrorParser, ScanBuildErrorParser],
    "code_cov": [CompilerErrorParser, GTestErrorParser],
    "unity": [CompilerErrorParser, GTestErrorParser],
    "lite": [CompilerErrorParser],
    "lite_test": [CompilerErrorParser, GTestErrorParser],
    "stress_crash": [CompilerErrorParser, DbCrashErrorParser],
    "stress_crash_with_atomic_flush": [CompilerErrorParser, DbCrashErrorParser],
    "stress_crash_with_txn": [CompilerErrorParser, DbCrashErrorParser],
    "write_stress": [CompilerErrorParser, WriteStressErrorParser],
    "asan": [CompilerErrorParser, GTestErrorParser, AsanErrorParser],
    "asan_crash": [CompilerErrorParser, AsanErrorParser, DbCrashErrorParser],
    "asan_crash_with_atomic_flush": [
        CompilerErrorParser,
        AsanErrorParser,
        DbCrashErrorParser,
    ],
    "asan_crash_with_txn": [CompilerErrorParser, AsanErrorParser, DbCrashErrorParser],
    "ubsan": [CompilerErrorParser, GTestErrorParser, UbsanErrorParser],
    "ubsan_crash": [CompilerErrorParser, UbsanErrorParser, DbCrashErrorParser],
    "ubsan_crash_with_atomic_flush": [
        CompilerErrorParser,
        UbsanErrorParser,
        DbCrashErrorParser,
    ],
    "ubsan_crash_with_txn": [CompilerErrorParser, UbsanErrorParser, DbCrashErrorParser],
    "valgrind": [CompilerErrorParser, GTestErrorParser, ValgrindErrorParser],
    "tsan": [CompilerErrorParser, GTestErrorParser, TsanErrorParser],
    "format_compatible": [CompilerErrorParser, CompatErrorParser],
    "run_format_compatible": [CompilerErrorParser, CompatErrorParser],
    "no_compression": [CompilerErrorParser, GTestErrorParser],
    "run_no_compression": [CompilerErrorParser, GTestErrorParser],
    "regression": [CompilerErrorParser],
    "run_regression": [CompilerErrorParser],
}


def main():
    if len(sys.argv) != 2:
        return "Usage: %s <test name>" % sys.argv[0]
    test_name = sys.argv[1]
    if test_name not in _TEST_NAME_TO_PARSERS:
        return "Unknown test name: %s" % test_name

    error_parsers = []
    for parser_cls in _TEST_NAME_TO_PARSERS[test_name]:
        error_parsers.append(parser_cls())

    for line in sys.stdin:
        line = line.strip()
        for error_parser in error_parsers:
            error_msg = error_parser.parse_error(line)
            if error_msg is not None:
                print(error_msg)


if __name__ == "__main__":
    sys.exit(main())