From 27033915cc396930f2b3d165d00bc82a5f8cd78e Mon Sep 17 00:00:00 2001
From: Matt Clay <matt@mystile.com>
Date: Fri, 14 Jul 2017 19:11:25 -0700
Subject: [PATCH] Add --list-targets and speed up --explain. (#26838)

* Add ansible-test --list-targets option.
* Speed up ansible-test integration --explain.
---
 test/runner/lib/cloud/__init__.py | 25 ++++++++++++++-----------
 test/runner/lib/config.py         |  4 ++++
 test/runner/lib/delegation.py     |  4 ++--
 test/runner/lib/executor.py       | 24 ++++++++++++++++++------
 test/runner/test.py               |  7 ++++++-
 5 files changed, 44 insertions(+), 20 deletions(-)

diff --git a/test/runner/lib/cloud/__init__.py b/test/runner/lib/cloud/__init__.py
index b4e4d7a27a9..86c379239ac 100644
--- a/test/runner/lib/cloud/__init__.py
+++ b/test/runner/lib/cloud/__init__.py
@@ -17,14 +17,14 @@ from lib.util import (
     load_plugins,
 )
 
-from lib.test import (
-    TestConfig,
-)
-
 from lib.target import (
     TestTarget,
 )
 
+from lib.config import (
+    IntegrationConfig,
+)
+
 PROVIDERS = {}
 ENVIRONMENTS = {}
 
@@ -39,10 +39,13 @@ def initialize_cloud_plugins():
 
 def get_cloud_platforms(args, targets=None):
     """
-    :type args: TestConfig
+    :type args: IntegrationConfig
     :type targets: tuple[IntegrationTarget] | None
     :rtype: list[str]
     """
+    if args.list_targets:
+        return []
+
     if targets is None:
         cloud_platforms = set(args.metadata.cloud_config or [])
     else:
@@ -76,7 +79,7 @@ def get_cloud_platform(target):
 
 def get_cloud_providers(args, targets=None):
     """
-    :type args: TestConfig
+    :type args: IntegrationConfig
     :type targets: tuple[IntegrationTarget] | None
     :rtype: list[CloudProvider]
     """
@@ -85,7 +88,7 @@ def get_cloud_providers(args, targets=None):
 
 def get_cloud_environment(args, target):
     """
-    :type args: TestConfig
+    :type args: IntegrationConfig
     :type target: IntegrationTarget
     :rtype: CloudEnvironment
     """
@@ -99,7 +102,7 @@ def get_cloud_environment(args, target):
 
 def cloud_filter(args, targets):
     """
-    :type args: TestConfig
+    :type args: IntegrationConfig
     :type targets: tuple[IntegrationTarget]
     :return: list[str]
     """
@@ -116,7 +119,7 @@ def cloud_filter(args, targets):
 
 def cloud_init(args, targets):
     """
-    :type args: TestConfig
+    :type args: IntegrationConfig
     :type targets: tuple[IntegrationTarget]
     """
     if args.metadata.cloud_config is not None:
@@ -139,7 +142,7 @@ class CloudBase(object):
 
     def __init__(self, args):
         """
-        :type args: TestConfig
+        :type args: IntegrationConfig
         """
         self.args = args
         self.platform = self.__module__.split('.')[2]
@@ -209,7 +212,7 @@ class CloudProvider(CloudBase):
 
     def __init__(self, args, config_extension='.yml'):
         """
-        :type args: TestConfig
+        :type args: IntegrationConfig
         :type config_extension: str
         """
         super(CloudProvider, self).__init__(args)
diff --git a/test/runner/lib/config.py b/test/runner/lib/config.py
index e04fbc99741..f4b8317b88f 100644
--- a/test/runner/lib/config.py
+++ b/test/runner/lib/config.py
@@ -143,10 +143,14 @@ class IntegrationConfig(TestConfig):
         self.continue_on_error = args.continue_on_error  # type: bool
         self.debug_strategy = args.debug_strategy  # type: bool
         self.changed_all_target = args.changed_all_target  # type: str
+        self.list_targets = args.list_targets  # type: bool
         self.tags = args.tags
         self.skip_tags = args.skip_tags
         self.diff = args.diff
 
+        if self.list_targets:
+            self.explain = True
+
 
 class PosixIntegrationConfig(IntegrationConfig):
     """Configuration for the posix integration command."""
diff --git a/test/runner/lib/delegation.py b/test/runner/lib/delegation.py
index ad914b43024..11ed0d6c717 100644
--- a/test/runner/lib/delegation.py
+++ b/test/runner/lib/delegation.py
@@ -225,7 +225,7 @@ def delegate_docker(args, exclude, require):
                     '--env', 'HTTPTESTER=1',
                 ]
 
