diff --git a/changelogs/fragments/ansible-test-sanity-requirements-again.yml b/changelogs/fragments/ansible-test-sanity-requirements-again.yml new file mode 100644 index 00000000000..d52fccbef86 --- /dev/null +++ b/changelogs/fragments/ansible-test-sanity-requirements-again.yml @@ -0,0 +1,5 @@ +minor_changes: + - ansible-test - Update pinned sanity test requirements, including upgrading to pylint 4.0.0. + - ansible-test - Filter out pylint messages for invalid filenames and display a notice when doing so. + - ansible-test - Update astroid imports in custom pylint checkers. + - ansible-test - Default to Python 3.14 in the ``base`` and ``default`` test containers. diff --git a/requirements.txt b/requirements.txt index 2daebde7d68..5454cada1de 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,4 +12,4 @@ packaging # NOTE: Ref: https://github.com/sarugaku/resolvelib/issues/69 # NOTE: When updating the upper bound, also update the latest version used # NOTE: in the ansible-galaxy-collection test suite. -resolvelib >= 0.5.3, < 2.0.0 # dependency resolver used by ansible-galaxy +resolvelib >= 0.8.0, < 2.0.0 # dependency resolver used by ansible-galaxy diff --git a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/plugin_utils/check_pylint.py b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/plugin_utils/check_pylint.py index 6ec9023478d..43409106d10 100644 --- a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/plugin_utils/check_pylint.py +++ b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/plugin_utils/check_pylint.py @@ -14,4 +14,4 @@ import string # pylint: disable=unused-import # 'Call' object has no attribute 'value' result = {None: None}[{}.get('something')] -foo = {}.keys() +foo = {}.keys() # should trigger disallowed-name, but doesn't in pylint 4.0.0, probably due to https://github.com/pylint-dev/pylint/issues/10652 diff --git a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/tests/sanity/ignore.txt b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/tests/sanity/ignore.txt index dcbe827ca56..e1b3f4ca09d 100644 --- a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/tests/sanity/ignore.txt +++ b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/tests/sanity/ignore.txt @@ -1,7 +1,6 @@ plugins/modules/bad.py import plugins/modules/bad.py pylint:ansible-bad-module-import plugins/lookup/bad.py import -plugins/plugin_utils/check_pylint.py pylint:disallowed-name tests/integration/targets/hello/files/bad.py pylint:ansible-bad-function tests/integration/targets/hello/files/bad.py pylint:ansible-bad-import tests/integration/targets/hello/files/bad.py pylint:ansible-bad-import-from diff --git a/test/lib/ansible_test/_data/completion/docker.txt b/test/lib/ansible_test/_data/completion/docker.txt index 05de8564387..a9dfdb30bdb 100644 --- a/test/lib/ansible_test/_data/completion/docker.txt +++ b/test/lib/ansible_test/_data/completion/docker.txt @@ -1,6 +1,6 @@ -base image=quay.io/ansible/base-test-container:v2.20-1 python=3.13,3.9,3.10,3.11,3.12,3.14 -default image=quay.io/ansible/default-test-container:v2.20-2 python=3.13,3.9,3.10,3.11,3.12,3.14 context=collection -default image=quay.io/ansible/ansible-core-test-container:v2.20-2 python=3.13,3.9,3.10,3.11,3.12,3.14 context=ansible-core +base image=quay.io/ansible/base-test-container:v2.20-1 python=3.14,3.9,3.10,3.11,3.12,3.13 +default image=quay.io/ansible/default-test-container:v2.20-2 python=3.14,3.9,3.10,3.11,3.12,3.13 context=collection +default image=quay.io/ansible/ansible-core-test-container:v2.20-2 python=3.14,3.9,3.10,3.11,3.12,3.13 context=ansible-core alpine322 image=quay.io/ansible/alpine-test-container:3.22-v2.20-0 python=3.12 cgroup=none audit=none fedora42 image=quay.io/ansible/fedora-test-container:42-v2.20-0 python=3.13 cgroup=v2-only ubuntu2204 image=quay.io/ansible/ubuntu-test-container:22.04-v2.20-0 python=3.10 diff --git a/test/lib/ansible_test/_data/requirements/ansible.txt b/test/lib/ansible_test/_data/requirements/ansible.txt index 2daebde7d68..5454cada1de 100644 --- a/test/lib/ansible_test/_data/requirements/ansible.txt +++ b/test/lib/ansible_test/_data/requirements/ansible.txt @@ -12,4 +12,4 @@ packaging # NOTE: Ref: https://github.com/sarugaku/resolvelib/issues/69 # NOTE: When updating the upper bound, also update the latest version used # NOTE: in the ansible-galaxy-collection test suite. -resolvelib >= 0.5.3, < 2.0.0 # dependency resolver used by ansible-galaxy +resolvelib >= 0.8.0, < 2.0.0 # dependency resolver used by ansible-galaxy diff --git a/test/lib/ansible_test/_data/requirements/sanity.ansible-doc.txt b/test/lib/ansible_test/_data/requirements/sanity.ansible-doc.txt index 269d4a24d63..a7497b3d7bc 100644 --- a/test/lib/ansible_test/_data/requirements/sanity.ansible-doc.txt +++ b/test/lib/ansible_test/_data/requirements/sanity.ansible-doc.txt @@ -1,5 +1,5 @@ # edit "sanity.ansible-doc.in" and generate with: hacking/update-sanity-requirements.py --test ansible-doc Jinja2==3.1.6 -MarkupSafe==3.0.2 +MarkupSafe==3.0.3 packaging==25.0 -PyYAML==6.0.2 +PyYAML==6.0.3 diff --git a/test/lib/ansible_test/_data/requirements/sanity.changelog.txt b/test/lib/ansible_test/_data/requirements/sanity.changelog.txt index ea1088f13ca..eac80cd3e9f 100644 --- a/test/lib/ansible_test/_data/requirements/sanity.changelog.txt +++ b/test/lib/ansible_test/_data/requirements/sanity.changelog.txt @@ -2,7 +2,7 @@ antsibull-changelog==0.29.0 docutils==0.18.1 packaging==25.0 -PyYAML==6.0.2 +PyYAML==6.0.3 rstcheck==5.0.0 semantic-version==2.10.0 types-docutils==0.18.3 diff --git a/test/lib/ansible_test/_data/requirements/sanity.import.plugin.txt b/test/lib/ansible_test/_data/requirements/sanity.import.plugin.txt index b0e534dbeb5..ddb6d647cbe 100644 --- a/test/lib/ansible_test/_data/requirements/sanity.import.plugin.txt +++ b/test/lib/ansible_test/_data/requirements/sanity.import.plugin.txt @@ -1,4 +1,4 @@ # edit "sanity.import.plugin.in" and generate with: hacking/update-sanity-requirements.py --test import.plugin Jinja2==3.1.6 -MarkupSafe==3.0.2 -PyYAML==6.0.2 +MarkupSafe==3.0.3 +PyYAML==6.0.3 diff --git a/test/lib/ansible_test/_data/requirements/sanity.import.txt b/test/lib/ansible_test/_data/requirements/sanity.import.txt index 3ea630668ed..35a937d224c 100644 --- a/test/lib/ansible_test/_data/requirements/sanity.import.txt +++ b/test/lib/ansible_test/_data/requirements/sanity.import.txt @@ -1,2 +1,2 @@ # edit "sanity.import.in" and generate with: hacking/update-sanity-requirements.py --test import -PyYAML==6.0.2 +PyYAML==6.0.3 diff --git a/test/lib/ansible_test/_data/requirements/sanity.integration-aliases.txt b/test/lib/ansible_test/_data/requirements/sanity.integration-aliases.txt index 3c7dd80db84..6b9824227f0 100644 --- a/test/lib/ansible_test/_data/requirements/sanity.integration-aliases.txt +++ b/test/lib/ansible_test/_data/requirements/sanity.integration-aliases.txt @@ -1,2 +1,2 @@ # edit "sanity.integration-aliases.in" and generate with: hacking/update-sanity-requirements.py --test integration-aliases -PyYAML==6.0.2 +PyYAML==6.0.3 diff --git a/test/lib/ansible_test/_data/requirements/sanity.pylint.txt b/test/lib/ansible_test/_data/requirements/sanity.pylint.txt index 8ad125663eb..610154df66a 100644 --- a/test/lib/ansible_test/_data/requirements/sanity.pylint.txt +++ b/test/lib/ansible_test/_data/requirements/sanity.pylint.txt @@ -1,9 +1,9 @@ # edit "sanity.pylint.in" and generate with: hacking/update-sanity-requirements.py --test pylint -astroid==3.3.11 +astroid==4.0.1 dill==0.4.0 -isort==6.0.1 +isort==7.0.0 mccabe==0.7.0 -platformdirs==4.4.0 -pylint==3.3.8 -PyYAML==6.0.2 +platformdirs==4.5.0 +pylint==4.0.0 +PyYAML==6.0.3 tomlkit==0.13.3 diff --git a/test/lib/ansible_test/_data/requirements/sanity.runtime-metadata.txt b/test/lib/ansible_test/_data/requirements/sanity.runtime-metadata.txt index e4b2449e5d5..5215462f869 100644 --- a/test/lib/ansible_test/_data/requirements/sanity.runtime-metadata.txt +++ b/test/lib/ansible_test/_data/requirements/sanity.runtime-metadata.txt @@ -1,3 +1,3 @@ # edit "sanity.runtime-metadata.in" and generate with: hacking/update-sanity-requirements.py --test runtime-metadata -PyYAML==6.0.2 +PyYAML==6.0.3 voluptuous==0.15.2 diff --git a/test/lib/ansible_test/_data/requirements/sanity.validate-modules.txt b/test/lib/ansible_test/_data/requirements/sanity.validate-modules.txt index 265ee3bb94e..8906d9ec282 100644 --- a/test/lib/ansible_test/_data/requirements/sanity.validate-modules.txt +++ b/test/lib/ansible_test/_data/requirements/sanity.validate-modules.txt @@ -1,6 +1,6 @@ # edit "sanity.validate-modules.in" and generate with: hacking/update-sanity-requirements.py --test validate-modules antsibull-docs-parser==1.0.0 Jinja2==3.1.6 -MarkupSafe==3.0.2 -PyYAML==6.0.2 +MarkupSafe==3.0.3 +PyYAML==6.0.3 voluptuous==0.15.2 diff --git a/test/lib/ansible_test/_data/requirements/sanity.yamllint.txt b/test/lib/ansible_test/_data/requirements/sanity.yamllint.txt index 1967d2c7598..95a0e350b3b 100644 --- a/test/lib/ansible_test/_data/requirements/sanity.yamllint.txt +++ b/test/lib/ansible_test/_data/requirements/sanity.yamllint.txt @@ -1,4 +1,4 @@ # edit "sanity.yamllint.in" and generate with: hacking/update-sanity-requirements.py --test yamllint pathspec==0.12.1 -PyYAML==6.0.2 +PyYAML==6.0.3 yamllint==1.37.1 diff --git a/test/lib/ansible_test/_internal/commands/sanity/pylint.py b/test/lib/ansible_test/_internal/commands/sanity/pylint.py index 5665f72aabc..301ff47c13d 100644 --- a/test/lib/ansible_test/_internal/commands/sanity/pylint.py +++ b/test/lib/ansible_test/_internal/commands/sanity/pylint.py @@ -301,4 +301,15 @@ class PylintTest(SanitySingleVersion): else: messages = [] + expected_paths = set(paths) + + unexpected_messages = [message for message in messages if message["path"] not in expected_paths] + messages = [message for message in messages if message["path"] in expected_paths] + + for unexpected_message in unexpected_messages: + display.info(f"Unexpected message: {json.dumps(unexpected_message)}", verbosity=4) + + if unexpected_messages: + display.notice(f"Discarded {len(unexpected_messages)} unexpected messages. Use -vvvv to display.") + return messages diff --git a/test/lib/ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py b/test/lib/ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py index 000213c4a76..da332e34a2e 100644 --- a/test/lib/ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +++ b/test/lib/ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py @@ -11,9 +11,11 @@ import functools import pathlib import re -import astroid -import astroid.context +import astroid.bases +import astroid.exceptions +import astroid.nodes import astroid.typing +import astroid.util import pylint.lint import pylint.checkers @@ -42,7 +44,7 @@ class DeprecationCallArgs: def all_args_dynamic(self) -> bool: """True if all args are dynamic or None, otherwise False.""" - return all(arg is None or isinstance(arg, astroid.NodeNG) for arg in dataclasses.asdict(self).values()) + return all(arg is None or isinstance(arg, astroid.nodes.NodeNG) for arg in dataclasses.asdict(self).values()) class AnsibleDeprecatedChecker(pylint.checkers.BaseChecker): @@ -177,7 +179,7 @@ class AnsibleDeprecatedChecker(pylint.checkers.BaseChecker): def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - self.module_cache: dict[str, astroid.Module] = {} + self.module_cache: dict[str, astroid.nodes.Module] = {} @functools.cached_property def collection_name(self) -> str | None: @@ -226,7 +228,7 @@ class AnsibleDeprecatedChecker(pylint.checkers.BaseChecker): return None @pylint.checkers.utils.only_required_for_messages(*(msgs.keys())) - def visit_call(self, node: astroid.Call) -> None: + def visit_call(self, node: astroid.nodes.Call) -> None: """Visit a call node.""" if inferred := self.infer(node.func): name = self.get_fully_qualified_name(inferred) @@ -234,50 +236,50 @@ class AnsibleDeprecatedChecker(pylint.checkers.BaseChecker): if args := self.DEPRECATION_FUNCTIONS.get(name): self.check_call(node, name, args) - def infer(self, node: astroid.NodeNG) -> astroid.NodeNG | None: + def infer(self, node: astroid.nodes.NodeNG) -> astroid.nodes.NodeNG | None: """Return the inferred node from the given node, or `None` if it cannot be unambiguously inferred.""" names: list[str] = [] - target: astroid.NodeNG | None = node + target: astroid.nodes.NodeNG | None = node inferred: astroid.typing.InferenceResult | None = None while target: if inferred := astroid.util.safe_infer(target): break - if isinstance(target, astroid.Call): + if isinstance(target, astroid.nodes.Call): inferred = self.infer(target.func) break - if isinstance(target, astroid.FunctionDef): + if isinstance(target, astroid.nodes.FunctionDef): inferred = target break - if isinstance(target, astroid.Name): + if isinstance(target, astroid.nodes.Name): target = self.infer_name(target) - elif isinstance(target, astroid.AssignName) and isinstance(target.parent, astroid.Assign): + elif isinstance(target, astroid.nodes.AssignName) and isinstance(target.parent, astroid.nodes.Assign): target = target.parent.value - elif isinstance(target, astroid.Attribute): + elif isinstance(target, astroid.nodes.Attribute): names.append(target.attrname) target = target.expr else: break for name in reversed(names): - if isinstance(inferred, astroid.Instance): + if isinstance(inferred, astroid.bases.Instance): try: attr = next(iter(inferred.getattr(name)), None) - except astroid.AttributeInferenceError: + except astroid.exceptions.AttributeInferenceError: break - if isinstance(attr, astroid.AssignAttr): + if isinstance(attr, astroid.nodes.AssignAttr): inferred = self.get_ansible_module(attr) continue - if isinstance(attr, astroid.FunctionDef): + if isinstance(attr, astroid.nodes.FunctionDef): inferred = attr continue - if not isinstance(inferred, (astroid.Module, astroid.ClassDef)): + if not isinstance(inferred, (astroid.nodes.Module, astroid.nodes.ClassDef)): inferred = None break @@ -288,15 +290,15 @@ class AnsibleDeprecatedChecker(pylint.checkers.BaseChecker): else: inferred = self.infer(inferred) - if isinstance(inferred, astroid.FunctionDef) and isinstance(inferred.parent, astroid.ClassDef): - inferred = astroid.BoundMethod(inferred, inferred.parent) + if isinstance(inferred, astroid.nodes.FunctionDef) and isinstance(inferred.parent, astroid.nodes.ClassDef): + inferred = astroid.bases.BoundMethod(inferred, inferred.parent) return inferred - def infer_name(self, node: astroid.Name) -> astroid.NodeNG | None: + def infer_name(self, node: astroid.nodes.Name) -> astroid.nodes.NodeNG | None: """Infer the node referenced by the given name, or `None` if it cannot be unambiguously inferred.""" scope = node.scope() - inferred: astroid.NodeNG | None = None + inferred: astroid.nodes.NodeNG | None = None name = node.name while scope: @@ -306,12 +308,12 @@ class AnsibleDeprecatedChecker(pylint.checkers.BaseChecker): scope = scope.parent.scope() if scope.parent else None continue - if isinstance(assignment, astroid.AssignName) and isinstance(assignment.parent, astroid.Assign): + if isinstance(assignment, astroid.nodes.AssignName) and isinstance(assignment.parent, astroid.nodes.Assign): inferred = assignment.parent.value elif ( - isinstance(scope, astroid.FunctionDef) - and isinstance(assignment, astroid.AssignName) - and isinstance(assignment.parent, astroid.Arguments) + isinstance(scope, astroid.nodes.FunctionDef) + and isinstance(assignment, astroid.nodes.AssignName) + and isinstance(assignment.parent, astroid.nodes.Arguments) and assignment.parent.annotations ): idx, _node = assignment.parent.find_argname(name) @@ -322,12 +324,12 @@ class AnsibleDeprecatedChecker(pylint.checkers.BaseChecker): except IndexError: pass else: - if isinstance(annotation, astroid.Name): + if isinstance(annotation, astroid.nodes.Name): name = annotation.name continue - elif isinstance(assignment, astroid.ClassDef): + elif isinstance(assignment, astroid.nodes.ClassDef): inferred = assignment - elif isinstance(assignment, astroid.ImportFrom): + elif isinstance(assignment, astroid.nodes.ImportFrom): if module := self.get_module(assignment): name = assignment.real_name(name) scope = module.scope() @@ -337,7 +339,7 @@ class AnsibleDeprecatedChecker(pylint.checkers.BaseChecker): return inferred - def get_module(self, node: astroid.ImportFrom) -> astroid.Module | None: + def get_module(self, node: astroid.nodes.ImportFrom) -> astroid.nodes.Module | None: """Import the requested module if possible and cache the result.""" module_name = pylint.checkers.utils.get_import_name(node, node.modname) @@ -357,21 +359,21 @@ class AnsibleDeprecatedChecker(pylint.checkers.BaseChecker): return module @staticmethod - def get_fully_qualified_name(node: astroid.NodeNG) -> str | None: + def get_fully_qualified_name(node: astroid.nodes.NodeNG) -> str | None: """Return the fully qualified name of the given inferred node.""" parent = node.parent parts: tuple[str, ...] | None - if isinstance(node, astroid.FunctionDef) and isinstance(parent, astroid.Module): + if isinstance(node, astroid.nodes.FunctionDef) and isinstance(parent, astroid.nodes.Module): parts = (parent.name, node.name) - elif isinstance(node, astroid.BoundMethod) and isinstance(parent, astroid.ClassDef) and isinstance(parent.parent, astroid.Module): + elif isinstance(node, astroid.bases.BoundMethod) and isinstance(parent, astroid.nodes.ClassDef) and isinstance(parent.parent, astroid.nodes.Module): parts = (parent.parent.name, parent.name, node.name) else: parts = None return '.'.join(parts) if parts else None - def check_call(self, node: astroid.Call, name: str, args: tuple[str, ...]) -> None: + def check_call(self, node: astroid.nodes.Call, name: str, args: tuple[str, ...]) -> None: """Check the given deprecation call node for valid arguments.""" call_args = self.get_deprecation_call_args(node, args) @@ -400,7 +402,7 @@ class AnsibleDeprecatedChecker(pylint.checkers.BaseChecker): self.check_version(node, name, call_args) @staticmethod - def get_deprecation_call_args(node: astroid.Call, args: tuple[str, ...]) -> DeprecationCallArgs: + def get_deprecation_call_args(node: astroid.nodes.Call, args: tuple[str, ...]) -> DeprecationCallArgs: """Get the deprecation call arguments from the given node.""" fields: dict[str, object] = {} @@ -413,12 +415,12 @@ class AnsibleDeprecatedChecker(pylint.checkers.BaseChecker): fields[keyword.arg] = keyword.value for key, value in fields.items(): - if isinstance(value, astroid.Const): + if isinstance(value, astroid.nodes.Const): fields[key] = value.value return DeprecationCallArgs(**fields) - def check_collection_name(self, node: astroid.Call, name: str, args: DeprecationCallArgs) -> None: + def check_collection_name(self, node: astroid.nodes.Call, name: str, args: DeprecationCallArgs) -> None: """Check the collection name provided to the given call node.""" deprecator_requirement = self.is_deprecator_required() @@ -459,14 +461,14 @@ class AnsibleDeprecatedChecker(pylint.checkers.BaseChecker): if args.collection_name and args.collection_name != expected_collection_name: self.add_message('wrong-collection-deprecated', node=node, args=(args.collection_name, name)) - def check_version(self, node: astroid.Call, name: str, args: DeprecationCallArgs) -> None: + def check_version(self, node: astroid.nodes.Call, name: str, args: DeprecationCallArgs) -> None: """Check the version provided to the given call node.""" if self.collection_name: self.check_collection_version(node, name, args) else: self.check_core_version(node, name, args) - def check_core_version(self, node: astroid.Call, name: str, args: DeprecationCallArgs) -> None: + def check_core_version(self, node: astroid.nodes.Call, name: str, args: DeprecationCallArgs) -> None: """Check the core version provided to the given call node.""" try: if not isinstance(args.version, str) or not args.version: @@ -480,7 +482,7 @@ class AnsibleDeprecatedChecker(pylint.checkers.BaseChecker): if self.ANSIBLE_VERSION >= strict_version: self.add_message('ansible-deprecated-version', node=node, args=(args.version, name)) - def check_collection_version(self, node: astroid.Call, name: str, args: DeprecationCallArgs) -> None: + def check_collection_version(self, node: astroid.nodes.Call, name: str, args: DeprecationCallArgs) -> None: """Check the collection version provided to the given call node.""" try: if not isinstance(args.version, str) or not args.version: @@ -497,7 +499,7 @@ class AnsibleDeprecatedChecker(pylint.checkers.BaseChecker): if semantic_version.major != 0 and (semantic_version.minor != 0 or semantic_version.patch != 0): self.add_message('removal-version-must-be-major', node=node, args=(args.version,)) - def check_date(self, node: astroid.Call, name: str, args: DeprecationCallArgs) -> None: + def check_date(self, node: astroid.nodes.Call, name: str, args: DeprecationCallArgs) -> None: """Check the date provided to the given call node.""" try: date_parsed = self.parse_isodate(args.date) @@ -515,18 +517,19 @@ class AnsibleDeprecatedChecker(pylint.checkers.BaseChecker): raise TypeError(type(value)) - def get_ansible_module(self, node: astroid.AssignAttr) -> astroid.Instance | None: + def get_ansible_module(self, node: astroid.nodes.AssignAttr) -> astroid.bases.Instance | None: """Infer an AnsibleModule instance node from the given assignment.""" - if isinstance(node.parent, astroid.Assign) and isinstance(node.parent.type_annotation, astroid.Name): + if isinstance(node.parent, astroid.nodes.Assign) and isinstance(node.parent.type_annotation, astroid.nodes.Name): inferred = self.infer_name(node.parent.type_annotation) - elif isinstance(node.parent, astroid.Assign) and isinstance(node.parent.parent, astroid.FunctionDef) and isinstance(node.parent.value, astroid.Name): + elif (isinstance(node.parent, astroid.nodes.Assign) and isinstance(node.parent.parent, astroid.nodes.FunctionDef) and + isinstance(node.parent.value, astroid.nodes.Name)): inferred = self.infer_name(node.parent.value) - elif isinstance(node.parent, astroid.AnnAssign) and isinstance(node.parent.annotation, astroid.Name): + elif isinstance(node.parent, astroid.nodes.AnnAssign) and isinstance(node.parent.annotation, astroid.nodes.Name): inferred = self.infer_name(node.parent.annotation) else: inferred = None - if isinstance(inferred, astroid.ClassDef) and inferred.name == 'AnsibleModule': + if isinstance(inferred, astroid.nodes.ClassDef) and inferred.name == 'AnsibleModule': return inferred.instantiate_class() return None diff --git a/test/lib/ansible_test/_util/controller/sanity/pylint/plugins/string_format.py b/test/lib/ansible_test/_util/controller/sanity/pylint/plugins/string_format.py index ef8703cc5f1..e640a2c7ab2 100644 --- a/test/lib/ansible_test/_util/controller/sanity/pylint/plugins/string_format.py +++ b/test/lib/ansible_test/_util/controller/sanity/pylint/plugins/string_format.py @@ -5,7 +5,9 @@ # -*- coding: utf-8 -*- from __future__ import annotations -import astroid +import astroid.bases +import astroid.exceptions +import astroid.nodes try: from pylint.checkers.utils import check_messages @@ -39,22 +41,22 @@ class AnsibleStringFormatChecker(BaseChecker): def visit_call(self, node): """Visit a call node.""" func = utils.safe_infer(node.func) - if (isinstance(func, astroid.BoundMethod) - and isinstance(func.bound, astroid.Instance) + if (isinstance(func, astroid.bases.BoundMethod) + and isinstance(func.bound, astroid.bases.Instance) and func.bound.name in ('str', 'unicode', 'bytes')): if func.name == 'format': self._check_new_format(node, func) def _check_new_format(self, node, func): """ Check the new string formatting """ - if (isinstance(node.func, astroid.Attribute) - and not isinstance(node.func.expr, astroid.Const)): + if (isinstance(node.func, astroid.nodes.Attribute) + and not isinstance(node.func.expr, astroid.nodes.Const)): return try: strnode = next(func.bound.infer()) - except astroid.InferenceError: + except astroid.exceptions.InferenceError: return - if not isinstance(strnode, astroid.Const): + if not isinstance(strnode, astroid.nodes.Const): return if isinstance(strnode.value, bytes): diff --git a/test/lib/ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py b/test/lib/ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py index 5535215855d..324084eeee6 100644 --- a/test/lib/ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py +++ b/test/lib/ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py @@ -6,7 +6,8 @@ import functools import os import typing as t -import astroid +import astroid.exceptions +import astroid.nodes from pylint.checkers import BaseChecker @@ -146,21 +147,21 @@ class AnsibleUnwantedChecker(BaseChecker): """True if ansible-core is being tested.""" return not self.linter.config.collection_name - def visit_import(self, node): # type: (astroid.node_classes.Import) -> None + def visit_import(self, node: astroid.nodes.Import) -> None: """Visit an import node.""" for name in node.names: self._check_import(node, name[0]) - def visit_importfrom(self, node): # type: (astroid.node_classes.ImportFrom) -> None + def visit_importfrom(self, node: astroid.nodes.ImportFrom) -> None: """Visit an import from node.""" self._check_importfrom(node, node.modname, node.names) - def visit_attribute(self, node): # type: (astroid.node_classes.Attribute) -> None + def visit_attribute(self, node: astroid.nodes.Attribute) -> None: """Visit an attribute node.""" last_child = node.last_child() # this is faster than using type inference and will catch the most common cases - if not isinstance(last_child, astroid.node_classes.Name): + if not isinstance(last_child, astroid.nodes.Name): return module = last_child.name @@ -171,13 +172,13 @@ class AnsibleUnwantedChecker(BaseChecker): if entry.applies_to(self.linter.current_file, node.attrname): self.add_message(self.BAD_IMPORT_FROM, args=(node.attrname, entry.alternative, module), node=node) - def visit_call(self, node): # type: (astroid.node_classes.Call) -> None + def visit_call(self, node: astroid.nodes.Call) -> None: """Visit a call node.""" try: for i in node.func.inferred(): func = None - if isinstance(i, astroid.scoped_nodes.FunctionDef) and isinstance(i.parent, astroid.scoped_nodes.Module): + if isinstance(i, astroid.nodes.FunctionDef) and isinstance(i.parent, astroid.nodes.Module): func = '%s.%s' % (i.parent.name, i.name) if not func: @@ -190,7 +191,7 @@ class AnsibleUnwantedChecker(BaseChecker): except astroid.exceptions.InferenceError: pass - def _check_import(self, node, modname): # type: (astroid.node_classes.Import, str) -> None + def _check_import(self, node: astroid.nodes.Import, modname: str) -> None: """Check the imports on the specified import node.""" self._check_module_import(node, modname) @@ -202,7 +203,7 @@ class AnsibleUnwantedChecker(BaseChecker): if entry.applies_to(self.linter.current_file): self.add_message(self.BAD_IMPORT, args=(entry.alternative, modname), node=node) - def _check_importfrom(self, node, modname, names): # type: (astroid.node_classes.ImportFrom, str, t.List[str]) -> None + def _check_importfrom(self, node: astroid.nodes.ImportFrom, modname: str, names: list[tuple[str, str | None]]) -> None: """Check the imports on the specified import from node.""" self._check_module_import(node, modname) @@ -215,7 +216,7 @@ class AnsibleUnwantedChecker(BaseChecker): if entry.applies_to(self.linter.current_file, name[0]): self.add_message(self.BAD_IMPORT_FROM, args=(name[0], entry.alternative, modname), node=node) - def _check_module_import(self, node, modname): # type: (t.Union[astroid.node_classes.Import, astroid.node_classes.ImportFrom], str) -> None + def _check_module_import(self, node: astroid.nodes.Import | astroid.nodes.ImportFrom, modname: str) -> None: """Check the module import on the given import or import from node.""" if not is_module_path(self.linter.current_file): return diff --git a/test/sanity/code-smell/black.requirements.txt b/test/sanity/code-smell/black.requirements.txt index d18c072d543..f3ac3d9cf84 100644 --- a/test/sanity/code-smell/black.requirements.txt +++ b/test/sanity/code-smell/black.requirements.txt @@ -1,7 +1,8 @@ # edit "black.requirements.in" and generate with: hacking/update-sanity-requirements.py --test black -black==25.1.0 -click==8.2.1 +black==25.9.0 +click==8.3.0 mypy_extensions==1.1.0 packaging==25.0 pathspec==0.12.1 -platformdirs==4.4.0 +platformdirs==4.5.0 +pytokens==0.1.10 diff --git a/test/sanity/code-smell/deprecated-config.requirements.txt b/test/sanity/code-smell/deprecated-config.requirements.txt index 548be8dbed0..c7470a56c40 100644 --- a/test/sanity/code-smell/deprecated-config.requirements.txt +++ b/test/sanity/code-smell/deprecated-config.requirements.txt @@ -1,4 +1,4 @@ # edit "deprecated-config.requirements.in" and generate with: hacking/update-sanity-requirements.py --test deprecated-config Jinja2==3.1.6 -MarkupSafe==3.0.2 -PyYAML==6.0.2 +MarkupSafe==3.0.3 +PyYAML==6.0.3 diff --git a/test/sanity/code-smell/mypy.requirements.txt b/test/sanity/code-smell/mypy.requirements.txt index 2027c540738..95c4f8454f3 100644 --- a/test/sanity/code-smell/mypy.requirements.txt +++ b/test/sanity/code-smell/mypy.requirements.txt @@ -1,24 +1,24 @@ # edit "mypy.requirements.in" and generate with: hacking/update-sanity-requirements.py --test mypy cffi==2.0.0 -cryptography==45.0.7 +cryptography==46.0.2 iniconfig==2.1.0 Jinja2==3.1.6 -MarkupSafe==3.0.2 -mypy==1.17.1 +MarkupSafe==3.0.3 +mypy==1.18.2 mypy_extensions==1.1.0 packaging==25.0 pathspec==0.12.1 pluggy==1.6.0 -pycparser==2.22 +pycparser==2.23 Pygments==2.19.2 pytest==8.4.2 -pytest-mock==3.15.0 -resolvelib==1.2.0 -tomli==2.2.1 +pytest-mock==3.15.1 +resolvelib==1.2.1 +tomli==2.3.0 types-backports==0.1.3 types-paramiko==4.0.0.20250822 -types-PyYAML==6.0.12.20250822 -types-requests==2.32.4.20250809 +types-PyYAML==6.0.12.20250915 +types-requests==2.32.4.20250913 types-setuptools==80.9.0.20250822 types-toml==0.10.8.20240310 typing_extensions==4.15.0 diff --git a/test/sanity/code-smell/mypy/ansible-test.ini b/test/sanity/code-smell/mypy/ansible-test.ini index 2fd72dd9a95..8b3b108d32a 100644 --- a/test/sanity/code-smell/mypy/ansible-test.ini +++ b/test/sanity/code-smell/mypy/ansible-test.ini @@ -36,10 +36,19 @@ ignore_missing_imports = True [mypy-astroid] ignore_missing_imports = True +[mypy-astroid.bases] +ignore_missing_imports = True + +[mypy-astroid.exceptions] +ignore_missing_imports = True + +[mypy-astroid.nodes] +ignore_missing_imports = True + [mypy-astroid.typing] ignore_missing_imports = True -[mypy-astroid.context] +[mypy-astroid.util] ignore_missing_imports = True [mypy-pylint] diff --git a/test/sanity/code-smell/pymarkdown.requirements.txt b/test/sanity/code-smell/pymarkdown.requirements.txt index 065677faba9..f627770ae0b 100644 --- a/test/sanity/code-smell/pymarkdown.requirements.txt +++ b/test/sanity/code-smell/pymarkdown.requirements.txt @@ -1,10 +1,10 @@ # edit "pymarkdown.requirements.in" and generate with: hacking/update-sanity-requirements.py --test pymarkdown application_properties==0.9.0 Columnar==1.4.1 -pyjson5==1.6.9 +pyjson5==2.0.0 pymarkdownlnt==0.9.32 -PyYAML==6.0.2 -tomli==2.2.1 +PyYAML==6.0.3 +tomli==2.3.0 toolz==1.0.0 typing_extensions==4.15.0 -wcwidth==0.2.13 +wcwidth==0.2.14 diff --git a/test/sanity/ignore.txt b/test/sanity/ignore.txt index 2cff1a050b4..65d370587e7 100644 --- a/test/sanity/ignore.txt +++ b/test/sanity/ignore.txt @@ -65,7 +65,6 @@ test/integration/targets/ansible-doc/library/facts_one shebang test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/tests/integration/targets/hello/files/bad.py pylint:ansible-bad-function # ignore, required for testing test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/tests/integration/targets/hello/files/bad.py pylint:ansible-bad-import-from # ignore, required for testing test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/tests/integration/targets/hello/files/bad.py pylint:ansible-bad-import # ignore, required for testing -test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/plugin_utils/check_pylint.py pylint:disallowed-name # ignore, required for testing test/integration/targets/ansible-test-integration/ansible_collections/ns/col/plugins/modules/hello.py pylint:relative-beyond-top-level test/integration/targets/ansible-test-units/ansible_collections/ns/col/plugins/modules/hello.py pylint:relative-beyond-top-level test/integration/targets/ansible-test-units/ansible_collections/ns/col/tests/unit/plugins/modules/test_hello.py pylint:relative-beyond-top-level @@ -115,6 +114,11 @@ test/integration/targets/win_script/files/test_script_with_args.ps1 pslint:PSAvo test/integration/targets/win_script/files/test_script_with_splatting.ps1 pslint:PSAvoidUsingWriteHost # Keep test/integration/targets/ssh_agent/fake_agents/ssh-agent-bad-shebang shebang # required for test test/lib/ansible_test/_data/requirements/sanity.pslint.ps1 pslint:PSCustomUseLiteralPath # Uses wildcards on purpose +test/lib/ansible_test/_internal/compat/packaging.py pylint:invalid-name # pylint bug: https://github.com/pylint-dev/pylint/issues/10652 +test/lib/ansible_test/_internal/compat/yaml.py pylint:invalid-name # pylint bug: https://github.com/pylint-dev/pylint/issues/10652 +test/lib/ansible_test/_internal/init.py pylint:invalid-name # pylint bug: https://github.com/pylint-dev/pylint/issues/10652 +test/lib/ansible_test/_internal/util.py pylint:invalid-name # pylint bug: https://github.com/pylint-dev/pylint/issues/10652 +test/lib/ansible_test/_util/target/setup/requirements.py pylint:invalid-name # pylint bug: https://github.com/pylint-dev/pylint/issues/10652 test/support/windows-integration/collections/ansible_collections/ansible/windows/plugins/module_utils/WebRequest.psm1 pslint!skip test/support/windows-integration/collections/ansible_collections/ansible/windows/plugins/modules/win_uri.ps1 pslint!skip test/support/windows-integration/plugins/modules/async_status.ps1 pslint!skip