From 87b8080f3163574580a207f15ff308da6010b0ff Mon Sep 17 00:00:00 2001 From: Daniel Watkins Date: Mon, 8 Jun 2020 17:08:43 -0400 Subject: test: move conftest.py to top-level, to cover tests/ also (#414) * test_opennebula: convert TestParseShellConfig to a pytest test And allow it to run bash. (We aren't aiming to convert TestCase tests to pytest tests as a rule. In this case, I needed to change its implementation to limit subp usage, and I chose pytest over CiTestCase.) * test: move conftest.py to top-level, to cover tests/ also This gives us a single conftest.py which is shared by all tests in the project. --- cloudinit/conftest.py | 72 ---------------------- conftest.py | 72 ++++++++++++++++++++++ tests/unittests/test_datasource/test_opennebula.py | 8 ++- tests/unittests/test_render_cloudcfg.py | 1 + 4 files changed, 79 insertions(+), 74 deletions(-) delete mode 100644 cloudinit/conftest.py create mode 100644 conftest.py diff --git a/cloudinit/conftest.py b/cloudinit/conftest.py deleted file mode 100644 index 251bca59..00000000 --- a/cloudinit/conftest.py +++ /dev/null @@ -1,72 +0,0 @@ -from unittest import mock - -import pytest - -from cloudinit import subp - - -@pytest.yield_fixture(autouse=True) -def disable_subp_usage(request): - """ - Across all (pytest) tests, ensure that subp.subp is not invoked. - - Note that this can only catch invocations where the util module is imported - and ``subp.subp(...)`` is called. ``from cloudinit.subp mport subp`` - imports happen before the patching here (or the CiTestCase monkey-patching) - happens, so are left untouched. - - To allow a particular test method or class to use subp.subp you can set the - parameter passed to this fixture to False using pytest.mark.parametrize:: - - @pytest.mark.parametrize("disable_subp_usage", [False], indirect=True) - def test_whoami(self): - subp.subp(["whoami"]) - - To instead allow subp.subp usage for a specific command, you can set the - parameter passed to this fixture to that command: - - @pytest.mark.parametrize("disable_subp_usage", ["bash"], indirect=True) - def test_bash(self): - subp.subp(["bash"]) - - To specify multiple commands, set the parameter to a list (note the - double-layered list: we specify a single parameter that is itself a list): - - @pytest.mark.parametrize( - "disable_subp_usage", ["bash", "whoami"], indirect=True) - def test_several_things(self): - subp.subp(["bash"]) - subp.subp(["whoami"]) - - This fixture (roughly) mirrors the functionality of - CiTestCase.allowed_subp. N.B. While autouse fixtures do affect non-pytest - tests, CiTestCase's allowed_subp does take precedence (and we have - TestDisableSubpUsageInTestSubclass to confirm that). - """ - should_disable = getattr(request, "param", True) - if should_disable: - if not isinstance(should_disable, (list, str)): - def side_effect(args, *other_args, **kwargs): - raise AssertionError("Unexpectedly used subp.subp") - else: - # Look this up before our patch is in place, so we have access to - # the real implementation in side_effect - real_subp = subp.subp - - if isinstance(should_disable, str): - should_disable = [should_disable] - - def side_effect(args, *other_args, **kwargs): - cmd = args[0] - if cmd not in should_disable: - raise AssertionError( - "Unexpectedly used subp.subp to call {} (allowed:" - " {})".format(cmd, ",".join(should_disable)) - ) - return real_subp(args, *other_args, **kwargs) - - with mock.patch('cloudinit.subp.subp', autospec=True) as m_subp: - m_subp.side_effect = side_effect - yield - else: - yield diff --git a/conftest.py b/conftest.py new file mode 100644 index 00000000..251bca59 --- /dev/null +++ b/conftest.py @@ -0,0 +1,72 @@ +from unittest import mock + +import pytest + +from cloudinit import subp + + +@pytest.yield_fixture(autouse=True) +def disable_subp_usage(request): + """ + Across all (pytest) tests, ensure that subp.subp is not invoked. + + Note that this can only catch invocations where the util module is imported + and ``subp.subp(...)`` is called. ``from cloudinit.subp mport subp`` + imports happen before the patching here (or the CiTestCase monkey-patching) + happens, so are left untouched. + + To allow a particular test method or class to use subp.subp you can set the + parameter passed to this fixture to False using pytest.mark.parametrize:: + + @pytest.mark.parametrize("disable_subp_usage", [False], indirect=True) + def test_whoami(self): + subp.subp(["whoami"]) + + To instead allow subp.subp usage for a specific command, you can set the + parameter passed to this fixture to that command: + + @pytest.mark.parametrize("disable_subp_usage", ["bash"], indirect=True) + def test_bash(self): + subp.subp(["bash"]) + + To specify multiple commands, set the parameter to a list (note the + double-layered list: we specify a single parameter that is itself a list): + + @pytest.mark.parametrize( + "disable_subp_usage", ["bash", "whoami"], indirect=True) + def test_several_things(self): + subp.subp(["bash"]) + subp.subp(["whoami"]) + + This fixture (roughly) mirrors the functionality of + CiTestCase.allowed_subp. N.B. While autouse fixtures do affect non-pytest + tests, CiTestCase's allowed_subp does take precedence (and we have + TestDisableSubpUsageInTestSubclass to confirm that). + """ + should_disable = getattr(request, "param", True) + if should_disable: + if not isinstance(should_disable, (list, str)): + def side_effect(args, *other_args, **kwargs): + raise AssertionError("Unexpectedly used subp.subp") + else: + # Look this up before our patch is in place, so we have access to + # the real implementation in side_effect + real_subp = subp.subp + + if isinstance(should_disable, str): + should_disable = [should_disable] + + def side_effect(args, *other_args, **kwargs): + cmd = args[0] + if cmd not in should_disable: + raise AssertionError( + "Unexpectedly used subp.subp to call {} (allowed:" + " {})".format(cmd, ",".join(should_disable)) + ) + return real_subp(args, *other_args, **kwargs) + + with mock.patch('cloudinit.subp.subp', autospec=True) as m_subp: + m_subp.side_effect = side_effect + yield + else: + yield diff --git a/tests/unittests/test_datasource/test_opennebula.py b/tests/unittests/test_datasource/test_opennebula.py index de896a9e..7c859c8a 100644 --- a/tests/unittests/test_datasource/test_opennebula.py +++ b/tests/unittests/test_datasource/test_opennebula.py @@ -9,6 +9,8 @@ import os import pwd import unittest +import pytest + TEST_VARS = { 'VAR1': 'single', @@ -914,12 +916,14 @@ class TestOpenNebulaNetwork(unittest.TestCase): self.assertEqual(expected, net.gen_conf()) -class TestParseShellConfig(unittest.TestCase): +class TestParseShellConfig: + + @pytest.mark.parametrize('disable_subp_usage', ['bash'], indirect=True) def test_no_seconds(self): cfg = '\n'.join(["foo=bar", "SECONDS=2", "xx=foo"]) # we could test 'sleep 2', but that would make the test run slower. ret = ds.parse_shell_config(cfg) - self.assertEqual(ret, {"foo": "bar", "xx": "foo"}) + assert ret == {"foo": "bar", "xx": "foo"} def populate_context_dir(path, variables): diff --git a/tests/unittests/test_render_cloudcfg.py b/tests/unittests/test_render_cloudcfg.py index 393a78b1..696915a3 100644 --- a/tests/unittests/test_render_cloudcfg.py +++ b/tests/unittests/test_render_cloudcfg.py @@ -13,6 +13,7 @@ DISTRO_VARIANTS = ["amazon", "arch", "centos", "debian", "fedora", "freebsd", "netbsd", "openbsd", "rhel", "suse", "ubuntu", "unknown"] +@pytest.mark.parametrize('disable_subp_usage', [sys.executable], indirect=True) class TestRenderCloudCfg: cmd = [sys.executable, os.path.realpath('tools/render-cloudcfg')] -- cgit v1.2.3