Skip to content

Commit 746bf85

Browse files
committed
Update tests
1 parent ddd1635 commit 746bf85

4 files changed

Lines changed: 172 additions & 107 deletions

File tree

Lib/test/datetimetester.py

Lines changed: 119 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -7155,55 +7155,53 @@ def test_datetime_from_timestamp(self):
71557155

71567156
self.assertEqual(dt_orig, dt_rt)
71577157

7158-
def assert_python_ok_in_subinterp(self, script,
7159-
setup='_testcapi.test_datetime_capi()',
7160-
mainsetup='_testcapi.test_datetime_capi()',
7161-
config='isolated'):
7158+
def assert_python_ok_in_subinterp(self, script, init='', fini='',
7159+
repeat=1, config='isolated'):
71627160
# iOS requires the use of the custom framework loader,
71637161
# not the ExtensionFileLoader.
71647162
if sys.platform == "ios":
71657163
extension_loader = "AppleFrameworkLoader"
71667164
else:
71677165
extension_loader = "ExtensionFileLoader"
71687166

7169-
maincode = textwrap.dedent(f'''
7170-
import textwrap
7171-
from test import support
7172-
7173-
subcode = textwrap.dedent("""
7174-
if {_interpreters is None}:
7175-
import _testcapi
7176-
else:
7177-
import importlib.machinery
7178-
import importlib.util
7179-
fullname = '_testcapi_datetime'
7180-
origin = importlib.util.find_spec('_testcapi').origin
7181-
loader = importlib.machinery.{extension_loader}(fullname, origin)
7182-
spec = importlib.util.spec_from_loader(fullname, loader)
7183-
_testcapi = importlib.util.module_from_spec(spec)
7184-
spec.loader.exec_module(_testcapi)
7185-
7186-
$SETUP$
7187-
$SCRIPT$
7188-
""")
7189-
7190-
import _testcapi
7191-
$MAINSETUP$
7192-
7167+
code = textwrap.dedent(f'''
7168+
subinterp_code = """
71937169
if {_interpreters is None}:
7194-
ret = support.run_in_subinterp(subcode)
7170+
import _testcapi
71957171
else:
7196-
import _interpreters
7197-
config = _interpreters.new_config('{config}').__dict__
7198-
ret = support.run_in_subinterp_with_config(subcode, **config)
7199-
7200-
assert ret == 0
7172+
import importlib.machinery
7173+
import importlib.util
7174+
fullname = '_testcapi_datetime'
7175+
origin = importlib.util.find_spec('_testcapi').origin
7176+
loader = importlib.machinery.{extension_loader}(fullname, origin)
7177+
spec = importlib.util.spec_from_loader(fullname, loader)
7178+
_testcapi = importlib.util.module_from_spec(spec)
7179+
spec.loader.exec_module(_testcapi)
7180+
interp_index = $INTERP_INDEX$
7181+
setup = _testcapi.test_datetime_capi_newinterp # call it if needed
7182+
$SCRIPT$
7183+
"""
72017184
7202-
''').replace('$MAINSETUP$', mainsetup)
7203-
maincode = maincode.replace('$SETUP$', textwrap.indent(setup, '\x20'*4))
7204-
maincode = maincode.replace('$SCRIPT$', textwrap.indent(script, '\x20'*4))
7185+
import _testcapi
7186+
from test import support
7187+
setup = _testcapi.test_datetime_capi_newinterp
7188+
$INIT$
72057189
7206-
res = script_helper.assert_python_ok('-c', maincode)
7190+
for idx in range({repeat}):
7191+
subcode = subinterp_code.replace('$INTERP_INDEX$', str(idx))
7192+
if {_interpreters is None}:
7193+
ret = support.run_in_subinterp(subcode)
7194+
else:
7195+
import _interpreters
7196+
config = _interpreters.new_config('{config}').__dict__
7197+
ret = support.run_in_subinterp_with_config(subcode, **config)
7198+
assert ret == 0
7199+
$FINI$
7200+
''')
7201+
code = code.replace('$INIT$', init).replace('$FINI$', fini)
7202+
code = code.replace('$SCRIPT$', script)
7203+
7204+
res = script_helper.assert_python_ok('-c', code)
72077205
return res
72087206

