#!/usr/bin/env python import gettext import os import unittest import sys import logging from nose import result from nose import core from nose import config from nose.plugins.skip import SkipTest class _AnsiColorizer(object): """ A colorizer is an object that loosely wraps around a stream, allowing callers to write text to the stream in a particular color. Colorizer classes must implement C{supported()} and C{write(text, color)}. """ _colors = dict(black=30, red=31, green=32, yellow=33, blue=34, magenta=35, cyan=36, white=37) def __init__(self, stream): self.stream = stream def supported(cls, stream=sys.stdout): """ A class method that returns True if the current platform supports coloring terminal output using this method. Returns False otherwise. """ if not stream.isatty(): return False # auto color only on TTYs try: import curses except ImportError: return False else: try: try: return curses.tigetnum("colors") > 2 except curses.error: curses.setupterm() return curses.tigetnum("colors") > 2 except: raise # guess false in case of error return False supported = classmethod(supported) def write(self, text, color): """ Write the given text to the stream in the given color. @param text: Text to be written to the stream. @param color: A string label for a color. e.g. 'red', 'white'. """ color = self._colors[color] self.stream.write('\x1b[%s;1m%s\x1b[0m' % (color, text)) class _Win32Colorizer(object): """ See _AnsiColorizer docstring. """ def __init__(self, stream): from win32console import GetStdHandle, STD_OUT_HANDLE from win32console import FOREGROUND_RED, FOREGROUND_BLUE from win32console import FOREGROUND_GREEN, FOREGROUND_INTENSITY red, green, blue, bold = (FOREGROUND_RED, FOREGROUND_GREEN, FOREGROUND_BLUE, FOREGROUND_INTENSITY) self.stream = stream self.screenBuffer = GetStdHandle(STD_OUT_HANDLE) self._colors = { 'normal': red | green | blue, 'red': red | bold, 'green': green | bold, 'blue': blue | bold, 'yellow': red | green | bold, 'magenta': red | blue | bold, 'cyan': green | blue | bold, 'white': red | green | blue | bold} def supported(cls, stream=sys.stdout): try: import win32console screenBuffer = win32console.GetStdHandle( win32console.STD_OUT_HANDLE) except ImportError: return False import pywintypes try: screenBuffer.SetConsoleTextAttribute( win32console.FOREGROUND_RED | win32console.FOREGROUND_GREEN | win32console.FOREGROUND_BLUE) except pywintypes.error: return False else: return True supported = classmethod(supported) def write(self, text, color): color = self._colors[color] self.screenBuffer.SetConsoleTextAttribute(color) self.stream.write(text) self.screenBuffer.SetConsoleTextAttribute(self._colors['normal']) class _NullColorizer(object): """ See _AnsiColorizer docstring. """ def __init__(self, stream): self.stream = stream def supported(cls, stream=sys.stdout): return True supported = classmethod(supported) def write(self, text, color): self.stream.write(text) class RyuTestResult(result.TextTestResult): def __init__(self, *args, **kw): result.TextTestResult.__init__(self, *args, **kw) self._last_case = None self.colorizer = None # NOTE(vish, tfukushima): reset stdout for the terminal check stdout = sys.__stdout__ sys.stdout = sys.__stdout__ for colorizer in [_Win32Colorizer, _AnsiColorizer, _NullColorizer]: if colorizer.supported(): self.colorizer = colorizer(self.stream) break sys.stdout = stdout def getDescription(self, test): return str(test) # NOTE(vish, tfukushima): copied from unittest with edit to add color def addSuccess(self, test): unittest.TestResult.addSuccess(self, test) if self.showAll: self.colorizer.write("OK", 'green') self.stream.writeln() elif self.dots: self.stream.write('.') self.stream.flush() # NOTE(vish, tfukushima): copied from unittest with edit to add color def addFailure(self, test, err): unittest.TestResult.addFailure(self, test, err) if self.showAll: self.colorizer.write("FAIL", 'red') self.stream.writeln() elif self.dots: self.stream.write('F') self.stream.flush() # NOTE(vish, tfukushima): copied from unittest with edit to add color def addError(self, test, err): """Overrides normal addError to add support for errorClasses. If the exception is a registered class, the error will be added to the list for that class, not errors. """ stream = getattr(self, 'stream', None) ec, ev, tb = err try: exc_info = self._exc_info_to_string(err, test) except TypeError: # This is for compatibility with Python 2.3. exc_info = self._exc_info_to_string(err) for cls, (storage, label, isfail) in self.errorClasses.items(): if result.isclass(ec) and issubclass(ec, cls): if isfail: test.passwd = False storage.append((test, exc_info)) # Might get patched into a streamless result if stream is not None: if self.showAll: message = [label] detail = result._exception_detail(err[1]) if detail: message.append(detail) stream.writeln(": ".join(message)) elif self.dots: stream.write(label[:1]) return self.errors.append((test, exc_info)) test.passed = False if stream is not None: if self.showAll: self.colorizer.write("ERROR", 'red') self.stream.writeln() elif self.dots: stream.write('E') def startTest(self, test): unittest.TestResult.startTest(self, test) current_case = test.test.__class__.__name__ if self.showAll: if current_case != self._last_case: self.stream.writeln(current_case) self._last_case = current_case #NOTE(salvatore-orlando): #slightly changed in order to print test case class #together with unit test name self.stream.write( ' %s' % str(test.test).ljust(60)) self.stream.flush() class RyuTestRunner(core.TextTestRunner): def _makeResult(self): return RyuTestResult(self.stream, self.descriptions, self.verbosity, self.config) def run_tests(c=None): logger = logging.getLogger() hdlr = logging.StreamHandler() formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') hdlr.setFormatter(formatter) logger.addHandler(hdlr) logger.setLevel(logging.DEBUG) # NOTE(bgh): I'm not entirely sure why but nose gets confused here when # calling run_tests from a plugin directory run_tests.py (instead of the # main run_tests.py). It will call run_tests with no arguments and the # testing of run_tests will fail (though the plugin tests will pass). For # now we just return True to let the run_tests test pass. if not c: return True runner = RyuTestRunner(stream=c.stream, verbosity=c.verbosity, config=c) return not core.run(config=c, testRunner=runner)