diff options
author | Lee Garrett <lgarrett@rocketjump.eu> | 2023-03-01 21:04:53 +0100 |
---|---|---|
committer | Lee Garrett <lgarrett@rocketjump.eu> | 2023-03-01 21:04:53 +0100 |
commit | 3cda7ad4dd15b514ff660905294b5b6330ecfb6f (patch) | |
tree | 527d16e74bfd1840c85efd675fdecad056c54107 /test | |
parent | 520506f035967306d0548a8f69a0fea3181dca35 (diff) | |
download | debian-ansible-core-3cda7ad4dd15b514ff660905294b5b6330ecfb6f.zip |
New upstream version 2.14.3
Diffstat (limited to 'test')
100 files changed, 879 insertions, 736 deletions
diff --git a/test/integration/targets/ansible-galaxy-collection-scm/meta/main.yml b/test/integration/targets/ansible-galaxy-collection-scm/meta/main.yml index e3dd5fb1..655a62f0 100644 --- a/test/integration/targets/ansible-galaxy-collection-scm/meta/main.yml +++ b/test/integration/targets/ansible-galaxy-collection-scm/meta/main.yml @@ -1,3 +1,4 @@ --- dependencies: - setup_remote_tmp_dir +- setup_gnutar diff --git a/test/integration/targets/ansible-galaxy-collection-scm/tasks/main.yml b/test/integration/targets/ansible-galaxy-collection-scm/tasks/main.yml index 546c4083..dab599b1 100644 --- a/test/integration/targets/ansible-galaxy-collection-scm/tasks/main.yml +++ b/test/integration/targets/ansible-galaxy-collection-scm/tasks/main.yml @@ -30,6 +30,7 @@ - include_tasks: ./download.yml - include_tasks: ./setup_collection_bad_version.yml - include_tasks: ./test_invalid_version.yml + - include_tasks: ./test_manifest_metadata.yml always: diff --git a/test/integration/targets/ansible-galaxy-collection-scm/tasks/test_manifest_metadata.yml b/test/integration/targets/ansible-galaxy-collection-scm/tasks/test_manifest_metadata.yml new file mode 100644 index 00000000..a01551ca --- /dev/null +++ b/test/integration/targets/ansible-galaxy-collection-scm/tasks/test_manifest_metadata.yml @@ -0,0 +1,55 @@ +- name: Test installing a collection from a git repo containing a MANIFEST.json + block: + - name: Create a temp directory for building the collection + file: + path: '{{ galaxy_dir }}/scratch' + state: directory + + - name: Initialize a collection + command: 'ansible-galaxy collection init namespace_3.collection_1' + args: + chdir: '{{ galaxy_dir }}/scratch' + + - name: Build the collection + command: 'ansible-galaxy collection build namespace_3/collection_1' + args: + chdir: '{{ galaxy_dir }}/scratch' + + - name: Initialize git repository + command: 'git init {{ scm_path }}/namespace_3' + + - name: Create the destination for the collection + file: + path: '{{ scm_path }}/namespace_3/collection_1' + state: directory + + - name: Unarchive the collection in the git repo + unarchive: + dest: '{{ scm_path }}/namespace_3/collection_1' + src: '{{ galaxy_dir }}/scratch/namespace_3-collection_1-1.0.0.tar.gz' + remote_src: yes + + - name: Commit the changes + shell: git add ./; git commit -m 'add collection' + args: + chdir: '{{ scm_path }}/namespace_3' + + - name: Install the collection in the git repository + command: 'ansible-galaxy collection install git+file://{{ scm_path }}/namespace_3/.git' + register: result + + - name: Assert the collection was installed successfully + assert: + that: + - '"namespace_3.collection_1:1.0.0 was installed successfully" in result.stdout_lines' + + always: + - name: Clean up directories from test + file: + path: '{{ galaxy_dir }}/scratch' + state: absent + loop: + - '{{ galaxy_dir }}/scratch' + - '{{ scm_path }}/namespace_3' + + - include_tasks: ./empty_installed_collections.yml diff --git a/test/integration/targets/handlers/79776-handlers.yml b/test/integration/targets/handlers/79776-handlers.yml new file mode 100644 index 00000000..639c9cad --- /dev/null +++ b/test/integration/targets/handlers/79776-handlers.yml @@ -0,0 +1,2 @@ +- debug: + msg: "Handler for {{ inventory_hostname }}" diff --git a/test/integration/targets/handlers/79776.yml b/test/integration/targets/handlers/79776.yml new file mode 100644 index 00000000..08d22272 --- /dev/null +++ b/test/integration/targets/handlers/79776.yml @@ -0,0 +1,10 @@ +- hosts: A,B + gather_facts: false + force_handlers: true + tasks: + - command: echo + notify: handler1 + when: inventory_hostname == "A" + handlers: + - name: handler1 + include_tasks: 79776-handlers.yml diff --git a/test/integration/targets/handlers/runme.sh b/test/integration/targets/handlers/runme.sh index e2d52180..76fc99d8 100755 --- a/test/integration/targets/handlers/runme.sh +++ b/test/integration/targets/handlers/runme.sh @@ -170,3 +170,14 @@ ansible-playbook test_flush_handlers_rescue_always.yml -i inventory.handlers "$@ ansible-playbook test_fqcn_meta_flush_handlers.yml -i inventory.handlers "$@" 2>&1 | tee out.txt grep out.txt -e "handler ran" grep out.txt -e "after flush" + +ansible-playbook 79776.yml -i inventory.handlers "$@" + +ansible-playbook test_block_as_handler.yml "$@" 2>&1 | tee out.txt +grep out.txt -e "ERROR! Using a block as a handler is not supported." + +ansible-playbook test_block_as_handler-include.yml "$@" 2>&1 | tee out.txt +grep out.txt -e "ERROR! Using a block as a handler is not supported." + +ansible-playbook test_block_as_handler-import.yml "$@" 2>&1 | tee out.txt +grep out.txt -e "ERROR! Using a block as a handler is not supported." diff --git a/test/integration/targets/handlers/test_block_as_handler-import.yml b/test/integration/targets/handlers/test_block_as_handler-import.yml new file mode 100644 index 00000000..ad6bb0d5 --- /dev/null +++ b/test/integration/targets/handlers/test_block_as_handler-import.yml @@ -0,0 +1,7 @@ +- hosts: localhost + gather_facts: false + tasks: + - debug: + handlers: + - name: handler + import_tasks: test_block_as_handler-include_import-handlers.yml diff --git a/test/integration/targets/handlers/test_block_as_handler-include.yml b/test/integration/targets/handlers/test_block_as_handler-include.yml new file mode 100644 index 00000000..5b03b0a8 --- /dev/null +++ b/test/integration/targets/handlers/test_block_as_handler-include.yml @@ -0,0 +1,8 @@ +- hosts: localhost + gather_facts: false + tasks: + - command: echo + notify: handler + handlers: + - name: handler + include_tasks: test_block_as_handler-include_import-handlers.yml diff --git a/test/integration/targets/handlers/test_block_as_handler-include_import-handlers.yml b/test/integration/targets/handlers/test_block_as_handler-include_import-handlers.yml new file mode 100644 index 00000000..61c058ba --- /dev/null +++ b/test/integration/targets/handlers/test_block_as_handler-include_import-handlers.yml @@ -0,0 +1,8 @@ +- name: handler + block: + - name: due to how handlers are implemented, this is correct as it is equivalent to an implicit block + debug: + - name: this is a parser error, blocks as handlers are not supported + block: + - name: handler in a nested block + debug: diff --git a/test/integration/targets/handlers/test_block_as_handler.yml b/test/integration/targets/handlers/test_block_as_handler.yml new file mode 100644 index 00000000..bd4f5b99 --- /dev/null +++ b/test/integration/targets/handlers/test_block_as_handler.yml @@ -0,0 +1,13 @@ +- hosts: localhost + gather_facts: false + tasks: + - debug: + handlers: + - name: handler + block: + - name: due to how handlers are implemented, this is correct as it is equivalent to an implicit block + debug: + - name: this is a parser error, blocks as handlers are not supported + block: + - name: handler in a nested block + debug: diff --git a/test/integration/targets/register/aliases b/test/integration/targets/register/aliases new file mode 100644 index 00000000..b76d5f67 --- /dev/null +++ b/test/integration/targets/register/aliases @@ -0,0 +1,2 @@ +shippable/posix/group3 +context/controller # this "module" is actually an action that runs on the controller diff --git a/test/integration/targets/register/can_register.yml b/test/integration/targets/register/can_register.yml new file mode 100644 index 00000000..da610101 --- /dev/null +++ b/test/integration/targets/register/can_register.yml @@ -0,0 +1,21 @@ +- hosts: testhost + gather_facts: false + tasks: + - name: test registering + debug: msg='does nothing really but register this' + register: debug_msg + + - name: validate registering + assert: + that: + - debug_msg is defined + + - name: test registering skipped + debug: msg='does nothing really but register this' + when: false + register: debug_skipped + + - name: validate registering + assert: + that: + - debug_skipped is defined diff --git a/test/integration/targets/register/invalid.yml b/test/integration/targets/register/invalid.yml new file mode 100644 index 00000000..bdca9d66 --- /dev/null +++ b/test/integration/targets/register/invalid.yml @@ -0,0 +1,11 @@ +- hosts: testhost + gather_facts: false + tasks: + - name: test registering + debug: msg='does nothing really but register this' + register: 200 + + - name: never gets here + assert: + that: + - 200 is not defined diff --git a/test/integration/targets/register/invalid_skipped.yml b/test/integration/targets/register/invalid_skipped.yml new file mode 100644 index 00000000..0ad31f51 --- /dev/null +++ b/test/integration/targets/register/invalid_skipped.yml @@ -0,0 +1,12 @@ +- hosts: testhost + gather_facts: false + tasks: + - name: test registering bad var when skipped + debug: msg='does nothing really but register this' + when: false + register: 200 + + - name: never gets here + assert: + that: + - 200 is not defined diff --git a/test/integration/targets/register/runme.sh b/test/integration/targets/register/runme.sh new file mode 100755 index 00000000..8adc5047 --- /dev/null +++ b/test/integration/targets/register/runme.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -eux + +# does it work? +ansible-playbook can_register.yml -i ../../inventory -v "$@" + +# ensure we do error when it its apprpos +set +e +result="$(ansible-playbook invalid.yml -i ../../inventory -v "$@" 2>&1)" +set -e +grep -q "Invalid variable name in " <<< "${result}" diff --git a/test/integration/targets/tasks/playbook.yml b/test/integration/targets/tasks/playbook.yml new file mode 100644 index 00000000..80d9f8b1 --- /dev/null +++ b/test/integration/targets/tasks/playbook.yml @@ -0,0 +1,19 @@ +- hosts: localhost + gather_facts: false + tasks: + # make sure tasks with an undefined variable in the name are gracefully handled + - name: "Task name with undefined variable: {{ not_defined }}" + debug: + msg: Hello + + - name: ensure malformed raw_params on arbitrary actions are not ignored + debug: + garbage {{"with a template"}} + ignore_errors: true + register: bad_templated_raw_param + + - assert: + that: + - bad_templated_raw_param is failed + - | + "invalid or malformed argument: 'garbage with a template'" in bad_templated_raw_param.msg diff --git a/test/integration/targets/tasks/runme.sh b/test/integration/targets/tasks/runme.sh new file mode 100755 index 00000000..594447bd --- /dev/null +++ b/test/integration/targets/tasks/runme.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +ansible-playbook playbook.yml "$@" diff --git a/test/integration/targets/tasks/tasks/main.yml b/test/integration/targets/tasks/tasks/main.yml deleted file mode 100644 index f6ac1114..00000000 --- a/test/integration/targets/tasks/tasks/main.yml +++ /dev/null @@ -1,4 +0,0 @@ -# make sure tasks with an undefined variable in the name are gracefully handled -- name: "Task name with undefined variable: {{ not_defined }}" - debug: - msg: Hello diff --git a/test/lib/ansible_test/_internal/ansible_util.py b/test/lib/ansible_test/_internal/ansible_util.py index 5798d352..9efcda26 100644 --- a/test/lib/ansible_test/_internal/ansible_util.py +++ b/test/lib/ansible_test/_internal/ansible_util.py @@ -284,11 +284,11 @@ def get_collection_detail(python: PythonConfig) -> CollectionDetail: def run_playbook( - args: EnvironmentConfig, - inventory_path: str, - playbook: str, - capture: bool, - variables: t.Optional[dict[str, t.Any]] = None, + args: EnvironmentConfig, + inventory_path: str, + playbook: str, + capture: bool, + variables: t.Optional[dict[str, t.Any]] = None, ) -> None: """Run the specified playbook using the given inventory file and playbook variables.""" playbook_path = os.path.join(ANSIBLE_TEST_DATA_ROOT, 'playbooks', playbook) diff --git a/test/lib/ansible_test/_internal/become.py b/test/lib/ansible_test/_internal/become.py index cabf97e4..e653959a 100644 --- a/test/lib/ansible_test/_internal/become.py +++ b/test/lib/ansible_test/_internal/become.py @@ -12,7 +12,7 @@ from .util import ( class Become(metaclass=abc.ABCMeta): """Base class for become implementations.""" @classmethod - def name(cls): + def name(cls) -> str: """The name of this plugin.""" return cls.__name__.lower() @@ -48,7 +48,7 @@ class Doas(Become): class DoasSudo(Doas): """Become using 'doas' in ansible-test and then after bootstrapping use 'sudo' for other ansible commands.""" @classmethod - def name(cls): + def name(cls) -> str: """The name of this plugin.""" return 'doas_sudo' @@ -78,7 +78,7 @@ class Su(Become): class SuSudo(Su): """Become using 'su' in ansible-test and then after bootstrapping use 'sudo' for other ansible commands.""" @classmethod - def name(cls): + def name(cls) -> str: """The name of this plugin.""" return 'su_sudo' diff --git a/test/lib/ansible_test/_internal/cgroup.py b/test/lib/ansible_test/_internal/cgroup.py index b55d878d..977e359d 100644 --- a/test/lib/ansible_test/_internal/cgroup.py +++ b/test/lib/ansible_test/_internal/cgroup.py @@ -29,7 +29,7 @@ class CGroupEntry: path: pathlib.PurePosixPath @property - def root_path(self): + def root_path(self) -> pathlib.PurePosixPath: """The root path for this cgroup subsystem.""" return pathlib.PurePosixPath(CGroupPath.ROOT, self.subsystem) diff --git a/test/lib/ansible_test/_internal/ci/__init__.py b/test/lib/ansible_test/_internal/ci/__init__.py index 677fafce..97e41dae 100644 --- a/test/lib/ansible_test/_internal/ci/__init__.py +++ b/test/lib/ansible_test/_internal/ci/__init__.py @@ -152,6 +152,8 @@ class CryptographyAuthHelper(AuthHelper, metaclass=abc.ABCMeta): private_key_pem = self.initialize_private_key() private_key = load_pem_private_key(to_bytes(private_key_pem), None, default_backend()) + assert isinstance(private_key, ec.EllipticCurvePrivateKey) + signature_raw_bytes = private_key.sign(payload_bytes, ec.ECDSA(hashes.SHA256())) return signature_raw_bytes diff --git a/test/lib/ansible_test/_internal/ci/azp.py b/test/lib/ansible_test/_internal/ci/azp.py index 557fbacb..9170dfec 100644 --- a/test/lib/ansible_test/_internal/ci/azp.py +++ b/test/lib/ansible_test/_internal/ci/azp.py @@ -40,7 +40,7 @@ CODE = 'azp' class AzurePipelines(CIProvider): """CI provider implementation for Azure Pipelines.""" - def __init__(self): + def __init__(self) -> None: self.auth = AzurePipelinesAuthHelper() @staticmethod diff --git a/test/lib/ansible_test/_internal/classification/__init__.py b/test/lib/ansible_test/_internal/classification/__init__.py index 863de23c..aacc2ca9 100644 --- a/test/lib/ansible_test/_internal/classification/__init__.py +++ b/test/lib/ansible_test/_internal/classification/__init__.py @@ -379,9 +379,9 @@ class PathMapper: if is_subdir(path, data_context().content.integration_path): if dirname == data_context().content.integration_path: for command in ( - 'integration', - 'windows-integration', - 'network-integration', + 'integration', + 'windows-integration', + 'network-integration', ): if name == command and ext == '.cfg': return { @@ -641,19 +641,19 @@ class PathMapper: if '/' not in path: if path in ( - '.gitignore', - 'COPYING', - 'LICENSE', - 'Makefile', + '.gitignore', + 'COPYING', + 'LICENSE', + 'Makefile', ): return minimal if ext in ( - '.in', - '.md', - '.rst', - '.toml', - '.txt', + '.in', + '.md', + '.rst', + '.toml', + '.txt', ): return minimal @@ -757,17 +757,17 @@ class PathMapper: if path.startswith('test/lib/ansible_test/_data/requirements/'): if name in ( - 'integration', - 'network-integration', - 'windows-integration', + 'integration', + 'network-integration', + 'windows-integration', ): return { name: self.integration_all_target, } if name in ( - 'sanity', - 'units', + 'sanity', + 'units', ): return { name: 'all', @@ -826,11 +826,11 @@ class PathMapper: if '/' not in path: if path in ( - '.gitattributes', - '.gitignore', - '.mailmap', - 'COPYING', - 'Makefile', + '.gitattributes', + '.gitignore', + '.mailmap', + 'COPYING', + 'Makefile', ): return minimal @@ -840,11 +840,11 @@ class PathMapper: return all_tests(self.args) # broad impact, run all tests if ext in ( - '.in', - '.md', - '.rst', - '.toml', - '.txt', + '.in', + '.md', + '.rst', + '.toml', + '.txt', ): return minimal diff --git a/test/lib/ansible_test/_internal/classification/python.py b/test/lib/ansible_test/_internal/classification/python.py index df888738..77ffeacf 100644 --- a/test/lib/ansible_test/_internal/classification/python.py +++ b/test/lib/ansible_test/_internal/classification/python.py @@ -146,10 +146,8 @@ def get_python_module_utils_name(path: str) -> str: return name -def enumerate_module_utils(): - """Return a list of available module_utils imports. - :rtype: set[str] - """ +def enumerate_module_utils() -> set[str]: + """Return a list of available module_utils imports.""" module_utils = [] for path in data_context().content.walk_files(data_context().content.module_utils_path): diff --git a/test/lib/ansible_test/_internal/cli/argparsing/__init__.py b/test/lib/ansible_test/_internal/cli/argparsing/__init__.py index 9356442d..540cf552 100644 --- a/test/lib/ansible_test/_internal/cli/argparsing/__init__.py +++ b/test/lib/ansible_test/_internal/cli/argparsing/__init__.py @@ -34,17 +34,17 @@ class RegisteredCompletionFinder(OptionCompletionFinder): These registered completions, if provided, are used to filter the final completion results. This works around a known bug: https://github.com/kislyuk/argcomplete/issues/221 """ - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.registered_completions: t.Optional[list[str]] = None def completer( - self, - prefix: str, - action: argparse.Action, - parsed_args: argparse.Namespace, - **kwargs, + self, + prefix: str, + action: argparse.Action, + parsed_args: argparse.Namespace, + **kwargs, ) -> list[str]: """ Return a list of completions for the specified prefix and action. @@ -63,10 +63,10 @@ class RegisteredCompletionFinder(OptionCompletionFinder): @abc.abstractmethod def get_completions( - self, - prefix: str, - action: argparse.Action, - parsed_args: argparse.Namespace, + self, + prefix: str, + action: argparse.Action, + parsed_args: argparse.Namespace, ) -> list[str]: """ Return a list of completions for the specified prefix and action. @@ -89,9 +89,9 @@ class CompositeAction(argparse.Action, metaclass=abc.ABCMeta): documentation_state: dict[t.Type[CompositeAction], DocumentationState] = {} def __init__( - self, - *args, - **kwargs, + self, + *args, + **kwargs, ): self.definition = self.create_parser() self.documentation_state[type(self)] = documentation_state = DocumentationState() @@ -108,11 +108,11 @@ class CompositeAction(argparse.Action, metaclass=abc.ABCMeta): """Return a namespace parser to parse the argument associated with this action.""" def __call__( - self, - parser, - namespace, - values, - option_string=None, + self, + parser, + namespace, + values, + option_string=None, ): state = ParserState(mode=ParserMode.PARSE, namespaces=[namespace], remainder=values) @@ -135,10 +135,10 @@ class CompositeAction(argparse.Action, metaclass=abc.ABCMeta): class CompositeActionCompletionFinder(RegisteredCompletionFinder): """Completion finder with support for composite argument parsing.""" def get_completions( - self, - prefix: str, - action: argparse.Action, - parsed_args: argparse.Namespace, + self, + prefix: str, + action: argparse.Action, + parsed_args: argparse.Namespace, ) -> list[str]: """Return a list of completions appropriate for the given prefix and action, taking into account the arguments that have already been parsed.""" assert isinstance(action, CompositeAction) @@ -232,8 +232,8 @@ def detect_false_file_completion(value: str, mode: ParserMode) -> bool: def complete( - completer: Parser, - state: ParserState, + completer: Parser, + state: ParserState, ) -> Completion: """Perform argument completion using the given completer and return the completion result.""" value = state.remainder diff --git a/test/lib/ansible_test/_internal/cli/argparsing/argcompletion.py b/test/lib/ansible_test/_internal/cli/argparsing/argcompletion.py index df19b338..cf5776da 100644 --- a/test/lib/ansible_test/_internal/cli/argparsing/argcompletion.py +++ b/test/lib/ansible_test/_internal/cli/argparsing/argcompletion.py @@ -9,7 +9,7 @@ import typing as t class Substitute: """Substitute for missing class which accepts all arguments.""" - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: pass @@ -87,7 +87,7 @@ class OptionCompletionFinder(CompletionFinder): """ enabled = bool(argcomplete) - def __init__(self, *args, validator=None, **kwargs): + def __init__(self, *args, validator=None, **kwargs) -> None: if validator: raise ValueError() diff --git a/test/lib/ansible_test/_internal/cli/argparsing/parsers.py b/test/lib/ansible_test/_internal/cli/argparsing/parsers.py index 429b9c0c..d07e03cb 100644 --- a/test/lib/ansible_test/_internal/cli/argparsing/parsers.py +++ b/test/lib/ansible_test/_internal/cli/argparsing/parsers.py @@ -341,7 +341,7 @@ class IntegerParser(DynamicChoicesParser): class BooleanParser(ChoicesParser): """Composite argument parser for boolean (yes/no) values.""" - def __init__(self): + def __init__(self) -> None: super().__init__(['yes', 'no']) def parse(self, state: ParserState) -> bool: diff --git a/test/lib/ansible_test/_internal/cli/commands/__init__.py b/test/lib/ansible_test/_internal/cli/commands/__init__.py index 2ecd3a5e..2eb14abc 100644 --- a/test/lib/ansible_test/_internal/cli/commands/__init__.py +++ b/test/lib/ansible_test/_internal/cli/commands/__init__.py @@ -44,8 +44,8 @@ from .units import ( def do_commands( - parent: argparse.ArgumentParser, - completer: CompositeActionCompletionFinder, + parent: argparse.ArgumentParser, + completer: CompositeActionCompletionFinder, ) -> None: """Command line parsing for all commands.""" common = argparse.ArgumentParser(add_help=False) diff --git a/test/lib/ansible_test/_internal/cli/commands/coverage/__init__.py b/test/lib/ansible_test/_internal/cli/commands/coverage/__init__.py index 96beafab..28e67709 100644 --- a/test/lib/ansible_test/_internal/cli/commands/coverage/__init__.py +++ b/test/lib/ansible_test/_internal/cli/commands/coverage/__init__.py @@ -37,9 +37,9 @@ from .xml import ( def do_coverage( - subparsers, - parent: argparse.ArgumentParser, - completer: CompositeActionCompletionFinder, + subparsers, + parent: argparse.ArgumentParser, + completer: CompositeActionCompletionFinder, ) -> None: """Command line parsing for all `coverage` commands.""" coverage_common = argparse.ArgumentParser(add_help=False, parents=[parent]) @@ -61,7 +61,7 @@ def do_coverage( def add_coverage_common( - parser: argparse.ArgumentParser, + parser: argparse.ArgumentParser, ): """Add common coverage arguments.""" parser.add_argument( diff --git a/test/lib/ansible_test/_internal/cli/commands/coverage/analyze/__init__.py b/test/lib/ansible_test/_internal/cli/commands/coverage/analyze/__init__.py index 9dbf8c0c..05fbd233 100644 --- a/test/lib/ansible_test/_internal/cli/commands/coverage/analyze/__init__.py +++ b/test/lib/ansible_test/_internal/cli/commands/coverage/analyze/__init__.py @@ -13,9 +13,9 @@ from ....environments import ( def do_analyze( - subparsers, - parent: argparse.ArgumentParser, - completer: CompositeActionCompletionFinder, + subparsers, + parent: argparse.ArgumentParser, + completer: CompositeActionCompletionFinder, ) -> None: """Command line parsing for all `coverage analyze` commands.""" parser: argparse.ArgumentParser = subparsers.add_parser( diff --git a/test/lib/ansible_test/_internal/cli/commands/coverage/analyze/targets/__init__.py b/test/lib/ansible_test/_internal/cli/commands/coverage/analyze/targets/__init__.py index 429111e2..7b6ea3eb 100644 --- a/test/lib/ansible_test/_internal/cli/commands/coverage/analyze/targets/__init__.py +++ b/test/lib/ansible_test/_internal/cli/commands/coverage/analyze/targets/__init__.py @@ -29,9 +29,9 @@ from .missing import ( def do_targets( - subparsers, - parent: argparse.ArgumentParser, - completer: CompositeActionCompletionFinder, + subparsers, + parent: argparse.ArgumentParser, + completer: CompositeActionCompletionFinder, ) -> None: """Command line parsing for all `coverage analyze targets` commands.""" targets = subparsers.add_parser( diff --git a/test/lib/ansible_test/_internal/cli/commands/coverage/analyze/targets/combine.py b/test/lib/ansible_test/_internal/cli/commands/coverage/analyze/targets/combine.py index d25240b4..7fa49bf9 100644 --- a/test/lib/ansible_test/_internal/cli/commands/coverage/analyze/targets/combine.py +++ b/test/lib/ansible_test/_internal/cli/commands/coverage/analyze/targets/combine.py @@ -17,9 +17,9 @@ from .....environments import ( def do_combine( - subparsers, - parent: argparse.ArgumentParser, - completer: CompositeActionCompletionFinder, + subparsers, + parent: argparse.ArgumentParser, + completer: CompositeActionCompletionFinder, ): """Command line parsing for the `coverage analyze targets combine` command.""" parser: argparse.ArgumentParser = subparsers.add_parser( diff --git a/test/lib/ansible_test/_internal/cli/commands/coverage/analyze/targets/expand.py b/test/lib/ansible_test/_internal/cli/commands/coverage/analyze/targets/expand.py index d34a12bc..f5f020fe 100644 --- a/test/lib/ansible_test/_internal/cli/commands/coverage/analyze/targets/expand.py +++ b/test/lib/ansible_test/_internal/cli/commands/coverage/analyze/targets/expand.py @@ -17,9 +17,9 @@ from .....environments import ( def do_expand( - subparsers, - parent: argparse.ArgumentParser, - completer: CompositeActionCompletionFinder, + subparsers, + parent: argparse.ArgumentParser, + completer: CompositeActionCompletionFinder, ): """Command line parsing for the `coverage analyze targets expand` command.""" parser: argparse.ArgumentParser = subparsers.add_parser( diff --git a/test/lib/ansible_test/_internal/cli/commands/coverage/analyze/targets/filter.py b/test/lib/ansible_test/_internal/cli/commands/coverage/analyze/targets/filter.py index 395d42a9..afcb828b 100644 --- a/test/lib/ansible_test/_internal/cli/commands/coverage/analyze/targets/filter.py +++ b/test/lib/ansible_test/_internal/cli/commands/coverage/analyze/targets/filter.py @@ -17,9 +17,9 @@ from .....environments import ( def do_filter( - subparsers, - parent: argparse.ArgumentParser, - completer: CompositeActionCompletionFinder, + subparsers, + parent: argparse.ArgumentParser, + completer: CompositeActionCompletionFinder, ): """Command line parsing for the `coverage analyze targets filter` command.""" parser: argparse.ArgumentParser = subparsers.add_parser( diff --git a/test/lib/ansible_test/_internal/cli/commands/coverage/analyze/targets/generate.py b/test/lib/ansible_test/_internal/cli/commands/coverage/analyze/targets/generate.py index 0090d277..0d13933d 100644 --- a/test/lib/ansible_test/_internal/cli/commands/coverage/analyze/targets/generate.py +++ b/test/lib/ansible_test/_internal/cli/commands/coverage/analyze/targets/generate.py @@ -17,9 +17,9 @@ from .....environments import ( def do_generate( - subparsers, - parent: argparse.ArgumentParser, - completer: CompositeActionCompletionFinder, + subparsers, + parent: argparse.ArgumentParser, + completer: CompositeActionCompletionFinder, ): """Command line parsing for the `coverage analyze targets generate` command.""" parser: argparse.ArgumentParser = subparsers.add_parser( diff --git a/test/lib/ansible_test/_internal/cli/commands/coverage/analyze/targets/missing.py b/test/lib/ansible_test/_internal/cli/commands/coverage/analyze/targets/missing.py index 7a6b847c..8af236f3 100644 --- a/test/lib/ansible_test/_internal/cli/commands/coverage/analyze/targets/missing.py +++ b/test/lib/ansible_test/_internal/cli/commands/coverage/analyze/targets/missing.py @@ -17,9 +17,9 @@ from .....environments import ( def do_missing( - subparsers, - parent: argparse.ArgumentParser, - completer: CompositeActionCompletionFinder, + subparsers, + parent: argparse.ArgumentParser, + completer: CompositeActionCompletionFinder, ): """Command line parsing for the `coverage analyze targets missing` command.""" parser: argparse.ArgumentParser = subparsers.add_parser( diff --git a/test/lib/ansible_test/_internal/cli/commands/coverage/combine.py b/test/lib/ansible_test/_internal/cli/commands/coverage/combine.py index 03d3ae89..9b6d34a3 100644 --- a/test/lib/ansible_test/_internal/cli/commands/coverage/combine.py +++ b/test/lib/ansible_test/_internal/cli/commands/coverage/combine.py @@ -19,10 +19,10 @@ from ...environments import ( def do_combine( - subparsers, - parent: argparse.ArgumentParser, - add_coverage_common: c.Callable[[argparse.ArgumentParser], None], - completer: CompositeActionCompletionFinder, + subparsers, + parent: argparse.ArgumentParser, + add_coverage_common: c.Callable[[argparse.ArgumentParser], None], + completer: CompositeActionCompletionFinder, ) -> None: """Command line parsing for the `coverage combine` command.""" parser: argparse.ArgumentParser = subparsers.add_parser( diff --git a/test/lib/ansible_test/_internal/cli/commands/coverage/erase.py b/test/lib/ansible_test/_internal/cli/commands/coverage/erase.py index 2491fa0d..ef356f02 100644 --- a/test/lib/ansible_test/_internal/cli/commands/coverage/erase.py +++ b/test/lib/ansible_test/_internal/cli/commands/coverage/erase.py @@ -17,9 +17,9 @@ from ...environments import ( def do_erase( - subparsers, - parent: argparse.ArgumentParser, - completer: CompositeActionCompletionFinder, + subparsers, + parent: argparse.ArgumentParser, + completer: CompositeActionCompletionFinder, ) -> None: """Command line parsing for the `coverage erase` command.""" parser: argparse.ArgumentParser = subparsers.add_parser( diff --git a/test/lib/ansible_test/_internal/cli/commands/coverage/html.py b/test/lib/ansible_test/_internal/cli/commands/coverage/html.py index 5823b692..5f719de7 100644 --- a/test/lib/ansible_test/_internal/cli/commands/coverage/html.py +++ b/test/lib/ansible_test/_internal/cli/commands/coverage/html.py @@ -19,10 +19,10 @@ from ...environments import ( def do_html( - subparsers, - parent: argparse.ArgumentParser, - add_coverage_common: c.Callable[[argparse.ArgumentParser], None], - completer: CompositeActionCompletionFinder, + subparsers, + parent: argparse.ArgumentParser, + add_coverage_common: c.Callable[[argparse.ArgumentParser], None], + completer: CompositeActionCompletionFinder, ) -> None: """Command line parsing for the `coverage html` command.""" parser: argparse.ArgumentParser = subparsers.add_parser( diff --git a/test/lib/ansible_test/_internal/cli/commands/coverage/report.py b/test/lib/ansible_test/_internal/cli/commands/coverage/report.py index 93f868fd..e6a6e805 100644 --- a/test/lib/ansible_test/_internal/cli/commands/coverage/report.py +++ b/test/lib/ansible_test/_internal/cli/commands/coverage/report.py @@ -19,10 +19,10 @@ from ...environments import ( def do_report( - subparsers, - parent: argparse.ArgumentParser, - add_coverage_common: c.Callable[[argparse.ArgumentParser], None], - completer: CompositeActionCompletionFinder, + subparsers, + parent: argparse.ArgumentParser, + add_coverage_common: c.Callable[[argparse.ArgumentParser], None], + completer: CompositeActionCompletionFinder, ) -> None: """Command line parsing for the `coverage report` command.""" parser: argparse.ArgumentParser = subparsers.add_parser( diff --git a/test/lib/ansible_test/_internal/cli/commands/coverage/xml.py b/test/lib/ansible_test/_internal/cli/commands/coverage/xml.py index 07e66252..e7b03ca8 100644 --- a/test/lib/ansible_test/_internal/cli/commands/coverage/xml.py +++ b/test/lib/ansible_test/_internal/cli/commands/coverage/xml.py @@ -19,10 +19,10 @@ from ...environments import ( def do_xml( - subparsers, - parent: argparse.ArgumentParser, - add_coverage_common: c.Callable[[argparse.ArgumentParser], None], - completer: CompositeActionCompletionFinder, + subparsers, + parent: argparse.ArgumentParser, + add_coverage_common: c.Callable[[argparse.ArgumentParser], None], + completer: CompositeActionCompletionFinder, ) -> None: """Command line parsing for the `coverage xml` command.""" parser: argparse.ArgumentParser = subparsers.add_parser( diff --git a/test/lib/ansible_test/_internal/cli/commands/env.py b/test/lib/ansible_test/_internal/cli/commands/env.py index 87fa41ad..0cd21145 100644 --- a/test/lib/ansible_test/_internal/cli/commands/env.py +++ b/test/lib/ansible_test/_internal/cli/commands/env.py @@ -17,9 +17,9 @@ from ..environments import ( def do_env( - subparsers, - parent: argparse.ArgumentParser, - completer: CompositeActionCompletionFinder, + subparsers, + parent: argparse.ArgumentParser, + completer: CompositeActionCompletionFinder, ): """Command line parsing for the `env` command.""" parser: argparse.ArgumentParser = subparsers.add_parser( diff --git a/test/lib/ansible_test/_internal/cli/commands/integration/__init__.py b/test/lib/ansible_test/_internal/cli/commands/integration/__init__.py index 916249fa..dfdefb11 100644 --- a/test/lib/ansible_test/_internal/cli/commands/integration/__init__.py +++ b/test/lib/ansible_test/_internal/cli/commands/integration/__init__.py @@ -26,9 +26,9 @@ from .windows import ( def do_integration( - subparsers, - parent: argparse.ArgumentParser, - completer: CompositeActionCompletionFinder, + subparsers, + parent: argparse.ArgumentParser, + completer: CompositeActionCompletionFinder, ): """Command line parsing for all integration commands.""" parser = argparse.ArgumentParser( @@ -42,7 +42,7 @@ def do_integration( def add_integration_common( - parser: argparse.ArgumentParser, + parser: argparse.ArgumentParser, ): """Add common integration arguments.""" register_completer(parser.add_argument( diff --git a/test/lib/ansible_test/_internal/cli/commands/integration/network.py b/test/lib/ansible_test/_internal/cli/commands/integration/network.py index 4d0eb918..a05985b5 100644 --- a/test/lib/ansible_test/_internal/cli/commands/integration/network.py +++ b/test/lib/ansible_test/_internal/cli/commands/integration/network.py @@ -35,10 +35,10 @@ from ...completers import ( def do_network_integration( - subparsers, - parent: argparse.ArgumentParser, - add_integration_common: c.Callable[[argparse.ArgumentParser], None], - completer: CompositeActionCompletionFinder, + subparsers, + parent: argparse.ArgumentParser, + add_integration_common: c.Callable[[argparse.ArgumentParser], None], + completer: CompositeActionCompletionFinder, ): """Command line parsing for the `network-integration` command.""" parser: argparse.ArgumentParser = subparsers.add_parser( diff --git a/test/lib/ansible_test/_internal/cli/commands/integration/posix.py b/test/lib/ansible_test/_internal/cli/commands/integration/posix.py index e6a9527c..78d61658 100644 --- a/test/lib/ansible_test/_internal/cli/commands/integration/posix.py +++ b/test/lib/ansible_test/_internal/cli/commands/integration/posix.py @@ -26,10 +26,10 @@ from ...environments import ( def do_posix_integration( - subparsers, - parent: argparse.ArgumentParser, - add_integration_common: c.Callable[[argparse.ArgumentParser], None], - completer: CompositeActionCompletionFinder, + subparsers, + parent: argparse.ArgumentParser, + add_integration_common: c.Callable[[argparse.ArgumentParser], None], + completer: CompositeActionCompletionFinder, ): """Command line parsing for the `integration` command.""" parser: argparse.ArgumentParser = subparsers.add_parser( diff --git a/test/lib/ansible_test/_internal/cli/commands/integration/windows.py b/test/lib/ansible_test/_internal/cli/commands/integration/windows.py index cfbdb44f..ab022e3b 100644 --- a/test/lib/ansible_test/_internal/cli/commands/integration/windows.py +++ b/test/lib/ansible_test/_internal/cli/commands/integration/windows.py @@ -26,10 +26,10 @@ from ...environments import ( def do_windows_integration( - subparsers, - parent: argparse.ArgumentParser, - add_integration_common: c.Callable[[argparse.ArgumentParser], None], - completer: CompositeActionCompletionFinder, + subparsers, + parent: argparse.ArgumentParser, + add_integration_common: c.Callable[[argparse.ArgumentParser], None], + completer: CompositeActionCompletionFinder, ): """Command line parsing for the `windows-integration` command.""" parser: argparse.ArgumentParser = subparsers.add_parser( diff --git a/test/lib/ansible_test/_internal/cli/commands/sanity.py b/test/lib/ansible_test/_internal/cli/commands/sanity.py index 36f0ec58..8b4a9ae5 100644 --- a/test/lib/ansible_test/_internal/cli/commands/sanity.py +++ b/test/lib/ansible_test/_internal/cli/commands/sanity.py @@ -29,9 +29,9 @@ from ..environments import ( def do_sanity( - subparsers, - parent: argparse.ArgumentParser, - completer: CompositeActionCompletionFinder, + subparsers, + parent: argparse.ArgumentParser, + completer: CompositeActionCompletionFinder, ): """Command line parsing for the `sanity` command.""" parser: argparse.ArgumentParser = subparsers.add_parser( diff --git a/test/lib/ansible_test/_internal/cli/commands/shell.py b/test/lib/ansible_test/_internal/cli/commands/shell.py index 16fb8b44..1baffc6e 100644 --- a/test/lib/ansible_test/_internal/cli/commands/shell.py +++ b/test/lib/ansible_test/_internal/cli/commands/shell.py @@ -20,9 +20,9 @@ from ..environments import ( def do_shell( - subparsers, - parent: argparse.ArgumentParser, - completer: CompositeActionCompletionFinder, + subparsers, + parent: argparse.ArgumentParser, + completer: CompositeActionCompletionFinder, ): """Command line parsing for the `shell` command.""" parser: argparse.ArgumentParser = subparsers.add_parser( diff --git a/test/lib/ansible_test/_internal/cli/commands/units.py b/test/lib/ansible_test/_internal/cli/commands/units.py index ab7f055c..c541a872 100644 --- a/test/lib/ansible_test/_internal/cli/commands/units.py +++ b/test/lib/ansible_test/_internal/cli/commands/units.py @@ -24,9 +24,9 @@ from ..environments import ( def do_units( - subparsers, - parent: argparse.ArgumentParser, - completer: CompositeActionCompletionFinder, + subparsers, + parent: argparse.ArgumentParser, + completer: CompositeActionCompletionFinder, ): """Command line parsing for the `units` command.""" parser: argparse.ArgumentParser = subparsers.add_parser( diff --git a/test/lib/ansible_test/_internal/cli/compat.py b/test/lib/ansible_test/_internal/cli/compat.py index c69b54d7..93006d5c 100644 --- a/test/lib/ansible_test/_internal/cli/compat.py +++ b/test/lib/ansible_test/_internal/cli/compat.py @@ -84,25 +84,25 @@ def get_option_name(name: str) -> str: class PythonVersionUnsupportedError(ApplicationError): """A Python version was requested for a context which does not support that version.""" - def __init__(self, context, version, versions): + def __init__(self, context: str, version: str, versions: c.Iterable[str]) -> None: super().__init__(f'Python {version} is not supported by environment `{context}`. Supported Python version(s) are: {", ".join(versions)}') class PythonVersionUnspecifiedError(ApplicationError): """A Python version was not specified for a context which is unknown, thus the Python version is unknown.""" - def __init__(self, context): + def __init__(self, context: str) -> None: super().__init__(f'A Python version was not specified for environment `{context}`. Use the `--python` option to specify a Python version.') class ControllerNotSupportedError(ApplicationError): """Option(s) were specified which do not provide support for the controller and would be ignored because they are irrelevant for the target.""" - def __init__(self, context): + def __init__(self, context: str) -> None: super().__init__(f'Environment `{context}` does not provide a Python version supported by the controller.') class OptionsConflictError(ApplicationError): """Option(s) were specified which conflict with other options.""" - def __init__(self, first, second): + def __init__(self, first: c.Iterable[str], second: c.Iterable[str]) -> None: super().__init__(f'Options `{" ".join(first)}` cannot be combined with options `{" ".join(second)}`.') @@ -170,30 +170,30 @@ class TargetMode(enum.Enum): NO_TARGETS = enum.auto() # coverage @property - def one_host(self): + def one_host(self) -> bool: """Return True if only one host (the controller) should be used, otherwise return False.""" return self in (TargetMode.SANITY, TargetMode.UNITS, TargetMode.NO_TARGETS) @property - def no_fallback(self): + def no_fallback(self) -> bool: """Return True if no fallback is acceptable for the controller (due to options not applying to the target), otherwise return False.""" return self in (TargetMode.WINDOWS_INTEGRATION, TargetMode.NETWORK_INTEGRATION, TargetMode.NO_TARGETS) @property - def multiple_pythons(self): + def multiple_pythons(self) -> bool: """Return True if multiple Python versions are allowed, otherwise False.""" return self in (TargetMode.SANITY, TargetMode.UNITS) @property - def has_python(self): + def has_python(self) -> bool: """Return True if this mode uses Python, otherwise False.""" return self in (TargetMode.POSIX_INTEGRATION, TargetMode.SANITY, TargetMode.UNITS, TargetMode.SHELL) def convert_legacy_args( - argv: list[str], - args: t.Union[argparse.Namespace, types.SimpleNamespace], - mode: TargetMode, + argv: list[str], + args: t.Union[argparse.Namespace, types.SimpleNamespace], + mode: TargetMode, ) -> HostSettings: """Convert pre-split host arguments in the given namespace to their split counterparts.""" old_options = LegacyHostOptions.create(args) @@ -262,9 +262,9 @@ def convert_legacy_args( def controller_targets( - mode: TargetMode, - options: LegacyHostOptions, - controller: ControllerHostConfig, + mode: TargetMode, + options: LegacyHostOptions, + controller: ControllerHostConfig, ) -> list[HostConfig]: """Return the configuration for controller targets.""" python = native_python(options) @@ -288,8 +288,8 @@ def native_python(options: LegacyHostOptions) -> t.Optional[NativePythonConfig]: def get_legacy_host_config( - mode: TargetMode, - options: LegacyHostOptions, + mode: TargetMode, + options: LegacyHostOptions, ) -> tuple[ControllerHostConfig, list[HostConfig], t.Optional[FallbackDetail]]: """ Returns controller and target host configs derived from the provided legacy host options. diff --git a/test/lib/ansible_test/_internal/cli/environments.py b/test/lib/ansible_test/_internal/cli/environments.py index 1dde9e63..5063715a 100644 --- a/test/lib/ansible_test/_internal/cli/environments.py +++ b/test/lib/ansible_test/_internal/cli/environments.py @@ -81,10 +81,10 @@ class ControllerMode(enum.Enum): def add_environments( - parser: argparse.ArgumentParser, - completer: CompositeActionCompletionFinder, - controller_mode: ControllerMode, - target_mode: TargetMode, + parser: argparse.ArgumentParser, + completer: CompositeActionCompletionFinder, + controller_mode: ControllerMode, + target_mode: TargetMode, ) -> None: """Add arguments for the environments used to run ansible-test and commands it invokes.""" no_environment = controller_mode == ControllerMode.NO_DELEGATION and target_mode == TargetMode.NO_TARGETS @@ -114,8 +114,8 @@ def add_environments( def add_global_options( - parser: argparse.ArgumentParser, - controller_mode: ControllerMode, + parser: argparse.ArgumentParser, + controller_mode: ControllerMode, ): """Add global options for controlling the test environment that work with both the legacy and composite options.""" global_parser = t.cast(argparse.ArgumentParser, parser.add_argument_group(title='global environment arguments')) @@ -156,10 +156,10 @@ def add_global_options( def add_composite_environment_options( - parser: argparse.ArgumentParser, - completer: CompositeActionCompletionFinder, - controller_mode: ControllerMode, - target_mode: TargetMode, + parser: argparse.ArgumentParser, + completer: CompositeActionCompletionFinder, + controller_mode: ControllerMode, + target_mode: TargetMode, ) -> list[t.Type[CompositeAction]]: """Add composite options for controlling the test environment.""" composite_parser = t.cast(argparse.ArgumentParser, parser.add_argument_group( @@ -246,9 +246,9 @@ def add_composite_environment_options( def add_legacy_environment_options( - parser: argparse.ArgumentParser, - controller_mode: ControllerMode, - target_mode: TargetMode, + parser: argparse.ArgumentParser, + controller_mode: ControllerMode, + target_mode: TargetMode, ): """Add legacy options for controlling the test environment.""" environment: argparse.ArgumentParser = parser.add_argument_group( # type: ignore[assignment] # real type private @@ -259,8 +259,8 @@ def add_legacy_environment_options( def add_environments_python( - environments_parser: argparse.ArgumentParser, - target_mode: TargetMode, + environments_parser: argparse.ArgumentParser, + target_mode: TargetMode, ) -> None: """Add environment arguments to control the Python version(s) used.""" python_versions: tuple[str, ...] @@ -285,9 +285,9 @@ def add_environments_python( def add_environments_host( - environments_parser: argparse.ArgumentParser, - controller_mode: ControllerMode, - target_mode: TargetMode, + environments_parser: argparse.ArgumentParser, + controller_mode: ControllerMode, + target_mode: TargetMode, ) -> None: """Add environment arguments for the given host and argument modes.""" environments_exclusive_group: argparse.ArgumentParser = environments_parser.add_mutually_exclusive_group() # type: ignore[assignment] # real type private @@ -341,7 +341,7 @@ def add_environment_network( def add_environment_windows( - environments_parser: argparse.ArgumentParser, + environments_parser: argparse.ArgumentParser, ) -> None: """Add environment arguments for running on a windows host.""" register_completer(environments_parser.add_argument( @@ -359,7 +359,7 @@ def add_environment_windows( def add_environment_local( - exclusive_parser: argparse.ArgumentParser, + exclusive_parser: argparse.ArgumentParser, ) -> None: """Add environment arguments for running on the local (origin) host.""" exclusive_parser.add_argument( @@ -370,8 +370,8 @@ def add_environment_local( def add_environment_venv( - exclusive_parser: argparse.ArgumentParser, - environments_parser: argparse.ArgumentParser, + exclusive_parser: argparse.ArgumentParser, + environments_parser: argparse.ArgumentParser, ) -> None: """Add environment arguments for running in ansible-test managed virtual environments.""" exclusive_parser.add_argument( @@ -387,8 +387,8 @@ def add_environment_venv( def add_global_docker( - parser: argparse.ArgumentParser, - controller_mode: ControllerMode, + parser: argparse.ArgumentParser, + controller_mode: ControllerMode, ) -> None: """Add global options for Docker.""" if controller_mode != ControllerMode.DELEGATED: @@ -450,9 +450,9 @@ def add_global_docker( def add_environment_docker( - exclusive_parser: argparse.ArgumentParser, - environments_parser: argparse.ArgumentParser, - target_mode: TargetMode, + exclusive_parser: argparse.ArgumentParser, + environments_parser: argparse.ArgumentParser, + target_mode: TargetMode, ) -> None: """Add environment arguments for running in docker containers.""" if target_mode in (TargetMode.POSIX_INTEGRATION, TargetMode.SHELL): @@ -490,8 +490,8 @@ def add_environment_docker( def add_global_remote( - parser: argparse.ArgumentParser, - controller_mode: ControllerMode, + parser: argparse.ArgumentParser, + controller_mode: ControllerMode, ) -> None: """Add global options for remote instances.""" if controller_mode != ControllerMode.DELEGATED: @@ -529,9 +529,9 @@ def add_global_remote( def add_environment_remote( - exclusive_parser: argparse.ArgumentParser, - environments_parser: argparse.ArgumentParser, - target_mode: TargetMode, + exclusive_parser: argparse.ArgumentParser, + environments_parser: argparse.ArgumentParser, + target_mode: TargetMode, ) -> None: """Add environment arguments for running in ansible-core-ci provisioned remote virtual machines.""" if target_mode == TargetMode.POSIX_INTEGRATION: diff --git a/test/lib/ansible_test/_internal/cli/parsers/base_argument_parsers.py b/test/lib/ansible_test/_internal/cli/parsers/base_argument_parsers.py index ed933bd5..aac7a694 100644 --- a/test/lib/ansible_test/_internal/cli/parsers/base_argument_parsers.py +++ b/test/lib/ansible_test/_internal/cli/parsers/base_argument_parsers.py @@ -69,5 +69,5 @@ class TargetsNamespaceParser(NamespaceParser, metaclass=abc.ABCMeta): class ControllerRequiredFirstError(CompletionError): """Exception raised when controller and target options are specified out-of-order.""" - def __init__(self): + def __init__(self) -> None: super().__init__('The `--controller` option must be specified before `--target` option(s).') diff --git a/test/lib/ansible_test/_internal/cli/parsers/key_value_parsers.py b/test/lib/ansible_test/_internal/cli/parsers/key_value_parsers.py index a6af7f80..049b71ee 100644 --- a/test/lib/ansible_test/_internal/cli/parsers/key_value_parsers.py +++ b/test/lib/ansible_test/_internal/cli/parsers/key_value_parsers.py @@ -99,7 +99,7 @@ class ControllerKeyValueParser(KeyValueParser): class DockerKeyValueParser(KeyValueParser): """Composite argument parser for docker key/value pairs.""" - def __init__(self, image, controller): + def __init__(self, image: str, controller: bool) -> None: self.controller = controller self.versions = get_docker_pythons(image, controller, False) self.allow_default = bool(get_docker_pythons(image, controller, True)) @@ -135,7 +135,7 @@ class DockerKeyValueParser(KeyValueParser): class PosixRemoteKeyValueParser(KeyValueParser): """Composite argument parser for POSIX remote key/value pairs.""" - def __init__(self, name, controller): + def __init__(self, name: str, controller: bool) -> None: self.controller = controller self.versions = get_remote_pythons(name, controller, False) self.allow_default = bool(get_remote_pythons(name, controller, True)) diff --git a/test/lib/ansible_test/_internal/commands/coverage/__init__.py b/test/lib/ansible_test/_internal/commands/coverage/__init__.py index cdf2d544..139cf3c6 100644 --- a/test/lib/ansible_test/_internal/commands/coverage/__init__.py +++ b/test/lib/ansible_test/_internal/commands/coverage/__init__.py @@ -2,7 +2,6 @@ from __future__ import annotations import collections.abc as c -import errno import json import os import re @@ -37,7 +36,7 @@ from ...python_requirements import ( install_requirements, ) -from ... target import ( +from ...target import ( walk_module_targets, ) @@ -135,11 +134,8 @@ def get_coverage_files(language: str, path: t.Optional[str] = None) -> list[str] try: coverage_files = [os.path.join(coverage_dir, f) for f in os.listdir(coverage_dir) if '=coverage.' in f and '=%s' % language in f] - except IOError as ex: - if ex.errno == errno.ENOENT: - return [] - - raise + except FileNotFoundError: + return [] return coverage_files @@ -162,11 +158,11 @@ def get_python_modules() -> dict[str, str]: def enumerate_python_arcs( - path: str, - coverage: coverage_module, - modules: dict[str, str], - collection_search_re: t.Optional[t.Pattern], - collection_sub_re: t.Optional[t.Pattern], + path: str, + coverage: coverage_module, + modules: dict[str, str], + collection_search_re: t.Optional[t.Pattern], + collection_sub_re: t.Optional[t.Pattern], ) -> c.Generator[tuple[str, set[tuple[int, int]]], None, None]: """Enumerate Python code coverage arcs in the given file.""" if os.path.getsize(path) == 0: @@ -231,7 +227,7 @@ def read_python_coverage_legacy(path: str) -> PythonArcs: contents = read_text_file(path) contents = re.sub(r'''^!coverage.py: This is a private format, don't read it directly!''', '', contents) data = json.loads(contents) - arcs: PythonArcs = {filename: [tuple(arc) for arc in arcs] for filename, arcs in data['arcs'].items()} + arcs: PythonArcs = {filename: [t.cast(tuple[int, int], tuple(arc)) for arc in arc_list] for filename, arc_list in data['arcs'].items()} except Exception as ex: raise CoverageError(path, f'Error reading JSON coverage file: {ex}') from ex @@ -239,9 +235,9 @@ def read_python_coverage_legacy(path: str) -> PythonArcs: def enumerate_powershell_lines( - path: str, - collection_search_re: t.Optional[t.Pattern], - collection_sub_re: t.Optional[t.Pattern], + path: str, + collection_search_re: t.Optional[t.Pattern], + collection_sub_re: t.Optional[t.Pattern], ) -> c.Generator[tuple[str, dict[int, int]], None, None]: """Enumerate PowerShell code coverage lines in the given file.""" if os.path.getsize(path) == 0: @@ -278,10 +274,10 @@ def enumerate_powershell_lines( def sanitize_filename( - filename: str, - modules: t.Optional[dict[str, str]] = None, - collection_search_re: t.Optional[t.Pattern] = None, - collection_sub_re: t.Optional[t.Pattern] = None, + filename: str, + modules: t.Optional[dict[str, str]] = None, + collection_search_re: t.Optional[t.Pattern] = None, + collection_sub_re: t.Optional[t.Pattern] = None, ) -> t.Optional[str]: """Convert the given code coverage path to a local absolute path and return its, or None if the path is not valid.""" ansible_path = os.path.abspath('lib/ansible/') + '/' diff --git a/test/lib/ansible_test/_internal/commands/coverage/analyze/targets/__init__.py b/test/lib/ansible_test/_internal/commands/coverage/analyze/targets/__init__.py index f16f7b4f..ad6cf86f 100644 --- a/test/lib/ansible_test/_internal/commands/coverage/analyze/targets/__init__.py +++ b/test/lib/ansible_test/_internal/commands/coverage/analyze/targets/__init__.py @@ -20,6 +20,7 @@ from .. import ( ) TargetKey = t.TypeVar('TargetKey', int, tuple[int, int]) +TFlexKey = t.TypeVar('TFlexKey', int, tuple[int, int], str) NamedPoints = dict[str, dict[TargetKey, set[str]]] IndexedPoints = dict[str, dict[TargetKey, set[int]]] Arcs = dict[str, dict[tuple[int, int], set[int]]] @@ -118,12 +119,12 @@ def get_target_index(name: str, target_indexes: TargetIndexes) -> int: def expand_indexes( - source_data: IndexedPoints, - source_index: list[str], - format_func: c.Callable[[TargetKey], str], -) -> NamedPoints: + source_data: IndexedPoints, + source_index: list[str], + format_func: c.Callable[[TargetKey], TFlexKey], +) -> dict[str, dict[TFlexKey, set[str]]]: """Expand indexes from the source into target names for easier processing of the data (arcs or lines).""" - combined_data: dict[str, dict[t.Any, set[str]]] = {} + combined_data: dict[str, dict[TFlexKey, set[str]]] = {} for covered_path, covered_points in source_data.items(): combined_points = combined_data.setdefault(covered_path, {}) diff --git a/test/lib/ansible_test/_internal/commands/coverage/analyze/targets/combine.py b/test/lib/ansible_test/_internal/commands/coverage/analyze/targets/combine.py index e7698974..e3782cee 100644 --- a/test/lib/ansible_test/_internal/commands/coverage/analyze/targets/combine.py +++ b/test/lib/ansible_test/_internal/commands/coverage/analyze/targets/combine.py @@ -58,10 +58,10 @@ def command_coverage_analyze_targets_combine(args: CoverageAnalyzeTargetsCombine def merge_indexes( - source_data: IndexedPoints, - source_index: list[str], - combined_data: IndexedPoints, - combined_index: TargetIndexes, + source_data: IndexedPoints, + source_index: list[str], + combined_data: IndexedPoints, + combined_index: TargetIndexes, ) -> None: """Merge indexes from the source into the combined data set (arcs or lines).""" for covered_path, covered_points in source_data.items(): diff --git a/test/lib/ansible_test/_internal/commands/coverage/analyze/targets/filter.py b/test/lib/ansible_test/_internal/commands/coverage/analyze/targets/filter.py index f1d8551a..29a8ee5b 100644 --- a/test/lib/ansible_test/_internal/commands/coverage/analyze/targets/filter.py +++ b/test/lib/ansible_test/_internal/commands/coverage/analyze/targets/filter.py @@ -24,6 +24,7 @@ from . import ( from . import ( NamedPoints, + TargetKey, TargetIndexes, ) @@ -50,8 +51,12 @@ def command_coverage_analyze_targets_filter(args: CoverageAnalyzeTargetsFilterCo covered_targets, covered_path_arcs, covered_path_lines = read_report(args.input_file) - filtered_path_arcs = expand_indexes(covered_path_arcs, covered_targets, lambda v: v) - filtered_path_lines = expand_indexes(covered_path_lines, covered_targets, lambda v: v) + def pass_target_key(value: TargetKey) -> TargetKey: + """Return the given target key unmodified.""" + return value + + filtered_path_arcs = expand_indexes(covered_path_arcs, covered_targets, pass_target_key) + filtered_path_lines = expand_indexes(covered_path_lines, covered_targets, pass_target_key) include_targets = set(args.include_targets) if args.include_targets else None exclude_targets = set(args.exclude_targets) if args.exclude_targets else None @@ -59,7 +64,7 @@ def command_coverage_analyze_targets_filter(args: CoverageAnalyzeTargetsFilterCo include_path = re.compile(args.include_path) if args.include_path else None exclude_path = re.compile(args.exclude_path) if args.exclude_path else None - def path_filter_func(path): + def path_filter_func(path: str) -> bool: """Return True if the given path should be included, otherwise return False.""" if include_path and not re.search(include_path, path): return False @@ -69,7 +74,7 @@ def command_coverage_analyze_targets_filter(args: CoverageAnalyzeTargetsFilterCo return True - def target_filter_func(targets): + def target_filter_func(targets: set[str]) -> set[str]: """Filter the given targets and return the result based on the defined includes and excludes.""" if include_targets: targets &= include_targets @@ -92,9 +97,9 @@ def command_coverage_analyze_targets_filter(args: CoverageAnalyzeTargetsFilterCo def filter_data( - data: NamedPoints, - path_filter_func: c.Callable[[str], bool], - target_filter_func: c.Callable[[set[str]], set[str]], + data: NamedPoints, + path_filter_func: c.Callable[[str], bool], + target_filter_func: c.Callable[[set[str]], set[str]], ) -> NamedPoints: """Filter the data set using the specified filter function.""" result: NamedPoints = {} diff --git a/test/lib/ansible_test/_internal/commands/coverage/analyze/targets/generate.py b/test/lib/ansible_test/_internal/commands/coverage/analyze/targets/generate.py index 2c61190a..127b5b7f 100644 --- a/test/lib/ansible_test/_internal/commands/coverage/analyze/targets/generate.py +++ b/test/lib/ansible_test/_internal/commands/coverage/analyze/targets/generate.py @@ -75,10 +75,10 @@ def command_coverage_analyze_targets_generate(args: CoverageAnalyzeTargetsGenera def analyze_python_coverage( - args: CoverageAnalyzeTargetsGenerateConfig, - host_state: HostState, - path: str, - target_indexes: TargetIndexes, + args: CoverageAnalyzeTargetsGenerateConfig, + host_state: HostState, + path: str, + target_indexes: TargetIndexes, ) -> Arcs: """Analyze Python code coverage.""" results: Arcs = {} @@ -107,9 +107,9 @@ def analyze_python_coverage( def analyze_powershell_coverage( - args: CoverageAnalyzeTargetsGenerateConfig, - path: str, - target_indexes: TargetIndexes, + args: CoverageAnalyzeTargetsGenerateConfig, + path: str, + target_indexes: TargetIndexes, ) -> Lines: """Analyze PowerShell code coverage""" results: Lines = {} @@ -136,9 +136,9 @@ def analyze_powershell_coverage( def prune_invalid_filenames( - args: CoverageAnalyzeTargetsGenerateConfig, - results: dict[str, t.Any], - collection_search_re: t.Optional[t.Pattern] = None, + args: CoverageAnalyzeTargetsGenerateConfig, + results: dict[str, t.Any], + collection_search_re: t.Optional[t.Pattern] = None, ) -> None: """Remove invalid filenames from the given result set.""" path_checker = PathChecker(args, collection_search_re) diff --git a/test/lib/ansible_test/_internal/commands/coverage/analyze/targets/missing.py b/test/lib/ansible_test/_internal/commands/coverage/analyze/targets/missing.py index 4d6b469e..c1c77e75 100644 --- a/test/lib/ansible_test/_internal/commands/coverage/analyze/targets/missing.py +++ b/test/lib/ansible_test/_internal/commands/coverage/analyze/targets/missing.py @@ -66,11 +66,11 @@ def command_coverage_analyze_targets_missing(args: CoverageAnalyzeTargetsMissing def find_gaps( - from_data: IndexedPoints, - from_index: list[str], - to_data: IndexedPoints, - target_indexes: TargetIndexes, - only_exists: bool, + from_data: IndexedPoints, + from_index: list[str], + to_data: IndexedPoints, + target_indexes: TargetIndexes, + only_exists: bool, ) -> IndexedPoints: """Find gaps in coverage between the from and to data sets.""" target_data: IndexedPoints = {} @@ -91,12 +91,12 @@ def find_gaps( def find_missing( - from_data: IndexedPoints, - from_index: list[str], - to_data: IndexedPoints, - to_index: list[str], - target_indexes: TargetIndexes, - only_exists: bool, + from_data: IndexedPoints, + from_index: list[str], + to_data: IndexedPoints, + to_index: list[str], + target_indexes: TargetIndexes, + only_exists: bool, ) -> IndexedPoints: """Find coverage in from_data not present in to_data (arcs or lines).""" target_data: IndexedPoints = {} diff --git a/test/lib/ansible_test/_internal/commands/coverage/combine.py b/test/lib/ansible_test/_internal/commands/coverage/combine.py index cb086fd5..66210c73 100644 --- a/test/lib/ansible_test/_internal/commands/coverage/combine.py +++ b/test/lib/ansible_test/_internal/commands/coverage/combine.py @@ -101,7 +101,7 @@ def combine_coverage_files(args: CoverageCombineConfig, host_state: HostState) - class ExportedCoverageDataNotFound(ApplicationError): """Exception when no combined coverage data is present yet is required.""" - def __init__(self): + def __init__(self) -> None: super().__init__( 'Coverage data must be exported before processing with the `--docker` or `--remote` option.\n' 'Export coverage with `ansible-test coverage combine` using the `--export` option.\n' @@ -283,9 +283,9 @@ def _get_coverage_targets(args: CoverageCombineConfig, walk_func: c.Callable) -> def _build_stub_groups( - args: CoverageCombineConfig, - sources: list[tuple[str, int]], - default_stub_value: c.Callable[[list[str]], dict[str, TValue]], + args: CoverageCombineConfig, + sources: list[tuple[str, int]], + default_stub_value: c.Callable[[list[str]], dict[str, TValue]], ) -> dict[str, dict[str, TValue]]: """ Split the given list of sources with line counts into groups, maintaining a maximum line count for each group. diff --git a/test/lib/ansible_test/_internal/commands/integration/__init__.py b/test/lib/ansible_test/_internal/commands/integration/__init__.py index 33bd45f6..8864d2ee 100644 --- a/test/lib/ansible_test/_internal/commands/integration/__init__.py +++ b/test/lib/ansible_test/_internal/commands/integration/__init__.py @@ -240,9 +240,9 @@ def delegate_inventory(args: IntegrationConfig, inventory_path_src: str) -> None @contextlib.contextmanager def integration_test_environment( - args: IntegrationConfig, - target: IntegrationTarget, - inventory_path_src: str, + args: IntegrationConfig, + target: IntegrationTarget, + inventory_path_src: str, ) -> c.Iterator[IntegrationEnvironment]: """Context manager that prepares the integration test environment and cleans it up.""" ansible_config_src = args.get_ansible_config() @@ -343,9 +343,9 @@ def integration_test_environment( @contextlib.contextmanager def integration_test_config_file( - args: IntegrationConfig, - env_config: CloudEnvironmentConfig, - integration_dir: str, + args: IntegrationConfig, + env_config: CloudEnvironmentConfig, + integration_dir: str, ) -> c.Iterator[t.Optional[str]]: """Context manager that provides a config file for integration tests, if needed.""" if not env_config: @@ -372,10 +372,10 @@ def integration_test_config_file( def create_inventory( - args: IntegrationConfig, - host_state: HostState, - inventory_path: str, - target: IntegrationTarget, + args: IntegrationConfig, + host_state: HostState, + inventory_path: str, + target: IntegrationTarget, ) -> None: """Create inventory.""" if isinstance(args, PosixIntegrationConfig): @@ -398,13 +398,13 @@ def create_inventory( def command_integration_filtered( - args: IntegrationConfig, - host_state: HostState, - targets: tuple[IntegrationTarget, ...], - all_targets: tuple[IntegrationTarget, ...], - inventory_path: str, - pre_target: t.Optional[c.Callable[[IntegrationTarget], None]] = None, - post_target: t.Optional[c.Callable[[IntegrationTarget], None]] = None, + args: IntegrationConfig, + host_state: HostState, + targets: tuple[IntegrationTarget, ...], + all_targets: tuple[IntegrationTarget, ...], + inventory_path: str, + pre_target: t.Optional[c.Callable[[IntegrationTarget], None]] = None, + post_target: t.Optional[c.Callable[[IntegrationTarget], None]] = None, ): """Run integration tests for the specified targets.""" found = False @@ -577,12 +577,12 @@ def command_integration_filtered( def command_integration_script( - args: IntegrationConfig, - host_state: HostState, - target: IntegrationTarget, - test_dir: str, - inventory_path: str, - coverage_manager: CoverageManager, + args: IntegrationConfig, + host_state: HostState, + target: IntegrationTarget, + test_dir: str, + inventory_path: str, + coverage_manager: CoverageManager, ): """Run an integration test script.""" display.info('Running %s integration test script' % target.name) @@ -629,13 +629,13 @@ def command_integration_script( def command_integration_role( - args: IntegrationConfig, - host_state: HostState, - target: IntegrationTarget, - start_at_task: t.Optional[str], - test_dir: str, - inventory_path: str, - coverage_manager: CoverageManager, + args: IntegrationConfig, + host_state: HostState, + target: IntegrationTarget, + start_at_task: t.Optional[str], + test_dir: str, + inventory_path: str, + coverage_manager: CoverageManager, ): """Run an integration test role.""" display.info('Running %s integration test role' % target.name) @@ -748,15 +748,15 @@ def command_integration_role( def run_setup_targets( - args: IntegrationConfig, - host_state: HostState, - test_dir: str, - target_names: c.Sequence[str], - targets_dict: dict[str, IntegrationTarget], - targets_executed: set[str], - inventory_path: str, - coverage_manager: CoverageManager, - always: bool, + args: IntegrationConfig, + host_state: HostState, + test_dir: str, + target_names: c.Sequence[str], + targets_dict: dict[str, IntegrationTarget], + targets_executed: set[str], + inventory_path: str, + coverage_manager: CoverageManager, + always: bool, ): """Run setup targets.""" for target_name in target_names: @@ -779,13 +779,13 @@ def run_setup_targets( def integration_environment( - args: IntegrationConfig, - target: IntegrationTarget, - test_dir: str, - inventory_path: str, - ansible_config: t.Optional[str], - env_config: t.Optional[CloudEnvironmentConfig], - test_env: IntegrationEnvironment, + args: IntegrationConfig, + target: IntegrationTarget, + test_dir: str, + inventory_path: str, + ansible_config: t.Optional[str], + env_config: t.Optional[CloudEnvironmentConfig], + test_env: IntegrationEnvironment, ) -> dict[str, str]: """Return a dictionary of environment variables to use when running the given integration test target.""" env = ansible_environment(args, ansible_config=ansible_config) @@ -819,7 +819,7 @@ def integration_environment( class IntegrationEnvironment: """Details about the integration environment.""" - def __init__(self, test_dir, integration_dir, targets_dir, inventory_path, ansible_config, vars_file): + def __init__(self, test_dir: str, integration_dir: str, targets_dir: str, inventory_path: str, ansible_config: str, vars_file: str) -> None: self.test_dir = test_dir self.integration_dir = integration_dir self.targets_dir = targets_dir @@ -831,17 +831,13 @@ class IntegrationEnvironment: class IntegrationCache(CommonCache): """Integration cache.""" @property - def integration_targets(self): - """ - :rtype: list[IntegrationTarget] - """ + def integration_targets(self) -> list[IntegrationTarget]: + """The list of integration test targets.""" return self.get('integration_targets', lambda: list(walk_integration_targets())) @property - def dependency_map(self): - """ - :rtype: dict[str, set[IntegrationTarget]] - """ + def dependency_map(self) -> dict[str, set[IntegrationTarget]]: + """The dependency map of integration test targets.""" return self.get('dependency_map', lambda: generate_dependency_map(self.integration_targets)) 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 32d90d6f..0c078b98 100644 --- a/test/lib/ansible_test/_internal/commands/integration/cloud/__init__.py +++ b/test/lib/ansible_test/_internal/commands/integration/cloud/__init__.py @@ -288,14 +288,14 @@ class CloudProvider(CloudBase): exclude.append(skip) if not self.uses_docker and self.uses_config: - display.warning('Excluding tests marked "%s" which require config (see "%s"): %s' - % (skip.rstrip('/'), self.config_template_path, ', '.join(skipped))) + display.warning('Excluding tests marked "%s" which require a "%s" config file (see "%s"): %s' + % (skip.rstrip('/'), self.config_static_path, self.config_template_path, ', '.join(skipped))) elif self.uses_docker and not self.uses_config: display.warning('Excluding tests marked "%s" which requires container support: %s' % (skip.rstrip('/'), ', '.join(skipped))) elif self.uses_docker and self.uses_config: - display.warning('Excluding tests marked "%s" which requires container support or config (see "%s"): %s' - % (skip.rstrip('/'), self.config_template_path, ', '.join(skipped))) + display.warning('Excluding tests marked "%s" which requires container support or a "%s" config file (see "%s"): %s' + % (skip.rstrip('/'), self.config_static_path, self.config_template_path, ', '.join(skipped))) def setup(self) -> None: """Setup the cloud resource before delegation and register a cleanup callback.""" diff --git a/test/lib/ansible_test/_internal/commands/integration/cloud/acme.py b/test/lib/ansible_test/_internal/commands/integration/cloud/acme.py index 8a83ed2b..007d383c 100644 --- a/test/lib/ansible_test/_internal/commands/integration/cloud/acme.py +++ b/test/lib/ansible_test/_internal/commands/integration/cloud/acme.py @@ -30,7 +30,7 @@ class ACMEProvider(CloudProvider): if os.environ.get('ANSIBLE_ACME_CONTAINER'): self.image = os.environ.get('ANSIBLE_ACME_CONTAINER') else: - self.image = 'quay.io/ansible/acme-test-container:2.0.0' + self.image = 'quay.io/ansible/acme-test-container:2.1.0' self.uses_docker = True diff --git a/test/lib/ansible_test/_internal/commands/integration/cloud/cs.py b/test/lib/ansible_test/_internal/commands/integration/cloud/cs.py index 25a02ff5..0037b423 100644 --- a/test/lib/ansible_test/_internal/commands/integration/cloud/cs.py +++ b/test/lib/ansible_test/_internal/commands/integration/cloud/cs.py @@ -131,12 +131,12 @@ class CsCloudProvider(CloudProvider): def _get_credentials(self, container_name: str) -> dict[str, t.Any]: """Wait for the CloudStack simulator to return credentials.""" - def check(value): + def check(value) -> bool: """Return True if the given configuration is valid JSON, otherwise return False.""" # noinspection PyBroadException try: json.loads(value) - except Exception: # pylint: disable=broad-except + except Exception: # pylint: disable=broad-except return False # sometimes the file exists but is not yet valid JSON return True diff --git a/test/lib/ansible_test/_internal/commands/integration/coverage.py b/test/lib/ansible_test/_internal/commands/integration/coverage.py index e9917692..5a486e93 100644 --- a/test/lib/ansible_test/_internal/commands/integration/coverage.py +++ b/test/lib/ansible_test/_internal/commands/integration/coverage.py @@ -158,7 +158,7 @@ class PosixCoverageHandler(CoverageHandler[PosixConfig]): self.teardown_controller() self.teardown_target() - def setup_controller(self): + def setup_controller(self) -> None: """Perform setup for code coverage on the controller.""" coverage_config_path = os.path.join(self.common_temp_path, COVERAGE_CONFIG_NAME) coverage_output_path = os.path.join(self.common_temp_path, ResultType.COVERAGE.name) @@ -171,7 +171,7 @@ class PosixCoverageHandler(CoverageHandler[PosixConfig]): os.mkdir(coverage_output_path) verified_chmod(coverage_output_path, MODE_DIRECTORY_WRITE) - def setup_target(self): + def setup_target(self) -> None: """Perform setup for code coverage on the target.""" if not self.target_profile: return diff --git a/test/lib/ansible_test/_internal/commands/integration/filters.py b/test/lib/ansible_test/_internal/commands/integration/filters.py index 1f242bd7..be03d7f4 100644 --- a/test/lib/ansible_test/_internal/commands/integration/filters.py +++ b/test/lib/ansible_test/_internal/commands/integration/filters.py @@ -67,12 +67,12 @@ class TargetFilter(t.Generic[THostConfig], metaclass=abc.ABCMeta): return self.configs[0] def skip( - self, - skip: str, - reason: str, - targets: list[IntegrationTarget], - exclude: set[str], - override: t.Optional[list[str]] = None, + self, + skip: str, + reason: str, + targets: list[IntegrationTarget], + exclude: set[str], + override: t.Optional[list[str]] = None, ) -> None: """Apply the specified skip rule to the given targets by updating the provided exclude list.""" if skip.startswith('skip/'): diff --git a/test/lib/ansible_test/_internal/commands/sanity/__init__.py b/test/lib/ansible_test/_internal/commands/sanity/__init__.py index 7aea3988..00b30310 100644 --- a/test/lib/ansible_test/_internal/commands/sanity/__init__.py +++ b/test/lib/ansible_test/_internal/commands/sanity/__init__.py @@ -631,11 +631,11 @@ class SanitySkipped(TestSkipped): class SanityFailure(TestFailure): """Sanity test failure.""" def __init__( - self, - test: str, - python_version: t.Optional[str] = None, - messages: t.Optional[c.Sequence[SanityMessage]] = None, - summary: t.Optional[str] = None, + self, + test: str, + python_version: t.Optional[str] = None, + messages: t.Optional[c.Sequence[SanityMessage]] = None, + summary: t.Optional[str] = None, ) -> None: super().__init__(COMMAND, test, python_version, messages, summary) @@ -827,7 +827,7 @@ class SanitySingleVersion(SanityTest, metaclass=abc.ABCMeta): class SanityCodeSmellTest(SanitySingleVersion): """Sanity test script.""" - def __init__(self, path): + def __init__(self, path) -> None: name = os.path.splitext(os.path.basename(path))[0] config_path = os.path.splitext(path)[0] + '.json' @@ -862,10 +862,10 @@ class SanityCodeSmellTest(SanitySingleVersion): self.extensions = [] self.prefixes = [] self.files = [] - self.text: t.Optional[bool] = None + self.text = None self.ignore_self = False - self.minimum_python_version: t.Optional[str] = None - self.maximum_python_version: t.Optional[str] = None + self.minimum_python_version = None + self.maximum_python_version = None self.__all_targets = False self.__no_targets = True @@ -1097,11 +1097,11 @@ def sanity_get_tests() -> tuple[SanityTest, ...]: def create_sanity_virtualenv( - args: SanityConfig, - python: PythonConfig, - name: str, - coverage: bool = False, - minimize: bool = False, + args: SanityConfig, + python: PythonConfig, + name: str, + coverage: bool = False, + minimize: bool = False, ) -> t.Optional[VirtualPythonConfig]: """Return an existing sanity virtual environment matching the requested parameters or create a new one.""" commands = collect_requirements( # create_sanity_virtualenv() diff --git a/test/lib/ansible_test/_internal/commands/sanity/import.py b/test/lib/ansible_test/_internal/commands/sanity/import.py index d1b2641b..8511d7ac 100644 --- a/test/lib/ansible_test/_internal/commands/sanity/import.py +++ b/test/lib/ansible_test/_internal/commands/sanity/import.py @@ -122,8 +122,8 @@ class ImportTest(SanityMultipleVersion): messages = [] for import_type, test in ( - ('module', _get_module_test(True)), - ('plugin', _get_module_test(False)), + ('module', _get_module_test(True)), + ('plugin', _get_module_test(False)), ): if import_type == 'plugin' and python.version in REMOTE_ONLY_PYTHON_VERSIONS: continue diff --git a/test/lib/ansible_test/_internal/commands/sanity/mypy.py b/test/lib/ansible_test/_internal/commands/sanity/mypy.py index c8497139..cb8ed12c 100644 --- a/test/lib/ansible_test/_internal/commands/sanity/mypy.py +++ b/test/lib/ansible_test/_internal/commands/sanity/mypy.py @@ -165,11 +165,11 @@ class MypyTest(SanityMultipleVersion): @staticmethod def test_context( - args: SanityConfig, - virtualenv_python: VirtualPythonConfig, - python: PythonConfig, - context: MyPyContext, - paths: list[str], + args: SanityConfig, + virtualenv_python: VirtualPythonConfig, + python: PythonConfig, + context: MyPyContext, + paths: list[str], ) -> list[SanityMessage]: """Run mypy tests for the specified context.""" context_paths = [path for path in paths if any(is_subdir(path, match_path) for match_path in context.paths)] diff --git a/test/lib/ansible_test/_internal/commands/sanity/pylint.py b/test/lib/ansible_test/_internal/commands/sanity/pylint.py index cd5a8350..86f287ab 100644 --- a/test/lib/ansible_test/_internal/commands/sanity/pylint.py +++ b/test/lib/ansible_test/_internal/commands/sanity/pylint.py @@ -58,7 +58,7 @@ from ...host_configs import ( class PylintTest(SanitySingleVersion): """Sanity test using pylint.""" - def __init__(self): + def __init__(self) -> None: super().__init__() self.optional_error_codes.update([ 'ansible-deprecated-date', @@ -189,13 +189,13 @@ class PylintTest(SanitySingleVersion): @staticmethod def pylint( - args: SanityConfig, - context: str, - paths: list[str], - plugin_dir: str, - plugin_names: list[str], - python: PythonConfig, - collection_detail: CollectionDetail, + args: SanityConfig, + context: str, + paths: list[str], + plugin_dir: str, + plugin_names: list[str], + python: PythonConfig, + collection_detail: CollectionDetail, ) -> list[dict[str, str]]: """Run pylint using the config specified by the context on the specified paths.""" rcfile = os.path.join(SANITY_ROOT, 'pylint', 'config', context.split('/')[0] + '.cfg') 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 9ab8970b..e1dacb7c 100644 --- a/test/lib/ansible_test/_internal/commands/sanity/validate_modules.py +++ b/test/lib/ansible_test/_internal/commands/sanity/validate_modules.py @@ -59,7 +59,7 @@ from ...host_configs import ( class ValidateModulesTest(SanitySingleVersion): """Sanity test using validate-modules.""" - def __init__(self): + def __init__(self) -> None: super().__init__() self.optional_error_codes.update([ diff --git a/test/lib/ansible_test/_internal/completion.py b/test/lib/ansible_test/_internal/completion.py index ee096772..f443181c 100644 --- a/test/lib/ansible_test/_internal/completion.py +++ b/test/lib/ansible_test/_internal/completion.py @@ -54,7 +54,7 @@ class CompletionConfig(metaclass=abc.ABCMeta): @property @abc.abstractmethod - def is_default(self): + def is_default(self) -> bool: """True if the completion entry is only used for defaults, otherwise False.""" @@ -107,17 +107,17 @@ class RemoteCompletionConfig(CompletionConfig): arch: t.Optional[str] = None @property - def platform(self): + def platform(self) -> str: """The name of the platform.""" return self.name.partition('/')[0] @property - def version(self): + def version(self) -> str: """The version of the platform.""" return self.name.partition('/')[2] @property - def is_default(self): + def is_default(self) -> bool: """True if the completion entry is only used for defaults, otherwise False.""" return not self.version @@ -166,7 +166,7 @@ class DockerCompletionConfig(PythonCompletionConfig): placeholder: bool = False @property - def is_default(self): + def is_default(self) -> bool: """True if the completion entry is only used for defaults, otherwise False.""" return False @@ -270,13 +270,15 @@ def parse_completion_entry(value: str) -> tuple[str, dict[str, str]]: def filter_completion( - completion: dict[str, TCompletionConfig], - controller_only: bool = False, - include_defaults: bool = False, + completion: dict[str, TCompletionConfig], + controller_only: bool = False, + include_defaults: bool = False, ) -> dict[str, TCompletionConfig]: """Return the given completion dictionary, filtering out configs which do not support the controller if controller_only is specified.""" if controller_only: - completion = {name: config for name, config in completion.items() if isinstance(config, PosixCompletionConfig) and config.controller_supported} + # The cast is needed because mypy gets confused here and forgets that completion values are TCompletionConfig. + completion = {name: t.cast(TCompletionConfig, config) for name, config in completion.items() if + isinstance(config, PosixCompletionConfig) and config.controller_supported} if not include_defaults: completion = {name: config for name, config in completion.items() if not config.is_default} diff --git a/test/lib/ansible_test/_internal/containers.py b/test/lib/ansible_test/_internal/containers.py index 95b1718b..a581ecf2 100644 --- a/test/lib/ansible_test/_internal/containers.py +++ b/test/lib/ansible_test/_internal/containers.py @@ -108,19 +108,19 @@ class CleanupMode(enum.Enum): def run_support_container( - args: EnvironmentConfig, - context: str, - image: str, - name: str, - ports: list[int], - aliases: t.Optional[list[str]] = None, - start: bool = True, - allow_existing: bool = False, - cleanup: t.Optional[CleanupMode] = None, - cmd: t.Optional[list[str]] = None, - env: t.Optional[dict[str, str]] = None, - options: t.Optional[list[str]] = None, - publish_ports: bool = True, + args: EnvironmentConfig, + context: str, + image: str, + name: str, + ports: list[int], + aliases: t.Optional[list[str]] = None, + start: bool = True, + allow_existing: bool = False, + cleanup: t.Optional[CleanupMode] = None, + cmd: t.Optional[list[str]] = None, + env: t.Optional[dict[str, str]] = None, + options: t.Optional[list[str]] = None, + publish_ports: bool = True, ) -> t.Optional[ContainerDescriptor]: """ Start a container used to support tests, but not run them. @@ -236,12 +236,12 @@ def run_support_container( def run_container( - args: EnvironmentConfig, - image: str, - name: str, - options: t.Optional[list[str]], - cmd: t.Optional[list[str]] = None, - create_only: bool = False, + args: EnvironmentConfig, + image: str, + name: str, + options: t.Optional[list[str]], + cmd: t.Optional[list[str]] = None, + create_only: bool = False, ) -> str: """Run a container using the given docker image.""" options = list(options or []) @@ -263,7 +263,7 @@ def run_container( stdout = docker_run(args, image, options, cmd)[0] except SubprocessError as ex: display.error(ex.message) - display.warning('Failed to run docker image "{image}". Waiting a few seconds before trying again.') + display.warning(f'Failed to run docker image "{image}". Waiting a few seconds before trying again.') docker_rm(args, name) # podman doesn't remove containers after create if run fails time.sleep(3) else: @@ -594,8 +594,8 @@ class SupportContainerContext: @contextlib.contextmanager def support_container_context( - args: EnvironmentConfig, - ssh: t.Optional[SshConnectionDetail], + args: EnvironmentConfig, + ssh: t.Optional[SshConnectionDetail], ) -> c.Iterator[t.Optional[ContainerDatabase]]: """Create a context manager for integration tests that use support containers.""" if not isinstance(args, (IntegrationConfig, UnitsConfig, SanityConfig, ShellConfig)): @@ -617,9 +617,9 @@ def support_container_context( def create_support_container_context( - args: EnvironmentConfig, - ssh: t.Optional[SshConnectionDetail], - containers: ContainerDatabase, + args: EnvironmentConfig, + ssh: t.Optional[SshConnectionDetail], + containers: ContainerDatabase, ) -> SupportContainerContext: """Context manager that provides SSH port forwards. Returns updated container metadata.""" host_type = HostType.control @@ -819,9 +819,9 @@ def create_hosts_entries(context: dict[str, ContainerAccess]) -> list[str]: def create_container_hooks( - args: IntegrationConfig, - control_connections: list[SshConnectionDetail], - managed_connections: t.Optional[list[SshConnectionDetail]], + args: IntegrationConfig, + control_connections: list[SshConnectionDetail], + managed_connections: t.Optional[list[SshConnectionDetail]], ) -> tuple[t.Optional[c.Callable[[IntegrationTarget], None]], t.Optional[c.Callable[[IntegrationTarget], None]]]: """Return pre and post target callbacks for enabling and disabling container access for each test target.""" containers = get_container_database(args) @@ -844,12 +844,12 @@ def create_container_hooks( control_state: dict[str, tuple[list[str], list[SshProcess]]] = {} managed_state: dict[str, tuple[list[str], list[SshProcess]]] = {} - def pre_target(target): + def pre_target(target: IntegrationTarget) -> None: """Configure hosts for SSH port forwarding required by the specified target.""" forward_ssh_ports(args, control_connections, '%s_hosts_prepare.yml' % control_type, control_state, target, HostType.control, control_contexts) forward_ssh_ports(args, managed_connections, '%s_hosts_prepare.yml' % managed_type, managed_state, target, HostType.managed, managed_contexts) - def post_target(target): + def post_target(target: IntegrationTarget) -> None: """Clean up previously configured SSH port forwarding which was required by the specified target.""" cleanup_ssh_ports(args, control_connections, '%s_hosts_restore.yml' % control_type, control_state, target, HostType.control) cleanup_ssh_ports(args, managed_connections, '%s_hosts_restore.yml' % managed_type, managed_state, target, HostType.managed) @@ -873,13 +873,13 @@ def create_managed_contexts(control_contexts: dict[str, dict[str, ContainerAcces def forward_ssh_ports( - args: IntegrationConfig, - ssh_connections: t.Optional[list[SshConnectionDetail]], - playbook: str, - target_state: dict[str, tuple[list[str], list[SshProcess]]], - target: IntegrationTarget, - host_type: str, - contexts: dict[str, dict[str, ContainerAccess]], + args: IntegrationConfig, + ssh_connections: t.Optional[list[SshConnectionDetail]], + playbook: str, + target_state: dict[str, tuple[list[str], list[SshProcess]]], + target: IntegrationTarget, + host_type: str, + contexts: dict[str, dict[str, ContainerAccess]], ) -> None: """Configure port forwarding using SSH and write hosts file entries.""" if ssh_connections is None: @@ -944,12 +944,12 @@ def forward_ssh_ports( def cleanup_ssh_ports( - args: IntegrationConfig, - ssh_connections: list[SshConnectionDetail], - playbook: str, - target_state: dict[str, tuple[list[str], list[SshProcess]]], - target: IntegrationTarget, - host_type: str, + args: IntegrationConfig, + ssh_connections: list[SshConnectionDetail], + playbook: str, + target_state: dict[str, tuple[list[str], list[SshProcess]]], + target: IntegrationTarget, + host_type: str, ) -> None: """Stop previously configured SSH port forwarding and remove previously written hosts file entries.""" state = target_state.pop(target.name, None) diff --git a/test/lib/ansible_test/_internal/core_ci.py b/test/lib/ansible_test/_internal/core_ci.py index cc2a868f..d62b9039 100644 --- a/test/lib/ansible_test/_internal/core_ci.py +++ b/test/lib/ansible_test/_internal/core_ci.py @@ -8,7 +8,6 @@ import os import re import traceback import uuid -import errno import time import typing as t @@ -114,10 +113,10 @@ class AnsibleCoreCI: DEFAULT_ENDPOINT = 'https://ansible-core-ci.testing.ansible.com' def __init__( - self, - args: EnvironmentConfig, - resource: Resource, - load: bool = True, + self, + args: EnvironmentConfig, + resource: Resource, + load: bool = True, ) -> None: self.args = args self.resource = resource @@ -174,11 +173,11 @@ class AnsibleCoreCI: self.endpoint = self.default_endpoint @property - def available(self): + def available(self) -> bool: """Return True if Ansible Core CI is supported.""" return self.ci_provider.supports_core_ci_auth() - def start(self): + def start(self) -> t.Optional[dict[str, t.Any]]: """Start instance.""" if self.started: display.info(f'Skipping started {self.label} instance.', verbosity=1) @@ -186,7 +185,7 @@ class AnsibleCoreCI: return self._start(self.ci_provider.prepare_core_ci_auth()) - def stop(self): + def stop(self) -> None: """Stop instance.""" if not self.started: display.info(f'Skipping invalid {self.label} instance.', verbosity=1) @@ -280,10 +279,10 @@ class AnsibleCoreCI: raise ApplicationError(f'Timeout waiting for {self.label} instance.') @property - def _uri(self): + def _uri(self) -> str: return f'{self.endpoint}/{self.stage}/{self.provider}/{self.instance_id}' - def _start(self, auth): + def _start(self, auth) -> dict[str, t.Any]: """Start instance.""" display.info(f'Initializing new {self.label} instance using: {self._uri}', verbosity=1) @@ -342,23 +341,19 @@ class AnsibleCoreCI: display.warning(f'{error}. Trying again after {sleep} seconds.') time.sleep(sleep) - def _clear(self): + def _clear(self) -> None: """Clear instance information.""" try: self.connection = None os.remove(self.path) - except OSError as ex: - if ex.errno != errno.ENOENT: - raise + except FileNotFoundError: + pass - def _load(self): + def _load(self) -> bool: """Load instance information.""" try: data = read_text_file(self.path) - except IOError as ex: - if ex.errno != errno.ENOENT: - raise - + except FileNotFoundError: return False if not data.startswith('{'): diff --git a/test/lib/ansible_test/_internal/coverage_util.py b/test/lib/ansible_test/_internal/coverage_util.py index 43d10718..0f445059 100644 --- a/test/lib/ansible_test/_internal/coverage_util.py +++ b/test/lib/ansible_test/_internal/coverage_util.py @@ -143,14 +143,14 @@ def get_sqlite_schema_version(path: str) -> int: def cover_python( - args: TestConfig, - python: PythonConfig, - cmd: list[str], - target_name: str, - env: dict[str, str], - capture: bool, - data: t.Optional[str] = None, - cwd: t.Optional[str] = None, + args: TestConfig, + python: PythonConfig, + cmd: list[str], + target_name: str, + env: dict[str, str], + capture: bool, + data: t.Optional[str] = None, + cwd: t.Optional[str] = None, ) -> tuple[t.Optional[str], t.Optional[str]]: """Run a command while collecting Python code coverage.""" if args.coverage: @@ -176,9 +176,9 @@ def get_coverage_platform(config: HostConfig) -> str: def get_coverage_environment( - args: TestConfig, - target_name: str, - version: str, + args: TestConfig, + target_name: str, + version: str, ) -> dict[str, str]: """Return environment variables needed to collect code coverage.""" # unit tests, sanity tests and other special cases (localhost only) diff --git a/test/lib/ansible_test/_internal/data.py b/test/lib/ansible_test/_internal/data.py index 66e21543..635b0c32 100644 --- a/test/lib/ansible_test/_internal/data.py +++ b/test/lib/ansible_test/_internal/data.py @@ -52,7 +52,7 @@ from .provider.layout.unsupported import ( class DataContext: """Data context providing details about the current execution environment for ansible-test.""" - def __init__(self): + def __init__(self) -> None: content_path = os.environ.get('ANSIBLE_TEST_CONTENT_ROOT') current_path = os.getcwd() @@ -245,7 +245,7 @@ class PluginInfo: @cache -def content_plugins(): +def content_plugins() -> dict[str, dict[str, PluginInfo]]: """ Analyze content. The primary purpose of this analysis is to facilitate mapping of integration tests to the plugin(s) they are intended to test. @@ -256,7 +256,7 @@ def content_plugins(): plugin_paths = sorted(data_context().content.walk_files(plugin_directory)) plugin_directory_offset = len(plugin_directory.split(os.path.sep)) - plugin_files = {} + plugin_files: dict[str, list[str]] = {} for plugin_path in plugin_paths: plugin_filename = os.path.basename(plugin_path) diff --git a/test/lib/ansible_test/_internal/delegation.py b/test/lib/ansible_test/_internal/delegation.py index 8c6879d2..0f181a23 100644 --- a/test/lib/ansible_test/_internal/delegation.py +++ b/test/lib/ansible_test/_internal/delegation.py @@ -226,7 +226,7 @@ def delegate_command(args: EnvironmentConfig, host_state: HostState, exclude: li target.on_target_failure() # when the controller is delegated, report failures after delegation fails -def insert_options(command, options): +def insert_options(command: list[str], options: list[str]) -> list[str]: """Insert addition command line options into the given command and return the result.""" result = [] @@ -267,12 +267,12 @@ def download_results(args: EnvironmentConfig, con: Connection, content_root: str def generate_command( - args: EnvironmentConfig, - python: PythonConfig, - ansible_bin_path: str, - content_root: str, - exclude: list[str], - require: list[str], + args: EnvironmentConfig, + python: PythonConfig, + ansible_bin_path: str, + content_root: str, + exclude: list[str], + require: list[str], ) -> list[str]: """Generate the command necessary to delegate ansible-test.""" cmd = [os.path.join(ansible_bin_path, 'ansible-test')] @@ -319,10 +319,10 @@ def generate_command( def filter_options( - args: EnvironmentConfig, - argv: list[str], - exclude: list[str], - require: list[str], + args: EnvironmentConfig, + argv: list[str], + exclude: list[str], + require: list[str], ) -> c.Iterable[str]: """Return an iterable that filters out unwanted CLI options and injects new ones as requested.""" replace: list[tuple[str, int, t.Optional[t.Union[bool, str, list[str]]]]] = [ diff --git a/test/lib/ansible_test/_internal/dev/container_probe.py b/test/lib/ansible_test/_internal/dev/container_probe.py index 84b88f4b..be22e01c 100644 --- a/test/lib/ansible_test/_internal/dev/container_probe.py +++ b/test/lib/ansible_test/_internal/dev/container_probe.py @@ -184,7 +184,7 @@ def check_container_cgroup_status(args: EnvironmentConfig, config: DockerConfig, write_text_file(os.path.join(args.dev_probe_cgroups, f'{identity}.log'), message) -def get_identity(args: EnvironmentConfig, config: DockerConfig, container_name: str): +def get_identity(args: EnvironmentConfig, config: DockerConfig, container_name: str) -> str: """Generate and return an identity string to use when logging test results.""" engine = require_docker().command diff --git a/test/lib/ansible_test/_internal/docker_util.py b/test/lib/ansible_test/_internal/docker_util.py index 77cdd4ee..6c38ddbd 100644 --- a/test/lib/ansible_test/_internal/docker_util.py +++ b/test/lib/ansible_test/_internal/docker_util.py @@ -401,11 +401,11 @@ def detect_host_properties(args: CommonConfig) -> ContainerHostProperties: def run_utility_container( - args: CommonConfig, - name: str, - cmd: list[str], - options: list[str], - data: t.Optional[str] = None, + args: CommonConfig, + name: str, + cmd: list[str], + options: list[str], + data: t.Optional[str] = None, ) -> tuple[t.Optional[str], t.Optional[str]]: """Run the specified command using the ansible-test utility container, returning stdout and stderr.""" options = options + [ @@ -670,30 +670,30 @@ def docker_cp_to(args: CommonConfig, container_id: str, src: str, dst: str) -> N def docker_create( - args: CommonConfig, - image: str, - options: list[str], - cmd: list[str] = None, + args: CommonConfig, + image: str, + options: list[str], + cmd: list[str] = None, ) -> tuple[t.Optional[str], t.Optional[str]]: """Create a container using the given docker image.""" return docker_command(args, ['create'] + options + [image] + cmd, capture=True) def docker_run( - args: CommonConfig, - image: str, - options: list[str], - cmd: list[str] = None, - data: t.Optional[str] = None, + args: CommonConfig, + image: str, + options: list[str], + cmd: list[str] = None, + data: t.Optional[str] = None, ) -> tuple[t.Optional[str], t.Optional[str]]: """Run a container using the given docker image.""" return docker_command(args, ['run'] + options + [image] + cmd, data=data, capture=True) def docker_start( - args: CommonConfig, - container_id: str, - options: list[str], + args: CommonConfig, + container_id: str, + options: list[str], ) -> tuple[t.Optional[str], t.Optional[str]]: """Start a container by name or ID.""" return docker_command(args, ['start'] + options + [container_id], capture=True) @@ -720,7 +720,7 @@ class DockerError(Exception): class ContainerNotFoundError(DockerError): """The container identified by `identifier` was not found.""" - def __init__(self, identifier): + def __init__(self, identifier: str) -> None: super().__init__('The container "%s" was not found.' % identifier) self.identifier = identifier @@ -943,16 +943,16 @@ def docker_logs(args: CommonConfig, container_id: str) -> None: def docker_exec( - args: CommonConfig, - container_id: str, - cmd: list[str], - capture: bool, - options: t.Optional[list[str]] = None, - stdin: t.Optional[t.IO[bytes]] = None, - stdout: t.Optional[t.IO[bytes]] = None, - interactive: bool = False, - output_stream: t.Optional[OutputStream] = None, - data: t.Optional[str] = None, + args: CommonConfig, + container_id: str, + cmd: list[str], + capture: bool, + options: t.Optional[list[str]] = None, + stdin: t.Optional[t.IO[bytes]] = None, + stdout: t.Optional[t.IO[bytes]] = None, + interactive: bool = False, + output_stream: t.Optional[OutputStream] = None, + data: t.Optional[str] = None, ) -> tuple[t.Optional[str], t.Optional[str]]: """Execute the given command in the specified container.""" if not options: @@ -966,15 +966,15 @@ def docker_exec( def docker_command( - args: CommonConfig, - cmd: list[str], - capture: bool, - stdin: t.Optional[t.IO[bytes]] = None, - stdout: t.Optional[t.IO[bytes]] = None, - interactive: bool = False, - output_stream: t.Optional[OutputStream] = None, - always: bool = False, - data: t.Optional[str] = None, + args: CommonConfig, + cmd: list[str], + capture: bool, + stdin: t.Optional[t.IO[bytes]] = None, + stdout: t.Optional[t.IO[bytes]] = None, + interactive: bool = False, + output_stream: t.Optional[OutputStream] = None, + always: bool = False, + data: t.Optional[str] = None, ) -> tuple[t.Optional[str], t.Optional[str]]: """Run the specified docker command.""" env = docker_environment() diff --git a/test/lib/ansible_test/_internal/executor.py b/test/lib/ansible_test/_internal/executor.py index b079df9f..0c94cf3b 100644 --- a/test/lib/ansible_test/_internal/executor.py +++ b/test/lib/ansible_test/_internal/executor.py @@ -81,13 +81,13 @@ def detect_changes(args: TestConfig) -> t.Optional[list[str]]: class NoChangesDetected(ApplicationWarning): """Exception when change detection was performed, but no changes were found.""" - def __init__(self): + def __init__(self) -> None: super().__init__('No changes detected.') class NoTestsForChanges(ApplicationWarning): """Exception when changes detected, but no tests trigger as a result.""" - def __init__(self): + def __init__(self) -> None: super().__init__('No tests found for detected changes.') @@ -111,5 +111,5 @@ class ListTargets(Exception): class AllTargetsSkipped(ApplicationWarning): """All targets skipped.""" - def __init__(self): + def __init__(self) -> None: super().__init__('All targets skipped.') diff --git a/test/lib/ansible_test/_internal/host_configs.py b/test/lib/ansible_test/_internal/host_configs.py index d7671c7f..48d5fd31 100644 --- a/test/lib/ansible_test/_internal/host_configs.py +++ b/test/lib/ansible_test/_internal/host_configs.py @@ -48,7 +48,7 @@ from .util import ( @dataclasses.dataclass(frozen=True) class OriginCompletionConfig(PosixCompletionConfig): """Pseudo completion config for the origin.""" - def __init__(self): + def __init__(self) -> None: super().__init__(name='origin') @property @@ -65,7 +65,7 @@ class OriginCompletionConfig(PosixCompletionConfig): return version @property - def is_default(self): + def is_default(self) -> bool: """True if the completion entry is only used for defaults, otherwise False.""" return False @@ -513,7 +513,7 @@ class HostSettings: with open_binary_file(path) as settings_file: return pickle.load(settings_file) - def apply_defaults(self): + def apply_defaults(self) -> None: """Apply defaults to the host settings.""" context = HostContext(controller_config=None) self.controller.apply_defaults(context, self.controller.get_defaults(context)) diff --git a/test/lib/ansible_test/_internal/host_profiles.py b/test/lib/ansible_test/_internal/host_profiles.py index 6575e7c1..0abc9961 100644 --- a/test/lib/ansible_test/_internal/host_profiles.py +++ b/test/lib/ansible_test/_internal/host_profiles.py @@ -351,7 +351,7 @@ class RemoteProfile(SshTargetHostProfile[TRemoteConfig], metaclass=abc.ABCMeta): return self.core_ci - def delete_instance(self): + def delete_instance(self) -> None: """Delete the AnsibleCoreCI VM instance.""" core_ci = self.get_instance() @@ -506,6 +506,13 @@ class DockerProfile(ControllerHostProfile[DockerConfig], SshTargetHostProfile[Do cgroup_version = get_docker_info(self.args).cgroup_version + # Podman 4.4.0 updated containers/common to 0.51.0, which removed the SYS_CHROOT capability from the default list. + # This capability is needed by services such as sshd, so is unconditionally added here. + # See: https://github.com/containers/podman/releases/tag/v4.4.0 + # See: https://github.com/containers/common/releases/tag/v0.51.0 + # See: https://github.com/containers/common/pull/1240 + options.extend(('--cap-add', 'SYS_CHROOT')) + # Without AUDIT_WRITE the following errors may appear in the system logs of a container after attempting to log in using SSH: # # fatal: linux_audit_write_entry failed: Operation not permitted @@ -892,7 +899,7 @@ class DockerProfile(ControllerHostProfile[DockerConfig], SshTargetHostProfile[Do return message - def check_cgroup_requirements(self): + def check_cgroup_requirements(self) -> None: """Check cgroup requirements for the container.""" cgroup_version = get_docker_info(self.args).cgroup_version @@ -1411,9 +1418,9 @@ def get_config_profile_type_map() -> dict[t.Type[HostConfig], t.Type[HostProfile def create_host_profile( - args: EnvironmentConfig, - config: HostConfig, - controller: bool, + args: EnvironmentConfig, + config: HostConfig, + controller: bool, ) -> HostProfile: """Create and return a host profile from the given host configuration.""" profile_type = get_config_profile_type_map()[type(config)] diff --git a/test/lib/ansible_test/_internal/io.py b/test/lib/ansible_test/_internal/io.py index a7e9f1b2..80d47699 100644 --- a/test/lib/ansible_test/_internal/io.py +++ b/test/lib/ansible_test/_internal/io.py @@ -1,7 +1,6 @@ """Functions for disk IO.""" from __future__ import annotations -import errno import io import json import os @@ -32,11 +31,7 @@ def read_binary_file(path: str) -> bytes: def make_dirs(path: str) -> None: """Create a directory at path, including any necessary parent directories.""" - try: - os.makedirs(to_bytes(path)) - except OSError as ex: - if ex.errno != errno.EEXIST: - raise + os.makedirs(to_bytes(path), exist_ok=True) def write_json_file(path: str, @@ -85,7 +80,7 @@ def open_binary_file(path: str, mode: str = 'rb') -> t.IO[bytes]: class SortedSetEncoder(json.JSONEncoder): """Encode sets as sorted lists.""" - def default(self, o): + def default(self, o: t.Any) -> t.Any: """Return a serialized version of the `o` object.""" if isinstance(o, set): return sorted(o) diff --git a/test/lib/ansible_test/_internal/metadata.py b/test/lib/ansible_test/_internal/metadata.py index e969f029..94bbc34a 100644 --- a/test/lib/ansible_test/_internal/metadata.py +++ b/test/lib/ansible_test/_internal/metadata.py @@ -19,7 +19,7 @@ from .diff import ( class Metadata: """Metadata object for passing data to delegated tests.""" - def __init__(self): + def __init__(self) -> None: """Initialize metadata.""" self.changes: dict[str, tuple[tuple[int, int], ...]] = {} self.cloud_config: t.Optional[dict[str, dict[str, t.Union[int, str, bool]]]] = None @@ -82,7 +82,7 @@ class Metadata: class ChangeDescription: """Description of changes.""" - def __init__(self): + def __init__(self) -> None: self.command: str = '' self.changed_paths: list[str] = [] self.deleted_paths: list[str] = [] diff --git a/test/lib/ansible_test/_internal/provider/layout/__init__.py b/test/lib/ansible_test/_internal/provider/layout/__init__.py index 2e8026bf..aa6693f0 100644 --- a/test/lib/ansible_test/_internal/provider/layout/__init__.py +++ b/test/lib/ansible_test/_internal/provider/layout/__init__.py @@ -150,7 +150,7 @@ class ContentLayout(Layout): class LayoutMessages: """Messages generated during layout creation that should be deferred for later display.""" - def __init__(self): + def __init__(self) -> None: self.info: list[str] = [] self.warning: list[str] = [] self.error: list[str] = [] diff --git a/test/lib/ansible_test/_internal/provisioning.py b/test/lib/ansible_test/_internal/provisioning.py index 8f914c2a..7547a302 100644 --- a/test/lib/ansible_test/_internal/provisioning.py +++ b/test/lib/ansible_test/_internal/provisioning.py @@ -97,10 +97,10 @@ class HostState: def prepare_profiles( - args: TEnvironmentConfig, - targets_use_pypi: bool = False, - skip_setup: bool = False, - requirements: t.Optional[c.Callable[[HostProfile], None]] = None, + args: TEnvironmentConfig, + targets_use_pypi: bool = False, + skip_setup: bool = False, + requirements: t.Optional[c.Callable[[HostProfile], None]] = None, ) -> HostState: """ Create new profiles, or load existing ones, and return them. diff --git a/test/lib/ansible_test/_internal/pypi_proxy.py b/test/lib/ansible_test/_internal/pypi_proxy.py index fa26b5fd..97663ead 100644 --- a/test/lib/ansible_test/_internal/pypi_proxy.py +++ b/test/lib/ansible_test/_internal/pypi_proxy.py @@ -119,7 +119,7 @@ def configure_target_pypi_proxy(args: EnvironmentConfig, profile: HostProfile, p create_posix_inventory(args, inventory_path, [profile]) - def cleanup_pypi_proxy(): + def cleanup_pypi_proxy() -> None: """Undo changes made to configure the PyPI proxy.""" run_playbook(args, inventory_path, 'pypi_proxy_restore.yml', capture=True) diff --git a/test/lib/ansible_test/_internal/python_requirements.py b/test/lib/ansible_test/_internal/python_requirements.py index 44cf53ae..e3733a5c 100644 --- a/test/lib/ansible_test/_internal/python_requirements.py +++ b/test/lib/ansible_test/_internal/python_requirements.py @@ -122,14 +122,14 @@ class PipBootstrap(PipCommand): def install_requirements( - args: EnvironmentConfig, - python: PythonConfig, - ansible: bool = False, - command: bool = False, - coverage: bool = False, - virtualenv: bool = False, - controller: bool = True, - connection: t.Optional[Connection] = None, + args: EnvironmentConfig, + python: PythonConfig, + ansible: bool = False, + command: bool = False, + coverage: bool = False, + virtualenv: bool = False, + controller: bool = True, + connection: t.Optional[Connection] = None, ) -> None: """Install requirements for the given Python using the specified arguments.""" create_result_directories(args) @@ -197,15 +197,15 @@ def collect_bootstrap(python: PythonConfig) -> list[PipCommand]: def collect_requirements( - python: PythonConfig, - controller: bool, - ansible: bool, - cryptography: bool, - coverage: bool, - virtualenv: bool, - minimize: bool, - command: t.Optional[str], - sanity: t.Optional[str], + python: PythonConfig, + controller: bool, + ansible: bool, + cryptography: bool, + coverage: bool, + virtualenv: bool, + minimize: bool, + command: t.Optional[str], + sanity: t.Optional[str], ) -> list[PipCommand]: """Collect requirements for the given Python using the specified arguments.""" commands: list[PipCommand] = [] @@ -252,10 +252,10 @@ def collect_requirements( def run_pip( - args: EnvironmentConfig, - python: PythonConfig, - commands: list[PipCommand], - connection: t.Optional[Connection], + args: EnvironmentConfig, + python: PythonConfig, + commands: list[PipCommand], + connection: t.Optional[Connection], ) -> None: """Run the specified pip commands for the given Python, and optionally the specified host.""" connection = connection or LocalConnection(args) @@ -367,10 +367,10 @@ def collect_integration_install(command: str, controller: bool) -> list[PipInsta def collect_install( - requirements_paths: list[tuple[str, str]], - constraints_paths: list[tuple[str, str]], - packages: t.Optional[list[str]] = None, - constraints: bool = True, + requirements_paths: list[tuple[str, str]], + constraints_paths: list[tuple[str, str]], + packages: t.Optional[list[str]] = None, + constraints: bool = True, ) -> list[PipInstall]: """Build a pip install list from the given requirements, constraints and packages.""" # listing content constraints first gives them priority over constraints provided by ansible-test diff --git a/test/lib/ansible_test/_internal/ssh.py b/test/lib/ansible_test/_internal/ssh.py index fd01ff25..840edf62 100644 --- a/test/lib/ansible_test/_internal/ssh.py +++ b/test/lib/ansible_test/_internal/ssh.py @@ -151,10 +151,10 @@ class SshProcess: def create_ssh_command( - ssh: SshConnectionDetail, - options: t.Optional[dict[str, t.Union[str, int]]] = None, - cli_args: list[str] = None, - command: t.Optional[str] = None, + ssh: SshConnectionDetail, + options: t.Optional[dict[str, t.Union[str, int]]] = None, + cli_args: list[str] = None, + command: t.Optional[str] = None, ) -> list[str]: """Create an SSH command using the specified options.""" cmd = [ @@ -207,11 +207,11 @@ def ssh_options_to_str(options: t.Union[dict[str, t.Union[int, str]], dict[str, def run_ssh_command( - args: EnvironmentConfig, - ssh: SshConnectionDetail, - options: t.Optional[dict[str, t.Union[str, int]]] = None, - cli_args: list[str] = None, - command: t.Optional[str] = None, + args: EnvironmentConfig, + ssh: SshConnectionDetail, + options: t.Optional[dict[str, t.Union[str, int]]] = None, + cli_args: list[str] = None, + command: t.Optional[str] = None, ) -> SshProcess: """Run the specified SSH command, returning the created SshProcess instance created.""" cmd = create_ssh_command(ssh, options, cli_args, command) @@ -233,9 +233,9 @@ def run_ssh_command( def create_ssh_port_forwards( - args: EnvironmentConfig, - ssh: SshConnectionDetail, - forwards: list[tuple[str, int]], + args: EnvironmentConfig, + ssh: SshConnectionDetail, + forwards: list[tuple[str, int]], ) -> SshProcess: """ Create SSH port forwards using the provided list of tuples (target_host, target_port). @@ -257,9 +257,9 @@ def create_ssh_port_forwards( def create_ssh_port_redirects( - args: EnvironmentConfig, - ssh: SshConnectionDetail, - redirects: list[tuple[int, str, int]], + args: EnvironmentConfig, + ssh: SshConnectionDetail, + redirects: list[tuple[int, str, int]], ) -> SshProcess: """Create SSH port redirections using the provided list of tuples (bind_port, target_host, target_port).""" options: dict[str, t.Union[str, int]] = {} diff --git a/test/lib/ansible_test/_internal/target.py b/test/lib/ansible_test/_internal/target.py index 4e04b10a..80411483 100644 --- a/test/lib/ansible_test/_internal/target.py +++ b/test/lib/ansible_test/_internal/target.py @@ -65,31 +65,30 @@ def walk_completion_targets(targets: c.Iterable[CompletionTarget], prefix: str, def walk_internal_targets( - targets: c.Iterable[TCompletionTarget], - includes: t.Optional[list[str]] = None, - excludes: t.Optional[list[str]] = None, - requires: t.Optional[list[str]] = None, + targets: c.Iterable[TCompletionTarget], + includes: t.Optional[list[str]] = None, + excludes: t.Optional[list[str]] = None, + requires: t.Optional[list[str]] = None, ) -> tuple[TCompletionTarget, ...]: """Return a tuple of matching completion targets.""" targets = tuple(targets) - include_targets = sorted(filter_targets(targets, includes, directories=False), key=lambda include_target: include_target.name) + include_targets = sorted(filter_targets(targets, includes), key=lambda include_target: include_target.name) if requires: - require_targets = set(filter_targets(targets, requires, directories=False)) + require_targets = set(filter_targets(targets, requires)) include_targets = [require_target for require_target in include_targets if require_target in require_targets] if excludes: - list(filter_targets(targets, excludes, include=False, directories=False)) + list(filter_targets(targets, excludes, include=False)) - internal_targets = set(filter_targets(include_targets, excludes, errors=False, include=False, directories=False)) + internal_targets = set(filter_targets(include_targets, excludes, errors=False, include=False)) return tuple(sorted(internal_targets, key=lambda sort_target: sort_target.name)) def filter_targets(targets: c.Iterable[TCompletionTarget], patterns: list[str], include: bool = True, - directories: bool = True, errors: bool = True, ) -> c.Iterable[TCompletionTarget]: """Iterate over the given targets and filter them based on the supplied arguments.""" @@ -130,20 +129,15 @@ def filter_targets(targets: c.Iterable[TCompletionTarget], if match != include: continue - if directories and matched_directories: - yield DirectoryTarget(to_text(sorted(matched_directories, key=len)[0]), target.modules) - else: - yield target + yield target if errors: if unmatched: raise TargetPatternsNotMatched(unmatched) -def walk_module_targets(): - """ - :rtype: collections.Iterable[TestTarget] - """ +def walk_module_targets() -> c.Iterable[TestTarget]: + """Iterate through the module test targets.""" for target in walk_test_targets(path=data_context().content.module_path, module_path=data_context().content.module_path, extensions=MODULE_EXTENSIONS): if not target.module: continue @@ -248,10 +242,8 @@ def walk_integration_targets() -> c.Iterable[IntegrationTarget]: yield IntegrationTarget(to_text(path), modules, prefixes) -def load_integration_prefixes(): - """ - :rtype: dict[str, str] - """ +def load_integration_prefixes() -> dict[str, str]: + """Load and return the integration test prefixes.""" path = data_context().content.integration_path file_paths = sorted(f for f in data_context().content.get_files(path) if os.path.splitext(os.path.basename(f))[0] == 'target-prefixes') prefixes = {} @@ -264,13 +256,13 @@ def load_integration_prefixes(): def walk_test_targets( - path: t.Optional[str] = None, - module_path: t.Optional[str] = None, - extensions: t.Optional[tuple[str, ...]] = None, - prefix: t.Optional[str] = None, - extra_dirs: t.Optional[tuple[str, ...]] = None, - include_symlinks: bool = False, - include_symlinked_directories: bool = False, + path: t.Optional[str] = None, + module_path: t.Optional[str] = None, + extensions: t.Optional[tuple[str, ...]] = None, + prefix: t.Optional[str] = None, + extra_dirs: t.Optional[tuple[str, ...]] = None, + include_symlinks: bool = False, + include_symlinked_directories: bool = False, ) -> c.Iterable[TestTarget]: """Iterate over available test targets.""" if path: @@ -317,7 +309,7 @@ def analyze_integration_target_dependencies(integration_targets: list[Integratio role_targets = [target for target in integration_targets if target.type == 'role'] hidden_role_target_names = set(target.name for target in role_targets if 'hidden/' in target.aliases) - dependencies = collections.defaultdict(set) + dependencies: collections.defaultdict[str, set[str]] = collections.defaultdict(set) # handle setup dependencies for target in integration_targets: @@ -409,12 +401,12 @@ def analyze_integration_target_dependencies(integration_targets: list[Integratio class CompletionTarget(metaclass=abc.ABCMeta): """Command-line argument completion target base class.""" - def __init__(self): - self.name = None - self.path = None - self.base_path = None - self.modules = tuple() - self.aliases = tuple() + def __init__(self) -> None: + self.name = '' + self.path = '' + self.base_path: t.Optional[str] = None + self.modules: tuple[str, ...] = tuple() + self.aliases: tuple[str, ...] = tuple() def __eq__(self, other): if isinstance(other, CompletionTarget): @@ -441,26 +433,16 @@ class CompletionTarget(metaclass=abc.ABCMeta): return self.name -class DirectoryTarget(CompletionTarget): - """Directory target.""" - def __init__(self, path: str, modules: tuple[str, ...]) -> None: - super().__init__() - - self.name = path - self.path = path - self.modules = modules - - class TestTarget(CompletionTarget): """Generic test target.""" def __init__( - self, - path: str, - module_path: t.Optional[str], - module_prefix: t.Optional[str], - base_path: str, - symlink: t.Optional[bool] = None, - ): + self, + path: str, + module_path: t.Optional[str], + module_prefix: t.Optional[str], + base_path: str, + symlink: t.Optional[bool] = None, + ) -> None: super().__init__() if symlink is None: @@ -679,8 +661,6 @@ class IntegrationTarget(CompletionTarget): target_type, actual_type = categorize_integration_test(self.name, list(static_aliases), force_target) - self._remove_group(groups, 'context') - groups.extend(['context/', f'context/{target_type.name.lower()}']) if target_type != actual_type: @@ -709,10 +689,6 @@ class IntegrationTarget(CompletionTarget): self.setup_always = tuple(sorted(set(g.split('/')[2] for g in groups if g.startswith('setup/always/')))) self.needs_target = tuple(sorted(set(g.split('/')[2] for g in groups if g.startswith('needs/target/')))) - @staticmethod - def _remove_group(groups, group): - return [g for g in groups if g != group and not g.startswith('%s/' % group)] - class TargetPatternsNotMatched(ApplicationError): """One or more targets were not matched when a match was required.""" diff --git a/test/lib/ansible_test/_internal/test.py b/test/lib/ansible_test/_internal/test.py index da6af355..211635c5 100644 --- a/test/lib/ansible_test/_internal/test.py +++ b/test/lib/ansible_test/_internal/test.py @@ -215,12 +215,12 @@ class TestSkipped(TestResult): class TestFailure(TestResult): """Test failure.""" def __init__( - self, - command: str, - test: str, - python_version: t.Optional[str] = None, - messages: t.Optional[c.Sequence[TestMessage]] = None, - summary: t.Optional[str] = None, + self, + command: str, + test: str, + python_version: t.Optional[str] = None, + messages: t.Optional[c.Sequence[TestMessage]] = None, + summary: t.Optional[str] = None, ): super().__init__(command, test, python_version) @@ -333,10 +333,8 @@ class TestFailure(TestResult): return command - def find_docs(self): - """ - :rtype: str - """ + def find_docs(self) -> t.Optional[str]: + """Return the docs URL for this test or None if there is no docs URL.""" if self.command != 'sanity': return None # only sanity tests have docs links @@ -381,14 +379,14 @@ class TestFailure(TestResult): class TestMessage: """Single test message for one file.""" def __init__( - self, - message: str, - path: str, - line: int = 0, - column: int = 0, - level: str = 'error', - code: t.Optional[str] = None, - confidence: t.Optional[int] = None, + self, + message: str, + path: str, + line: int = 0, + column: int = 0, + level: str = 'error', + code: t.Optional[str] = None, + confidence: t.Optional[int] = None, ): self.__path = path self.__line = line diff --git a/test/lib/ansible_test/_internal/thread.py b/test/lib/ansible_test/_internal/thread.py index d0ed1bab..edaf1b5c 100644 --- a/test/lib/ansible_test/_internal/thread.py +++ b/test/lib/ansible_test/_internal/thread.py @@ -21,7 +21,7 @@ class WrappedThread(threading.Thread): self.action = action self.result = None - def run(self): + def run(self) -> None: """ Run action and capture results or exception. Do not override. Do not call directly. Executed by the start() method. @@ -35,11 +35,8 @@ class WrappedThread(threading.Thread): except: # noqa self._result.put((None, sys.exc_info())) - def wait_for_result(self): - """ - Wait for thread to exit and return the result or raise an exception. - :rtype: any - """ + def wait_for_result(self) -> t.Any: + """Wait for thread to exit and return the result or raise an exception.""" result, exception = self._result.get() if exception: diff --git a/test/lib/ansible_test/_internal/timeout.py b/test/lib/ansible_test/_internal/timeout.py index da5cfceb..90ba5835 100644 --- a/test/lib/ansible_test/_internal/timeout.py +++ b/test/lib/ansible_test/_internal/timeout.py @@ -75,7 +75,7 @@ def configure_test_timeout(args: TestConfig) -> None: display.info('The %d minute test timeout expires in %s at %s.' % ( timeout_duration, timeout_remaining, timeout_deadline), verbosity=1) - def timeout_handler(_dummy1, _dummy2): + def timeout_handler(_dummy1: t.Any, _dummy2: t.Any) -> None: """Runs when SIGUSR1 is received.""" test_timeout.write(args) diff --git a/test/lib/ansible_test/_internal/util.py b/test/lib/ansible_test/_internal/util.py index 12316239..ec485a2b 100644 --- a/test/lib/ansible_test/_internal/util.py +++ b/test/lib/ansible_test/_internal/util.py @@ -3,7 +3,6 @@ from __future__ import annotations import abc import collections.abc as c -import errno import enum import fcntl import importlib.util @@ -346,19 +345,19 @@ def get_available_python_versions() -> dict[str, str]: def raw_command( - cmd: c.Iterable[str], - capture: bool, - env: t.Optional[dict[str, str]] = None, - data: t.Optional[str] = None, - cwd: t.Optional[str] = None, - explain: bool = False, - stdin: t.Optional[t.Union[t.IO[bytes], int]] = None, - stdout: t.Optional[t.Union[t.IO[bytes], int]] = None, - interactive: bool = False, - output_stream: t.Optional[OutputStream] = None, - cmd_verbosity: int = 1, - str_errors: str = 'strict', - error_callback: t.Optional[c.Callable[[SubprocessError], None]] = None, + cmd: c.Iterable[str], + capture: bool, + env: t.Optional[dict[str, str]] = None, + data: t.Optional[str] = None, + cwd: t.Optional[str] = None, + explain: bool = False, + stdin: t.Optional[t.Union[t.IO[bytes], int]] = None, + stdout: t.Optional[t.Union[t.IO[bytes], int]] = None, + interactive: bool = False, + output_stream: t.Optional[OutputStream] = None, + cmd_verbosity: int = 1, + str_errors: str = 'strict', + error_callback: t.Optional[c.Callable[[SubprocessError], None]] = None, ) -> tuple[t.Optional[str], t.Optional[str]]: """Run the specified command and return stdout and stderr as a tuple.""" output_stream = output_stream or OutputStream.AUTO @@ -467,10 +466,8 @@ def raw_command( cmd_bytes = [to_bytes(arg) for arg in cmd] env_bytes = dict((to_bytes(k), to_bytes(v)) for k, v in env.items()) process = subprocess.Popen(cmd_bytes, env=env_bytes, stdin=stdin, stdout=stdout, stderr=stderr, cwd=cwd) # pylint: disable=consider-using-with - except OSError as ex: - if ex.errno == errno.ENOENT: - raise ApplicationError('Required program "%s" not found.' % cmd[0]) - raise + except FileNotFoundError as ex: + raise ApplicationError('Required program "%s" not found.' % cmd[0]) from ex if communicate: data_bytes = to_optional_bytes(data) @@ -499,12 +496,12 @@ def raw_command( def communicate_with_process( - process: subprocess.Popen, - stdin: t.Optional[bytes], - stdout: bool, - stderr: bool, - capture: bool, - output_stream: OutputStream, + process: subprocess.Popen, + stdin: t.Optional[bytes], + stdout: bool, + stderr: bool, + capture: bool, + output_stream: OutputStream, ) -> tuple[bytes, bytes]: """Communicate with the specified process, handling stdin/stdout/stderr as requested.""" threads: list[WrappedThread] = [] @@ -614,7 +611,7 @@ class OutputThread(ReaderThread): src.close() -def common_environment(): +def common_environment() -> dict[str, str]: """Common environment used for executing all programs.""" env = dict( LC_ALL=CONFIGURED_LOCALE, @@ -694,12 +691,11 @@ def verified_chmod(path: str, mode: int) -> None: def remove_tree(path: str) -> None: - """Remove the specified directory, siliently continuing if the directory does not exist.""" + """Remove the specified directory, silently continuing if the directory does not exist.""" try: shutil.rmtree(to_bytes(path)) - except OSError as ex: - if ex.errno != errno.ENOENT: - raise + except FileNotFoundError: + pass def is_binary_file(path: str) -> bool: @@ -797,17 +793,17 @@ class Display: 3: cyan, } - def __init__(self): + def __init__(self) -> None: self.verbosity = 0 self.color = sys.stdout.isatty() - self.warnings = [] - self.warnings_unique = set() + self.warnings: list[str] = [] + self.warnings_unique: set[str] = set() self.fd = sys.stderr # default to stderr until config is initialized to avoid early messages going to stdout self.rows = 0 self.columns = 0 self.truncate = 0 self.redact = True - self.sensitive = set() + self.sensitive: set[str] = set() if os.isatty(0): self.rows, self.columns = unpack('HHHH', fcntl.ioctl(0, TIOCGWINSZ, pack('HHHH', 0, 0, 0, 0)))[:2] @@ -859,11 +855,11 @@ class Display: self.print_message(message, color=color, truncate=truncate) def print_message( # pylint: disable=locally-disabled, invalid-name - self, - message: str, - color: t.Optional[str] = None, - stderr: bool = False, - truncate: bool = False, + self, + message: str, + color: t.Optional[str] = None, + stderr: bool = False, + truncate: bool = False, ) -> None: """Display a message.""" if self.redact and self.sensitive: @@ -905,13 +901,13 @@ class ApplicationWarning(Exception): class SubprocessError(ApplicationError): """Error resulting from failed subprocess execution.""" def __init__( - self, - cmd: list[str], - status: int = 0, - stdout: t.Optional[str] = None, - stderr: t.Optional[str] = None, - runtime: t.Optional[float] = None, - error_callback: t.Optional[c.Callable[[SubprocessError], None]] = None, + self, + cmd: list[str], + status: int = 0, + stdout: t.Optional[str] = None, + stderr: t.Optional[str] = None, + runtime: t.Optional[float] = None, + error_callback: t.Optional[c.Callable[[SubprocessError], None]] = None, ) -> None: message = 'Command "%s" returned exit status %s.\n' % (shlex.join(cmd), status) @@ -963,7 +959,7 @@ class HostConnectionError(ApplicationError): self._callback() -def retry(func, ex_type=SubprocessError, sleep=10, attempts=10, warn=True): +def retry(func: t.Callable[..., TValue], ex_type: t.Type[BaseException] = SubprocessError, sleep: int = 10, attempts: int = 10, warn: bool = True) -> TValue: """Retry the specified function on failure.""" for dummy in range(1, attempts): try: @@ -1094,7 +1090,7 @@ def load_module(path: str, name: str) -> None: spec.loader.exec_module(module) -def sanitize_host_name(name): +def sanitize_host_name(name: str) -> str: """Return a sanitized version of the given name, suitable for use as a hostname.""" return re.sub('[^A-Za-z0-9]+', '-', name)[:63].strip('-') diff --git a/test/lib/ansible_test/_internal/util_common.py b/test/lib/ansible_test/_internal/util_common.py index fbd9e71d..1dfc7f38 100644 --- a/test/lib/ansible_test/_internal/util_common.py +++ b/test/lib/ansible_test/_internal/util_common.py @@ -96,7 +96,7 @@ class ResultType: TMP: ResultType = None @staticmethod - def _populate(): + def _populate() -> None: ResultType.BOT = ResultType('bot') ResultType.COVERAGE = ResultType('coverage') ResultType.DATA = ResultType('data') @@ -288,7 +288,7 @@ def get_injector_path() -> str: verified_chmod(injector_path, MODE_DIRECTORY) - def cleanup_injector(): + def cleanup_injector() -> None: """Remove the temporary injector directory.""" remove_tree(injector_path) @@ -388,7 +388,7 @@ def create_interpreter_wrapper(interpreter: str, injected_interpreter: str) -> N verified_chmod(injected_interpreter, MODE_FILE_EXECUTE) -def cleanup_python_paths(): +def cleanup_python_paths() -> None: """Clean up all temporary python directories.""" for path in sorted(PYTHON_PATHS.values()): display.info('Cleaning up temporary python directory: %s' % path, verbosity=2) @@ -396,14 +396,14 @@ def cleanup_python_paths(): def intercept_python( - args: CommonConfig, - python: PythonConfig, - cmd: list[str], - env: dict[str, str], - capture: bool, - data: t.Optional[str] = None, - cwd: t.Optional[str] = None, - always: bool = False, + args: CommonConfig, + python: PythonConfig, + cmd: list[str], + env: dict[str, str], + capture: bool, + data: t.Optional[str] = None, + cwd: t.Optional[str] = None, + always: bool = False, ) -> tuple[t.Optional[str], t.Optional[str]]: """ Run a command while intercepting invocations of Python to control the version used. @@ -428,20 +428,20 @@ def intercept_python( def run_command( - args: CommonConfig, - cmd: c.Iterable[str], - capture: bool, - env: t.Optional[dict[str, str]] = None, - data: t.Optional[str] = None, - cwd: t.Optional[str] = None, - always: bool = False, - stdin: t.Optional[t.IO[bytes]] = None, - stdout: t.Optional[t.IO[bytes]] = None, - interactive: bool = False, - output_stream: t.Optional[OutputStream] = None, - cmd_verbosity: int = 1, - str_errors: str = 'strict', - error_callback: t.Optional[c.Callable[[SubprocessError], None]] = None, + args: CommonConfig, + cmd: c.Iterable[str], + capture: bool, + env: t.Optional[dict[str, str]] = None, + data: t.Optional[str] = None, + cwd: t.Optional[str] = None, + always: bool = False, + stdin: t.Optional[t.IO[bytes]] = None, + stdout: t.Optional[t.IO[bytes]] = None, + interactive: bool = False, + output_stream: t.Optional[OutputStream] = None, + cmd_verbosity: int = 1, + str_errors: str = 'strict', + error_callback: t.Optional[c.Callable[[SubprocessError], None]] = None, ) -> tuple[t.Optional[str], t.Optional[str]]: """Run the specified command and return stdout and stderr as a tuple.""" explain = args.explain and not always @@ -449,7 +449,7 @@ def run_command( output_stream=output_stream, cmd_verbosity=cmd_verbosity, str_errors=str_errors, error_callback=error_callback) -def yamlcheck(python): +def yamlcheck(python: PythonConfig) -> t.Optional[bool]: """Return True if PyYAML has libyaml support, False if it does not and None if it was not found.""" result = json.loads(raw_command([python.path, os.path.join(ANSIBLE_TEST_TARGET_TOOLS_ROOT, 'yamlcheck.py')], capture=True)[0]) diff --git a/test/lib/ansible_test/_internal/venv.py b/test/lib/ansible_test/_internal/venv.py index 9e999c16..ec498ed9 100644 --- a/test/lib/ansible_test/_internal/venv.py +++ b/test/lib/ansible_test/_internal/venv.py @@ -41,8 +41,8 @@ from .python_requirements import ( def get_virtual_python( - args: EnvironmentConfig, - python: VirtualPythonConfig, + args: EnvironmentConfig, + python: VirtualPythonConfig, ) -> VirtualPythonConfig: """Create a virtual environment for the given Python and return the path to its root.""" if python.system_site_packages: diff --git a/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py b/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py index 9513ed30..270c9f44 100644 --- a/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +++ b/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py @@ -22,7 +22,6 @@ import argparse import ast import datetime import json -import errno import os import re import subprocess @@ -2467,12 +2466,9 @@ class GitCache: self.head_tree = self._get_module_files() else: raise - except OSError as ex: - if ex.errno == errno.ENOENT: - # fallback when git is not installed - self.head_tree = self._get_module_files() - else: - raise + except FileNotFoundError: + # fallback when git is not installed + self.head_tree = self._get_module_files() allowed_exts = ('.py', '.ps1') if plugin_type != 'module': diff --git a/test/lib/ansible_test/_util/target/sanity/import/importer.py b/test/lib/ansible_test/_util/target/sanity/import/importer.py index 3180530c..44a5ddc9 100644 --- a/test/lib/ansible_test/_util/target/sanity/import/importer.py +++ b/test/lib/ansible_test/_util/target/sanity/import/importer.py @@ -44,7 +44,8 @@ def main(): # noinspection PyCompatibility from importlib import import_module except ImportError: - def import_module(name): + def import_module(name, package=None): # type: (str, str | None) -> types.ModuleType + assert package is None __import__(name) return sys.modules[name] diff --git a/test/units/playbook/test_helpers.py b/test/units/playbook/test_helpers.py index e784312f..a89730ca 100644 --- a/test/units/playbook/test_helpers.py +++ b/test/units/playbook/test_helpers.py @@ -160,20 +160,10 @@ class TestLoadListOfTasks(unittest.TestCase, MixinForMocks): self.assertIsInstance(block.always, list) self.assertEqual(len(block.always), 0) - def test_block_unknown_action_use_handlers(self): - ds = [{ - 'block': [{'action': 'foo_test_block_unknown_action'}] - }] - res = helpers.load_list_of_tasks(ds, play=self.mock_play, use_handlers=True, - variable_manager=self.mock_variable_manager, loader=self.fake_loader) - self._assert_is_task_list_or_blocks(res) - self.assertIsInstance(res[0], Block) - self._assert_default_block(res[0]) - - def test_one_bogus_block_use_handlers(self): + def test_block_use_handlers(self): ds = [{'block': True}] self.assertRaisesRegex(errors.AnsibleParserError, - "A malformed block was encountered", + "Using a block as a handler is not supported.", helpers.load_list_of_tasks, ds, play=self.mock_play, use_handlers=True, variable_manager=self.mock_variable_manager, loader=self.fake_loader) |