# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright (c) 2010 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Colorizer Code is borrowed from Twisted: # Copyright (c) 2001-2010 Twisted Matrix Laboratories. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import sys import unittest from nose import core from nose import result from quantum.common import constants class _AnsiColorizer(object): """ANSI colored texts. 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): """Checks for coloring terminal support. 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 Exception: 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 QuantumTestResult(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_details(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 QuantumTestRunner(core.TextTestRunner): def _makeResult(self): return QuantumTestResult(self.stream, self.descriptions, self.verbosity, self.config) def run_tests(c=None): # 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 = QuantumTestRunner(stream=c.stream, verbosity=c.verbosity, config=c) return not core.run(config=c, testRunner=runner) # describes parameters used by different unit/functional tests # a plugin-specific testing mechanism should import this dictionary # and override the values in it if needed (e.g., run_tests.py in # quantum/plugins/openvswitch/ ) test_config = { "plugin_name": "", "default_net_op_status": constants.NET_STATUS_ACTIVE, "default_port_op_status": constants.PORT_STATUS_ACTIVE, }