File: test_handler.py

package info (click to toggle)
ansible-core 2.19.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 32,752 kB
  • sloc: python: 181,000; cs: 4,929; sh: 4,611; xml: 34; makefile: 21
file content (137 lines) | stat: -rw-r--r-- 5,678 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
from __future__ import annotations

import os

import pytest
import pytest_mock

from ansible.constants import config
from ansible.errors import AnsibleUndefinedConfigEntry
from ansible._internal._errors._handler import ErrorHandler, ErrorAction, Skippable, _SkipException
from ansible.utils.display import Display


def test_skippable_ignore_skips_body() -> None:
    """Verify that `skip_on_ignore=True` skips the body within the context manager when `action=ErrorAction.IGNORE`."""
    body_ran = False
    assert not body_ran  # satisfy static analysis which assumes the context manager body will run

    with Skippable, ErrorHandler(ErrorAction.IGNORE).handle(Exception, skip_on_ignore=True):
        body_ran = True

    assert not body_ran


def test_skippable_without_skip_on_ignore() -> None:
    """
    Verify using `Skippable` without invoking a handler with `skip_on_ignore=True` will fail.
    This protects against accidental use of `Skippable` by itself, or forgetting to use `skip_on_ignore=True` -- both of which have no effect.
    """
    body_ran = False
    assert not body_ran  # satisfy static analysis which assumes the context manager body will run

    with pytest.raises(RuntimeError) as err:
        with Skippable:
            body_ran = True

    assert body_ran
    assert 'handler was never invoked' in str(err.value)


def test_skippable_non_skip_exception() -> None:
    """Verify that `Skippable` does not interfere with exceptions."""
    ex_to_raise = RuntimeError('let me through')

    with pytest.raises(RuntimeError) as err:
        with Skippable:
            raise ex_to_raise

    assert err.value is ex_to_raise


@pytest.mark.parametrize("error_action", (ErrorAction.IGNORE, ErrorAction.WARNING, ErrorAction.ERROR))
def test_skip_on_ignore_missing_skippable(error_action: ErrorAction) -> None:
    """Verify that a `_SkipException` is raised when `skip_on_ignore=True` and no `Skippable` context was used to suppress it."""
    body_ran = False
    assert not body_ran  # satisfy static analysis which assumes the context manager body will run

    with pytest.raises(_SkipException):
        with ErrorHandler(error_action).handle(Exception, skip_on_ignore=True):
            body_ran = True

    if error_action is ErrorAction.IGNORE:
        assert not body_ran
    else:
        assert body_ran


@pytest.mark.parametrize("exception_type", (RuntimeError, NotImplementedError))
def test_ignore_success(exception_type: type[Exception]) -> None:
    """Verify that `ErrorAction.IGNORE` suppresses the specified exception types."""
    body_ran = False
    assert not body_ran  # satisfy static analysis which assumes the context manager body will run

    with ErrorHandler(ErrorAction.IGNORE).handle(RuntimeError, NotImplementedError):
        body_ran = True
        raise exception_type('should be ignored')

    assert body_ran


def test_ignore_passes_other_exceptions() -> None:
    """Verify that `ErrorAction.IGNORE` does not suppress exception types not passed to `handle`."""
    with pytest.raises(NotImplementedError):
        with ErrorHandler(ErrorAction.IGNORE).handle(TypeError, ValueError):
            raise NotImplementedError()


@pytest.mark.parametrize("exception_type", (RuntimeError, NotImplementedError))
def test_warn_success(exception_type: type[Exception], mocker: pytest_mock.MockerFixture) -> None:
    """Verify that `ErrorAction.WARNING` eats the specified error type and calls `error_as_warning` with the exception instance raised."""
    eaw = mocker.patch.object(Display(), 'error_as_warning')
    with ErrorHandler(ErrorAction.WARNING).handle(RuntimeError, NotImplementedError):
        raise exception_type()

    assert isinstance(eaw.call_args.kwargs['exception'], exception_type)


def test_warn_passes_other_exceptions(mocker: pytest_mock.MockerFixture) -> None:
    """Verify that `ErrorAction.WARNING` does not suppress exception types not passed to `handle`, and that `error_as_warning` is not called for them."""
    eaw = mocker.patch.object(Display(), 'error_as_warning')

    with pytest.raises(NotImplementedError):
        with ErrorHandler(ErrorAction.WARNING).handle(TypeError, ValueError):
            raise NotImplementedError()

    assert not eaw.called


@pytest.mark.parametrize("exception_type", (AttributeError, NotImplementedError, ValueError))
def test_fail(exception_type: type[Exception]) -> None:
    """Verify that `ErrorAction.ERROR` passes through all exception types, regardless of what was passed to `handle`."""
    with pytest.raises(exception_type):
        with ErrorHandler(ErrorAction.ERROR).handle(AttributeError, NotImplementedError):
            raise exception_type()


def test_no_exceptions_to_handle():
    """Verify that passing no exceptions to `handle` fails."""
    with pytest.raises(ValueError):
        with ErrorHandler(ErrorAction.IGNORE).handle():
            pass


@pytest.mark.parametrize("value", ('ignore', 'warning', 'error'))
def test_from_config_env_success(value: str, mocker: pytest_mock.MockerFixture) -> None:
    """Verify that `from_config` correctly creates handlers with the requested error action config string."""
    mocker.patch.dict(os.environ, dict(_ANSIBLE_CALLBACK_DISPATCH_ERROR_BEHAVIOR=value))

    assert config.get_config_value("_CALLBACK_DISPATCH_ERROR_BEHAVIOR") == value
    eh = ErrorHandler.from_config("_CALLBACK_DISPATCH_ERROR_BEHAVIOR")
    assert eh.action == ErrorAction[value.upper()]


def test_from_config_fail() -> None:
    """Verify that `from_config` fails on an invalid config entry name."""
    with pytest.raises(AnsibleUndefinedConfigEntry):
        ErrorHandler.from_config("invalid")