Add collection config support to ansible-test.

This commit is contained in:
Matt Clay 2021-04-22 11:55:16 -07:00
parent bacede7a2b
commit c4e76a7f80
13 changed files with 404 additions and 95 deletions

View File

@ -24,7 +24,7 @@ recursive-include licenses *.txt
recursive-include packaging *
recursive-include test/ansible_test *.py Makefile
recursive-include test/integration *
recursive-include test/lib/ansible_test/config *.template
recursive-include test/lib/ansible_test/config *.yml *.template
recursive-include test/lib/ansible_test/_data *.cfg *.ini *.json *.ps1 *.psd1 *.py *.sh *.txt *.yml coveragerc inventory
recursive-include test/lib/ansible_test/_data/injector ansible ansible-config ansible-connection ansible-console ansible-doc ansible-galaxy ansible-playbook ansible-pull ansible-test ansible-vault pytest
recursive-include test/lib/ansible_test/_data/sanity/validate-modules validate-modules

View File

@ -0,0 +1,9 @@
minor_changes:
- ansible-test - Add support for an ansible-test configuration file in collections under ``tests/config.yml``.
- ansible-test - Collections can limit the Python versions used for testing their remote-only code (modules/module_utils and related tests).
- ansible-test - Collections can declare their remote-only code (modules/module_utils and related tests) as controller-only.
- ansible-test - Sanity test warnings relating to Python version support have been improved.
major_changes:
- ansible-test - The ``import`` and ``compile`` sanity tests limit remote-only Python version checks to remote-only code.
- ansible-test - The ``future-import-boilerplate`` and ``metaclass-boilerplate`` sanity tests are limited to remote-only code.
Additionally, they are skipped for collections which declare no support for Python 2.x.

View File

@ -2,5 +2,6 @@
"extensions": [
".py"
],
"py2_compat": true,
"output": "path-message"
}

View File

@ -2,5 +2,6 @@
"extensions": [
".py"
],
"py2_compat": true,
"output": "path-message"
}

View File

@ -0,0 +1,12 @@
"""Packaging compatibility."""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
try:
from packaging.specifiers import SpecifierSet
from packaging.version import Version
PACKAGING_IMPORT_ERROR = None
except ImportError as ex:
SpecifierSet = None
Version = None
PACKAGING_IMPORT_ERROR = ex

View File

@ -0,0 +1,21 @@
"""PyYAML compatibility."""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from functools import (
partial,
)
try:
import yaml as _yaml
YAML_IMPORT_ERROR = None
except ImportError as ex:
yaml_load = None # pylint: disable=invalid-name
YAML_IMPORT_ERROR = ex
else:
try:
_SafeLoader = _yaml.CSafeLoader
except AttributeError:
_SafeLoader = _yaml.SafeLoader
yaml_load = partial(_yaml.load, Loader=_SafeLoader)

View File

