From 5fe350172c3a938450482a8c732aef2062fca1f7 Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Mon, 11 Feb 2019 16:47:14 -0800 Subject: [PATCH 01/12] Start v28.py with a copy of v24.py so diffing is easy --- pytest_ansible/module_dispatcher/v28.py | 151 ++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 pytest_ansible/module_dispatcher/v28.py diff --git a/pytest_ansible/module_dispatcher/v28.py b/pytest_ansible/module_dispatcher/v28.py new file mode 100644 index 00000000..341a075d --- /dev/null +++ b/pytest_ansible/module_dispatcher/v28.py @@ -0,0 +1,151 @@ +import warnings +import ansible.constants +import ansible.utils +import ansible.errors + +from pkg_resources import parse_version +from ansible.plugins.callback import CallbackBase +from ansible.executor.task_queue_manager import TaskQueueManager +from ansible.playbook.play import Play +from ansible.cli import CLI +from pytest_ansible.logger import get_logger +from pytest_ansible.module_dispatcher.v2 import ModuleDispatcherV2 +from pytest_ansible.results import AdHocResult +from pytest_ansible.errors import AnsibleConnectionFailure + +has_ansible_v24 = parse_version(ansible.__version__) >= parse_version('2.4.0') + +if not has_ansible_v24: + raise ImportError("Only supported with ansible-2.4 and newer") +else: + from ansible.plugins.loader import module_loader + + +log = get_logger(__name__) + + +class ResultAccumulator(CallbackBase): + + """Fixme.""" + + def __init__(self, *args, **kwargs): + """Initialize object.""" + super(ResultAccumulator, self).__init__(*args, **kwargs) + self.contacted = {} + self.unreachable = {} + + def v2_runner_on_failed(self, result, *args, **kwargs): + self.contacted[result._host.get_name()] = result._result + + v2_runner_on_ok = v2_runner_on_failed + + def v2_runner_on_unreachable(self, result): + self.unreachable[result._host.get_name()] = result._result + + @property + def results(self): + return dict(contacted=self.contacted, unreachable=self.unreachable) + + +class ModuleDispatcherV24(ModuleDispatcherV2): + + """Pass.""" + + required_kwargs = ('inventory', 'inventory_manager', 'variable_manager', 'host_pattern', 'loader') + + def has_module(self, name): + + return module_loader.has_plugin(name) + + def _run(self, *module_args, **complex_args): + """Execute an ansible adhoc command returning the result in a AdhocResult object.""" + # Assemble module argument string + if module_args: + complex_args.update(dict(_raw_params=' '.join(module_args))) + + # Assert hosts matching the provided pattern exist + hosts = self.options['inventory_manager'].list_hosts() + no_hosts = False + if len(hosts) == 0: + no_hosts = True + warnings.warn("provided hosts list is empty, only localhost is available") + + self.options['inventory_manager'].subset(self.options.get('subset')) + hosts = self.options['inventory_manager'].list_hosts(self.options['host_pattern']) + if len(hosts) == 0 and not no_hosts: + raise ansible.errors.AnsibleError("Specified hosts and/or --limit does not match any hosts") + + # Log the module and parameters + log.debug("[%s] %s: %s" % (self.options['host_pattern'], self.options['module_name'], complex_args)) + + parser = CLI.base_parser( + runas_opts=True, + inventory_opts=True, + async_opts=True, + output_opts=True, + connect_opts=True, + check_opts=True, + runtask_opts=True, + vault_opts=True, + fork_opts=True, + module_opts=True, + ) + (options, args) = parser.parse_args([]) + + # Pass along cli options + options.verbosity = 5 + options.connection = self.options.get('connection') + options.remote_user = self.options.get('user') + options.become = self.options.get('become') + options.become_method = self.options.get('become_method') + options.become_user = self.options.get('become_user') + options.module_path = self.options.get('module_path') + + # Initialize callback to capture module JSON responses + cb = ResultAccumulator() + + kwargs = dict( + inventory=self.options['inventory_manager'], + variable_manager=self.options['variable_manager'], + loader=self.options['loader'], + options=options, + stdout_callback=cb, + passwords=dict(conn_pass=None, become_pass=None), + ) + + # create a pseudo-play to execute the specified module via a single task + play_ds = dict( + name="pytest-ansible", + hosts=self.options['host_pattern'], + gather_facts='no', + tasks=[ + dict( + action=dict( + module=self.options['module_name'], args=complex_args + ), + ), + ] + ) + log.debug("Play(%s)", play_ds) + play = Play().load(play_ds, variable_manager=self.options['variable_manager'], loader=self.options['loader']) + + # now create a task queue manager to execute the play + tqm = None + try: + log.debug("TaskQueueManager(%s)", kwargs) + tqm = TaskQueueManager(**kwargs) + tqm.run(play) + finally: + if tqm: + tqm.cleanup() + + # Log the results + log.debug(cb.results) + + # Raise exception if host(s) unreachable + # FIXME - if multiple hosts were involved, should an exception be raised? + if cb.unreachable: + raise AnsibleConnectionFailure("Host unreachable", dark=cb.unreachable, contacted=cb.contacted) + + # Success! + return AdHocResult(contacted=cb.contacted) From a66bac67c83c83dbdb1cd3f2cfe07d606ba0ea24 Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Mon, 11 Feb 2019 16:47:42 -0800 Subject: [PATCH 02/12] Add help for setting command line args in Ansible-2.8 --- pytest_ansible/module_dispatcher/v28.py | 51 ++++++++++++------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/pytest_ansible/module_dispatcher/v28.py b/pytest_ansible/module_dispatcher/v28.py index 341a075d..6ea9504c 100644 --- a/pytest_ansible/module_dispatcher/v28.py +++ b/pytest_ansible/module_dispatcher/v28.py @@ -8,15 +8,16 @@ from ansible.executor.task_queue_manager import TaskQueueManager from ansible.playbook.play import Play from ansible.cli import CLI +from ansible.cli.adhoc import AdHocCLI from pytest_ansible.logger import get_logger from pytest_ansible.module_dispatcher.v2 import ModuleDispatcherV2 from pytest_ansible.results import AdHocResult from pytest_ansible.errors import AnsibleConnectionFailure -has_ansible_v24 = parse_version(ansible.__version__) >= parse_version('2.4.0') +has_ansible_v28 = parse_version(ansible.__version__) >= parse_version('2.8.0') -if not has_ansible_v24: - raise ImportError("Only supported with ansible-2.4 and newer") +if not has_ansible_v28: + raise ImportError("Only supported with ansible-2.8 and newer") else: from ansible.plugins.loader import module_loader @@ -47,7 +48,7 @@ def results(self): return dict(contacted=self.contacted, unreachable=self.unreachable) -class ModuleDispatcherV24(ModuleDispatcherV2): +class ModuleDispatcherV28(ModuleDispatcherV2): """Pass.""" @@ -78,28 +79,27 @@ def _run(self, *module_args, **complex_args): # Log the module and parameters log.debug("[%s] %s: %s" % (self.options['host_pattern'], self.options['module_name'], complex_args)) - parser = CLI.base_parser( - runas_opts=True, - inventory_opts=True, - async_opts=True, - output_opts=True, - connect_opts=True, - check_opts=True, - runtask_opts=True, - vault_opts=True, - fork_opts=True, - module_opts=True, - ) - (options, args) = parser.parse_args([]) - # Pass along cli options - options.verbosity = 5 - options.connection = self.options.get('connection') - options.remote_user = self.options.get('user') - options.become = self.options.get('become') - options.become_method = self.options.get('become_method') - options.become_user = self.options.get('become_user') - options.module_path = self.options.get('module_path') + args = ['pytest-ansible', '-vvvvv'] + for argument in ('connection', 'user', 'become', 'become_method', 'become_usder', 'module_path'): + arg_value = self.options.get(argument) + argument = argument.replace('_', '-') + + if arg_value in (None, False): + continue + + if arg_value is True: + args.append('--{0}'.format(argument)) + else: + args.append('--{0}={1}'.format(argument, arg_value)) + + # Use Ansible's own adhoc cli to parse the fake command line we created and then save it + # into Ansible's global context + adhoc = AdHocCLI(args) + adhoc.parse() + + # And now we'll never speak of this again + del adhoc # Initialize callback to capture module JSON responses cb = ResultAccumulator() @@ -108,7 +108,6 @@ def _run(self, *module_args, **complex_args): inventory=self.options['inventory_manager'], variable_manager=self.options['variable_manager'], loader=self.options['loader'], - options=options, stdout_callback=cb, passwords=dict(conn_pass=None, become_pass=None), ) From f5000a72f1a95ab12a5be3898d9ff3075266af22 Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Mon, 11 Feb 2019 16:59:46 -0800 Subject: [PATCH 03/12] One bugfix to v28.py modulemanager --- pytest_ansible/module_dispatcher/v28.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest_ansible/module_dispatcher/v28.py b/pytest_ansible/module_dispatcher/v28.py index 6ea9504c..f1157770 100644 --- a/pytest_ansible/module_dispatcher/v28.py +++ b/pytest_ansible/module_dispatcher/v28.py @@ -80,7 +80,7 @@ def _run(self, *module_args, **complex_args): log.debug("[%s] %s: %s" % (self.options['host_pattern'], self.options['module_name'], complex_args)) # Pass along cli options - args = ['pytest-ansible', '-vvvvv'] + args = ['pytest-ansible', '-vvvvv', self.options['host_pattern']] for argument in ('connection', 'user', 'become', 'become_method', 'become_usder', 'module_path'): arg_value = self.options.get(argument) argument = argument.replace('_', '-') From 717e11cad4e65eb1c0c4589cec2b2fd2ec25ebe0 Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Mon, 11 Feb 2019 17:09:25 -0800 Subject: [PATCH 04/12] Update tox to test supported ansible versions --- tox.ini | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/tox.ini b/tox.ini index 8e00cecf..63a42afe 100644 --- a/tox.ini +++ b/tox.ini @@ -1,10 +1,9 @@ [tox] envlist = lint, - ansible_v23, - ansible_v24, ansible_v25, ansible_v26, + ansible_v27, ansible_devel, coveralls skip_missing_interpreters = true @@ -34,26 +33,6 @@ commands = commands= - coveralls -[testenv:ansible_v19] -deps = - {[testenv]deps} - ansible>=1.9.0,<2.0.0 - -[testenv:ansible_v22] -deps = - {[testenv]deps} - ansible>=2.2.0,<2.3.0 - -[testenv:ansible_v23] -deps = - {[testenv]deps} - ansible>=2.3.0,<2.4.0 - -[testenv:ansible_v24] -deps = - {[testenv]deps} - ansible>=2.4.0,<2.5.0 - [testenv:ansible_v25] deps = {[testenv]deps} @@ -64,6 +43,11 @@ deps = {[testenv]deps} ansible>=2.6.0,<2.7.0 +[testenv:ansible_v27] +deps = + {[testenv]deps} + ansible>=2.7.0,<2.8.0 + [testenv:ansible_devel] deps = {[testenv]deps} From 62e7fc0c3c054752fdc0defc87e9a23629fc9f09 Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Mon, 11 Feb 2019 17:09:58 -0800 Subject: [PATCH 05/12] Update host_manager to test with ansible-2.8 --- pytest_ansible/host_manager/__init__.py | 5 ++++- pytest_ansible/host_manager/v28.py | 27 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 pytest_ansible/host_manager/v28.py diff --git a/pytest_ansible/host_manager/__init__.py b/pytest_ansible/host_manager/__init__.py index 62e5bb36..14cd155c 100644 --- a/pytest_ansible/host_manager/__init__.py +++ b/pytest_ansible/host_manager/__init__.py @@ -7,6 +7,7 @@ # conditionally import ansible libraries has_ansible_v2 = parse_version(ansible.__version__) >= parse_version('2.0.0') has_ansible_v24 = parse_version(ansible.__version__) >= parse_version('2.4.0') +has_ansible_v28 = parse_version(ansible.__version__) >= parse_version('2.8.0') log = get_logger(__name__) @@ -102,7 +103,9 @@ def get_host_manager(*args, **kwargs): """Initialize and return a HostManager instance.""" log.debug("get_host_manager(%s, %s)" % (args, kwargs)) - if has_ansible_v24: + if has_ansible_v28: + from pytest_ansible.host_manager.v28 import HostManagerV28 as HostManager + elif has_ansible_v24: from pytest_ansible.host_manager.v24 import HostManagerV24 as HostManager # from .v24 import HostManagerV24 as HostManager elif has_ansible_v2: diff --git a/pytest_ansible/host_manager/v28.py b/pytest_ansible/host_manager/v28.py new file mode 100644 index 00000000..148e2e87 --- /dev/null +++ b/pytest_ansible/host_manager/v28.py @@ -0,0 +1,27 @@ +from ansible.parsing.dataloader import DataLoader +from pytest_ansible.logger import get_logger +from pytest_ansible.host_manager import BaseHostManager +from pytest_ansible.module_dispatcher.v28 import ModuleDispatcherV28 +from ansible.vars.manager import VariableManager +from ansible.inventory.manager import InventoryManager + +log = get_logger(__name__) + + +class HostManagerV28(BaseHostManager): + + """Fixme.""" + + def __init__(self, *args, **kwargs): + """Fixme.""" + super(HostManagerV28, self).__init__(*args, **kwargs) + self._dispatcher = ModuleDispatcherV28 + + def initialize_inventory(self): + log.debug("HostManagerV28.initialize_inventory()") + self.options['loader'] = DataLoader() + self.options['inventory_manager'] = InventoryManager(loader=self.options['loader'], + sources=self.options['inventory']) + self.options['variable_manager'] = VariableManager(loader=self.options['loader'], + inventory=self.options['inventory_manager']) + # self.options['inventory_manager'].clear_caches() From 089dda8147852de27b36b69ed2293c95fd865406 Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Mon, 11 Feb 2019 17:10:23 -0800 Subject: [PATCH 06/12] Add the ability to omit tests on ansible-2.8 --- tests/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/conftest.py b/tests/conftest.py index 16d6c980..c38301de 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -68,6 +68,7 @@ def pytest_runtest_setup(item): if isinstance(item, pytest.Function): has_ansible_v1 = parse_version(ansible.__version__) < parse_version('2.0.0') has_ansible_v24 = parse_version(ansible.__version__) >= parse_version('2.4.0') + has_ansible_v28 = parse_version(ansible.__version__) >= parse_version('2.8.0') # conditionally skip if item.get_closest_marker('requires_ansible_v1') and not has_ansible_v1: From 9c8c03634e5b74fe6e6dc08b20a19555bc86ebdf Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Mon, 11 Feb 2019 17:16:57 -0800 Subject: [PATCH 07/12] Also update travis for new builds --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0add32bd..d808cb17 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,9 +5,9 @@ python: - "2.7" - "3.6" env: - - TOXENV=ansible_v24 - TOXENV=ansible_v25 - TOXENV=ansible_v26 + - TOXENV=ansible_v27 - TOXENV=ansible_devel install: - pip install -r test-requirements.txt From e06f445539fff0bfdd11e1dc687db43e1618ed78 Mon Sep 17 00:00:00 2001 From: Elijah DeLee Date: Tue, 12 Feb 2019 10:42:45 -0500 Subject: [PATCH 08/12] Get tests working and lint happy --- pytest_ansible/host_manager/__init__.py | 2 +- pytest_ansible/module_dispatcher/v28.py | 3 +-- tests/conftest.py | 2 +- tests/test_adhoc.py | 5 +++++ tests/test_params.py | 9 ++++++++- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/pytest_ansible/host_manager/__init__.py b/pytest_ansible/host_manager/__init__.py index 14cd155c..dfd686ce 100644 --- a/pytest_ansible/host_manager/__init__.py +++ b/pytest_ansible/host_manager/__init__.py @@ -7,7 +7,7 @@ # conditionally import ansible libraries has_ansible_v2 = parse_version(ansible.__version__) >= parse_version('2.0.0') has_ansible_v24 = parse_version(ansible.__version__) >= parse_version('2.4.0') -has_ansible_v28 = parse_version(ansible.__version__) >= parse_version('2.8.0') +has_ansible_v28 = parse_version(ansible.__version__) >= parse_version('2.8.0.dev0') log = get_logger(__name__) diff --git a/pytest_ansible/module_dispatcher/v28.py b/pytest_ansible/module_dispatcher/v28.py index f1157770..a1a50a43 100644 --- a/pytest_ansible/module_dispatcher/v28.py +++ b/pytest_ansible/module_dispatcher/v28.py @@ -7,14 +7,13 @@ from ansible.plugins.callback import CallbackBase from ansible.executor.task_queue_manager import TaskQueueManager from ansible.playbook.play import Play -from ansible.cli import CLI from ansible.cli.adhoc import AdHocCLI from pytest_ansible.logger import get_logger from pytest_ansible.module_dispatcher.v2 import ModuleDispatcherV2 from pytest_ansible.results import AdHocResult from pytest_ansible.errors import AnsibleConnectionFailure -has_ansible_v28 = parse_version(ansible.__version__) >= parse_version('2.8.0') +has_ansible_v28 = parse_version(ansible.__version__) >= parse_version('2.8.0.dev0') if not has_ansible_v28: raise ImportError("Only supported with ansible-2.8 and newer") diff --git a/tests/conftest.py b/tests/conftest.py index c38301de..fa8cb142 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -68,7 +68,7 @@ def pytest_runtest_setup(item): if isinstance(item, pytest.Function): has_ansible_v1 = parse_version(ansible.__version__) < parse_version('2.0.0') has_ansible_v24 = parse_version(ansible.__version__) >= parse_version('2.4.0') - has_ansible_v28 = parse_version(ansible.__version__) >= parse_version('2.8.0') + has_ansible_v28 = parse_version(ansible.__version__) >= parse_version('2.8.0.dev0') # conditionally skip if item.get_closest_marker('requires_ansible_v1') and not has_ansible_v1: diff --git a/tests/test_adhoc.py b/tests/test_adhoc.py index bbdf7e9d..ec414d69 100644 --- a/tests/test_adhoc.py +++ b/tests/test_adhoc.py @@ -123,6 +123,11 @@ def test_func(ansible_module): assert len(contacted) == len(ansible_module) for result in contacted.values(): # Assert test failed as expected + if parse_version(ansible.__version__) >= parse_version('2.8.0.dev0'): + assert 'sudo: a password is required' in result['module_stderr'] + assert 'MODULE FAILURE' in result['msg'] + return + if parse_version(ansible.__version__) < parse_version('2.4.0'): assert 'failed' in result, "Missing expected field in JSON response: failed" assert result['failed'], "Test did not fail as expected" diff --git a/tests/test_params.py b/tests/test_params.py index bc994ef3..5e3de135 100644 --- a/tests/test_params.py +++ b/tests/test_params.py @@ -1,7 +1,10 @@ import sys import pytest import ansible -import mock +try: + import mock +except ImportError: + from unittest import mock import re from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED, EXIT_INTERRUPTED from pkg_resources import parse_version @@ -206,6 +209,10 @@ def test_func(ansible_module): @pytest.mark.requires_ansible_v24 +@pytest.mark.skipif( + parse_version(ansible.__version__) >= parse_version('2.8.0.dev0'), + reason="requires ansible < 2.8" +) def test_params_required_with_bogus_inventory_v24(testdir, option, recwarn): src = """ import pytest From 00cc92e0a66523205302704bf745c68d7bc31da0 Mon Sep 17 00:00:00 2001 From: Elijah DeLee Date: Tue, 12 Feb 2019 12:11:24 -0500 Subject: [PATCH 09/12] For ansible 2.8+ need to clear command line args Because ansible is creating an immutable global Singleton context with the results of command line parsing. So you have to "reset" that first, before doing it again in a test. Do this in a try/except block to keep backwards compatablity --- tests/conftest.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index fa8cb142..0b3d57e4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,11 @@ import pytest import logging from pkg_resources import parse_version +try: + from ansible.utils import context_objects as co +except ImportError: + # if it does not exist because of old version of ansible, we don't need it + co = None pytest_plugins = 'pytester', @@ -125,6 +130,12 @@ def args(self): args.append('native') return args +@pytest.fixture(autouse=True) +def clear_global_context(): + # Reset the stored command line args + # if context object does not exist because of old version of ansible, we don't need it + if co is not None: + co.GlobalCLIArgs._Singleton__instance = None @pytest.fixture() def option(request, testdir): From ceaee91fc4deca02f56956eff564089503df1636 Mon Sep 17 00:00:00 2001 From: Elijah DeLee Date: Wed, 13 Feb 2019 09:37:26 -0500 Subject: [PATCH 10/12] make version comparison more flexible to handle 2.8 after release --- pytest_ansible/host_manager/__init__.py | 7 ++++++- pytest_ansible/module_dispatcher/v28.py | 7 ++++++- tests/conftest.py | 8 +++++++- tests/test_adhoc.py | 5 +++++ tests/test_params.py | 8 +++++++- tox.ini | 2 +- 6 files changed, 32 insertions(+), 5 deletions(-) diff --git a/pytest_ansible/host_manager/__init__.py b/pytest_ansible/host_manager/__init__.py index dfd686ce..3dba19a4 100644 --- a/pytest_ansible/host_manager/__init__.py +++ b/pytest_ansible/host_manager/__init__.py @@ -7,7 +7,12 @@ # conditionally import ansible libraries has_ansible_v2 = parse_version(ansible.__version__) >= parse_version('2.0.0') has_ansible_v24 = parse_version(ansible.__version__) >= parse_version('2.4.0') -has_ansible_v28 = parse_version(ansible.__version__) >= parse_version('2.8.0.dev0') + + +def has_ansible_v28(): + """Requires some extra massaging because the dev version does not play nice""" + return parse_version(ansible.__version__) >= parse_version('2.8.0.dev0') \ + or parse_version(ansible.__version__) >= parse_version('2.8.0') log = get_logger(__name__) diff --git a/pytest_ansible/module_dispatcher/v28.py b/pytest_ansible/module_dispatcher/v28.py index a1a50a43..c41a7c2e 100644 --- a/pytest_ansible/module_dispatcher/v28.py +++ b/pytest_ansible/module_dispatcher/v28.py @@ -13,7 +13,12 @@ from pytest_ansible.results import AdHocResult from pytest_ansible.errors import AnsibleConnectionFailure -has_ansible_v28 = parse_version(ansible.__version__) >= parse_version('2.8.0.dev0') + +def has_ansible_v28(): + """Requires some extra massaging because the dev version does not play nice""" + return parse_version(ansible.__version__) >= parse_version('2.8.0.dev0') \ + or parse_version(ansible.__version__) >= parse_version('2.8.0') + if not has_ansible_v28: raise ImportError("Only supported with ansible-2.8 and newer") diff --git a/tests/conftest.py b/tests/conftest.py index 0b3d57e4..a3d80590 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -73,7 +73,13 @@ def pytest_runtest_setup(item): if isinstance(item, pytest.Function): has_ansible_v1 = parse_version(ansible.__version__) < parse_version('2.0.0') has_ansible_v24 = parse_version(ansible.__version__) >= parse_version('2.4.0') - has_ansible_v28 = parse_version(ansible.__version__) >= parse_version('2.8.0.dev0') + + + def has_ansible_v28(): + """Requires some extra massaging because the dev version does not play nice""" + return parse_version(ansible.__version__) >= parse_version('2.8.0.dev0') \ + or parse_version(ansible.__version__) >= parse_version('2.8.0') + # conditionally skip if item.get_closest_marker('requires_ansible_v1') and not has_ansible_v1: diff --git a/tests/test_adhoc.py b/tests/test_adhoc.py index ec414d69..b2a1be0b 100644 --- a/tests/test_adhoc.py +++ b/tests/test_adhoc.py @@ -128,6 +128,11 @@ def test_func(ansible_module): assert 'MODULE FAILURE' in result['msg'] return + if parse_version(ansible.__version__) >= parse_version('2.8.0'): + assert 'sudo: a password is required' in result['module_stderr'] + assert 'MODULE FAILURE' in result['msg'] + return + if parse_version(ansible.__version__) < parse_version('2.4.0'): assert 'failed' in result, "Missing expected field in JSON response: failed" assert result['failed'], "Test did not fail as expected" diff --git a/tests/test_params.py b/tests/test_params.py index 5e3de135..18a686b9 100644 --- a/tests/test_params.py +++ b/tests/test_params.py @@ -15,6 +15,12 @@ import builtins # NOQA +def has_ansible_v28(): + """Requires some extra massaging because the dev version does not play nice""" + return parse_version(ansible.__version__) >= parse_version('2.8.0.dev0') \ + or parse_version(ansible.__version__) >= parse_version('2.8.0') + + def test_plugin_help(testdir): """Verifies expected output from of py.test --help""" @@ -210,7 +216,7 @@ def test_func(ansible_module): @pytest.mark.requires_ansible_v24 @pytest.mark.skipif( - parse_version(ansible.__version__) >= parse_version('2.8.0.dev0'), + has_ansible_v28(), reason="requires ansible < 2.8" ) def test_params_required_with_bogus_inventory_v24(testdir, option, recwarn): diff --git a/tox.ini b/tox.ini index 63a42afe..8ebaf68d 100644 --- a/tox.ini +++ b/tox.ini @@ -54,7 +54,7 @@ deps = -egit+https://github.com/ansible/ansible.git@devel#egg=ansible [pytest] -minversion = 2.8 +minversion = 4.2 addopts = -v --tb=native [pylama] From 80db0163e5d2c6ec1abf91bc65187be276edfe15 Mon Sep 17 00:00:00 2001 From: Elijah DeLee Date: Wed, 13 Feb 2019 10:13:13 -0500 Subject: [PATCH 11/12] Refactor has_ansible_version use --- pytest_ansible/has_version.py | 8 ++++++++ pytest_ansible/host_manager/__init__.py | 15 +++++---------- pytest_ansible/module_dispatcher/v1.py | 3 +-- pytest_ansible/module_dispatcher/v2.py | 4 +--- pytest_ansible/module_dispatcher/v24.py | 7 ++++--- pytest_ansible/module_dispatcher/v28.py | 8 +------- tests/conftest.py | 20 ++++++++------------ tests/test_params.py | 11 +++-------- 8 files changed, 31 insertions(+), 45 deletions(-) create mode 100644 pytest_ansible/has_version.py diff --git a/pytest_ansible/has_version.py b/pytest_ansible/has_version.py new file mode 100644 index 00000000..ef4c47d4 --- /dev/null +++ b/pytest_ansible/has_version.py @@ -0,0 +1,8 @@ +import ansible +from pkg_resources import parse_version + +has_ansible_v1 = parse_version(ansible.__version__) < parse_version('2.0.0') +has_ansible_v2 = parse_version(ansible.__version__) >= parse_version('2.0.0') +has_ansible_v24 = parse_version(ansible.__version__) >= parse_version('2.4.0') +has_ansible_v28 = parse_version(ansible.__version__) >= parse_version('2.8.0.dev0') \ + or parse_version(ansible.__version__) >= parse_version('2.8.0') diff --git a/pytest_ansible/host_manager/__init__.py b/pytest_ansible/host_manager/__init__.py index 3dba19a4..8f1af318 100644 --- a/pytest_ansible/host_manager/__init__.py +++ b/pytest_ansible/host_manager/__init__.py @@ -1,18 +1,13 @@ """Fixme.""" import ansible -from pkg_resources import parse_version from pytest_ansible.logger import get_logger +from pytest_ansible.has_version import ( + has_ansible_v2, + has_ansible_v24, + has_ansible_v28, +) -# conditionally import ansible libraries -has_ansible_v2 = parse_version(ansible.__version__) >= parse_version('2.0.0') -has_ansible_v24 = parse_version(ansible.__version__) >= parse_version('2.4.0') - - -def has_ansible_v28(): - """Requires some extra massaging because the dev version does not play nice""" - return parse_version(ansible.__version__) >= parse_version('2.8.0.dev0') \ - or parse_version(ansible.__version__) >= parse_version('2.8.0') log = get_logger(__name__) diff --git a/pytest_ansible/module_dispatcher/v1.py b/pytest_ansible/module_dispatcher/v1.py index 5e254a81..a80e7a4c 100644 --- a/pytest_ansible/module_dispatcher/v1.py +++ b/pytest_ansible/module_dispatcher/v1.py @@ -4,14 +4,13 @@ import ansible.utils import ansible.errors -from pkg_resources import parse_version from ansible.runner import Runner from pytest_ansible.logger import get_logger from pytest_ansible.module_dispatcher import BaseModuleDispatcher from pytest_ansible.errors import AnsibleConnectionFailure from pytest_ansible.results import AdHocResult +from pytest_ansible.has_version import has_ansible_v1 -has_ansible_v1 = parse_version(ansible.__version__) < parse_version('2.0.0') if not has_ansible_v1: raise ImportError("Only supported with ansible < 2.0") diff --git a/pytest_ansible/module_dispatcher/v2.py b/pytest_ansible/module_dispatcher/v2.py index 11d3c68b..71bd0735 100644 --- a/pytest_ansible/module_dispatcher/v2.py +++ b/pytest_ansible/module_dispatcher/v2.py @@ -3,7 +3,6 @@ import ansible.utils import ansible.errors -from pkg_resources import parse_version from ansible.plugins.callback import CallbackBase from ansible.executor.task_queue_manager import TaskQueueManager from ansible.playbook.play import Play @@ -13,8 +12,7 @@ from pytest_ansible.module_dispatcher import BaseModuleDispatcher from pytest_ansible.results import AdHocResult from pytest_ansible.errors import AnsibleConnectionFailure - -has_ansible_v2 = parse_version(ansible.__version__) >= parse_version('2.0.0') +from pytest_ansible.has_version import has_ansible_v2 if not has_ansible_v2: diff --git a/pytest_ansible/module_dispatcher/v24.py b/pytest_ansible/module_dispatcher/v24.py index 341a075d..5a215832 100644 --- a/pytest_ansible/module_dispatcher/v24.py +++ b/pytest_ansible/module_dispatcher/v24.py @@ -3,7 +3,6 @@ import ansible.utils import ansible.errors -from pkg_resources import parse_version from ansible.plugins.callback import CallbackBase from ansible.executor.task_queue_manager import TaskQueueManager from ansible.playbook.play import Play @@ -12,10 +11,12 @@ from pytest_ansible.module_dispatcher.v2 import ModuleDispatcherV2 from pytest_ansible.results import AdHocResult from pytest_ansible.errors import AnsibleConnectionFailure - -has_ansible_v24 = parse_version(ansible.__version__) >= parse_version('2.4.0') +from pytest_ansible.has_version import ( + has_ansible_v24, +) if not has_ansible_v24: + raise ImportError("Only supported with ansible-2.4 and newer") else: from ansible.plugins.loader import module_loader diff --git a/pytest_ansible/module_dispatcher/v28.py b/pytest_ansible/module_dispatcher/v28.py index c41a7c2e..1a063be8 100644 --- a/pytest_ansible/module_dispatcher/v28.py +++ b/pytest_ansible/module_dispatcher/v28.py @@ -3,7 +3,6 @@ import ansible.utils import ansible.errors -from pkg_resources import parse_version from ansible.plugins.callback import CallbackBase from ansible.executor.task_queue_manager import TaskQueueManager from ansible.playbook.play import Play @@ -12,12 +11,7 @@ from pytest_ansible.module_dispatcher.v2 import ModuleDispatcherV2 from pytest_ansible.results import AdHocResult from pytest_ansible.errors import AnsibleConnectionFailure - - -def has_ansible_v28(): - """Requires some extra massaging because the dev version does not play nice""" - return parse_version(ansible.__version__) >= parse_version('2.8.0.dev0') \ - or parse_version(ansible.__version__) >= parse_version('2.8.0') +from pytest_ansible.has_version import has_ansible_v28 if not has_ansible_v28: diff --git a/tests/conftest.py b/tests/conftest.py index a3d80590..19697406 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,9 @@ -import ansible import pytest import logging -from pkg_resources import parse_version +from pytest_ansible.has_version import ( + has_ansible_v1, + has_ansible_v24, +) try: from ansible.utils import context_objects as co except ImportError: @@ -71,16 +73,6 @@ def pytest_runtest_setup(item): # Conditionally skip tests that are pinned to a specific ansible version if isinstance(item, pytest.Function): - has_ansible_v1 = parse_version(ansible.__version__) < parse_version('2.0.0') - has_ansible_v24 = parse_version(ansible.__version__) >= parse_version('2.4.0') - - - def has_ansible_v28(): - """Requires some extra massaging because the dev version does not play nice""" - return parse_version(ansible.__version__) >= parse_version('2.8.0.dev0') \ - or parse_version(ansible.__version__) >= parse_version('2.8.0') - - # conditionally skip if item.get_closest_marker('requires_ansible_v1') and not has_ansible_v1: pytest.skip("requires < ansible-2.*") @@ -88,6 +80,8 @@ def has_ansible_v28(): pytest.skip("requires >= ansible-2.*") if item.get_closest_marker('requires_ansible_v24') and not has_ansible_v24: pytest.skip("requires >= ansible-2.4.*") + if item.get_closest_marker('requires_ansible_v28') and not has_ansible_v24: + pytest.skip("requires >= ansible-2.8.*") # conditionally xfail mark = item.get_closest_marker('ansible_v1_xfail') @@ -136,6 +130,7 @@ def args(self): args.append('native') return args + @pytest.fixture(autouse=True) def clear_global_context(): # Reset the stored command line args @@ -143,6 +138,7 @@ def clear_global_context(): if co is not None: co.GlobalCLIArgs._Singleton__instance = None + @pytest.fixture() def option(request, testdir): '''Returns an instance of PyTestOption to help tests pass parameters and diff --git a/tests/test_params.py b/tests/test_params.py index 18a686b9..3d8cbce9 100644 --- a/tests/test_params.py +++ b/tests/test_params.py @@ -1,13 +1,14 @@ import sys import pytest import ansible +from pkg_resources import parse_version +from pytest_ansible.has_version import has_ansible_v28 try: import mock except ImportError: from unittest import mock import re from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED, EXIT_INTERRUPTED -from pkg_resources import parse_version if sys.version_info[0] == 2: import __builtin__ as builtins # NOQA @@ -15,12 +16,6 @@ import builtins # NOQA -def has_ansible_v28(): - """Requires some extra massaging because the dev version does not play nice""" - return parse_version(ansible.__version__) >= parse_version('2.8.0.dev0') \ - or parse_version(ansible.__version__) >= parse_version('2.8.0') - - def test_plugin_help(testdir): """Verifies expected output from of py.test --help""" @@ -216,7 +211,7 @@ def test_func(ansible_module): @pytest.mark.requires_ansible_v24 @pytest.mark.skipif( - has_ansible_v28(), + has_ansible_v28, reason="requires ansible < 2.8" ) def test_params_required_with_bogus_inventory_v24(testdir, option, recwarn): From f53f2484d58c18a02c9839cc92b0c89467cc1cd8 Mon Sep 17 00:00:00 2001 From: Elijah DeLee Date: Wed, 13 Feb 2019 11:12:48 -0500 Subject: [PATCH 12/12] try and make travis happy --- tests/test_adhoc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_adhoc.py b/tests/test_adhoc.py index b2a1be0b..b752425c 100644 --- a/tests/test_adhoc.py +++ b/tests/test_adhoc.py @@ -124,12 +124,12 @@ def test_func(ansible_module): for result in contacted.values(): # Assert test failed as expected if parse_version(ansible.__version__) >= parse_version('2.8.0.dev0'): - assert 'sudo: a password is required' in result['module_stderr'] + assert 'msg' in result, "Missing expected field in JSON response: msg" assert 'MODULE FAILURE' in result['msg'] return if parse_version(ansible.__version__) >= parse_version('2.8.0'): - assert 'sudo: a password is required' in result['module_stderr'] + assert 'msg' in result, "Missing expected field in JSON response: msg" assert 'MODULE FAILURE' in result['msg'] return