72097207
def test_type_check_in_subinterp(self):
@@ -7212,18 +7210,18 @@ def run(type_checker, obj):
72127210
if not type_checker(obj, True):
72137211
raise TypeError(f'{{type(obj)}} is not C API type')
72147212
7213+
setup()
72157214
import _datetime
72167215
run(_testcapi.datetime_check_date, _datetime.date.today())
72177216
run(_testcapi.datetime_check_datetime, _datetime.datetime.now())
72187217
run(_testcapi.datetime_check_time, _datetime.time(12, 30))
72197218
run(_testcapi.datetime_check_delta, _datetime.timedelta(1))
72207219
run(_testcapi.datetime_check_tzinfo, _datetime.tzinfo())
7221-
""")
7222-
self.assert_python_ok_in_subinterp(script, mainsetup='')
7220+
""")
7221+
self.assert_python_ok_in_subinterp(script)
72237222
if _interpreters is not None:
7224-
with self.subTest('legacy'):
7225-
self.assert_python_ok_in_subinterp(script, mainsetup='',
7226-
config='legacy')
7223+
with self.subTest(name := 'legacy'):
7224+
self.assert_python_ok_in_subinterp(script, config=name)
72277225

72287226

72297227
class ExtensionModuleTests(unittest.TestCase):
@@ -7232,6 +7230,9 @@ def setUp(self):
72327230
if self.__class__.__name__.endswith('Pure'):
72337231
self.skipTest('Not relevant in pure Python')
72347232

7233+
def assert_python_ok_in_subinterp(self, *args, **kwargs):
7234+
return CapiTest.assert_python_ok_in_subinterp(self, *args, **kwargs)
7235+
72357236
@support.cpython_only
72367237
def test_gh_120161(self):
72377238
with self.subTest('simple'):
@@ -7300,11 +7301,65 @@ def test_update_type_cache(self):
73007301
res = script_helper.assert_python_ok('-c', script)
73017302
self.assertFalse(res.err)
73027303

