diff -r 37905786b34b Lib/test/support/__init__.py --- a/Lib/test/support/__init__.py Sun Apr 12 23:24:17 2015 -0500 +++ b/Lib/test/support/__init__.py Tue Apr 14 15:18:59 2015 -0400 @@ -88,7 +88,7 @@ "skip_unless_symlink", "requires_gzip", "requires_bz2", "requires_lzma", "bigmemtest", "bigaddrspacetest", "cpython_only", "get_attribute", "requires_IEEE_754", "skip_unless_xattr", "requires_zlib", - "anticipate_failure", "load_package_tests", + "anticipate_failure", "load_package_tests", "detect_api_mismatch", # sys "is_jython", "check_impl_detail", # network @@ -2188,6 +2188,21 @@ return False +def detect_api_mismatch(ref_api, other_api, *, ignore=None): + """Return set of items in ref_api not in other_api, except for a + defined list of items to be ignored in this check. + + By default this skips private attributes beginning with '_' but + includes all public magic methods, i.e. those ending in '__'. + """ + missing_items = set(dir(ref_api)) - set(dir(other_api)) + if ignore: + missing_items -= set(ignore) + missing_items = set(m for m in missing_items + if not m.startswith('_') or m.endswith('__')) + return missing_items + + class SuppressCrashReport: """Try to prevent a crash report from popping up. diff -r 37905786b34b Lib/test/test_io.py --- a/Lib/test/test_io.py Sun Apr 12 23:24:17 2015 -0500 +++ b/Lib/test/test_io.py Tue Apr 14 15:18:59 2015 -0400 @@ -719,6 +719,20 @@ pass +class CPyMatchTest(unittest.TestCase): + + @unittest.skip('test to be fixed by issue 9858') + def test_RawIOBase_io_in_pyio_match(self): + """Test that pyio RawIOBase class has all c RawIOBase methods""" + mismatch = support.detect_api_mismatch(pyio.RawIOBase, io.RawIOBase) + self.assertEqual(mismatch, set(), msg='Python RawIOBase does not have all C RawIOBase methods') + + def test_RawIOBase_pyio_in_io_match(self): + """Test that c RawIOBase class has all pyio RawIOBase methods""" + mismatch = support.detect_api_mismatch(io.RawIOBase, pyio.RawIOBase) + self.assertEqual(mismatch, set(), msg='C RawIOBase does not have all Python RawIOBase methods') + + class CommonBufferedTests: # Tests common to BufferedReader, BufferedWriter and BufferedRandom @@ -3663,7 +3677,7 @@ def load_tests(*args): - tests = (CIOTest, PyIOTest, + tests = (CIOTest, PyIOTest, CPyMatchTest, CBufferedReaderTest, PyBufferedReaderTest, CBufferedWriterTest, PyBufferedWriterTest, CBufferedRWPairTest, PyBufferedRWPairTest, diff -r 37905786b34b Lib/test/test_support.py --- a/Lib/test/test_support.py Sun Apr 12 23:24:17 2015 -0500 +++ b/Lib/test/test_support.py Tue Apr 14 15:18:59 2015 -0400 @@ -280,6 +280,46 @@ self.assertEqual(D["item"], 5) self.assertEqual(D["item"], 1) + def test_detect_api_mismatch(self): + class RefClass: + attribute1 = None + attribute2 = None + _hidden_attribute1 = None + __magic_1__ = None + + class OtherClass: + attribute2 = None + attribute3 = None + __magic_1__ = None + __magic_2__ = None + + missing_items = support.detect_api_mismatch(RefClass, OtherClass) + self.assertEqual({'attribute1'}, missing_items) + + missing_items = support.detect_api_mismatch(OtherClass, RefClass) + self.assertEqual({'attribute3', '__magic_2__'}, missing_items) + + def test_detect_api_mismatch__ignore(self): + class RefClass: + attribute1 = None + attribute2 = None + _hidden_attribute1 = None + __magic_1__ = None + + class OtherClass: + attribute2 = None + attribute3 = None + __magic_1__ = None + __magic_2__ = None + + ignore = ['attribute1', 'attribute3', '__magic_2__', 'not_in_either'] + + missing_items = support.detect_api_mismatch(RefClass, OtherClass, ignore=ignore) + self.assertEqual(set(), missing_items) + + missing_items = support.detect_api_mismatch(OtherClass, RefClass, ignore=ignore) + self.assertEqual(set(), missing_items) + # XXX -follows a list of untested API # make_legacy_pyc # is_resource_enabled