Skip to content

Commit c7969f1

Browse files
authored
Merge pull request #48 from Eddy114514/mro_warning
Add warning to old/classic class inheritance problem
2 parents 8ca49cb + a9473ae commit c7969f1

2 files changed

Lines changed: 622 additions & 16 deletions

File tree

Lib/test/test_py3kwarn.py

Lines changed: 141 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import unittest
22
import sys
3+
import contextlib
34
from test.test_support import check_py3k_warnings, CleanImport, run_unittest
45
from test.script_helper import assert_python_ok
56
import warnings
@@ -36,6 +37,24 @@ def reset_module_registry(module):
3637

3738
class TestPy3KWarnings(unittest.TestCase):
3839

40+
@contextlib.contextmanager
41+
def check_py3k_warnings_with_fix(self):
42+
frame = sys._getframe(2)
43+
registry = frame.f_globals.get('__warningregistry__')
44+
if registry:
45+
registry.clear()
46+
with warnings.catch_warnings(record=True) as w:
47+
# PyErr_WarnExplicit_WithFix uses this runtime hook directly.
48+
showwarningwithfix = warnings.showwarningwithfix
49+
def record_warning_with_fix(*args, **kwargs):
50+
w.append(warnings.WarningMessageWithFix(*args, **kwargs))
51+
warnings.showwarningwithfix = record_warning_with_fix
52+
warnings.simplefilter("always")
53+
try:
54+
yield test_support.WarningsRecorder(w)
55+
finally:
56+
warnings.showwarningwithfix = showwarningwithfix
57+
3958
def assertWarning(self, _, warning, expected_message):
4059
self.assertEqual(str(warning.message), expected_message)
4160

@@ -425,30 +444,137 @@ def test_b32encode_warns(self):
425444
expected = "base64.b32encode returns str in Python 2 (bytes in 3.x)"
426445
base64.b32encode(b'test')
427446
check_py3k_warnings(expected, UserWarning)
428-
447+
429448
def test_b16encode_warns(self):
430449
expected = "base64.b16encode returns str in Python 2 (bytes in 3.x)"
431450
base64.b16encode(b'test')
432451
check_py3k_warnings(expected, UserWarning)
433452

434-
def assertExecLocalWritebackWarning(self, recorder, local_name):
453+
def assertMROWarning(self, recorder, expected_message):
435454
self.assertEqual(len(recorder.warnings), 1)
436455
msg = str(recorder.warnings[0].message)
437-
self.assertTrue(msg.startswith(
438-
"exec() modified local '%s'" % local_name))
456+
self.assertEqual(msg, expected_message)
439457
recorder.reset()
458+
459+
def test_classic_mro_resolution_change_warning(self):
460+
with self.check_py3k_warnings_with_fix() as w:
461+
class A:
462+
def do_this(self):
463+
return "A"
464+
465+
class B(A):
466+
pass
467+
468+
class C(A):
469+
def do_this(self):
470+
return "C"
471+
472+
class D(B, C):
473+
pass
474+
475+
self.assertEqual(D().do_this(), "A")
476+
self.assertMROWarning(
477+
w,
478+
"classic multiple inheritance for class 'D' will resolve "
479+
"attribute 'do_this' from 'A' in 2.x but from 'C' in 3.x "
480+
"due to C3 MRO")
481+
482+
def test_classic_mro_single_inheritance_no_warning(self):
483+
with self.check_py3k_warnings_with_fix() as w:
484+
class A:
485+
def only_here(self):
486+
return "A"
487+
488+
class B(A):
489+
pass
490+
491+
self.assertEqual(B().only_here(), "A")
492+
self.assertEqual(len(w.warnings), 0)
493+
494+
def test_classic_mro_no_conflicting_name_no_warning(self):
495+
with self.check_py3k_warnings_with_fix() as w:
496+
class A:
497+
pass
498+
499+
class B(A):
500+
def left(self):
501+
return "left"
502+
503+
class C(A):
504+
def right(self):
505+
return "right"
506+
507+
class D(B, C):
508+
pass
509+
510+
self.assertEqual(D().left(), "left")
511+
self.assertEqual(D().right(), "right")
512+
self.assertEqual(len(w.warnings), 0)
513+
514+
def test_classic_mro_provider_unchanged_no_warning(self):
515+
with self.check_py3k_warnings_with_fix() as w:
516+
class A:
517+
def only_here(self):
518+
return "A"
519+
520+
class B(A):
521+
pass
522+
523+
class C(A):
524+
pass
440525

441-
def test_exec_local_writeback_warning(self):
442-
rc, out, err = assert_python_ok(
443-
"-3",
444-
"-c",
445-
"def f(code):\n"
446-
" b = 42\n"
447-
" exec code\n"
448-
" return b\n"
449-
"f('b = 99')\n")
450-
self.assertEqual(rc, 0)
451-
self.assertIn("exec() modified local 'b' which is read later", err)
526+
class D(B, C):
527+
pass
528+
529+
self.assertEqual(D().only_here(), "A")
530+
self.assertEqual(len(w.warnings), 0)
531+
532+
def test_classic_mro_c3_conflict_warning(self):
533+
with self.check_py3k_warnings_with_fix() as w:
534+
class A:
535+
pass
536+
537+
class B:
538+
pass
539+
540+
class X(A, B):
541+
pass
542+
543+
class Y(B, A):
544+
pass
545+
546+
class Z(X, Y):
547+
pass
548+
549+
self.assertMROWarning(
550+
w,
551+
"classic multiple inheritance hierarchy for class 'Z' has no "
552+
"consistent C3 MRO and will fail in 3.x")
553+
554+
def test_classic_mro_bases_update_warning(self):
555+
with self.check_py3k_warnings_with_fix() as w:
556+
class A:
557+
def do_this(self):
558+
return "A"
559+
560+
class B(A):
561+
pass
562+
563+
class C(A):
564+
def do_this(self):
565+
return "C"
566+
567+
class D(B):
568+
pass
569+
570+
self.assertEqual(len(w.warnings), 0)
571+
D.__bases__ = (B, C)
572+
self.assertEqual(D().do_this(), "A")
573+
self.assertMROWarning(
574+
w,
575+
"classic multiple inheritance for class 'D' will resolve "
576+
"attribute 'do_this' from 'A' in 2.x but from 'C' in 3.x "
577+
"due to C3 MRO")
452578

453579

454580
class TestStdlibRemovals(unittest.TestCase):

0 commit comments

Comments
 (0)