From 9848f660367b27ce3463c8a102c54a23db3a356e Mon Sep 17 00:00:00 2001 From: Felix Matouschek Date: Fri, 12 Apr 2024 09:26:49 +0200 Subject: [PATCH] cleanup: Migrate existing unit tests to pytest Cleanup the existing unit tests and move them to pytest. By using pytest it becomes easier to add new tests for existing functionality in the collection. Signed-off-by: Felix Matouschek --- test-requirements.txt | 1 + tests/unit/conftest.py | 44 --- tests/unit/modules/test_module_kubevirt_vm.py | 285 ++++++++---------- .../modules/test_module_kubevirt_vm_info.py | 66 ++-- tests/unit/utils/ansible_module_mock.py | 10 +- 5 files changed, 159 insertions(+), 247 deletions(-) delete mode 100644 tests/unit/conftest.py diff --git a/test-requirements.txt b/test-requirements.txt index f90b181..8784bed 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,4 +1,5 @@ pytest pytest-ansible +pytest-mock pytest-xdist tox-ansible diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py deleted file mode 100644 index 20615ad..0000000 --- a/tests/unit/conftest.py +++ /dev/null @@ -1,44 +0,0 @@ -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -import json -import sys -from io import BytesIO - -import pytest - -import ansible.module_utils.basic -from ansible.module_utils.six import string_types -from ansible.module_utils._text import to_bytes -from ansible.module_utils.common._collections_compat import MutableMapping - - -@pytest.fixture -def stdin(mocker, request): - old_args = ansible.module_utils.basic._ANSIBLE_ARGS - ansible.module_utils.basic._ANSIBLE_ARGS = None - old_argv = sys.argv - sys.argv = ["ansible_unittest"] - - if isinstance(request.param, string_types): - args = request.param - elif isinstance(request.param, MutableMapping): - if "ANSIBLE_MODULE_ARGS" not in request.param: - request.param = {"ANSIBLE_MODULE_ARGS": request.param} - if "_ansible_remote_tmp" not in request.param["ANSIBLE_MODULE_ARGS"]: - request.param["ANSIBLE_MODULE_ARGS"]["_ansible_remote_tmp"] = "/tmp" - if "_ansible_keep_remote_files" not in request.param["ANSIBLE_MODULE_ARGS"]: - request.param["ANSIBLE_MODULE_ARGS"]["_ansible_keep_remote_files"] = False - args = json.dumps(request.param) - else: - raise Exception("Malformed data to the stdin pytest fixture") - - fake_stdin = BytesIO(to_bytes(args, errors="surrogate_or_strict")) - mocker.patch("ansible.module_utils.basic.sys.stdin", mocker.MagicMock()) - mocker.patch("ansible.module_utils.basic.sys.stdin.buffer", fake_stdin) - - yield fake_stdin - - ansible.module_utils.basic._ANSIBLE_ARGS = old_args - sys.argv = old_argv diff --git a/tests/unit/modules/test_module_kubevirt_vm.py b/tests/unit/modules/test_module_kubevirt_vm.py index 16f5964..ee78b69 100644 --- a/tests/unit/modules/test_module_kubevirt_vm.py +++ b/tests/unit/modules/test_module_kubevirt_vm.py @@ -6,11 +6,9 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type -import unittest +import pytest -from unittest.mock import patch, ANY - -from ansible.module_utils import basic +from ansible.module_utils.basic import AnsibleModule from ansible_collections.kubernetes.core.plugins.module_utils.k8s import runner from ansible_collections.kubevirt.core.plugins.modules import kubevirt_vm from ansible_collections.kubevirt.core.tests.unit.utils.ansible_module_mock import ( @@ -19,47 +17,55 @@ from ansible_collections.kubevirt.core.tests.unit.utils.ansible_module_mock impo exit_json, fail_json, set_module_args, - get_api_client, ) -FIXTURE1 = { - "apiVersion": "kubevirt.io/v1", - "kind": "VirtualMachine", - "metadata": { - "name": "testvm", - "namespace": "default", - "labels": {"environment": "staging", "service": "loadbalancer"}, - }, - "spec": { - "running": True, - "instancetype": {"name": "u1.medium"}, - "preference": {"name": "fedora"}, - "dataVolumeTemplates": [ - { - "metadata": {"name": "testdv"}, - "spec": { - "source": { - "registry": { - "url": "docker://quay.io/containerdisks/fedora:latest" + +@pytest.fixture(scope="module") +def vm_definition(): + return { + "apiVersion": "kubevirt.io/v1", + "kind": "VirtualMachine", + "metadata": { + "name": "testvm", + "namespace": "default", + "labels": {"environment": "staging", "service": "loadbalancer"}, + }, + "spec": { + "running": True, + "instancetype": {"name": "u1.medium"}, + "preference": {"name": "fedora"}, + "dataVolumeTemplates": [ + { + "metadata": {"name": "testdv"}, + "spec": { + "source": { + "registry": { + "url": "docker://quay.io/containerdisks/fedora:latest" + }, + }, + "storage": { + "accessModes": ["ReadWriteOnce"], + "resources": {"requests": {"storage": "5Gi"}}, }, }, - "storage": { - "accessModes": ["ReadWriteOnce"], - "resources": {"requests": {"storage": "5Gi"}}, - }, + } + ], + "template": { + "metadata": { + "labels": {"environment": "staging", "service": "loadbalancer"} + }, + "spec": { + "domain": {"devices": {}}, + "terminationGracePeriodSeconds": 180, }, - } - ], - "template": { - "metadata": { - "labels": {"environment": "staging", "service": "loadbalancer"} }, - "spec": {"domain": {"devices": {}}, "terminationGracePeriodSeconds": 180}, }, - }, -} + } -METADATA = """apiVersion: kubevirt.io/v1 + +@pytest.fixture(scope="module") +def vm_manifest(): + return """apiVersion: kubevirt.io/v1 kind: VirtualMachine metadata: name: "testvm" @@ -97,121 +103,96 @@ spec: terminationGracePeriodSeconds: 180 """ -FIXTURE2 = { - "name": "testvm", - "namespace": "default", - "state": "present", - "labels": {"service": "loadbalancer", "environment": "staging"}, - "instancetype": {"name": "u1.medium"}, - "preference": {"name": "fedora"}, - "data_volume_templates": [ - { - "metadata": {"name": "testdv"}, - "spec": { - "source": { - "registry": { - "url": "docker://quay.io/containerdisks/fedora:latest" + +@pytest.fixture(scope="module") +def module_params_create(): + return { + "name": "testvm", + "namespace": "default", + "state": "present", + "labels": {"service": "loadbalancer", "environment": "staging"}, + "instancetype": {"name": "u1.medium"}, + "preference": {"name": "fedora"}, + "data_volume_templates": [ + { + "metadata": {"name": "testdv"}, + "spec": { + "source": { + "registry": { + "url": "docker://quay.io/containerdisks/fedora:latest" + }, + }, + "storage": { + "accessModes": ["ReadWriteOnce"], + "resources": {"requests": {"storage": "5Gi"}}, }, }, - "storage": { - "accessModes": ["ReadWriteOnce"], - "resources": {"requests": {"storage": "5Gi"}}, - }, - }, - } - ], - "spec": {"domain": {"devices": {}}, "terminationGracePeriodSeconds": 180}, - "api_version": "kubevirt.io/v1", - "running": True, - "wait": False, - "wait_sleep": 5, - "wait_timeout": 120, - "force": False, - "generate_name": None, - "annotations": None, - "kubeconfig": None, - "context": None, - "host": None, - "api_key": None, - "username": None, - "password": None, - "validate_certs": None, - "ca_cert": None, - "client_cert": None, - "client_key": None, - "proxy": None, - "no_proxy": None, - "proxy_headers": None, - "persist_config": None, - "impersonate_user": None, - "impersonate_groups": None, - "delete_options": None, - "resource_definition": METADATA, - "wait_condition": {"type": "Ready", "status": True}, -} - - -class TestCreateVM(unittest.TestCase): - def setUp(self): - self.mock_module_helper = patch.multiple( - basic.AnsibleModule, exit_json=exit_json, fail_json=fail_json - ) - self.mock_module_helper.start() - - self.mock_runner = patch.multiple(runner, get_api_client=get_api_client) - self.mock_runner.start() - - # Stop the patch after test execution - # like tearDown but executed also when the setup failed - self.addCleanup(self.mock_module_helper.stop) - self.addCleanup(self.mock_runner.stop) - - def test_module_fail_when_required_args_missing(self): - with self.assertRaises(AnsibleFailJson): - set_module_args({}) - kubevirt_vm.main() - - def test_create(self): - set_module_args( - { - "name": "testvm", - "namespace": "default", - "state": "present", - "labels": {"service": "loadbalancer", "environment": "staging"}, - "instancetype": {"name": "u1.medium"}, - "preference": {"name": "fedora"}, - "data_volume_templates": [ - { - "metadata": {"name": "testdv"}, - "spec": { - "source": { - "registry": { - "url": "docker://quay.io/containerdisks/fedora:latest" - }, - }, - "storage": { - "accessModes": ["ReadWriteOnce"], - "resources": {"requests": {"storage": "5Gi"}}, - }, - }, - } - ], - "spec": { - "domain": {"devices": {}}, - "terminationGracePeriodSeconds": 180, - }, } - ) - with patch.object(runner, "perform_action") as mock_run_command: - mock_run_command.return_value = { - "method": "create", - "changed": True, - "result": "success", - } # successful execution - with self.assertRaises(AnsibleExitJson) as result: - kubevirt_vm.main() - mock_run_command.assert_called_once_with( - ANY, - FIXTURE1, - FIXTURE2, - ) + ], + "spec": { + "domain": {"devices": {}}, + "terminationGracePeriodSeconds": 180, + }, + } + + +@pytest.fixture(scope="module") +def k8s_module_params_create(module_params_create, vm_manifest): + return module_params_create | { + "api_version": "kubevirt.io/v1", + "running": True, + "wait": False, + "wait_sleep": 5, + "wait_timeout": 120, + "force": False, + "generate_name": None, + "annotations": None, + "kubeconfig": None, + "context": None, + "host": None, + "api_key": None, + "username": None, + "password": None, + "validate_certs": None, + "ca_cert": None, + "client_cert": None, + "client_key": None, + "proxy": None, + "no_proxy": None, + "proxy_headers": None, + "persist_config": None, + "impersonate_user": None, + "impersonate_groups": None, + "delete_options": None, + "resource_definition": vm_manifest, + "wait_condition": {"type": "Ready", "status": True}, + } + + +def test_module_fails_when_required_args_missing(monkeypatch): + monkeypatch.setattr(AnsibleModule, "fail_json", fail_json) + with pytest.raises(AnsibleFailJson): + set_module_args({}) + kubevirt_vm.main() + + +def test_module_create( + monkeypatch, mocker, module_params_create, k8s_module_params_create, vm_definition +): + monkeypatch.setattr(AnsibleModule, "exit_json", exit_json) + monkeypatch.setattr(runner, "get_api_client", lambda _: None) + + set_module_args(module_params_create) + + perform_action = mocker.patch.object(runner, "perform_action") + perform_action.return_value = { + "method": "create", + "changed": True, + "result": "success", + } + + with pytest.raises(AnsibleExitJson): + kubevirt_vm.main() + perform_action.assert_called_once_with( + mocker.ANY, vm_definition, k8s_module_params_create + ) diff --git a/tests/unit/modules/test_module_kubevirt_vm_info.py b/tests/unit/modules/test_module_kubevirt_vm_info.py index 67dc279..f06d0bd 100644 --- a/tests/unit/modules/test_module_kubevirt_vm_info.py +++ b/tests/unit/modules/test_module_kubevirt_vm_info.py @@ -6,8 +6,7 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type -from unittest import TestCase -from unittest.mock import patch +import pytest from ansible.module_utils.basic import AnsibleModule from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import ( @@ -19,12 +18,10 @@ from ansible_collections.kubevirt.core.plugins.modules import ( from ansible_collections.kubevirt.core.tests.unit.utils.ansible_module_mock import ( AnsibleExitJson, exit_json, - fail_json, set_module_args, - get_api_client, ) -FIXTURE1 = { +FIND_ARGS_DEFAULT = { "kind": "VirtualMachine", "api_version": "kubevirt.io/v1", "name": None, @@ -37,7 +34,7 @@ FIXTURE1 = { "condition": {"type": "Ready", "status": True}, } -FIXTURE2 = { +FIND_ARGS_NAME_NAMESPACE = { "kind": "VirtualMachine", "api_version": "kubevirt.io/v1", "name": "testvm", @@ -51,43 +48,26 @@ FIXTURE2 = { } -class TestDescribeVM(TestCase): - def setUp(self): - self.mock_module_helper = patch.multiple( - AnsibleModule, exit_json=exit_json, fail_json=fail_json - ) - self.mock_module_helper.start() +@pytest.mark.parametrize( + "module_args,find_args", + [ + ({}, FIND_ARGS_DEFAULT), + ({"name": "testvm", "namespace": "default"}, FIND_ARGS_NAME_NAMESPACE), + ], +) +def test_module(monkeypatch, mocker, module_args, find_args): + monkeypatch.setattr(AnsibleModule, "exit_json", exit_json) + monkeypatch.setattr(kubevirt_vm_info, "get_api_client", lambda _: None) - self.mock_main = patch.multiple(kubevirt_vm_info, get_api_client=get_api_client) - self.mock_main.start() + set_module_args(module_args) - # Stop the patch after test execution - # like tearDown but executed also when the setup failed - self.addCleanup(self.mock_module_helper.stop) - self.addCleanup(self.mock_main.stop) + find = mocker.patch.object(K8sService, "find") + find.return_value = { + "api_found": True, + "failed": False, + "resources": [], + } - def run_module(self, fixture): - with patch.object(K8sService, "find") as mock_find_command: - mock_find_command.return_value = { - "api_found": True, - "failed": False, - "resources": [], - } # successful execution - with self.assertRaises(AnsibleExitJson): - kubevirt_vm_info.main() - mock_find_command.assert_called_once_with( - **fixture, - ) - - def test_describe_without_args(self): - set_module_args({}) - self.run_module(FIXTURE1) - - def test_describe_with_args(self): - set_module_args( - { - "name": "testvm", - "namespace": "default", - } - ) - self.run_module(FIXTURE2) + with pytest.raises(AnsibleExitJson): + kubevirt_vm_info.main() + find.assert_called_once_with(**find_args) diff --git a/tests/unit/utils/ansible_module_mock.py b/tests/unit/utils/ansible_module_mock.py index 7904f9f..d489756 100644 --- a/tests/unit/utils/ansible_module_mock.py +++ b/tests/unit/utils/ansible_module_mock.py @@ -2,7 +2,8 @@ # Copyright: (c) 2021, Ansible Project # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -# This module maock the AnsibleModule class for more information please visite +# This file allows to run modules in unit tests. +# It was taken from: # https://docs.ansible.com/ansible/latest/dev_guide/testing_units_modules.html#module-argument-processing from __future__ import absolute_import, division, print_function @@ -23,13 +24,11 @@ def set_module_args(args): class AnsibleExitJson(Exception): """Exception class to be raised by module.exit_json and caught by the test case""" - pass class AnsibleFailJson(Exception): """Exception class to be raised by module.fail_json and caught by the test case""" - pass @@ -44,8 +43,3 @@ def fail_json(*args, **kwargs): """function to patch over fail_json; package return data into an exception""" kwargs["failed"] = True raise AnsibleFailJson(kwargs) - - -def get_api_client(*args, **kwargs): - """function to patch over get_api_client""" - pass