diff options
author | Lee Garrett <lgarrett@rocketjump.eu> | 2023-07-18 13:23:44 +0200 |
---|---|---|
committer | Lee Garrett <lgarrett@rocketjump.eu> | 2023-07-18 13:23:44 +0200 |
commit | aff27d44d75c760b1288814b4948fc2c4a937d6e (patch) | |
tree | cf44068a623e41bae78b03c7ee52e2e35273c20f /test | |
parent | a00ca87e07387d5be8152f7e1d2a69701f9949d6 (diff) | |
download | debian-ansible-core-aff27d44d75c760b1288814b4948fc2c4a937d6e.zip |
New upstream version 2.14.8
Diffstat (limited to 'test')
21 files changed, 285 insertions, 65 deletions
diff --git a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/failure/README.rst b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/failure/README.md index bf1003fa..bf1003fa 100644 --- a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/failure/README.rst +++ b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/failure/README.md diff --git a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/failure/galaxy.yml b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/failure/galaxy.yml index 3b116713..fcb47c6b 100644 --- a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/failure/galaxy.yml +++ b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/failure/galaxy.yml @@ -1,6 +1,6 @@ namespace: ns name: failure version: 1.0.0 -readme: README.rst +readme: README.md authors: - Ansible diff --git a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/ps_only/README.rst b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/ps_only/README.md index bbdd5138..bbdd5138 100644 --- a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/ps_only/README.rst +++ b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/ps_only/README.md diff --git a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/ps_only/galaxy.yml b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/ps_only/galaxy.yml index 0a78b9e1..96fddb7f 100644 --- a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/ps_only/galaxy.yml +++ b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/ps_only/galaxy.yml @@ -1,6 +1,6 @@ namespace: ns name: ps_only version: 1.0.0 -readme: README.rst +readme: README.md authors: - Ansible diff --git a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/README.rst b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/README.md index d8138d3b..d8138d3b 100644 --- a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/README.rst +++ b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/README.md diff --git a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/galaxy.yml b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/galaxy.yml index 08a32e80..cb5403f1 100644 --- a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/galaxy.yml +++ b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/galaxy.yml @@ -1,6 +1,6 @@ namespace: ns name: col version: 1.0.0 -readme: README.rst +readme: README.md authors: - Ansible diff --git a/test/integration/targets/dnf/tasks/main.yml b/test/integration/targets/dnf/tasks/main.yml index 66a171ac..65b77ceb 100644 --- a/test/integration/targets/dnf/tasks/main.yml +++ b/test/integration/targets/dnf/tasks/main.yml @@ -59,7 +59,6 @@ - include_tasks: modularity.yml when: - - astream_name is defined - (ansible_distribution == 'Fedora' and ansible_distribution_major_version is version('29', '>=')) or (ansible_distribution in ['RedHat', 'CentOS'] and ansible_distribution_major_version is version('8', '>=')) tags: diff --git a/test/integration/targets/dnf/vars/RedHat-9.yml b/test/integration/targets/dnf/vars/RedHat-9.yml index 5681e701..680157dc 100644 --- a/test/integration/targets/dnf/vars/RedHat-9.yml +++ b/test/integration/targets/dnf/vars/RedHat-9.yml @@ -1,3 +1,2 @@ -# RHEL9.0 contains no modules, to be re-introduced in 9.1 -# astream_name: '@container-tools:latest/common' -# astream_name_no_stream: '@container-tools/common' +astream_name: '@php:8.1/minimal' +astream_name_no_stream: '@php/minimal' diff --git a/test/integration/targets/reboot/tasks/main.yml b/test/integration/targets/reboot/tasks/main.yml index 4884f104..e92c2662 100644 --- a/test/integration/targets/reboot/tasks/main.yml +++ b/test/integration/targets/reboot/tasks/main.yml @@ -37,7 +37,6 @@ when: not in_container_env and in_split_controller_mode block: - import_tasks: test_standard_scenarios.yml - - import_tasks: test_reboot_command.yml - import_tasks: test_invalid_parameter.yml - import_tasks: test_invalid_test_command.yml - import_tasks: test_molly_guard.yml diff --git a/test/integration/targets/reboot/tasks/test_reboot_command.yml b/test/integration/targets/reboot/tasks/test_reboot_command.yml deleted file mode 100644 index 779d380b..00000000 --- a/test/integration/targets/reboot/tasks/test_reboot_command.yml +++ /dev/null @@ -1,22 +0,0 @@ -- import_tasks: get_boot_time.yml -- name: Reboot with custom reboot_command using unqualified path - reboot: - reboot_command: reboot - register: reboot_result -- import_tasks: check_reboot.yml - - -- import_tasks: get_boot_time.yml -- name: Reboot with custom reboot_command using absolute path - reboot: - reboot_command: /sbin/reboot - register: reboot_result -- import_tasks: check_reboot.yml - - -- import_tasks: get_boot_time.yml -- name: Reboot with custom reboot_command with parameters - reboot: - reboot_command: shutdown -r now - register: reboot_result -- import_tasks: check_reboot.yml diff --git a/test/lib/ansible_test/_internal/__init__.py b/test/lib/ansible_test/_internal/__init__.py index ee24a852..35584746 100644 --- a/test/lib/ansible_test/_internal/__init__.py +++ b/test/lib/ansible_test/_internal/__init__.py @@ -43,6 +43,7 @@ from .data import ( from .util_common import ( CommonConfig, + ExitHandler, ) from .cli import ( @@ -59,6 +60,12 @@ from .config import ( def main(cli_args: t.Optional[list[str]] = None) -> None: + """Wrapper around the main program function to invoke cleanup functions at exit.""" + with ExitHandler.context(): + main_internal(cli_args) + + +def main_internal(cli_args: t.Optional[list[str]] = None) -> None: """Main program function.""" try: os.chdir(data_context().content.root) diff --git a/test/lib/ansible_test/_internal/commands/integration/cloud/__init__.py b/test/lib/ansible_test/_internal/commands/integration/cloud/__init__.py index eac9265a..cf920bc9 100644 --- a/test/lib/ansible_test/_internal/commands/integration/cloud/__init__.py +++ b/test/lib/ansible_test/_internal/commands/integration/cloud/__init__.py @@ -2,7 +2,6 @@ from __future__ import annotations import abc -import atexit import datetime import os import re @@ -28,6 +27,7 @@ from ....util import ( ) from ....util_common import ( + ExitHandler, ResultType, write_json_test_results, ) @@ -306,7 +306,7 @@ class CloudProvider(CloudBase): self.resource_prefix = self.ci_provider.generate_resource_prefix() self.resource_prefix = re.sub(r'[^a-zA-Z0-9]+', '-', self.resource_prefix)[:63].lower().rstrip('-') - atexit.register(self.cleanup) + ExitHandler.register(self.cleanup) def cleanup(self) -> None: """Clean up the cloud resource and any temporary configuration files after tests complete.""" diff --git a/test/lib/ansible_test/_internal/commands/sanity/validate_modules.py b/test/lib/ansible_test/_internal/commands/sanity/validate_modules.py index ab7dd93c..72616e77 100644 --- a/test/lib/ansible_test/_internal/commands/sanity/validate_modules.py +++ b/test/lib/ansible_test/_internal/commands/sanity/validate_modules.py @@ -1,7 +1,6 @@ """Sanity test using validate-modules.""" from __future__ import annotations -import atexit import collections import contextlib import json @@ -37,6 +36,7 @@ from ...util import ( ) from ...util_common import ( + ExitHandler, process_scoped_temporary_directory, run_command, ResultType, @@ -237,7 +237,7 @@ class ValidateModulesTest(SanitySingleVersion): files = payload_config.files files.append((path, os.path.relpath(path, data_context().content.root))) - atexit.register(cleanup) + ExitHandler.register(cleanup) data_context().register_payload_callback(git_callback) make_dirs(os.path.dirname(path)) diff --git a/test/lib/ansible_test/_internal/containers.py b/test/lib/ansible_test/_internal/containers.py index bfc36434..869f1fba 100644 --- a/test/lib/ansible_test/_internal/containers.py +++ b/test/lib/ansible_test/_internal/containers.py @@ -1,7 +1,6 @@ """High level functions for working with containers.""" from __future__ import annotations -import atexit import collections.abc as c import contextlib import enum @@ -20,6 +19,7 @@ from .util import ( ) from .util_common import ( + ExitHandler, named_temporary_file, ) @@ -225,7 +225,7 @@ def run_support_container( raise Exception(f'Container already defined: {name}') if not support_containers: - atexit.register(cleanup_containers, args) + ExitHandler.register(cleanup_containers, args) support_containers[name] = descriptor diff --git a/test/lib/ansible_test/_internal/coverage_util.py b/test/lib/ansible_test/_internal/coverage_util.py index 0af1cac4..ae640249 100644 --- a/test/lib/ansible_test/_internal/coverage_util.py +++ b/test/lib/ansible_test/_internal/coverage_util.py @@ -1,7 +1,6 @@ """Utility code for facilitating collection of code coverage when running tests.""" from __future__ import annotations -import atexit import dataclasses import os import sqlite3 @@ -34,6 +33,7 @@ from .data import ( ) from .util_common import ( + ExitHandler, intercept_python, ResultType, ) @@ -223,7 +223,7 @@ def get_coverage_config(args: TestConfig) -> str: temp_dir = '/tmp/coverage-temp-dir' else: temp_dir = tempfile.mkdtemp() - atexit.register(lambda: remove_tree(temp_dir)) + ExitHandler.register(lambda: remove_tree(temp_dir)) path = os.path.join(temp_dir, COVERAGE_CONFIG_NAME) diff --git a/test/lib/ansible_test/_internal/payload.py b/test/lib/ansible_test/_internal/payload.py index 10dde7b8..ab9739b4 100644 --- a/test/lib/ansible_test/_internal/payload.py +++ b/test/lib/ansible_test/_internal/payload.py @@ -1,7 +1,6 @@ """Payload management for sending Ansible files and test content to other systems (VMs, containers).""" from __future__ import annotations -import atexit import os import stat import tarfile @@ -32,6 +31,7 @@ from .data import ( from .util_common import ( CommonConfig, + ExitHandler, ) # improve performance by disabling uid/gid lookups @@ -192,7 +192,7 @@ def create_temporary_bin_files(args: CommonConfig) -> tuple[tuple[str, str], ... temp_path = '/tmp/ansible-tmp-bin' else: temp_path = tempfile.mkdtemp(prefix='ansible', suffix='bin') - atexit.register(remove_tree, temp_path) + ExitHandler.register(remove_tree, temp_path) for name, dest in ANSIBLE_BIN_SYMLINK_MAP.items(): path = os.path.join(temp_path, name) diff --git a/test/lib/ansible_test/_internal/provisioning.py b/test/lib/ansible_test/_internal/provisioning.py index e7f0fd31..4710757b 100644 --- a/test/lib/ansible_test/_internal/provisioning.py +++ b/test/lib/ansible_test/_internal/provisioning.py @@ -1,7 +1,6 @@ """Provision hosts for running tests.""" from __future__ import annotations -import atexit import collections.abc as c import dataclasses import functools @@ -27,6 +26,10 @@ from .util import ( type_guard, ) +from .util_common import ( + ExitHandler, +) + from .thread import ( WrappedThread, ) @@ -124,7 +127,7 @@ def prepare_profiles( raise PrimeContainers() - atexit.register(functools.partial(cleanup_profiles, host_state)) + ExitHandler.register(functools.partial(cleanup_profiles, host_state)) def provision(profile: HostProfile) -> None: """Provision the given profile.""" diff --git a/test/lib/ansible_test/_internal/pypi_proxy.py b/test/lib/ansible_test/_internal/pypi_proxy.py index 97663ead..5380dd9b 100644 --- a/test/lib/ansible_test/_internal/pypi_proxy.py +++ b/test/lib/ansible_test/_internal/pypi_proxy.py @@ -1,7 +1,6 @@ """PyPI proxy management.""" from __future__ import annotations -import atexit import os import urllib.parse @@ -23,6 +22,7 @@ from .util import ( ) from .util_common import ( + ExitHandler, process_scoped_temporary_file, ) @@ -128,7 +128,7 @@ def configure_target_pypi_proxy(args: EnvironmentConfig, profile: HostProfile, p run_playbook(args, inventory_path, 'pypi_proxy_prepare.yml', capture=True, variables=dict( pypi_endpoint=pypi_endpoint, pypi_hostname=pypi_hostname, force=force)) - atexit.register(cleanup_pypi_proxy) + ExitHandler.register(cleanup_pypi_proxy) def configure_pypi_proxy_pip(args: EnvironmentConfig, profile: HostProfile, pypi_endpoint: str, pypi_hostname: str) -> None: @@ -153,7 +153,7 @@ trusted-host = {1} if not args.explain: write_text_file(pip_conf_path, pip_conf, True) - atexit.register(pip_conf_cleanup) + ExitHandler.register(pip_conf_cleanup) def configure_pypi_proxy_easy_install(args: EnvironmentConfig, profile: HostProfile, pypi_endpoint: str) -> None: @@ -177,4 +177,4 @@ index_url = {0} if not args.explain: write_text_file(pydistutils_cfg_path, pydistutils_cfg, True) - atexit.register(pydistutils_cfg_cleanup) + ExitHandler.register(pydistutils_cfg_cleanup) diff --git a/test/lib/ansible_test/_internal/util_common.py b/test/lib/ansible_test/_internal/util_common.py index 79ff6c03..222366e4 100644 --- a/test/lib/ansible_test/_internal/util_common.py +++ b/test/lib/ansible_test/_internal/util_common.py @@ -1,7 +1,6 @@ """Common utility code that depends on CommonConfig.""" from __future__ import annotations -import atexit import collections.abc as c import contextlib import json @@ -64,6 +63,39 @@ from .host_configs import ( CHECK_YAML_VERSIONS: dict[str, t.Any] = {} +class ExitHandler: + """Simple exit handler implementation.""" + _callbacks: list[tuple[t.Callable, tuple[t.Any, ...], dict[str, t.Any]]] = [] + + @staticmethod + def register(func: t.Callable, *args, **kwargs) -> None: + """Register the given function and args as a callback to execute during program termination.""" + ExitHandler._callbacks.append((func, args, kwargs)) + + @staticmethod + @contextlib.contextmanager + def context() -> t.Generator[None, None, None]: + """Run all registered handlers when the context is exited.""" + last_exception: BaseException | None = None + + try: + yield + finally: + queue = list(ExitHandler._callbacks) + + while queue: + func, args, kwargs = queue.pop() + + try: + func(*args, **kwargs) + except BaseException as ex: # pylint: disable=broad-except + last_exception = ex + display.fatal(f'Exit handler failed: {ex}') + + if last_exception: + raise last_exception + + class ShellScriptTemplate: """A simple substitution template for shell scripts.""" @@ -211,7 +243,7 @@ def process_scoped_temporary_file(args: CommonConfig, prefix: t.Optional[str] = else: temp_fd, path = tempfile.mkstemp(prefix=prefix, suffix=suffix) os.close(temp_fd) - atexit.register(lambda: os.remove(path)) + ExitHandler.register(lambda: os.remove(path)) return path @@ -222,7 +254,7 @@ def process_scoped_temporary_directory(args: CommonConfig, prefix: t.Optional[st path = os.path.join(tempfile.gettempdir(), f'{prefix or tempfile.gettempprefix()}{generate_name()}{suffix or ""}') else: path = tempfile.mkdtemp(prefix=prefix, suffix=suffix) - atexit.register(lambda: remove_tree(path)) + ExitHandler.register(lambda: remove_tree(path)) return path @@ -296,7 +328,7 @@ def get_injector_path() -> str: """Remove the temporary injector directory.""" remove_tree(injector_path) - atexit.register(cleanup_injector) + ExitHandler.register(cleanup_injector) return injector_path @@ -354,7 +386,7 @@ def get_python_path(interpreter: str) -> str: verified_chmod(python_path, MODE_DIRECTORY) if not PYTHON_PATHS: - atexit.register(cleanup_python_paths) + ExitHandler.register(cleanup_python_paths) PYTHON_PATHS[interpreter] = python_path @@ -364,7 +396,7 @@ def get_python_path(interpreter: str) -> str: def create_temp_dir(prefix: t.Optional[str] = None, suffix: t.Optional[str] = None, base_dir: t.Optional[str] = None) -> str: """Create a temporary directory that persists until the current process exits.""" temp_path = tempfile.mkdtemp(prefix=prefix or 'tmp', suffix=suffix or '', dir=base_dir) - atexit.register(remove_tree, temp_path) + ExitHandler.register(remove_tree, temp_path) return temp_path diff --git a/test/units/cli/galaxy/test_collection_extract_tar.py b/test/units/cli/galaxy/test_collection_extract_tar.py index 526442cc..b84f60b6 100644 --- a/test/units/cli/galaxy/test_collection_extract_tar.py +++ b/test/units/cli/galaxy/test_collection_extract_tar.py @@ -14,6 +14,7 @@ from ansible.galaxy.collection import _extract_tar_dir @pytest.fixture def fake_tar_obj(mocker): m_tarfile = mocker.Mock() + m_tarfile._ansible_normalized_cache = {'/some/dir': mocker.Mock()} m_tarfile.type = mocker.Mock(return_value=b'99') m_tarfile.SYMTYPE = mocker.Mock(return_value=b'22') @@ -22,23 +23,11 @@ def fake_tar_obj(mocker): def test_extract_tar_member_trailing_sep(mocker): m_tarfile = mocker.Mock() - m_tarfile.getmember = mocker.Mock(side_effect=KeyError) + m_tarfile._ansible_normalized_cache = {} with pytest.raises(AnsibleError, match='Unable to extract'): _extract_tar_dir(m_tarfile, '/some/dir/', b'/some/dest') - assert m_tarfile.getmember.call_count == 1 - - -def test_extract_tar_member_no_trailing_sep(mocker): - m_tarfile = mocker.Mock() - m_tarfile.getmember = mocker.Mock(side_effect=KeyError) - - with pytest.raises(AnsibleError, match='Unable to extract'): - _extract_tar_dir(m_tarfile, '/some/dir', b'/some/dest') - - assert m_tarfile.getmember.call_count == 2 - def test_extract_tar_dir_exists(mocker, fake_tar_obj): mocker.patch('os.makedirs', return_value=None) diff --git a/test/units/plugins/action/test_reboot.py b/test/units/plugins/action/test_reboot.py new file mode 100644 index 00000000..36d9e12d --- /dev/null +++ b/test/units/plugins/action/test_reboot.py @@ -0,0 +1,214 @@ +# Copyright (c) 2022 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +"""Tests for the reboot action plugin.""" +import os + +import pytest + +from ansible.errors import AnsibleConnectionFailure +from ansible.playbook.task import Task +from ansible.plugins.action.reboot import ActionModule as RebootAction +from ansible.plugins.loader import connection_loader + + +@pytest.fixture +def task_args(request): + """Return playbook task args.""" + return getattr(request, 'param', {}) + + +@pytest.fixture +def module_task(mocker, task_args): + """Construct a task object.""" + task = mocker.MagicMock(Task) + task.action = 'reboot' + task.args = task_args + task.async_val = False + return task + + +@pytest.fixture +def play_context(mocker): + """Construct a play context.""" + ctx = mocker.MagicMock() + ctx.check_mode = False + ctx.shell = 'sh' + return ctx + + +@pytest.fixture +def action_plugin(play_context, module_task): + """Initialize an action plugin.""" + connection = connection_loader.get('local', play_context, os.devnull) + loader = None + templar = None + shared_loader_obj = None + + return RebootAction( + module_task, + connection, + play_context, + loader, + templar, + shared_loader_obj, + ) + + +_SENTINEL_REBOOT_COMMAND = '/reboot-command-mock --arg' +_SENTINEL_SHORT_REBOOT_COMMAND = '/reboot-command-mock' +_SENTINEL_TEST_COMMAND = 'cmd-stub' + + +@pytest.mark.parametrize( + 'task_args', + ( + { + 'reboot_timeout': 5, + 'reboot_command': _SENTINEL_REBOOT_COMMAND, + 'test_command': _SENTINEL_TEST_COMMAND, + }, + { + 'reboot_timeout': 5, + 'reboot_command': _SENTINEL_SHORT_REBOOT_COMMAND, + 'test_command': _SENTINEL_TEST_COMMAND, + }, + ), + ids=('reboot command with spaces', 'reboot command without spaces'), + indirect=('task_args', ), +) +def test_reboot_command(action_plugin, mocker, monkeypatch, task_args): + """Check that the reboot command gets called and reboot verified.""" + def _patched_low_level_execute_command(cmd, *args, **kwargs): + return { + _SENTINEL_TEST_COMMAND: { + 'rc': 0, + 'stderr': '<test command stub-stderr>', + 'stdout': '<test command stub-stdout>', + }, + _SENTINEL_REBOOT_COMMAND: { + 'rc': 0, + 'stderr': '<reboot command stub-stderr>', + 'stdout': '<reboot command stub-stdout>', + }, + f'{_SENTINEL_SHORT_REBOOT_COMMAND} ': { # no args is concatenated + 'rc': 0, + 'stderr': '<short reboot command stub-stderr>', + 'stdout': '<short reboot command stub-stdout>', + }, + }[cmd] + + monkeypatch.setattr( + action_plugin, + '_low_level_execute_command', + _patched_low_level_execute_command, + ) + + action_plugin._connection = mocker.Mock() + + monkeypatch.setattr(action_plugin, 'check_boot_time', lambda *_a, **_kw: 5) + monkeypatch.setattr(action_plugin, 'get_distribution', mocker.MagicMock()) + monkeypatch.setattr(action_plugin, 'get_system_boot_time', lambda d: 0) + + low_level_cmd_spy = mocker.spy(action_plugin, '_low_level_execute_command') + + action_result = action_plugin.run() + + assert low_level_cmd_spy.called + + expected_reboot_command = ( + task_args['reboot_command'] if ' ' in task_args['reboot_command'] + else f'{task_args["reboot_command"] !s} ' + ) + low_level_cmd_spy.assert_any_call(expected_reboot_command, sudoable=True) + low_level_cmd_spy.assert_any_call(task_args['test_command'], sudoable=True) + + assert low_level_cmd_spy.call_count == 2 + assert low_level_cmd_spy.spy_return == { + 'rc': 0, + 'stderr': '<test command stub-stderr>', + 'stdout': '<test command stub-stdout>', + } + assert low_level_cmd_spy.spy_exception is None + + assert 'failed' not in action_result + assert action_result == {'rebooted': True, 'changed': True, 'elapsed': 0} + + +@pytest.mark.parametrize( + 'task_args', + ( + { + 'reboot_timeout': 5, + 'reboot_command': _SENTINEL_REBOOT_COMMAND, + 'test_command': _SENTINEL_TEST_COMMAND, + }, + ), + ids=('reboot command with spaces', ), + indirect=('task_args', ), +) +def test_reboot_command_connection_fail(action_plugin, mocker, monkeypatch, task_args): + """Check that the reboot command gets called and reboot verified.""" + def _patched_low_level_execute_command(cmd, *args, **kwargs): + if cmd == _SENTINEL_REBOOT_COMMAND: + raise AnsibleConnectionFailure('Fake connection drop') + return { + _SENTINEL_TEST_COMMAND: { + 'rc': 0, + 'stderr': '<test command stub-stderr>', + 'stdout': '<test command stub-stdout>', + }, + }[cmd] + + monkeypatch.setattr( + action_plugin, + '_low_level_execute_command', + _patched_low_level_execute_command, + ) + + action_plugin._connection = mocker.Mock() + + monkeypatch.setattr(action_plugin, 'check_boot_time', lambda *_a, **_kw: 5) + monkeypatch.setattr(action_plugin, 'get_distribution', mocker.MagicMock()) + monkeypatch.setattr(action_plugin, 'get_system_boot_time', lambda d: 0) + + low_level_cmd_spy = mocker.spy(action_plugin, '_low_level_execute_command') + + action_result = action_plugin.run() + + assert low_level_cmd_spy.called + + low_level_cmd_spy.assert_any_call( + task_args['reboot_command'], sudoable=True, + ) + low_level_cmd_spy.assert_any_call(task_args['test_command'], sudoable=True) + + assert low_level_cmd_spy.call_count == 2 + assert low_level_cmd_spy.spy_return == { + 'rc': 0, + 'stderr': '<test command stub-stderr>', + 'stdout': '<test command stub-stdout>', + } + + assert 'failed' not in action_result + assert action_result == {'rebooted': True, 'changed': True, 'elapsed': 0} + + +def test_reboot_connection_local(action_plugin, module_task): + """Verify that using local connection doesn't let reboot happen.""" + expected_message = ' '.join( + ( + 'Running', module_task.action, + 'with local connection would reboot the control node.', + ), + ) + expected_action_result = { + 'changed': False, + 'elapsed': 0, + 'failed': True, + 'msg': expected_message, + 'rebooted': False, + } + + action_result = action_plugin.run() + + assert action_result == expected_action_result |