[virt-tools-list] [virt-bootstrap] [PATCH v5 07/11] tests: Add unit tests for 'virt_bootstrap' module

Cedric Bosdonnat cbosdonnat at suse.com
Mon Jul 24 13:35:09 UTC 2017


On Mon, 2017-07-24 at 09:14 +0100, Radostin Stoyanov wrote:
> ---
>  tests/__init__.py            |  41 ++++
>  tests/test_virt_bootstrap.py | 467 +++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 508 insertions(+)
>  create mode 100644 tests/test_virt_bootstrap.py
> 
> diff --git a/tests/__init__.py b/tests/__init__.py
> index e69de29..5e479c0 100644
> --- a/tests/__init__.py
> +++ b/tests/__init__.py
> @@ -0,0 +1,41 @@
> +"""
> +    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..a1dbe17
> --- /dev/null
> +++ b/tests/test_virt_bootstrap.py
> @@ -0,0 +1,467 @@
> +# 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('invalid')
> +        self.assertIn('invalid', 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, fmt, dest, root_password = 'foo', 'fmt', '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,
> +                                     fmt=fmt,
> +                                     root_password=root_password)
> +
> +            mocked['utils'].set_root_password.assert_called_once_with(
> +                fmt, 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)

ACK

--
Cedric




More information about the virt-tools-list mailing list