From 06bb68a6c6f3c06bca7889f4a1ba771fce9011c4 Mon Sep 17 00:00:00 2001 From: NakanoMiku Date: Thu, 30 Nov 2023 19:45:47 +0800 Subject: [PATCH 01/23] Update test_cmd_line.py from CPython v3.12.0 --- Lib/test/test_cmd_line.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index 7e15bc8f88..bd1b6620c0 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -18,9 +18,6 @@ if not support.has_subprocess_support: raise unittest.SkipTest("test module requires subprocess") -# Debug build? -Py_DEBUG = hasattr(sys, "gettotalrefcount") - # XXX (ncoghlan): Move to script_helper and make consistent with run_python def _kill_python_and_exit_code(p): @@ -144,7 +141,7 @@ def run_python(*args): # "-X showrefcount" shows the refcount, but only in debug builds rc, out, err = run_python('-I', '-X', 'showrefcount', '-c', code) self.assertEqual(out.rstrip(), b"{'showrefcount': True}") - if Py_DEBUG: + if support.Py_DEBUG: # bpo-46417: Tolerate negative reference count which can occur # because of bugs in C extensions. This test is only about checking # the showrefcount feature. @@ -753,7 +750,7 @@ def test_xdev(self): code = ("import warnings; " "print(' '.join('%s::%s' % (f[0], f[2].__name__) " "for f in warnings.filters))") - if Py_DEBUG: + if support.Py_DEBUG: expected_filters = "default::Warning" else: expected_filters = ("default::Warning " @@ -827,7 +824,7 @@ def test_warnings_filter_precedence(self): expected_filters = ("error::BytesWarning " "once::UserWarning " "always::UserWarning") - if not Py_DEBUG: + if not support.Py_DEBUG: expected_filters += (" " "default::DeprecationWarning " "ignore::DeprecationWarning " @@ -867,10 +864,10 @@ def test_pythonmalloc(self): # Test the PYTHONMALLOC environment variable pymalloc = support.with_pymalloc() if pymalloc: - default_name = 'pymalloc_debug' if Py_DEBUG else 'pymalloc' + default_name = 'pymalloc_debug' if support.Py_DEBUG else 'pymalloc' default_name_debug = 'pymalloc_debug' else: - default_name = 'malloc_debug' if Py_DEBUG else 'malloc' + default_name = 'malloc_debug' if support.Py_DEBUG else 'malloc' default_name_debug = 'malloc_debug' tests = [ @@ -950,7 +947,8 @@ def res2int(res): return tuple(int(i) for i in out.split()) res = assert_python_ok('-c', code) - self.assertEqual(res2int(res), (-1, sys.get_int_max_str_digits())) + current_max = sys.get_int_max_str_digits() + self.assertEqual(res2int(res), (current_max, current_max)) res = assert_python_ok('-X', 'int_max_str_digits=0', '-c', code) self.assertEqual(res2int(res), (0, 0)) res = assert_python_ok('-X', 'int_max_str_digits=4000', '-c', code) From 999dcbdd1bd1dec0b16b04309bf24742d889436b Mon Sep 17 00:00:00 2001 From: NakanoMiku Date: Thu, 30 Nov 2023 19:48:22 +0800 Subject: [PATCH 02/23] Edit test_cmd_line.py --- Lib/test/test_cmd_line.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index bd1b6620c0..02f060ba2c 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -930,6 +930,8 @@ def test_parsing_error(self): self.assertTrue(proc.stderr.startswith(err_msg), proc.stderr) self.assertNotEqual(proc.returncode, 0) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_int_max_str_digits(self): code = "import sys; print(sys.flags.int_max_str_digits, sys.get_int_max_str_digits())" From 700f2b9c12862a11d5c6b0b53c095565437b4ccc Mon Sep 17 00:00:00 2001 From: NakanoMiku Date: Thu, 30 Nov 2023 19:49:27 +0800 Subject: [PATCH 03/23] Update test_cmd_line_script.py from CPython v3.12.0 --- Lib/test/test_cmd_line_script.py | 35 ++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py index b258edca19..9f368d8069 100644 --- a/Lib/test/test_cmd_line_script.py +++ b/Lib/test/test_cmd_line_script.py @@ -662,9 +662,9 @@ def test_syntaxerror_multi_line_fstring(self): self.assertEqual( stderr.splitlines()[-3:], [ - b' foo"""', - b' ^', - b'SyntaxError: f-string: empty expression not allowed', + b' foo = f"""{}', + b' ^', + b'SyntaxError: f-string: valid expression required before \'}\'', ], ) @@ -685,8 +685,31 @@ def test_syntaxerror_invalid_escape_sequence_multi_line(self): ], ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + def test_syntaxerror_null_bytes(self): + script = "x = '\0' nothing to see here\n';import os;os.system('echo pwnd')\n" + with os_helper.temp_dir() as script_dir: + script_name = _make_test_script(script_dir, 'script', script) + exitcode, stdout, stderr = assert_python_failure(script_name) + self.assertEqual( + stderr.splitlines()[-2:], + [ b" x = '", + b'SyntaxError: source code cannot contain null bytes' + ], + ) + + def test_syntaxerror_null_bytes_in_multiline_string(self): + scripts = ["\n'''\nmultilinestring\0\n'''", "\nf'''\nmultilinestring\0\n'''"] # Both normal and f-strings + with os_helper.temp_dir() as script_dir: + for script in scripts: + script_name = _make_test_script(script_dir, 'script', script) + _, _, stderr = assert_python_failure(script_name) + self.assertEqual( + stderr.splitlines()[-2:], + [ b" multilinestring", + b'SyntaxError: source code cannot contain null bytes' + ] + ) + def test_consistent_sys_path_for_direct_execution(self): # This test case ensures that the following all give the same # sys.path configuration: @@ -785,7 +808,7 @@ def test_script_as_dev_fd(self): with os_helper.temp_dir() as work_dir: script_name = _make_test_script(work_dir, 'script.py', script) with open(script_name, "r") as fp: - p = spawn_python(f"/dev/fd/{fp.fileno()}", close_fds=False, pass_fds=(0,1,2,fp.fileno())) + p = spawn_python(f"/dev/fd/{fp.fileno()}", close_fds=True, pass_fds=(0,1,2,fp.fileno())) out, err = p.communicate() self.assertEqual(out, b"12345678912345678912345\n") From 9a61716f74ea990a60c3ebad6addeb421795d163 Mon Sep 17 00:00:00 2001 From: NakanoMiku Date: Thu, 30 Nov 2023 19:52:25 +0800 Subject: [PATCH 04/23] Edit test_cmd_line_script.py --- Lib/test/test_cmd_line_script.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py index 9f368d8069..e40069d780 100644 --- a/Lib/test/test_cmd_line_script.py +++ b/Lib/test/test_cmd_line_script.py @@ -685,6 +685,8 @@ def test_syntaxerror_invalid_escape_sequence_multi_line(self): ], ) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_syntaxerror_null_bytes(self): script = "x = '\0' nothing to see here\n';import os;os.system('echo pwnd')\n" with os_helper.temp_dir() as script_dir: @@ -697,6 +699,8 @@ def test_syntaxerror_null_bytes(self): ], ) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_syntaxerror_null_bytes_in_multiline_string(self): scripts = ["\n'''\nmultilinestring\0\n'''", "\nf'''\nmultilinestring\0\n'''"] # Both normal and f-strings with os_helper.temp_dir() as script_dir: @@ -710,6 +714,8 @@ def test_syntaxerror_null_bytes_in_multiline_string(self): ] ) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_consistent_sys_path_for_direct_execution(self): # This test case ensures that the following all give the same # sys.path configuration: From c28cca97d1483f0100db8349ab2d17faacffe73f Mon Sep 17 00:00:00 2001 From: NakanoMiku Date: Thu, 30 Nov 2023 19:53:37 +0800 Subject: [PATCH 05/23] Update test_code_module.py from CPython v3.12.0 --- Lib/test/test_code_module.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_code_module.py b/Lib/test/test_code_module.py index 2b379fc378..5ac17ef16e 100644 --- a/Lib/test/test_code_module.py +++ b/Lib/test/test_code_module.py @@ -6,6 +6,7 @@ from unittest import mock from test.support import import_helper + code = import_helper.import_module('code') From c5364ca1573b5a02d1041ace5bb0695bd79bb137 Mon Sep 17 00:00:00 2001 From: NakanoMiku Date: Thu, 30 Nov 2023 20:01:48 +0800 Subject: [PATCH 06/23] Update test_compare.py from CPython v3.12.0 --- Lib/test/test_compare.py | 426 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 409 insertions(+), 17 deletions(-) diff --git a/Lib/test/test_compare.py b/Lib/test/test_compare.py index 2b3faed796..8166b0eea3 100644 --- a/Lib/test/test_compare.py +++ b/Lib/test/test_compare.py @@ -1,21 +1,27 @@ +"""Test equality and order comparisons.""" import unittest from test.support import ALWAYS_EQ +from fractions import Fraction +from decimal import Decimal -class Empty: - def __repr__(self): - return '' -class Cmp: - def __init__(self,arg): - self.arg = arg +class ComparisonSimpleTest(unittest.TestCase): + """Test equality and order comparisons for some simple cases.""" - def __repr__(self): - return '' % self.arg + class Empty: + def __repr__(self): + return '' - def __eq__(self, other): - return self.arg == other + class Cmp: + def __init__(self, arg): + self.arg = arg + + def __repr__(self): + return '' % self.arg + + def __eq__(self, other): + return self.arg == other -class ComparisonTest(unittest.TestCase): set1 = [2, 2.0, 2, 2+0j, Cmp(2.0)] set2 = [[1], (3,), None, Empty()] candidates = set1 + set2 @@ -32,16 +38,15 @@ def test_id_comparisons(self): # Ensure default comparison compares id() of args L = [] for i in range(10): - L.insert(len(L)//2, Empty()) + L.insert(len(L)//2, self.Empty()) for a in L: for b in L: - self.assertEqual(a == b, id(a) == id(b), - 'a=%r, b=%r' % (a, b)) + self.assertEqual(a == b, a is b, 'a=%r, b=%r' % (a, b)) def test_ne_defaults_to_not_eq(self): - a = Cmp(1) - b = Cmp(1) - c = Cmp(2) + a = self.Cmp(1) + b = self.Cmp(1) + c = self.Cmp(2) self.assertIs(a == b, True) self.assertIs(a != b, False) self.assertIs(a != c, True) @@ -114,5 +119,392 @@ def test_issue_1393(self): self.assertEqual(ALWAYS_EQ, y) +class ComparisonFullTest(unittest.TestCase): + """Test equality and ordering comparisons for built-in types and + user-defined classes that implement relevant combinations of rich + comparison methods. + """ + + class CompBase: + """Base class for classes with rich comparison methods. + + The "x" attribute should be set to an underlying value to compare. + + Derived classes have a "meth" tuple attribute listing names of + comparison methods implemented. See assert_total_order(). + """ + + # Class without any rich comparison methods. + class CompNone(CompBase): + meth = () + + # Classes with all combinations of value-based equality comparison methods. + class CompEq(CompBase): + meth = ("eq",) + def __eq__(self, other): + return self.x == other.x + + class CompNe(CompBase): + meth = ("ne",) + def __ne__(self, other): + return self.x != other.x + + class CompEqNe(CompBase): + meth = ("eq", "ne") + def __eq__(self, other): + return self.x == other.x + def __ne__(self, other): + return self.x != other.x + + # Classes with all combinations of value-based less/greater-than order + # comparison methods. + class CompLt(CompBase): + meth = ("lt",) + def __lt__(self, other): + return self.x < other.x + + class CompGt(CompBase): + meth = ("gt",) + def __gt__(self, other): + return self.x > other.x + + class CompLtGt(CompBase): + meth = ("lt", "gt") + def __lt__(self, other): + return self.x < other.x + def __gt__(self, other): + return self.x > other.x + + # Classes with all combinations of value-based less/greater-or-equal-than + # order comparison methods + class CompLe(CompBase): + meth = ("le",) + def __le__(self, other): + return self.x <= other.x + + class CompGe(CompBase): + meth = ("ge",) + def __ge__(self, other): + return self.x >= other.x + + class CompLeGe(CompBase): + meth = ("le", "ge") + def __le__(self, other): + return self.x <= other.x + def __ge__(self, other): + return self.x >= other.x + + # It should be sufficient to combine the comparison methods only within + # each group. + all_comp_classes = ( + CompNone, + CompEq, CompNe, CompEqNe, # equal group + CompLt, CompGt, CompLtGt, # less/greater-than group + CompLe, CompGe, CompLeGe) # less/greater-or-equal group + + def create_sorted_instances(self, class_, values): + """Create objects of type `class_` and return them in a list. + + `values` is a list of values that determines the value of data + attribute `x` of each object. + + Objects in the returned list are sorted by their identity. They + assigned values in `values` list order. By assign decreasing + values to objects with increasing identities, testcases can assert + that order comparison is performed by value and not by identity. + """ + + instances = [class_() for __ in range(len(values))] + instances.sort(key=id) + # Assign the provided values to the instances. + for inst, value in zip(instances, values): + inst.x = value + return instances + + def assert_equality_only(self, a, b, equal): + """Assert equality result and that ordering is not implemented. + + a, b: Instances to be tested (of same or different type). + equal: Boolean indicating the expected equality comparison results. + """ + self.assertEqual(a == b, equal) + self.assertEqual(b == a, equal) + self.assertEqual(a != b, not equal) + self.assertEqual(b != a, not equal) + with self.assertRaisesRegex(TypeError, "not supported"): + a < b + with self.assertRaisesRegex(TypeError, "not supported"): + a <= b + with self.assertRaisesRegex(TypeError, "not supported"): + a > b + with self.assertRaisesRegex(TypeError, "not supported"): + a >= b + with self.assertRaisesRegex(TypeError, "not supported"): + b < a + with self.assertRaisesRegex(TypeError, "not supported"): + b <= a + with self.assertRaisesRegex(TypeError, "not supported"): + b > a + with self.assertRaisesRegex(TypeError, "not supported"): + b >= a + + def assert_total_order(self, a, b, comp, a_meth=None, b_meth=None): + """Test total ordering comparison of two instances. + + a, b: Instances to be tested (of same or different type). + + comp: -1, 0, or 1 indicates that the expected order comparison + result for operations that are supported by the classes is + a <, ==, or > b. + + a_meth, b_meth: Either None, indicating that all rich comparison + methods are available, aa for builtins, or the tuple (subset) + of "eq", "ne", "lt", "le", "gt", and "ge" that are available + for the corresponding instance (of a user-defined class). + """ + self.assert_eq_subtest(a, b, comp, a_meth, b_meth) + self.assert_ne_subtest(a, b, comp, a_meth, b_meth) + self.assert_lt_subtest(a, b, comp, a_meth, b_meth) + self.assert_le_subtest(a, b, comp, a_meth, b_meth) + self.assert_gt_subtest(a, b, comp, a_meth, b_meth) + self.assert_ge_subtest(a, b, comp, a_meth, b_meth) + + # The body of each subtest has form: + # + # if value-based comparison methods: + # expect what the testcase defined for a op b and b rop a; + # else: no value-based comparison + # expect default behavior of object for a op b and b rop a. + + def assert_eq_subtest(self, a, b, comp, a_meth, b_meth): + if a_meth is None or "eq" in a_meth or "eq" in b_meth: + self.assertEqual(a == b, comp == 0) + self.assertEqual(b == a, comp == 0) + else: + self.assertEqual(a == b, a is b) + self.assertEqual(b == a, a is b) + + def assert_ne_subtest(self, a, b, comp, a_meth, b_meth): + if a_meth is None or not {"ne", "eq"}.isdisjoint(a_meth + b_meth): + self.assertEqual(a != b, comp != 0) + self.assertEqual(b != a, comp != 0) + else: + self.assertEqual(a != b, a is not b) + self.assertEqual(b != a, a is not b) + + def assert_lt_subtest(self, a, b, comp, a_meth, b_meth): + if a_meth is None or "lt" in a_meth or "gt" in b_meth: + self.assertEqual(a < b, comp < 0) + self.assertEqual(b > a, comp < 0) + else: + with self.assertRaisesRegex(TypeError, "not supported"): + a < b + with self.assertRaisesRegex(TypeError, "not supported"): + b > a + + def assert_le_subtest(self, a, b, comp, a_meth, b_meth): + if a_meth is None or "le" in a_meth or "ge" in b_meth: + self.assertEqual(a <= b, comp <= 0) + self.assertEqual(b >= a, comp <= 0) + else: + with self.assertRaisesRegex(TypeError, "not supported"): + a <= b + with self.assertRaisesRegex(TypeError, "not supported"): + b >= a + + def assert_gt_subtest(self, a, b, comp, a_meth, b_meth): + if a_meth is None or "gt" in a_meth or "lt" in b_meth: + self.assertEqual(a > b, comp > 0) + self.assertEqual(b < a, comp > 0) + else: + with self.assertRaisesRegex(TypeError, "not supported"): + a > b + with self.assertRaisesRegex(TypeError, "not supported"): + b < a + + def assert_ge_subtest(self, a, b, comp, a_meth, b_meth): + if a_meth is None or "ge" in a_meth or "le" in b_meth: + self.assertEqual(a >= b, comp >= 0) + self.assertEqual(b <= a, comp >= 0) + else: + with self.assertRaisesRegex(TypeError, "not supported"): + a >= b + with self.assertRaisesRegex(TypeError, "not supported"): + b <= a + + def test_objects(self): + """Compare instances of type 'object'.""" + a = object() + b = object() + self.assert_equality_only(a, a, True) + self.assert_equality_only(a, b, False) + + def test_comp_classes_same(self): + """Compare same-class instances with comparison methods.""" + + for cls in self.all_comp_classes: + with self.subTest(cls): + instances = self.create_sorted_instances(cls, (1, 2, 1)) + + # Same object. + self.assert_total_order(instances[0], instances[0], 0, + cls.meth, cls.meth) + + # Different objects, same value. + self.assert_total_order(instances[0], instances[2], 0, + cls.meth, cls.meth) + + # Different objects, value ascending for ascending identities. + self.assert_total_order(instances[0], instances[1], -1, + cls.meth, cls.meth) + + # different objects, value descending for ascending identities. + # This is the interesting case to assert that order comparison + # is performed based on the value and not based on the identity. + self.assert_total_order(instances[1], instances[2], +1, + cls.meth, cls.meth) + + def test_comp_classes_different(self): + """Compare different-class instances with comparison methods.""" + + for cls_a in self.all_comp_classes: + for cls_b in self.all_comp_classes: + with self.subTest(a=cls_a, b=cls_b): + a1 = cls_a() + a1.x = 1 + b1 = cls_b() + b1.x = 1 + b2 = cls_b() + b2.x = 2 + + self.assert_total_order( + a1, b1, 0, cls_a.meth, cls_b.meth) + self.assert_total_order( + a1, b2, -1, cls_a.meth, cls_b.meth) + + def test_str_subclass(self): + """Compare instances of str and a subclass.""" + class StrSubclass(str): + pass + + s1 = str("a") + s2 = str("b") + c1 = StrSubclass("a") + c2 = StrSubclass("b") + c3 = StrSubclass("b") + + self.assert_total_order(s1, s1, 0) + self.assert_total_order(s1, s2, -1) + self.assert_total_order(c1, c1, 0) + self.assert_total_order(c1, c2, -1) + self.assert_total_order(c2, c3, 0) + + self.assert_total_order(s1, c2, -1) + self.assert_total_order(s2, c3, 0) + self.assert_total_order(c1, s2, -1) + self.assert_total_order(c2, s2, 0) + + def test_numbers(self): + """Compare number types.""" + + # Same types. + i1 = 1001 + i2 = 1002 + self.assert_total_order(i1, i1, 0) + self.assert_total_order(i1, i2, -1) + + f1 = 1001.0 + f2 = 1001.1 + self.assert_total_order(f1, f1, 0) + self.assert_total_order(f1, f2, -1) + + q1 = Fraction(2002, 2) + q2 = Fraction(2003, 2) + self.assert_total_order(q1, q1, 0) + self.assert_total_order(q1, q2, -1) + + d1 = Decimal('1001.0') + d2 = Decimal('1001.1') + self.assert_total_order(d1, d1, 0) + self.assert_total_order(d1, d2, -1) + + c1 = 1001+0j + c2 = 1001+1j + self.assert_equality_only(c1, c1, True) + self.assert_equality_only(c1, c2, False) + + + # Mixing types. + for n1, n2 in ((i1,f1), (i1,q1), (i1,d1), (f1,q1), (f1,d1), (q1,d1)): + self.assert_total_order(n1, n2, 0) + for n1 in (i1, f1, q1, d1): + self.assert_equality_only(n1, c1, True) + + def test_sequences(self): + """Compare list, tuple, and range.""" + l1 = [1, 2] + l2 = [2, 3] + self.assert_total_order(l1, l1, 0) + self.assert_total_order(l1, l2, -1) + + t1 = (1, 2) + t2 = (2, 3) + self.assert_total_order(t1, t1, 0) + self.assert_total_order(t1, t2, -1) + + r1 = range(1, 2) + r2 = range(2, 2) + self.assert_equality_only(r1, r1, True) + self.assert_equality_only(r1, r2, False) + + self.assert_equality_only(t1, l1, False) + self.assert_equality_only(l1, r1, False) + self.assert_equality_only(r1, t1, False) + + def test_bytes(self): + """Compare bytes and bytearray.""" + bs1 = b'a1' + bs2 = b'b2' + self.assert_total_order(bs1, bs1, 0) + self.assert_total_order(bs1, bs2, -1) + + ba1 = bytearray(b'a1') + ba2 = bytearray(b'b2') + self.assert_total_order(ba1, ba1, 0) + self.assert_total_order(ba1, ba2, -1) + + self.assert_total_order(bs1, ba1, 0) + self.assert_total_order(bs1, ba2, -1) + self.assert_total_order(ba1, bs1, 0) + self.assert_total_order(ba1, bs2, -1) + + def test_sets(self): + """Compare set and frozenset.""" + s1 = {1, 2} + s2 = {1, 2, 3} + self.assert_total_order(s1, s1, 0) + self.assert_total_order(s1, s2, -1) + + f1 = frozenset(s1) + f2 = frozenset(s2) + self.assert_total_order(f1, f1, 0) + self.assert_total_order(f1, f2, -1) + + self.assert_total_order(s1, f1, 0) + self.assert_total_order(s1, f2, -1) + self.assert_total_order(f1, s1, 0) + self.assert_total_order(f1, s2, -1) + + def test_mappings(self): + """ Compare dict. + """ + d1 = {1: "a", 2: "b"} + d2 = {2: "b", 3: "c"} + d3 = {3: "c", 2: "b"} + self.assert_equality_only(d1, d1, True) + self.assert_equality_only(d1, d2, False) + self.assert_equality_only(d2, d3, True) + + if __name__ == '__main__': unittest.main() From fa3eae677d84518139bfe2fb2f517f347c9640e3 Mon Sep 17 00:00:00 2001 From: NakanoMiku Date: Thu, 30 Nov 2023 20:06:24 +0800 Subject: [PATCH 07/23] Update test_complex.py from CPython v3.12.0 --- Lib/test/test_complex.py | 56 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index fd6e6de5fc..23d0e3bc81 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -109,6 +109,8 @@ def test_truediv(self): complex(random(), random())) self.assertAlmostEqual(complex.__truediv__(2+0j, 1+1j), 1-1j) + self.assertRaises(TypeError, operator.truediv, 1j, None) + self.assertRaises(TypeError, operator.truediv, None, 1j) for denom_real, denom_imag in [(0, NAN), (NAN, 0), (NAN, NAN)]: z = complex(0, 0) / complex(denom_real, denom_imag) @@ -140,6 +142,7 @@ def test_floordiv_zero_division(self): def test_richcompare(self): self.assertIs(complex.__eq__(1+1j, 1<<10000), False) self.assertIs(complex.__lt__(1+1j, None), NotImplemented) + self.assertIs(complex.__eq__(1+1j, None), NotImplemented) self.assertIs(complex.__eq__(1+1j, 1+1j), True) self.assertIs(complex.__eq__(1+1j, 2+2j), False) self.assertIs(complex.__ne__(1+1j, 1+1j), False) @@ -162,6 +165,7 @@ def test_richcompare(self): self.assertIs(operator.eq(1+1j, 2+2j), False) self.assertIs(operator.ne(1+1j, 1+1j), False) self.assertIs(operator.ne(1+1j, 2+2j), True) + self.assertIs(operator.eq(1+1j, 2.0), False) # TODO: RUSTPYTHON @unittest.expectedFailure @@ -182,6 +186,27 @@ def check(n, deltas, is_equal, imag = 0.0): check(2 ** pow, range(1, 101), lambda delta: False, float(i)) check(2 ** 53, range(-100, 0), lambda delta: True) + def test_add(self): + self.assertEqual(1j + int(+1), complex(+1, 1)) + self.assertEqual(1j + int(-1), complex(-1, 1)) + self.assertRaises(OverflowError, operator.add, 1j, 10**1000) + self.assertRaises(TypeError, operator.add, 1j, None) + self.assertRaises(TypeError, operator.add, None, 1j) + + def test_sub(self): + self.assertEqual(1j - int(+1), complex(-1, 1)) + self.assertEqual(1j - int(-1), complex(1, 1)) + self.assertRaises(OverflowError, operator.sub, 1j, 10**1000) + self.assertRaises(TypeError, operator.sub, 1j, None) + self.assertRaises(TypeError, operator.sub, None, 1j) + + def test_mul(self): + self.assertEqual(1j * int(20), complex(0, 20)) + self.assertEqual(1j * int(-1), complex(0, -1)) + self.assertRaises(OverflowError, operator.mul, 1j, 10**1000) + self.assertRaises(TypeError, operator.mul, 1j, None) + self.assertRaises(TypeError, operator.mul, None, 1j) + def test_mod(self): # % is no longer supported on complex numbers with self.assertRaises(TypeError): @@ -214,11 +239,18 @@ def test_divmod_zero_division(self): def test_pow(self): self.assertAlmostEqual(pow(1+1j, 0+0j), 1.0) self.assertAlmostEqual(pow(0+0j, 2+0j), 0.0) + self.assertEqual(pow(0+0j, 2000+0j), 0.0) + self.assertEqual(pow(0, 0+0j), 1.0) + self.assertEqual(pow(-1, 0+0j), 1.0) self.assertRaises(ZeroDivisionError, pow, 0+0j, 1j) + self.assertRaises(ZeroDivisionError, pow, 0+0j, -1000) self.assertAlmostEqual(pow(1j, -1), 1/1j) self.assertAlmostEqual(pow(1j, 200), 1) self.assertRaises(ValueError, pow, 1+1j, 1+1j, 1+1j) self.assertRaises(OverflowError, pow, 1e200+1j, 1e200+1j) + self.assertRaises(TypeError, pow, 1j, None) + self.assertRaises(TypeError, pow, None, 1j) + self.assertAlmostEqual(pow(1j, 0.5), 0.7071067811865476+0.7071067811865475j) a = 3.33+4.43j self.assertEqual(a ** 0j, 1) @@ -303,6 +335,7 @@ def test_boolcontext(self): for i in range(100): self.assertTrue(complex(random() + 1e-6, random() + 1e-6)) self.assertTrue(not complex(0.0, 0.0)) + self.assertTrue(1j) def test_conjugate(self): self.assertClose(complex(5.3, 9.8).conjugate(), 5.3-9.8j) @@ -318,6 +351,8 @@ def __complex__(self): return self.value self.assertRaises(TypeError, complex, {}) self.assertRaises(TypeError, complex, NS(1.5)) self.assertRaises(TypeError, complex, NS(1)) + self.assertRaises(TypeError, complex, object()) + self.assertRaises(TypeError, complex, NS(4.25+0.5j), object()) self.assertAlmostEqual(complex("1+10j"), 1+10j) self.assertAlmostEqual(complex(10), 10+0j) @@ -363,6 +398,8 @@ def __complex__(self): return self.value self.assertAlmostEqual(complex('1e-500'), 0.0 + 0.0j) self.assertAlmostEqual(complex('-1e-500j'), 0.0 - 0.0j) self.assertAlmostEqual(complex('-1e-500+1e-500j'), -0.0 + 0.0j) + self.assertEqual(complex('1-1j'), 1.0 - 1j) + self.assertEqual(complex('1J'), 1j) class complex2(complex): pass self.assertAlmostEqual(complex(complex2(1+1j)), 1+1j) @@ -533,8 +570,12 @@ class complex2(complex): self.assertFloatsAreIdentical(z.real, x) self.assertFloatsAreIdentical(z.imag, y) - # TODO: RUSTPYTHON - @unittest.expectedFailure + def test_constructor_negative_nans_from_string(self): + self.assertEqual(copysign(1., complex("-nan").real), -1.) + self.assertEqual(copysign(1., complex("-nanj").imag), -1.) + self.assertEqual(copysign(1., complex("-nan-nanj").real), -1.) + self.assertEqual(copysign(1., complex("-nan-nanj").imag), -1.) + def test_underscores(self): # check underscores for lit in VALID_UNDERSCORE_LITERALS: @@ -553,6 +594,8 @@ def test_hash(self): x /= 3.0 # now check against floating point self.assertEqual(hash(x), hash(complex(x, 0.))) + self.assertNotEqual(hash(2000005 - 1j), -1) + def test_abs(self): nums = [complex(x/3., y/7.) for x in range(-9,9) for y in range(-9,9)] for num in nums: @@ -575,6 +618,7 @@ def test(v, expected, test_fn=self.assertEqual): test(complex(NAN, 1), "(nan+1j)") test(complex(1, NAN), "(1+nanj)") test(complex(NAN, NAN), "(nan+nanj)") + test(complex(-NAN, -NAN), "(nan+nanj)") test(complex(0, INF), "infj") test(complex(0, -INF), "-infj") @@ -601,6 +645,14 @@ def test(v, expected, test_fn=self.assertEqual): test(complex(-0., 0.), "(-0+0j)") test(complex(-0., -0.), "(-0-0j)") + def test_pos(self): + class ComplexSubclass(complex): + pass + + self.assertEqual(+(1+6j), 1+6j) + self.assertEqual(+ComplexSubclass(1, 6), 1+6j) + self.assertIs(type(+ComplexSubclass(1, 6)), complex) + def test_neg(self): self.assertEqual(-(1+6j), -1-6j) From 07f013dae2b1d0cc7d45dc5c1d49f9d6f594a13d Mon Sep 17 00:00:00 2001 From: NakanoMiku Date: Thu, 30 Nov 2023 20:07:44 +0800 Subject: [PATCH 08/23] Edit test_complex.py ExpectedFailures added --- Lib/test/test_complex.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index 23d0e3bc81..72999c2f7d 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -236,6 +236,8 @@ def test_divmod_zero_division(self): for a, b in ZERO_DIVISION: self.assertRaises(TypeError, divmod, a, b) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_pow(self): self.assertAlmostEqual(pow(1+1j, 0+0j), 1.0) self.assertAlmostEqual(pow(0+0j, 2+0j), 0.0) @@ -570,12 +572,16 @@ class complex2(complex): self.assertFloatsAreIdentical(z.real, x) self.assertFloatsAreIdentical(z.imag, y) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_constructor_negative_nans_from_string(self): self.assertEqual(copysign(1., complex("-nan").real), -1.) self.assertEqual(copysign(1., complex("-nanj").imag), -1.) self.assertEqual(copysign(1., complex("-nan-nanj").real), -1.) self.assertEqual(copysign(1., complex("-nan-nanj").imag), -1.) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_underscores(self): # check underscores for lit in VALID_UNDERSCORE_LITERALS: From b93199f007f7a2dbaac2fa58b53da0b62bcd6420 Mon Sep 17 00:00:00 2001 From: NakanoMiku Date: Thu, 30 Nov 2023 20:11:24 +0800 Subject: [PATCH 09/23] Update test_context.py from CPython v3.12.0 --- Lib/test/test_context.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_context.py b/Lib/test/test_context.py index 5d62fa0405..01b3399327 100644 --- a/Lib/test/test_context.py +++ b/Lib/test/test_context.py @@ -6,6 +6,7 @@ import time import unittest import weakref +from test import support from test.support import threading_helper try: @@ -608,6 +609,7 @@ def test_hamt_collision_3(self): self.assertEqual({k.name for k in h.keys()}, {'C', 'D', 'E'}) + @support.requires_resource('cpu') def test_hamt_stress(self): COLLECTION_SIZE = 7000 TEST_ITERS_EVERY = 647 From 5c6b4cbd3c13530f17dd42a7791dec45aab4f8a2 Mon Sep 17 00:00:00 2001 From: NakanoMiku Date: Thu, 30 Nov 2023 20:27:09 +0800 Subject: [PATCH 10/23] Update test_decorators.py from CPython v3.12.0 --- Lib/test/test_decorators.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_decorators.py b/Lib/test/test_decorators.py index 39b8dc0fac..739c9b3909 100644 --- a/Lib/test/test_decorators.py +++ b/Lib/test/test_decorators.py @@ -1,4 +1,3 @@ -from test import support import unittest from types import MethodType From 926c4cef27a51f2becbb31bb5dba7ad18ab35c38 Mon Sep 17 00:00:00 2001 From: NakanoMiku Date: Thu, 30 Nov 2023 20:27:46 +0800 Subject: [PATCH 11/23] Update test_defaultdict.py from CPython v3.12.0 --- Lib/test/test_defaultdict.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/test/test_defaultdict.py b/Lib/test/test_defaultdict.py index 68fc449780..bdbe9b81e8 100644 --- a/Lib/test/test_defaultdict.py +++ b/Lib/test/test_defaultdict.py @@ -1,9 +1,7 @@ """Unit tests for collections.defaultdict.""" -import os import copy import pickle -import tempfile import unittest from collections import defaultdict From b05d5920ab7b9f847272de5a76fca626efa1c418 Mon Sep 17 00:00:00 2001 From: NakanoMiku Date: Thu, 30 Nov 2023 20:40:19 +0800 Subject: [PATCH 12/23] Update test_descr.py from CPython v3.12.0 --- Lib/test/test_descr.py | 58 +++++++++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 9e74541aac..bff14e8601 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -21,6 +21,11 @@ except ImportError: _testcapi = None +try: + import xxsubtype +except ImportError: + xxsubtype = None + class OperatorsTest(unittest.TestCase): @@ -299,6 +304,7 @@ def test_explicit_reverse_methods(self): self.assertEqual(float.__rsub__(3.0, 1), -2.0) @support.impl_detail("the module 'xxsubtype' is internal") + @unittest.skipIf(xxsubtype is None, "requires xxsubtype module") def test_spam_lists(self): # Testing spamlist operations... import copy, xxsubtype as spam @@ -343,6 +349,7 @@ def foo(self): return 1 self.assertEqual(a.getstate(), 42) @support.impl_detail("the module 'xxsubtype' is internal") + @unittest.skipIf(xxsubtype is None, "requires xxsubtype module") def test_spam_dicts(self): # Testing spamdict operations... import copy, xxsubtype as spam @@ -426,7 +433,7 @@ def __init__(self_local, *a, **kw): def __getitem__(self, key): return self.get(key, 0) def __setitem__(self_local, key, value): - self.assertIsInstance(key, type(0)) + self.assertIsInstance(key, int) dict.__setitem__(self_local, key, value) def setstate(self, state): self.state = state @@ -842,7 +849,7 @@ def __delattr__(self, name): ("getattr", "foo"), ("delattr", "foo")]) - # http://python.org/sf/1174712 + # https://bugs.python.org/issue1174712 try: class Module(types.ModuleType, str): pass @@ -875,7 +882,7 @@ def setstate(self, state): self.assertEqual(a.getstate(), 10) class D(dict, C): def __init__(self): - type({}).__init__(self) + dict.__init__(self) C.__init__(self) d = D() self.assertEqual(list(d.keys()), []) @@ -1627,6 +1634,7 @@ def test_refleaks_in_classmethod___init__(self): self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10) @support.impl_detail("the module 'xxsubtype' is internal") + @unittest.skipIf(xxsubtype is None, "requires xxsubtype module") def test_classmethods_in_c(self): # Testing C-based class methods... import xxsubtype as spam @@ -1712,6 +1720,7 @@ def test_refleaks_in_staticmethod___init__(self): self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10) @support.impl_detail("the module 'xxsubtype' is internal") + @unittest.skipIf(xxsubtype is None, "requires xxsubtype module") def test_staticmethods_in_c(self): # Testing C-based static methods... import xxsubtype as spam @@ -1848,9 +1857,7 @@ def __init__(self, foo): object.__init__(A(3)) self.assertRaises(TypeError, object.__init__, A(3), 5) - # TODO: RUSTPYTHON, CPython 3.5 and above expect this test case to fail, but in RustPython this currently passes. - # See https://github.com/python/cpython/issues/49572 for more details. - # @unittest.expectedFailure + @unittest.expectedFailure def test_restored_object_new(self): class A(object): def __new__(cls, *args, **kwargs): @@ -2006,7 +2013,7 @@ def __getattr__(self, attr): ns = {} exec(code, ns) number_attrs = ns["number_attrs"] - # Warm up the the function for quickening (PEP 659) + # Warm up the function for quickening (PEP 659) for _ in range(30): self.assertEqual(number_attrs(Numbers()), list(range(280))) @@ -3298,12 +3305,8 @@ def __get__(self, object, otype): if otype: otype = otype.__name__ return 'object=%s; type=%s' % (object, otype) - class OldClass: - __doc__ = DocDescr() - class NewClass(object): + class NewClass: __doc__ = DocDescr() - self.assertEqual(OldClass.__doc__, 'object=None; type=OldClass') - self.assertEqual(OldClass().__doc__, 'object=OldClass instance; type=OldClass') self.assertEqual(NewClass.__doc__, 'object=None; type=NewClass') self.assertEqual(NewClass().__doc__, 'object=NewClass instance; type=NewClass') @@ -3345,7 +3348,7 @@ class Int(int): __slots__ = [] cant(True, int) cant(2, bool) o = object() - cant(o, type(1)) + cant(o, int) cant(o, type(None)) del o class G(object): @@ -3622,7 +3625,6 @@ class MyInt(int): def test_str_of_str_subclass(self): # Testing __str__ defined in subclass of str ... import binascii - import io class octetstring(str): def __str__(self): @@ -4528,7 +4530,7 @@ class Oops(object): o.whatever = Provoker(o) del o - @unittest.skip("TODO: RUSTPYTHON, rustpython segmentation fault") + @support.requires_resource('cpu') def test_wrapper_segfault(self): # SF 927248: deeply nested wrappers could cause stack overflow f = lambda:None @@ -5095,6 +5097,32 @@ class Child(Parent): gc.collect() self.assertEqual(Parent.__subclasses__(), []) + def test_attr_raise_through_property(self): + # test case for gh-103272 + class A: + def __getattr__(self, name): + raise ValueError("FOO") + + @property + def foo(self): + return self.__getattr__("asdf") + + with self.assertRaisesRegex(ValueError, "FOO"): + A().foo + + # test case for gh-103551 + class B: + @property + def __getattr__(self, name): + raise ValueError("FOO") + + @property + def foo(self): + raise NotImplementedError("BAR") + + with self.assertRaisesRegex(NotImplementedError, "BAR"): + B().foo + class DictProxyTests(unittest.TestCase): def setUp(self): From b11d554c11370ad6e836a916e08c9a1942ca59b4 Mon Sep 17 00:00:00 2001 From: NakanoMiku Date: Thu, 30 Nov 2023 20:45:05 +0800 Subject: [PATCH 13/23] Edit test_descr.py Skip test at line 1861 ExpectedFailure at line 5104 --- Lib/test/test_descr.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index bff14e8601..e634458e00 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -1858,6 +1858,7 @@ def __init__(self, foo): self.assertRaises(TypeError, object.__init__, A(3), 5) @unittest.expectedFailure + @unittest.skip("TODO: RUSTPYTHON") def test_restored_object_new(self): class A(object): def __new__(cls, *args, **kwargs): @@ -4529,7 +4530,8 @@ class Oops(object): o = Oops() o.whatever = Provoker(o) del o - + + @unittest.skip("TODO: RUSTPYTHON, rustpython segmentation fault") @support.requires_resource('cpu') def test_wrapper_segfault(self): # SF 927248: deeply nested wrappers could cause stack overflow @@ -5097,6 +5099,8 @@ class Child(Parent): gc.collect() self.assertEqual(Parent.__subclasses__(), []) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_attr_raise_through_property(self): # test case for gh-103272 class A: From c4e2d6e3790f7cbd866e331582d56b6ad40157ec Mon Sep 17 00:00:00 2001 From: NakanoMiku Date: Thu, 30 Nov 2023 20:46:29 +0800 Subject: [PATCH 14/23] Add test_descrtut.py from CPython v3.12.0 --- Lib/test/test_descrtut.py | 482 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 482 insertions(+) create mode 100644 Lib/test/test_descrtut.py diff --git a/Lib/test/test_descrtut.py b/Lib/test/test_descrtut.py new file mode 100644 index 0000000000..7796031ed0 --- /dev/null +++ b/Lib/test/test_descrtut.py @@ -0,0 +1,482 @@ +# This contains most of the executable examples from Guido's descr +# tutorial, once at +# +# https://www.python.org/download/releases/2.2.3/descrintro/ +# +# A few examples left implicit in the writeup were fleshed out, a few were +# skipped due to lack of interest (e.g., faking super() by hand isn't +# of much interest anymore), and a few were fiddled to make the output +# deterministic. + +from test.support import sortdict +import doctest +import unittest + + +class defaultdict(dict): + def __init__(self, default=None): + dict.__init__(self) + self.default = default + + def __getitem__(self, key): + try: + return dict.__getitem__(self, key) + except KeyError: + return self.default + + def get(self, key, *args): + if not args: + args = (self.default,) + return dict.get(self, key, *args) + + def merge(self, other): + for key in other: + if key not in self: + self[key] = other[key] + +test_1 = """ + +Here's the new type at work: + + >>> print(defaultdict) # show our type + + >>> print(type(defaultdict)) # its metatype + + >>> a = defaultdict(default=0.0) # create an instance + >>> print(a) # show the instance + {} + >>> print(type(a)) # show its type + + >>> print(a.__class__) # show its class + + >>> print(type(a) is a.__class__) # its type is its class + True + >>> a[1] = 3.25 # modify the instance + >>> print(a) # show the new value + {1: 3.25} + >>> print(a[1]) # show the new item + 3.25 + >>> print(a[0]) # a non-existent item + 0.0 + >>> a.merge({1:100, 2:200}) # use a dict method + >>> print(sortdict(a)) # show the result + {1: 3.25, 2: 200} + >>> + +We can also use the new type in contexts where classic only allows "real" +dictionaries, such as the locals/globals dictionaries for the exec +statement or the built-in function eval(): + + >>> print(sorted(a.keys())) + [1, 2] + >>> a['print'] = print # need the print function here + >>> exec("x = 3; print(x)", a) + 3 + >>> print(sorted(a.keys(), key=lambda x: (str(type(x)), x))) + [1, 2, '__builtins__', 'print', 'x'] + >>> print(a['x']) + 3 + >>> + +Now I'll show that defaultdict instances have dynamic instance variables, +just like classic classes: + + >>> a.default = -1 + >>> print(a["noway"]) + -1 + >>> a.default = -1000 + >>> print(a["noway"]) + -1000 + >>> 'default' in dir(a) + True + >>> a.x1 = 100 + >>> a.x2 = 200 + >>> print(a.x1) + 100 + >>> d = dir(a) + >>> 'default' in d and 'x1' in d and 'x2' in d + True + >>> print(sortdict(a.__dict__)) + {'default': -1000, 'x1': 100, 'x2': 200} + >>> +""" + +class defaultdict2(dict): + __slots__ = ['default'] + + def __init__(self, default=None): + dict.__init__(self) + self.default = default + + def __getitem__(self, key): + try: + return dict.__getitem__(self, key) + except KeyError: + return self.default + + def get(self, key, *args): + if not args: + args = (self.default,) + return dict.get(self, key, *args) + + def merge(self, other): + for key in other: + if key not in self: + self[key] = other[key] + +test_2 = """ + +The __slots__ declaration takes a list of instance variables, and reserves +space for exactly these in the instance. When __slots__ is used, other +instance variables cannot be assigned to: + + >>> a = defaultdict2(default=0.0) + >>> a[1] + 0.0 + >>> a.default = -1 + >>> a[1] + -1 + >>> a.x1 = 1 + Traceback (most recent call last): + File "", line 1, in ? + AttributeError: 'defaultdict2' object has no attribute 'x1' + >>> + +""" + +test_3 = """ + +Introspecting instances of built-in types + +For instance of built-in types, x.__class__ is now the same as type(x): + + >>> type([]) + + >>> [].__class__ + + >>> list + + >>> isinstance([], list) + True + >>> isinstance([], dict) + False + >>> isinstance([], object) + True + >>> + +You can get the information from the list type: + + >>> import pprint + >>> pprint.pprint(dir(list)) # like list.__dict__.keys(), but sorted + ['__add__', + '__class__', + '__class_getitem__', + '__contains__', + '__delattr__', + '__delitem__', + '__dir__', + '__doc__', + '__eq__', + '__format__', + '__ge__', + '__getattribute__', + '__getitem__', + '__getstate__', + '__gt__', + '__hash__', + '__iadd__', + '__imul__', + '__init__', + '__init_subclass__', + '__iter__', + '__le__', + '__len__', + '__lt__', + '__mul__', + '__ne__', + '__new__', + '__reduce__', + '__reduce_ex__', + '__repr__', + '__reversed__', + '__rmul__', + '__setattr__', + '__setitem__', + '__sizeof__', + '__str__', + '__subclasshook__', + 'append', + 'clear', + 'copy', + 'count', + 'extend', + 'index', + 'insert', + 'pop', + 'remove', + 'reverse', + 'sort'] + +The new introspection API gives more information than the old one: in +addition to the regular methods, it also shows the methods that are +normally invoked through special notations, e.g. __iadd__ (+=), __len__ +(len), __ne__ (!=). You can invoke any method from this list directly: + + >>> a = ['tic', 'tac'] + >>> list.__len__(a) # same as len(a) + 2 + >>> a.__len__() # ditto + 2 + >>> list.append(a, 'toe') # same as a.append('toe') + >>> a + ['tic', 'tac', 'toe'] + >>> + +This is just like it is for user-defined classes. +""" + +test_4 = """ + +Static methods and class methods + +The new introspection API makes it possible to add static methods and class +methods. Static methods are easy to describe: they behave pretty much like +static methods in C++ or Java. Here's an example: + + >>> class C: + ... + ... @staticmethod + ... def foo(x, y): + ... print("staticmethod", x, y) + + >>> C.foo(1, 2) + staticmethod 1 2 + >>> c = C() + >>> c.foo(1, 2) + staticmethod 1 2 + +Class methods use a similar pattern to declare methods that receive an +implicit first argument that is the *class* for which they are invoked. + + >>> class C: + ... @classmethod + ... def foo(cls, y): + ... print("classmethod", cls, y) + + >>> C.foo(1) + classmethod 1 + >>> c = C() + >>> c.foo(1) + classmethod 1 + + >>> class D(C): + ... pass + + >>> D.foo(1) + classmethod 1 + >>> d = D() + >>> d.foo(1) + classmethod 1 + +This prints "classmethod __main__.D 1" both times; in other words, the +class passed as the first argument of foo() is the class involved in the +call, not the class involved in the definition of foo(). + +But notice this: + + >>> class E(C): + ... @classmethod + ... def foo(cls, y): # override C.foo + ... print("E.foo() called") + ... C.foo(y) + + >>> E.foo(1) + E.foo() called + classmethod 1 + >>> e = E() + >>> e.foo(1) + E.foo() called + classmethod 1 + +In this example, the call to C.foo() from E.foo() will see class C as its +first argument, not class E. This is to be expected, since the call +specifies the class C. But it stresses the difference between these class +methods and methods defined in metaclasses (where an upcall to a metamethod +would pass the target class as an explicit first argument). +""" + +test_5 = """ + +Attributes defined by get/set methods + + + >>> class property(object): + ... + ... def __init__(self, get, set=None): + ... self.__get = get + ... self.__set = set + ... + ... def __get__(self, inst, type=None): + ... return self.__get(inst) + ... + ... def __set__(self, inst, value): + ... if self.__set is None: + ... raise AttributeError("this attribute is read-only") + ... return self.__set(inst, value) + +Now let's define a class with an attribute x defined by a pair of methods, +getx() and setx(): + + >>> class C(object): + ... + ... def __init__(self): + ... self.__x = 0 + ... + ... def getx(self): + ... return self.__x + ... + ... def setx(self, x): + ... if x < 0: x = 0 + ... self.__x = x + ... + ... x = property(getx, setx) + +Here's a small demonstration: + + >>> a = C() + >>> a.x = 10 + >>> print(a.x) + 10 + >>> a.x = -10 + >>> print(a.x) + 0 + >>> + +Hmm -- property is builtin now, so let's try it that way too. + + >>> del property # unmask the builtin + >>> property + + + >>> class C(object): + ... def __init__(self): + ... self.__x = 0 + ... def getx(self): + ... return self.__x + ... def setx(self, x): + ... if x < 0: x = 0 + ... self.__x = x + ... x = property(getx, setx) + + + >>> a = C() + >>> a.x = 10 + >>> print(a.x) + 10 + >>> a.x = -10 + >>> print(a.x) + 0 + >>> +""" + +test_6 = """ + +Method resolution order + +This example is implicit in the writeup. + +>>> class A: # implicit new-style class +... def save(self): +... print("called A.save()") +>>> class B(A): +... pass +>>> class C(A): +... def save(self): +... print("called C.save()") +>>> class D(B, C): +... pass + +>>> D().save() +called C.save() + +>>> class A(object): # explicit new-style class +... def save(self): +... print("called A.save()") +>>> class B(A): +... pass +>>> class C(A): +... def save(self): +... print("called C.save()") +>>> class D(B, C): +... pass + +>>> D().save() +called C.save() +""" + +class A(object): + def m(self): + return "A" + +class B(A): + def m(self): + return "B" + super(B, self).m() + +class C(A): + def m(self): + return "C" + super(C, self).m() + +class D(C, B): + def m(self): + return "D" + super(D, self).m() + + +test_7 = """ + +Cooperative methods and "super" + +>>> print(D().m()) # "DCBA" +DCBA +""" + +test_8 = """ + +Backwards incompatibilities + +>>> class A: +... def foo(self): +... print("called A.foo()") + +>>> class B(A): +... pass + +>>> class C(A): +... def foo(self): +... B.foo(self) + +>>> C().foo() +called A.foo() + +>>> class C(A): +... def foo(self): +... A.foo(self) +>>> C().foo() +called A.foo() +""" + +__test__ = {"tut1": test_1, + "tut2": test_2, + "tut3": test_3, + "tut4": test_4, + "tut5": test_5, + "tut6": test_6, + "tut7": test_7, + "tut8": test_8} + +def load_tests(loader, tests, pattern): + tests.addTest(doctest.DocTestSuite()) + return tests + + +if __name__ == "__main__": + unittest.main() From f1991c25bc5e802525492575042a8a1fcb1ec8da Mon Sep 17 00:00:00 2001 From: NakanoMiku Date: Thu, 30 Nov 2023 20:48:58 +0800 Subject: [PATCH 15/23] Edit test_descrtut.py ExpectedFailures at line 469-472 I'm not able to put a @unittest.expectedFailure for each tests so I commented them out --- Lib/test/test_descrtut.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_descrtut.py b/Lib/test/test_descrtut.py index 7796031ed0..4c128f770e 100644 --- a/Lib/test/test_descrtut.py +++ b/Lib/test/test_descrtut.py @@ -34,6 +34,7 @@ def merge(self, other): if key not in self: self[key] = other[key] + test_1 = """ Here's the new type at work: @@ -464,10 +465,11 @@ def m(self): called A.foo() """ -__test__ = {"tut1": test_1, - "tut2": test_2, - "tut3": test_3, - "tut4": test_4, +# TODO: RUSTPYTHON +__test__ = {# "tut1": test_1, + # "tut2": test_2, + # "tut3": test_3, + # "tut4": test_4, "tut5": test_5, "tut6": test_6, "tut7": test_7, From d5e4af7a4bf02accbb2d03c17dc03c47ff6a0265 Mon Sep 17 00:00:00 2001 From: NakanoMiku Date: Thu, 30 Nov 2023 20:51:40 +0800 Subject: [PATCH 16/23] Update test_dict.py from CPython v3.12.0 --- Lib/test/test_dict.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index 3ec0ac541b..4aa6f1089a 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -8,7 +8,7 @@ import unittest import weakref from test import support -from test.support import import_helper +from test.support import import_helper, C_RECURSION_LIMIT class DictTest(unittest.TestCase): @@ -599,7 +599,7 @@ def __repr__(self): @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON Windows') def test_repr_deep(self): d = {} - for i in range(sys.getrecursionlimit() + 100): + for i in range(C_RECURSION_LIMIT + 1): d = {1: d} self.assertRaises(RecursionError, repr, d) @@ -1099,6 +1099,21 @@ def __init__(self, order): d.update(o.__dict__) self.assertEqual(list(d), ["c", "b", "a"]) + @support.cpython_only + def test_splittable_to_generic_combinedtable(self): + """split table must be correctly resized and converted to generic combined table""" + class C: + pass + + a = C() + a.x = 1 + d = a.__dict__ + before_resize = sys.getsizeof(d) + d[2] = 2 # split table is resized to a generic combined table + + self.assertGreater(sys.getsizeof(d), before_resize) + self.assertEqual(list(d), ['x', 2]) + def test_iterator_pickling(self): for proto in range(pickle.HIGHEST_PROTOCOL + 1): data = {1:"a", 2:"b", 3:"c"} From 43ede611e350875cc08ad90c33b846287475f711 Mon Sep 17 00:00:00 2001 From: NakanoMiku Date: Thu, 30 Nov 2023 20:53:15 +0800 Subject: [PATCH 17/23] Update test_dictviews.py from CPython v3.12.0 --- Lib/test/test_dictviews.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_dictviews.py b/Lib/test/test_dictviews.py index e8db2a61a0..b16353f2b6 100644 --- a/Lib/test/test_dictviews.py +++ b/Lib/test/test_dictviews.py @@ -3,6 +3,7 @@ import pickle import sys import unittest +from test.support import C_RECURSION_LIMIT class DictSetTest(unittest.TestCase): @@ -170,6 +171,10 @@ def test_items_set_operations(self): {('a', 1), ('b', 2)}) self.assertEqual(d1.items() & set(d2.items()), {('b', 2)}) self.assertEqual(d1.items() & set(d3.items()), set()) + self.assertEqual(d1.items() & (("a", 1), ("b", 2)), + {('a', 1), ('b', 2)}) + self.assertEqual(d1.items() & (("a", 2), ("b", 2)), {('b', 2)}) + self.assertEqual(d1.items() & (("d", 4), ("e", 5)), set()) self.assertEqual(d1.items() | d1.items(), {('a', 1), ('b', 2)}) @@ -183,12 +188,23 @@ def test_items_set_operations(self): {('a', 1), ('a', 2), ('b', 2)}) self.assertEqual(d1.items() | set(d3.items()), {('a', 1), ('b', 2), ('d', 4), ('e', 5)}) + self.assertEqual(d1.items() | (('a', 1), ('b', 2)), + {('a', 1), ('b', 2)}) + self.assertEqual(d1.items() | (('a', 2), ('b', 2)), + {('a', 1), ('a', 2), ('b', 2)}) + self.assertEqual(d1.items() | (('d', 4), ('e', 5)), + {('a', 1), ('b', 2), ('d', 4), ('e', 5)}) self.assertEqual(d1.items() ^ d1.items(), set()) self.assertEqual(d1.items() ^ d2.items(), {('a', 1), ('a', 2)}) self.assertEqual(d1.items() ^ d3.items(), {('a', 1), ('b', 2), ('d', 4), ('e', 5)}) + self.assertEqual(d1.items() ^ (('a', 1), ('b', 2)), set()) + self.assertEqual(d1.items() ^ (("a", 2), ("b", 2)), + {('a', 1), ('a', 2)}) + self.assertEqual(d1.items() ^ (("d", 4), ("e", 5)), + {('a', 1), ('b', 2), ('d', 4), ('e', 5)}) self.assertEqual(d1.items() - d1.items(), set()) self.assertEqual(d1.items() - d2.items(), {('a', 1)}) @@ -196,6 +212,9 @@ def test_items_set_operations(self): self.assertEqual(d1.items() - set(d1.items()), set()) self.assertEqual(d1.items() - set(d2.items()), {('a', 1)}) self.assertEqual(d1.items() - set(d3.items()), {('a', 1), ('b', 2)}) + self.assertEqual(d1.items() - (('a', 1), ('b', 2)), set()) + self.assertEqual(d1.items() - (("a", 2), ("b", 2)), {('a', 1)}) + self.assertEqual(d1.items() - (("d", 4), ("e", 5)), {('a', 1), ('b', 2)}) self.assertFalse(d1.items().isdisjoint(d1.items())) self.assertFalse(d1.items().isdisjoint(d2.items())) @@ -261,7 +280,7 @@ def test_recursive_repr(self): def test_deeply_nested_repr(self): d = {} - for i in range(sys.getrecursionlimit() + 100): + for i in range(C_RECURSION_LIMIT//2 + 100): d = {42: d.values()} self.assertRaises(RecursionError, repr, d) From 186eac5095ea8f110cb3ea401cbc344d6e3cfa8a Mon Sep 17 00:00:00 2001 From: NakanoMiku Date: Thu, 30 Nov 2023 20:55:02 +0800 Subject: [PATCH 18/23] Update test_dtrace.py from CPython v3.12.0 --- Lib/test/test_dtrace.py | 82 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/Lib/test/test_dtrace.py b/Lib/test/test_dtrace.py index 4b971deacc..e1adf8e974 100644 --- a/Lib/test/test_dtrace.py +++ b/Lib/test/test_dtrace.py @@ -3,6 +3,7 @@ import re import subprocess import sys +import sysconfig import types import unittest @@ -173,6 +174,87 @@ class SystemTapOptimizedTests(TraceTests, unittest.TestCase): backend = SystemTapBackend() optimize_python = 2 +class CheckDtraceProbes(unittest.TestCase): + @classmethod + def setUpClass(cls): + if sysconfig.get_config_var('WITH_DTRACE'): + readelf_major_version, readelf_minor_version = cls.get_readelf_version() + if support.verbose: + print(f"readelf version: {readelf_major_version}.{readelf_minor_version}") + else: + raise unittest.SkipTest("CPython must be configured with the --with-dtrace option.") + + + @staticmethod + def get_readelf_version(): + try: + cmd = ["readelf", "--version"] + proc = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, + ) + with proc: + version, stderr = proc.communicate() + + if proc.returncode: + raise Exception( + f"Command {' '.join(cmd)!r} failed " + f"with exit code {proc.returncode}: " + f"stdout={version!r} stderr={stderr!r}" + ) + except OSError: + raise unittest.SkipTest("Couldn't find readelf on the path") + + # Regex to parse: + # 'GNU readelf (GNU Binutils) 2.40.0\n' -> 2.40 + match = re.search(r"^(?:GNU) readelf.*?\b(\d+)\.(\d+)", version) + if match is None: + raise unittest.SkipTest(f"Unable to parse readelf version: {version}") + + return int(match.group(1)), int(match.group(2)) + + def get_readelf_output(self): + command = ["readelf", "-n", sys.executable] + stdout, _ = subprocess.Popen( + command, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True, + ).communicate() + return stdout + + def test_check_probes(self): + readelf_output = self.get_readelf_output() + + available_probe_names = [ + "Name: import__find__load__done", + "Name: import__find__load__start", + "Name: audit", + "Name: gc__start", + "Name: gc__done", + ] + + for probe_name in available_probe_names: + with self.subTest(probe_name=probe_name): + self.assertIn(probe_name, readelf_output) + + @unittest.expectedFailure + def test_missing_probes(self): + readelf_output = self.get_readelf_output() + + # Missing probes will be added in the future. + missing_probe_names = [ + "Name: function__entry", + "Name: function__return", + "Name: line", + ] + + for probe_name in missing_probe_names: + with self.subTest(probe_name=probe_name): + self.assertIn(probe_name, readelf_output) + if __name__ == '__main__': unittest.main() From f7c7398ba8077605ef0b3e68d6a572bcd7e046cd Mon Sep 17 00:00:00 2001 From: NakanoMiku Date: Thu, 30 Nov 2023 20:55:54 +0800 Subject: [PATCH 19/23] Update test_dynamic.py from CPython v3.12.0 --- Lib/test/test_dynamic.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_dynamic.py b/Lib/test/test_dynamic.py index 2155b40289..4217e8e01c 100644 --- a/Lib/test/test_dynamic.py +++ b/Lib/test/test_dynamic.py @@ -142,12 +142,14 @@ class MyGlobals(dict): def __missing__(self, key): return int(key.removeprefix("_number_")) - code = "lambda: " + "+".join(f"_number_{i}" for i in range(1000)) - sum_1000 = eval(code, MyGlobals()) - expected = sum(range(1000)) - # Warm up the the function for quickening (PEP 659) + # Need more than 256 variables to use EXTENDED_ARGS + variables = 400 + code = "lambda: " + "+".join(f"_number_{i}" for i in range(variables)) + sum_func = eval(code, MyGlobals()) + expected = sum(range(variables)) + # Warm up the function for quickening (PEP 659) for _ in range(30): - self.assertEqual(sum_1000(), expected) + self.assertEqual(sum_func(), expected) class TestTracing(unittest.TestCase): From 1d76b762c7f82cffcfee78fa1dfeb1dddc19466b Mon Sep 17 00:00:00 2001 From: NakanoMiku Date: Thu, 30 Nov 2023 21:01:17 +0800 Subject: [PATCH 20/23] Update test_eintr.py from CPython v3.12.0 --- Lib/test/test_eintr.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_eintr.py b/Lib/test/test_eintr.py index 528147802b..49b15f1a2d 100644 --- a/Lib/test/test_eintr.py +++ b/Lib/test/test_eintr.py @@ -9,6 +9,7 @@ class EINTRTests(unittest.TestCase): @unittest.skipUnless(hasattr(signal, "setitimer"), "requires setitimer()") + @support.requires_resource('walltime') def test_all(self): # Run the tester in a sub-process, to make sure there is only one # thread (for reliable signal delivery). From ad3f0aecafeae690e6de2bad727db786cd82d3af Mon Sep 17 00:00:00 2001 From: NakanoMiku Date: Thu, 30 Nov 2023 21:05:18 +0800 Subject: [PATCH 21/23] Update test_eof.py from CPython v3.12.0 --- Lib/test/test_eof.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_eof.py b/Lib/test/test_eof.py index 21f83db75f..cf12dd3d5d 100644 --- a/Lib/test/test_eof.py +++ b/Lib/test/test_eof.py @@ -4,6 +4,7 @@ from test import support from test.support import os_helper from test.support import script_helper +from test.support import warnings_helper import unittest # TODO: RUSTPYTHON @@ -38,10 +39,11 @@ def test_EOFS_with_file(self): rc, out, err = script_helper.assert_python_failure(file_name) self.assertIn(b'unterminated triple-quoted string literal (detected at line 3)', err) + @warnings_helper.ignore_warnings(category=SyntaxWarning) def test_eof_with_line_continuation(self): expect = "unexpected EOF while parsing (, line 1)" try: - compile('"\\xhh" \\', '', 'exec', dont_inherit=True) + compile('"\\Xhh" \\', '', 'exec') except SyntaxError as msg: self.assertEqual(str(msg), expect) else: From 3945e9a4ed2ce2149b9a73ccd069377f18eb17db Mon Sep 17 00:00:00 2001 From: NakanoMiku Date: Thu, 30 Nov 2023 21:06:09 +0800 Subject: [PATCH 22/23] Update test_epoll.py from CPython v3.12.0 --- Lib/test/test_epoll.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_epoll.py b/Lib/test/test_epoll.py index b623852f9e..c94946a6ae 100644 --- a/Lib/test/test_epoll.py +++ b/Lib/test/test_epoll.py @@ -27,6 +27,7 @@ import socket import time import unittest +from test import support if not hasattr(select, "epoll"): raise unittest.SkipTest("test works only on Linux 2.6") @@ -186,10 +187,16 @@ def test_control_and_wait(self): client.sendall(b"Hello!") server.sendall(b"world!!!") - now = time.monotonic() - events = ep.poll(1.0, 4) - then = time.monotonic() - self.assertFalse(then - now > 0.01) + # we might receive events one at a time, necessitating multiple calls to + # poll + events = [] + for _ in support.busy_retry(support.SHORT_TIMEOUT): + now = time.monotonic() + events += ep.poll(1.0, 4) + then = time.monotonic() + self.assertFalse(then - now > 0.01) + if len(events) >= 2: + break expected = [(client.fileno(), select.EPOLLIN | select.EPOLLOUT), (server.fileno(), select.EPOLLIN | select.EPOLLOUT)] From 784b5f1b1c6412844aac9ed0675bbcee6de19cb4 Mon Sep 17 00:00:00 2001 From: NakanoMiku Date: Thu, 30 Nov 2023 21:58:26 +0800 Subject: [PATCH 23/23] Edit test_dictviews ExpectedFailure added at line 282 --- Lib/test/test_dictviews.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_dictviews.py b/Lib/test/test_dictviews.py index b16353f2b6..172b98aa68 100644 --- a/Lib/test/test_dictviews.py +++ b/Lib/test/test_dictviews.py @@ -278,6 +278,8 @@ def test_recursive_repr(self): # Again. self.assertIsInstance(r, str) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_deeply_nested_repr(self): d = {} for i in range(C_RECURSION_LIMIT//2 + 100):