From f1297f72a5c6f8dc4f89461850a7d8ebaa01cf04 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Wed, 20 Mar 2019 07:31:55 +0000 Subject: [PATCH] Add unit tests --- .gitignore | 3 + .stestr.conf | 3 + interface.yaml | 9 +++ provides.py | 13 +++- test-requirements.txt | 6 ++ tox.ini | 28 +++++++ unit_tests/__init__.py | 0 unit_tests/test_provides.py | 147 ++++++++++++++++++++++++++++++++++++ 8 files changed, 205 insertions(+), 4 deletions(-) create mode 100644 .stestr.conf 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 index 9dd3eb8..9faca0b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ .tox .testrepository +.unit-state.db +.stestr/ +__pycache__/ diff --git a/.stestr.conf b/.stestr.conf new file mode 100644 index 0000000..5fcccac --- /dev/null +++ b/.stestr.conf @@ -0,0 +1,3 @@ +[DEFAULT] +test_path=./unit_tests +top_dir=./ diff --git a/interface.yaml b/interface.yaml index 6fd2f04..28b81f0 100644 --- a/interface.yaml +++ b/interface.yaml @@ -1,3 +1,12 @@ name: pacemaker-remote summary: Basic pacemaker-remote interface version: 1 +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 1a5e61f..baf22fe 100644 --- a/provides.py +++ b/provides.py @@ -2,23 +2,28 @@ import base64 from charms.reactive import Endpoint -class PacemakerProvides(Endpoint): +class PacemakerRemoteProvides(Endpoint): - def publish_info(self, stonith_hostname=None): + def publish_info(self, remote_hostname, stonith_hostname=None, + enable_resources=True): """ Publish the stonith info """ for relation in self.relations: relation.to_publish['stonith-hostname'] = stonith_hostname + relation.to_publish['remote-hostname'] = remote_hostname + relation.to_publish['enable-resources'] = enable_resources def get_pacemaker_key(self): for relation in self.relations: pacemaker_keys = [] for unit in relation.units: - pacemaker_keys.append(unit.received['pacemaker-key']) + key = unit.received.get('pacemaker-key') + if key: + pacemaker_keys.append(key) unique_keys = len(set(pacemaker_keys)) if unique_keys > 1: raise Exception("Inconsistent keys") elif unique_keys == 1: - return base64.decode(unique_keys[0]) + return base64.b64decode(pacemaker_keys[0]) return 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..25d16bf --- /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 = stestr run {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..154f566 --- /dev/null +++ b/unit_tests/test_provides.py @@ -0,0 +1,147 @@ +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.to_publish = {} + self.application_name = application_name + self.units = units + + +class TestPacemakerRemoteProvides(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.relation_obj = provides.PacemakerRemoteProvides( + '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.relation_obj = None + for k, v in self._patches.items(): + v.stop() + setattr(self, k, None) + self._patches = None + self._patches_start = None + + def patch_relation_obj(self, attr, return_value=None): + mocked = mock.patch.object(self.relation_obj, attr) + self._patches[attr] = mocked + started = mocked.start() + started.return_value = return_value + self._patches_start[attr] = started + setattr(self, attr, started) + + def test_publish_info(self): + mock_rel = _relation_mock() + self.relation_obj._relations = [mock_rel] + self.relation_obj.publish_info( + 'node1.az1.local', + stonith_hostname='node1.stonith', + enable_resources=True) + expect = { + 'remote-hostname': 'node1.az1.local', + 'stonith-hostname': 'node1.stonith', + 'enable-resources': True} + self.assertEqual( + mock_rel.to_publish, + expect) + + def test_get_pacemaker_key(self): + unit1 = _unit_mock( + 'unit1', + received={'pacemaker-key': 'cG1ha2Vya2V5MQo='}) + mock_rel = _relation_mock(units=[unit1]) + self.relation_obj._relations = [mock_rel] + self.assertEqual( + self.relation_obj.get_pacemaker_key(), + b'pmakerkey1\n') + + def test_get_pacemaker_key_inconsistent(self): + unit1 = _unit_mock( + 'unit1', + received={'pacemaker-key': 'cG1ha2Vya2V5MQo='}) + unit2 = _unit_mock( + 'unit2', + received={'pacemaker-key': 'cG1ha2Vya2V5Mgo='}) + mock_rel = _relation_mock(units=[unit1, unit2]) + self.relation_obj._relations = [mock_rel] + with self.assertRaises(Exception): + self.relation_obj.get_pacemaker_key() + + def test_get_pacemaker_key_missing(self): + unit1 = _unit_mock( + 'unit1', + received={}) + unit2 = _unit_mock( + 'unit2', + received={}) + mock_rel = _relation_mock(units=[unit1, unit2]) + self.relation_obj._relations = [mock_rel] + self.assertEqual( + self.relation_obj.get_pacemaker_key(), + None)