bpo-15450: Allow subclassing of dircmp (GH-23424) (#23424)

Co-authored-by: Chris Jerdonek <chris.jerdonek@gmail.com>
diff --git a/Lib/test/test_filecmp.py b/Lib/test/test_filecmp.py
index ca9b4f3..fa4f67b 100644
--- a/Lib/test/test_filecmp.py
+++ b/Lib/test/test_filecmp.py
@@ -66,6 +66,8 @@ def setUp(self):
         for dir in (self.dir, self.dir_same, self.dir_diff, self.dir_ignored):
             shutil.rmtree(dir, True)
             os.mkdir(dir)
+            subdir_path = os.path.join(dir, 'subdir')
+            os.mkdir(subdir_path)
             if self.caseinsensitive and dir is self.dir_same:
                 fn = 'FiLe'     # Verify case-insensitive comparison
             else:
@@ -110,6 +112,11 @@ def test_cmpfiles(self):
                     "Comparing mismatched directories fails")
 
 
+    def _assert_lists(self, actual, expected):
+        """Assert that two lists are equal, up to ordering."""
+        self.assertEqual(sorted(actual), sorted(expected))
+
+
     def test_dircmp(self):
         # Check attributes for comparison of two identical directories
         left_dir, right_dir = self.dir, self.dir_same
@@ -117,10 +124,13 @@ def test_dircmp(self):
         self.assertEqual(d.left, left_dir)
         self.assertEqual(d.right, right_dir)
         if self.caseinsensitive:
-            self.assertEqual([d.left_list, d.right_list],[['file'], ['FiLe']])
+            self._assert_lists(d.left_list, ['file', 'subdir'])
+            self._assert_lists(d.right_list, ['FiLe', 'subdir'])
         else:
-            self.assertEqual([d.left_list, d.right_list],[['file'], ['file']])
-        self.assertEqual(d.common, ['file'])
+            self._assert_lists(d.left_list, ['file', 'subdir'])
+            self._assert_lists(d.right_list, ['file', 'subdir'])
+        self._assert_lists(d.common, ['file', 'subdir'])
+        self._assert_lists(d.common_dirs, ['subdir'])
         self.assertEqual(d.left_only, [])
         self.assertEqual(d.right_only, [])
         self.assertEqual(d.same_files, ['file'])
@@ -128,6 +138,7 @@ def test_dircmp(self):
         expected_report = [
             "diff {} {}".format(self.dir, self.dir_same),
             "Identical files : ['file']",
+            "Common subdirectories : ['subdir']",
         ]
         self._assert_report(d.report, expected_report)
 
@@ -136,9 +147,10 @@ def test_dircmp(self):
         d = filecmp.dircmp(left_dir, right_dir)
         self.assertEqual(d.left, left_dir)
         self.assertEqual(d.right, right_dir)
-        self.assertEqual(d.left_list, ['file'])
-        self.assertEqual(d.right_list, ['file', 'file2'])
-        self.assertEqual(d.common, ['file'])
+        self._assert_lists(d.left_list, ['file', 'subdir'])
+        self._assert_lists(d.right_list, ['file', 'file2', 'subdir'])
+        self._assert_lists(d.common, ['file', 'subdir'])
+        self._assert_lists(d.common_dirs, ['subdir'])
         self.assertEqual(d.left_only, [])
         self.assertEqual(d.right_only, ['file2'])
         self.assertEqual(d.same_files, ['file'])
@@ -147,6 +159,7 @@ def test_dircmp(self):
             "diff {} {}".format(self.dir, self.dir_diff),
             "Only in {} : ['file2']".format(self.dir_diff),
             "Identical files : ['file']",
+            "Common subdirectories : ['subdir']",
         ]
         self._assert_report(d.report, expected_report)
 
@@ -159,9 +172,9 @@ def test_dircmp(self):
         d = filecmp.dircmp(left_dir, right_dir)
         self.assertEqual(d.left, left_dir)
         self.assertEqual(d.right, right_dir)
-        self.assertEqual(d.left_list, ['file', 'file2'])
-        self.assertEqual(d.right_list, ['file'])
-        self.assertEqual(d.common, ['file'])
+        self._assert_lists(d.left_list, ['file', 'file2', 'subdir'])
+        self._assert_lists(d.right_list, ['file', 'subdir'])
+        self._assert_lists(d.common, ['file', 'subdir'])
         self.assertEqual(d.left_only, ['file2'])
         self.assertEqual(d.right_only, [])
         self.assertEqual(d.same_files, ['file'])
@@ -170,6 +183,7 @@ def test_dircmp(self):
             "diff {} {}".format(self.dir, self.dir_diff),
             "Only in {} : ['file2']".format(self.dir),
             "Identical files : ['file']",
+            "Common subdirectories : ['subdir']",
         ]
         self._assert_report(d.report, expected_report)
 
@@ -183,24 +197,45 @@ def test_dircmp(self):
             "diff {} {}".format(self.dir, self.dir_diff),
             "Identical files : ['file']",
             "Differing files : ['file2']",
+            "Common subdirectories : ['subdir']",
         ]
         self._assert_report(d.report, expected_report)
 
+    def test_dircmp_subdirs_type(self):
+        """Check that dircmp.subdirs respects subclassing."""
+        class MyDirCmp(filecmp.dircmp):
+            pass
+        d = MyDirCmp(self.dir, self.dir_diff)
+        sub_dirs = d.subdirs
+        self.assertEqual(list(sub_dirs.keys()), ['subdir'])
+        sub_dcmp = sub_dirs['subdir']
+        self.assertEqual(type(sub_dcmp), MyDirCmp)
+
     def test_report_partial_closure(self):
         left_dir, right_dir = self.dir, self.dir_same
         d = filecmp.dircmp(left_dir, right_dir)
+        left_subdir = os.path.join(left_dir, 'subdir')
+        right_subdir = os.path.join(right_dir, 'subdir')
         expected_report = [
             "diff {} {}".format(self.dir, self.dir_same),
             "Identical files : ['file']",
+            "Common subdirectories : ['subdir']",
+            '',
+            "diff {} {}".format(left_subdir, right_subdir),
         ]
         self._assert_report(d.report_partial_closure, expected_report)
 
     def test_report_full_closure(self):
         left_dir, right_dir = self.dir, self.dir_same
         d = filecmp.dircmp(left_dir, right_dir)
+        left_subdir = os.path.join(left_dir, 'subdir')
+        right_subdir = os.path.join(right_dir, 'subdir')
         expected_report = [
             "diff {} {}".format(self.dir, self.dir_same),
             "Identical files : ['file']",
+            "Common subdirectories : ['subdir']",
+            '',
+            "diff {} {}".format(left_subdir, right_subdir),
         ]
         self._assert_report(d.report_full_closure, expected_report)