From 2351b3ad56530744e587b87bd2ce7f98a2bfc413 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Tue, 15 Jan 2019 15:20:35 +0000 Subject: [PATCH] Add unit_tests and drive by assorted * Add unit tests * Fix path to repo in layers.yaml * Ignore unit_tests etc in layers.yaml * Make set_state calls consistently from conversation * Setup travis (interim step until this interface is in OpenStack gerrit) --- .gitignore | 5 ++ .testr.conf | 8 +++ .travis.yml | 6 ++ interface.yaml | 11 ++- provides.py | 8 +-- test-requirements.txt | 6 ++ tox.ini | 28 ++++++++ unit_tests/__init__.py | 0 unit_tests/test_provides.py | 130 ++++++++++++++++++++++++++++++++++++ 9 files changed, 197 insertions(+), 5 deletions(-) create mode 100644 .gitignore create mode 100644 .testr.conf create mode 100644 .travis.yml create mode 100644 test-requirements.txt create mode 100644 tox.ini create mode 100644 unit_tests/__init__.py create mode 100644 unit_tests/test_provides.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9faca0b --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.tox +.testrepository +.unit-state.db +.stestr/ +__pycache__/ diff --git a/.testr.conf b/.testr.conf new file mode 100644 index 0000000..801646b --- /dev/null +++ b/.testr.conf @@ -0,0 +1,8 @@ +[DEFAULT] +test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ + OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ + OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ + ${PYTHON:-python} -m subunit.run discover -t ./ ./unit_tests $LISTOPT $IDOPTION + +test_id_option=--load-list $IDFILE +test_list_option=--list diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..f7118c9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +sudo: false +language: python +python: + - "3.4" +install: pip install tox-travis +script: tox -e pep8,py3 diff --git a/interface.yaml b/interface.yaml index fd2a39c..37e20a9 100644 --- a/interface.yaml +++ b/interface.yaml @@ -1,4 +1,13 @@ name: cinder-backend summary: OpenStack Cinder backend interface version: 1 -repo: https://github.com/mskalka/interface-cinder-backend +repo: https://github.com/openstack-charmers/interface-cinder-backend +ignore: + - 'unit_tests' + - 'Makefile' + - '.testr.conf' + - 'test-requirements.txt' + - 'tox.ini' + - '.gitignore' + - '.gitreview' + - '.unit-state.db' diff --git a/provides.py b/provides.py index f6199b2..062f23d 100644 --- a/provides.py +++ b/provides.py @@ -15,15 +15,15 @@ class CinderBackendProvides(RelationBase): def cinder_backend_joined(self): conv = self.conversation() conv.set_state('{relation_name}.joined') - self.set_state('{relation_name}.connected') - self.set_state('{relation_name}.available') + conv.set_state('{relation_name}.connected') + conv.set_state('{relation_name}.available') @hook('{provides:cinder-backend}-relation-{broken, departed}') def cinder_backend_departed(self): conv = self.conversation() conv.remove_state('{relation_name}.joined') - self.remove_state('{relation_name}.available') - self.remove_state('{relation_name}.connected') + conv.remove_state('{relation_name}.available') + conv.remove_state('{relation_name}.connected') conv.set_state('{relation_name}.departing') def configure_principal(self, backend_name, configuration, stateless=None): diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..c706224 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,6 @@ +# Lint and unit test requirements +flake8 +os-testr>=0.4.1 +charms.reactive +mock>=1.2 +coverage>=3.6 diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..8d5209b --- /dev/null +++ b/tox.ini @@ -0,0 +1,28 @@ +[tox] +skipsdist = True +envlist = pep8,py3 +skip_missing_interpreters = True + +[testenv] +setenv = VIRTUAL_ENV={envdir} + PYTHONHASHSEED=0 + TERM=linux +install_command = + pip install {opts} {packages} + +[testenv:py3] +basepython = python3 +deps = -r{toxinidir}/test-requirements.txt +commands = ostestr {posargs} + +[testenv:pep8] +basepython = python3 +deps = -r{toxinidir}/test-requirements.txt +commands = flake8 {posargs} . + +[testenv:venv] +commands = {posargs} + +[flake8] +# E402 ignore necessary for path append before sys module import in actions +ignore = E402 diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/unit_tests/test_provides.py b/unit_tests/test_provides.py new file mode 100644 index 0000000..d257761 --- /dev/null +++ b/unit_tests/test_provides.py @@ -0,0 +1,130 @@ +import unittest +import mock + + +with mock.patch('charmhelpers.core.hookenv.metadata') as _meta: + _meta.return_Value = 'ss' + import provides + + +_hook_args = {} + +TO_PATCH = [ +] + + +def mock_hook(*args, **kwargs): + + def inner(f): + # remember what we were passed. Note that we can't actually determine + # the class we're attached to, as the decorator only gets the function. + _hook_args[f.__name__] = dict(args=args, kwargs=kwargs) + return f + return inner + + +class _unit_mock: + def __init__(self, unit_name, received=None): + self.unit_name = unit_name + self.received = received or {} + + +class _relation_mock: + def __init__(self, application_name=None, units=None): + self.to_publish_raw = {} + self.application_name = application_name + self.units = units + + +class TestCinderBackendProvides(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls._patched_hook = mock.patch('charms.reactive.when', mock_hook) + cls._patched_hook_started = cls._patched_hook.start() + # force provides to rerun the mock_hook decorator: + # try except is Python2/Python3 compatibility as Python3 has moved + # reload to importlib. + try: + reload(provides) + except NameError: + import importlib + importlib.reload(provides) + + @classmethod + def tearDownClass(cls): + cls._patched_hook.stop() + cls._patched_hook_started = None + cls._patched_hook = None + # and fix any breakage we did to the module + try: + reload(provides) + except NameError: + import importlib + importlib.reload(provides) + + def patch(self, method): + _m = mock.patch.object(self.obj, method) + _mock = _m.start() + self.addCleanup(_m.stop) + return _mock + + def setUp(self): + self.cr = provides.CinderBackendProvides('some-relation', []) + self._patches = {} + self._patches_start = {} + self.obj = provides + for method in TO_PATCH: + setattr(self, method, self.patch(method)) + + def tearDown(self): + self.cr = None + for k, v in self._patches.items(): + v.stop() + setattr(self, k, None) + self._patches = None + self._patches_start = None + + def patch_kr(self, attr, return_value=None): + mocked = mock.patch.object(self.cr, attr) + self._patches[attr] = mocked + started = mocked.start() + started.return_value = return_value + self._patches_start[attr] = started + setattr(self, attr, started) + + def test_cinder_backend_joined(self): + mock_conv = mock.MagicMock() + self.patch_kr('conversation', mock_conv) + self.cr.cinder_backend_joined() + expected_calls = [ + mock.call('{relation_name}.joined'), + mock.call('{relation_name}.connected'), + mock.call('{relation_name}.available')] + mock_conv.set_state.assert_has_calls(expected_calls) + + def test_cinder_backend_departed(self): + mock_conv = mock.MagicMock() + self.patch_kr('conversation', mock_conv) + self.cr.cinder_backend_departed() + expected_calls = [ + mock.call('{relation_name}.joined'), + mock.call('{relation_name}.available'), + mock.call('{relation_name}.connected')] + mock_conv.remove_state.assert_has_calls(expected_calls) + mock_conv.set_state.assert_called_once_with( + '{relation_name}.departing') + + def test_configure_principal(self): + mock_conv = mock.MagicMock() + self.patch_kr('conversation', mock_conv) + self.cr.configure_principal( + 'cinder-supernas', + [('cinder', 'ss')], + True) + expect = ('{"cinder": {"/etc/cinder/cinder.conf": {"sections": ' + '{"cinder-supernas": [["cinder", "ss"]]}}}}') + mock_conv.set_remote.assert_called_once_with( + backend_name='cinder-supernas', + stateless=True, + subordinate_configuration=expect)