summaryrefslogtreecommitdiff
path: root/nova/test.py
diff options
context:
space:
mode:
Diffstat (limited to 'nova/test.py')
-rw-r--r--nova/test.py145
1 files changed, 138 insertions, 7 deletions
diff --git a/nova/test.py b/nova/test.py
index dae66cbfa4..d15cb64b28 100644
--- a/nova/test.py
+++ b/nova/test.py
@@ -21,8 +21,7 @@ inline callbacks.
"""
-import eventlet # noqa
-eventlet.monkey_patch()
+import nova.monkey_patch # noqa
import abc
import copy
@@ -30,10 +29,12 @@ import datetime
import inspect
import itertools
import os
+import os.path
import pprint
import sys
import fixtures
+import mock
from oslo_cache import core as cache
from oslo_concurrency import lockutils
from oslo_config import cfg
@@ -47,9 +48,11 @@ from oslo_versionedobjects import fixture as ovo_fixture
from oslotest import mock_fixture
from oslotest import moxstubout
import six
+from six.moves import builtins
import testtools
from nova.compute import resource_tracker
+from nova.compute import rpcapi as compute_rpcapi
from nova import context
from nova.db import api as db
from nova import exception
@@ -60,10 +63,15 @@ from nova.objects import base as objects_base
from nova import quota
from nova.tests import fixtures as nova_fixtures
from nova.tests.unit import conf_fixture
+from nova.tests.unit import matchers
from nova.tests.unit import policy_fixture
from nova import utils
from nova.virt import images
+if six.PY2:
+ import contextlib2 as contextlib
+else:
+ import contextlib
CONF = cfg.CONF
@@ -232,11 +240,11 @@ class TestCase(testtools.TestCase):
if self.STUB_RPC:
self.useFixture(nova_fixtures.RPCFixture('nova.test'))
- # we cannot set this in the ConfFixture as oslo only registers the
- # notification opts at the first instantiation of a Notifier that
- # happens only in the RPCFixture
- CONF.set_default('driver', ['test'],
- group='oslo_messaging_notifications')
+ # we cannot set this in the ConfFixture as oslo only registers the
+ # notification opts at the first instantiation of a Notifier that
+ # happens only in the RPCFixture
+ CONF.set_default('driver', ['test'],
+ group='oslo_messaging_notifications')
# NOTE(danms): Make sure to reset us back to non-remote objects
# for each test to avoid interactions. Also, backup the object
@@ -284,6 +292,9 @@ class TestCase(testtools.TestCase):
# Reset the global QEMU version flag.
images.QEMU_VERSION = None
+ # Reset the compute RPC API globals (mostly the _ROUTER).
+ compute_rpcapi.reset_globals()
+
mox_fixture = self.useFixture(moxstubout.MoxStubout())
self.mox = mox_fixture.mox
self.stubs = mox_fixture.stubs
@@ -376,6 +387,29 @@ class TestCase(testtools.TestCase):
"""
self.useFixture(fixtures.MonkeyPatch(old, new))
+ @staticmethod
+ def patch_exists(patched_path, result):
+ """Provide a static method version of patch_exists(), which if you
+ haven't already imported nova.test can be slightly easier to
+ use as a context manager within a test method via:
+
+ def test_something(self):
+ with self.patch_exists(path, True):
+ ...
+ """
+ return patch_exists(patched_path, result)
+
+ @staticmethod
+ def patch_open(patched_path, read_data):
+ """Provide a static method version of patch_open() which is easier to
+ use as a context manager within a test method via:
+
+ def test_something(self):
+ with self.patch_open(path, "fake contents of file"):
+ ...
+ """
+ return patch_open(patched_path, read_data)
+
def flags(self, **kw):
"""Override flag variables for a test."""
group = kw.pop('group', None)
@@ -515,6 +549,9 @@ class TestCase(testtools.TestCase):
error.difference = difference
raise error
+ def assertXmlEqual(self, expected, observed):
+ self.assertThat(observed, matchers.XMLMatches(expected))
+
def assertPublicAPISignatures(self, baseinst, inst):
def get_public_apis(inst):
methods = {}
@@ -757,3 +794,97 @@ class ContainKeyValue(object):
def __repr__(self):
return "<ContainKeyValue: key " + str(self.wantkey) + \
" and value " + str(self.wantvalue) + ">"
+
+
+@contextlib.contextmanager
+def patch_exists(patched_path, result):
+ """Selectively patch os.path.exists() so that if it's called with
+ patched_path, return result. Calls with any other path are passed
+ through to the real os.path.exists() function.
+
+ Either import and use as a decorator / context manager, or use the
+ nova.TestCase.patch_exists() static method as a context manager.
+
+ Currently it is *not* recommended to use this if any of the
+ following apply:
+
+ - You want to patch via decorator *and* make assertions about how the
+ mock is called (since using it in the decorator form will not make
+ the mock available to your code).
+
+ - You want the result of the patched exists() call to be determined
+ programmatically (e.g. by matching substrings of patched_path).
+
+ - You expect exists() to be called multiple times on the same path
+ and return different values each time.
+
+ Additionally within unit tests which only test a very limited code
+ path, it may be possible to ensure that the code path only invokes
+ exists() once, in which case it's slightly overkill to do
+ selective patching based on the path. In this case something like
+ like this may be more appropriate:
+
+ @mock.patch('os.path.exists', return_value=True)
+ def test_my_code(self, mock_exists):
+ ...
+ mock_exists.assert_called_once_with(path)
+ """
+ real_exists = os.path.exists
+
+ def fake_exists(path):
+ if path == patched_path:
+ return result
+ return real_exists(path)
+
+ with mock.patch.object(os.path, "exists") as mock_exists:
+ mock_exists.side_effect = fake_exists
+ yield mock_exists
+
+
+@contextlib.contextmanager
+def patch_open(patched_path, read_data):
+ """Selectively patch open() so that if it's called with patched_path,
+ return a mock which makes it look like the file contains
+ read_data. Calls with any other path are passed through to the
+ real open() function.
+
+ Either import and use as a decorator, or use the
+ nova.TestCase.patch_open() static method as a context manager.
+
+ Currently it is *not* recommended to use this if any of the
+ following apply:
+
+ - The code under test will attempt to write to patched_path.
+
+ - You want to patch via decorator *and* make assertions about how the
+ mock is called (since using it in the decorator form will not make
+ the mock available to your code).
+
+ - You want the faked file contents to be determined
+ programmatically (e.g. by matching substrings of patched_path).
+
+ - You expect open() to be called multiple times on the same path
+ and return different file contents each time.
+
+ Additionally within unit tests which only test a very limited code
+ path, it may be possible to ensure that the code path only invokes
+ open() once, in which case it's slightly overkill to do
+ selective patching based on the path. In this case something like
+ like this may be more appropriate:
+
+ @mock.patch(six.moves.builtins, 'open')
+ def test_my_code(self, mock_open):
+ ...
+ mock_open.assert_called_once_with(path)
+ """
+ real_open = builtins.open
+ m = mock.mock_open(read_data=read_data)
+
+ def selective_fake_open(path, *args, **kwargs):
+ if path == patched_path:
+ return m(patched_path)
+ return real_open(path, *args, **kwargs)
+
+ with mock.patch.object(builtins, 'open') as mock_open:
+ mock_open.side_effect = selective_fake_open
+ yield m