-            if isinstance(args, TestConfig):
+            if isinstance(args, IntegrationConfig):
                 cloud_platforms = get_cloud_providers(args)
 
                 for cloud_platform in cloud_platforms:
@@ -305,7 +305,7 @@ def delegate_remote(args, exclude, require):
 
         ssh_options = []
 
-        if isinstance(args, TestConfig):
+        if isinstance(args, IntegrationConfig):
             cloud_platforms = get_cloud_providers(args)
 
             for cloud_platform in cloud_platforms:
diff --git a/test/runner/lib/executor.py b/test/runner/lib/executor.py
index 073b5d6daad..b22411a6409 100644
--- a/test/runner/lib/executor.py
+++ b/test/runner/lib/executor.py
@@ -162,7 +162,7 @@ def install_command_requirements(args):
 
     extras = []
 
-    if isinstance(args, TestConfig):
+    if isinstance(args, IntegrationConfig):
         extras += ['cloud.%s' % cp for cp in get_cloud_platforms(args)]
 
     cmd = generate_pip_install(args.command, packages, extras)
@@ -520,7 +520,7 @@ def command_integration_filtered(args, targets):
 
     test_dir = os.path.expanduser('~/ansible_testing')
 
-    if any('needs/ssh/' in target.aliases for target in targets):
+    if not args.explain and any('needs/ssh/' in target.aliases for target in targets):
         max_tries = 20
         display.info('SSH service required for tests. Checking to make sure we can connect.')
         for i in range(1, max_tries + 1):
@@ -544,12 +544,16 @@ def command_integration_filtered(args, targets):
             if not found:
                 continue
 
+        if args.list_targets:
+            print(target.name)
+            continue
+
         tries = 2 if args.retry_on_error else 1
         verbosity = args.verbosity
 
         cloud_environment = get_cloud_environment(args, target)
 
-        original_environment = EnvironmentDescription()
+        original_environment = EnvironmentDescription(args)
 
         display.info('>>> Environment Description\n%s' % original_environment, verbosity=3)
 
@@ -1227,8 +1231,16 @@ def get_integration_remote_filter(args, targets):
 
 class EnvironmentDescription(object):
     """Description of current running environment."""
-    def __init__(self):
-        """Initialize snapshot of environment configuration."""
+    def __init__(self, args):
+        """Initialize snapshot of environment configuration.
+        :type args: IntegrationConfig
+        """
+        self.args = args
+
+        if self.args.explain:
+            self.data = {}
+            return
+
         versions = ['']
         versions += SUPPORTED_PYTHON_VERSIONS
         versions += list(set(v.split('.')[0] for v in SUPPORTED_PYTHON_VERSIONS))
@@ -1262,7 +1274,7 @@ class EnvironmentDescription(object):
         :type throw: bool
         :rtype: bool
         """
-        current = EnvironmentDescription()
+        current = EnvironmentDescription(self.args)
 
         original_json = str(self)
         current_json = str(current)
diff --git a/test/runner/test.py b/test/runner/test.py
index 002595f3bbf..a25c265cd8f 100755
--- a/test/runner/test.py
+++ b/test/runner/test.py
@@ -34,6 +34,7 @@ from lib.executor import (
 )
 
 from lib.config import (
+    IntegrationConfig,
     PosixIntegrationConfig,
     WindowsIntegrationConfig,
     NetworkIntegrationConfig,
@@ -81,7 +82,7 @@ def main():
         config = args.config(args)
         display.verbosity = config.verbosity
         display.color = config.color
-        display.info_stderr = isinstance(config, SanityConfig) and config.lint
+        display.info_stderr = (isinstance(config, SanityConfig) and config.lint) or (isinstance(config, IntegrationConfig) and config.list_targets)
         check_startup()
 
         try:
@@ -229,6 +230,10 @@ def parse_args():
                              default='all',
                              help='target to run when all tests are needed')
 
+    integration.add_argument('--list-targets',
+                             action='store_true',
+                             help='list matching targets instead of running tests')
+
     subparsers = parser.add_subparsers(metavar='COMMAND')
     subparsers.required = True  # work-around for python 3 bug which makes subparsers optional