summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorLee Garrett <lgarrett@rocketjump.eu>2023-07-18 13:23:44 +0200
committerLee Garrett <lgarrett@rocketjump.eu>2023-07-18 13:23:44 +0200
commitaff27d44d75c760b1288814b4948fc2c4a937d6e (patch)
treecf44068a623e41bae78b03c7ee52e2e35273c20f /test
parenta00ca87e07387d5be8152f7e1d2a69701f9949d6 (diff)
downloaddebian-ansible-core-aff27d44d75c760b1288814b4948fc2c4a937d6e.zip
New upstream version 2.14.8
Diffstat (limited to 'test')
-rw-r--r--test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/failure/README.md (renamed from test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/failure/README.rst)0
-rw-r--r--test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/failure/galaxy.yml2
-rw-r--r--test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/ps_only/README.md (renamed from test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/ps_only/README.rst)0
-rw-r--r--test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/ps_only/galaxy.yml2
-rw-r--r--test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/README.md (renamed from test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/README.rst)0
-rw-r--r--test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/galaxy.yml2
-rw-r--r--test/integration/targets/dnf/tasks/main.yml1
-rw-r--r--test/integration/targets/dnf/vars/RedHat-9.yml5
-rw-r--r--test/integration/targets/reboot/tasks/main.yml1
-rw-r--r--test/integration/targets/reboot/tasks/test_reboot_command.yml22
-rw-r--r--test/lib/ansible_test/_internal/__init__.py7
-rw-r--r--test/lib/ansible_test/_internal/commands/integration/cloud/__init__.py4
-rw-r--r--test/lib/ansible_test/_internal/commands/sanity/validate_modules.py4
-rw-r--r--test/lib/ansible_test/_internal/containers.py4
-rw-r--r--test/lib/ansible_test/_internal/coverage_util.py4
-rw-r--r--test/lib/ansible_test/_internal/payload.py4
-rw-r--r--test/lib/ansible_test/_internal/provisioning.py7
-rw-r--r--test/lib/ansible_test/_internal/pypi_proxy.py8
-rw-r--r--test/lib/ansible_test/_internal/util_common.py44
-rw-r--r--test/units/cli/galaxy/test_collection_extract_tar.py15
-rw-r--r--test/units/plugins/action/test_reboot.py214
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