Move patch_exists() to nova.test.TestCase for reuse
Several existing tests patch os.path.exists() to return a given value, whilst avoiding interfering with other checks for path existence inside the test virtualenv. Currently they do it by duplicating logic. Furthermore, in the near future, more tests (specifically, some SEV functional tests) will want to do the same selective patching, and similarly will need to avoid impacting existence checks on other files within the test virtualenv, e.g. placement-policy.yaml. TestGuestConfigSysinfoSerialOS already had a nice patch_exists() context manager for selectively patching os.path.exists based on the path parameter, so move this to nova.test.TestCase so that it can be reused to reduce duplication, and add appropriate unit test cases. In order to make patch_exists() generic, the path has to be introduced as an extra parameter alongside the desired return value. Additionally make it usable as a decorator, not just a context manager. This is achievable natively in Python 3 with contextlib, and in Python 2 via contextlib2. Change-Id: Ibe7cb29620f06d31059f2a5f94ca180b8671046e
This commit is contained in:
parent
2e85453879
commit
f0edbff6f2
|
@ -12,7 +12,7 @@ cffi==1.11.5
|
|||
cliff==2.11.0
|
||||
cmd2==0.8.1
|
||||
colorama==0.3.9
|
||||
contextlib2==0.5.5
|
||||
contextlib2==0.5.5;python_version=='2.7'
|
||||
coverage==4.0
|
||||
cryptography==2.1.4
|
||||
cursive==0.2.1
|
||||
|
|
63
nova/test.py
63
nova/test.py
|
@ -29,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
|
||||
|
@ -64,6 +66,10 @@ 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
|
||||
|
||||
|
@ -376,6 +382,18 @@ 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)
|
||||
|
||||
def flags(self, **kw):
|
||||
"""Override flag variables for a test."""
|
||||
group = kw.pop('group', None)
|
||||
|
@ -760,3 +778,48 @@ 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
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
|
||||
"""Tests for the testing base code."""
|
||||
|
||||
import os.path
|
||||
|
||||
import mock
|
||||
from oslo_log import log as logging
|
||||
import oslo_messaging as messaging
|
||||
import six
|
||||
|
@ -312,3 +315,62 @@ class NovaExceptionReraiseFormatErrorTestCase(test.NoDBTestCase):
|
|||
# not enough kwargs
|
||||
ex = self.assertRaises(KeyError, FakeImageException, image_id='image')
|
||||
self.assertIn('type', six.text_type(ex))
|
||||
|
||||
|
||||
class PatchExistsTestCase(test.NoDBTestCase):
|
||||
def test_with_patch_exists_true(self):
|
||||
"""Test that "with patch_exists" can fake the existence of a file
|
||||
without changing other file existence checks, and that calls can
|
||||
be asserted on the mocked method.
|
||||
"""
|
||||
self.assertFalse(os.path.exists('fake_file'))
|
||||
with self.patch_exists('fake_file', True) as mock_exists:
|
||||
self.assertTrue(os.path.exists('fake_file'))
|
||||
self.assertTrue(os.path.exists(__file__))
|
||||
self.assertFalse(os.path.exists('non-existent/file'))
|
||||
self.assertIn(mock.call('fake_file'), mock_exists.mock_calls)
|
||||
|
||||
def test_with_patch_exists_false(self):
|
||||
"""Test that "with patch_exists" can fake the non-existence of a file
|
||||
without changing other file existence checks, and that calls can
|
||||
be asserted on the mocked method.
|
||||
"""
|
||||
self.assertTrue(os.path.exists(__file__))
|
||||
with self.patch_exists(__file__, False) as mock_exists:
|
||||
self.assertFalse(os.path.exists(__file__))
|
||||
self.assertTrue(os.path.exists(os.path.dirname(__file__)))
|
||||
self.assertFalse(os.path.exists('non-existent/file'))
|
||||
self.assertIn(mock.call(__file__), mock_exists.mock_calls)
|
||||
|
||||
@test.patch_exists('fake_file', True)
|
||||
def test_patch_exists_decorator_true(self):
|
||||
"""Test that @patch_exists can fake the existence of a file
|
||||
without changing other file existence checks.
|
||||
"""
|
||||
self.assertTrue(os.path.exists('fake_file'))
|
||||
self.assertTrue(os.path.exists(__file__))
|
||||
self.assertFalse(os.path.exists('non-existent/file'))
|
||||
|
||||
@test.patch_exists(__file__, False)
|
||||
def test_patch_exists_decorator_false(self):
|
||||
"""Test that @patch_exists can fake the non-existence of a file
|
||||
without changing other file existence checks.
|
||||
"""
|
||||
self.assertFalse(os.path.exists(__file__))
|
||||
self.assertTrue(os.path.exists(os.path.dirname(__file__)))
|
||||
self.assertFalse(os.path.exists('non-existent/file'))
|
||||
|
||||
@test.patch_exists('fake_file1', True)
|
||||
@test.patch_exists('fake_file2', True)
|
||||
@test.patch_exists(__file__, False)
|
||||
def test_patch_exists_multiple_decorators(self):
|
||||
"""Test that @patch_exists can be used multiple times on the
|
||||
same method.
|
||||
"""
|
||||
self.assertTrue(os.path.exists('fake_file1'))
|
||||
self.assertTrue(os.path.exists('fake_file2'))
|
||||
self.assertFalse(os.path.exists(__file__))
|
||||
|
||||
# Check non-patched parameters
|
||||
self.assertTrue(os.path.exists(os.path.dirname(__file__)))
|
||||
self.assertFalse(os.path.exists('non-existent/file'))
|
||||
|
|
|
@ -18195,19 +18195,6 @@ class TestGuestConfigSysinfoSerialOS(test.NoDBTestCase):
|
|||
# Don't initialise the Host
|
||||
self.useFixture(fixtures.MockPatch('nova.virt.libvirt.driver.host'))
|
||||
|
||||
@contextlib.contextmanager
|
||||
def patch_exists(self, result):
|
||||
real_exists = os.path.exists
|
||||
|
||||
def fake_exists(filename):
|
||||
if filename == "/etc/machine-id":
|
||||
return result
|
||||
return real_exists(filename)
|
||||
|
||||
with mock.patch.object(os.path, "exists") as mock_exists:
|
||||
mock_exists.side_effect = fake_exists
|
||||
yield mock_exists
|
||||
|
||||
def _test_get_guest_config_sysinfo_serial(self, expected_serial):
|
||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
||||
|
||||
|
@ -18252,7 +18239,7 @@ class TestGuestConfigSysinfoSerialOS(test.NoDBTestCase):
|
|||
with test.nested(
|
||||
mock.patch.object(six.moves.builtins, "open",
|
||||
mock.mock_open(read_data=theuuid)),
|
||||
self.patch_exists(True)):
|
||||
self.patch_exists("/etc/machine-id", True)):
|
||||
self._test_get_guest_config_sysinfo_serial(theuuid)
|
||||
|
||||
def test_get_guest_config_sysinfo_serial_os_empty_machine_id(self):
|
||||
|
@ -18260,55 +18247,37 @@ class TestGuestConfigSysinfoSerialOS(test.NoDBTestCase):
|
|||
with test.nested(
|
||||
mock.patch.object(six.moves.builtins, "open",
|
||||
mock.mock_open(read_data="")),
|
||||
self.patch_exists(True)):
|
||||
self.patch_exists("/etc/machine-id", True)):
|
||||
self.assertRaises(exception.NovaException,
|
||||
self._test_get_guest_config_sysinfo_serial,
|
||||
None)
|
||||
|
||||
def test_get_guest_config_sysinfo_serial_os_no_machine_id_file(self):
|
||||
self.flags(sysinfo_serial="os", group="libvirt")
|
||||
with self.patch_exists(False):
|
||||
with self.patch_exists("/etc/machine-id", False):
|
||||
self.assertRaises(exception.NovaException,
|
||||
self._test_get_guest_config_sysinfo_serial,
|
||||
None)
|
||||
|
||||
theuuid = "56b40135-a973-4eb3-87bb-a2382a3e6dbc"
|
||||
|
||||
@test.patch_exists("/etc/machine-id", False)
|
||||
def test_get_guest_config_sysinfo_serial_auto_hardware(self):
|
||||
self.flags(sysinfo_serial="auto", group="libvirt")
|
||||
|
||||
real_exists = os.path.exists
|
||||
with test.nested(
|
||||
mock.patch.object(os.path, "exists"),
|
||||
mock.patch.object(libvirt_driver.LibvirtDriver,
|
||||
"_get_host_sysinfo_serial_hardware")
|
||||
) as (mock_exists, mock_uuid):
|
||||
def fake_exists(filename):
|
||||
if filename == "/etc/machine-id":
|
||||
return False
|
||||
return real_exists(filename)
|
||||
with mock.patch.object(libvirt_driver.LibvirtDriver,
|
||||
"_get_host_sysinfo_serial_hardware") \
|
||||
as mock_uuid:
|
||||
mock_uuid.return_value = self.theuuid
|
||||
|
||||
mock_exists.side_effect = fake_exists
|
||||
|
||||
theuuid = "56b40135-a973-4eb3-87bb-a2382a3e6dbc"
|
||||
mock_uuid.return_value = theuuid
|
||||
|
||||
self._test_get_guest_config_sysinfo_serial(theuuid)
|
||||
self._test_get_guest_config_sysinfo_serial(self.theuuid)
|
||||
|
||||
@test.patch_exists("/etc/machine-id", True)
|
||||
def test_get_guest_config_sysinfo_serial_auto_os(self):
|
||||
self.flags(sysinfo_serial="auto", group="libvirt")
|
||||
|
||||
real_exists = os.path.exists
|
||||
real_open = builtins.open
|
||||
with test.nested(
|
||||
mock.patch.object(os.path, "exists"),
|
||||
mock.patch.object(builtins, "open"),
|
||||
) as (mock_exists, mock_open):
|
||||
def fake_exists(filename):
|
||||
if filename == "/etc/machine-id":
|
||||
return True
|
||||
return real_exists(filename)
|
||||
|
||||
mock_exists.side_effect = fake_exists
|
||||
|
||||
with mock.patch.object(builtins, "open") as mock_open:
|
||||
theuuid = "56b40135-a973-4eb3-87bb-a2382a3e6dbc"
|
||||
|
||||
def fake_open(filename, *args, **kwargs):
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
# process, which may cause wedges in the gate later.
|
||||
|
||||
hacking>=1.1.0,<1.2.0 # Apache-2.0
|
||||
contextlib2>=0.5.5;python_version<'3.0' # PSF License
|
||||
coverage!=4.4,>=4.0 # Apache-2.0
|
||||
ddt>=1.0.1 # MIT
|
||||
fixtures>=3.0.0 # Apache-2.0/BSD
|
||||
|
|
Loading…
Reference in New Issue