@ -0,0 +1,155 @@
"""Content configuration."""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import os
from . import types as t
from .compat.packaging import (
PACKAGING_IMPORT_ERROR,
SpecifierSet,
Version,
)
from .compat.yaml import (
YAML_IMPORT_ERROR,
yaml_load,
)
from .io import (
read_text_file,
)
from .util import (
ApplicationError,
CONTROLLER_PYTHON_VERSIONS,
SUPPORTED_PYTHON_VERSIONS,
display,
str_to_version,
)
from .data import (
data_context,
)
MISSING = object()
class BaseConfig:
"""Base class for content configuration."""
def __init__(self, data): # type: (t.Any) -> None
if not isinstance(data, dict):
raise Exception('config must be type `dict` not `%s`' % type(data))
class ModulesConfig(BaseConfig):
"""Configuration for modules."""
def __init__(self, data): # type: (t.Any) -> None
super(ModulesConfig, self).__init__(data)
python_requires = data.get('python_requires', MISSING)
if python_requires == MISSING:
raise KeyError('python_requires is required')
self.python_requires = python_requires
self.python_versions = parse_python_requires(python_requires)
self.controller_only = python_requires == 'controller'
class ContentConfig(BaseConfig):
"""Configuration for all content."""
def __init__(self, data): # type: (t.Any) -> None
super(ContentConfig, self).__init__(data)
# Configuration specific to modules/module_utils.
self.modules = ModulesConfig(data.get('modules', {}))
# Python versions supported by the controller, combined with Python versions supported by modules/module_utils.
# Mainly used for display purposes and to limit the Python versions used for sanity tests.
self.python_versions = [version for version in SUPPORTED_PYTHON_VERSIONS
if version in CONTROLLER_PYTHON_VERSIONS or version in self.modules.python_versions]
# True if Python 2.x is supported.
self.py2_support = any(version for version in self.python_versions if str_to_version(version)[0] == 2)
def load_config(path): # type: (str) -> t.Optional[ContentConfig]
"""Load and parse the specified config file and return the result or None if loading/parsing failed."""
if YAML_IMPORT_ERROR:
raise ApplicationError('The "PyYAML" module is required to parse config: %s' % YAML_IMPORT_ERROR)
if PACKAGING_IMPORT_ERROR:
raise ApplicationError('The "packaging" module is required to parse config: %s' % PACKAGING_IMPORT_ERROR)
value = read_text_file(path)
try:
yaml_value = yaml_load(value)
except Exception as ex: # pylint: disable=broad-except
display.warning('Ignoring config "%s" due to a YAML parsing error: %s' % (path, ex))
return None
try:
config = ContentConfig(yaml_value)
except Exception as ex: # pylint: disable=broad-except
display.warning('Ignoring config "%s" due a config parsing error: %s' % (path, ex))
return None
display.info('Loaded configuration: %s' % path, verbosity=1)
return config
def get_content_config(): # type: () -> ContentConfig
"""
Parse and return the content configuration (if any) for the current collection.
For ansible-core, a default configuration is used.
Results are cached.
"""
try:
return get_content_config.config
except AttributeError:
pass
collection_config_path = 'tests/config.yml'
config = None
if data_context().content.collection and os.path.exists(collection_config_path):
config = load_config(collection_config_path)
if not config:
config = ContentConfig(dict(
modules=dict(
python_requires='default',
),
))
get_content_config.config = config
if not config.modules.python_versions:
raise ApplicationError('This collection does not declare support for modules/module_utils on any known Python version.\n'
'Ansible supports modules/module_utils on Python versions: %s\n'
'This collection provides the Python requirement: %s' % (
', '.join(SUPPORTED_PYTHON_VERSIONS), config.modules.python_requires))
return config
def parse_python_requires(value): # type: (t.Any) -> t.List[str]
"""Parse the given 'python_requires' version specifier and return the matching Python versions."""
if not isinstance(value, str):
raise ValueError('python_requires must must be of type `str` not type `%s`' % type(value))
if value == 'default':
versions = list(SUPPORTED_PYTHON_VERSIONS)
elif value == 'controller':
versions = list(CONTROLLER_PYTHON_VERSIONS)
else:
specifier_set = SpecifierSet(value)
versions = [version for version in SUPPORTED_PYTHON_VERSIONS if specifier_set.contains(Version(version))]
return versions

View File