7303-
def test_static_type_at_shutdown1(self):
7304+
def test_module_free(self):
7305+
script = textwrap.dedent("""
7306+
import sys
7307+
import gc
7308+
import weakref
7309+
ws = weakref.WeakSet()
7310+
for _ in range(3):
7311+
import _datetime
7312+
timedelta = _datetime.timedelta # static type
7313+
ws.add(_datetime)
7314+
del sys.modules['_datetime']
7315+
del _datetime
7316+
gc.collect()
7317+
assert len(ws) == 1 # only one remains
7318+
""")
7319+
script_helper.assert_python_ok('-c', script)
7320+
7321+
@unittest.skipIf(not support.Py_DEBUG, "Debug builds only")
7322+
def test_no_leak(self):
7323+
script = textwrap.dedent("""
7324+
import datetime
7325+
datetime.datetime.strptime('20000101', '%Y%m%d').strftime('%Y%m%d')
7326+
""")
7327+
res = script_helper.assert_python_ok('-X', 'showrefcount', '-c', script)
7328+
self.assertIn(b'[0 refs, 0 blocks]', res.err)
7329+
7330+
def test_static_type_on_subinterp(self):
7331+
script = textwrap.dedent("""
7332+
date = _testcapi.get_capi_types()['date']
7333+
date.today
7334+
""")
7335+
with_setup = 'setup()' + script
7336+
with self.subTest('[PyDateTime_IMPORT] main: no, sub: yes'):
7337+
self.assert_python_ok_in_subinterp(with_setup)
7338+
7339+
with self.subTest('[PyDateTime_IMPORT] main: yes, sub: yes'):
7340+
# Fails if the setup() means test_datetime_capi() rather than
7341+
# test_datetime_capi_newinterp()
7342+
self.assert_python_ok_in_subinterp(with_setup, 'setup()')
7343+
self.assert_python_ok_in_subinterp('setup()', fini=with_setup)
7344+
self.assert_python_ok_in_subinterp(with_setup, repeat=2)
7345+
7346+
with_import = 'import _datetime' + script
7347+
with self.subTest('Explicit import'):
7348+
self.assert_python_ok_in_subinterp(with_import, 'setup()')
7349+
7350+
with_import = textwrap.dedent("""
7351+
timedelta = _testcapi.get_capi_types()['timedelta']
7352+
timedelta(days=1)
7353+
""") + script
7354+
with self.subTest('Implicit import'):
7355+
self.assert_python_ok_in_subinterp(with_import, 'setup()')
7356+
7357+
def test_static_type_at_shutdown(self):
73047358
# gh-132413
73057359
script = textwrap.dedent("""
73067360
import sys
73077361
import _datetime
7362+
timedelta = _datetime.timedelta
73087363
73097364
def gen():
73107365
try:
@@ -7314,85 +7369,45 @@ def gen():
73147369
assert not sys.modules
73157370
td = _datetime.timedelta(days=1)
73167371
assert td.days == 1
7372+
td = timedelta(days=1)
7373+
assert td.days == 1
73177374
assert not sys.modules
73187375
73197376
it = gen()
73207377
next(it)
7321-
""")
7322-
res = script_helper.assert_python_ok('-c', script)
7323-
self.assertFalse(res.err)
7378+
""")
7379+
with self.subTest('MainInterpreter'):
7380+
res = script_helper.assert_python_ok('-c', script)
7381+
self.assertFalse(res.err)
7382+
with self.subTest('Subinterpreter'):
7383+
res = self.assert_python_ok_in_subinterp(script)
7384+
self.assertFalse(res.err)
73247385

7325-
def test_static_type_at_shutdown2(self):
73267386
script = textwrap.dedent("""
73277387
import sys
7328-
from _datetime import timedelta
7388+
timedelta = _testcapi.get_capi_types()['timedelta']
73297389
73307390
def gen():
73317391
try:
73327392
yield
73337393
finally:
7394+
# Exceptions are ignored here
73347395
assert not sys.modules
73357396
td = timedelta(days=1)
73367397
assert td.days == 1
73377398
assert not sys.modules
73387399
73397400
it = gen()
73407401
next(it)
7341-
""")
7342-
res = script_helper.assert_python_ok('-c', script)
7343-
self.assertFalse(res.err)
7344-
7345-
def test_static_type_at_shutdown3(self):
7346-
script = textwrap.dedent("""
7347-
timedelta = _testcapi.get_capi_types()['timedelta']
7348-
7349-
def gen():
7350-
try:
7351-
yield
7352-
finally:
7353-
timedelta(days=1)
7354-
7355-
it = gen()
7356-
next(it)
7357-
""")
7358-
res = CapiTest.assert_python_ok_in_subinterp(self, script, setup='')
7359-
self.assertIn(b'ImportError: sys.meta_path is None', res.err)
7360-
7361-
def test_static_type_before_shutdown(self):
7362-
script = textwrap.dedent(f"""
7363-
import sys
7364-
assert '_datetime' not in sys.modules
7365-
timedelta = _testcapi.get_capi_types()['timedelta']
7366-
timedelta(days=1)
7367-
assert '_datetime' in sys.modules
7368-
""")
7369-
CapiTest.assert_python_ok_in_subinterp(self, script, setup='')
7370-
7371-
def test_remain_only_one_module(self):
7372-
script = textwrap.dedent("""
7373-
import sys
7374-
import gc
7375-
import weakref
7376-
ws = weakref.WeakSet()
7377-
for _ in range(3):
7378-
import _datetime
7379-
timedelta = _datetime.timedelta
7380-
ws.add(_datetime)
7381-
del sys.modules['_datetime']
7382-
del _datetime
7383-
gc.collect()
7384-
assert len(ws) == 1
7385-
""")
7386-
script_helper.assert_python_ok('-c', script)
7387-
7388-
@unittest.skipIf(not support.Py_DEBUG, "Debug builds only")
7389-
def test_no_leak(self):
7390-
script = textwrap.dedent("""
7391-
import datetime
7392-
datetime.datetime.strptime('20000101', '%Y%m%d').strftime('%Y%m%d')
7393-
""")
7394-
res = script_helper.assert_python_ok('-X', 'showrefcount', '-c', script)
7395-
self.assertIn(b'[0 refs, 0 blocks]', res.err)
7402+
""")
7403+
with self.subTest('[PyDateTime_IMPORT] main: yes, sub: no'):
7404+
res = self.assert_python_ok_in_subinterp(script, 'setup()')
7405+
self.assertIn(b'ImportError: sys.meta_path is None', res.err)
7406+
7407+
with_import = 'setup()' + script
7408+
with self.subTest('[PyDateTime_IMPORT] main: no, sub: yes'):
7409+
res = self.assert_python_ok_in_subinterp(with_import)
7410+
self.assertFalse(res.err)
73967411

73977412

73987413
def load_tests(loader, standard_tests, pattern):

Lib/test/test_embed.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,34 @@ def test_datetime_reset_strptime(self):
440440
out, err = self.run_embedded_interpreter("test_repeated_init_exec", code)
441441
self.assertEqual(out, '20000101\n' * INIT_LOOPS)
442442

