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