xref: /openbsd-src/gnu/llvm/lldb/packages/Python/lldbsuite/test/test_result.py (revision be691f3bb6417f04a68938fadbcaee2d5795e764)
1061da546Spatrick"""
2061da546SpatrickPart of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
3061da546SpatrickSee https://llvm.org/LICENSE.txt for license information.
4061da546SpatrickSPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5061da546Spatrick
6061da546SpatrickProvides the LLDBTestResult class, which holds information about progress
7061da546Spatrickand results of a single test run.
8061da546Spatrick"""
9061da546Spatrick
10061da546Spatrick# System modules
11061da546Spatrickimport os
12*be691f3bSpatrickimport traceback
13061da546Spatrick
14061da546Spatrick# Third-party modules
15061da546Spatrickimport unittest2
16061da546Spatrick
17061da546Spatrick# LLDB Modules
18061da546Spatrickfrom . import configuration
19061da546Spatrickfrom lldbsuite.test_event import build_exception
20061da546Spatrick
21061da546Spatrick
22061da546Spatrickclass LLDBTestResult(unittest2.TextTestResult):
23061da546Spatrick    """
24061da546Spatrick    Enforce a singleton pattern to allow introspection of test progress.
25061da546Spatrick
26061da546Spatrick    Overwrite addError(), addFailure(), and addExpectedFailure() methods
27061da546Spatrick    to enable each test instance to track its failure/error status.  It
28061da546Spatrick    is used in the LLDB test framework to emit detailed trace messages
29061da546Spatrick    to a log file for easier human inspection of test failures/errors.
30061da546Spatrick    """
31061da546Spatrick    __singleton__ = None
32061da546Spatrick    __ignore_singleton__ = False
33061da546Spatrick
34061da546Spatrick    @staticmethod
35061da546Spatrick    def getTerminalSize():
36061da546Spatrick        import os
37061da546Spatrick        env = os.environ
38061da546Spatrick
39061da546Spatrick        def ioctl_GWINSZ(fd):
40061da546Spatrick            try:
41061da546Spatrick                import fcntl
42061da546Spatrick                import termios
43061da546Spatrick                import struct
44061da546Spatrick                cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ,
45061da546Spatrick                                                     '1234'))
46061da546Spatrick            except:
47061da546Spatrick                return
48061da546Spatrick            return cr
49061da546Spatrick        cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
50061da546Spatrick        if not cr:
51061da546Spatrick            try:
52061da546Spatrick                fd = os.open(os.ctermid(), os.O_RDONLY)
53061da546Spatrick                cr = ioctl_GWINSZ(fd)
54061da546Spatrick                os.close(fd)
55061da546Spatrick            except:
56061da546Spatrick                pass
57061da546Spatrick        if not cr:
58061da546Spatrick            cr = (env.get('LINES', 25), env.get('COLUMNS', 80))
59061da546Spatrick        return int(cr[1]), int(cr[0])
60061da546Spatrick
61061da546Spatrick    def __init__(self, *args):
62061da546Spatrick        if not LLDBTestResult.__ignore_singleton__ and LLDBTestResult.__singleton__:
63061da546Spatrick            raise Exception("LLDBTestResult instantiated more than once")
64061da546Spatrick        super(LLDBTestResult, self).__init__(*args)
65061da546Spatrick        LLDBTestResult.__singleton__ = self
66061da546Spatrick        # Now put this singleton into the lldb module namespace.
67061da546Spatrick        configuration.test_result = self
68061da546Spatrick        # Computes the format string for displaying the counter.
69061da546Spatrick        counterWidth = len(str(configuration.suite.countTestCases()))
70061da546Spatrick        self.fmt = "%" + str(counterWidth) + "d: "
71061da546Spatrick        self.indentation = ' ' * (counterWidth + 2)
72061da546Spatrick        # This counts from 1 .. suite.countTestCases().
73061da546Spatrick        self.counter = 0
74061da546Spatrick        (width, height) = LLDBTestResult.getTerminalSize()
75061da546Spatrick
76061da546Spatrick    def _config_string(self, test):
77061da546Spatrick        compiler = getattr(test, "getCompiler", None)
78061da546Spatrick        arch = getattr(test, "getArchitecture", None)
79061da546Spatrick        return "%s-%s" % (compiler() if compiler else "",
80061da546Spatrick                          arch() if arch else "")
81061da546Spatrick
82061da546Spatrick    def _exc_info_to_string(self, err, test):
83061da546Spatrick        """Overrides superclass TestResult's method in order to append
84061da546Spatrick        our test config info string to the exception info string."""
85061da546Spatrick        if hasattr(test, "getArchitecture") and hasattr(test, "getCompiler"):
86061da546Spatrick            return '%sConfig=%s-%s' % (super(LLDBTestResult,
87061da546Spatrick                                             self)._exc_info_to_string(err,
88061da546Spatrick                                                                       test),
89061da546Spatrick                                       test.getArchitecture(),
90061da546Spatrick                                       test.getCompiler())
91061da546Spatrick        else:
92061da546Spatrick            return super(LLDBTestResult, self)._exc_info_to_string(err, test)
93061da546Spatrick
94061da546Spatrick    def getDescription(self, test):
95061da546Spatrick        doc_first_line = test.shortDescription()
96061da546Spatrick        if self.descriptions and doc_first_line:
97061da546Spatrick            return '\n'.join((str(test), self.indentation + doc_first_line))
98061da546Spatrick        else:
99061da546Spatrick            return str(test)
100061da546Spatrick
101061da546Spatrick    def _getTestPath(self, test):
102061da546Spatrick        # Use test.test_filename if the test was created with
103061da546Spatrick        # lldbinline.MakeInlineTest().
104061da546Spatrick        if test is None:
105061da546Spatrick            return ""
106061da546Spatrick        elif hasattr(test, "test_filename"):
107061da546Spatrick            return test.test_filename
108061da546Spatrick        else:
109061da546Spatrick            import inspect
110061da546Spatrick            return inspect.getsourcefile(test.__class__)
111061da546Spatrick
112061da546Spatrick    def _getFileBasedCategories(self, test):
113061da546Spatrick        """
114061da546Spatrick        Returns the list of categories to which this test case belongs by
115*be691f3bSpatrick        collecting values of "categories" files. We start at the folder the test is in
116061da546Spatrick        and traverse the hierarchy upwards until the test-suite root directory.
117061da546Spatrick        """
118061da546Spatrick        start_path = self._getTestPath(test)
119061da546Spatrick
120061da546Spatrick        import os.path
121061da546Spatrick        folder = os.path.dirname(start_path)
122061da546Spatrick
123061da546Spatrick        from lldbsuite import lldb_test_root as test_root
124061da546Spatrick        if test_root != os.path.commonprefix([folder, test_root]):
125061da546Spatrick            raise Exception("The test file %s is outside the test root directory" % start_path)
126061da546Spatrick
127061da546Spatrick        categories = set()
128061da546Spatrick        while not os.path.samefile(folder, test_root):
129*be691f3bSpatrick            categories_file_name = os.path.join(folder, "categories")
130061da546Spatrick            if os.path.exists(categories_file_name):
131061da546Spatrick                categories_file = open(categories_file_name, 'r')
132061da546Spatrick                categories_str = categories_file.readline().strip()
133061da546Spatrick                categories_file.close()
134061da546Spatrick                categories.update(categories_str.split(','))
135061da546Spatrick            folder = os.path.dirname(folder)
136061da546Spatrick
137061da546Spatrick        return list(categories)
138061da546Spatrick
139061da546Spatrick    def getCategoriesForTest(self, test):
140061da546Spatrick        """
141061da546Spatrick        Gets all the categories for the currently running test method in test case
142061da546Spatrick        """
143061da546Spatrick        test_categories = []
144061da546Spatrick        test_method = getattr(test, test._testMethodName)
145061da546Spatrick        if test_method is not None and hasattr(test_method, "categories"):
146061da546Spatrick            test_categories.extend(test_method.categories)
147061da546Spatrick
148061da546Spatrick        test_categories.extend(self._getFileBasedCategories(test))
149061da546Spatrick
150061da546Spatrick        return test_categories
151061da546Spatrick
152061da546Spatrick    def hardMarkAsSkipped(self, test):
153061da546Spatrick        getattr(test, test._testMethodName).__func__.__unittest_skip__ = True
154061da546Spatrick        getattr(
155061da546Spatrick            test,
156061da546Spatrick            test._testMethodName).__func__.__unittest_skip_why__ = "test case does not fall in any category of interest for this run"
157061da546Spatrick
158061da546Spatrick    def checkExclusion(self, exclusion_list, name):
159061da546Spatrick        if exclusion_list:
160061da546Spatrick            import re
161061da546Spatrick            for item in exclusion_list:
162061da546Spatrick                if re.search(item, name):
163061da546Spatrick                    return True
164061da546Spatrick        return False
165061da546Spatrick
166061da546Spatrick    def checkCategoryExclusion(self, exclusion_list, test):
167061da546Spatrick        return not set(exclusion_list).isdisjoint(
168061da546Spatrick            self.getCategoriesForTest(test))
169061da546Spatrick
170061da546Spatrick    def startTest(self, test):
171061da546Spatrick        if configuration.shouldSkipBecauseOfCategories(
172061da546Spatrick                self.getCategoriesForTest(test)):
173061da546Spatrick            self.hardMarkAsSkipped(test)
174061da546Spatrick        if self.checkExclusion(
175061da546Spatrick                configuration.skip_tests, test.id()):
176061da546Spatrick            self.hardMarkAsSkipped(test)
177061da546Spatrick
178061da546Spatrick        self.counter += 1
179061da546Spatrick        test.test_number = self.counter
180061da546Spatrick        if self.showAll:
181061da546Spatrick            self.stream.write(self.fmt % self.counter)
182061da546Spatrick        super(LLDBTestResult, self).startTest(test)
183061da546Spatrick
184061da546Spatrick    def addSuccess(self, test):
185061da546Spatrick        if (self.checkExclusion(
186061da546Spatrick                configuration.xfail_tests, test.id()) or
187061da546Spatrick            self.checkCategoryExclusion(
188061da546Spatrick                configuration.xfail_categories, test)):
189061da546Spatrick            self.addUnexpectedSuccess(test, None)
190061da546Spatrick            return
191061da546Spatrick
192061da546Spatrick        super(LLDBTestResult, self).addSuccess(test)
193061da546Spatrick        self.stream.write(
194061da546Spatrick            "PASS: LLDB (%s) :: %s\n" %
195061da546Spatrick            (self._config_string(test), str(test)))
196061da546Spatrick
197061da546Spatrick    def _isBuildError(self, err_tuple):
198061da546Spatrick        exception = err_tuple[1]
199061da546Spatrick        return isinstance(exception, build_exception.BuildError)
200061da546Spatrick
201061da546Spatrick    def _saveBuildErrorTuple(self, test, err):
202061da546Spatrick        # Adjust the error description so it prints the build command and build error
203061da546Spatrick        # rather than an uninformative Python backtrace.
204061da546Spatrick        build_error = err[1]
205061da546Spatrick        error_description = "{}\nTest Directory:\n{}".format(
206061da546Spatrick            str(build_error),
207061da546Spatrick            os.path.dirname(self._getTestPath(test)))
208061da546Spatrick        self.errors.append((test, error_description))
209061da546Spatrick        self._mirrorOutput = True
210061da546Spatrick
211061da546Spatrick    def addError(self, test, err):
212061da546Spatrick        configuration.sdir_has_content = True
213061da546Spatrick        if self._isBuildError(err):
214061da546Spatrick            self._saveBuildErrorTuple(test, err)
215061da546Spatrick        else:
216061da546Spatrick            super(LLDBTestResult, self).addError(test, err)
217061da546Spatrick
218061da546Spatrick        method = getattr(test, "markError", None)
219061da546Spatrick        if method:
220061da546Spatrick            method()
221061da546Spatrick        self.stream.write(
222061da546Spatrick            "FAIL: LLDB (%s) :: %s\n" %
223061da546Spatrick            (self._config_string(test), str(test)))
224061da546Spatrick
225061da546Spatrick    def addCleanupError(self, test, err):
226061da546Spatrick        configuration.sdir_has_content = True
227061da546Spatrick        super(LLDBTestResult, self).addCleanupError(test, err)
228061da546Spatrick        method = getattr(test, "markCleanupError", None)
229061da546Spatrick        if method:
230061da546Spatrick            method()
231061da546Spatrick        self.stream.write(
232*be691f3bSpatrick            "CLEANUP ERROR: LLDB (%s) :: %s\n%s\n" %
233*be691f3bSpatrick            (self._config_string(test), str(test), traceback.format_exc()))
234061da546Spatrick
235061da546Spatrick    def addFailure(self, test, err):
236061da546Spatrick        if (self.checkExclusion(
237061da546Spatrick                configuration.xfail_tests, test.id()) or
238061da546Spatrick            self.checkCategoryExclusion(
239061da546Spatrick                configuration.xfail_categories, test)):
240061da546Spatrick            self.addExpectedFailure(test, err, None)
241061da546Spatrick            return
242061da546Spatrick
243061da546Spatrick        configuration.sdir_has_content = True
244061da546Spatrick        super(LLDBTestResult, self).addFailure(test, err)
245061da546Spatrick        method = getattr(test, "markFailure", None)
246061da546Spatrick        if method:
247061da546Spatrick            method()
248061da546Spatrick        self.stream.write(
249061da546Spatrick            "FAIL: LLDB (%s) :: %s\n" %
250061da546Spatrick            (self._config_string(test), str(test)))
251061da546Spatrick        if configuration.use_categories:
252061da546Spatrick            test_categories = self.getCategoriesForTest(test)
253061da546Spatrick            for category in test_categories:
254061da546Spatrick                if category in configuration.failures_per_category:
255061da546Spatrick                    configuration.failures_per_category[
256061da546Spatrick                        category] = configuration.failures_per_category[category] + 1
257061da546Spatrick                else:
258061da546Spatrick                    configuration.failures_per_category[category] = 1
259061da546Spatrick
260061da546Spatrick    def addExpectedFailure(self, test, err, bugnumber):
261061da546Spatrick        configuration.sdir_has_content = True
262061da546Spatrick        super(LLDBTestResult, self).addExpectedFailure(test, err, bugnumber)
263061da546Spatrick        method = getattr(test, "markExpectedFailure", None)
264061da546Spatrick        if method:
265061da546Spatrick            method(err, bugnumber)
266061da546Spatrick        self.stream.write(
267061da546Spatrick            "XFAIL: LLDB (%s) :: %s\n" %
268061da546Spatrick            (self._config_string(test), str(test)))
269061da546Spatrick
270061da546Spatrick    def addSkip(self, test, reason):
271061da546Spatrick        configuration.sdir_has_content = True
272061da546Spatrick        super(LLDBTestResult, self).addSkip(test, reason)
273061da546Spatrick        method = getattr(test, "markSkippedTest", None)
274061da546Spatrick        if method:
275061da546Spatrick            method()
276061da546Spatrick        self.stream.write(
277061da546Spatrick            "UNSUPPORTED: LLDB (%s) :: %s (%s) \n" %
278061da546Spatrick            (self._config_string(test), str(test), reason))
279061da546Spatrick
280061da546Spatrick    def addUnexpectedSuccess(self, test, bugnumber):
281061da546Spatrick        configuration.sdir_has_content = True
282061da546Spatrick        super(LLDBTestResult, self).addUnexpectedSuccess(test, bugnumber)
283061da546Spatrick        method = getattr(test, "markUnexpectedSuccess", None)
284061da546Spatrick        if method:
285061da546Spatrick            method(bugnumber)
286061da546Spatrick        self.stream.write(
287061da546Spatrick            "XPASS: LLDB (%s) :: %s\n" %
288061da546Spatrick            (self._config_string(test), str(test)))
289