443+
def test_datetime_capi_at_shutdown(self):
444+
# gh-132413: datetime module is currently tested in an interp's life.
445+
# PyDateTime_IMPORT needs to be called at least once after the restart.
446+
code = textwrap.dedent("""
447+
import sys
448+
import _testcapi
449+
_testcapi.test_datetime_capi_newinterp()
450+
timedelta = _testcapi.get_capi_types()['timedelta']
451+
452+
def gen():
453+
try:
454+
yield
455+
finally:
456+
assert not sys.modules
457+
res = 0
458+
try:
459+
timedelta(days=1)
460+
res = 1
461+
except ImportError:
462+
res = 2
463+
print(res)
464+
465+
it = gen()
466+
next(it)
467+
""")
468+
out, err = self.run_embedded_interpreter("test_repeated_init_exec", code)
469+
self.assertEqual(out, '1\n' * INIT_LOOPS)
470+
443471
def test_static_types_inherited_slots(self):
444472
script = textwrap.dedent("""
445473
import test.support

Modules/_datetimemodule.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
#include "Python.h"
1313
#include "pycore_long.h" // _PyLong_GetOne()
1414
#include "pycore_object.h" // _PyObject_Init()
15-
#include "pycore_pylifecycle.h" // _Py_IsInterpreterFinalizing()
1615
#include "pycore_time.h" // _PyTime_ObjectToTime_t()
1716
#include "pycore_unicodeobject.h" // _PyUnicode_Copy()
1817

@@ -174,7 +173,6 @@ _get_current_state(PyObject **p_mod)
174173
* so we must re-import the module. */
175174
mod = PyImport_ImportModule("_datetime");
176175
if (mod == NULL) {
177-
assert(_Py_IsInterpreterFinalizing(interp));
178176
return NULL;
179177
}
180178
}

Modules/_testcapi/datetime.c

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,29 @@ test_datetime_capi(PyObject *self, PyObject *args)
3535
Py_RETURN_NONE;
3636
}
3737

38+
static PyObject *
39+
test_datetime_capi_newinterp(PyObject *self, PyObject *args)
40+
{
41+
// Call PyDateTime_IMPORT at least once in each interpreter's life
42+
if (PyDateTimeAPI != NULL && test_run_counter == 0) {
43+
PyErr_SetString(PyExc_AssertionError,
44+
"PyDateTime_CAPI somehow initialized");
45+
return NULL;
46+
}
47+
test_run_counter++;
48+
PyDateTime_IMPORT;
49+
50+
if (PyDateTimeAPI == NULL) {
51+
return NULL;
52+
}
53+
assert(!PyType_HasFeature(PyDateTimeAPI->DateType, Py_TPFLAGS_HEAPTYPE));
54+
assert(!PyType_HasFeature(PyDateTimeAPI->TimeType, Py_TPFLAGS_HEAPTYPE));
55+
assert(!PyType_HasFeature(PyDateTimeAPI->DateTimeType, Py_TPFLAGS_HEAPTYPE));
56+
assert(!PyType_HasFeature(PyDateTimeAPI->DeltaType, Py_TPFLAGS_HEAPTYPE));
57+
assert(!PyType_HasFeature(PyDateTimeAPI->TZInfoType, Py_TPFLAGS_HEAPTYPE));
58+
Py_RETURN_NONE;
59+
}
60+
3861
/* Functions exposing the C API type checking for testing */
3962
#define MAKE_DATETIME_CHECK_FUNC(check_method, exact_method) \
4063
do { \
@@ -507,6 +530,7 @@ static PyMethodDef test_methods[] = {
507530
{"get_capi_types", get_capi_types, METH_NOARGS},
508531
{"make_timezones_capi", make_timezones_capi, METH_NOARGS},
509532
{"test_datetime_capi", test_datetime_capi, METH_NOARGS},
533+
{"test_datetime_capi_newinterp",test_datetime_capi_newinterp, METH_NOARGS},
510534
{NULL},
511535
};
512536

@@ -527,7 +551,7 @@ _PyTestCapi_Init_DateTime(PyObject *mod)
527551
static int
528552
_testcapi_datetime_exec(PyObject *mod)
529553
{
530-
// Call test_datetime_capi() in each test.
554+
// The execution does not invoke PyDateTime_IMPORT
531555
return 0;
532556
}
533557

0 commit comments

Comments
 (0)