From 90007cd9085909a1e1ac59732a0b371dd79c2557 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Mon, 21 Feb 2011 15:55:50 -0800 Subject: pretty colors for logs and a few optimizations --- bin/nova-dhcpbridge | 2 + run_tests.py | 202 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 202 insertions(+), 2 deletions(-) diff --git a/bin/nova-dhcpbridge b/bin/nova-dhcpbridge index eda2dc072..04a1771f0 100755 --- a/bin/nova-dhcpbridge +++ b/bin/nova-dhcpbridge @@ -113,6 +113,8 @@ def main(): FLAGS.num_networks = 5 path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', + 'nova', + 'tests', 'tests.sqlite')) FLAGS.sql_connection = 'sqlite:///%s' % path action = argv[1] diff --git a/run_tests.py b/run_tests.py index 82345433a..43508394f 100644 --- a/run_tests.py +++ b/run_tests.py @@ -17,6 +17,29 @@ # 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 gettext import os import unittest @@ -28,14 +51,186 @@ from nose import core from nova import log as logging +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(fd=stream.fileno()) + 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_ERROR_HANDLE, \ + FOREGROUND_RED, FOREGROUND_BLUE, FOREGROUND_GREEN, \ + FOREGROUND_INTENSITY + red, green, blue, bold = (FOREGROUND_RED, FOREGROUND_GREEN, + FOREGROUND_BLUE, FOREGROUND_INTENSITY) + self.stream = stream + self.screenBuffer = GetStdHandle(STD_ERROR_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_ERROR_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 NovaTestResult(result.TextTestResult): def __init__(self, *args, **kw): result.TextTestResult.__init__(self, *args, **kw) self._last_case = None + self.colorizer = None + for colorizer in [_Win32Colorizer, _AnsiColorizer, _NullColorizer]: + # NOTE(vish): nose does funky stuff with stdout, so use stderr + # to setup the colorizer + if colorizer.supported(sys.stderr): + self.colorizer = colorizer(self.stream) + break def getDescription(self, test): return str(test) + def addSuccess(self, test): + if self.showAll: + self.colorizer.write("OK", 'green') + self.stream.writeln() + elif self.dots: + self.stream.write('.') + self.stream.flush() + + def addFailure(self, test): + if self.showAll: + self.colorizer.write("FAIL", 'red') + self.stream.writeln() + elif self.dots: + self.stream.write('F') + self.stream.flush() + + 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: + # 2.3 compat + 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.passed = 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__ @@ -59,12 +254,15 @@ class NovaTestRunner(core.TextTestRunner): if __name__ == '__main__': - if os.path.exists("tests.sqlite"): - os.unlink("tests.sqlite") logging.setup() + testdir = os.path.abspath(os.path.join("nova","tests")) + testdb = os.path.join(testdir, "tests.sqlite") + if os.path.exists(testdb): + os.unlink(testdb) c = config.Config(stream=sys.stdout, env=os.environ, verbosity=3, + workingDir=testdir, plugins=core.DefaultPluginManager()) runner = NovaTestRunner(stream=c.stream, -- cgit From 0f402b72cbf80d1adde503eb532a578944fa0c79 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Mon, 21 Feb 2011 16:22:09 -0800 Subject: update based on prereq branch --- nova/tests/fake_flags.py | 2 +- run_tests.sh | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/nova/tests/fake_flags.py b/nova/tests/fake_flags.py index cfa65c137..575fefff6 100644 --- a/nova/tests/fake_flags.py +++ b/nova/tests/fake_flags.py @@ -39,6 +39,6 @@ FLAGS.num_shelves = 2 FLAGS.blades_per_shelf = 4 FLAGS.iscsi_num_targets = 8 FLAGS.verbose = True -FLAGS.sql_connection = 'sqlite:///nova.sqlite' +FLAGS.sql_connection = 'sqlite:///tests.sqlite' FLAGS.use_ipv6 = True FLAGS.logfile = 'tests.log' diff --git a/run_tests.sh b/run_tests.sh index 70212cc6a..e8433bc06 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -39,7 +39,6 @@ done function run_tests { # Just run the test suites in current environment - ${wrapper} rm -f nova.sqlite ${wrapper} $NOSETESTS } -- cgit From 60ed7c9c52306d08b1ad3e759e173931b0a495a8 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 23 Feb 2011 00:59:15 -0800 Subject: fix failures --- run_tests.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/run_tests.py b/run_tests.py index 4084d8d80..01a1cf8bc 100644 --- a/run_tests.py +++ b/run_tests.py @@ -181,6 +181,7 @@ class NovaTestResult(result.TextTestResult): return str(test) def addSuccess(self, test): + unittest.TestResult.addSuccess(self, test) if self.showAll: self.colorizer.write("OK", 'green') self.stream.writeln() @@ -188,7 +189,8 @@ class NovaTestResult(result.TextTestResult): self.stream.write('.') self.stream.flush() - def addFailure(self, test): + def addFailure(self, test, err): + unittest.TestResult.addFailure(self, test, err) if self.showAll: self.colorizer.write("FAIL", 'red') self.stream.writeln() -- cgit From 5e2f82b1487b8f8e43539d0c71466fbbfed23121 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 23 Feb 2011 15:29:02 -0800 Subject: revert logfile redirection and make colors work by temporarily switching stdout --- nova/tests/fake_flags.py | 1 - run_tests.py | 16 +++++++++------- run_tests.sh | 4 ++-- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/nova/tests/fake_flags.py b/nova/tests/fake_flags.py index 575fefff6..2b1919407 100644 --- a/nova/tests/fake_flags.py +++ b/nova/tests/fake_flags.py @@ -41,4 +41,3 @@ FLAGS.iscsi_num_targets = 8 FLAGS.verbose = True FLAGS.sql_connection = 'sqlite:///tests.sqlite' FLAGS.use_ipv6 = True -FLAGS.logfile = 'tests.log' diff --git a/run_tests.py b/run_tests.py index 877849ab5..2c04a0641 100644 --- a/run_tests.py +++ b/run_tests.py @@ -97,7 +97,7 @@ class _AnsiColorizer(object): try: return curses.tigetnum("colors") > 2 except curses.error: - curses.setupterm(fd=stream.fileno()) + curses.setupterm() return curses.tigetnum("colors") > 2 except: raise @@ -122,13 +122,13 @@ class _Win32Colorizer(object): See _AnsiColorizer docstring. """ def __init__(self, stream): - from win32console import GetStdHandle, STD_ERROR_HANDLE, \ + from win32console import GetStdHandle, STD_OUT_HANDLE, \ FOREGROUND_RED, FOREGROUND_BLUE, FOREGROUND_GREEN, \ FOREGROUND_INTENSITY red, green, blue, bold = (FOREGROUND_RED, FOREGROUND_GREEN, FOREGROUND_BLUE, FOREGROUND_INTENSITY) self.stream = stream - self.screenBuffer = GetStdHandle(STD_ERROR_HANDLE) + self.screenBuffer = GetStdHandle(STD_OUT_HANDLE) self._colors = { 'normal': red | green | blue, 'red': red | bold, @@ -144,7 +144,7 @@ class _Win32Colorizer(object): try: import win32console screenBuffer = win32console.GetStdHandle( - win32console.STD_ERROR_HANDLE) + win32console.STD_OUT_HANDLE) except ImportError: return False import pywintypes @@ -185,12 +185,14 @@ class NovaTestResult(result.TextTestResult): result.TextTestResult.__init__(self, *args, **kw) self._last_case = None self.colorizer = None + # NOTE(vish): reset stdout for the terminal check + stdout = sys.stdout + sys.stdout = sys.__stdout__ for colorizer in [_Win32Colorizer, _AnsiColorizer, _NullColorizer]: - # NOTE(vish): nose does funky stuff with stdout, so use stderr - # to setup the colorizer - if colorizer.supported(sys.stderr): + if colorizer.supported(): self.colorizer = colorizer(self.stream) break + sys.stdout = stdout def getDescription(self, test): return str(test) diff --git a/run_tests.sh b/run_tests.sh index 7aab9ecd9..d4586a57e 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -39,12 +39,12 @@ done function run_tests { # Just run the test suites in current environment - ${wrapper} $NOSETESTS + ${wrapper} $NOSETESTS 2> run_tests.log # If we get some short import error right away, print the error log directly RESULT=$? if [ "$RESULT" -ne "0" ]; then - ERRSIZE=`wc -l run_tests.err.log | awk '{print \$1}'` + ERRSIZE=`wc -l run_tests.log | awk '{print \$1}'` if [ "$ERRSIZE" -lt "40" ]; then cat run_tests.err.log -- cgit From c27c19ea316b343f1623a7c1bf21c53cd426603b Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 23 Feb 2011 15:35:30 -0800 Subject: fix pep8 --- run_tests.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/run_tests.py b/run_tests.py index 2c04a0641..eef0120bc 100644 --- a/run_tests.py +++ b/run_tests.py @@ -87,7 +87,7 @@ class _AnsiColorizer(object): coloring terminal output using this method. Returns False otherwise. """ if not stream.isatty(): - return False # auto color only on TTYs + return False # auto color only on TTYs try: import curses except ImportError: @@ -180,6 +180,7 @@ class _NullColorizer(object): def write(self, text, color): self.stream.write(text) + class NovaTestResult(result.TextTestResult): def __init__(self, *args, **kw): result.TextTestResult.__init__(self, *args, **kw) @@ -276,7 +277,7 @@ class NovaTestRunner(core.TextTestRunner): if __name__ == '__main__': logging.setup() - testdir = os.path.abspath(os.path.join("nova","tests")) + testdir = os.path.abspath(os.path.join("nova", "tests")) testdb = os.path.join(testdir, "tests.sqlite") if os.path.exists(testdb): os.unlink(testdb) -- cgit From 78eddce564cccf0d9be19b303cbc122966f5fa71 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 23 Feb 2011 15:42:59 -0800 Subject: added comments about where code came from --- run_tests.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/run_tests.py b/run_tests.py index eef0120bc..8025548e5 100644 --- a/run_tests.py +++ b/run_tests.py @@ -198,6 +198,7 @@ class NovaTestResult(result.TextTestResult): def getDescription(self, test): return str(test) + # NOTE(vish): copied from unittest with edit to add color def addSuccess(self, test): unittest.TestResult.addSuccess(self, test) if self.showAll: @@ -207,6 +208,7 @@ class NovaTestResult(result.TextTestResult): self.stream.write('.') self.stream.flush() + # NOTE(vish): copied from unittest with edit to add color def addFailure(self, test, err): unittest.TestResult.addFailure(self, test, err) if self.showAll: @@ -216,6 +218,7 @@ class NovaTestResult(result.TextTestResult): self.stream.write('F') self.stream.flush() + # NOTE(vish): copied from nose 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 -- cgit