Cover unit tests with mypy (#84084)

* Added support for testing unit tests with mypy.
* Added support for ignoring individual mypy error codes.
* Added missing assert on unit tests and marked xfail.
* Added type hints for some unit tests.
* Added ignores for unit tests not passing mypy.
* Fixed incorrect autouse argument in unit test fixtures.
* Fixed minor issues causing problems with mypy in unit tests.
pull/84087/head
Matt Clay 2 months ago committed by GitHub
parent 18c6b40e19
commit 955e310b4c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -3,11 +3,13 @@
"lib/ansible/", "lib/ansible/",
"test/lib/ansible_test/_internal/", "test/lib/ansible_test/_internal/",
"packaging/", "packaging/",
"test/units",
"test/lib/ansible_test/_util/target/sanity/import/" "test/lib/ansible_test/_util/target/sanity/import/"
], ],
"extensions": [ "extensions": [
".py" ".py"
], ],
"multi_version": "controller", "multi_version": "controller",
"error_code": "ansible-test",
"output": "path-line-column-code-message" "output": "path-line-column-code-message"
} }

@ -36,6 +36,8 @@ def main() -> None:
MyPyContext('ansible-core', ['lib/ansible/'], controller_python_versions), MyPyContext('ansible-core', ['lib/ansible/'], controller_python_versions),
MyPyContext('modules', ['lib/ansible/modules/', 'lib/ansible/module_utils/'], remote_only_python_versions), MyPyContext('modules', ['lib/ansible/modules/', 'lib/ansible/module_utils/'], remote_only_python_versions),
MyPyContext('packaging', ['packaging/'], controller_python_versions), MyPyContext('packaging', ['packaging/'], controller_python_versions),
MyPyContext('modules', ['test/units/modules/', 'test/units/module_utils/'], remote_only_python_versions),
MyPyContext('ansible-core', ['test/units/'], controller_python_versions),
) )
unfiltered_messages: list[SanityMessage] = [] unfiltered_messages: list[SanityMessage] = []

@ -2,6 +2,7 @@ mypy
cryptography # type stubs not published separately cryptography # type stubs not published separately
jinja2 # type stubs not published separately jinja2 # type stubs not published separately
packaging # type stubs not published separately packaging # type stubs not published separately
pytest # type stubs not published separately
tomli # type stubs not published separately, required for toml inventory plugin tomli # type stubs not published separately, required for toml inventory plugin
types-backports types-backports
types-paramiko types-paramiko

@ -1,12 +1,15 @@
# edit "mypy.requirements.in" and generate with: hacking/update-sanity-requirements.py --test mypy # edit "mypy.requirements.in" and generate with: hacking/update-sanity-requirements.py --test mypy
cffi==1.17.1 cffi==1.17.1
cryptography==43.0.1 cryptography==43.0.1
iniconfig==2.0.0
Jinja2==3.1.4 Jinja2==3.1.4
MarkupSafe==2.1.5 MarkupSafe==2.1.5
mypy==1.11.2 mypy==1.11.2
mypy-extensions==1.0.0 mypy-extensions==1.0.0
packaging==24.1 packaging==24.1
pluggy==1.5.0
pycparser==2.22 pycparser==2.22
pytest==8.3.3
tomli==2.0.2 tomli==2.0.2
types-backports==0.1.3 types-backports==0.1.3
types-paramiko==3.5.0.20240928 types-paramiko==3.5.0.20240928

@ -11,6 +11,16 @@ strict_optional = False
# The safe-super rule is disabled because it reports false positives on methods which return None. # The safe-super rule is disabled because it reports false positives on methods which return None.
disable_error_code = attr-defined,safe-super disable_error_code = attr-defined,safe-super
# Some controller unit tests use ansible_collections imports, both real and test-specific.
# The real imports are not currently visible to mypy.
# There's little point in exposing the test-specific imports.
[mypy-ansible_collections.*]
ignore_missing_imports = True
# Some unit tests for ansible-test use ansible_test imports, which are not currently visible to mypy.
[mypy-ansible_test.*]
ignore_missing_imports = True
[mypy-ansible.module_utils.six.moves.*] [mypy-ansible.module_utils.six.moves.*]
ignore_missing_imports = True ignore_missing_imports = True

