Removed the Requirement to Install Python and NodeJS (Now Bundled with Borealis)

This commit is contained in:
2025-04-24 00:42:19 -06:00
parent 785265d3e7
commit 9c68cdea84
7786 changed files with 2386458 additions and 217 deletions

View File

@ -0,0 +1,11 @@
import os
import unittest
from test import support
if not support.Py_GIL_DISABLED:
raise unittest.SkipTest("GIL enabled")
def load_tests(*args):
return support.load_package_tests(os.path.dirname(__file__), *args)

View File

@ -0,0 +1,30 @@
import unittest
from threading import Thread
from unittest import TestCase
from test.support import threading_helper
@threading_helper.requires_working_threading()
class TestCode(TestCase):
def test_code_attrs(self):
"""Test concurrent accesses to lazily initialized code attributes"""
code_objects = []
for _ in range(1000):
code_objects.append(compile("a + b", "<string>", "eval"))
def run_in_thread():
for code in code_objects:
self.assertIsInstance(code.co_code, bytes)
self.assertIsInstance(code.co_freevars, tuple)
self.assertIsInstance(code.co_varnames, tuple)
threads = [Thread(target=run_in_thread) for _ in range(2)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
if __name__ == "__main__":
unittest.main()

View File

@ -0,0 +1,182 @@
import gc
import time
import unittest
import weakref
from ast import Or
from functools import partial
from threading import Thread
from unittest import TestCase
try:
import _testcapi
except ImportError:
_testcapi = None
from test.support import threading_helper
@threading_helper.requires_working_threading()
class TestDict(TestCase):
def test_racing_creation_shared_keys(self):
"""Verify that creating dictionaries is thread safe when we
have a type with shared keys"""
class C(int):
pass
self.racing_creation(C)
def test_racing_creation_no_shared_keys(self):
"""Verify that creating dictionaries is thread safe when we
have a type with an ordinary dict"""
self.racing_creation(Or)
def test_racing_creation_inline_values_invalid(self):
"""Verify that re-creating a dict after we have invalid inline values
is thread safe"""
class C:
pass
def make_obj():
a = C()
# Make object, make inline values invalid, and then delete dict
a.__dict__ = {}
del a.__dict__
return a
self.racing_creation(make_obj)
def test_racing_creation_nonmanaged_dict(self):
"""Verify that explicit creation of an unmanaged dict is thread safe
outside of the normal attribute setting code path"""
def make_obj():
def f(): pass
return f
def set(func, name, val):
# Force creation of the dict via PyObject_GenericGetDict
func.__dict__[name] = val
self.racing_creation(make_obj, set)
def racing_creation(self, cls, set=setattr):
objects = []
processed = []
OBJECT_COUNT = 100
THREAD_COUNT = 10
CUR = 0
for i in range(OBJECT_COUNT):
objects.append(cls())
def writer_func(name):
last = -1
while True:
if CUR == last:
continue
elif CUR == OBJECT_COUNT:
break
obj = objects[CUR]
set(obj, name, name)
last = CUR
processed.append(name)
writers = []
for x in range(THREAD_COUNT):
writer = Thread(target=partial(writer_func, f"a{x:02}"))
writers.append(writer)
writer.start()
for i in range(OBJECT_COUNT):
CUR = i
while len(processed) != THREAD_COUNT:
time.sleep(0.001)
processed.clear()
CUR = OBJECT_COUNT
for writer in writers:
writer.join()
for obj_idx, obj in enumerate(objects):
assert (
len(obj.__dict__) == THREAD_COUNT
), f"{len(obj.__dict__)} {obj.__dict__!r} {obj_idx}"
for i in range(THREAD_COUNT):
assert f"a{i:02}" in obj.__dict__, f"a{i:02} missing at {obj_idx}"
def test_racing_set_dict(self):
"""Races assigning to __dict__ should be thread safe"""
def f(): pass
l = []
THREAD_COUNT = 10
class MyDict(dict): pass
def writer_func(l):
for i in range(1000):
d = MyDict()
l.append(weakref.ref(d))
f.__dict__ = d
lists = []
writers = []
for x in range(THREAD_COUNT):
thread_list = []
lists.append(thread_list)
writer = Thread(target=partial(writer_func, thread_list))
writers.append(writer)
for writer in writers:
writer.start()
for writer in writers:
writer.join()
f.__dict__ = {}
gc.collect()
for thread_list in lists:
for ref in thread_list:
self.assertIsNone(ref())
@unittest.skipIf(_testcapi is None, 'need _testcapi module')
def test_dict_version(self):
dict_version = _testcapi.dict_version
THREAD_COUNT = 10
DICT_COUNT = 10000
lists = []
writers = []
def writer_func(thread_list):
for i in range(DICT_COUNT):
thread_list.append(dict_version({}))
for x in range(THREAD_COUNT):
thread_list = []
lists.append(thread_list)
writer = Thread(target=partial(writer_func, thread_list))
writers.append(writer)
for writer in writers:
writer.start()
for writer in writers:
writer.join()
total_len = 0
values = set()
for thread_list in lists:
for v in thread_list:
if v in values:
print('dup', v, (v/4096)%256)
values.add(v)
total_len += len(thread_list)
versions = set(dict_version for thread_list in lists for dict_version in thread_list)
self.assertEqual(len(versions), THREAD_COUNT*DICT_COUNT)
if __name__ == "__main__":
unittest.main()

View File

@ -0,0 +1,67 @@
import concurrent.futures
import unittest
import inspect
from threading import Thread, Barrier
from unittest import TestCase
from test.support import threading_helper, Py_GIL_DISABLED
threading_helper.requires_working_threading(module=True)
def get_func_annotation(f, b):
b.wait()
return inspect.get_annotations(f)
def get_func_annotation_dunder(f, b):
b.wait()
return f.__annotations__
def set_func_annotation(f, b):
b.wait()
f.__annotations__ = {'x': int, 'y': int, 'return': int}
return f.__annotations__
@unittest.skipUnless(Py_GIL_DISABLED, "Enable only in FT build")
class TestFTFuncAnnotations(TestCase):
NUM_THREADS = 8
def test_concurrent_read(self):
def f(x: int) -> int:
return x + 1
for _ in range(100):
with concurrent.futures.ThreadPoolExecutor(max_workers=self.NUM_THREADS) as executor:
b = Barrier(self.NUM_THREADS)
futures = {executor.submit(get_func_annotation, f, b): i for i in range(self.NUM_THREADS)}
for fut in concurrent.futures.as_completed(futures):
annotate = fut.result()
self.assertIsNotNone(annotate)
self.assertEqual(annotate, {'x': int, 'return': int})
with concurrent.futures.ThreadPoolExecutor(max_workers=self.NUM_THREADS) as executor:
b = Barrier(self.NUM_THREADS)
futures = {executor.submit(get_func_annotation_dunder, f, b): i for i in range(self.NUM_THREADS)}
for fut in concurrent.futures.as_completed(futures):
annotate = fut.result()
self.assertIsNotNone(annotate)
self.assertEqual(annotate, {'x': int, 'return': int})
def test_concurrent_write(self):
def bar(x: int, y: float) -> float:
return y ** x
for _ in range(100):
with concurrent.futures.ThreadPoolExecutor(max_workers=self.NUM_THREADS) as executor:
b = Barrier(self.NUM_THREADS)
futures = {executor.submit(set_func_annotation, bar, b): i for i in range(self.NUM_THREADS)}
for fut in concurrent.futures.as_completed(futures):
annotate = fut.result()
self.assertIsNotNone(annotate)
self.assertEqual(annotate, {'x': int, 'y': int, 'return': int})
# func_get_annotations returns in-place dict, so bar.__annotations__ should be modified as well
self.assertEqual(bar.__annotations__, {'x': int, 'y': int, 'return': int})

View File

@ -0,0 +1,61 @@
import unittest
import threading
from threading import Thread
from unittest import TestCase
import gc
from test.support import threading_helper
class MyObj:
pass
@threading_helper.requires_working_threading()
class TestGC(TestCase):
def test_get_objects(self):
event = threading.Event()
def gc_thread():
for i in range(100):
o = gc.get_objects()
event.set()
def mutator_thread():
while not event.is_set():
o1 = MyObj()
o2 = MyObj()
o3 = MyObj()
o4 = MyObj()
gcs = [Thread(target=gc_thread)]
mutators = [Thread(target=mutator_thread) for _ in range(4)]
with threading_helper.start_threads(gcs + mutators):
pass
def test_get_referrers(self):
event = threading.Event()
obj = MyObj()
def gc_thread():
for i in range(100):
o = gc.get_referrers(obj)
event.set()
def mutator_thread():
while not event.is_set():
d1 = { "key": obj }
d2 = { "key": obj }
d3 = { "key": obj }
d4 = { "key": obj }
gcs = [Thread(target=gc_thread) for _ in range(2)]
mutators = [Thread(target=mutator_thread) for _ in range(4)]
with threading_helper.start_threads(gcs + mutators):
pass
if __name__ == "__main__":
unittest.main()

View File

@ -0,0 +1,76 @@
import unittest
from threading import Thread
from unittest import TestCase
from test.support import threading_helper
NTHREAD = 10
OBJECT_COUNT = 5_000
class C:
def __init__(self, v):
self.v = v
@threading_helper.requires_working_threading()
class TestList(TestCase):
def test_racing_iter_append(self):
l = []
def writer_func():
for i in range(OBJECT_COUNT):
l.append(C(i + OBJECT_COUNT))
def reader_func():
while True:
count = len(l)
for i, x in enumerate(l):
self.assertEqual(x.v, i + OBJECT_COUNT)
if count == OBJECT_COUNT:
break
writer = Thread(target=writer_func)
readers = []
for x in range(NTHREAD):
reader = Thread(target=reader_func)
readers.append(reader)
reader.start()
writer.start()
writer.join()
for reader in readers:
reader.join()
def test_racing_iter_extend(self):
l = []
def writer_func():
for i in range(OBJECT_COUNT):
l.extend([C(i + OBJECT_COUNT)])
def reader_func():
while True:
count = len(l)
for i, x in enumerate(l):
self.assertEqual(x.v, i + OBJECT_COUNT)
if count == OBJECT_COUNT:
break
writer = Thread(target=writer_func)
readers = []
for x in range(NTHREAD):
reader = Thread(target=reader_func)
readers.append(reader)
reader.start()
writer.start()
writer.join()
for reader in readers:
reader.join()
if __name__ == "__main__":
unittest.main()

View File

@ -0,0 +1,248 @@
"""Tests monitoring, sys.settrace, and sys.setprofile in a multi-threaded
environment to verify things are thread-safe in a free-threaded build"""
import sys
import time
import unittest
import weakref
from sys import monitoring
from test.support import threading_helper
from threading import Thread, _PyRLock
from unittest import TestCase
class InstrumentationMultiThreadedMixin:
thread_count = 10
func_count = 50
fib = 12
def after_threads(self):
"""Runs once after all the threads have started"""
pass
def during_threads(self):
"""Runs repeatedly while the threads are still running"""
pass
def work(self, n, funcs):
"""Fibonacci function which also calls a bunch of random functions"""
for func in funcs:
func()
if n < 2:
return n
return self.work(n - 1, funcs) + self.work(n - 2, funcs)
def start_work(self, n, funcs):
# With the GIL builds we need to make sure that the hooks have
# a chance to run as it's possible to run w/o releasing the GIL.
time.sleep(0.1)
self.work(n, funcs)
def after_test(self):
"""Runs once after the test is done"""
pass
def test_instrumentation(self):
# Setup a bunch of functions which will need instrumentation...
funcs = []
for i in range(self.func_count):
x = {}
exec("def f(): pass", x)
funcs.append(x["f"])
threads = []
for i in range(self.thread_count):
# Each thread gets a copy of the func list to avoid contention
t = Thread(target=self.start_work, args=(self.fib, list(funcs)))
t.start()
threads.append(t)
self.after_threads()
while True:
any_alive = False
for t in threads:
if t.is_alive():
any_alive = True
break
if not any_alive:
break
self.during_threads()
self.after_test()
class MonitoringTestMixin:
def setUp(self):
for i in range(6):
if monitoring.get_tool(i) is None:
self.tool_id = i
monitoring.use_tool_id(i, self.__class__.__name__)
break
def tearDown(self):
monitoring.free_tool_id(self.tool_id)
@threading_helper.requires_working_threading()
class SetPreTraceMultiThreaded(InstrumentationMultiThreadedMixin, TestCase):
"""Sets tracing one time after the threads have started"""
def setUp(self):
super().setUp()
self.called = False
def after_test(self):
self.assertTrue(self.called)
def trace_func(self, frame, event, arg):
self.called = True
return self.trace_func
def after_threads(self):
sys.settrace(self.trace_func)
@threading_helper.requires_working_threading()
class MonitoringMultiThreaded(
MonitoringTestMixin, InstrumentationMultiThreadedMixin, TestCase
):
"""Uses sys.monitoring and repeatedly toggles instrumentation on and off"""
def setUp(self):
super().setUp()
self.set = False
self.called = False
monitoring.register_callback(
self.tool_id, monitoring.events.LINE, self.callback
)
def tearDown(self):
monitoring.set_events(self.tool_id, 0)
super().tearDown()
def callback(self, *args):
self.called = True
def after_test(self):
self.assertTrue(self.called)
def during_threads(self):
if self.set:
monitoring.set_events(
self.tool_id, monitoring.events.CALL | monitoring.events.LINE
)
else:
monitoring.set_events(self.tool_id, 0)
self.set = not self.set
@threading_helper.requires_working_threading()
class SetTraceMultiThreaded(InstrumentationMultiThreadedMixin, TestCase):
"""Uses sys.settrace and repeatedly toggles instrumentation on and off"""
def setUp(self):
self.set = False
self.called = False
def after_test(self):
self.assertTrue(self.called)
def tearDown(self):
sys.settrace(None)
def trace_func(self, frame, event, arg):
self.called = True
return self.trace_func
def during_threads(self):
if self.set:
sys.settrace(self.trace_func)
else:
sys.settrace(None)
self.set = not self.set
@threading_helper.requires_working_threading()
class SetProfileMultiThreaded(InstrumentationMultiThreadedMixin, TestCase):
"""Uses sys.setprofile and repeatedly toggles instrumentation on and off"""
def setUp(self):
self.set = False
self.called = False
def after_test(self):
self.assertTrue(self.called)
def tearDown(self):
sys.setprofile(None)
def trace_func(self, frame, event, arg):
self.called = True
return self.trace_func
def during_threads(self):
if self.set:
sys.setprofile(self.trace_func)
else:
sys.setprofile(None)
self.set = not self.set
@threading_helper.requires_working_threading()
class MonitoringMisc(MonitoringTestMixin, TestCase):
def register_callback(self):
def callback(*args):
pass
for i in range(200):
monitoring.register_callback(self.tool_id, monitoring.events.LINE, callback)
self.refs.append(weakref.ref(callback))
def test_register_callback(self):
self.refs = []
threads = []
for i in range(50):
t = Thread(target=self.register_callback)
t.start()
threads.append(t)
for thread in threads:
thread.join()
monitoring.register_callback(self.tool_id, monitoring.events.LINE, None)
for ref in self.refs:
self.assertEqual(ref(), None)
def test_set_local_trace_opcodes(self):
def trace(frame, event, arg):
frame.f_trace_opcodes = True
return trace
loops = 1_000
sys.settrace(trace)
try:
l = _PyRLock()
def f():
for i in range(loops):
with l:
pass
t = Thread(target=f)
t.start()
for i in range(loops):
with l:
pass
t.join()
finally:
sys.settrace(None)
if __name__ == "__main__":
unittest.main()

View File

@ -0,0 +1,41 @@
import unittest
from threading import Thread, Barrier
from unittest import TestCase
from test.support import threading_helper
@threading_helper.requires_working_threading()
class TestSet(TestCase):
def test_repr_clear(self):
"""Test repr() of a set while another thread is calling clear()"""
NUM_ITERS = 10
NUM_REPR_THREADS = 10
barrier = Barrier(NUM_REPR_THREADS + 1)
s = {1, 2, 3, 4, 5, 6, 7, 8}
def clear_set():
barrier.wait()
s.clear()
def repr_set():
barrier.wait()
set_reprs.append(repr(s))
for _ in range(NUM_ITERS):
set_reprs = []
threads = [Thread(target=clear_set)]
for _ in range(NUM_REPR_THREADS):
threads.append(Thread(target=repr_set))
for t in threads:
t.start()
for t in threads:
t.join()
for set_repr in set_reprs:
self.assertIn(set_repr, ("set()", "{1, 2, 3, 4, 5, 6, 7, 8}"))
if __name__ == "__main__":
unittest.main()

View File

@ -0,0 +1,43 @@
import threading
from test.support import threading_helper
from unittest import TestCase
def run_in_threads(targets):
"""Run `targets` in separate threads"""
threads = [
threading.Thread(target=target)
for target in targets
]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
@threading_helper.requires_working_threading()
class TestSlots(TestCase):
def test_object(self):
class Spam:
__slots__ = [
"eggs",
]
def __init__(self, initial_value):
self.eggs = initial_value
spam = Spam(0)
iters = 20_000
def writer():
for _ in range(iters):
spam.eggs += 1
def reader():
for _ in range(iters):
eggs = spam.eggs
assert type(eggs) is int
assert 0 <= eggs <= iters
run_in_threads([writer, reader, reader, reader])

View File

@ -0,0 +1,75 @@
import sys
import unittest
from itertools import cycle
from threading import Event, Thread
from unittest import TestCase
from test.support import threading_helper
@threading_helper.requires_working_threading()
class TestStr(TestCase):
def test_racing_join_extend(self):
'''Test joining a string being extended by another thread'''
l = []
ITERS = 100
READERS = 10
done_event = Event()
def writer_func():
for i in range(ITERS):
l.extend(map(str, range(i)))
l.clear()
done_event.set()
def reader_func():
while not done_event.is_set():
''.join(l)
writer = Thread(target=writer_func)
readers = []
for x in range(READERS):
reader = Thread(target=reader_func)
readers.append(reader)
reader.start()
writer.start()
writer.join()
for reader in readers:
reader.join()
def test_racing_join_replace(self):
'''
Test joining a string of characters being replaced with ephemeral
strings by another thread.
'''
l = [*'abcdefg']
MAX_ORDINAL = 1_000
READERS = 10
done_event = Event()
def writer_func():
for i, c in zip(cycle(range(len(l))),
map(chr, range(128, MAX_ORDINAL))):
l[i] = c
done_event.set()
def reader_func():
while not done_event.is_set():
''.join(l)
''.join(l)
''.join(l)
''.join(l)
writer = Thread(target=writer_func)
readers = []
for x in range(READERS):
reader = Thread(target=reader_func)
readers.append(reader)
reader.start()
writer.start()
writer.join()
for reader in readers:
reader.join()
if __name__ == "__main__":
unittest.main()

View File

@ -0,0 +1,57 @@
import io
import time
import unittest
import tokenize
from functools import partial
from threading import Thread
from test.support import threading_helper
@threading_helper.requires_working_threading()
class TestTokenize(unittest.TestCase):
def test_tokenizer_iter(self):
source = io.StringIO("for _ in a:\n pass")
it = tokenize._tokenize.TokenizerIter(source.readline, extra_tokens=False)
tokens = []
def next_token(it):
while True:
try:
r = next(it)
tokens.append(tokenize.TokenInfo._make(r))
time.sleep(0.03)
except StopIteration:
return
threads = []
for _ in range(5):
threads.append(Thread(target=partial(next_token, it)))
for thread in threads:
thread.start()
for thread in threads:
thread.join()
expected_tokens = [
tokenize.TokenInfo(type=1, string='for', start=(1, 0), end=(1, 3), line='for _ in a:\n'),
tokenize.TokenInfo(type=1, string='_', start=(1, 4), end=(1, 5), line='for _ in a:\n'),
tokenize.TokenInfo(type=1, string='in', start=(1, 6), end=(1, 8), line='for _ in a:\n'),
tokenize.TokenInfo(type=1, string='a', start=(1, 9), end=(1, 10), line='for _ in a:\n'),
tokenize.TokenInfo(type=11, string=':', start=(1, 10), end=(1, 11), line='for _ in a:\n'),
tokenize.TokenInfo(type=4, string='', start=(1, 11), end=(1, 11), line='for _ in a:\n'),
tokenize.TokenInfo(type=5, string='', start=(2, -1), end=(2, -1), line=' pass'),
tokenize.TokenInfo(type=1, string='pass', start=(2, 2), end=(2, 6), line=' pass'),
tokenize.TokenInfo(type=4, string='', start=(2, 6), end=(2, 6), line=' pass'),
tokenize.TokenInfo(type=6, string='', start=(2, -1), end=(2, -1), line=' pass'),
tokenize.TokenInfo(type=0, string='', start=(2, -1), end=(2, -1), line=' pass'),
]
tokens.sort()
expected_tokens.sort()
self.assertListEqual(tokens, expected_tokens)
if __name__ == "__main__":
unittest.main()

View File

@ -0,0 +1,157 @@
import threading
import unittest
from concurrent.futures import ThreadPoolExecutor
from threading import Thread
from unittest import TestCase
from test import support
from test.support import threading_helper
NTHREADS = 6
BOTTOM = 0
TOP = 1000
ITERS = 100
class A:
attr = 1
@threading_helper.requires_working_threading()
class TestType(TestCase):
def test_attr_cache(self):
def read(id0):
for _ in range(ITERS):
for _ in range(BOTTOM, TOP):
A.attr
def write(id0):
for _ in range(ITERS):
for _ in range(BOTTOM, TOP):
# Make _PyType_Lookup cache hot first
A.attr
A.attr
x = A.attr
x += 1
A.attr = x
with ThreadPoolExecutor(NTHREADS) as pool:
pool.submit(read, (1,))
pool.submit(write, (1,))
pool.shutdown(wait=True)
def test_attr_cache_consistency(self):
class C:
x = 0
DONE = False
def writer_func():
for i in range(3000):
C.x
C.x
C.x += 1
nonlocal DONE
DONE = True
def reader_func():
while True:
# We should always see a greater value read from the type than the
# dictionary
a = C.__dict__['x']
b = C.x
self.assertGreaterEqual(b, a)
if DONE:
break
self.run_one(writer_func, reader_func)
def test_attr_cache_consistency_subclass(self):
class C:
x = 0
class D(C):
pass
DONE = False
def writer_func():
for i in range(3000):
D.x
D.x
C.x += 1
nonlocal DONE
DONE = True
def reader_func():
while True:
# We should always see a greater value read from the type than the
# dictionary
a = C.__dict__['x']
b = D.x
self.assertGreaterEqual(b, a)
if DONE:
break
self.run_one(writer_func, reader_func)
def test___class___modification(self):
loops = 200
class Foo:
pass
class Bar:
pass
thing = Foo()
def work():
foo = thing
for _ in range(loops):
foo.__class__ = Bar
type(foo)
foo.__class__ = Foo
type(foo)
threads = []
for i in range(NTHREADS):
thread = threading.Thread(target=work)
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
def test_object_class_change(self):
class Base:
def __init__(self):
self.attr = 123
class ClassA(Base):
pass
class ClassB(Base):
pass
obj = ClassA()
# keep reference to __dict__
d = obj.__dict__
obj.__class__ = ClassB
def run_one(self, writer_func, reader_func):
writer = Thread(target=writer_func)
readers = []
for x in range(30):
reader = Thread(target=reader_func)
readers.append(reader)
reader.start()
writer.start()
writer.join()
for reader in readers:
reader.join()
if __name__ == "__main__":
unittest.main()