diff options
| author | Jenkins <jenkins@review.openstack.org> | 2011-12-07 18:13:48 +0000 |
|---|---|---|
| committer | Gerrit Code Review <review@openstack.org> | 2011-12-07 18:13:48 +0000 |
| commit | 0b28e574afa8563524e21d31d95972c8ba126de2 (patch) | |
| tree | f093132f890b76bb500898f6a3d3102e849fe26d /nova | |
| parent | 0c78c2ed61c84ff967c2e6894f5dcff0987c2dec (diff) | |
| parent | f845891184b17e2c31a2b02dbc9217978abd3242 (diff) | |
Merge "First steps towards consolidating testing infrastructure"
Diffstat (limited to 'nova')
| -rw-r--r-- | nova/api/ec2/__init__.py | 2 | ||||
| -rw-r--r-- | nova/auth/ldapdriver.py | 2 | ||||
| -rw-r--r-- | nova/auth/manager.py | 2 | ||||
| -rw-r--r-- | nova/rpc/impl_carrot.py | 4 | ||||
| -rw-r--r-- | nova/test.py | 4 | ||||
| -rw-r--r-- | nova/testing/README.rst | 44 | ||||
| -rw-r--r-- | nova/testing/__init__.py | 0 | ||||
| -rw-r--r-- | nova/testing/fake/__init__.py | 2 | ||||
| -rw-r--r-- | nova/testing/fake/memcache.py (renamed from nova/fakememcache.py) | 0 | ||||
| -rw-r--r-- | nova/testing/fake/rabbit.py (renamed from nova/fakerabbit.py) | 2 | ||||
| -rw-r--r-- | nova/testing/runner.py | 370 | ||||
| -rw-r--r-- | nova/tests/test_iptables_network.py | 10 |
12 files changed, 429 insertions, 13 deletions
diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 23042dab0..0433d5152 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -120,7 +120,7 @@ class Lockout(wsgi.Middleware): if FLAGS.memcached_servers: import memcache else: - from nova import fakememcache as memcache + from nova.testing.fake import memcache self.mc = memcache.Client(FLAGS.memcached_servers, debug=0) super(Lockout, self).__init__(application) diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index bc37d2d87..6b9969dad 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -72,7 +72,7 @@ LOG = logging.getLogger("nova.ldapdriver") if FLAGS.memcached_servers: import memcache else: - from nova import fakememcache as memcache + from nova.testing.fake import memcache # TODO(vish): make an abstract base class with the same public methods diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 93b4244ad..3d984f1bf 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -82,7 +82,7 @@ LOG = logging.getLogger('nova.auth.manager') if FLAGS.memcached_servers: import memcache else: - from nova import fakememcache as memcache + from nova.testing.fake import memcache class AuthBase(object): diff --git a/nova/rpc/impl_carrot.py b/nova/rpc/impl_carrot.py index 57fd074f0..eed8cb10d 100644 --- a/nova/rpc/impl_carrot.py +++ b/nova/rpc/impl_carrot.py @@ -41,9 +41,9 @@ import greenlet from nova import context from nova import exception -from nova import fakerabbit from nova import flags from nova.rpc.common import RemoteError, LOG +from nova.testing import fake # Needed for tests eventlet.monkey_patch() @@ -71,7 +71,7 @@ class Connection(carrot_connection.BrokerConnection): virtual_host=FLAGS.rabbit_virtual_host) if FLAGS.fake_rabbit: - params['backend_cls'] = fakerabbit.Backend + params['backend_cls'] = fake.rabbit.Backend # NOTE(vish): magic is fun! # pylint: disable=W0142 diff --git a/nova/test.py b/nova/test.py index 6c565f53d..448af047e 100644 --- a/nova/test.py +++ b/nova/test.py @@ -35,12 +35,12 @@ import nova.image.fake import shutil import stubout -from nova import fakerabbit from nova import flags from nova import log from nova import rpc from nova import utils from nova import service +from nova.testing.fake import rabbit from nova.virt import fake @@ -142,7 +142,7 @@ class TestCase(unittest.TestCase): finally: # Clean out fake_rabbit's queue if we used it if FLAGS.fake_rabbit: - fakerabbit.reset_all() + rabbit.reset_all() if FLAGS.connection_type == 'fake': if hasattr(fake.FakeConnection, '_instance'): diff --git a/nova/testing/README.rst b/nova/testing/README.rst new file mode 100644 index 000000000..036f1c77d --- /dev/null +++ b/nova/testing/README.rst @@ -0,0 +1,44 @@ +===================================== +OpenStack Nova Testing Infrastructure +===================================== + +A note of clarification is in order, to help those who are new to testing in +OpenStack nova: + +- actual unit tests are created in the "tests" directory; +- the "testing" directory is used to house the infrastructure needed to support + testing in OpenStack Nova. + +This README file attempts to provide current and prospective contributors with +everything they need to know in order to start creating unit tests and +utilizing the convenience code provided in nova.testing. + +Note: the content for the rest of this file will be added as the work items in +the following blueprint are completed: + https://blueprints.launchpad.net/nova/+spec/consolidate-testing-infrastructure + + +Test Types: Unit vs. Functional vs. Integration +----------------------------------------------- + +TBD + +Writing Unit Tests +------------------ + +TBD + +Using Fakes +~~~~~~~~~~~ + +TBD + +Writing Functional Tests +------------------------ + +TBD + +Writing Integration Tests +------------------------- + +TBD diff --git a/nova/testing/__init__.py b/nova/testing/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/nova/testing/__init__.py diff --git a/nova/testing/fake/__init__.py b/nova/testing/fake/__init__.py new file mode 100644 index 000000000..d9eebbd23 --- /dev/null +++ b/nova/testing/fake/__init__.py @@ -0,0 +1,2 @@ +import memcache +import rabbit diff --git a/nova/fakememcache.py b/nova/testing/fake/memcache.py index e4f238aa9..e4f238aa9 100644 --- a/nova/fakememcache.py +++ b/nova/testing/fake/memcache.py diff --git a/nova/fakerabbit.py b/nova/testing/fake/rabbit.py index 2807a76f6..0cb91bd25 100644 --- a/nova/fakerabbit.py +++ b/nova/testing/fake/rabbit.py @@ -26,7 +26,7 @@ from eventlet import greenthread from nova import log as logging -LOG = logging.getLogger("nova.fakerabbit") +LOG = logging.getLogger("nova.testing.fake.rabbit") EXCHANGES = {} diff --git a/nova/testing/runner.py b/nova/testing/runner.py new file mode 100644 index 000000000..9fbbb418d --- /dev/null +++ b/nova/testing/runner.py @@ -0,0 +1,370 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# 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. +"""Unittest runner for Nova. + +To run all tests + python nova/testing/runner.py + +To run a single test module: + python nova/testing/runner.py test_compute + + or + + python nova/testing/runner.py api.test_wsgi + +To run a single test: + python nova/testing/runner.py \ + test_compute:ComputeTestCase.test_run_terminate + +""" + +import gettext +import heapq +import os +import unittest +import sys +import time + +gettext.install('nova', unicode=1) + +import eventlet +from nose import config +from nose import core +from nose import result + +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() + 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, \ + 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_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) + + +def get_elapsed_time_color(elapsed_time): + if elapsed_time > 1.0: + return 'red' + elif elapsed_time > 0.25: + return 'yellow' + else: + return 'green' + + +class NovaTestResult(result.TextTestResult): + def __init__(self, *args, **kw): + self.show_elapsed = kw.pop('show_elapsed') + result.TextTestResult.__init__(self, *args, **kw) + self.num_slow_tests = 5 + self.slow_tests = [] # this is a fixed-sized heap + 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]: + if colorizer.supported(): + self.colorizer = colorizer(self.stream) + break + sys.stdout = stdout + + # NOTE(lorinh): Initialize start_time in case a sqlalchemy-migrate + # error results in it failing to be initialized later. Otherwise, + # _handleElapsedTime will fail, causing the wrong error message to + # be outputted. + self.start_time = time.time() + + def getDescription(self, test): + return str(test) + + def _handleElapsedTime(self, test): + self.elapsed_time = time.time() - self.start_time + item = (self.elapsed_time, test) + # Record only the n-slowest tests using heap + if len(self.slow_tests) >= self.num_slow_tests: + heapq.heappushpop(self.slow_tests, item) + else: + heapq.heappush(self.slow_tests, item) + + def _writeElapsedTime(self, test): + color = get_elapsed_time_color(self.elapsed_time) + self.colorizer.write(" %.2f" % self.elapsed_time, color) + + def _writeResult(self, test, long_result, color, short_result, success): + if self.showAll: + self.colorizer.write(long_result, color) + if self.show_elapsed and success: + self._writeElapsedTime(test) + self.stream.writeln() + elif self.dots: + self.stream.write(short_result) + self.stream.flush() + + # NOTE(vish): copied from unittest with edit to add color + def addSuccess(self, test): + unittest.TestResult.addSuccess(self, test) + self._handleElapsedTime(test) + self._writeResult(test, 'OK', 'green', '.', True) + + # NOTE(vish): copied from unittest with edit to add color + def addFailure(self, test, err): + unittest.TestResult.addFailure(self, test, err) + self._handleElapsedTime(test) + self._writeResult(test, 'FAIL', 'red', 'F', False) + + # 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 + error will be added to the list for that class, not errors. + """ + self._handleElapsedTime(test) + 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: + self._writeResult(test, 'ERROR', 'red', 'E', False) + + def startTest(self, test): + unittest.TestResult.startTest(self, test) + self.start_time = time.time() + 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 + + self.stream.write( + ' %s' % str(test.test._testMethodName).ljust(60)) + self.stream.flush() + + +class NovaTestRunner(core.TextTestRunner): + def __init__(self, *args, **kwargs): + self.show_elapsed = kwargs.pop('show_elapsed') + core.TextTestRunner.__init__(self, *args, **kwargs) + + def _makeResult(self): + return NovaTestResult(self.stream, + self.descriptions, + self.verbosity, + self.config, + show_elapsed=self.show_elapsed) + + def _writeSlowTests(self, result_): + # Pare out 'fast' tests + slow_tests = [item for item in result_.slow_tests + if get_elapsed_time_color(item[0]) != 'green'] + if slow_tests: + slow_total_time = sum(item[0] for item in slow_tests) + self.stream.writeln("Slowest %i tests took %.2f secs:" + % (len(slow_tests), slow_total_time)) + for elapsed_time, test in sorted(slow_tests, reverse=True): + time_str = "%.2f" % elapsed_time + self.stream.writeln(" %s %s" % (time_str.ljust(10), test)) + + def run(self, test): + result_ = core.TextTestRunner.run(self, test) + if self.show_elapsed: + self._writeSlowTests(result_) + return result_ + + +def run(): + logging.setup() + # If any argument looks like a test name but doesn't have "nova.tests" in + # front of it, automatically add that so we don't have to type as much + show_elapsed = True + argv = [] + for x in sys.argv: + if x.startswith('test_'): + argv.append('nova.tests.%s' % x) + elif x.startswith('--hide-elapsed'): + show_elapsed = False + else: + argv.append(x) + + testdir = os.path.abspath(os.path.join("nova", "tests")) + c = config.Config(stream=sys.stdout, + env=os.environ, + verbosity=3, + workingDir=testdir, + plugins=core.DefaultPluginManager()) + + runner = NovaTestRunner(stream=c.stream, + verbosity=c.verbosity, + config=c, + show_elapsed=show_elapsed) + sys.exit(not core.run(config=c, testRunner=runner, argv=argv)) + + +if __name__ == '__main__': + eventlet.monkey_patch() + run() diff --git a/nova/tests/test_iptables_network.py b/nova/tests/test_iptables_network.py index 918034269..478dfa70d 100644 --- a/nova/tests/test_iptables_network.py +++ b/nova/tests/test_iptables_network.py @@ -84,12 +84,12 @@ class IptablesManagerTestCase(test.TestCase): table = self.manager.ipv4['filter'] table.add_rule('FORWARD', '-s 1.2.3.4/5 -j DROP') new_lines = self.manager._modify_rules(current_lines, table) - self.assertTrue('-A run_tests.py-FORWARD ' + self.assertTrue('-A runner.py-FORWARD ' '-s 1.2.3.4/5 -j DROP' in new_lines) table.remove_rule('FORWARD', '-s 1.2.3.4/5 -j DROP') new_lines = self.manager._modify_rules(current_lines, table) - self.assertTrue('-A run_tests.py-FORWARD ' + self.assertTrue('-A runner.py-FORWARD ' '-s 1.2.3.4/5 -j DROP' not in new_lines) def test_nat_rules(self): @@ -123,7 +123,7 @@ class IptablesManagerTestCase(test.TestCase): "nova-postouting-bottom: %s" % last_postrouting_line) for chain in ['POSTROUTING', 'PREROUTING', 'OUTPUT']: - self.assertTrue('-A %s -j run_tests.py-%s' \ + self.assertTrue('-A %s -j runner.py-%s' \ % (chain, chain) in new_lines, "Built-in chain %s not wrapped" % (chain,)) @@ -155,10 +155,10 @@ class IptablesManagerTestCase(test.TestCase): break self.assertTrue('-A nova-filter-top ' - '-j run_tests.py-local' in new_lines, + '-j runner.py-local' in new_lines, "nova-filter-top does not jump to wrapped local chain") for chain in ['INPUT', 'OUTPUT', 'FORWARD']: - self.assertTrue('-A %s -j run_tests.py-%s' \ + self.assertTrue('-A %s -j runner.py-%s' \ % (chain, chain) in new_lines, "Built-in chain %s not wrapped" % (chain,)) |