@ -164,3 +164,72 @@ lib/ansible/module_utils/pycompat24.py pylint:ansible-deprecated-version
lib/ansible/plugins/connection/__init__.py pylint:ansible-deprecated-version lib/ansible/plugins/connection/__init__.py pylint:ansible-deprecated-version
lib/ansible/plugins/filter/core.py pylint:ansible-deprecated-version lib/ansible/plugins/filter/core.py pylint:ansible-deprecated-version
lib/ansible/vars/manager.py pylint:ansible-deprecated-version lib/ansible/vars/manager.py pylint:ansible-deprecated-version
test/units/module_utils/basic/test_exit_json.py mypy-3.13:assignment
test/units/module_utils/basic/test_exit_json.py mypy-3.13:misc
test/units/module_utils/common/text/converters/test_json_encode_fallback.py mypy-3.13:abstract
test/units/module_utils/facts/other/test_facter.py mypy-3.13:assignment
test/units/module_utils/facts/other/test_ohai.py mypy-3.13:assignment
test/units/module_utils/facts/system/test_lsb.py mypy-3.13:assignment
test/units/module_utils/facts/test_collectors.py mypy-3.13:assignment
test/units/module_utils/facts/test_facts.py mypy-3.13:assignment
test/units/modules/mount_facts_data.py mypy-3.13:arg-type
test/units/modules/test_apt.py mypy-3.13:name-match
test/units/modules/test_mount_facts.py mypy-3.13:index
test/units/playbook/test_base.py mypy-3.13:assignment
test/units/module_utils/basic/test_exit_json.py mypy-3.12:assignment
test/units/module_utils/basic/test_exit_json.py mypy-3.12:misc
test/units/module_utils/common/text/converters/test_json_encode_fallback.py mypy-3.12:abstract
test/units/module_utils/facts/other/test_facter.py mypy-3.12:assignment
test/units/module_utils/facts/other/test_ohai.py mypy-3.12:assignment
test/units/module_utils/facts/system/test_lsb.py mypy-3.12:assignment
test/units/module_utils/facts/test_collectors.py mypy-3.12:assignment
test/units/module_utils/facts/test_facts.py mypy-3.12:assignment
test/units/modules/mount_facts_data.py mypy-3.12:arg-type
test/units/modules/test_apt.py mypy-3.12:name-match
test/units/modules/test_mount_facts.py mypy-3.12:index
test/units/playbook/test_base.py mypy-3.12:assignment
test/units/module_utils/basic/test_exit_json.py mypy-3.11:assignment
test/units/module_utils/basic/test_exit_json.py mypy-3.11:misc
test/units/module_utils/common/text/converters/test_json_encode_fallback.py mypy-3.11:abstract
test/units/module_utils/facts/other/test_facter.py mypy-3.11:assignment
test/units/module_utils/facts/other/test_ohai.py mypy-3.11:assignment
test/units/module_utils/facts/system/test_lsb.py mypy-3.11:assignment
test/units/module_utils/facts/test_collectors.py mypy-3.11:assignment
test/units/module_utils/facts/test_facts.py mypy-3.11:assignment
test/units/modules/mount_facts_data.py mypy-3.11:arg-type
test/units/modules/test_apt.py mypy-3.11:name-match
test/units/modules/test_mount_facts.py mypy-3.11:index
test/units/playbook/test_base.py mypy-3.11:assignment
test/units/module_utils/basic/test_exit_json.py mypy-3.10:assignment
test/units/module_utils/basic/test_exit_json.py mypy-3.10:misc
test/units/module_utils/common/text/converters/test_json_encode_fallback.py mypy-3.10:abstract
test/units/module_utils/facts/other/test_facter.py mypy-3.10:assignment
test/units/module_utils/facts/other/test_ohai.py mypy-3.10:assignment
test/units/module_utils/facts/system/test_lsb.py mypy-3.10:assignment
test/units/module_utils/facts/test_collectors.py mypy-3.10:assignment
test/units/module_utils/facts/test_facts.py mypy-3.10:assignment
test/units/modules/mount_facts_data.py mypy-3.10:arg-type
test/units/modules/test_apt.py mypy-3.10:name-match
test/units/modules/test_mount_facts.py mypy-3.10:index
test/units/module_utils/basic/test_exit_json.py mypy-3.9:assignment
test/units/module_utils/basic/test_exit_json.py mypy-3.9:misc
test/units/module_utils/common/text/converters/test_json_encode_fallback.py mypy-3.9:abstract
test/units/module_utils/facts/other/test_facter.py mypy-3.9:assignment
test/units/module_utils/facts/other/test_ohai.py mypy-3.9:assignment
test/units/module_utils/facts/system/test_lsb.py mypy-3.9:assignment
test/units/module_utils/facts/test_collectors.py mypy-3.9:assignment
test/units/module_utils/facts/test_facts.py mypy-3.9:assignment
test/units/modules/mount_facts_data.py mypy-3.9:arg-type
test/units/modules/test_apt.py mypy-3.9:name-match
test/units/modules/test_mount_facts.py mypy-3.9:index
test/units/module_utils/basic/test_exit_json.py mypy-3.8:assignment
test/units/module_utils/basic/test_exit_json.py mypy-3.8:misc
test/units/module_utils/common/text/converters/test_json_encode_fallback.py mypy-3.8:abstract
test/units/module_utils/facts/other/test_facter.py mypy-3.8:assignment
test/units/module_utils/facts/other/test_ohai.py mypy-3.8:assignment
test/units/module_utils/facts/system/test_lsb.py mypy-3.8:assignment
test/units/module_utils/facts/test_collectors.py mypy-3.8:assignment
test/units/module_utils/facts/test_facts.py mypy-3.8:assignment
test/units/modules/mount_facts_data.py mypy-3.8:arg-type
test/units/modules/test_apt.py mypy-3.8:name-match
test/units/modules/test_mount_facts.py mypy-3.8:index

