1"""
2                     The LLVM Compiler Infrastructure
3
4This file is distributed under the University of Illinois Open Source
5License. See LICENSE.TXT for details.
6
7Provides the LLDBTestResult class, which holds information about progress
8and results of a single test run.
9"""
10
11from __future__ import absolute_import
12from __future__ import print_function
13
14# System modules
15import inspect
16import os
17
18# Third-party modules
19import unittest2
20
21# LLDB Modules
22from . import configuration
23from lldbsuite.test_event.event_builder import EventBuilder
24from lldbsuite.test_event import build_exception
25
26
27class LLDBTestResult(unittest2.TextTestResult):
28    """
29    Enforce a singleton pattern to allow introspection of test progress.
30
31    Overwrite addError(), addFailure(), and addExpectedFailure() methods
32    to enable each test instance to track its failure/error status.  It
33    is used in the LLDB test framework to emit detailed trace messages
34    to a log file for easier human inspection of test failures/errors.
35    """
36    __singleton__ = None
37    __ignore_singleton__ = False
38
39    @staticmethod
40    def getTerminalSize():
41        import os
42        env = os.environ
43
44        def ioctl_GWINSZ(fd):
45            try:
46                import fcntl
47                import termios
48                import struct
49                import os
50                cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ,
51                                                     '1234'))
52            except:
53                return
54            return cr
55        cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
56        if not cr:
57            try:
58                fd = os.open(os.ctermid(), os.O_RDONLY)
59                cr = ioctl_GWINSZ(fd)
60                os.close(fd)
61            except:
62                pass
63        if not cr:
64            cr = (env.get('LINES', 25), env.get('COLUMNS', 80))
65        return int(cr[1]), int(cr[0])
66
67    def __init__(self, *args):
68        if not LLDBTestResult.__ignore_singleton__ and LLDBTestResult.__singleton__:
69            raise Exception("LLDBTestResult instantiated more than once")
70        super(LLDBTestResult, self).__init__(*args)
71        LLDBTestResult.__singleton__ = self
72        # Now put this singleton into the lldb module namespace.
73        configuration.test_result = self
74        # Computes the format string for displaying the counter.
75        counterWidth = len(str(configuration.suite.countTestCases()))
76        self.fmt = "%" + str(counterWidth) + "d: "
77        self.indentation = ' ' * (counterWidth + 2)
78        # This counts from 1 .. suite.countTestCases().
79        self.counter = 0
80        (width, height) = LLDBTestResult.getTerminalSize()
81        self.results_formatter = configuration.results_formatter_object
82
83    def _config_string(self, test):
84        compiler = getattr(test, "getCompiler", None)
85        arch = getattr(test, "getArchitecture", None)
86        return "%s-%s" % (compiler() if compiler else "",
87                          arch() if arch else "")
88
89    def _exc_info_to_string(self, err, test):
90        """Overrides superclass TestResult's method in order to append
91        our test config info string to the exception info string."""
92        if hasattr(test, "getArchitecture") and hasattr(test, "getCompiler"):
93            return '%sConfig=%s-%s' % (super(LLDBTestResult,
94                                             self)._exc_info_to_string(err,
95                                                                       test),
96                                       test.getArchitecture(),
97                                       test.getCompiler())
98        else:
99            return super(LLDBTestResult, self)._exc_info_to_string(err, test)
100
101    def getDescription(self, test):
102        doc_first_line = test.shortDescription()
103        if self.descriptions and doc_first_line:
104            return '\n'.join((str(test), self.indentation + doc_first_line))
105        else:
106            return str(test)
107
108    def getCategoriesForTest(self, test):
109        """
110        Gets all the categories for the currently running test method in test case
111        """
112        test_categories = []
113        test_method = getattr(test, test._testMethodName)
114        if test_method is not None and hasattr(test_method, "categories"):
115            test_categories.extend(test_method.categories)
116
117        test_categories.extend(test.getCategories())
118
119        return test_categories
120
121    def hardMarkAsSkipped(self, test):
122        getattr(test, test._testMethodName).__func__.__unittest_skip__ = True
123        getattr(
124            test,
125            test._testMethodName).__func__.__unittest_skip_why__ = "test case does not fall in any category of interest for this run"
126
127    def checkExclusion(self, exclusion_list, name):
128        if exclusion_list:
129            import re
130            for item in exclusion_list:
131                if re.search(item, name):
132                    return True
133        return False
134
135    def startTest(self, test):
136        if configuration.shouldSkipBecauseOfCategories(
137                self.getCategoriesForTest(test)):
138            self.hardMarkAsSkipped(test)
139        if self.checkExclusion(
140                configuration.skip_tests, test.id()):
141            self.hardMarkAsSkipped(test)
142
143        configuration.setCrashInfoHook(
144            "%s at %s" %
145            (str(test), inspect.getfile(
146                test.__class__)))
147        self.counter += 1
148        # if self.counter == 4:
149        #    import crashinfo
150        #    crashinfo.testCrashReporterDescription(None)
151        test.test_number = self.counter
152        if self.showAll:
153            self.stream.write(self.fmt % self.counter)
154        super(LLDBTestResult, self).startTest(test)
155        if self.results_formatter:
156            self.results_formatter.handle_event(
157                EventBuilder.event_for_start(test))
158
159    def addSuccess(self, test):
160        if self.checkExclusion(
161                configuration.xfail_tests, test.id()):
162            self.addUnexpectedSuccess(test, None)
163            return
164
165        super(LLDBTestResult, self).addSuccess(test)
166        if configuration.parsable:
167            self.stream.write(
168                "PASS: LLDB (%s) :: %s\n" %
169                (self._config_string(test), str(test)))
170        if self.results_formatter:
171            self.results_formatter.handle_event(
172                EventBuilder.event_for_success(test))
173
174    def _isBuildError(self, err_tuple):
175        exception = err_tuple[1]
176        return isinstance(exception, build_exception.BuildError)
177
178    def _getTestPath(self, test):
179        if test is None:
180            return ""
181        elif hasattr(test, "test_filename"):
182            return test.test_filename
183        else:
184            return inspect.getsourcefile(test.__class__)
185
186    def _saveBuildErrorTuple(self, test, err):
187        # Adjust the error description so it prints the build command and build error
188        # rather than an uninformative Python backtrace.
189        build_error = err[1]
190        error_description = "{}\nTest Directory:\n{}".format(
191            str(build_error),
192            os.path.dirname(self._getTestPath(test)))
193        self.errors.append((test, error_description))
194        self._mirrorOutput = True
195
196    def addError(self, test, err):
197        configuration.sdir_has_content = True
198        if self._isBuildError(err):
199            self._saveBuildErrorTuple(test, err)
200        else:
201            super(LLDBTestResult, self).addError(test, err)
202
203        method = getattr(test, "markError", None)
204        if method:
205            method()
206        if configuration.parsable:
207            self.stream.write(
208                "FAIL: LLDB (%s) :: %s\n" %
209                (self._config_string(test), str(test)))
210        if self.results_formatter:
211            # Handle build errors as a separate event type
212            if self._isBuildError(err):
213                error_event = EventBuilder.event_for_build_error(test, err)
214            else:
215                error_event = EventBuilder.event_for_error(test, err)
216            self.results_formatter.handle_event(error_event)
217
218    def addCleanupError(self, test, err):
219        configuration.sdir_has_content = True
220        super(LLDBTestResult, self).addCleanupError(test, err)
221        method = getattr(test, "markCleanupError", None)
222        if method:
223            method()
224        if configuration.parsable:
225            self.stream.write(
226                "CLEANUP ERROR: LLDB (%s) :: %s\n" %
227                (self._config_string(test), str(test)))
228        if self.results_formatter:
229            self.results_formatter.handle_event(
230                EventBuilder.event_for_cleanup_error(
231                    test, err))
232
233    def addFailure(self, test, err):
234        if self.checkExclusion(
235                configuration.xfail_tests, test.id()):
236            self.addExpectedFailure(test, err, None)
237            return
238
239        configuration.sdir_has_content = True
240        super(LLDBTestResult, self).addFailure(test, err)
241        method = getattr(test, "markFailure", None)
242        if method:
243            method()
244        if configuration.parsable:
245            self.stream.write(
246                "FAIL: LLDB (%s) :: %s\n" %
247                (self._config_string(test), str(test)))
248        if configuration.useCategories:
249            test_categories = self.getCategoriesForTest(test)
250            for category in test_categories:
251                if category in configuration.failuresPerCategory:
252                    configuration.failuresPerCategory[
253                        category] = configuration.failuresPerCategory[category] + 1
254                else:
255                    configuration.failuresPerCategory[category] = 1
256        if self.results_formatter:
257            self.results_formatter.handle_event(
258                EventBuilder.event_for_failure(test, err))
259
260    def addExpectedFailure(self, test, err, bugnumber):
261        configuration.sdir_has_content = True
262        super(LLDBTestResult, self).addExpectedFailure(test, err, bugnumber)
263        method = getattr(test, "markExpectedFailure", None)
264        if method:
265            method(err, bugnumber)
266        if configuration.parsable:
267            self.stream.write(
268                "XFAIL: LLDB (%s) :: %s\n" %
269                (self._config_string(test), str(test)))
270        if self.results_formatter:
271            self.results_formatter.handle_event(
272                EventBuilder.event_for_expected_failure(
273                    test, err, bugnumber))
274
275    def addSkip(self, test, reason):
276        configuration.sdir_has_content = True
277        super(LLDBTestResult, self).addSkip(test, reason)
278        method = getattr(test, "markSkippedTest", None)
279        if method:
280            method()
281        if configuration.parsable:
282            self.stream.write(
283                "UNSUPPORTED: LLDB (%s) :: %s (%s) \n" %
284                (self._config_string(test), str(test), reason))
285        if self.results_formatter:
286            self.results_formatter.handle_event(
287                EventBuilder.event_for_skip(test, reason))
288
289    def addUnexpectedSuccess(self, test, bugnumber):
290        configuration.sdir_has_content = True
291        super(LLDBTestResult, self).addUnexpectedSuccess(test, bugnumber)
292        method = getattr(test, "markUnexpectedSuccess", None)
293        if method:
294            method(bugnumber)
295        if configuration.parsable:
296            self.stream.write(
297                "XPASS: LLDB (%s) :: %s\n" %
298                (self._config_string(test), str(test)))
299        if self.results_formatter:
300            self.results_formatter.handle_event(
301                EventBuilder.event_for_unexpected_success(
302                    test, bugnumber))
303