From eb31592efd475bc5fa5149483618f8c26cc96c5c Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Sat, 22 Jun 2024 04:09:24 +0900 Subject: [PATCH 1/5] [3.13] gh-120782: Update internal type cache when reloading datetime When reloading _datetime module, the single-phase version did not invoke the PyInit__datetime function, whereas the current multi-phase version updates the static types through the module init. The outdated static type cache in the interpreter state needs to be invalidated at the end of reloading the multi-phase module. --- Lib/test/datetimetester.py | 17 +++++++++++++++++ ...24-06-21-12-00-16.gh-issue-120782.LOE8tj.rst | 1 + Modules/_datetimemodule.c | 6 ++++++ 3 files changed, 24 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-06-21-12-00-16.gh-issue-120782.LOE8tj.rst diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 77472d84e60ca3..2b676def196de8 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -6881,6 +6881,23 @@ def pickle_fake_date(datetime_) -> Type[FakeDate]: """) script_helper.assert_python_ok('-c', script) + def test_update_type_cache(self): + # gh-120782 + script = textwrap.dedent(""" + import sys + for i in range(5): + import _datetime + _datetime.date.max > _datetime.date.min + _datetime.time.max > _datetime.time.min + _datetime.datetime.max > _datetime.datetime.min + _datetime.timedelta.max > _datetime.timedelta.min + assert isinstance(_datetime.timezone.min, _datetime.tzinfo) + assert isinstance(_datetime.timezone.utc, _datetime.tzinfo) + assert isinstance(_datetime.timezone.max, _datetime.tzinfo) + del sys.modules['_datetime'] + """) + script_helper.assert_python_ok('-c', script) + def load_tests(loader, standard_tests, pattern): standard_tests.addTest(ZoneInfoCompleteTest()) diff --git a/Misc/NEWS.d/next/Library/2024-06-21-12-00-16.gh-issue-120782.LOE8tj.rst b/Misc/NEWS.d/next/Library/2024-06-21-12-00-16.gh-issue-120782.LOE8tj.rst new file mode 100644 index 00000000000000..02acbd2873009b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-06-21-12-00-16.gh-issue-120782.LOE8tj.rst @@ -0,0 +1 @@ +Fix wrong references of the :mod:`datetime` types after reloading the module. diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 135d6cb683f0d2..5388e39ca02a2d 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -7293,6 +7293,12 @@ _datetime_exec(PyObject *module) static_assert(DI100Y == 25 * DI4Y - 1, "DI100Y"); assert(DI100Y == days_before_year(100+1)); + if (reloading) { + for (size_t i = 0; i < Py_ARRAY_LENGTH(capi_types); i++) { + PyType_Modified(capi_types[i]); + } + } + if (set_current_module(interp, module) < 0) { goto error; } From 0f3c382265bbf9a04badecf6a1473c61c276041c Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Sat, 22 Jun 2024 04:40:23 +0900 Subject: [PATCH 2/5] add test cases --- Lib/test/datetimetester.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 2b676def196de8..26dc5156027243 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -6894,6 +6894,9 @@ def test_update_type_cache(self): assert isinstance(_datetime.timezone.min, _datetime.tzinfo) assert isinstance(_datetime.timezone.utc, _datetime.tzinfo) assert isinstance(_datetime.timezone.max, _datetime.tzinfo) + assert _datetime.timezone.__dict__["min"] is _datetime.timezone.min + assert _datetime.timezone.__dict__["utc"] is _datetime.timezone.utc + assert _datetime.timezone.__dict__["max"] is _datetime.timezone.max del sys.modules['_datetime'] """) script_helper.assert_python_ok('-c', script) From 1459029e125d342e3feff78b9e9276e4cf1470e1 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Sat, 22 Jun 2024 04:45:55 +0900 Subject: [PATCH 3/5] add missing asserts --- Lib/test/datetimetester.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 26dc5156027243..8d1313c77e2058 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -6887,10 +6887,10 @@ def test_update_type_cache(self): import sys for i in range(5): import _datetime - _datetime.date.max > _datetime.date.min - _datetime.time.max > _datetime.time.min - _datetime.datetime.max > _datetime.datetime.min - _datetime.timedelta.max > _datetime.timedelta.min + assert _datetime.date.max > _datetime.date.min + assert _datetime.time.max > _datetime.time.min + assert _datetime.datetime.max > _datetime.datetime.min + assert _datetime.timedelta.max > _datetime.timedelta.min assert isinstance(_datetime.timezone.min, _datetime.tzinfo) assert isinstance(_datetime.timezone.utc, _datetime.tzinfo) assert isinstance(_datetime.timezone.max, _datetime.tzinfo) From 1020c948596ff745c960126dcc0d528e459d5753 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Sat, 22 Jun 2024 06:07:24 +0900 Subject: [PATCH 4/5] add test cases --- Lib/test/datetimetester.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 8d1313c77e2058..39a6946e7ddd31 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -6891,12 +6891,24 @@ def test_update_type_cache(self): assert _datetime.time.max > _datetime.time.min assert _datetime.datetime.max > _datetime.datetime.min assert _datetime.timedelta.max > _datetime.timedelta.min - assert isinstance(_datetime.timezone.min, _datetime.tzinfo) - assert isinstance(_datetime.timezone.utc, _datetime.tzinfo) - assert isinstance(_datetime.timezone.max, _datetime.tzinfo) + assert _datetime.date.__dict__["min"] is _datetime.date.min + assert _datetime.date.__dict__["max"] is _datetime.date.max + assert _datetime.date.__dict__["resolution"] is _datetime.date.resolution + assert _datetime.time.__dict__["min"] is _datetime.time.min + assert _datetime.time.__dict__["max"] is _datetime.time.max + assert _datetime.time.__dict__["resolution"] is _datetime.time.resolution + assert _datetime.datetime.__dict__["min"] is _datetime.datetime.min + assert _datetime.datetime.__dict__["max"] is _datetime.datetime.max + assert _datetime.datetime.__dict__["resolution"] is _datetime.datetime.resolution + assert _datetime.timedelta.__dict__["min"] is _datetime.timedelta.min + assert _datetime.timedelta.__dict__["max"] is _datetime.timedelta.max + assert _datetime.timedelta.__dict__["resolution"] is _datetime.timedelta.resolution assert _datetime.timezone.__dict__["min"] is _datetime.timezone.min - assert _datetime.timezone.__dict__["utc"] is _datetime.timezone.utc assert _datetime.timezone.__dict__["max"] is _datetime.timezone.max + assert _datetime.timezone.__dict__["utc"] is _datetime.timezone.utc + assert isinstance(_datetime.timezone.min, _datetime.tzinfo) + assert isinstance(_datetime.timezone.max, _datetime.tzinfo) + assert isinstance(_datetime.timezone.utc, _datetime.tzinfo) del sys.modules['_datetime'] """) script_helper.assert_python_ok('-c', script) From 09ec03297a7cac71150574bfe3e11183686afb77 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Sat, 22 Jun 2024 19:04:43 +0900 Subject: [PATCH 5/5] drop @no_rerun decorator from CapiTest class --- Lib/test/datetimetester.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 39a6946e7ddd31..849aa50af5a219 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -52,25 +52,6 @@ pass # -# This is copied from test_import/__init__.py. -def no_rerun(reason): - """Skip rerunning for a particular test. - - WARNING: Use this decorator with care; skipping rerunning makes it - impossible to find reference leaks. Provide a clear reason for skipping the - test using the 'reason' parameter. - """ - def deco(func): - _has_run = False - def wrapper(self): - nonlocal _has_run - if _has_run: - self.skipTest(reason) - func(self) - _has_run = True - return wrapper - return deco - pickle_loads = {pickle.loads, pickle._loads} pickle_choices = [(pickle, pickle, proto) @@ -6412,7 +6393,6 @@ class IranTest(ZoneInfoTest): @unittest.skipIf(_testcapi is None, 'need _testcapi module') -@no_rerun("the encapsulated datetime C API does not support reloading") class CapiTest(unittest.TestCase): def setUp(self): # Since the C API is not present in the _Pure tests, skip all tests