@ -45,7 +45,7 @@ import unittest
from unittest.mock import patch, MagicMock from unittest.mock import patch, MagicMock
@pytest.fixture(autouse='function') @pytest.fixture(autouse=True)
def reset_cli_args(): def reset_cli_args():
co.GlobalCLIArgs._Singleton__instance = None co.GlobalCLIArgs._Singleton__instance = None
yield yield

@ -35,7 +35,7 @@ from ansible.utils import context_objects as co
# mock calls # mock calls
@pytest.fixture(autouse='function') @pytest.fixture(autouse=True)
def reset_cli_args(): def reset_cli_args():
co.GlobalCLIArgs._Singleton__instance = None co.GlobalCLIArgs._Singleton__instance = None
yield yield

@ -29,7 +29,7 @@ from ansible.utils import context_objects as co
from ansible.utils.display import Display from ansible.utils.display import Display
@pytest.fixture(autouse='function') @pytest.fixture(autouse=True)
def reset_cli_args(): def reset_cli_args():
co.GlobalCLIArgs._Singleton__instance = None co.GlobalCLIArgs._Singleton__instance = None
# Required to initialise the GalaxyAPI object # Required to initialise the GalaxyAPI object

@ -31,7 +31,7 @@ from ansible.utils.display import Display
from ansible.utils.hashing import secure_hash_s from ansible.utils.hashing import secure_hash_s
@pytest.fixture(autouse='function') @pytest.fixture(autouse=True)
def reset_cli_args(): def reset_cli_args():
co.GlobalCLIArgs._Singleton__instance = None co.GlobalCLIArgs._Singleton__instance = None
yield yield

@ -53,7 +53,7 @@ def call_galaxy_cli(args):
co.GlobalCLIArgs._Singleton__instance = orig co.GlobalCLIArgs._Singleton__instance = orig
@pytest.fixture(autouse='function') @pytest.fixture(autouse=True)
def reset_cli_args(): def reset_cli_args():
co.GlobalCLIArgs._Singleton__instance = None co.GlobalCLIArgs._Singleton__instance = None
yield yield
@ -992,9 +992,7 @@ def test_install_collection_with_no_dependency(collection_artifact, monkeypatch)
(["good_signature", "good_signature"], '2', [], True), (["good_signature", "good_signature"], '2', [], True),
] ]
) )
def test_verify_file_signatures(signatures, required_successful_count, ignore_errors, expected_success): def test_verify_file_signatures(signatures: list[str], required_successful_count: str, ignore_errors: list[str], expected_success: bool) -> None:
# type: (List[bool], int, bool, bool) -> None
def gpg_error_generator(results): def gpg_error_generator(results):
for result in results: for result in results:
if isinstance(result, collection.gpg.GpgBaseError): if isinstance(result, collection.gpg.GpgBaseError):

