Skip to content

Commit e0222d2

Browse files
ambvmeta-codesync[bot]
authored andcommitted
Sync pre-release CPython 3.15 branch from GitHub (2026-06-17)
Summary: Imported python/cpython `3.15.0b2+dev` from upstream rev [`8fe5897`](https://www.github.com/python/cpython/commit/8fe5897853b1f5d8b7a1dc9fc2b72c6244a74983) (committed 2026-06-17 18:40:33+00:00). # Commit Info - Base: (`3.15.0b2+dev`) - [`ff6e973`](https://www.github.com/python/cpython/commit/ff6e973c3bbe419ff8aef16b05a1c77749c073c2) (commit date: 2026-06-16 16:48:56+00:00) - Imported: (`3.15.0b2+dev`) - [`8fe5897`](https://www.github.com/python/cpython/commit/8fe5897853b1f5d8b7a1dc9fc2b72c6244a74983) (commit date: 2026-06-17 18:40:33+00:00) # Noteworthy file changes - Low-signal files (5 added) (NEWS.d, docs, .github) Complete list of added/removed files: https://www.internalfb.com/intern/everpaste/?color=0&handle=GIDWfSILYS1aSKoEAAwdGYaOLpt8br0LAAAz Reviewed By: yoney Differential Revision: D108979122 fbshipit-source-id: e286d2b26425f315d5e5826febff4575e8e99738
1 parent f924661 commit e0222d2

20 files changed

Lines changed: 177 additions & 29 deletions

.github/workflows/reusable-san.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,12 @@ jobs:
8686
run: >-
8787
./python -m test
8888
${{ inputs.sanitizer == 'TSan' && '--tsan' || '' }}
89-
-j4 -W
89+
-j4 -W --timeout=900 --slowest
9090
- name: Parallel tests
9191
if: >-
9292
inputs.sanitizer == 'TSan'
9393
&& fromJSON(inputs.free-threading)
94-
run: ./python -m test --tsan-parallel --parallel-threads=4 -j4 -W
94+
run: ./python -m test --tsan-parallel --parallel-threads=4 -j4 -W --timeout=600 --slowest
9595
- name: Display logs
9696
if: always()
9797
run: find "${GITHUB_WORKSPACE}" -name 'san_log.*' | xargs head -n 1000

Lib/test/test_dtrace.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import dis
22
import os.path
33
import re
4+
import signal
45
import subprocess
56
import sys
67
import sysconfig
@@ -50,6 +51,24 @@ def normalize_trace_output(output):
5051
)
5152

5253

54+
USE_PROCESS_GROUP = (hasattr(os, "setsid") and hasattr(os, "killpg"))
55+
56+
def create_process_group(*args, **kwargs):
57+
if USE_PROCESS_GROUP:
58+
kwargs['start_new_session'] = True
59+
return subprocess.Popen(*args, **kwargs)
60+
61+
def kill_process_group(proc):
62+
if USE_PROCESS_GROUP:
63+
try:
64+
os.killpg(proc.pid, signal.SIGKILL)
65+
except ProcessLookupError:
66+
pass
67+
else:
68+
proc.kill()
69+
proc.communicate() # Clean up
70+
71+
5372
class TraceBackend:
5473
EXTENSION = None
5574
COMMAND = None
@@ -205,15 +224,15 @@ def run_case(self, name, optimize_python=None):
205224
program = self.PROGRAMS[name].format(python=sys.executable)
206225

