summaryrefslogtreecommitdiffstats
path: root/test/lib/ansible_test/_internal/cli.py
diff options
context:
space:
mode:
Diffstat (limited to 'test/lib/ansible_test/_internal/cli.py')
-rw-r--r--test/lib/ansible_test/_internal/cli.py1217
1 files changed, 1217 insertions, 0 deletions
diff --git a/test/lib/ansible_test/_internal/cli.py b/test/lib/ansible_test/_internal/cli.py
new file mode 100644
index 00000000..e406b2dd
--- /dev/null
+++ b/test/lib/ansible_test/_internal/cli.py
@@ -0,0 +1,1217 @@
+"""Test runner for all Ansible tests."""
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import errno
+import os
+import sys
+
+# This import should occur as early as possible.
+# It must occur before subprocess has been imported anywhere in the current process.
+from .init import (
+ CURRENT_RLIMIT_NOFILE,
+)
+
+from . import types as t
+
+from .util import (
+ ApplicationError,
+ display,
+ raw_command,
+ generate_pip_command,
+ read_lines_without_comments,
+ MAXFD,
+ ANSIBLE_TEST_DATA_ROOT,
+)
+
+from .delegation import (
+ check_delegation_args,
+ delegate,
+)
+
+from .executor import (
+ command_posix_integration,
+ command_network_integration,
+ command_windows_integration,
+ command_shell,
+ SUPPORTED_PYTHON_VERSIONS,
+ ApplicationWarning,
+ Delegate,
+ generate_pip_install,
+ check_startup,
+)
+
+from .config import (
+ PosixIntegrationConfig,
+ WindowsIntegrationConfig,
+ NetworkIntegrationConfig,
+ SanityConfig,
+ UnitsConfig,
+ ShellConfig,
+)
+
+from .env import (
+ EnvConfig,
+ command_env,
+ configure_timeout,
+)
+
+from .sanity import (
+ command_sanity,
+ sanity_init,
+ sanity_get_tests,
+)
+
+from .units import (
+ command_units,
+)
+
+from .target import (
+ find_target_completion,
+ walk_posix_integration_targets,
+ walk_network_integration_targets,
+ walk_windows_integration_targets,
+ walk_units_targets,
+ walk_sanity_targets,
+)
+
+from .core_ci import (
+ AWS_ENDPOINTS,
+)
+
+from .cloud import (
+ initialize_cloud_plugins,
+)
+
+from .data import (
+ data_context,
+)
+
+from .util_common import (
+ get_docker_completion,
+ get_network_completion,
+ get_remote_completion,
+ CommonConfig,
+)
+
+from .coverage.combine import (
+ command_coverage_combine,
+)
+
+from .coverage.erase import (
+ command_coverage_erase,
+)
+
+from .coverage.html import (
+ command_coverage_html,
+)
+
+from .coverage.report import (
+ command_coverage_report,
+ CoverageReportConfig,
+)
+
+from .coverage.xml import (
+ command_coverage_xml,
+)
+
+from .coverage.analyze.targets.generate import (
+ command_coverage_analyze_targets_generate,
+ CoverageAnalyzeTargetsGenerateConfig,
+)
+
+from .coverage.analyze.targets.expand import (
+ command_coverage_analyze_targets_expand,
+ CoverageAnalyzeTargetsExpandConfig,
+)
+
+from .coverage.analyze.targets.filter import (
+ command_coverage_analyze_targets_filter,
+ CoverageAnalyzeTargetsFilterConfig,
+)
+
+from .coverage.analyze.targets.combine import (
+ command_coverage_analyze_targets_combine,
+ CoverageAnalyzeTargetsCombineConfig,
+)
+
+from .coverage.analyze.targets.missing import (
+ command_coverage_analyze_targets_missing,
+ CoverageAnalyzeTargetsMissingConfig,
+)
+
+from .coverage import (
+ COVERAGE_GROUPS,
+ CoverageConfig,
+)
+
+if t.TYPE_CHECKING:
+ import argparse as argparse_module
+
+
+def main():
+ """Main program function."""
+ try:
+ os.chdir(data_context().content.root)
+ initialize_cloud_plugins()
+ sanity_init()
+ args = parse_args()
+ config = args.config(args) # type: CommonConfig
+ display.verbosity = config.verbosity
+ display.truncate = config.truncate
+ display.redact = config.redact
+ display.color = config.color
+ display.info_stderr = config.info_stderr
+ check_startup()
+ check_delegation_args(config)
+ configure_timeout(config)
+
+ display.info('RLIMIT_NOFILE: %s' % (CURRENT_RLIMIT_NOFILE,), verbosity=2)
+ display.info('MAXFD: %d' % MAXFD, verbosity=2)
+
+ try:
+ args.func(config)
+ delegate_args = None
+ except Delegate as ex:
+ # save delegation args for use once we exit the exception handler
+ delegate_args = (ex.exclude, ex.require, ex.integration_targets)
+
+ if delegate_args:
+ # noinspection PyTypeChecker
+ delegate(config, *delegate_args)
+
+ display.review_warnings()
+ except ApplicationWarning as ex:
+ display.warning(u'%s' % ex)
+ sys.exit(0)
+ except ApplicationError as ex:
+ display.error(u'%s' % ex)
+ sys.exit(1)
+ except KeyboardInterrupt:
+ sys.exit(2)
+ except IOError as ex:
+ if ex.errno == errno.EPIPE:
+ sys.exit(3)
+ raise
+
+
+def parse_args():
+ """Parse command line arguments."""
+ try:
+ import argparse
+ except ImportError:
+ if '--requirements' not in sys.argv:
+ raise
+ # install argparse without using constraints since pip may be too old to support them
+ # not using the ansible-test requirements file since this install is for sys.executable rather than the delegated python (which may be different)
+ # argparse has no special requirements, so upgrading pip is not required here
+ raw_command(generate_pip_install(generate_pip_command(sys.executable), '', packages=['argparse'], use_constraints=False))
+ import argparse
+
+ try:
+ import argcomplete
+ except ImportError:
+ argcomplete = None
+
+ if argcomplete:
+ epilog = 'Tab completion available using the "argcomplete" python package.'
+ else:
+ epilog = 'Install the "argcomplete" python package to enable tab completion.'
+
+ def key_value_type(value): # type: (str) -> t.Tuple[str, str]
+ """Wrapper around key_value."""
+ return key_value(argparse, value)
+
+ parser = argparse.ArgumentParser(epilog=epilog)
+
+ common = argparse.ArgumentParser(add_help=False)
+
+ common.add_argument('-e', '--explain',
+ action='store_true',
+ help='explain commands that would be executed')
+
+ common.add_argument('-v', '--verbose',
+ dest='verbosity',
+ action='count',
+ default=0,
+ help='display more output')
+
+ common.add_argument('--color',
+ metavar='COLOR',
+ nargs='?',
+ help='generate color output: %(choices)s',
+ choices=('yes', 'no', 'auto'),
+ const='yes',
+ default='auto')
+
+ common.add_argument('--debug',
+ action='store_true',
+ help='run ansible commands in debug mode')
+
+ # noinspection PyTypeChecker
+ common.add_argument('--truncate',
+ dest='truncate',
+ metavar='COLUMNS',
+ type=int,
+ default=display.columns,
+ help='truncate some long output (0=disabled) (default: auto)')
+
+ common.add_argument('--redact',
+ dest='redact',
+ action='store_true',
+ default=True,
+ help='redact sensitive values in output')
+
+ common.add_argument('--no-redact',
+ dest='redact',
+ action='store_false',
+ default=False,
+ help='show sensitive values in output')
+
+ common.add_argument('--check-python',
+ choices=SUPPORTED_PYTHON_VERSIONS,
+ help=argparse.SUPPRESS)
+
+ test = argparse.ArgumentParser(add_help=False, parents=[common])
+
+ test.add_argument('include',
+ metavar='TARGET',
+ nargs='*',
+ help='test the specified target').completer = complete_target
+
+ test.add_argument('--include',
+ metavar='TARGET',
+ action='append',
+ help='include the specified target').completer = complete_target
+
+ test.add_argument('--exclude',
+ metavar='TARGET',
+ action='append',
+ help='exclude the specified target').completer = complete_target
+
+ test.add_argument('--require',
+ metavar='TARGET',
+ action='append',
+ help='require the specified target').completer = complete_target
+
+ test.add_argument('--coverage',
+ action='store_true',
+ help='analyze code coverage when running tests')
+
+ test.add_argument('--coverage-label',
+ default='',
+ help='label to include in coverage output file names')
+
+ test.add_argument('--coverage-check',
+ action='store_true',
+ help='only verify code coverage can be enabled')
+
+ test.add_argument('--metadata',
+ help=argparse.SUPPRESS)
+
+ test.add_argument('--base-branch',
+ help='base branch used for change detection')
+
+ add_changes(test, argparse)
+ add_environments(test)
+
+ integration = argparse.ArgumentParser(add_help=False, parents=[test])
+
+ integration.add_argument('--python',
+ metavar='VERSION',
+ choices=SUPPORTED_PYTHON_VERSIONS + ('default',),
+ help='python version: %s' % ', '.join(SUPPORTED_PYTHON_VERSIONS))
+
+ integration.add_argument('--start-at',
+ metavar='TARGET',
+ help='start at the specified target').completer = complete_target
+
+ integration.add_argument('--start-at-task',
+ metavar='TASK',
+ help='start at the specified task')
+
+ integration.add_argument('--tags',
+ metavar='TAGS',
+ help='only run plays and tasks tagged with these values')
+
+ integration.add_argument('--skip-tags',
+ metavar='TAGS',
+ help='only run plays and tasks whose tags do not match these values')
+
+ integration.add_argument('--diff',
+ action='store_true',
+ help='show diff output')
+
+ integration.add_argument('--allow-destructive',
+ action='store_true',
+ help='allow destructive tests')
+
+ integration.add_argument('--allow-root',
+ action='store_true',
+ help='allow tests requiring root when not root')
+
+ integration.add_argument('--allow-disabled',
+ action='store_true',
+ help='allow tests which have been marked as disabled')
+
+ integration.add_argument('--allow-unstable',
+ action='store_true',
+ help='allow tests which have been marked as unstable')
+
+ integration.add_argument('--allow-unstable-changed',
+ action='store_true',
+ help='allow tests which have been marked as unstable when focused changes are detected')
+
+ integration.add_argument('--allow-unsupported',
+ action='store_true',
+ help='allow tests which have been marked as unsupported')
+
+ integration.add_argument('--retry-on-error',
+ action='store_true',
+ help='retry failed test with increased verbosity')
+
+ integration.add_argument('--continue-on-error',
+ action='store_true',
+ help='continue after failed test')
+
+ integration.add_argument('--debug-strategy',
+ action='store_true',
+ help='run test playbooks using the debug strategy')
+
+ integration.add_argument('--changed-all-target',
+ metavar='TARGET',
+ default='all',
+ help='target to run when all tests are needed')
+
+ integration.add_argument('--changed-all-mode',
+ metavar='MODE',
+ choices=('default', 'include', 'exclude'),
+ help='include/exclude behavior with --changed-all-target: %(choices)s')
+
+ integration.add_argument('--list-targets',
+ action='store_true',
+ help='list matching targets instead of running tests')
+
+ integration.add_argument('--no-temp-workdir',
+ action='store_true',
+ help='do not run tests from a temporary directory (use only for verifying broken tests)')
+
+ integration.add_argument('--no-temp-unicode',
+ action='store_true',
+ help='avoid unicode characters in temporary directory (use only for verifying broken tests)')
+
+ subparsers = parser.add_subparsers(metavar='COMMAND')
+ subparsers.required = True # work-around for python 3 bug which makes subparsers optional
+
+ posix_integration = subparsers.add_parser('integration',
+ parents=[integration],
+ help='posix integration tests')
+
+ posix_integration.set_defaults(func=command_posix_integration,
+ targets=walk_posix_integration_targets,
+ config=PosixIntegrationConfig)
+
+ add_extra_docker_options(posix_integration)
+ add_httptester_options(posix_integration, argparse)
+
+ network_integration = subparsers.add_parser('network-integration',
+ parents=[integration],
+ help='network integration tests')
+
+ network_integration.set_defaults(func=command_network_integration,
+ targets=walk_network_integration_targets,
+ config=NetworkIntegrationConfig)
+
+ add_extra_docker_options(network_integration, integration=False)
+
+ network_integration.add_argument('--platform',
+ metavar='PLATFORM',
+ action='append',
+ help='network platform/version').completer = complete_network_platform
+
+ network_integration.add_argument('--platform-collection',
+ type=key_value_type,
+ metavar='PLATFORM=COLLECTION',
+ action='append',
+ help='collection used to test platform').completer = complete_network_platform_collection
+
+ network_integration.add_argument('--platform-connection',
+ type=key_value_type,
+ metavar='PLATFORM=CONNECTION',
+ action='append',
+ help='connection used to test platform').completer = complete_network_platform_connection
+
+ network_integration.add_argument('--inventory',
+ metavar='PATH',
+ help='path to inventory used for tests')
+
+ network_integration.add_argument('--testcase',
+ metavar='TESTCASE',
+ help='limit a test to a specified testcase').completer = complete_network_testcase
+
+ windows_integration = subparsers.add_parser('windows-integration',
+ parents=[integration],
+ help='windows integration tests')
+
+ windows_integration.set_defaults(func=command_windows_integration,
+ targets=walk_windows_integration_targets,
+ config=WindowsIntegrationConfig)
+
+ add_extra_docker_options(windows_integration, integration=False)
+ add_httptester_options(windows_integration, argparse)
+
+ windows_integration.add_argument('--windows',
+ metavar='VERSION',
+ action='append',
+ help='windows version').completer = complete_windows
+
+ windows_integration.add_argument('--inventory',
+ metavar='PATH',
+ help='path to inventory used for tests')
+
+ units = subparsers.add_parser('units',
+ parents=[test],
+ help='unit tests')
+
+ units.set_defaults(func=command_units,
+ targets=walk_units_targets,
+ config=UnitsConfig)
+
+ units.add_argument('--python',
+ metavar='VERSION',
+ choices=SUPPORTED_PYTHON_VERSIONS + ('default',),
+ help='python version: %s' % ', '.join(SUPPORTED_PYTHON_VERSIONS))
+
+ units.add_argument('--collect-only',
+ action='store_true',
+ help='collect tests but do not execute them')
+
+ # noinspection PyTypeChecker
+ units.add_argument('--num-workers',
+ type=int,
+ help='number of workers to use (default: auto)')
+
+ units.add_argument('--requirements-mode',
+ choices=('only', 'skip'),
+ help=argparse.SUPPRESS)
+
+ add_extra_docker_options(units, integration=False)
+
+ sanity = subparsers.add_parser('sanity',
+ parents=[test],
+ help='sanity tests')
+
+ sanity.set_defaults(func=command_sanity,
+ targets=walk_sanity_targets,
+ config=SanityConfig)
+
+ sanity.add_argument('--test',
+ metavar='TEST',
+ action='append',
+ choices=[test.name for test in sanity_get_tests()],
+ help='tests to run').completer = complete_sanity_test
+
+ sanity.add_argument('--skip-test',
+ metavar='TEST',
+ action='append',
+ choices=[test.name for test in sanity_get_tests()],
+ help='tests to skip').completer = complete_sanity_test
+
+ sanity.add_argument('--allow-disabled',
+ action='store_true',
+ help='allow tests to run which are disabled by default')
+
+ sanity.add_argument('--list-tests',
+ action='store_true',
+ help='list available tests')
+
+ sanity.add_argument('--python',
+ metavar='VERSION',
+ choices=SUPPORTED_PYTHON_VERSIONS + ('default',),
+ help='python version: %s' % ', '.join(SUPPORTED_PYTHON_VERSIONS))
+
+ sanity.add_argument('--enable-optional-errors',
+ action='store_true',
+ help='enable optional errors')
+
+ add_lint(sanity)
+ add_extra_docker_options(sanity, integration=False)
+
+ shell = subparsers.add_parser('shell',
+ parents=[common],
+ help='open an interactive shell')
+
+ shell.add_argument('--python',
+ metavar='VERSION',
+ choices=SUPPORTED_PYTHON_VERSIONS + ('default',),
+ help='python version: %s' % ', '.join(SUPPORTED_PYTHON_VERSIONS))
+
+ shell.set_defaults(func=command_shell,
+ config=ShellConfig)
+
+ shell.add_argument('--raw',
+ action='store_true',
+ help='direct to shell with no setup')
+
+ add_environments(shell)
+ add_extra_docker_options(shell)
+ add_httptester_options(shell, argparse)
+
+ coverage_common = argparse.ArgumentParser(add_help=False, parents=[common])
+
+ add_environments(coverage_common, isolated_delegation=False)
+
+ coverage = subparsers.add_parser('coverage',
+ help='code coverage management and reporting')
+
+ coverage_subparsers = coverage.add_subparsers(metavar='COMMAND')
+ coverage_subparsers.required = True # work-around for python 3 bug which makes subparsers optional
+
+ add_coverage_analyze(coverage_subparsers, coverage_common)
+
+ coverage_combine = coverage_subparsers.add_parser('combine',
+ parents=[coverage_common],
+ help='combine coverage data and rewrite remote paths')
+
+ coverage_combine.set_defaults(func=command_coverage_combine,
+ config=CoverageConfig)
+
+ coverage_combine.add_argument('--export',
+ help='directory to export combined coverage files to')
+
+ add_extra_coverage_options(coverage_combine)
+
+ coverage_erase = coverage_subparsers.add_parser('erase',
+ parents=[coverage_common],
+ help='erase coverage data files')
+
+ coverage_erase.set_defaults(func=command_coverage_erase,
+ config=CoverageConfig)
+
+ coverage_report = coverage_subparsers.add_parser('report',
+ parents=[coverage_common],
+ help='generate console coverage report')
+
+ coverage_report.set_defaults(func=command_coverage_report,
+ config=CoverageReportConfig)
+
+ coverage_report.add_argument('--show-missing',
+ action='store_true',
+ help='show line numbers of statements not executed')
+ coverage_report.add_argument('--include',
+ metavar='PAT1,PAT2,...',
+ help='include only files whose paths match one of these '
+ 'patterns. Accepts shell-style wildcards, which must be '
+ 'quoted.')
+ coverage_report.add_argument('--omit',
+ metavar='PAT1,PAT2,...',
+ help='omit files whose paths match one of these patterns. '
+ 'Accepts shell-style wildcards, which must be quoted.')
+
+ add_extra_coverage_options(coverage_report)
+
+ coverage_html = coverage_subparsers.add_parser('html',
+ parents=[coverage_common],
+ help='generate html coverage report')
+
+ coverage_html.set_defaults(func=command_coverage_html,
+ config=CoverageConfig)
+
+ add_extra_coverage_options(coverage_html)
+
+ coverage_xml = coverage_subparsers.add_parser('xml',
+ parents=[coverage_common],
+ help='generate xml coverage report')
+
+ coverage_xml.set_defaults(func=command_coverage_xml,
+ config=CoverageConfig)
+
+ add_extra_coverage_options(coverage_xml)
+
+ env = subparsers.add_parser('env',
+ parents=[common],
+ help='show information about the test environment')
+
+ env.set_defaults(func=command_env,
+ config=EnvConfig)
+
+ env.add_argument('--show',
+ action='store_true',
+ help='show environment on stdout')
+
+ env.add_argument('--dump',
+ action='store_true',
+ help='dump environment to disk')
+
+ env.add_argument('--list-files',
+ action='store_true',
+ help='list files on stdout')
+
+ # noinspection PyTypeChecker
+ env.add_argument('--timeout',
+ type=int,
+ metavar='MINUTES',
+ help='timeout for future ansible-test commands (0 clears)')
+
+ if argcomplete:
+ argcomplete.autocomplete(parser, always_complete_options=False, validator=lambda i, k: True)
+
+ args = parser.parse_args()
+
+ if args.explain and not args.verbosity:
+ args.verbosity = 1
+
+ if args.color == 'yes':
+ args.color = True
+ elif args.color == 'no':
+ args.color = False
+ else:
+ args.color = sys.stdout.isatty()
+
+ return args
+
+
+def key_value(argparse, value): # type: (argparse_module, str) -> t.Tuple[str, str]
+ """Type parsing and validation for argparse key/value pairs separated by an '=' character."""
+ parts = value.split('=')
+
+ if len(parts) != 2:
+ raise argparse.ArgumentTypeError('"%s" must be in the format "key=value"' % value)
+
+ return parts[0], parts[1]
+
+
+# noinspection PyProtectedMember
+def add_coverage_analyze(coverage_subparsers, coverage_common): # type: (argparse_module._SubParsersAction, argparse_module.ArgumentParser) -> None
+ """Add the `coverage analyze` subcommand."""
+ analyze = coverage_subparsers.add_parser(
+ 'analyze',
+ help='analyze collected coverage data',
+ )
+
+ analyze_subparsers = analyze.add_subparsers(metavar='COMMAND')
+ analyze_subparsers.required = True # work-around for python 3 bug which makes subparsers optional
+
+ targets = analyze_subparsers.add_parser(
+ 'targets',
+ help='analyze integration test target coverage',
+ )
+
+ targets_subparsers = targets.add_subparsers(metavar='COMMAND')
+ targets_subparsers.required = True # work-around for python 3 bug which makes subparsers optional
+
+ targets_generate = targets_subparsers.add_parser(
+ 'generate',
+ parents=[coverage_common],
+ help='aggregate coverage by integration test target',
+ )
+
+ targets_generate.set_defaults(
+ func=command_coverage_analyze_targets_generate,
+ config=CoverageAnalyzeTargetsGenerateConfig,
+ )
+
+ targets_generate.add_argument(
+ 'input_dir',
+ nargs='?',
+ help='directory to read coverage from',
+ )
+
+ targets_generate.add_argument(
+ 'output_file',
+ help='output file for aggregated coverage',
+ )
+
+ targets_expand = targets_subparsers.add_parser(
+ 'expand',
+ parents=[coverage_common],
+ help='expand target names from integers in aggregated coverage',
+ )
+
+ targets_expand.set_defaults(
+ func=command_coverage_analyze_targets_expand,
+ config=CoverageAnalyzeTargetsExpandConfig,
+ )
+
+ targets_expand.add_argument(
+ 'input_file',
+ help='input file to read aggregated coverage from',
+ )
+
+ targets_expand.add_argument(
+ 'output_file',
+ help='output file to write expanded coverage to',
+ )
+
+ targets_filter = targets_subparsers.add_parser(
+ 'filter',
+ parents=[coverage_common],
+ help='filter aggregated coverage data',
+ )
+
+ targets_filter.set_defaults(
+ func=command_coverage_analyze_targets_filter,
+ config=CoverageAnalyzeTargetsFilterConfig,
+ )
+
+ targets_filter.add_argument(
+ 'input_file',
+ help='input file to read aggregated coverage from',
+ )
+
+ targets_filter.add_argument(
+ 'output_file',
+ help='output file to write expanded coverage to',
+ )
+
+ targets_filter.add_argument(
+ '--include-target',
+ dest='include_targets',
+ action='append',
+ help='include the specified targets',
+ )
+
+ targets_filter.add_argument(
+ '--exclude-target',
+ dest='exclude_targets',
+ action='append',
+ help='exclude the specified targets',
+ )
+
+ targets_filter.add_argument(
+ '--include-path',
+ help='include paths matching the given regex',
+ )
+
+ targets_filter.add_argument(
+ '--exclude-path',
+ help='exclude paths matching the given regex',
+ )
+
+ targets_combine = targets_subparsers.add_parser(
+ 'combine',
+ parents=[coverage_common],
+ help='combine multiple aggregated coverage files',
+ )
+
+ targets_combine.set_defaults(
+ func=command_coverage_analyze_targets_combine,
+ config=CoverageAnalyzeTargetsCombineConfig,
+ )
+
+ targets_combine.add_argument(
+ 'input_file',
+ nargs='+',
+ help='input file to read aggregated coverage from',
+ )
+
+ targets_combine.add_argument(
+ 'output_file',
+ help='output file to write aggregated coverage to',
+ )
+
+ targets_missing = targets_subparsers.add_parser(
+ 'missing',
+ parents=[coverage_common],
+ help='identify coverage in one file missing in another',
+ )
+
+ targets_missing.set_defaults(
+ func=command_coverage_analyze_targets_missing,
+ config=CoverageAnalyzeTargetsMissingConfig,
+ )
+
+ targets_missing.add_argument(
+ 'from_file',
+ help='input file containing aggregated coverage',
+ )
+
+ targets_missing.add_argument(
+ 'to_file',
+ help='input file containing aggregated coverage',
+ )
+
+ targets_missing.add_argument(
+ 'output_file',
+ help='output file to write aggregated coverage to',
+ )
+
+ targets_missing.add_argument(
+ '--only-gaps',
+ action='store_true',
+ help='report only arcs/lines not hit by any target',
+ )
+
+ targets_missing.add_argument(
+ '--only-exists',
+ action='store_true',
+ help='limit results to files that exist',
+ )
+
+
+def add_lint(parser):
+ """
+ :type parser: argparse.ArgumentParser
+ """
+ parser.add_argument('--lint',
+ action='store_true',
+ help='write lint output to stdout, everything else stderr')
+
+ parser.add_argument('--junit',
+ action='store_true',
+ help='write test failures to junit xml files')
+
+ parser.add_argument('--failure-ok',
+ action='store_true',
+ help='exit successfully on failed tests after saving results')
+
+
+def add_changes(parser, argparse):
+ """
+ :type parser: argparse.ArgumentParser
+ :type argparse: argparse
+ """
+ parser.add_argument('--changed', action='store_true', help='limit targets based on changes')
+
+ changes = parser.add_argument_group(title='change detection arguments')
+
+ changes.add_argument('--tracked', action='store_true', help=argparse.SUPPRESS)
+ changes.add_argument('--untracked', action='store_true', help='include untracked files')
+ changes.add_argument('--ignore-committed', dest='committed', action='store_false', help='exclude committed files')
+ changes.add_argument('--ignore-staged', dest='staged', action='store_false', help='exclude staged files')
+ changes.add_argument('--ignore-unstaged', dest='unstaged', action='store_false', help='exclude unstaged files')
+
+ changes.add_argument('--changed-from', metavar='PATH', help=argparse.SUPPRESS)
+ changes.add_argument('--changed-path', metavar='PATH', action='append', help=argparse.SUPPRESS)
+
+
+def add_environments(parser, isolated_delegation=True):
+ """
+ :type parser: argparse.ArgumentParser
+ :type isolated_delegation: bool
+ """
+ parser.add_argument('--requirements',
+ action='store_true',
+ help='install command requirements')
+
+ parser.add_argument('--python-interpreter',
+ metavar='PATH',
+ default=None,
+ help='path to the docker or remote python interpreter')
+
+ parser.add_argument('--no-pip-check',
+ dest='pip_check',
+ default=True,
+ action='store_false',
+ help='do not run "pip check" to verify requirements')
+
+ environments = parser.add_mutually_exclusive_group()
+
+ environments.add_argument('--local',
+ action='store_true',
+ help='run from the local environment')
+
+ environments.add_argument('--venv',
+ action='store_true',
+ help='run from ansible-test managed virtual environments')
+
+ venv = parser.add_argument_group(title='venv arguments')
+
+ venv.add_argument('--venv-system-site-packages',
+ action='store_true',
+ help='enable system site packages')
+
+ if not isolated_delegation:
+ environments.set_defaults(
+ docker=None,
+ remote=None,
+ remote_stage=None,
+ remote_provider=None,
+ remote_aws_region=None,
+ remote_terminate=None,
+ remote_endpoint=None,
+ python_interpreter=None,
+ )
+
+ return
+
+ environments.add_argument('--docker',
+ metavar='IMAGE',
+ nargs='?',
+ default=None,
+ const='default',
+ help='run from a docker container').completer = complete_docker
+
+ environments.add_argument('--remote',
+ metavar='PLATFORM',
+ default=None,
+ help='run from a remote instance').completer = complete_remote_shell if parser.prog.endswith(' shell') else complete_remote
+
+ remote = parser.add_argument_group(title='remote arguments')
+
+ remote.add_argument('--remote-stage',
+ metavar='STAGE',
+ help='remote stage to use: prod, dev',
+ default='prod').completer = complete_remote_stage
+
+ remote.add_argument('--remote-provider',
+ metavar='PROVIDER',
+ help='remote provider to use: %(choices)s',
+ choices=['default', 'aws', 'azure', 'parallels', 'ibmvpc', 'ibmps'],
+ default='default')
+
+ remote.add_argument('--remote-endpoint',
+ metavar='ENDPOINT',
+ help='remote provisioning endpoint to use (default: auto)',
+ default=None)
+
+ remote.add_argument('--remote-aws-region',
+ metavar='REGION',
+ help='remote aws region to use: %(choices)s (default: auto)',
+ choices=sorted(AWS_ENDPOINTS),
+ default=None)
+
+ remote.add_argument('--remote-terminate',
+ metavar='WHEN',
+ help='terminate remote instance: %(choices)s (default: %(default)s)',
+ choices=['never', 'always', 'success'],
+ default='never')
+
+
+def add_extra_coverage_options(parser):
+ """
+ :type parser: argparse.ArgumentParser
+ """
+ parser.add_argument('--group-by',
+ metavar='GROUP',
+ action='append',
+ choices=COVERAGE_GROUPS,
+ help='group output by: %s' % ', '.join(COVERAGE_GROUPS))
+
+ parser.add_argument('--all',
+ action='store_true',
+ help='include all python/powershell source files')
+
+ parser.add_argument('--stub',
+ action='store_true',
+ help='generate empty report of all python/powershell source files')
+
+
+def add_httptester_options(parser, argparse):
+ """
+ :type parser: argparse.ArgumentParser
+ :type argparse: argparse
+ """
+ group = parser.add_mutually_exclusive_group()
+
+ group.add_argument('--httptester',
+ metavar='IMAGE',
+ default='quay.io/ansible/http-test-container:1.0.0',
+ help='docker image to use for the httptester container')
+
+ group.add_argument('--disable-httptester',
+ dest='httptester',
+ action='store_const',
+ const='',
+ help='do not use the httptester container')
+
+ parser.add_argument('--inject-httptester',
+ action='store_true',
+ help=argparse.SUPPRESS) # internal use only
+
+
+def add_extra_docker_options(parser, integration=True):
+ """
+ :type parser: argparse.ArgumentParser
+ :type integration: bool
+ """
+ docker = parser.add_argument_group(title='docker arguments')
+
+ docker.add_argument('--docker-no-pull',
+ action='store_false',
+ dest='docker_pull',
+ help='do not explicitly pull the latest docker images')
+
+ if data_context().content.is_ansible:
+ docker.add_argument('--docker-keep-git',
+ action='store_true',
+ help='transfer git related files into the docker container')
+ else:
+ docker.set_defaults(
+ docker_keep_git=False,
+ )
+
+ docker.add_argument('--docker-seccomp',
+ metavar='SC',
+ choices=('default', 'unconfined'),
+ default=None,
+ help='set seccomp confinement for the test container: %(choices)s')
+
+ docker.add_argument('--docker-terminate',
+ metavar='WHEN',
+ help='terminate docker container: %(choices)s (default: %(default)s)',
+ choices=['never', 'always', 'success'],
+ default='always')
+
+ if not integration:
+ return
+
+ docker.add_argument('--docker-privileged',
+ action='store_true',
+ help='run docker container in privileged mode')
+
+ docker.add_argument('--docker-network',
+ help='run using the specified docker network')
+
+ # noinspection PyTypeChecker
+ docker.add_argument('--docker-memory',
+ help='memory limit for docker in bytes', type=int)
+
+
+# noinspection PyUnusedLocal
+def complete_remote_stage(prefix, parsed_args, **_): # pylint: disable=unused-argument
+ """
+ :type prefix: unicode
+ :type parsed_args: any
+ :rtype: list[str]
+ """
+ return [stage for stage in ('prod', 'dev') if stage.startswith(prefix)]
+
+
+def complete_target(prefix, parsed_args, **_):
+ """
+ :type prefix: unicode
+ :type parsed_args: any
+ :rtype: list[str]
+ """
+ return find_target_completion(parsed_args.targets, prefix)
+
+
+# noinspection PyUnusedLocal
+def complete_remote(prefix, parsed_args, **_):
+ """
+ :type prefix: unicode
+ :type parsed_args: any
+ :rtype: list[str]
+ """
+ del parsed_args
+
+ images = sorted(get_remote_completion().keys())
+
+ return [i for i in images if i.startswith(prefix)]
+
+
+# noinspection PyUnusedLocal
+def complete_remote_shell(prefix, parsed_args, **_):
+ """
+ :type prefix: unicode
+ :type parsed_args: any
+ :rtype: list[str]
+ """
+ del parsed_args
+
+ images = sorted(get_remote_completion().keys())
+
+ # 2008 doesn't support SSH so we do not add to the list of valid images
+ windows_completion_path = os.path.join(ANSIBLE_TEST_DATA_ROOT, 'completion', 'windows.txt')
+ images.extend(["windows/%s" % i for i in read_lines_without_comments(windows_completion_path, remove_blank_lines=True) if i != '2008'])
+
+ return [i for i in images if i.startswith(prefix)]
+
+
+# noinspection PyUnusedLocal
+def complete_docker(prefix, parsed_args, **_):
+ """
+ :type prefix: unicode
+ :type parsed_args: any
+ :rtype: list[str]
+ """
+ del parsed_args
+
+ images = sorted(get_docker_completion().keys())
+
+ return [i for i in images if i.startswith(prefix)]
+
+
+def complete_windows(prefix, parsed_args, **_):
+ """
+ :type prefix: unicode
+ :type parsed_args: any
+ :rtype: list[str]
+ """
+ images = read_lines_without_comments(os.path.join(ANSIBLE_TEST_DATA_ROOT, 'completion', 'windows.txt'), remove_blank_lines=True)
+
+ return [i for i in images if i.startswith(prefix) and (not parsed_args.windows or i not in parsed_args.windows)]
+
+
+def complete_network_platform(prefix, parsed_args, **_):
+ """
+ :type prefix: unicode
+ :type parsed_args: any
+ :rtype: list[str]
+ """
+ images = sorted(get_network_completion())
+
+ return [i for i in images if i.startswith(prefix) and (not parsed_args.platform or i not in parsed_args.platform)]
+
+
+def complete_network_platform_collection(prefix, parsed_args, **_):
+ """
+ :type prefix: unicode
+ :type parsed_args: any
+ :rtype: list[str]
+ """
+ left = prefix.split('=')[0]
+ images = sorted(set(image.split('/')[0] for image in get_network_completion()))
+
+ return [i + '=' for i in images if i.startswith(left) and (not parsed_args.platform_collection or i not in [x[0] for x in parsed_args.platform_collection])]
+
+
+def complete_network_platform_connection(prefix, parsed_args, **_):
+ """
+ :type prefix: unicode
+ :type parsed_args: any
+ :rtype: list[str]
+ """
+ left = prefix.split('=')[0]
+ images = sorted(set(image.split('/')[0] for image in get_network_completion()))
+
+ return [i + '=' for i in images if i.startswith(left) and (not parsed_args.platform_connection or i not in [x[0] for x in parsed_args.platform_connection])]
+
+
+def complete_network_testcase(prefix, parsed_args, **_):
+ """
+ :type prefix: unicode
+ :type parsed_args: any
+ :rtype: list[str]
+ """
+ testcases = []
+
+ # since testcases are module specific, don't autocomplete if more than one
+ # module is specidied
+ if len(parsed_args.include) != 1:
+ return []
+
+ test_dir = os.path.join(data_context().content.integration_targets_path, parsed_args.include[0], 'tests')
+ connection_dirs = data_context().content.get_dirs(test_dir)
+
+ for connection_dir in connection_dirs:
+ for testcase in [os.path.basename(path) for path in data_context().content.get_files(connection_dir)]:
+ if testcase.startswith(prefix):
+ testcases.append(testcase.split('.')[0])
+
+ return testcases
+
+
+# noinspection PyUnusedLocal
+def complete_sanity_test(prefix, parsed_args, **_):
+ """
+ :type prefix: unicode
+ :type parsed_args: any
+ :rtype: list[str]
+ """
+ del parsed_args
+
+ tests = sorted(test.name for test in sanity_get_tests())
+
+ return [i for i in tests if i.startswith(prefix)]