From eabc5711021cb01655b26007522bfccb8fefa806 Mon Sep 17 00:00:00 2001 From: Wojtek Porczyk Date: Wed, 7 Jan 2015 16:46:59 +0100 Subject: [PATCH] qubes/tests: colourful test runner --- qubes/tests/run.py | 179 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 177 insertions(+), 2 deletions(-) diff --git a/qubes/tests/run.py b/qubes/tests/run.py index 8a5b703a..0d3d9080 100755 --- a/qubes/tests/run.py +++ b/qubes/tests/run.py @@ -1,5 +1,6 @@ #!/usr/bin/python -O +import curses import importlib import sys import unittest @@ -13,6 +14,178 @@ test_order = [ sys.path.insert(0, '../../') +class ANSIColor(dict): + def __init__(self): + super(ANSIColor, self).__init__() + try: + curses.setupterm() + except curses.error: + return + + self['black'] = curses.tparm(curses.tigetstr('setaf'), 0) + self['red'] = curses.tparm(curses.tigetstr('setaf'), 1) + self['green'] = curses.tparm(curses.tigetstr('setaf'), 2) + self['yellow'] = curses.tparm(curses.tigetstr('setaf'), 3) + self['blue'] = curses.tparm(curses.tigetstr('setaf'), 4) + self['magenta'] = curses.tparm(curses.tigetstr('setaf'), 5) + self['cyan'] = curses.tparm(curses.tigetstr('setaf'), 6) + self['white'] = curses.tparm(curses.tigetstr('setaf'), 7) + + self['bold'] = curses.tigetstr('bold') + self['normal'] = curses.tigetstr('sgr0') + + def __missing__(self, key): + return '' + + +class ANSITestResult(unittest.TestResult): + '''A test result class that can print colourful text results to a stream. + + Used by TextTestRunner. This is a lightly rewritten unittest.TextTestResult. + ''' + + separator1 = unittest.TextTestResult.separator1 + separator2 = unittest.TextTestResult.separator2 + + def __init__(self, stream, descriptions, verbosity): + super(ANSITestResult, self).__init__(stream, descriptions, verbosity) + self.stream = stream + self.showAll = verbosity > 1 + self.dots = verbosity == 1 + self.descriptions = descriptions + + self.color = ANSIColor() + + def _fmtexc(self, err): + s = str(err[1]) + if s: + return '{bold}{}:{normal} {!s}'.format( + err[0].__name__, err[1], **self.color) + else: + return '{bold}{}{normal}'.format(err[0].__name__, **self.color) + + def getDescription(self, test): + teststr = str(test).split('/') + teststr[-1] = '{bold}{}{normal}'.format(teststr[-1], **self.color) + teststr = '/'.join(teststr) + + doc_first_line = test.shortDescription() + if self.descriptions and doc_first_line: + return '\n'.join((teststr, ' {}'.format( + doc_first_line, **self.color))) + else: + return teststr + + def startTest(self, test): + super(ANSITestResult, self).startTest(test) + if self.showAll: + self.stream.write(self.getDescription(test)) + self.stream.write(' ... ') + self.stream.flush() + + def addSuccess(self, test): + super(ANSITestResult, self).addSuccess(test) + if self.showAll: + self.stream.writeln('{green}ok{normal}'.format(**self.color)) + elif self.dots: + self.stream.write('.') + self.stream.flush() + + def addError(self, test, err): + super(ANSITestResult, self).addError(test, err) + if self.showAll: + self.stream.writeln('{red}{bold}ERROR{normal} ({})'.format( + self._fmtexc(err), **self.color)) + elif self.dots: + self.stream.write('{red}{bold}E{normal}'.format(**self.color)) + self.stream.flush() + + def addFailure(self, test, err): + super(ANSITestResult, self).addFailure(test, err) + if self.showAll: + self.stream.writeln('{red}FAIL{normal}'.format(**self.color)) + elif self.dots: + self.stream.write('{red}F{normal}'.format(**self.color)) + self.stream.flush() + + def addSkip(self, test, reason): + super(ANSITestResult, self).addSkip(test, reason) + if self.showAll: + self.stream.writeln('{cyan}skipped{normal} ({})'.format( + reason, **self.color)) + elif self.dots: + self.stream.write('{cyan}s{normal}'.format(**self.color)) + self.stream.flush() + + def addExpectedFailure(self, test, err): + super(ANSITestResult, self).addExpectedFailure(test, err) + if self.showAll: + self.stream.writeln('{yellow}expected failure{normal}'.format( + **self.color)) + elif self.dots: + self.stream.write('{yellow}x{normal}'.format(**self.color)) + self.stream.flush() + + def addUnexpectedSuccess(self, test): + super(ANSITestResult, self).addUnexpectedSuccess(test) + if self.showAll: + self.stream.writeln( + '{yellow}{bold}unexpected success{normal}'.format(**self.color)) + elif self.dots: + self.stream.write('{yellow}{bold}u{normal}'.format(**self.color)) + self.stream.flush() + + def printErrors(self): + if self.dots or self.showAll: + self.stream.writeln() + self.printErrorList( + '{red}{bold}ERROR{normal}'.format(**self.color), self.errors) + self.printErrorList( + '{red}FAIL{normal}'.format(**self.color), self.failures) + + def printErrorList(self, flavour, errors): + for test, err in errors: + self.stream.writeln(self.separator1) + self.stream.writeln('%s: %s' % (flavour,self.getDescription(test))) + self.stream.writeln(self.separator2) + self.stream.writeln('%s' % err) + + +def demo(verbosity=2): + import qubes.tests + class TC_Demo(qubes.tests.QubesTestCase): + '''Demo class''' + def test_0_success(self): + '''Demo test (success)''' + pass + def test_1_error(self): + '''Demo test (error)''' + raise Exception() + def test_2_failure(self): + '''Demo test (failure)''' + self.fail('boo') + def test_3_skip(self): + '''Demo test (skipped by call to self.skipTest())''' + self.skipTest('skip') + @unittest.skip(None) + def test_4_skip_decorator(self): + '''Demo test (skipped by decorator)''' + pass + @unittest.expectedFailure + def test_5_expected_failure(self): + '''Demo test (expected failure)''' + self.fail() + @unittest.expectedFailure + def test_6_unexpected_success(self): + '''Demo test (unexpected success)''' + pass + + suite = unittest.TestLoader().loadTestsFromTestCase(TC_Demo) + runner = unittest.TextTestRunner(stream=sys.stdout, verbosity=verbosity) + runner.resultclass = ANSITestResult + return runner.run(suite).wasSuccessful() + + def main(): suite = unittest.TestSuite() loader = unittest.TestLoader() @@ -20,7 +193,9 @@ def main(): module = importlib.import_module(modname) suite.addTests(loader.loadTestsFromModule(module)) - unittest.TextTestRunner(stream=sys.stdout, verbosity=2).run(suite) + runner = unittest.TextTestRunner(stream=sys.stdout, verbosity=2) + runner.resultclass = ANSITestResult + return runner.run(suite).wasSuccessful() if __name__ == '__main__': - main() + sys.exit(not main())