207226
try:
208-
proc = subprocess.Popen(
227+
proc = create_process_group(
209228
["bpftrace", "-e", program, "-c", " ".join(subcommand)],
210229
stdout=subprocess.PIPE,
211230
stderr=subprocess.PIPE,
212231
universal_newlines=True,
213232
)
214233
stdout, stderr = proc.communicate(timeout=60)
215234
except subprocess.TimeoutExpired:
216-
proc.kill()
235+
kill_process_group(proc)
217236
raise AssertionError("bpftrace timed out")
218237
except (FileNotFoundError, PermissionError) as e:
219238
raise unittest.SkipTest(f"bpftrace not available: {e}")
@@ -243,16 +262,15 @@ def assert_usable(self):
243262
# Check if bpftrace is available and can attach to USDT probes
244263
program = f'usdt:{sys.executable}:python:function__entry {{ printf("probe: success\\n"); exit(); }}'
245264
try:
246-
proc = subprocess.Popen(
265+
proc = create_process_group(
247266
["bpftrace", "-e", program, "-c", f"{sys.executable} -c pass"],
248267
stdout=subprocess.PIPE,
249268
stderr=subprocess.PIPE,
250269
universal_newlines=True,
251270
)
252271
stdout, stderr = proc.communicate(timeout=10)
253272
except subprocess.TimeoutExpired:
254-
proc.kill()
255-
proc.communicate() # Clean up
273+
kill_process_group(proc)
256274
raise unittest.SkipTest("bpftrace timed out during usability check")
257275
except OSError as e:
258276
raise unittest.SkipTest(f"bpftrace not available: {e}")

Lib/test/test_lazy_import/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1957,6 +1957,17 @@ def filter(*args):
19571957
def test_set_bad_filter(self):
19581958
self.assertRaises(ValueError, _testcapi.PyImport_SetLazyImportsFilter, 42)
19591959

1960+
def test_dunder_lazy_import_without_frame(self):
1961+
# gh-151510: __lazy_import__() called with no globals and no running
1962+
# Python frame must raise TypeError instead of crashing.
1963+
with self.assertRaisesRegex(
1964+
TypeError,
1965+
r"__lazy_import__\(\) missing globals when called without a frame",
1966+
):
1967+
_testcapi.lazy_import_without_frame(
1968+
"test.test_lazy_import.data.basic2"
1969+
)
1970+
19601971

19611972
if __name__ == '__main__':
19621973
unittest.main()

Lib/test/test_profiling/test_sampling_profiler/test_live_collector_ui.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -825,17 +825,34 @@ def test_get_all_lines_full_display(self):
825825
class TestLiveModeErrors(unittest.TestCase):
826826
"""Tests running error commands in the live mode fails gracefully."""
827827

828+
class QuitWhenFinishedDisplay(MockDisplay):
829+
def __init__(self, collector):
830+
super().__init__()
831+
self.collector = collector
832+
833+
def get_input(self):
834+
ch = super().get_input()
835+
if ch != -1:
836+
return ch
837+
# Sampling only stops once the target process has exited, at
838+
# which point the collector is marked finished. Quit then so the
839+
# run can surface the target's stderr. We must not rely on the
840+
# target's pid still being signalable: once it exits it lingers
841+
# as a zombie (it is reaped after sample_live returns), so a
842+
# liveness check would never observe it gone and would hang.
843+
if self.collector.finished:
844+
return ord('q')
845+
return -1
846+
828847
def mock_curses_wrapper(self, func):
829848
func(mock.MagicMock())
830849

831850
def mock_init_curses_side_effect(self, n_times, mock_self, stdscr):
832-
mock_self.display = MockDisplay()
833-
# Allow the loop to run for a bit (approx 0.5s) before quitting
834-
# This ensures we don't exit too early while the subprocess is
835-
# still failing
851+
mock_self.display = self.QuitWhenFinishedDisplay(mock_self)
852+
# Feed non-input events so live mode keeps polling while the target
853+
# process is still running; once it exits the display quits on its own.
836854
for _ in range(n_times):
837855
mock_self.display.simulate_input(-1)
838-
mock_self.display.simulate_input(ord('q'))
839856

840857
def test_run_failed_module_live(self):
841858
"""Test that running a existing module that fails exits with clean error."""

Lib/test/test_unittest/test_loader.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import abc
12
import functools
23
import sys
34
import types
@@ -98,6 +99,22 @@ def test_loadTestsFromTestCase__from_FunctionTestCase(self):
9899
self.assertIsInstance(suite, loader.suiteClass)
99100
self.assertEqual(list(suite), [])
100101

102+
# "Do not load any tests from a TestCase-derived class that is an abstract
103+
# base class."
104+
def test_loadTestsFromTestCase__from_abc_TestCase(self):
105+
class FooBase(unittest.TestCase, metaclass=abc.ABCMeta):
106+
@abc.abstractmethod
107+
def test(self): ...
108+
class Foo(FooBase):
109+
def test(self): pass
110+
111+
empty_suite = unittest.TestSuite()
112+
113+
loader = unittest.TestLoader()
114+
suite = loader.loadTestsFromTestCase(Foo)
115+
self.assertEqual(loader.loadTestsFromTestCase(FooBase), empty_suite)
116+
self.assertEqual(list(suite), [Foo('test')])
117+
101118
################################################################
102119
### /Tests for TestLoader.loadTestsFromTestCase
103120

@@ -252,6 +269,24 @@ def load_tests(loader, tests, pattern):
252269

253270
self.assertRaisesRegex(TypeError, "some failure", test.m)
254271

272+
# Check that loadTestsFromModule skips abstract base classes derived from
273+
# TestCase, which can't be instantiated.
274+
def test_loadTestsFromModule__skip_abc_TestCase(self):
275+
m = types.ModuleType('m')
276+
class MyTestCaseBase(unittest.TestCase, metaclass=abc.ABCMeta):
277+
@abc.abstractmethod
278+
def test(self):
279+
...
280+
class MyTestCase(MyTestCaseBase):
281+
def test(self):
282+
pass
283+
m.testcase_1 = MyTestCaseBase
284+
m.testcase_2 = MyTestCase
285+
loader = unittest.TestLoader()
286+
suite = loader.loadTestsFromModule(m)
287+
expected = [loader.suiteClass([MyTestCase('test')])]
288+
self.assertEqual(list(suite), expected)
289+
255290
################################################################
256291
### /Tests for TestLoader.loadTestsFromModule()
257292

@@ -1052,6 +1087,22 @@ def test_loadTestsFromNames__module_not_loaded(self):
10521087
if module_name in sys.modules:
10531088
del sys.modules[module_name]
10541089

1090+
# "The specifier should not refer to a test method in a TestCase-derived
1091+
# subclass that is an abstract base class"
1092+
def test_loadTestsFromNames__testmethod_in_abc_TestCase(self):
1093+
m = types.ModuleType('m')
1094+
class Foo(unittest.TestCase, metaclass=abc.ABCMeta):
1095+
@abc.abstractmethod
1096+
def test_1(self): ...
1097+
def test_2(self): pass
1098+
m.Foo = Foo
1099+
1100+
loader = unittest.TestLoader()
1101+
for name in 'Foo.test_1', 'Foo.test_2':
1102+
with self.subTest(name=name), self.assertRaisesRegex(TypeError,
1103+
"Cannot instantiate abstract test case Foo"):
1104+
loader.loadTestsFromNames([name], m)
1105+
10551106
################################################################
10561107
### /Tests for TestLoader.loadTestsFromNames()
10571108

Lib/unittest/loader.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Loading unittests."""
22

3+
import inspect
34
import os
45
import re
56
import sys
@@ -84,8 +85,10 @@ def loadTestsFromTestCase(self, testCaseClass):
8485
raise TypeError("Test cases should not be derived from "
8586
"TestSuite. Maybe you meant to derive from "
8687
"TestCase?")
87-
if testCaseClass in (case.TestCase, case.FunctionTestCase):
88-
# We don't load any tests from base types that should not be loaded.
88+
if (testCaseClass in (case.TestCase, case.FunctionTestCase) or
89+
inspect.isabstract(testCaseClass)):
90+
# We don't load any tests from base types that should not be loaded,
91+
# and abstract base classes that can't be instantiated
8992
testCaseNames = []
9093
else:
9194
testCaseNames = self.getTestCaseNames(testCaseClass)
@@ -103,6 +106,7 @@ def loadTestsFromModule(self, module, *, pattern=None):
103106
isinstance(obj, type)
104107
and issubclass(obj, case.TestCase)
105108
and obj not in (case.TestCase, case.FunctionTestCase)
109+
and not inspect.isabstract(obj)
106110
):
107111
tests.append(self.loadTestsFromTestCase(obj))
108112

@@ -181,6 +185,9 @@ def loadTestsFromName(self, name, module=None):
181185
elif (isinstance(obj, types.FunctionType) and
182186
isinstance(parent, type) and
183187
issubclass(parent, case.TestCase)):
188+
if inspect.isabstract(parent):
189+
raise TypeError(
190+
"Cannot instantiate abstract test case %s" % parent.__name__)
184191
name = parts[-1]
185192
inst = parent(name)
186193
# static methods follow a different path

Makefile.pre.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1709,6 +1709,8 @@ Programs/_bootstrap_python.o: Programs/_bootstrap_python.c $(BOOTSTRAP_HEADERS)
17091709
_bootstrap_python: $(LIBRARY_OBJS_OMIT_FROZEN) Programs/_bootstrap_python.o Modules/getpath.o Modules/Setup.local
17101710
$(LINKCC) $(PY_LDFLAGS_NOLTO) -o $@ $(LIBRARY_OBJS_OMIT_FROZEN) \
17111711
Programs/_bootstrap_python.o Modules/getpath.o $(LIBS) $(MODLIBS) $(SYSLIBS)
1712+
# Dummy pybuilddir.txt is needed for _bootstrap_python to be runnable
1713+
@echo "none" > ./pybuilddir.txt
17121714

17131715

17141716
############################################################################
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a crash in :func:`!__lazy_import__` when called without an explicit
2+
``globals`` argument and without a current Python frame.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix the stack limit check if Python is linked to musl (ex: Alpine Linux).
2+
Use the stack size set by the linker to compute the stack limits. Patch by
3+
Victor Stinner.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed an issue where ``unittest`` loaders would load and instantiate :class:`unittest.TestCase`-derived subclasses that are also abstract base classes, which can't be instantiated.

0 commit comments

Comments
 (0)