@ -28,7 +28,7 @@ def call_galaxy_cli(args):
co.GlobalCLIArgs._Singleton__instance = orig co.GlobalCLIArgs._Singleton__instance = orig
@pytest.fixture(autouse='function') @pytest.fixture(autouse=True)
def reset_cli_args(): def reset_cli_args():
co.GlobalCLIArgs._Singleton__instance = None co.GlobalCLIArgs._Singleton__instance = None
yield yield

@ -8,6 +8,7 @@ from __future__ import annotations
import json import json
import os import os
import typing as t
import pytest import pytest
@ -69,7 +70,7 @@ VALID_SPECS = (
({'arg': {'type': 'int'}}, {'arg': 1, 'invalid': True, '_ansible_ignore_unknown_opts': True}, 1), ({'arg': {'type': 'int'}}, {'arg': 1, 'invalid': True, '_ansible_ignore_unknown_opts': True}, 1),
) )
INVALID_SPECS = ( INVALID_SPECS: tuple[tuple[dict[str, t.Any], dict[str, t.Any], str], ...] = (
# Type is int; unable to convert this string # Type is int; unable to convert this string
({'arg': {'type': 'int'}}, {'arg': "wolf"}, f"is of type {type('wolf')} and we were unable to convert to int:"), ({'arg': {'type': 'int'}}, {'arg': "wolf"}, f"is of type {type('wolf')} and we were unable to convert to int:"),
# Type is list elements is int; unable to convert this string # Type is list elements is int; unable to convert this string
@ -495,7 +496,7 @@ class TestComplexOptions:
) )
# (Parameters, failure message) # (Parameters, failure message)
FAILING_PARAMS_LIST = ( FAILING_PARAMS_LIST: tuple[tuple[dict[str, list[dict[str, t.Any]]], str], ...] = (
# Missing required option # Missing required option
({'foobar': [{}]}, 'missing required arguments: foo found in foobar'), ({'foobar': [{}]}, 'missing required arguments: foo found in foobar'),
# Invalid option # Invalid option
@ -518,7 +519,7 @@ class TestComplexOptions:
) )
# (Parameters, failure message) # (Parameters, failure message)
FAILING_PARAMS_DICT = ( FAILING_PARAMS_DICT: tuple[tuple[dict[str, dict[str, t.Any]], str], ...] = (
# Missing required option # Missing required option
({'foobar': {}}, 'missing required arguments: foo found in foobar'), ({'foobar': {}}, 'missing required arguments: foo found in foobar'),
# Invalid option # Invalid option

@ -7,12 +7,13 @@ from __future__ import annotations
import json import json
import sys import sys
import datetime import datetime
import typing as t
import pytest import pytest
from ansible.module_utils.common import warnings from ansible.module_utils.common import warnings
EMPTY_INVOCATION = {u'module_args': {}} EMPTY_INVOCATION: dict[str, dict[str, t.Any]] = {u'module_args': {}}
DATETIME = datetime.datetime.strptime('2020-07-13 12:50:00', '%Y-%m-%d %H:%M:%S') DATETIME = datetime.datetime.strptime('2020-07-13 12:50:00', '%Y-%m-%d %H:%M:%S')
@ -20,7 +21,7 @@ class TestAnsibleModuleExitJson:
""" """
Test that various means of calling exitJson and FailJson return the messages they've been given Test that various means of calling exitJson and FailJson return the messages they've been given
""" """
DATA = ( DATA: tuple[tuple[dict[str, t.Any]], ...] = (
({}, {'invocation': EMPTY_INVOCATION}), ({}, {'invocation': EMPTY_INVOCATION}),
({'msg': 'message'}, {'msg': 'message', 'invocation': EMPTY_INVOCATION}), ({'msg': 'message'}, {'msg': 'message', 'invocation': EMPTY_INVOCATION}),
({'msg': 'success', 'changed': True}, ({'msg': 'success', 'changed': True},

@ -5,6 +5,7 @@
from __future__ import annotations from __future__ import annotations
import typing as t
import unittest import unittest
from ansible.module_utils.basic import remove_values from ansible.module_utils.basic import remove_values
@ -12,7 +13,7 @@ from ansible.module_utils.common.parameters import _return_datastructure_name
class TestReturnValues(unittest.TestCase): class TestReturnValues(unittest.TestCase):
dataset = ( dataset: tuple[tuple[t.Any, frozenset[str]], ...] = (
('string', frozenset(['string'])), ('string', frozenset(['string'])),
('', frozenset()), ('', frozenset()),
(1, frozenset(['1'])), (1, frozenset(['1'])),

@ -5,11 +5,14 @@
from __future__ import annotations from __future__ import annotations
from itertools import chain from itertools import chain
import typing as t
import pytest import pytest
# Strings that should be converted into a typed value # Strings that should be converted into a typed value
VALID_STRINGS = ( VALID_STRINGS: tuple[tuple[str, t.Any], ...] = (
("'a'", 'a'), ("'a'", 'a'),
("'1'", '1'), ("'1'", '1'),
("1", 1), ("1", 1),

@ -67,7 +67,7 @@ ALIAS_TEST_CASES = [
# id, argument spec, parameters, expected parameters, error # id, argument spec, parameters, expected parameters, error
ALIAS_TEST_CASES_INVALID = [ ALIAS_TEST_CASES_INVALID: list[tuple[str, dict, dict, dict, str]] = [
( (
"alias-invalid", "alias-invalid",
{'path': {'aliases': 'bad'}}, {'path': {'aliases': 'bad'}},

@ -39,16 +39,14 @@ class FakeAnsibleVaultEncryptedUnicode(Sequence):
TEST_STRINGS = u'he', u'Україна', u'Česká republika' TEST_STRINGS = u'he', u'Україна', u'Česká republika'
TEST_STRINGS = TEST_STRINGS + tuple(s.encode('utf-8') for s in TEST_STRINGS) + (FakeAnsibleVaultEncryptedUnicode(u'foo'),) TEST_STRINGS = TEST_STRINGS + tuple(s.encode('utf-8') for s in TEST_STRINGS) + (FakeAnsibleVaultEncryptedUnicode(u'foo'),)
TEST_ITEMS_NON_SEQUENCES = ( TEST_ITEMS_NON_SEQUENCES: tuple = (
{}, object(), frozenset(), {}, object(), frozenset(),
4, 0., 4, 0.,
) + TEST_STRINGS ) + TEST_STRINGS
TEST_ITEMS_SEQUENCES = ( TEST_ITEMS_SEQUENCES: tuple = (
[], (), [], (),
SeqStub(), SeqStub(),
)
TEST_ITEMS_SEQUENCES = TEST_ITEMS_SEQUENCES + (
# Iterable effectively containing nested random data: # Iterable effectively containing nested random data:
TEST_ITEMS_NON_SEQUENCES, TEST_ITEMS_NON_SEQUENCES,
) )

@ -29,8 +29,6 @@ from . linux_data import LSBLK_OUTPUT, LSBLK_OUTPUT_2, LSBLK_UUIDS, MTAB, MTAB_E
with open(os.path.join(os.path.dirname(__file__), '../fixtures/findmount_output.txt')) as f: with open(os.path.join(os.path.dirname(__file__), '../fixtures/findmount_output.txt')) as f:
FINDMNT_OUTPUT = f.read() FINDMNT_OUTPUT = f.read()
GET_MOUNT_SIZE = {}
def mock_get_mount_size(mountpoint): def mock_get_mount_size(mountpoint):
return STATVFS_INFO.get(mountpoint, {}) return STATVFS_INFO.get(mountpoint, {})

@ -200,7 +200,7 @@ class TestCollectedFacts(unittest.TestCase):
'env'] 'env']
not_expected_facts = ['facter', 'ohai'] not_expected_facts = ['facter', 'ohai']
collected_facts = {} collected_facts: dict[str, str] = {}
def _mock_module(self, gather_subset=None): def _mock_module(self, gather_subset=None):
return mock_module(gather_subset=self.gather_subset) return mock_module(gather_subset=self.gather_subset)

@ -11,102 +11,109 @@ from ansible.modules.copy import AnsibleModuleError, split_pre_existing_dir
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
THREE_DIRS_DATA: tuple[tuple[str, tuple[str, list[str]] | None, tuple[str, list[str]], tuple[str, list[str]], tuple[str, list[str]]], ...] = (
('/dir1/dir2',
# 0 existing dirs: error (because / should always exist)
None,
# 1 existing dir:
('/', ['dir1', 'dir2']),
# 2 existing dirs:
('/dir1', ['dir2']),
# 3 existing dirs:
('/dir1/dir2', [])
),
('/dir1/dir2/',
# 0 existing dirs: error (because / should always exist)
None,
# 1 existing dir:
('/', ['dir1', 'dir2']),
# 2 existing dirs:
('/dir1', ['dir2']),
# 3 existing dirs:
('/dir1/dir2', [])
),
)
THREE_DIRS_DATA = (('/dir1/dir2', TWO_DIRS_DATA: tuple[tuple[str, tuple[str, list[str]] | None, tuple[str, list[str]], tuple[str, list[str]]], ...] = (
# 0 existing dirs: error (because / should always exist) ('dir1/dir2',
None, # 0 existing dirs:
# 1 existing dir: ('.', ['dir1', 'dir2']),
('/', ['dir1', 'dir2']), # 1 existing dir:
# 2 existing dirs: ('dir1', ['dir2']),
('/dir1', ['dir2']), # 2 existing dirs:
# 3 existing dirs: ('dir1/dir2', []),
('/dir1/dir2', []) # 3 existing dirs: Same as 2 because we never get to the third
), ),
('/dir1/dir2/', ('dir1/dir2/',
# 0 existing dirs: error (because / should always exist) # 0 existing dirs:
None, ('.', ['dir1', 'dir2']),
# 1 existing dir: # 1 existing dir:
('/', ['dir1', 'dir2']), ('dir1', ['dir2']),
# 2 existing dirs: # 2 existing dirs:
('/dir1', ['dir2']), ('dir1/dir2', []),
# 3 existing dirs: # 3 existing dirs: Same as 2 because we never get to the third
('/dir1/dir2', []) ),
), ('/dir1',
) # 0 existing dirs: error (because / should always exist)
None,
# 1 existing dir:
TWO_DIRS_DATA = (('dir1/dir2', ('/', ['dir1']),
# 0 existing dirs: # 2 existing dirs:
('.', ['dir1', 'dir2']), ('/dir1', []),
# 1 existing dir: # 3 existing dirs: Same as 2 because we never get to the third
('dir1', ['dir2']), ),
# 2 existing dirs: ('/dir1/',
('dir1/dir2', []), # 0 existing dirs: error (because / should always exist)
# 3 existing dirs: Same as 2 because we never get to the third None,
), # 1 existing dir:
('dir1/dir2/', ('/', ['dir1']),
# 0 existing dirs: # 2 existing dirs:
('.', ['dir1', 'dir2']), ('/dir1', []),
# 1 existing dir: # 3 existing dirs: Same as 2 because we never get to the third
('dir1', ['dir2']), ),
# 2 existing dirs: )
('dir1/dir2', []), TWO_DIRS_DATA += tuple(item[:4] for item in THREE_DIRS_DATA)
# 3 existing dirs: Same as 2 because we never get to the third
),
('/dir1', ONE_DIR_DATA: tuple[tuple[str, tuple[str, list[str]] | None, tuple[str, list[str]]], ...] = (
# 0 existing dirs: error (because / should always exist) ('dir1',
None, # 0 existing dirs:
# 1 existing dir: ('.', ['dir1']),
('/', ['dir1']), # 1 existing dir:
# 2 existing dirs: ('dir1', []),
('/dir1', []), # 2 existing dirs: Same as 1 because we never get to the third
# 3 existing dirs: Same as 2 because we never get to the third ),
), ('dir1/',
('/dir1/', # 0 existing dirs:
# 0 existing dirs: error (because / should always exist) ('.', ['dir1']),
None, # 1 existing dir:
# 1 existing dir: ('dir1', []),
('/', ['dir1']), # 2 existing dirs: Same as 1 because we never get to the third
# 2 existing dirs: ),
('/dir1', []), )
# 3 existing dirs: Same as 2 because we never get to the third ONE_DIR_DATA += tuple(item[:3] for item in TWO_DIRS_DATA)
),
) + THREE_DIRS_DATA
ONE_DIR_DATA = (('dir1',
# 0 existing dirs:
('.', ['dir1']),
# 1 existing dir:
('dir1', []),
# 2 existing dirs: Same as 1 because we never get to the third
),
('dir1/',
# 0 existing dirs:
('.', ['dir1']),
# 1 existing dir:
('dir1', []),
# 2 existing dirs: Same as 1 because we never get to the third
),
) + TWO_DIRS_DATA
@pytest.mark.parametrize('directory, expected', ((d[0], d[4]) for d in THREE_DIRS_DATA)) @pytest.mark.parametrize('directory, expected', ((d[0], d[4]) for d in THREE_DIRS_DATA))
@pytest.mark.xfail(reason='broken test and/or code, original test missing assert', strict=False)
def test_split_pre_existing_dir_three_levels_exist(directory, expected, mocker): def test_split_pre_existing_dir_three_levels_exist(directory, expected, mocker):
mocker.patch('os.path.exists', side_effect=[True, True, True]) mocker.patch('os.path.exists', side_effect=[True, True, True])
split_pre_existing_dir(directory) == expected assert split_pre_existing_dir(directory) == expected
@pytest.mark.parametrize('directory, expected', ((d[0], d[3]) for d in TWO_DIRS_DATA)) @pytest.mark.parametrize('directory, expected', ((d[0], d[3]) for d in TWO_DIRS_DATA))
@pytest.mark.xfail(reason='broken test and/or code, original test missing assert', strict=False)
def test_split_pre_existing_dir_two_levels_exist(directory, expected, mocker): def test_split_pre_existing_dir_two_levels_exist(directory, expected, mocker):
mocker.patch('os.path.exists', side_effect=[True, True, False]) mocker.patch('os.path.exists', side_effect=[True, True, False])
split_pre_existing_dir(directory) == expected assert split_pre_existing_dir(directory) == expected
@pytest.mark.parametrize('directory, expected', ((d[0], d[2]) for d in ONE_DIR_DATA)) @pytest.mark.parametrize('directory, expected', ((d[0], d[2]) for d in ONE_DIR_DATA))
@pytest.mark.xfail(reason='broken test and/or code, original test missing assert', strict=False)
def test_split_pre_existing_dir_one_level_exists(directory, expected, mocker): def test_split_pre_existing_dir_one_level_exists(directory, expected, mocker):
mocker.patch('os.path.exists', side_effect=[True, False, False]) mocker.patch('os.path.exists', side_effect=[True, False, False])
split_pre_existing_dir(directory) == expected assert split_pre_existing_dir(directory) == expected
@pytest.mark.parametrize('directory', (d[0] for d in ONE_DIR_DATA if d[1] is None)) @pytest.mark.parametrize('directory', (d[0] for d in ONE_DIR_DATA if d[1] is None))
@ -118,9 +125,10 @@ def test_split_pre_existing_dir_root_does_not_exist(directory, mocker):
@pytest.mark.parametrize('directory, expected', ((d[0], d[1]) for d in ONE_DIR_DATA if not d[0].startswith('/'))) @pytest.mark.parametrize('directory, expected', ((d[0], d[1]) for d in ONE_DIR_DATA if not d[0].startswith('/')))
@pytest.mark.xfail(reason='broken test and/or code, original test missing assert', strict=False)
def test_split_pre_existing_dir_working_dir_exists(directory, expected, mocker): def test_split_pre_existing_dir_working_dir_exists(directory, expected, mocker):
mocker.patch('os.path.exists', return_value=False) mocker.patch('os.path.exists', return_value=False)
split_pre_existing_dir(directory) == expected assert split_pre_existing_dir(directory) == expected
# #

@ -23,7 +23,7 @@ from ansible.errors import AnsibleParserError
import pytest import pytest
SPLIT_DATA = ( SPLIT_DATA: tuple[tuple[str | None, list[str], dict[str, str]], ...] = (
(None, (None,
[], [],
{}), {}),

@ -6,6 +6,7 @@ from __future__ import annotations
import pytest import pytest
import sys import sys
import typing as t
from io import StringIO from io import StringIO
from unittest.mock import MagicMock from unittest.mock import MagicMock
@ -46,7 +47,7 @@ def psrp_connection():
class TestConnectionPSRP(object): class TestConnectionPSRP(object):
OPTIONS_DATA = ( OPTIONS_DATA: tuple[tuple[dict[str, t.Any], dict[str, t.Any]], ...] = (
# default options # default options
( (
{}, {},

@ -5,6 +5,7 @@
from __future__ import annotations from __future__ import annotations
import os import os
import typing as t
import pytest import pytest
@ -22,7 +23,7 @@ pytest.importorskip("winrm")
class TestConnectionWinRM(object): class TestConnectionWinRM(object):
OPTIONS_DATA = ( OPTIONS_DATA: tuple[tuple[dict[str, t.Any], dict[str, t.Any], dict[str, t.Any], bool], ...] = (
# default options # default options
( (
{'_extras': {}}, {'_extras': {}},

@ -11,7 +11,7 @@ import ansible.plugins.filter.mathstuff as ms
from ansible.errors import AnsibleFilterError, AnsibleFilterTypeError from ansible.errors import AnsibleFilterError, AnsibleFilterTypeError
UNIQUE_DATA = [ UNIQUE_DATA: list[tuple[list, list]] = [
([], []), ([], []),
([1, 3, 4, 2], [1, 3, 4, 2]), ([1, 3, 4, 2], [1, 3, 4, 2]),
([1, 3, 2, 4, 2, 3], [1, 3, 2, 4]), ([1, 3, 2, 4, 2, 3], [1, 3, 2, 4]),
@ -19,7 +19,7 @@ UNIQUE_DATA = [
([1, 1, 4, 2, 1, 4, 3, 2], [1, 4, 2, 3]), ([1, 1, 4, 2, 1, 4, 3, 2], [1, 4, 2, 3]),
] ]
TWO_SETS_DATA = [ TWO_SETS_DATA: list[tuple[list, list, tuple]] = [
([], [], ([], [], [])), ([], [], ([], [], [])),
([1, 2], [1, 2], ([1, 2], [], [])), ([1, 2], [1, 2], ([1, 2], [], [])),
([1, 2], [3, 4], ([], [1, 2], [1, 2, 3, 4])), ([1, 2], [3, 4], ([], [1, 2], [1, 2, 3, 4])),

@ -387,8 +387,11 @@ class TestWritePasswordFile(unittest.TestCase):
def setUp(self): def setUp(self):
self.makedirs_safe = password.makedirs_safe self.makedirs_safe = password.makedirs_safe
self.os_chmod = password.os.chmod self.os_chmod = password.os.chmod
password.makedirs_safe = lambda path, mode: None password.makedirs_safe = self.noop
password.os.chmod = lambda path, mode: None password.os.chmod = self.noop
def noop(self, *args, **kwargs):
pass
def tearDown(self): def tearDown(self):
password.makedirs_safe = self.makedirs_safe password.makedirs_safe = self.makedirs_safe
@ -410,11 +413,14 @@ class BaseTestLookupModule(unittest.TestCase):
self.password_lookup._loader = self.fake_loader self.password_lookup._loader = self.fake_loader
self.os_path_exists = password.os.path.exists self.os_path_exists = password.os.path.exists
self.os_open = password.os.open self.os_open = password.os.open
password.os.open = lambda path, flag: None password.os.open = self.noop
self.os_close = password.os.close self.os_close = password.os.close
password.os.close = lambda fd: None password.os.close = self.noop
self.makedirs_safe = password.makedirs_safe self.makedirs_safe = password.makedirs_safe
password.makedirs_safe = lambda path, mode: None password.makedirs_safe = self.noop
def noop(self, *args, **kwargs):
pass
def tearDown(self): def tearDown(self):
password.os.path.exists = self.os_path_exists password.os.path.exists = self.os_path_exists

Loading…
Cancel
Save