From 458367e60c14fd1b443af4ed7a3fd5135b6d0c35 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 22 Jul 2026 08:53:27 +0300 Subject: [PATCH 01/10] gh-154419: Find the fish shell in test_venv (GH-154420) shutil.which('fish') can find the unrelated "Go Fish" game, which is shipped on several BSD systems. Search PATH for an executable which behaves like the fish shell instead. Co-authored-by: Claude Opus 4.8 --- Lib/test/test_venv.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py index 58ae85fb268042e..e98e52c2ea20453 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -783,8 +783,16 @@ def test_fish_activate_shadowed_builtins(self): shadows one of those builtins (a common pattern for `.`-style directory navigators) must not hijack the prompt or break status restoration. """ - fish = shutil.which('fish') - if fish is None: + # Some systems have an unrelated "fish" game (see fish(6)), which + # can precede the fish shell in PATH. + for dirname in None, *os.get_exec_path(): + fish = shutil.which('fish', path=dirname) + if fish is not None and subprocess.run( + [fish, '-c', 'echo $FISH_VERSION'], + stdin=subprocess.DEVNULL, + capture_output=True).stdout.strip(): + break + else: self.skipTest('fish required for this test') rmtree(self.env_dir) builder = venv.EnvBuilder(clear=True) From 6f8f97fe5246837b93e6c2f3525aa9f623ecd30b Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 22 Jul 2026 10:00:11 +0300 Subject: [PATCH 02/10] gh-154427: Check the access time in UtimeTests only if it is stored (GH-154428) HAMMER2 on DragonFly BSD does not store the access time and os.stat() returns the modification time instead. Probe the file system, like it is already done for the timestamp resolution. Co-authored-by: Claude Opus 4.8 --- Lib/test/test_os/test_os.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_os/test_os.py b/Lib/test/test_os/test_os.py index b4310043b60178b..c62bceb25cabe05 100644 --- a/Lib/test/test_os/test_os.py +++ b/Lib/test/test_os/test_os.py @@ -1058,11 +1058,18 @@ def support_subsecond(self, filename): or (st.st_mtime != st[8]) or (st.st_ctime != st[9])) + def support_atime(self, filename): + # Heuristic to check if the filesystem stores the access time. + # Use whole seconds, to not depend on the timestamp resolution. + os.utime(filename, (1, 2)) + return os.stat(filename).st_atime == 1 + def _test_utime(self, set_time, filename=None): if not filename: filename = self.fname support_subsecond = self.support_subsecond(filename) + support_atime = self.support_atime(filename) if support_subsecond: # Timestamp with a resolution of 1 microsecond (10^-6). # @@ -1089,18 +1096,22 @@ def _test_utime(self, set_time, filename=None): # digits worth of sub-second precision. # Some day it would be good to fix this upstream. delta=1e-5 - self.assertAlmostEqual(st.st_atime, atime_ns * 1e-9, delta=1e-5) + if support_atime: + self.assertAlmostEqual(st.st_atime, atime_ns * 1e-9, delta=1e-5) + self.assertAlmostEqual(st.st_atime_ns, atime_ns, delta=1e9 * 1e-5) self.assertAlmostEqual(st.st_mtime, mtime_ns * 1e-9, delta=1e-5) - self.assertAlmostEqual(st.st_atime_ns, atime_ns, delta=1e9 * 1e-5) self.assertAlmostEqual(st.st_mtime_ns, mtime_ns, delta=1e9 * 1e-5) else: if support_subsecond: - self.assertAlmostEqual(st.st_atime, atime_ns * 1e-9, delta=1e-6) + if support_atime: + self.assertAlmostEqual(st.st_atime, atime_ns * 1e-9, delta=1e-6) self.assertAlmostEqual(st.st_mtime, mtime_ns * 1e-9, delta=1e-6) else: - self.assertEqual(st.st_atime, atime_ns * 1e-9) + if support_atime: + self.assertEqual(st.st_atime, atime_ns * 1e-9) self.assertEqual(st.st_mtime, mtime_ns * 1e-9) - self.assertEqual(st.st_atime_ns, atime_ns) + if support_atime: + self.assertEqual(st.st_atime_ns, atime_ns) self.assertEqual(st.st_mtime_ns, mtime_ns) def test_utime(self): From 0eac28e3637d0ee15f4e8737b5d1a311623e46f9 Mon Sep 17 00:00:00 2001 From: Piotr Kaznowski Date: Wed, 22 Jul 2026 10:08:03 +0200 Subject: [PATCH 03/10] gh-154007: Improve test coverage for the `shlex` module (#154009) --- Lib/test/test_shlex.py | 200 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) diff --git a/Lib/test/test_shlex.py b/Lib/test/test_shlex.py index 2adaee81b063085..4c0cd88bbcda14f 100644 --- a/Lib/test/test_shlex.py +++ b/Lib/test/test_shlex.py @@ -1,8 +1,11 @@ import io import itertools +import os import shlex import string +import tempfile import unittest +from unittest.mock import patch from test.support import cpython_only from test.support import import_helper @@ -376,6 +379,203 @@ def testPunctuationCharsReadOnly(self): with self.assertRaises(AttributeError): shlex_instance.punctuation_chars = False + def testLinenoAfterNewLine(self): + s = shlex.shlex("line 1\nline 2") + self.assertEqual(s.lineno, 1) # before consumption + list(s) + self.assertEqual(s.lineno, 2) + + def testLinenoAfterComment(self): + """Comment handler increments lineno even without a trailing newline.""" + s = shlex.shlex("line 1 # line 2") + list(s) + self.assertEqual(s.lineno, 2) + + def testPushToken(self): + s = shlex.shlex("b c") + s.push_token("a") + self.assertListEqual(list(s), ["a", "b", "c"]) + + def testPushTokenLifo(self): + s = shlex.shlex("") + s.push_token("first") + s.push_token("last") + self.assertListEqual(list(s), ["last", "first"]) + + def testPushTokenDebug(self): + s = shlex.shlex("") + s.debug = 1 + tok = "a" + with patch("builtins.print") as mock_print: + s.push_token(tok) + mock_print.assert_called_once_with(f"shlex: pushing token {tok!r}") + + def testPushSourceString(self): + s = shlex.shlex("world") + s.push_source("hello") + self.assertListEqual(list(s), ["hello", "world"]) + + def testPushSourceStream(self): + s = shlex.shlex("world") + s.push_source(io.StringIO("hello")) + self.assertListEqual(list(s), ["hello", "world"]) + + def testPushSourceStreamDebug(self): + s = shlex.shlex("") + stream = io.StringIO("hello") + s.debug = 1 + with patch("builtins.print") as mock_print: + s.push_source(stream) + mock_print.assert_called_once_with(f"shlex: pushing to stream {stream}") + + def testPushSourceNewfile(self): + """shlex.push_source sets infile to newfile; pop_source restores the original on exhaustion.""" + original_file = "original.sh" + new_file = "new.sh" + s = shlex.shlex("b", infile=original_file) + s.debug = 1 + with patch("builtins.print") as mock_print: + s.push_source("a", newfile=new_file) + mock_print.assert_called_once_with(f"shlex: pushing to file {new_file}") + self.assertEqual(s.infile, new_file) + s.debug = 0 + list(s) + self.assertEqual(s.infile, original_file) + + def testPopSourceDebug(self): + """pop_source emits debug output when debug is set.""" + s = shlex.shlex("b") + original_stream = s.instream + s.push_source("a") + s.debug = 1 + with patch("builtins.print") as mock_print: + list(s) # exhausts pushed source and triggers pop_source internally + mock_print.assert_any_call(f"shlex: popping to {original_stream}, line 1") + + def testErrorLeaderTracksPosition(self): + infile_label = "test.sh" + s = shlex.shlex("line 1\nline 2", infile=infile_label) + list(s) + result = s.error_leader() + self.assertEqual(result, f'"{infile_label}", line 2: ') + + def testErrorLeaderOverrides(self): + s = shlex.shlex("foo", infile="original.sh") + infile_label_override = "override.sh" + lineno_override = 42 + result = s.error_leader(infile=infile_label_override, lineno=lineno_override) + self.assertEqual(result, f'"{infile_label_override}", line {lineno_override}: ') + + def testNoClosingQuotation(self): + s = shlex.shlex('"foo') + with self.assertRaisesRegex(ValueError, "No closing quotation"): + list(s) + + def testNoEscapedCharacter(self): + s = shlex.shlex("\\", posix=True) + with self.assertRaisesRegex(ValueError, "No escaped character"): + list(s) + + def testSourcehookStripsQuotes(self): + with tempfile.NamedTemporaryFile(mode="w", suffix=".sh", delete_on_close=False) as f: + f.write("hello") + f.close() + s = shlex.shlex("") + newfile, stream = s.sourcehook(f'"{f.name}"') + stream.close() + self.assertEqual(newfile, f.name) + + def testSourcehookAbsolutePath(self): + with tempfile.NamedTemporaryFile(mode="w", delete_on_close=False) as f: + f.close() + s = shlex.shlex("", infile="/some/dir/main.sh") + newfile, stream = s.sourcehook(f.name) + stream.close() + self.assertEqual(newfile, f.name) + + def testSourcehookRelativePath(self): + with tempfile.TemporaryDirectory() as d: + fpath = os.path.join(d, "included.sh") + with open(fpath, "w"): + pass + s = shlex.shlex("", infile=os.path.join(d, "main.sh")) + newfile, stream = s.sourcehook("included.sh") + stream.close() + self.assertEqual(newfile, fpath) + + def testSourceInclusion(self): + """shlex.source sets a trigger keyword: when the lexer reads a token equal + to it, the next token is consumed as a filename and passed to + sourcehook, which returns a stream to push onto the input stack. + Tokens flow from that stream first, then resume from the original. + """ + s = shlex.shlex("trigger filename remaining") + s.source = "trigger" + s.sourcehook = lambda f: (f, io.StringIO("included")) + self.assertEqual(list(s), ["included", "remaining"]) + + def testGetTokenPopsPushbackDebug(self): + s = shlex.shlex("") + s.push_token("hello") + s.debug = 1 # set after push_token to isolate the pop-token branch + with patch("builtins.print") as mock_print: + tok = s.get_token() + self.assertEqual(tok, "hello") + mock_print.assert_called_once_with("shlex: popping token 'hello'") + + def testDebugWhitespaceInWhitespaceState(self): + s = shlex.shlex(" a") + s.debug = 2 + with patch("builtins.print") as mock_print: + list(s) + mock_print.assert_any_call("shlex: I see whitespace in whitespace state") + + def testDebugWhitespaceInWordState(self): + s = shlex.shlex("a b") + s.debug = 2 + with patch("builtins.print") as mock_print: + list(s) + mock_print.assert_any_call("shlex: I see whitespace in word state") + + def testDebugPunctuationInWordState(self): + s = shlex.shlex("a(") + s.debug = 2 + with patch("builtins.print") as mock_print: + list(s) + mock_print.assert_any_call("shlex: I see punctuation in word state") + + def testDebugRawToken(self): + s = shlex.shlex("hello") + s.debug = 2 + with patch("builtins.print") as mock_print: + list(s) + mock_print.assert_any_call("shlex: raw token='hello'") + + def testDebugEOFInQuote(self): + s = shlex.shlex('"oops', posix=True) + s.debug = 2 + with patch('builtins.print') as mock_print: + with self.assertRaises(ValueError): + list(s) + msgs = [call.args[0] for call in mock_print.call_args_list] + self.assertTrue(any("EOF in quotes" in m for m in msgs)) + + def testDebugEOFInEscape(self): + s = shlex.shlex("oops\\", posix=True) + s.debug = 2 + with patch("builtins.print") as mock_print: + with self.assertRaises(ValueError): + list(s) + msgs = [call.args[0] for call in mock_print.call_args_list] + self.assertTrue(any("EOF in escape" in m for m in msgs)) + + def testDebugStateTrace(self): + s = shlex.shlex("a") + s.debug = 3 + with patch("builtins.print") as mock_print: + list(s) + mock_print.assert_any_call("shlex: in state ' ' I see character: 'a'") + @cpython_only def test_lazy_imports(self): import_helper.ensure_lazy_imports('shlex', {'collections', 're', 'os'}) From cbd15390e474e254ad2590c57de7e3bf657c0a09 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 22 Jul 2026 11:15:22 +0300 Subject: [PATCH 04/10] gh-154441: Skip the scheduler tests if the API requires privileges (GH-154442) DragonFly BSD requires privileges to use the scheduler API, even for the calling process. Co-authored-by: Claude Opus 4.8 --- Lib/test/test_os/test_posix.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_os/test_posix.py b/Lib/test/test_os/test_posix.py index 79e234cbcd2ae25..299ab9774fc812c 100644 --- a/Lib/test/test_os/test_posix.py +++ b/Lib/test/test_os/test_posix.py @@ -46,7 +46,8 @@ def _supports_sched(): try: posix.sched_getscheduler(0) except OSError as e: - if e.errno == errno.ENOSYS: + # DragonFly BSD requires privileges to use the scheduler API. + if e.errno in (errno.ENOSYS, errno.EPERM): return False return True @@ -1453,6 +1454,7 @@ def test_bug_140634(self): del sched_priority, param # should not crash support.gc_collect() # just to be sure + @requires_sched @unittest.skipUnless(hasattr(posix, "sched_rr_get_interval"), "no function") def test_sched_rr_get_interval(self): try: From 2cf8e59a38251ff6dfbcd326104068be01c95385 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 22 Jul 2026 11:36:49 +0300 Subject: [PATCH 05/10] gh-154443: Fix test_makedev on DragonFly BSD (GH-154444) major() and minor() do not preserve NODEV on DragonFly BSD. Co-authored-by: Claude Opus 4.8 --- Lib/test/test_os/test_posix.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/test/test_os/test_posix.py b/Lib/test/test_os/test_posix.py index 299ab9774fc812c..41a730708974c25 100644 --- a/Lib/test/test_os/test_posix.py +++ b/Lib/test/test_os/test_posix.py @@ -824,8 +824,7 @@ def test_makedev(self): # a special case for NODEV, on others this is just an implementation # artifact. if (hasattr(posix, 'NODEV') and - sys.platform.startswith(('linux', 'macos', 'freebsd', 'dragonfly', - 'sunos'))): + sys.platform.startswith(('linux', 'macos', 'freebsd', 'sunos'))): NODEV = posix.NODEV self.assertEqual(posix.major(NODEV), NODEV) self.assertEqual(posix.minor(NODEV), NODEV) From 08a0d10709f04cf03260e5e852381cecb1c531e1 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 22 Jul 2026 11:39:11 +0300 Subject: [PATCH 06/10] gh-154435: Fix os.posix_fadvise() and os.posix_fallocate() on DragonFly BSD (GH-154436) They return -1 and set errno instead of returning the error number, so OSError was raised with a meaningless error code. Co-authored-by: Claude Opus 4.8 --- .../2026-07-22-10-30-08.gh-issue-154435.xCxm0T.rst | 3 +++ Modules/posixmodule.c | 8 ++++++++ 2 files changed, 11 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-07-22-10-30-08.gh-issue-154435.xCxm0T.rst diff --git a/Misc/NEWS.d/next/Library/2026-07-22-10-30-08.gh-issue-154435.xCxm0T.rst b/Misc/NEWS.d/next/Library/2026-07-22-10-30-08.gh-issue-154435.xCxm0T.rst new file mode 100644 index 000000000000000..b468e929b1ca18b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-22-10-30-08.gh-issue-154435.xCxm0T.rst @@ -0,0 +1,3 @@ +Fix :func:`os.posix_fadvise` and :func:`os.posix_fallocate` on DragonFly BSD: +they raised :exc:`OSError` with a meaningless error code, +because these functions return -1 and set ``errno`` there. diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 75ee7e260ce9850..de9575781881563 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -13508,6 +13508,10 @@ os_posix_fallocate_impl(PyObject *module, int fd, Py_off_t offset, Py_BEGIN_ALLOW_THREADS result = posix_fallocate(fd, offset, length); Py_END_ALLOW_THREADS + // DragonFly BSD returns -1 and sets errno. + if (result == -1) { + result = errno; + } } while (result == EINTR && !(async_err = PyErr_CheckSignals())); if (result == 0) @@ -13555,6 +13559,10 @@ os_posix_fadvise_impl(PyObject *module, int fd, Py_off_t offset, Py_BEGIN_ALLOW_THREADS result = posix_fadvise(fd, offset, length, advice); Py_END_ALLOW_THREADS + // DragonFly BSD returns -1 and sets errno. + if (result == -1) { + result = errno; + } } while (result == EINTR && !(async_err = PyErr_CheckSignals())); if (result == 0) From 1bf86c134a5260876ad31fb32b832cbea24c21ad Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 22 Jul 2026 12:05:06 +0300 Subject: [PATCH 07/10] gh-154437: Fix test_getcwd_long_path on DragonFly BSD (GH-154438) DragonFly BSD raises EFAULT instead of ENAMETOOLONG when the path exceeds PATH_MAX. Co-authored-by: Claude Opus 4.8 --- Lib/test/test_os/test_os.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_os/test_os.py b/Lib/test/test_os/test_os.py index c62bceb25cabe05..3e5ad52c4ab130d 100644 --- a/Lib/test/test_os/test_os.py +++ b/Lib/test/test_os/test_os.py @@ -156,7 +156,8 @@ def test_getcwd_long_path(self): # ("The filename or extension is too long") break except OSError as exc: - if exc.errno == errno.ENAMETOOLONG: + # DragonFly BSD raises EFAULT for a too long path. + if exc.errno in (errno.ENAMETOOLONG, errno.EFAULT): break else: raise From d1174a48ea8fda8bd0057f10e9776ec148276100 Mon Sep 17 00:00:00 2001 From: Bhuvansh Date: Wed, 22 Jul 2026 14:47:47 +0530 Subject: [PATCH 08/10] gh-154014: Initialize cold executor vm_data fields (GH-154142) --- Lib/test/test_capi/test_opt.py | 10 ++++++++++ .../2026-07-19-16-18-25.gh-issue-154014.CJsjVL.rst | 2 ++ Python/optimizer.c | 7 ++++++- 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-07-19-16-18-25.gh-issue-154014.CJsjVL.rst diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 25b2c393e6773de..5806216d46e7eb6 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -5127,6 +5127,16 @@ def f(): f" {executor} at offset {idx} rather" f" than expected _EXIT_TRACE") + def test_jit_shutdown_after_cold_executor_creation(self): + script_helper.assert_python_ok("-c", textwrap.dedent(f""" + def f(): + for x in range({TIER2_THRESHOLD + 3}): + for y in range({TIER2_THRESHOLD + 3}): + z = x + y + + f() + """), PYTHON_JIT="1") + def test_enter_executor_valid_op_arg(self): script_helper.assert_python_ok("-c", textwrap.dedent(""" import sys diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-19-16-18-25.gh-issue-154014.CJsjVL.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-19-16-18-25.gh-issue-154014.CJsjVL.rst new file mode 100644 index 000000000000000..6bdcf83725168a2 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-19-16-18-25.gh-issue-154014.CJsjVL.rst @@ -0,0 +1,2 @@ +Fix a JIT assertion during interpreter shutdown by initializing ``vm_data`` +fields for cold executors that bypass ``_Py_ExecutorInit()``. diff --git a/Python/optimizer.c b/Python/optimizer.c index c9f6ebdb62f07b2..e05adb344c8d06d 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -1797,6 +1797,11 @@ make_cold_executor(uint16_t opcode) Py_FatalError("Cannot allocate core JIT code"); } ((_PyUOpInstruction *)cold->trace)->opcode = opcode; + // Cold executors bypass _Py_ExecutorInit(). + cold->vm_data.valid = true; + cold->vm_data.pending_deletion = 0; + cold->vm_data.code = NULL; + // This is initialized to false so we can prevent the executor // from being immediately detected as cold and invalidated. cold->vm_data.cold = false; @@ -1804,7 +1809,7 @@ make_cold_executor(uint16_t opcode) cold->jit_code = NULL; cold->jit_size = 0; if (_PyJIT_Compile(cold, cold->trace, 1)) { - Py_DECREF(cold); + _PyExecutor_Free(cold); Py_FatalError("Cannot allocate core JIT code"); } #endif From 7dc920a5da4d519b2ce47d2598846762e78f979f Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 22 Jul 2026 13:35:42 +0300 Subject: [PATCH 09/10] gh-84649: Fix unstable test_rollover_at_midnight (GH-154463) Create the log file in a fresh directory under a name which has never been used. On Windows, NTFS file tunneling restored the original creation time of a file recreated with the same name, which made the rollover time earlier than the current time and caused an unwanted rollover. Also check that the rollover does not happen before the specified time. Co-authored-by: Claude Opus 4.8 (1M context) --- Lib/test/test_logging.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index a7ea128d64055a1..06b3aa66fc47a31 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -6704,7 +6704,14 @@ def add_record(message: str) -> None: self.assertTrue(found, msg=msg) def test_rollover_at_midnight(self, weekly=False): + # Create the log file in a fresh directory under a never used name: + # on Windows, NTFS file tunneling restores the original creation time + # of a file recreated with the same name. os_helper.unlink(self.fn) + dirname = tempfile.mkdtemp() + self.addCleanup(os_helper.rmtree, dirname) + self.fn = os.path.join(dirname, 'test_rollover.log') + # Emit the first records a little after the beginning of a whole # second, so that their file times fall inside that second and not the # previous one, which would cause an unwanted rollover. @@ -6730,7 +6737,24 @@ def test_rollover_at_midnight(self, weekly=False): # changed, so the rollover cannot be forced by back-dating the file. # Wait until the clock reaches a rollover time set one second ahead. rollover = int(time.time()) + 1 + if rollover - time.time() < 0.1: + # Leave time to emit a record before the rollover time. + rollover += 1 atTime = datetime.datetime.fromtimestamp(rollover).time() + rolloverDate = (datetime.datetime.fromtimestamp(rollover) + - datetime.timedelta(days=7 if weekly else 1)) + otherfn = f'{self.fn}.{rolloverDate:%Y-%m-%d}' + + # A record emitted before the rollover time is not rolled over. + fh = logging.handlers.TimedRotatingFileHandler( + self.fn, encoding="utf-8", when=when, atTime=atTime) + fh.setFormatter(fmt) + r2 = logging.makeLogRecord({'msg': 'testing1 3'}) + fh.emit(r2) + fh.close() + self.assertFalse(os.path.exists(otherfn), + msg=f'{otherfn} was rolled over too early') + while time.time() < rollover: time.sleep(rollover - time.time()) for i in range(2): @@ -6740,9 +6764,6 @@ def test_rollover_at_midnight(self, weekly=False): r2 = logging.makeLogRecord({'msg': f'testing2 {i}'}) fh.emit(r2) fh.close() - rolloverDate = (datetime.datetime.fromtimestamp(rollover) - - datetime.timedelta(days=7 if weekly else 1)) - otherfn = f'{self.fn}.{rolloverDate:%Y-%m-%d}' self.assertLogFile(otherfn) with open(self.fn, encoding="utf-8") as f: for i, line in enumerate(f): From 13e3f8aa839d71285f3408241b0518da499eac39 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 22 Jul 2026 14:15:41 +0300 Subject: [PATCH 10/10] gh-154272: Skip forkserver tests when the start method is unavailable (GH-154274) Co-authored-by: Claude Opus 4.8 (1M context) --- Lib/test/test_concurrent_futures/util.py | 2 ++ Lib/test/test_multiprocessing_forkserver/__init__.py | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/Lib/test/test_concurrent_futures/util.py b/Lib/test/test_concurrent_futures/util.py index 006360c8d941c9d..9dc1d057e75b4bc 100644 --- a/Lib/test/test_concurrent_futures/util.py +++ b/Lib/test/test_concurrent_futures/util.py @@ -139,6 +139,8 @@ def get_context(self): self.skipTest("require unix system") if support.check_sanitizer(thread=True): self.skipTest("TSAN doesn't support threads after fork") + if "forkserver" not in multiprocessing.get_all_start_methods(): + self.skipTest("forkserver start method is not available") return super().get_context() def create_event(self): diff --git a/Lib/test/test_multiprocessing_forkserver/__init__.py b/Lib/test/test_multiprocessing_forkserver/__init__.py index c58375e2861c62e..600285cbf613003 100644 --- a/Lib/test/test_multiprocessing_forkserver/__init__.py +++ b/Lib/test/test_multiprocessing_forkserver/__init__.py @@ -1,3 +1,4 @@ +import multiprocessing import os.path import sys import unittest @@ -12,5 +13,10 @@ if not support.has_fork_support: raise unittest.SkipTest("requires working os.fork()") +# The forkserver start method requires passing file descriptors over a Unix +# socket, which is not available on every platform (e.g. Solaris/illumos). +if "forkserver" not in multiprocessing.get_all_start_methods(): + raise unittest.SkipTest("forkserver start method is not available") + def load_tests(*args): return support.load_package_tests(os.path.dirname(__file__), *args)