bpo-30523: regrtest: Add --list-cases option (#2238)

* bpo-30523: regrtest: Add --list-cases option

* bpo-30523: Enhance --list-cases

* Add get_abs_module() function, use it in list_cases()
* list_cases() now logs skipped tests into stderr

* Remove unused doctest
diff --git a/Lib/test/libregrtest/cmdline.py b/Lib/test/libregrtest/cmdline.py
index 7f66997..bf64062 100644
--- a/Lib/test/libregrtest/cmdline.py
+++ b/Lib/test/libregrtest/cmdline.py
@@ -250,6 +250,9 @@
     group.add_argument('--list-tests', action='store_true',
                        help="only write the name of tests that will be run, "
                             "don't execute them")
+    group.add_argument('--list-cases', action='store_true',
+                       help='only write the name of test cases that will be run'
+                            ' , don\'t execute them')
     group.add_argument('-P', '--pgo', dest='pgo', action='store_true',
                        help='enable Profile Guided Optimization training')
 
diff --git a/Lib/test/libregrtest/main.py b/Lib/test/libregrtest/main.py
index 7b11fe9..527de17 100644
--- a/Lib/test/libregrtest/main.py
+++ b/Lib/test/libregrtest/main.py
@@ -10,9 +10,10 @@
 import tempfile
 import textwrap
 import time
+import unittest
 from test.libregrtest.cmdline import _parse_args
 from test.libregrtest.runtest import (
-    findtests, runtest,
+    findtests, runtest, get_abs_module,
     STDTESTS, NOTTESTS, PASSED, FAILED, ENV_CHANGED, SKIPPED, RESOURCE_DENIED,
     INTERRUPTED, CHILD_ERROR,
     PROGRESS_MIN_TIME, format_test_result)
@@ -248,6 +249,29 @@
         for name in self.selected:
             print(name)
 
+    def _list_cases(self, suite):
+        for test in suite:
+            if isinstance(test, unittest.loader._FailedTest):
+                continue
+            if isinstance(test, unittest.TestSuite):
+                self._list_cases(test)
+            elif isinstance(test, unittest.TestCase):
+                print(test.id())
+
+    def list_cases(self):
+        for test in self.selected:
+            abstest = get_abs_module(self.ns, test)
+            try:
+                suite = unittest.defaultTestLoader.loadTestsFromName(abstest)
+                self._list_cases(suite)
+            except unittest.SkipTest:
+                self.skipped.append(test)
+
+        if self.skipped:
+            print(file=sys.stderr)
+            print(count(len(self.skipped), "test"), "skipped:", file=sys.stderr)
+            printlist(self.skipped, file=sys.stderr)
+
     def rerun_failed_tests(self):
         self.ns.verbose = True
         self.ns.failfast = False
@@ -499,6 +523,10 @@
             self.list_tests()
             sys.exit(0)
 
+        if self.ns.list_cases:
+            self.list_cases()
+            sys.exit(0)
+
         self.run_tests()
         self.display_result()
 
@@ -525,7 +553,7 @@
         return "%d %ss" % (n, word)
 
 
-def printlist(x, width=70, indent=4):
+def printlist(x, width=70, indent=4, file=None):
     """Print the elements of iterable x to stdout.
 
     Optional arg width (default 70) is the maximum line length.
@@ -536,7 +564,8 @@
     blanks = ' ' * indent
     # Print the sorted list: 'x' may be a '--random' list or a set()
     print(textwrap.fill(' '.join(str(elt) for elt in sorted(x)), width,
-                        initial_indent=blanks, subsequent_indent=blanks))
+                        initial_indent=blanks, subsequent_indent=blanks),
+          file=file)
 
 
 def main(tests=None, **kwargs):
diff --git a/Lib/test/libregrtest/runtest.py b/Lib/test/libregrtest/runtest.py
index ba0df0a..fda4ca1 100644
--- a/Lib/test/libregrtest/runtest.py
+++ b/Lib/test/libregrtest/runtest.py
@@ -71,6 +71,14 @@
     return stdtests + sorted(tests)
 
 
+def get_abs_module(ns, test):
+    if test.startswith('test.') or ns.testdir:
+        return test
+    else:
+        # Always import it from the test package
+        return 'test.' + test
+
+
 def runtest(ns, test):
     """Run a single test.
 
@@ -141,11 +149,7 @@
     test_time = 0.0
     refleak = False  # True if the test leaked references.
     try:
-        if test.startswith('test.') or ns.testdir:
-            abstest = test
-        else:
-            # Always import it from the test package
-            abstest = 'test.' + test
+        abstest = get_abs_module(ns, test)
         clear_caches()
         with saved_test_environment(test, ns.verbose, ns.quiet, pgo=ns.pgo) as environment:
             start_time = time.time()