[virt-tools-list] [virt-bootstrap] [PATCH v4 08/13] tests: Add unit tests for 'virt_bootstrap' module
Radostin Stoyanov
rstoyanov1 at gmail.com
Fri Jul 21 12:13:24 UTC 2017
---
tests/__init__.py | 42 ++++
tests/test_virt_bootstrap.py | 464 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 506 insertions(+)
create mode 100644 tests/__init__.py
create mode 100644 tests/test_virt_bootstrap.py
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..2d3edce
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1,42 @@
+"""
+
+Test suite for virt-bootstrap
+
+ Authors:
+ Cedric Bosdonnat <cbosdonnat at suse.com>
+ Radostin Stoyanov <rstoyanov1 at gmail.com>
+
+ Copyright (C) 2017 Radostin Stoyanov
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+
+import sys
+import unittest
+
+try:
+ import mock
+except ImportError:
+ import unittest.mock as mock
+
+sys.path += '../src' # noqa: E402
+
+# pylint: disable=import-error
+from virtBootstrap import virt_bootstrap
+from virtBootstrap import sources
+from virtBootstrap import progress
+from virtBootstrap import utils
+
+__all__ = ['unittest', 'mock',
+ 'virt_bootstrap', 'sources', 'progress', 'utils']
diff --git a/tests/test_virt_bootstrap.py b/tests/test_virt_bootstrap.py
new file mode 100644
index 0000000..00e8b96
--- /dev/null
+++ b/tests/test_virt_bootstrap.py
@@ -0,0 +1,464 @@
+# Authors:
+# Cedric Bosdonnat <cbosdonnat at suse.com>
+# Radostin Stoyanov <rstoyanov1 at gmail.com>
+#
+# Copyright (C) 2017 SUSE, Inc.
+# Copyright (C) 2017 Radostin Stoyanov
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+"""
+Unit tests for functions defined in virtBootstrap.virt-bootstrap
+"""
+
+from tests import unittest
+from tests import mock
+from tests import virt_bootstrap
+from tests import sources
+
+
+# pylint: disable=invalid-name
+class TestVirtBootstrap(unittest.TestCase):
+ """
+ Test cases for virt_bootstrap module
+ """
+
+ ###################################
+ # Tests for: get_source(source_type)
+ ###################################
+ def test_get_invaid_source_type_should_fail(self):
+ """
+ Ensures that get_source() throws an Exception when invalid source
+ name was specified.
+ """
+ with self.assertRaises(Exception) as source:
+ virt_bootstrap.get_source('fake')
+ self.assertIn('fake', str(source.exception))
+
+ def test_get_docker_source(self):
+ """
+ Ensures that get_source() returns DockerSource when source name
+ "docker" is requested.
+ """
+ self.assertIs(virt_bootstrap.get_source('docker'),
+ sources.DockerSource)
+
+ def test_get_file_source(self):
+ """
+ Ensures that get_source() returns FileSource when source name
+ "file" is requested.
+ """
+ self.assertIs(virt_bootstrap.get_source('file'),
+ sources.FileSource)
+
+ ###################################
+ # Tests for: mapping_uid_gid()
+ ###################################
+ def test_mapping_uid_gid(self):
+ """
+ Ensures that mapping_uid_gid() calls map_id() with
+ correct parameters.
+ """
+ dest = '/path'
+ calls = [
+ { # Call 1
+ 'dest': dest,
+ 'uid': [[0, 1000, 10]],
+ 'gid': [[0, 1000, 10]]
+ },
+ { # Call 2
+ 'dest': dest,
+ 'uid': [],
+ 'gid': [[0, 1000, 10]]
+ },
+ { # Call 3
+ 'dest': dest,
+ 'uid': [[0, 1000, 10]],
+ 'gid': []
+ },
+ { # Call 4
+ 'dest': dest,
+ 'uid': [[0, 1000, 10], [500, 500, 10]],
+ 'gid': [[0, 1000, 10]]
+ }
+ ]
+
+ expected_calls = [
+ # Expected from call 1
+ mock.call(dest, [0, 1000, 10], [0, 1000, 10]),
+ # Expected from call 2
+ mock.call(dest, None, [0, 1000, 10]),
+ # Expected from call 3
+ mock.call(dest, [0, 1000, 10], None),
+ # Expected from call 4
+ mock.call(dest, [0, 1000, 10], [0, 1000, 10]),
+ mock.call(dest, [500, 500, 10], None)
+
+ ]
+ with mock.patch('virtBootstrap.virt_bootstrap.map_id') as m_map_id:
+ for args in calls:
+ virt_bootstrap.mapping_uid_gid(args['dest'],
+ args['uid'],
+ args['gid'])
+
+ m_map_id.assert_has_calls(expected_calls)
+
+ ###################################
+ # Tests for: map_id()
+ ###################################
+ @mock.patch('os.path.realpath')
+ def test_map_id(self, m_realpath):
+ """
+ Ensures that the UID/GID mapping applies to all files
+ and directories in root file system.
+ """
+ root_path = '/root'
+ files = ['foo1', 'foo2']
+ m_realpath.return_value = root_path
+
+ map_uid = [0, 1000, 10]
+ map_gid = [0, 1000, 10]
+ new_id = 'new_id'
+
+ expected_calls = [
+ mock.call('/root', new_id, new_id),
+ mock.call('/root/foo1', new_id, new_id),
+ mock.call('/root/foo2', new_id, new_id)
+ ]
+
+ with mock.patch.multiple('os',
+ lchown=mock.DEFAULT,
+ lstat=mock.DEFAULT,
+ walk=mock.DEFAULT) as mocked:
+
+ mocked['walk'].return_value = [(root_path, [], files)]
+ mocked['lstat']().st_uid = map_uid[0]
+ mocked['lstat']().st_gid = map_gid[0]
+
+ get_map_id = 'virtBootstrap.virt_bootstrap.get_map_id'
+ with mock.patch(get_map_id) as m_get_map_id:
+ m_get_map_id.return_value = new_id
+ virt_bootstrap.map_id(root_path, map_uid, map_gid)
+
+ mocked['lchown'].assert_has_calls(expected_calls)
+
+ ###################################
+ # Tests for: get_mapping_opts()
+ ###################################
+ def test_get_mapping_opts(self):
+ """
+ Ensures that get_mapping_opts() returns correct options for
+ mapping value.
+ """
+ test_values = [
+ {
+ 'mapping': [0, 1000, 10],
+ 'expected_result': {'first': 0, 'last': 10, 'offset': 1000},
+ },
+ {
+ 'mapping': [0, 1000, 10],
+ 'expected_result': {'first': 0, 'last': 10, 'offset': 1000},
+ },
+ {
+ 'mapping': [500, 1500, 1],
+ 'expected_result': {'first': 500, 'last': 501, 'offset': 1000},
+ },
+ {
+ 'mapping': [-1, -1, -1],
+ 'expected_result': {'first': 0, 'last': 1, 'offset': 0},
+ }
+ ]
+
+ for test in test_values:
+ res = virt_bootstrap.get_mapping_opts(test['mapping'])
+ self.assertEqual(test['expected_result'], res)
+
+ ###################################
+ # Tests for: get_map_id()
+ ###################################
+ def test_get_map_id(self):
+ """
+ Ensures that get_map_id() returns correct UID/GID mapping value.
+ """
+ test_values = [
+ {
+ 'old_id': 0,
+ 'mapping': [0, 1000, 10],
+ 'expected_result': 1000
+ },
+ {
+ 'old_id': 5,
+ 'mapping': [0, 500, 10],
+ 'expected_result': 505
+ },
+ {
+ 'old_id': 10,
+ 'mapping': [0, 100, 10],
+ 'expected_result': -1
+ },
+ ]
+ for test in test_values:
+ opts = virt_bootstrap.get_mapping_opts(test['mapping'])
+ res = virt_bootstrap.get_map_id(test['old_id'], opts)
+ self.assertEqual(test['expected_result'], res)
+
+ ###################################
+ # Tests for: parse_idmap()
+ ###################################
+ def test_parse_idmap(self):
+ """
+ Ensures that parse_idmap() returns correct UID/GID mapping value.
+ """
+ test_values = [
+ {
+ 'mapping': ['0:1000:10', '0:100:10'],
+ 'expected_result': [[0, 1000, 10], [0, 100, 10]],
+ },
+ {
+ 'mapping': ['0:1000:10'],
+ 'expected_result': [[0, 1000, 10]],
+ },
+ {
+ 'mapping': ['500:1500:1'],
+ 'expected_result': [[500, 1500, 1]],
+ },
+ {
+ 'mapping': ['-1:-1:-1'],
+ 'expected_result': [[-1, -1, -1]],
+ },
+ {
+ 'mapping': [],
+ 'expected_result': None,
+ }
+ ]
+ for test in test_values:
+ res = virt_bootstrap.parse_idmap(test['mapping'])
+ self.assertEqual(test['expected_result'], res)
+
+ def test_parse_idmap_raise_exception_on_invalid_mapping_value(self):
+ """
+ Ensures that parse_idmap() raise ValueError on mapping value.
+ """
+ with self.assertRaises(ValueError):
+ virt_bootstrap.parse_idmap(['invalid'])
+
+ ###################################
+ # Tests for: bootstrap()
+ ###################################
+ def test_bootsrap_creates_directory_if_does_not_exist(self):
+ """
+ Ensures that bootstrap() creates destination directory if
+ it does not exists.
+ """
+ src, dest = 'foo', 'bar'
+ with mock.patch.multiple(virt_bootstrap,
+ get_source=mock.DEFAULT,
+ os=mock.DEFAULT) as mocked:
+ mocked['os'].path.exists.return_value = False
+ virt_bootstrap.bootstrap(src, dest)
+ mocked['os'].path.exists.assert_called_once_with(dest)
+ mocked['os'].makedirs.assert_called_once_with(dest)
+
+ def test_bootstrap_exit_if_dest_is_invalid(self):
+ """
+ Ensures that bootstrap() exits with code 1 when the destination
+ path exists but it is not directory.
+ """
+ src, dest = 'foo', 'bar'
+ with mock.patch.multiple(virt_bootstrap,
+ get_source=mock.DEFAULT,
+ os=mock.DEFAULT,
+ logger=mock.DEFAULT,
+ sys=mock.DEFAULT) as mocked:
+ mocked['os'].path.exists.return_value = True
+ mocked['os'].path.isdir.return_value = False
+ virt_bootstrap.bootstrap(src, dest)
+ mocked['os'].path.isdir.assert_called_once_with(dest)
+ mocked['sys'].exit.assert_called_once_with(1)
+
+ def test_bootsrap_exit_if_no_write_access_on_dest(self):
+ """
+ Ensures that bootstrap() exits with code 1 when the current user
+ has not write permissions on the destination folder.
+ """
+ src, dest = 'foo', 'bar'
+ with mock.patch.multiple(virt_bootstrap,
+ get_source=mock.DEFAULT,
+ os=mock.DEFAULT,
+ logger=mock.DEFAULT,
+ sys=mock.DEFAULT) as mocked:
+ mocked['os'].path.exists.return_value = True
+ mocked['os'].path.isdir.return_value = True
+ mocked['os'].access.return_value = False
+ virt_bootstrap.bootstrap(src, dest)
+ mocked['os'].access.assert_called_once_with(dest,
+ mocked['os'].W_OK)
+ mocked['sys'].exit.assert_called_once_with(1)
+
+ def test_bootstrap_use_file_source_if_none_was_specified(self):
+ """
+ Ensures that bootstrap() calls get_source() with argument
+ 'file' when source format is not specified.
+ """
+ src, dest = 'foo', 'bar'
+ with mock.patch.multiple(virt_bootstrap,
+ get_source=mock.DEFAULT,
+ os=mock.DEFAULT,
+ sys=mock.DEFAULT) as mocked:
+ virt_bootstrap.bootstrap(src, dest)
+ mocked['get_source'].assert_called_once_with('file')
+
+ def test_bootstrap_successful_call(self):
+ """
+ Ensures that bootstrap() creates source instance and calls the
+ unpack method with destination path as argument.
+ """
+ src, dest = 'foo', 'bar'
+ with mock.patch.multiple(virt_bootstrap,
+ get_source=mock.DEFAULT,
+ os=mock.DEFAULT,
+ sys=mock.DEFAULT) as mocked:
+ mocked['os'].path.exists.return_value = True
+ mocked['os'].path.isdir.return_value = True
+ mocked['os'].access.return_value = True
+ mocked_source = mock.Mock()
+ mocked_unpack = mock.Mock()
+ mocked_source.return_value.unpack = mocked_unpack
+ mocked['get_source'].return_value = mocked_source
+ virt_bootstrap.bootstrap(src, dest)
+ # sys.exit should not be called
+ mocked['sys'].exit.assert_not_called()
+ mocked_source.assert_called_once()
+ mocked_unpack.assert_called_once_with(dest)
+
+ def test_bootstrap_all_params_are_passed_to_source_instance(self):
+ """
+ Ensures that bootstrap() is passing all arguments to newly created
+ source instance.
+ """
+ params_list = ['dest', 'fmt', 'username', 'password', 'root_password',
+ 'not_secure', 'no_cache', 'progress_cb']
+ params = {param: param for param in params_list}
+
+ for kw_param in params_list:
+ params[kw_param] = kw_param
+
+ with mock.patch.multiple(virt_bootstrap,
+ get_source=mock.DEFAULT,
+ os=mock.DEFAULT,
+ urlparse=mock.DEFAULT,
+ progress=mock.DEFAULT,
+ utils=mock.DEFAULT,
+ sys=mock.DEFAULT) as mocked:
+ mocked['os'].path.exists.return_value = True
+ mocked['os'].path.isdir.return_value = True
+ mocked['os'].access.return_value = True
+
+ mocked['progress'].Progress.return_value = params['progress_cb']
+
+ mocked_source = mock.Mock()
+ mocked_unpack = mock.Mock()
+ mocked_source.return_value.unpack = mocked_unpack
+ mocked['get_source'].return_value = mocked_source
+
+ mocked_uri = mock.Mock()
+ mocked['urlparse'].return_value = mocked_uri
+ params['uri'] = mocked_uri
+
+ virt_bootstrap.bootstrap(**params)
+ # sys.exit should not be called
+ mocked['sys'].exit.assert_not_called()
+
+ mocked_source.assert_called_once()
+ mocked_unpack.assert_called_once_with(params['dest'])
+
+ called_with_args, called_with_kwargs = mocked_source.call_args
+ self.assertEqual(called_with_args, ())
+
+ del params['dest']
+ del params['root_password']
+ params['progress'] = params.pop('progress_cb')
+ for kwarg in params:
+ self.assertEqual(called_with_kwargs[kwarg], params[kwarg])
+
+ def test_if_bootstrap_calls_set_root_password(self):
+ """
+ Ensures that bootstrap() calls set_root_password() when the argument
+ root_password is specified.
+ """
+ src, dest, root_password = 'foo', 'bar', 'root_password'
+ with mock.patch.multiple(virt_bootstrap,
+ get_source=mock.DEFAULT,
+ os=mock.DEFAULT,
+ utils=mock.DEFAULT,
+ sys=mock.DEFAULT) as mocked:
+ mocked['os'].path.exists.return_value = True
+ mocked['os'].path.isdir.return_value = True
+ mocked['os'].access.return_value = True
+
+ virt_bootstrap.bootstrap(src, dest, root_password=root_password)
+ mocked['utils'].set_root_password.assert_called_once_with(
+ dest, root_password)
+
+ def test_if_bootstrap_calls_set_mapping_uid_gid(self):
+ """
+ Ensures that bootstrap() calls mapping_uid_gid() when the argument
+ uid_map or gid_map is specified.
+ """
+ src, dest, uid_map, gid_map = 'foo', 'bar', 'id', 'id'
+ expected_calls = [
+ mock.call('bar', None, 'id'),
+ mock.call('bar', 'id', None),
+ mock.call('bar', 'id', 'id')
+ ]
+
+ with mock.patch.multiple(virt_bootstrap,
+ get_source=mock.DEFAULT,
+ os=mock.DEFAULT,
+ mapping_uid_gid=mock.DEFAULT,
+ utils=mock.DEFAULT,
+ sys=mock.DEFAULT) as mocked:
+ mocked['os'].path.exists.return_value = True
+ mocked['os'].path.isdir.return_value = True
+ mocked['os'].access.return_value = True
+
+ virt_bootstrap.bootstrap(src, dest, gid_map=gid_map)
+ virt_bootstrap.bootstrap(src, dest, uid_map=uid_map)
+ virt_bootstrap.bootstrap(src, dest,
+ uid_map=uid_map, gid_map=gid_map)
+ mocked['mapping_uid_gid'].assert_has_calls(expected_calls)
+
+ ###################################
+ # Tests for: set_logging_conf()
+ ###################################
+ def test_if_logging_level_format_handler_are_set(self):
+ """
+ Ensures that set_logging_conf() sets log level and adds new stream
+ handler with formatting.
+ """
+ with mock.patch('virtBootstrap.virt_bootstrap.logging') as m_logging:
+ mocked_stream_hdlr = mock.Mock()
+ m_logger = mock.Mock()
+ m_logging.getLogger.return_value = m_logger
+ m_logging.StreamHandler.return_value = mocked_stream_hdlr
+ virt_bootstrap.set_logging_conf()
+ m_logging.getLogger.assert_called_once_with('virtBootstrap')
+ mocked_stream_hdlr.setFormatter.assert_called_once()
+ m_logger.addHandler.assert_called_once_with(mocked_stream_hdlr)
+ m_logger.setLevel.assert_called_once()
+
+
+if __name__ == '__main__':
+ unittest.main(exit=False)
--
2.9.4
More information about the virt-tools-list
mailing list