@ -33,6 +33,7 @@ from ..util import (
str_to_version,
SUPPORTED_PYTHON_VERSIONS,
CONTROLLER_PYTHON_VERSIONS,
REMOTE_ONLY_PYTHON_VERSIONS,
)
from ..util_common import (
@ -74,6 +75,10 @@ from ..data import (
data_context,
)
from ..content_config import (
get_content_config,
)
COMMAND = 'sanity'
SANITY_ROOT = os.path.join(ANSIBLE_TEST_DATA_ROOT, 'sanity')
@ -142,18 +147,21 @@ def command_sanity(args):
options = ''
if test.supported_python_versions and version not in test.supported_python_versions:
display.warning("Skipping sanity test '%s' on Python %s. Supported Python versions: %s" % (
test.name, version, ', '.join(test.supported_python_versions)))
result = SanitySkipped(test.name, skip_version)
elif not args.python and version not in available_versions:
display.warning("Skipping sanity test '%s' on Python %s due to missing interpreter." % (test.name, version))
# There are two ways this situation can occur:
#
# - A specific Python version was requested with the `--python` option and that version is not supported by the test.
# This means that the test supports only a subset of the controller supported Python versions, and not the one given by the `--python` option.
# Or that a remote-only Python version was specified for a Python based sanity test that is not multi-version.
#
# - No specific Python version was requested and no supported version was found on the system.
# This means that the test supports only a subset of the controller supported Python versions, and not the one used to run ansible-test.
# Or that the Python version used to run ansible-test is not supported by the controller, a condition which will soon not be possible.
#
# Neither of these are affected by the Python versions supported by a collection.
result = SanitySkipped(test.name, skip_version)
result.reason = "Skipping sanity test '%s' on Python %s. Supported Python versions: %s" % (
test.name, version, ', '.join(test.supported_python_versions))
else:
if test.supported_python_versions:
display.info("Running sanity test '%s' with Python %s" % (test.name, version))
else:
display.info("Running sanity test '%s'" % test.name)
if isinstance(test, SanityCodeSmellTest):
settings = test.load_processor(args)
elif isinstance(test, SanityMultipleVersion):
@ -177,11 +185,27 @@ def command_sanity(args):
all_targets = SanityTargets.filter_and_inject_targets(test, all_targets)
usable_targets = SanityTargets.filter_and_inject_targets(test, usable_targets)
usable_targets = sorted(test.filter_targets(list(usable_targets)))
usable_targets = sorted(test.filter_targets_by_version(list(usable_targets), version))
usable_targets = settings.filter_skipped_targets(usable_targets)
sanity_targets = SanityTargets(tuple(all_targets), tuple(usable_targets))
if usable_targets or test.no_targets:
test_needed = bool(usable_targets or test.no_targets)
result = None
if test_needed and not args.python and version not in available_versions:
# Deferred checking of Python availability. Done here since it is now known to be required for running the test.
# Earlier checking could cause a spurious warning to be generated for a collection which does not support the Python version.
# If the `--python` option was used, this warning will be skipped and an error will be reported when running the test instead.
result = SanitySkipped(test.name, skip_version)
result.reason = "Skipping sanity test '%s' on Python %s due to missing interpreter." % (test.name, version)
if not result:
if test.supported_python_versions:
display.info("Running sanity test '%s' with Python %s" % (test.name, version))
else:
display.info("Running sanity test '%s'" % test.name)
if test_needed and not result:
install_command_requirements(args, version, context=test.name, enable_pyyaml_check=True)
if isinstance(test, SanityCodeSmellTest):
@ -195,6 +219,8 @@ def command_sanity(args):
result = test.test(args, sanity_targets)
else:
raise Exception('Unsupported test type: %s' % type(test))
elif result:
pass
else:
result = SanitySkipped(test.name, skip_version)
@ -274,13 +300,18 @@ class SanityIgnoreParser:
for test in sanity_get_tests():
test_targets = SanityTargets.filter_and_inject_targets(test, targets)
paths_by_test[test.name] = set(target.path for target in test.filter_targets(test_targets))
if isinstance(test, SanityMultipleVersion):
versioned_test_names.add(test.name)
tests_by_name.update(dict(('%s-%s' % (test.name, python_version), test) for python_version in test.supported_python_versions))
for python_version in test.supported_python_versions:
test_name = '%s-%s' % (test.name, python_version)
paths_by_test[test_name] = set(target.path for target in test.filter_targets_by_version(test_targets, python_version))
tests_by_name[test_name] = test
else:
unversioned_test_names.update(dict(('%s-%s' % (test.name, python_version), test.name) for python_version in SUPPORTED_PYTHON_VERSIONS))
paths_by_test[test.name] = set(target.path for target in test.filter_targets_by_version(test_targets, ''))
tests_by_name[test.name] = test
for line_no, line in enumerate(lines, start=1):
@ -347,7 +378,7 @@ class SanityIgnoreParser:
self.parse_errors.append((line_no, 1, "Sanity test '%s' does not support directory paths" % test_name))
continue
if path not in paths_by_test[test.name] and not test.no_targets:
if path not in paths_by_test[test_name] and not test.no_targets:
self.parse_errors.append((line_no, 1, "Sanity test '%s' does not test path '%s'" % (test_name, path)))
continue
@ -657,6 +688,11 @@ class SanityTest(ABC):
"""True if the test targets should include symlinks."""
return False
@property
def py2_compat(self): # type: () -> bool
"""True if the test only applies to code that runs on Python 2.x."""
return False
@property
def supported_python_versions(self): # type: () -> t.Optional[t.Tuple[str, ...]]
"""A tuple of supported Python versions or None if the test does not depend on specific Python versions."""
@ -669,6 +705,47 @@ class SanityTest(ABC):
raise NotImplementedError('Sanity test "%s" must implement "filter_targets" or set "no_targets" to True.' % self.name)
def filter_targets_by_version(self, targets, python_version): # type: (t.List[TestTarget], str) -> t.List[TestTarget]
"""Return the given list of test targets, filtered to include only those relevant for the test, taking into account the Python version."""
del python_version # python_version is not used here, but derived classes may make use of it
targets = self.filter_targets(targets)
if self.py2_compat:
# This sanity test is a Python 2.x compatibility test.
content_config = get_content_config()
if content_config.py2_support:
# This collection supports Python 2.x.
# Filter targets to include only those that require support for remote-only Python versions.
targets = self.filter_remote_targets(targets)
else:
# This collection does not support Python 2.x.
# There are no targets to test.
targets = []
return targets
def filter_remote_targets(self, targets): # type: (t.List[TestTarget]) -> t.List[TestTarget]
"""Return a filtered list of the given targets, including only those that require support for remote-only Python versions."""
targets = [target for target in targets if (
is_subdir(target.path, data_context().content.module_path) or
is_subdir(target.path, data_context().content.module_utils_path) or
is_subdir(target.path, data_context().content.unit_module_path) or
is_subdir(target.path, data_context().content.unit_module_utils_path) or
# include modules/module_utils within integration test library directories
re.search('^%s/.*/library/' % re.escape(data_context().content.integration_targets_path), target.path) or
# special handling for content in ansible-core
(data_context().content.is_ansible and (
# temporary solution until ansible-test code is reorganized when the split controller/remote implementation is complete
is_subdir(target.path, 'test/lib/ansible_test/') or
# integration test support modules/module_utils continue to require support for remote-only Python versions
re.search('^test/support/integration/.*/(modules|module_utils)/', target.path)
))
)]
return targets
class SanityCodeSmellTest(SanityTest):
"""Sanity test script."""
@ -701,6 +778,7 @@ class SanityCodeSmellTest(SanityTest):
self.__no_targets = self.config.get('no_targets') # type: bool
self.__include_directories = self.config.get('include_directories') # type: bool
self.__include_symlinks = self.config.get('include_symlinks') # type: bool
self.__py2_compat = self.config.get('py2_compat', False) # type: bool
else:
self.output = None
self.extensions = []
@ -715,6 +793,7 @@ class SanityCodeSmellTest(SanityTest):
self.__no_targets = True
self.__include_directories = False
self.__include_symlinks = False
self.__py2_compat = False
if self.no_targets:
mutually_exclusive = (
@ -753,6 +832,11 @@ class SanityCodeSmellTest(SanityTest):
"""True if the test targets should include symlinks."""
return self.__include_symlinks
@property
def py2_compat(self): # type: () -> bool
"""True if the test only applies to code that runs on Python 2.x."""
return self.__py2_compat
@property
def supported_python_versions(self): # type: () -> t.Optional[t.Tuple[str, ...]]
"""A tuple of supported Python versions or None if the test does not depend on specific Python versions."""
@ -937,6 +1021,25 @@ class SanityMultipleVersion(SanityFunc):
"""A tuple of supported Python versions or None if the test does not depend on specific Python versions."""
return SUPPORTED_PYTHON_VERSIONS
def filter_targets_by_version(self, targets, python_version): # type: (t.List[TestTarget], str) -> t.List[TestTarget]
"""Return the given list of test targets, filtered to include only those relevant for the test, taking into account the Python version."""
if not python_version:
raise Exception('python_version is required to filter multi-version tests')
targets = super(SanityMultipleVersion, self).filter_targets_by_version(targets, python_version)
if python_version in REMOTE_ONLY_PYTHON_VERSIONS:
content_config = get_content_config()
if python_version not in content_config.modules.python_versions:
# when a remote-only python version is not supported there are no paths to test
return []
# when a remote-only python version is supported, tests must be applied only to targets that support remote-only Python versions
targets = self.filter_remote_targets(targets)
return targets
SANITY_TESTS = (
)

View File

@ -228,16 +228,29 @@ class TestSuccess(TestResult):
class TestSkipped(TestResult):
"""Test skipped."""
def __init__(self, command, test, python_version=None):
"""
:type command: str
:type test: str
:type python_version: str
"""
super(TestSkipped, self).__init__(command, test, python_version)
self.reason = None # type: t.Optional[str]
def write_console(self):
"""Write results to console."""
display.info('No tests applicable.', verbosity=1)
if self.reason:
display.warning(self.reason)
else:
display.info('No tests applicable.', verbosity=1)
def write_junit(self, args):
"""
:type args: TestConfig
"""
test_case = self.junit.TestCase(classname=self.command, name=self.name)
test_case.add_skipped_info('No tests applicable.')
test_case.add_skipped_info(self.reason or 'No tests applicable.')
self.save_junit(args, test_case)

View File

@ -60,6 +60,10 @@ from ..executor import (
install_command_requirements,
)
from ..content_config import (
get_content_config,
)
class TestContext:
"""Contexts that unit tests run in based on the type of content."""
@ -80,8 +84,18 @@ def command_units(args):
paths = [target.path for target in include]
module_paths = [path for path in paths if is_subdir(path, data_context().content.unit_module_path)]
module_utils_paths = [path for path in paths if is_subdir(path, data_context().content.unit_module_utils_path)]
content_config = get_content_config()
supported_remote_python_versions = content_config.modules.python_versions
if content_config.modules.controller_only:
# controller-only collections run modules/module_utils unit tests as controller-only tests
module_paths = []
module_utils_paths = []
else:
# normal collections run modules/module_utils unit tests isolated from controller code due to differences in python version requirements
module_paths = [path for path in paths if is_subdir(path, data_context().content.unit_module_path)]
module_utils_paths = [path for path in paths if is_subdir(path, data_context().content.unit_module_utils_path)]
controller_paths = sorted(path for path in set(paths) - set(module_paths) - set(module_utils_paths))
remote_paths = module_paths or module_utils_paths
@ -96,10 +110,20 @@ def command_units(args):
raise AllTargetsSkipped()
if args.python and args.python in REMOTE_ONLY_PYTHON_VERSIONS:
if args.python not in supported_remote_python_versions:
display.warning('Python %s is not supported by this collection. Supported Python versions are: %s' % (
args.python, ', '.join(content_config.python_versions)))
raise AllTargetsSkipped()
if not remote_paths:
display.warning('Python %s is only supported by module and module_utils unit tests, but none were selected.' % args.python)
raise AllTargetsSkipped()
if args.python and args.python not in supported_remote_python_versions and not controller_paths:
display.warning('Python %s is not supported by this collection for modules/module_utils. Supported Python versions are: %s' % (
args.python, ', '.join(supported_remote_python_versions)))
raise AllTargetsSkipped()
if args.delegate:
raise Delegate(require=changes, exclude=args.exclude)
@ -118,6 +142,9 @@ def command_units(args):
if test_context == TestContext.controller:
if version not in CONTROLLER_PYTHON_VERSIONS:
continue
else:
if version not in supported_remote_python_versions:
continue
if not paths:
continue

View File

@ -0,0 +1,41 @@
# Sample ansible-test configuration file for collections.
# Support for this feature was first added in ansible-core 2.12.
# Use of this file is optional.
# If used, this file must be placed in "tests/config.yml" relative to the base of the collection.
modules:
# Configuration for modules/module_utils.
# These settings do not apply to other content in the collection.
python_requires: default
# Python versions supported by modules/module_utils.
# This setting is required.
#
# Possible values:
#
# - 'default' - All Python versions supported by Ansible.
# This is the default value if no configuration is provided.
# - 'controller' - All Python versions supported by the Ansible controller.
# This indicates the modules/module_utils can only run on the controller.
# Intended for use only with modules/module_utils that depend on ansible-connection, which only runs on the controller.
# Unit tests for modules/module_utils will be permitted to import any Ansible code, instead of only module_utils.
# - SpecifierSet - A PEP 440 specifier set indicating the supported Python versions.
# This is only needed when modules/module_utils do not support all Python versions supported by Ansible.
# It is not necessary to exclude versions which Ansible does not support, as this will be done automatically.
#
# What does this affect?
#
# - Unit tests will be skipped on any unsupported Python version.
# - Sanity tests that are Python version specific will be skipped on any unsupported Python version that is not supported by the controller.
#
# Sanity tests that are Python version specific will always be executed for Python versions supported by the controller, regardless of this setting.
# Reasons for this restriction include, but are not limited to:
#
# - AnsiballZ must be able to AST parse modules/module_utils on the controller, even though they may execute on a managed node.
# - ansible-doc must be able to AST parse modules/module_utils on the controller to display documentation.
# - ansible-test must be able to AST parse modules/module_utils to perform static analysis on them.
# - ansible-test must be able to execute portions of modules/module_utils to validate their argument specs.
#
# These settings only apply to modules/module_utils.
# It is not possible to declare supported Python versions for controller-only code.
# All Python versions supported by the controller must be supported by controller-only code.

View File

@ -6,32 +6,7 @@ examples/scripts/my_test_facts.py shebang # example module but not in a normal m
examples/scripts/my_test_info.py shebang # example module but not in a normal module location
examples/scripts/upgrade_to_ps3.ps1 pslint:PSCustomUseLiteralPath
examples/scripts/upgrade_to_ps3.ps1 pslint:PSUseApprovedVerbs
hacking/build_library/build_ansible/announce.py compile-2.6!skip # release process only, 3.6+ required
hacking/build_library/build_ansible/announce.py compile-2.7!skip # release process only, 3.6+ required
hacking/build_library/build_ansible/announce.py compile-3.5!skip # release process only, 3.6+ required
hacking/build_library/build_ansible/command_plugins/dump_config.py compile-2.6!skip # docs build only, 3.6+ required
hacking/build_library/build_ansible/command_plugins/dump_config.py compile-2.7!skip # docs build only, 3.6+ required
hacking/build_library/build_ansible/command_plugins/dump_config.py compile-3.5!skip # docs build only, 3.6+ required
hacking/build_library/build_ansible/command_plugins/dump_keywords.py compile-2.6!skip # docs build only, 3.6+ required
hacking/build_library/build_ansible/command_plugins/dump_keywords.py compile-2.7!skip # docs build only, 3.6+ required
hacking/build_library/build_ansible/command_plugins/dump_keywords.py compile-3.5!skip # docs build only, 3.6+ required
hacking/build_library/build_ansible/command_plugins/generate_man.py compile-2.6!skip # docs build only, 3.6+ required
hacking/build_library/build_ansible/command_plugins/generate_man.py compile-2.7!skip # docs build only, 3.6+ required
hacking/build_library/build_ansible/command_plugins/generate_man.py compile-3.5!skip # docs build only, 3.6+ required
hacking/build_library/build_ansible/command_plugins/porting_guide.py compile-2.6!skip # release process only, 3.6+ required
hacking/build_library/build_ansible/command_plugins/porting_guide.py compile-2.7!skip # release process only, 3.6+ required
hacking/build_library/build_ansible/command_plugins/porting_guide.py compile-3.5!skip # release process only, 3.6+ required
hacking/build_library/build_ansible/command_plugins/release_announcement.py compile-2.6!skip # release process only, 3.6+ required
hacking/build_library/build_ansible/command_plugins/release_announcement.py compile-2.7!skip # release process only, 3.6+ required
hacking/build_library/build_ansible/command_plugins/release_announcement.py compile-3.5!skip # release process only, 3.6+ required
hacking/build_library/build_ansible/command_plugins/update_intersphinx.py compile-2.6!skip # release process and docs build only, 3.6+ required
hacking/build_library/build_ansible/command_plugins/update_intersphinx.py compile-2.7!skip # release process and docs build only, 3.6+ required
hacking/build_library/build_ansible/command_plugins/update_intersphinx.py compile-3.5!skip # release process and docs build only, 3.6+ required
hacking/build_library/build_ansible/commands.py compile-2.6!skip # release and docs process only, 3.6+ required
hacking/build_library/build_ansible/commands.py compile-2.7!skip # release and docs process only, 3.6+ required
hacking/build_library/build_ansible/commands.py compile-3.5!skip # release and docs process only, 3.6+ required
lib/ansible/cli/console.py pylint:blacklisted-name
lib/ansible/cli/galaxy.py compile-2.6!skip # 'ansible-galaxy collection' requires 2.7+
lib/ansible/cli/scripts/ansible_cli_stub.py pylint:ansible-deprecated-version
lib/ansible/cli/scripts/ansible_cli_stub.py shebang
lib/ansible/cli/scripts/ansible_connection_cli_stub.py shebang
@ -41,10 +16,6 @@ lib/ansible/executor/powershell/async_watchdog.ps1 pslint:PSCustomUseLiteralPath
lib/ansible/executor/powershell/async_wrapper.ps1 pslint:PSCustomUseLiteralPath
lib/ansible/executor/powershell/exec_wrapper.ps1 pslint:PSCustomUseLiteralPath
lib/ansible/executor/task_queue_manager.py pylint:blacklisted-name
lib/ansible/galaxy/collection/__init__.py compile-2.6!skip # 'ansible-galaxy collection' requires 2.7+
lib/ansible/galaxy/collection/galaxy_api_proxy.py compile-2.6!skip # 'ansible-galaxy collection' requires 2.7+
lib/ansible/galaxy/dependency_resolution/dataclasses.py compile-2.6!skip # 'ansible-galaxy collection' requires 2.7+
lib/ansible/galaxy/dependency_resolution/providers.py compile-2.6!skip # 'ansible-galaxy collection' requires 2.7+
lib/ansible/keyword_desc.yml no-unwanted-files
lib/ansible/module_utils/compat/_selectors2.py future-import-boilerplate # ignore bundled
lib/ansible/module_utils/compat/_selectors2.py metaclass-boilerplate # ignore bundled
@ -165,7 +136,6 @@ test/integration/targets/ansible-test/ansible_collections/ns/col/tests/integrati
test/integration/targets/ansible-test/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/ansible_collections/ns/col/tests/unit/plugins/module_utils/test_my_util.py pylint:relative-beyond-top-level
test/integration/targets/ansible-test/ansible_collections/ns/col/tests/unit/plugins/modules/test_hello.py pylint:relative-beyond-top-level
test/integration/targets/collections_plugin_namespace/collection_root/ansible_collections/my_ns/my_col/plugins/lookup/lookup_no_future_boilerplate.py future-import-boilerplate # testing Python 2.x implicit relative imports
test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/module_utils/my_util2.py pylint:relative-beyond-top-level
test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/module_utils/my_util3.py pylint:relative-beyond-top-level
test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/modules/my_module.py pylint:relative-beyond-top-level
@ -230,55 +200,13 @@ test/support/integration/plugins/module_utils/postgres.py future-import-boilerpl
test/support/integration/plugins/module_utils/postgres.py metaclass-boilerplate
test/support/integration/plugins/modules/lvg.py pylint:blacklisted-name
test/support/integration/plugins/modules/timezone.py pylint:blacklisted-name
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/doc_fragments/netconf.py future-import-boilerplate
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/doc_fragments/netconf.py metaclass-boilerplate
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/doc_fragments/network_agnostic.py future-import-boilerplate
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/doc_fragments/network_agnostic.py metaclass-boilerplate
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/compat/ipaddress.py future-import-boilerplate
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/compat/ipaddress.py metaclass-boilerplate
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/compat/ipaddress.py no-unicode-literals
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/compat/ipaddress.py pep8:E203
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/cfg/base.py future-import-boilerplate
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/cfg/base.py metaclass-boilerplate
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/config.py future-import-boilerplate
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/config.py metaclass-boilerplate
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/facts/facts.py future-import-boilerplate
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/facts/facts.py metaclass-boilerplate
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/facts/facts.py pylint:unnecessary-comprehension
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/netconf.py future-import-boilerplate
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/netconf.py metaclass-boilerplate
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/network.py future-import-boilerplate
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/network.py metaclass-boilerplate
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/parsing.py future-import-boilerplate
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/parsing.py metaclass-boilerplate
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/utils.py future-import-boilerplate
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/utils.py metaclass-boilerplate
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/netconf/netconf.py future-import-boilerplate
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/netconf/netconf.py metaclass-boilerplate
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/restconf/restconf.py future-import-boilerplate
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/restconf/restconf.py metaclass-boilerplate
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/netconf/default.py pylint:unnecessary-comprehension
test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/doc_fragments/ios.py future-import-boilerplate
test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/doc_fragments/ios.py metaclass-boilerplate
test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/module_utils/network/ios/ios.py future-import-boilerplate
test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/module_utils/network/ios/ios.py metaclass-boilerplate
test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/modules/ios_command.py future-import-boilerplate
test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/modules/ios_command.py metaclass-boilerplate
test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/modules/ios_config.py future-import-boilerplate
test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/modules/ios_config.py metaclass-boilerplate
test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/modules/ios_config.py pep8:E501
test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/doc_fragments/vyos.py future-import-boilerplate
test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/doc_fragments/vyos.py metaclass-boilerplate
test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/module_utils/network/vyos/vyos.py future-import-boilerplate
test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/module_utils/network/vyos/vyos.py metaclass-boilerplate
test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/modules/vyos_command.py future-import-boilerplate
test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/modules/vyos_command.py metaclass-boilerplate
test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/modules/vyos_command.py pep8:E231
test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/modules/vyos_command.py pylint:blacklisted-name
test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/modules/vyos_config.py future-import-boilerplate
test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/modules/vyos_config.py metaclass-boilerplate
test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/modules/vyos_facts.py future-import-boilerplate
test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/modules/vyos_facts.py metaclass-boilerplate
test/support/windows-integration/plugins/modules/async_status.ps1 pslint!skip
test/support/windows-integration/plugins/modules/setup.ps1 pslint!skip
test/support/windows-integration/plugins/modules/win_copy.ps1 pslint!skip
@ -303,8 +231,6 @@ test/units/playbook/role/test_role.py pylint:blacklisted-name
test/units/plugins/test_plugins.py pylint:blacklisted-name
test/units/template/test_templar.py pylint:blacklisted-name
test/units/utils/collection_loader/fixtures/collections/ansible_collections/testns/testcoll/plugins/action/my_action.py pylint:relative-beyond-top-level
test/units/utils/collection_loader/fixtures/collections/ansible_collections/testns/testcoll/plugins/module_utils/my_util.py future-import-boilerplate # test expects no boilerplate
test/units/utils/collection_loader/fixtures/collections/ansible_collections/testns/testcoll/plugins/module_utils/my_util.py metaclass-boilerplate # test expects no boilerplate
test/units/utils/collection_loader/fixtures/collections/ansible_collections/testns/testcoll/plugins/modules/__init__.py empty-init # testing that collections don't need inits
test/units/utils/collection_loader/fixtures/collections_masked/ansible_collections/__init__.py empty-init # testing that collections don't need inits
test/units/utils/collection_loader/fixtures/collections_masked/ansible_collections/ansible/__init__.py empty-init # testing that collections don't need inits