summaryrefslogtreecommitdiff
path: root/venv/lib/python3.11/site-packages/greenlet/tests
diff options
context:
space:
mode:
authorcyfraeviolae <cyfraeviolae>2024-04-03 03:10:44 -0400
committercyfraeviolae <cyfraeviolae>2024-04-03 03:10:44 -0400
commit6d7ba58f880be618ade07f8ea080fe8c4bf8a896 (patch)
treeb1c931051ffcebd2bd9d61d98d6233ffa289bbce /venv/lib/python3.11/site-packages/greenlet/tests
parent4f884c9abc32990b4061a1bb6997b4b37e58ea0b (diff)
venv
Diffstat (limited to 'venv/lib/python3.11/site-packages/greenlet/tests')
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/__init__.py237
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/__init__.cpython-311.pycbin0 -> 9744 bytes
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/fail_clearing_run_switches.cpython-311.pycbin0 -> 2405 bytes
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/fail_cpp_exception.cpython-311.pycbin0 -> 1721 bytes
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/fail_initialstub_already_started.cpython-311.pycbin0 -> 3881 bytes
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/fail_slp_switch.cpython-311.pycbin0 -> 1432 bytes
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/fail_switch_three_greenlets.cpython-311.pycbin0 -> 1898 bytes
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/fail_switch_three_greenlets2.cpython-311.pycbin0 -> 2906 bytes
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/fail_switch_two_greenlets.cpython-311.pycbin0 -> 1901 bytes
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/leakcheck.cpython-311.pycbin0 -> 12777 bytes
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_contextvars.cpython-311.pycbin0 -> 18611 bytes
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_cpp.cpython-311.pycbin0 -> 4567 bytes
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_extension_interface.cpython-311.pycbin0 -> 8576 bytes
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_gc.cpython-311.pycbin0 -> 5518 bytes
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_generator.cpython-311.pycbin0 -> 3540 bytes
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_generator_nested.cpython-311.pycbin0 -> 9394 bytes
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_greenlet.cpython-311.pycbin0 -> 86450 bytes
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_greenlet_trash.cpython-311.pycbin0 -> 7015 bytes
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_leaks.cpython-311.pycbin0 -> 21765 bytes
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_stack_saved.cpython-311.pycbin0 -> 1514 bytes
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_throw.cpython-311.pycbin0 -> 8934 bytes
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_tracing.cpython-311.pycbin0 -> 15840 bytes
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_version.cpython-311.pycbin0 -> 2927 bytes
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_weakref.cpython-311.pycbin0 -> 3080 bytes
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/_test_extension.c231
-rwxr-xr-xvenv/lib/python3.11/site-packages/greenlet/tests/_test_extension.cpython-311-x86_64-linux-gnu.sobin0 -> 36624 bytes
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/_test_extension_cpp.cpp226
-rwxr-xr-xvenv/lib/python3.11/site-packages/greenlet/tests/_test_extension_cpp.cpython-311-x86_64-linux-gnu.sobin0 -> 57288 bytes
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/fail_clearing_run_switches.py47
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/fail_cpp_exception.py33
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/fail_initialstub_already_started.py78
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/fail_slp_switch.py29
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/fail_switch_three_greenlets.py44
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/fail_switch_three_greenlets2.py55
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/fail_switch_two_greenlets.py41
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/leakcheck.py319
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/test_contextvars.py310
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/test_cpp.py73
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/test_extension_interface.py115
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/test_gc.py86
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/test_generator.py59
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/test_generator_nested.py168
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/test_greenlet.py1311
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/test_greenlet_trash.py178
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/test_leaks.py443
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/test_stack_saved.py19
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/test_throw.py128
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/test_tracing.py291
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/test_version.py41
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/test_weakref.py35
50 files changed, 4597 insertions, 0 deletions
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/__init__.py b/venv/lib/python3.11/site-packages/greenlet/tests/__init__.py
new file mode 100644
index 0000000..e249e35
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/__init__.py
@@ -0,0 +1,237 @@
+# -*- coding: utf-8 -*-
+"""
+Tests for greenlet.
+
+"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import sys
+import unittest
+
+from gc import collect
+from gc import get_objects
+from threading import active_count as active_thread_count
+from time import sleep
+from time import time
+
+import psutil
+
+from greenlet import greenlet as RawGreenlet
+from greenlet import getcurrent
+
+from greenlet._greenlet import get_pending_cleanup_count
+from greenlet._greenlet import get_total_main_greenlets
+
+from . import leakcheck
+
+PY312 = sys.version_info[:2] >= (3, 12)
+WIN = sys.platform.startswith("win")
+
+class TestCaseMetaClass(type):
+ # wrap each test method with
+ # a) leak checks
+ def __new__(cls, classname, bases, classDict):
+ # pylint and pep8 fight over what this should be called (mcs or cls).
+ # pylint gets it right, but we can't scope disable pep8, so we go with
+ # its convention.
+ # pylint: disable=bad-mcs-classmethod-argument
+ check_totalrefcount = True
+
+ # Python 3: must copy, we mutate the classDict. Interestingly enough,
+ # it doesn't actually error out, but under 3.6 we wind up wrapping
+ # and re-wrapping the same items over and over and over.
+ for key, value in list(classDict.items()):
+ if key.startswith('test') and callable(value):
+ classDict.pop(key)
+ if check_totalrefcount:
+ value = leakcheck.wrap_refcount(value)
+ classDict[key] = value
+ return type.__new__(cls, classname, bases, classDict)
+
+
+class TestCase(TestCaseMetaClass(
+ "NewBase",
+ (unittest.TestCase,),
+ {})):
+
+ cleanup_attempt_sleep_duration = 0.001
+ cleanup_max_sleep_seconds = 1
+
+ def wait_for_pending_cleanups(self,
+ initial_active_threads=None,
+ initial_main_greenlets=None):
+ initial_active_threads = initial_active_threads or self.threads_before_test
+ initial_main_greenlets = initial_main_greenlets or self.main_greenlets_before_test
+ sleep_time = self.cleanup_attempt_sleep_duration
+ # NOTE: This is racy! A Python-level thread object may be dead
+ # and gone, but the C thread may not yet have fired its
+ # destructors and added to the queue. There's no particular
+ # way to know that's about to happen. We try to watch the
+ # Python threads to make sure they, at least, have gone away.
+ # Counting the main greenlets, which we can easily do deterministically,
+ # also helps.
+
+ # Always sleep at least once to let other threads run
+ sleep(sleep_time)
+ quit_after = time() + self.cleanup_max_sleep_seconds
+ # TODO: We could add an API that calls us back when a particular main greenlet is deleted?
+ # It would have to drop the GIL
+ while (
+ get_pending_cleanup_count()
+ or active_thread_count() > initial_active_threads
+ or (not self.expect_greenlet_leak
+ and get_total_main_greenlets() > initial_main_greenlets)):
+ sleep(sleep_time)
+ if time() > quit_after:
+ print("Time limit exceeded.")
+ print("Threads: Waiting for only", initial_active_threads,
+ "-->", active_thread_count())
+ print("MGlets : Waiting for only", initial_main_greenlets,
+ "-->", get_total_main_greenlets())
+ break
+ collect()
+
+ def count_objects(self, kind=list, exact_kind=True):
+ # pylint:disable=unidiomatic-typecheck
+ # Collect the garbage.
+ for _ in range(3):
+ collect()
+ if exact_kind:
+ return sum(
+ 1
+ for x in get_objects()
+ if type(x) is kind
+ )
+ # instances
+ return sum(
+ 1
+ for x in get_objects()
+ if isinstance(x, kind)
+ )
+
+ greenlets_before_test = 0
+ threads_before_test = 0
+ main_greenlets_before_test = 0
+ expect_greenlet_leak = False
+
+ def count_greenlets(self):
+ """
+ Find all the greenlets and subclasses tracked by the GC.
+ """
+ return self.count_objects(RawGreenlet, False)
+
+ def setUp(self):
+ # Ensure the main greenlet exists, otherwise the first test
+ # gets a false positive leak
+ super().setUp()
+ getcurrent()
+ self.threads_before_test = active_thread_count()
+ self.main_greenlets_before_test = get_total_main_greenlets()
+ self.wait_for_pending_cleanups(self.threads_before_test, self.main_greenlets_before_test)
+ self.greenlets_before_test = self.count_greenlets()
+
+ def tearDown(self):
+ if getattr(self, 'skipTearDown', False):
+ return
+
+ self.wait_for_pending_cleanups(self.threads_before_test, self.main_greenlets_before_test)
+ super().tearDown()
+
+ def get_expected_returncodes_for_aborted_process(self):
+ import signal
+ # The child should be aborted in an unusual way. On POSIX
+ # platforms, this is done with abort() and signal.SIGABRT,
+ # which is reflected in a negative return value; however, on
+ # Windows, even though we observe the child print "Fatal
+ # Python error: Aborted" and in older versions of the C
+ # runtime "This application has requested the Runtime to
+ # terminate it in an unusual way," it always has an exit code
+ # of 3. This is interesting because 3 is the error code for
+ # ERROR_PATH_NOT_FOUND; BUT: the C runtime abort() function
+ # also uses this code.
+ #
+ # If we link to the static C library on Windows, the error
+ # code changes to '0xc0000409' (hex(3221226505)), which
+ # apparently is STATUS_STACK_BUFFER_OVERRUN; but "What this
+ # means is that nowadays when you get a
+ # STATUS_STACK_BUFFER_OVERRUN, it doesn’t actually mean that
+ # there is a stack buffer overrun. It just means that the
+ # application decided to terminate itself with great haste."
+ #
+ #
+ # On windows, we've also seen '0xc0000005' (hex(3221225477)).
+ # That's "Access Violation"
+ #
+ # See
+ # https://devblogs.microsoft.com/oldnewthing/20110519-00/?p=10623
+ # and
+ # https://docs.microsoft.com/en-us/previous-versions/k089yyh0(v=vs.140)?redirectedfrom=MSDN
+ # and
+ # https://devblogs.microsoft.com/oldnewthing/20190108-00/?p=100655
+ expected_exit = (
+ -signal.SIGABRT,
+ # But beginning on Python 3.11, the faulthandler
+ # that prints the C backtraces sometimes segfaults after
+ # reporting the exception but before printing the stack.
+ # This has only been seen on linux/gcc.
+ -signal.SIGSEGV,
+ ) if not WIN else (
+ 3,
+ 0xc0000409,
+ 0xc0000005,
+ )
+ return expected_exit
+
+ def get_process_uss(self):
+ """
+ Return the current process's USS in bytes.
+
+ uss is available on Linux, macOS, Windows. Also known as
+ "Unique Set Size", this is the memory which is unique to a
+ process and which would be freed if the process was terminated
+ right now.
+
+ If this is not supported by ``psutil``, this raises the
+ :exc:`unittest.SkipTest` exception.
+ """
+ try:
+ return psutil.Process().memory_full_info().uss
+ except AttributeError as e:
+ raise unittest.SkipTest("uss not supported") from e
+
+ def run_script(self, script_name, show_output=True):
+ import subprocess
+ import os
+ script = os.path.join(
+ os.path.dirname(__file__),
+ script_name,
+ )
+
+ try:
+ return subprocess.check_output([sys.executable, script],
+ encoding='utf-8',
+ stderr=subprocess.STDOUT)
+ except subprocess.CalledProcessError as ex:
+ if show_output:
+ print('-----')
+ print('Failed to run script', script)
+ print('~~~~~')
+ print(ex.output)
+ print('------')
+ raise
+
+
+ def assertScriptRaises(self, script_name, exitcodes=None):
+ import subprocess
+ with self.assertRaises(subprocess.CalledProcessError) as exc:
+ output = self.run_script(script_name, show_output=False)
+ __traceback_info__ = output
+ # We're going to fail the assertion if we get here, at least
+ # preserve the output in the traceback.
+
+ if exitcodes is None:
+ exitcodes = self.get_expected_returncodes_for_aborted_process()
+ self.assertIn(exc.exception.returncode, exitcodes)
+ return exc.exception
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000..245565c
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/__init__.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/fail_clearing_run_switches.cpython-311.pyc b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/fail_clearing_run_switches.cpython-311.pyc
new file mode 100644
index 0000000..f8ca571
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/fail_clearing_run_switches.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/fail_cpp_exception.cpython-311.pyc b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/fail_cpp_exception.cpython-311.pyc
new file mode 100644
index 0000000..1293a2e
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/fail_cpp_exception.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/fail_initialstub_already_started.cpython-311.pyc b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/fail_initialstub_already_started.cpython-311.pyc
new file mode 100644
index 0000000..12af9c5
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/fail_initialstub_already_started.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/fail_slp_switch.cpython-311.pyc b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/fail_slp_switch.cpython-311.pyc
new file mode 100644
index 0000000..41fa814
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/fail_slp_switch.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/fail_switch_three_greenlets.cpython-311.pyc b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/fail_switch_three_greenlets.cpython-311.pyc
new file mode 100644
index 0000000..73d0e65
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/fail_switch_three_greenlets.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/fail_switch_three_greenlets2.cpython-311.pyc b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/fail_switch_three_greenlets2.cpython-311.pyc
new file mode 100644
index 0000000..dcdbc02
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/fail_switch_three_greenlets2.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/fail_switch_two_greenlets.cpython-311.pyc b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/fail_switch_two_greenlets.cpython-311.pyc
new file mode 100644
index 0000000..97be12a
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/fail_switch_two_greenlets.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/leakcheck.cpython-311.pyc b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/leakcheck.cpython-311.pyc
new file mode 100644
index 0000000..7a2616e
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/leakcheck.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_contextvars.cpython-311.pyc b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_contextvars.cpython-311.pyc
new file mode 100644
index 0000000..afb0e23
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_contextvars.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_cpp.cpython-311.pyc b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_cpp.cpython-311.pyc
new file mode 100644
index 0000000..3f24186
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_cpp.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_extension_interface.cpython-311.pyc b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_extension_interface.cpython-311.pyc
new file mode 100644
index 0000000..987e490
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_extension_interface.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_gc.cpython-311.pyc b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_gc.cpython-311.pyc
new file mode 100644
index 0000000..c4edf24
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_gc.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_generator.cpython-311.pyc b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_generator.cpython-311.pyc
new file mode 100644
index 0000000..936decb
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_generator.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_generator_nested.cpython-311.pyc b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_generator_nested.cpython-311.pyc
new file mode 100644
index 0000000..fa8ca9c
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_generator_nested.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_greenlet.cpython-311.pyc b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_greenlet.cpython-311.pyc
new file mode 100644
index 0000000..6f4f510
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_greenlet.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_greenlet_trash.cpython-311.pyc b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_greenlet_trash.cpython-311.pyc
new file mode 100644
index 0000000..e40225d
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_greenlet_trash.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_leaks.cpython-311.pyc b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_leaks.cpython-311.pyc
new file mode 100644
index 0000000..88e931a
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_leaks.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_stack_saved.cpython-311.pyc b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_stack_saved.cpython-311.pyc
new file mode 100644
index 0000000..b3f464d
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_stack_saved.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_throw.cpython-311.pyc b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_throw.cpython-311.pyc
new file mode 100644
index 0000000..2427360
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_throw.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_tracing.cpython-311.pyc b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_tracing.cpython-311.pyc
new file mode 100644
index 0000000..a488241
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_tracing.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_version.cpython-311.pyc b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_version.cpython-311.pyc
new file mode 100644
index 0000000..16d09c1
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_version.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_weakref.cpython-311.pyc b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_weakref.cpython-311.pyc
new file mode 100644
index 0000000..ba0b403
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/__pycache__/test_weakref.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/_test_extension.c b/venv/lib/python3.11/site-packages/greenlet/tests/_test_extension.c
new file mode 100644
index 0000000..05e81c0
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/_test_extension.c
@@ -0,0 +1,231 @@
+/* This is a set of functions used by test_extension_interface.py to test the
+ * Greenlet C API.
+ */
+
+#include "../greenlet.h"
+
+#ifndef Py_RETURN_NONE
+# define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None
+#endif
+
+#define TEST_MODULE_NAME "_test_extension"
+
+static PyObject*
+test_switch(PyObject* self, PyObject* greenlet)
+{
+ PyObject* result = NULL;
+
+ if (greenlet == NULL || !PyGreenlet_Check(greenlet)) {
+ PyErr_BadArgument();
+ return NULL;
+ }
+
+ result = PyGreenlet_Switch((PyGreenlet*)greenlet, NULL, NULL);
+ if (result == NULL) {
+ if (!PyErr_Occurred()) {
+ PyErr_SetString(PyExc_AssertionError,
+ "greenlet.switch() failed for some reason.");
+ }
+ return NULL;
+ }
+ Py_INCREF(result);
+ return result;
+}
+
+static PyObject*
+test_switch_kwargs(PyObject* self, PyObject* args, PyObject* kwargs)
+{
+ PyGreenlet* g = NULL;
+ PyObject* result = NULL;
+
+ PyArg_ParseTuple(args, "O!", &PyGreenlet_Type, &g);
+
+ if (g == NULL || !PyGreenlet_Check(g)) {
+ PyErr_BadArgument();
+ return NULL;
+ }
+
+ result = PyGreenlet_Switch(g, NULL, kwargs);
+ if (result == NULL) {
+ if (!PyErr_Occurred()) {
+ PyErr_SetString(PyExc_AssertionError,
+ "greenlet.switch() failed for some reason.");
+ }
+ return NULL;
+ }
+ Py_XINCREF(result);
+ return result;
+}
+
+static PyObject*
+test_getcurrent(PyObject* self)
+{
+ PyGreenlet* g = PyGreenlet_GetCurrent();
+ if (g == NULL || !PyGreenlet_Check(g) || !PyGreenlet_ACTIVE(g)) {
+ PyErr_SetString(PyExc_AssertionError,
+ "getcurrent() returned an invalid greenlet");
+ Py_XDECREF(g);
+ return NULL;
+ }
+ Py_DECREF(g);
+ Py_RETURN_NONE;
+}
+
+static PyObject*
+test_setparent(PyObject* self, PyObject* arg)
+{
+ PyGreenlet* current;
+ PyGreenlet* greenlet = NULL;
+
+ if (arg == NULL || !PyGreenlet_Check(arg)) {
+ PyErr_BadArgument();
+ return NULL;
+ }
+ if ((current = PyGreenlet_GetCurrent()) == NULL) {
+ return NULL;
+ }
+ greenlet = (PyGreenlet*)arg;
+ if (PyGreenlet_SetParent(greenlet, current)) {
+ Py_DECREF(current);
+ return NULL;
+ }
+ Py_DECREF(current);
+ if (PyGreenlet_Switch(greenlet, NULL, NULL) == NULL) {
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+static PyObject*
+test_new_greenlet(PyObject* self, PyObject* callable)
+{
+ PyObject* result = NULL;
+ PyGreenlet* greenlet = PyGreenlet_New(callable, NULL);
+
+ if (!greenlet) {
+ return NULL;
+ }
+
+ result = PyGreenlet_Switch(greenlet, NULL, NULL);
+ Py_CLEAR(greenlet);
+ if (result == NULL) {
+ return NULL;
+ }
+
+ Py_INCREF(result);
+ return result;
+}
+
+static PyObject*
+test_raise_dead_greenlet(PyObject* self)
+{
+ PyErr_SetString(PyExc_GreenletExit, "test GreenletExit exception.");
+ return NULL;
+}
+
+static PyObject*
+test_raise_greenlet_error(PyObject* self)
+{
+ PyErr_SetString(PyExc_GreenletError, "test greenlet.error exception");
+ return NULL;
+}
+
+static PyObject*
+test_throw(PyObject* self, PyGreenlet* g)
+{
+ const char msg[] = "take that sucka!";
+ PyObject* msg_obj = Py_BuildValue("s", msg);
+ PyGreenlet_Throw(g, PyExc_ValueError, msg_obj, NULL);
+ Py_DECREF(msg_obj);
+ if (PyErr_Occurred()) {
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+static PyObject*
+test_throw_exact(PyObject* self, PyObject* args)
+{
+ PyGreenlet* g = NULL;
+ PyObject* typ = NULL;
+ PyObject* val = NULL;
+ PyObject* tb = NULL;
+
+ if (!PyArg_ParseTuple(args, "OOOO:throw", &g, &typ, &val, &tb)) {
+ return NULL;
+ }
+
+ PyGreenlet_Throw(g, typ, val, tb);
+ if (PyErr_Occurred()) {
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef test_methods[] = {
+ {"test_switch",
+ (PyCFunction)test_switch,
+ METH_O,
+ "Switch to the provided greenlet sending provided arguments, and \n"
+ "return the results."},
+ {"test_switch_kwargs",
+ (PyCFunction)test_switch_kwargs,
+ METH_VARARGS | METH_KEYWORDS,
+ "Switch to the provided greenlet sending the provided keyword args."},
+ {"test_getcurrent",
+ (PyCFunction)test_getcurrent,
+ METH_NOARGS,
+ "Test PyGreenlet_GetCurrent()"},
+ {"test_setparent",
+ (PyCFunction)test_setparent,
+ METH_O,
+ "Se the parent of the provided greenlet and switch to it."},
+ {"test_new_greenlet",
+ (PyCFunction)test_new_greenlet,
+ METH_O,
+ "Test PyGreenlet_New()"},
+ {"test_raise_dead_greenlet",
+ (PyCFunction)test_raise_dead_greenlet,
+ METH_NOARGS,
+ "Just raise greenlet.GreenletExit"},
+ {"test_raise_greenlet_error",
+ (PyCFunction)test_raise_greenlet_error,
+ METH_NOARGS,
+ "Just raise greenlet.error"},
+ {"test_throw",
+ (PyCFunction)test_throw,
+ METH_O,
+ "Throw a ValueError at the provided greenlet"},
+ {"test_throw_exact",
+ (PyCFunction)test_throw_exact,
+ METH_VARARGS,
+ "Throw exactly the arguments given at the provided greenlet"},
+ {NULL, NULL, 0, NULL}
+};
+
+
+#define INITERROR return NULL
+
+static struct PyModuleDef moduledef = {PyModuleDef_HEAD_INIT,
+ TEST_MODULE_NAME,
+ NULL,
+ 0,
+ test_methods,
+ NULL,
+ NULL,
+ NULL,
+ NULL};
+
+PyMODINIT_FUNC
+PyInit__test_extension(void)
+{
+ PyObject* module = NULL;
+ module = PyModule_Create(&moduledef);
+
+ if (module == NULL) {
+ return NULL;
+ }
+
+ PyGreenlet_Import();
+ return module;
+}
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/_test_extension.cpython-311-x86_64-linux-gnu.so b/venv/lib/python3.11/site-packages/greenlet/tests/_test_extension.cpython-311-x86_64-linux-gnu.so
new file mode 100755
index 0000000..2c7fff0
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/_test_extension.cpython-311-x86_64-linux-gnu.so
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/_test_extension_cpp.cpp b/venv/lib/python3.11/site-packages/greenlet/tests/_test_extension_cpp.cpp
new file mode 100644
index 0000000..5cbe6a7
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/_test_extension_cpp.cpp
@@ -0,0 +1,226 @@
+/* This is a set of functions used to test C++ exceptions are not
+ * broken during greenlet switches
+ */
+
+#include "../greenlet.h"
+#include "../greenlet_compiler_compat.hpp"
+#include <exception>
+#include <stdexcept>
+
+struct exception_t {
+ int depth;
+ exception_t(int depth) : depth(depth) {}
+};
+
+/* Functions are called via pointers to prevent inlining */
+static void (*p_test_exception_throw_nonstd)(int depth);
+static void (*p_test_exception_throw_std)();
+static PyObject* (*p_test_exception_switch_recurse)(int depth, int left);
+
+static void
+test_exception_throw_nonstd(int depth)
+{
+ throw exception_t(depth);
+}
+
+static void
+test_exception_throw_std()
+{
+ throw std::runtime_error("Thrown from an extension.");
+}
+
+static PyObject*
+test_exception_switch_recurse(int depth, int left)
+{
+ if (left > 0) {
+ return p_test_exception_switch_recurse(depth, left - 1);
+ }
+
+ PyObject* result = NULL;
+ PyGreenlet* self = PyGreenlet_GetCurrent();
+ if (self == NULL)
+ return NULL;
+
+ try {
+ if (PyGreenlet_Switch(PyGreenlet_GET_PARENT(self), NULL, NULL) == NULL) {
+ Py_DECREF(self);
+ return NULL;
+ }
+ p_test_exception_throw_nonstd(depth);
+ PyErr_SetString(PyExc_RuntimeError,
+ "throwing C++ exception didn't work");
+ }
+ catch (const exception_t& e) {
+ if (e.depth != depth)
+ PyErr_SetString(PyExc_AssertionError, "depth mismatch");
+ else
+ result = PyLong_FromLong(depth);
+ }
+ catch (...) {
+ PyErr_SetString(PyExc_RuntimeError, "unexpected C++ exception");
+ }
+
+ Py_DECREF(self);
+ return result;
+}
+
+/* test_exception_switch(int depth)
+ * - recurses depth times
+ * - switches to parent inside try/catch block
+ * - throws an exception that (expected to be caught in the same function)
+ * - verifies depth matches (exceptions shouldn't be caught in other greenlets)
+ */
+static PyObject*
+test_exception_switch(PyObject* UNUSED(self), PyObject* args)
+{
+ int depth;
+ if (!PyArg_ParseTuple(args, "i", &depth))
+ return NULL;
+ return p_test_exception_switch_recurse(depth, depth);
+}
+
+
+static PyObject*
+py_test_exception_throw_nonstd(PyObject* self, PyObject* args)
+{
+ if (!PyArg_ParseTuple(args, ""))
+ return NULL;
+ p_test_exception_throw_nonstd(0);
+ PyErr_SetString(PyExc_AssertionError, "unreachable code running after throw");
+ return NULL;
+}
+
+static PyObject*
+py_test_exception_throw_std(PyObject* self, PyObject* args)
+{
+ if (!PyArg_ParseTuple(args, ""))
+ return NULL;
+ p_test_exception_throw_std();
+ PyErr_SetString(PyExc_AssertionError, "unreachable code running after throw");
+ return NULL;
+}
+
+static PyObject*
+py_test_call(PyObject* self, PyObject* arg)
+{
+ PyObject* noargs = PyTuple_New(0);
+ PyObject* ret = PyObject_Call(arg, noargs, nullptr);
+ Py_DECREF(noargs);
+ return ret;
+}
+
+
+
+/* test_exception_switch_and_do_in_g2(g2func)
+ * - creates new greenlet g2 to run g2func
+ * - switches to g2 inside try/catch block
+ * - verifies that no exception has been caught
+ *
+ * it is used together with test_exception_throw to verify that unhandled
+ * exceptions thrown in one greenlet do not propagate to other greenlet nor
+ * segfault the process.
+ */
+static PyObject*
+test_exception_switch_and_do_in_g2(PyObject* self, PyObject* args)
+{
+ PyObject* g2func = NULL;
+ PyObject* result = NULL;
+
+ if (!PyArg_ParseTuple(args, "O", &g2func))
+ return NULL;
+ PyGreenlet* g2 = PyGreenlet_New(g2func, NULL);
+ if (!g2) {
+ return NULL;
+ }
+
+ try {
+ result = PyGreenlet_Switch(g2, NULL, NULL);
+ if (!result) {
+ return NULL;
+ }
+ }
+ catch (const exception_t& e) {
+ /* if we are here the memory can be already corrupted and the program
+ * might crash before below py-level exception might become printed.
+ * -> print something to stderr to make it clear that we had entered
+ * this catch block.
+ * See comments in inner_bootstrap()
+ */
+#if defined(WIN32) || defined(_WIN32)
+ fprintf(stderr, "C++ exception unexpectedly caught in g1\n");
+ PyErr_SetString(PyExc_AssertionError, "C++ exception unexpectedly caught in g1");
+ Py_XDECREF(result);
+ return NULL;
+#else
+ throw;
+#endif
+ }
+
+ Py_XDECREF(result);
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef test_methods[] = {
+ {"test_exception_switch",
+ (PyCFunction)&test_exception_switch,
+ METH_VARARGS,
+ "Switches to parent twice, to test exception handling and greenlet "
+ "switching."},
+ {"test_exception_switch_and_do_in_g2",
+ (PyCFunction)&test_exception_switch_and_do_in_g2,
+ METH_VARARGS,
+ "Creates new greenlet g2 to run g2func and switches to it inside try/catch "
+ "block. Used together with test_exception_throw to verify that unhandled "
+ "C++ exceptions thrown in a greenlet doe not corrupt memory."},
+ {"test_exception_throw_nonstd",
+ (PyCFunction)&py_test_exception_throw_nonstd,
+ METH_VARARGS,
+ "Throws non-standard C++ exception. Calling this function directly should abort the process."
+ },
+ {"test_exception_throw_std",
+ (PyCFunction)&py_test_exception_throw_std,
+ METH_VARARGS,
+ "Throws standard C++ exception. Calling this function directly should abort the process."
+ },
+ {"test_call",
+ (PyCFunction)&py_test_call,
+ METH_O,
+ "Call the given callable. Unlike calling it directly, this creates a "
+ "new C-level stack frame, which may be helpful in testing."
+ },
+ {NULL, NULL, 0, NULL}
+};
+
+
+static struct PyModuleDef moduledef = {PyModuleDef_HEAD_INIT,
+ "greenlet.tests._test_extension_cpp",
+ NULL,
+ 0,
+ test_methods,
+ NULL,
+ NULL,
+ NULL,
+ NULL};
+
+PyMODINIT_FUNC
+PyInit__test_extension_cpp(void)
+{
+ PyObject* module = NULL;
+
+ module = PyModule_Create(&moduledef);
+
+ if (module == NULL) {
+ return NULL;
+ }
+
+ PyGreenlet_Import();
+ if (_PyGreenlet_API == NULL) {
+ return NULL;
+ }
+
+ p_test_exception_throw_nonstd = test_exception_throw_nonstd;
+ p_test_exception_throw_std = test_exception_throw_std;
+ p_test_exception_switch_recurse = test_exception_switch_recurse;
+
+ return module;
+}
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/_test_extension_cpp.cpython-311-x86_64-linux-gnu.so b/venv/lib/python3.11/site-packages/greenlet/tests/_test_extension_cpp.cpython-311-x86_64-linux-gnu.so
new file mode 100755
index 0000000..714dfa8
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/_test_extension_cpp.cpython-311-x86_64-linux-gnu.so
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/fail_clearing_run_switches.py b/venv/lib/python3.11/site-packages/greenlet/tests/fail_clearing_run_switches.py
new file mode 100644
index 0000000..6dd1492
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/fail_clearing_run_switches.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+"""
+If we have a run callable passed to the constructor or set as an
+attribute, but we don't actually use that (because ``__getattribute__``
+or the like interferes), then when we clear callable before beginning
+to run, there's an opportunity for Python code to run.
+
+"""
+import greenlet
+
+g = None
+main = greenlet.getcurrent()
+
+results = []
+
+class RunCallable:
+
+ def __del__(self):
+ results.append(('RunCallable', '__del__'))
+ main.switch('from RunCallable')
+
+
+class G(greenlet.greenlet):
+
+ def __getattribute__(self, name):
+ if name == 'run':
+ results.append(('G.__getattribute__', 'run'))
+ return run_func
+ return object.__getattribute__(self, name)
+
+
+def run_func():
+ results.append(('run_func', 'enter'))
+
+
+g = G(RunCallable())
+# Try to start G. It will get to the point where it deletes
+# its run callable C++ variable in inner_bootstrap. That triggers
+# the __del__ method, which switches back to main before g
+# actually even starts running.
+x = g.switch()
+results.append(('main: g.switch()', x))
+# In the C++ code, this results in g->g_switch() appearing to return, even though
+# it has yet to run.
+print('In main with', x, flush=True)
+g.switch()
+print('RESULTS', results)
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/fail_cpp_exception.py b/venv/lib/python3.11/site-packages/greenlet/tests/fail_cpp_exception.py
new file mode 100644
index 0000000..fa4dc2e
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/fail_cpp_exception.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+"""
+Helper for testing a C++ exception throw aborts the process.
+
+Takes one argument, the name of the function in :mod:`_test_extension_cpp` to call.
+"""
+import sys
+import greenlet
+from greenlet.tests import _test_extension_cpp
+print('fail_cpp_exception is running')
+
+def run_unhandled_exception_in_greenlet_aborts():
+ def _():
+ _test_extension_cpp.test_exception_switch_and_do_in_g2(
+ _test_extension_cpp.test_exception_throw_nonstd
+ )
+ g1 = greenlet.greenlet(_)
+ g1.switch()
+
+
+func_name = sys.argv[1]
+try:
+ func = getattr(_test_extension_cpp, func_name)
+except AttributeError:
+ if func_name == run_unhandled_exception_in_greenlet_aborts.__name__:
+ func = run_unhandled_exception_in_greenlet_aborts
+ elif func_name == 'run_as_greenlet_target':
+ g = greenlet.greenlet(_test_extension_cpp.test_exception_throw_std)
+ func = g.switch
+ else:
+ raise
+print('raising', func, flush=True)
+func()
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/fail_initialstub_already_started.py b/venv/lib/python3.11/site-packages/greenlet/tests/fail_initialstub_already_started.py
new file mode 100644
index 0000000..c1a44ef
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/fail_initialstub_already_started.py
@@ -0,0 +1,78 @@
+"""
+Testing initialstub throwing an already started exception.
+"""
+
+import greenlet
+
+a = None
+b = None
+c = None
+main = greenlet.getcurrent()
+
+# If we switch into a dead greenlet,
+# we go looking for its parents.
+# if a parent is not yet started, we start it.
+
+results = []
+
+def a_run(*args):
+ #results.append('A')
+ results.append(('Begin A', args))
+
+
+def c_run():
+ results.append('Begin C')
+ b.switch('From C')
+ results.append('C done')
+
+class A(greenlet.greenlet): pass
+
+class B(greenlet.greenlet):
+ doing_it = False
+ def __getattribute__(self, name):
+ if name == 'run' and not self.doing_it:
+ assert greenlet.getcurrent() is c
+ self.doing_it = True
+ results.append('Switch to b from B.__getattribute__ in '
+ + type(greenlet.getcurrent()).__name__)
+ b.switch()
+ results.append('B.__getattribute__ back from main in '
+ + type(greenlet.getcurrent()).__name__)
+ if name == 'run':
+ name = '_B_run'
+ return object.__getattribute__(self, name)
+
+ def _B_run(self, *arg):
+ results.append(('Begin B', arg))
+ results.append('_B_run switching to main')
+ main.switch('From B')
+
+class C(greenlet.greenlet):
+ pass
+a = A(a_run)
+b = B(parent=a)
+c = C(c_run, b)
+
+# Start a child; while running, it will start B,
+# but starting B will ALSO start B.
+result = c.switch()
+results.append(('main from c', result))
+
+# Switch back to C, which was in the middle of switching
+# already. This will throw the ``GreenletStartedWhileInPython``
+# exception, which results in parent A getting started (B is finished)
+c.switch()
+
+results.append(('A dead?', a.dead, 'B dead?', b.dead, 'C dead?', c.dead))
+
+# A and B should both be dead now.
+assert a.dead
+assert b.dead
+assert not c.dead
+
+result = c.switch()
+results.append(('main from c.2', result))
+# Now C is dead
+assert c.dead
+
+print("RESULTS:", results)
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/fail_slp_switch.py b/venv/lib/python3.11/site-packages/greenlet/tests/fail_slp_switch.py
new file mode 100644
index 0000000..0990526
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/fail_slp_switch.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+"""
+A test helper for seeing what happens when slp_switch()
+fails.
+"""
+# pragma: no cover
+
+import greenlet
+
+
+print('fail_slp_switch is running', flush=True)
+
+runs = []
+def func():
+ runs.append(1)
+ greenlet.getcurrent().parent.switch()
+ runs.append(2)
+ greenlet.getcurrent().parent.switch()
+ runs.append(3)
+
+g = greenlet._greenlet.UnswitchableGreenlet(func)
+g.switch()
+assert runs == [1]
+g.switch()
+assert runs == [1, 2]
+g.force_slp_switch_error = True
+
+# This should crash.
+g.switch()
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/fail_switch_three_greenlets.py b/venv/lib/python3.11/site-packages/greenlet/tests/fail_switch_three_greenlets.py
new file mode 100644
index 0000000..e151b19
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/fail_switch_three_greenlets.py
@@ -0,0 +1,44 @@
+"""
+Uses a trace function to switch greenlets at unexpected times.
+
+In the trace function, we switch from the current greenlet to another
+greenlet, which switches
+"""
+import greenlet
+
+g1 = None
+g2 = None
+
+switch_to_g2 = False
+
+def tracefunc(*args):
+ print('TRACE', *args)
+ global switch_to_g2
+ if switch_to_g2:
+ switch_to_g2 = False
+ g2.switch()
+ print('\tLEAVE TRACE', *args)
+
+def g1_run():
+ print('In g1_run')
+ global switch_to_g2
+ switch_to_g2 = True
+ from_parent = greenlet.getcurrent().parent.switch()
+ print('Return to g1_run')
+ print('From parent', from_parent)
+
+def g2_run():
+ #g1.switch()
+ greenlet.getcurrent().parent.switch()
+
+greenlet.settrace(tracefunc)
+
+g1 = greenlet.greenlet(g1_run)
+g2 = greenlet.greenlet(g2_run)
+
+# This switch didn't actually finish!
+# And if it did, it would raise TypeError
+# because g1_run() doesn't take any arguments.
+g1.switch(1)
+print('Back in main')
+g1.switch(2)
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/fail_switch_three_greenlets2.py b/venv/lib/python3.11/site-packages/greenlet/tests/fail_switch_three_greenlets2.py
new file mode 100644
index 0000000..1f6b66b
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/fail_switch_three_greenlets2.py
@@ -0,0 +1,55 @@
+"""
+Like fail_switch_three_greenlets, but the call into g1_run would actually be
+valid.
+"""
+import greenlet
+
+g1 = None
+g2 = None
+
+switch_to_g2 = True
+
+results = []
+
+def tracefunc(*args):
+ results.append(('trace', args[0]))
+ print('TRACE', *args)
+ global switch_to_g2
+ if switch_to_g2:
+ switch_to_g2 = False
+ g2.switch('g2 from tracefunc')
+ print('\tLEAVE TRACE', *args)
+
+def g1_run(arg):
+ results.append(('g1 arg', arg))
+ print('In g1_run')
+ from_parent = greenlet.getcurrent().parent.switch('from g1_run')
+ results.append(('g1 from parent', from_parent))
+ return 'g1 done'
+
+def g2_run(arg):
+ #g1.switch()
+ results.append(('g2 arg', arg))
+ parent = greenlet.getcurrent().parent.switch('from g2_run')
+ global switch_to_g2
+ switch_to_g2 = False
+ results.append(('g2 from parent', parent))
+ return 'g2 done'
+
+
+greenlet.settrace(tracefunc)
+
+g1 = greenlet.greenlet(g1_run)
+g2 = greenlet.greenlet(g2_run)
+
+x = g1.switch('g1 from main')
+results.append(('main g1', x))
+print('Back in main', x)
+x = g1.switch('g2 from main')
+results.append(('main g2', x))
+print('back in amain again', x)
+x = g1.switch('g1 from main 2')
+results.append(('main g1.2', x))
+x = g2.switch()
+results.append(('main g2.2', x))
+print("RESULTS:", results)
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/fail_switch_two_greenlets.py b/venv/lib/python3.11/site-packages/greenlet/tests/fail_switch_two_greenlets.py
new file mode 100644
index 0000000..3e52345
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/fail_switch_two_greenlets.py
@@ -0,0 +1,41 @@
+"""
+Uses a trace function to switch greenlets at unexpected times.
+
+In the trace function, we switch from the current greenlet to another
+greenlet, which switches
+"""
+import greenlet
+
+g1 = None
+g2 = None
+
+switch_to_g2 = False
+
+def tracefunc(*args):
+ print('TRACE', *args)
+ global switch_to_g2
+ if switch_to_g2:
+ switch_to_g2 = False
+ g2.switch()
+ print('\tLEAVE TRACE', *args)
+
+def g1_run():
+ print('In g1_run')
+ global switch_to_g2
+ switch_to_g2 = True
+ greenlet.getcurrent().parent.switch()
+ print('Return to g1_run')
+ print('Falling off end of g1_run')
+
+def g2_run():
+ g1.switch()
+ print('Falling off end of g2')
+
+greenlet.settrace(tracefunc)
+
+g1 = greenlet.greenlet(g1_run)
+g2 = greenlet.greenlet(g2_run)
+
+g1.switch()
+print('Falling off end of main')
+g2.switch()
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/leakcheck.py b/venv/lib/python3.11/site-packages/greenlet/tests/leakcheck.py
new file mode 100644
index 0000000..a5152fb
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/leakcheck.py
@@ -0,0 +1,319 @@
+# Copyright (c) 2018 gevent community
+# Copyright (c) 2021 greenlet community
+#
+# This was originally part of gevent's test suite. The main author
+# (Jason Madden) vendored a copy of it into greenlet.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+from __future__ import print_function
+
+import os
+import sys
+import gc
+
+from functools import wraps
+import unittest
+
+
+import objgraph
+
+# graphviz 0.18 (Nov 7 2021), available only on Python 3.6 and newer,
+# has added type hints (sigh). It wants to use ``typing.Literal`` for
+# some stuff, but that's only available on Python 3.9+. If that's not
+# found, it creates a ``unittest.mock.MagicMock`` object and annotates
+# with that. These are GC'able objects, and doing almost *anything*
+# with them results in an explosion of objects. For example, trying to
+# compare them for equality creates new objects. This causes our
+# leakchecks to fail, with reports like:
+#
+# greenlet.tests.leakcheck.LeakCheckError: refcount increased by [337, 1333, 343, 430, 530, 643, 769]
+# _Call 1820 +546
+# dict 4094 +76
+# MagicProxy 585 +73
+# tuple 2693 +66
+# _CallList 24 +3
+# weakref 1441 +1
+# function 5996 +1
+# type 736 +1
+# cell 592 +1
+# MagicMock 8 +1
+#
+# To avoid this, we *could* filter this type of object out early. In
+# principle it could leak, but we don't use mocks in greenlet, so it
+# doesn't leak from us. However, a further issue is that ``MagicMock``
+# objects have subobjects that are also GC'able, like ``_Call``, and
+# those create new mocks of their own too. So we'd have to filter them
+# as well, and they're not public. That's OK, we can workaround the
+# problem by being very careful to never compare by equality or other
+# user-defined operators, only using object identity or other builtin
+# functions.
+
+RUNNING_ON_GITHUB_ACTIONS = os.environ.get('GITHUB_ACTIONS')
+RUNNING_ON_TRAVIS = os.environ.get('TRAVIS') or RUNNING_ON_GITHUB_ACTIONS
+RUNNING_ON_APPVEYOR = os.environ.get('APPVEYOR')
+RUNNING_ON_CI = RUNNING_ON_TRAVIS or RUNNING_ON_APPVEYOR
+RUNNING_ON_MANYLINUX = os.environ.get('GREENLET_MANYLINUX')
+SKIP_LEAKCHECKS = RUNNING_ON_MANYLINUX or os.environ.get('GREENLET_SKIP_LEAKCHECKS')
+SKIP_FAILING_LEAKCHECKS = os.environ.get('GREENLET_SKIP_FAILING_LEAKCHECKS')
+ONLY_FAILING_LEAKCHECKS = os.environ.get('GREENLET_ONLY_FAILING_LEAKCHECKS')
+
+def ignores_leakcheck(func):
+ """
+ Ignore the given object during leakchecks.
+
+ Can be applied to a method, in which case the method will run, but
+ will not be subject to leak checks.
+
+ If applied to a class, the entire class will be skipped during leakchecks. This
+ is intended to be used for classes that are very slow and cause problems such as
+ test timeouts; typically it will be used for classes that are subclasses of a base
+ class and specify variants of behaviour (such as pool sizes).
+ """
+ func.ignore_leakcheck = True
+ return func
+
+def fails_leakcheck(func):
+ """
+ Mark that the function is known to leak.
+ """
+ func.fails_leakcheck = True
+ if SKIP_FAILING_LEAKCHECKS:
+ func = unittest.skip("Skipping known failures")(func)
+ return func
+
+class LeakCheckError(AssertionError):
+ pass
+
+if hasattr(sys, 'getobjects'):
+ # In a Python build with ``--with-trace-refs``, make objgraph
+ # trace *all* the objects, not just those that are tracked by the
+ # GC
+ class _MockGC(object):
+ def get_objects(self):
+ return sys.getobjects(0) # pylint:disable=no-member
+ def __getattr__(self, name):
+ return getattr(gc, name)
+ objgraph.gc = _MockGC()
+ fails_strict_leakcheck = fails_leakcheck
+else:
+ def fails_strict_leakcheck(func):
+ """
+ Decorator for a function that is known to fail when running
+ strict (``sys.getobjects()``) leakchecks.
+
+ This type of leakcheck finds all objects, even those, such as
+ strings, which are not tracked by the garbage collector.
+ """
+ return func
+
+class ignores_types_in_strict_leakcheck(object):
+ def __init__(self, types):
+ self.types = types
+ def __call__(self, func):
+ func.leakcheck_ignore_types = self.types
+ return func
+
+class _RefCountChecker(object):
+
+ # Some builtin things that we ignore
+ # XXX: Those things were ignored by gevent, but they're important here,
+ # presumably.
+ IGNORED_TYPES = () #(tuple, dict, types.FrameType, types.TracebackType)
+
+ def __init__(self, testcase, function):
+ self.testcase = testcase
+ self.function = function
+ self.deltas = []
+ self.peak_stats = {}
+ self.ignored_types = ()
+
+ # The very first time we are called, we have already been
+ # self.setUp() by the test runner, so we don't need to do it again.
+ self.needs_setUp = False
+
+ def _include_object_p(self, obj):
+ # pylint:disable=too-many-return-statements
+ #
+ # See the comment block at the top. We must be careful to
+ # avoid invoking user-defined operations.
+ if obj is self:
+ return False
+ kind = type(obj)
+ # ``self._include_object_p == obj`` returns NotImplemented
+ # for non-function objects, which causes the interpreter
+ # to try to reverse the order of arguments...which leads
+ # to the explosion of mock objects. We don't want that, so we implement
+ # the check manually.
+ if kind == type(self._include_object_p):
+ try:
+ # pylint:disable=not-callable
+ exact_method_equals = self._include_object_p.__eq__(obj)
+ except AttributeError:
+ # Python 2.7 methods may only have __cmp__, and that raises a
+ # TypeError for non-method arguments
+ # pylint:disable=no-member
+ exact_method_equals = self._include_object_p.__cmp__(obj) == 0
+
+ if exact_method_equals is not NotImplemented and exact_method_equals:
+ return False
+
+ # Similarly, we need to check identity in our __dict__ to avoid mock explosions.
+ for x in self.__dict__.values():
+ if obj is x:
+ return False
+
+
+ if kind in self.ignored_types or kind in self.IGNORED_TYPES:
+ return False
+
+ return True
+
+ def _growth(self):
+ return objgraph.growth(limit=None, peak_stats=self.peak_stats,
+ filter=self._include_object_p)
+
+ def _report_diff(self, growth):
+ if not growth:
+ return "<Unable to calculate growth>"
+
+ lines = []
+ width = max(len(name) for name, _, _ in growth)
+ for name, count, delta in growth:
+ lines.append('%-*s%9d %+9d' % (width, name, count, delta))
+
+ diff = '\n'.join(lines)
+ return diff
+
+
+ def _run_test(self, args, kwargs):
+ gc_enabled = gc.isenabled()
+ gc.disable()
+
+ if self.needs_setUp:
+ self.testcase.setUp()
+ self.testcase.skipTearDown = False
+ try:
+ self.function(self.testcase, *args, **kwargs)
+ finally:
+ self.testcase.tearDown()
+ self.testcase.doCleanups()
+ self.testcase.skipTearDown = True
+ self.needs_setUp = True
+ if gc_enabled:
+ gc.enable()
+
+ def _growth_after(self):
+ # Grab post snapshot
+ # pylint:disable=no-member
+ if 'urlparse' in sys.modules:
+ sys.modules['urlparse'].clear_cache()
+ if 'urllib.parse' in sys.modules:
+ sys.modules['urllib.parse'].clear_cache()
+
+ return self._growth()
+
+ def _check_deltas(self, growth):
+ # Return false when we have decided there is no leak,
+ # true if we should keep looping, raises an assertion
+ # if we have decided there is a leak.
+
+ deltas = self.deltas
+ if not deltas:
+ # We haven't run yet, no data, keep looping
+ return True
+
+ if gc.garbage:
+ raise LeakCheckError("Generated uncollectable garbage %r" % (gc.garbage,))
+
+
+ # the following configurations are classified as "no leak"
+ # [0, 0]
+ # [x, 0, 0]
+ # [... a, b, c, d] where a+b+c+d = 0
+ #
+ # the following configurations are classified as "leak"
+ # [... z, z, z] where z > 0
+
+ if deltas[-2:] == [0, 0] and len(deltas) in (2, 3):
+ return False
+
+ if deltas[-3:] == [0, 0, 0]:
+ return False
+
+ if len(deltas) >= 4 and sum(deltas[-4:]) == 0:
+ return False
+
+ if len(deltas) >= 3 and deltas[-1] > 0 and deltas[-1] == deltas[-2] and deltas[-2] == deltas[-3]:
+ diff = self._report_diff(growth)
+ raise LeakCheckError('refcount increased by %r\n%s' % (deltas, diff))
+
+ # OK, we don't know for sure yet. Let's search for more
+ if sum(deltas[-3:]) <= 0 or sum(deltas[-4:]) <= 0 or deltas[-4:].count(0) >= 2:
+ # this is suspicious, so give a few more runs
+ limit = 11
+ else:
+ limit = 7
+ if len(deltas) >= limit:
+ raise LeakCheckError('refcount increased by %r\n%s'
+ % (deltas,
+ self._report_diff(growth)))
+
+ # We couldn't decide yet, keep going
+ return True
+
+ def __call__(self, args, kwargs):
+ for _ in range(3):
+ gc.collect()
+
+ expect_failure = getattr(self.function, 'fails_leakcheck', False)
+ if expect_failure:
+ self.testcase.expect_greenlet_leak = True
+ self.ignored_types = getattr(self.function, "leakcheck_ignore_types", ())
+
+ # Capture state before; the incremental will be
+ # updated by each call to _growth_after
+ growth = self._growth()
+
+ try:
+ while self._check_deltas(growth):
+ self._run_test(args, kwargs)
+
+ growth = self._growth_after()
+
+ self.deltas.append(sum((stat[2] for stat in growth)))
+ except LeakCheckError:
+ if not expect_failure:
+ raise
+ else:
+ if expect_failure:
+ raise LeakCheckError("Expected %s to leak but it did not." % (self.function,))
+
+def wrap_refcount(method):
+ if getattr(method, 'ignore_leakcheck', False) or SKIP_LEAKCHECKS:
+ return method
+
+ @wraps(method)
+ def wrapper(self, *args, **kwargs): # pylint:disable=too-many-branches
+ if getattr(self, 'ignore_leakcheck', False):
+ raise unittest.SkipTest("This class ignored during leakchecks")
+ if ONLY_FAILING_LEAKCHECKS and not getattr(method, 'fails_leakcheck', False):
+ raise unittest.SkipTest("Only running tests that fail leakchecks.")
+ return _RefCountChecker(self, method)(args, kwargs)
+
+ return wrapper
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/test_contextvars.py b/venv/lib/python3.11/site-packages/greenlet/tests/test_contextvars.py
new file mode 100644
index 0000000..9a16f67
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/test_contextvars.py
@@ -0,0 +1,310 @@
+from __future__ import print_function
+
+import gc
+import sys
+import unittest
+
+from functools import partial
+from unittest import skipUnless
+from unittest import skipIf
+
+from greenlet import greenlet
+from greenlet import getcurrent
+from . import TestCase
+
+
+try:
+ from contextvars import Context
+ from contextvars import ContextVar
+ from contextvars import copy_context
+ # From the documentation:
+ #
+ # Important: Context Variables should be created at the top module
+ # level and never in closures. Context objects hold strong
+ # references to context variables which prevents context variables
+ # from being properly garbage collected.
+ ID_VAR = ContextVar("id", default=None)
+ VAR_VAR = ContextVar("var", default=None)
+ ContextVar = None
+except ImportError:
+ Context = ContextVar = copy_context = None
+
+# We don't support testing if greenlet's built-in context var support is disabled.
+@skipUnless(Context is not None, "ContextVar not supported")
+class ContextVarsTests(TestCase):
+ def _new_ctx_run(self, *args, **kwargs):
+ return copy_context().run(*args, **kwargs)
+
+ def _increment(self, greenlet_id, callback, counts, expect):
+ ctx_var = ID_VAR
+ if expect is None:
+ self.assertIsNone(ctx_var.get())
+ else:
+ self.assertEqual(ctx_var.get(), expect)
+ ctx_var.set(greenlet_id)
+ for _ in range(2):
+ counts[ctx_var.get()] += 1
+ callback()
+
+ def _test_context(self, propagate_by):
+ # pylint:disable=too-many-branches
+ ID_VAR.set(0)
+
+ callback = getcurrent().switch
+ counts = dict((i, 0) for i in range(5))
+
+ lets = [
+ greenlet(partial(
+ partial(
+ copy_context().run,
+ self._increment
+ ) if propagate_by == "run" else self._increment,
+ greenlet_id=i,
+ callback=callback,
+ counts=counts,
+ expect=(
+ i - 1 if propagate_by == "share" else
+ 0 if propagate_by in ("set", "run") else None
+ )
+ ))
+ for i in range(1, 5)
+ ]
+
+ for let in lets:
+ if propagate_by == "set":
+ let.gr_context = copy_context()
+ elif propagate_by == "share":
+ let.gr_context = getcurrent().gr_context
+
+ for i in range(2):
+ counts[ID_VAR.get()] += 1
+ for let in lets:
+ let.switch()
+
+ if propagate_by == "run":
+ # Must leave each context.run() in reverse order of entry
+ for let in reversed(lets):
+ let.switch()
+ else:
+ # No context.run(), so fine to exit in any order.
+ for let in lets:
+ let.switch()
+
+ for let in lets:
+ self.assertTrue(let.dead)
+ # When using run(), we leave the run() as the greenlet dies,
+ # and there's no context "underneath". When not using run(),
+ # gr_context still reflects the context the greenlet was
+ # running in.
+ if propagate_by == 'run':
+ self.assertIsNone(let.gr_context)
+ else:
+ self.assertIsNotNone(let.gr_context)
+
+
+ if propagate_by == "share":
+ self.assertEqual(counts, {0: 1, 1: 1, 2: 1, 3: 1, 4: 6})
+ else:
+ self.assertEqual(set(counts.values()), set([2]))
+
+ def test_context_propagated_by_context_run(self):
+ self._new_ctx_run(self._test_context, "run")
+
+ def test_context_propagated_by_setting_attribute(self):
+ self._new_ctx_run(self._test_context, "set")
+
+ def test_context_not_propagated(self):
+ self._new_ctx_run(self._test_context, None)
+
+ def test_context_shared(self):
+ self._new_ctx_run(self._test_context, "share")
+
+ def test_break_ctxvars(self):
+ let1 = greenlet(copy_context().run)
+ let2 = greenlet(copy_context().run)
+ let1.switch(getcurrent().switch)
+ let2.switch(getcurrent().switch)
+ # Since let2 entered the current context and let1 exits its own, the
+ # interpreter emits:
+ # RuntimeError: cannot exit context: thread state references a different context object
+ let1.switch()
+
+ def test_not_broken_if_using_attribute_instead_of_context_run(self):
+ let1 = greenlet(getcurrent().switch)
+ let2 = greenlet(getcurrent().switch)
+ let1.gr_context = copy_context()
+ let2.gr_context = copy_context()
+ let1.switch()
+ let2.switch()
+ let1.switch()
+ let2.switch()
+
+ def test_context_assignment_while_running(self):
+ # pylint:disable=too-many-statements
+ ID_VAR.set(None)
+
+ def target():
+ self.assertIsNone(ID_VAR.get())
+ self.assertIsNone(gr.gr_context)
+
+ # Context is created on first use
+ ID_VAR.set(1)
+ self.assertIsInstance(gr.gr_context, Context)
+ self.assertEqual(ID_VAR.get(), 1)
+ self.assertEqual(gr.gr_context[ID_VAR], 1)
+
+ # Clearing the context makes it get re-created as another
+ # empty context when next used
+ old_context = gr.gr_context
+ gr.gr_context = None # assign None while running
+ self.assertIsNone(ID_VAR.get())
+ self.assertIsNone(gr.gr_context)
+ ID_VAR.set(2)
+ self.assertIsInstance(gr.gr_context, Context)
+ self.assertEqual(ID_VAR.get(), 2)
+ self.assertEqual(gr.gr_context[ID_VAR], 2)
+
+ new_context = gr.gr_context
+ getcurrent().parent.switch((old_context, new_context))
+ # parent switches us back to old_context
+
+ self.assertEqual(ID_VAR.get(), 1)
+ gr.gr_context = new_context # assign non-None while running
+ self.assertEqual(ID_VAR.get(), 2)
+
+ getcurrent().parent.switch()
+ # parent switches us back to no context
+ self.assertIsNone(ID_VAR.get())
+ self.assertIsNone(gr.gr_context)
+ gr.gr_context = old_context
+ self.assertEqual(ID_VAR.get(), 1)
+
+ getcurrent().parent.switch()
+ # parent switches us back to no context
+ self.assertIsNone(ID_VAR.get())
+ self.assertIsNone(gr.gr_context)
+
+ gr = greenlet(target)
+
+ with self.assertRaisesRegex(AttributeError, "can't delete context attribute"):
+ del gr.gr_context
+
+ self.assertIsNone(gr.gr_context)
+ old_context, new_context = gr.switch()
+ self.assertIs(new_context, gr.gr_context)
+ self.assertEqual(old_context[ID_VAR], 1)
+ self.assertEqual(new_context[ID_VAR], 2)
+ self.assertEqual(new_context.run(ID_VAR.get), 2)
+ gr.gr_context = old_context # assign non-None while suspended
+ gr.switch()
+ self.assertIs(gr.gr_context, new_context)
+ gr.gr_context = None # assign None while suspended
+ gr.switch()
+ self.assertIs(gr.gr_context, old_context)
+ gr.gr_context = None
+ gr.switch()
+ self.assertIsNone(gr.gr_context)
+
+ # Make sure there are no reference leaks
+ gr = None
+ gc.collect()
+ self.assertEqual(sys.getrefcount(old_context), 2)
+ self.assertEqual(sys.getrefcount(new_context), 2)
+
+ def test_context_assignment_different_thread(self):
+ import threading
+ VAR_VAR.set(None)
+ ctx = Context()
+
+ is_running = threading.Event()
+ should_suspend = threading.Event()
+ did_suspend = threading.Event()
+ should_exit = threading.Event()
+ holder = []
+
+ def greenlet_in_thread_fn():
+ VAR_VAR.set(1)
+ is_running.set()
+ should_suspend.wait(10)
+ VAR_VAR.set(2)
+ getcurrent().parent.switch()
+ holder.append(VAR_VAR.get())
+
+ def thread_fn():
+ gr = greenlet(greenlet_in_thread_fn)
+ gr.gr_context = ctx
+ holder.append(gr)
+ gr.switch()
+ did_suspend.set()
+ should_exit.wait(10)
+ gr.switch()
+ del gr
+ greenlet() # trigger cleanup
+
+ thread = threading.Thread(target=thread_fn, daemon=True)
+ thread.start()
+ is_running.wait(10)
+ gr = holder[0]
+
+ # Can't access or modify context if the greenlet is running
+ # in a different thread
+ with self.assertRaisesRegex(ValueError, "running in a different"):
+ getattr(gr, 'gr_context')
+ with self.assertRaisesRegex(ValueError, "running in a different"):
+ gr.gr_context = None
+
+ should_suspend.set()
+ did_suspend.wait(10)
+
+ # OK to access and modify context if greenlet is suspended
+ self.assertIs(gr.gr_context, ctx)
+ self.assertEqual(gr.gr_context[VAR_VAR], 2)
+ gr.gr_context = None
+
+ should_exit.set()
+ thread.join(10)
+
+ self.assertEqual(holder, [gr, None])
+
+ # Context can still be accessed/modified when greenlet is dead:
+ self.assertIsNone(gr.gr_context)
+ gr.gr_context = ctx
+ self.assertIs(gr.gr_context, ctx)
+
+ # Otherwise we leak greenlets on some platforms.
+ # XXX: Should be able to do this automatically
+ del holder[:]
+ gr = None
+ thread = None
+
+ def test_context_assignment_wrong_type(self):
+ g = greenlet()
+ with self.assertRaisesRegex(TypeError,
+ "greenlet context must be a contextvars.Context or None"):
+ g.gr_context = self
+
+
+@skipIf(Context is not None, "ContextVar supported")
+class NoContextVarsTests(TestCase):
+ def test_contextvars_errors(self):
+ let1 = greenlet(getcurrent().switch)
+ self.assertFalse(hasattr(let1, 'gr_context'))
+ with self.assertRaises(AttributeError):
+ getattr(let1, 'gr_context')
+
+ with self.assertRaises(AttributeError):
+ let1.gr_context = None
+
+ let1.switch()
+
+ with self.assertRaises(AttributeError):
+ getattr(let1, 'gr_context')
+
+ with self.assertRaises(AttributeError):
+ let1.gr_context = None
+
+ del let1
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/test_cpp.py b/venv/lib/python3.11/site-packages/greenlet/tests/test_cpp.py
new file mode 100644
index 0000000..2d0cc9c
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/test_cpp.py
@@ -0,0 +1,73 @@
+from __future__ import print_function
+from __future__ import absolute_import
+
+import subprocess
+import unittest
+
+import greenlet
+from . import _test_extension_cpp
+from . import TestCase
+from . import WIN
+
+class CPPTests(TestCase):
+ def test_exception_switch(self):
+ greenlets = []
+ for i in range(4):
+ g = greenlet.greenlet(_test_extension_cpp.test_exception_switch)
+ g.switch(i)
+ greenlets.append(g)
+ for i, g in enumerate(greenlets):
+ self.assertEqual(g.switch(), i)
+
+ def _do_test_unhandled_exception(self, target):
+ import os
+ import sys
+ script = os.path.join(
+ os.path.dirname(__file__),
+ 'fail_cpp_exception.py',
+ )
+ args = [sys.executable, script, target.__name__ if not isinstance(target, str) else target]
+ __traceback_info__ = args
+ with self.assertRaises(subprocess.CalledProcessError) as exc:
+ subprocess.check_output(
+ args,
+ encoding='utf-8',
+ stderr=subprocess.STDOUT
+ )
+
+ ex = exc.exception
+ expected_exit = self.get_expected_returncodes_for_aborted_process()
+ self.assertIn(ex.returncode, expected_exit)
+ self.assertIn('fail_cpp_exception is running', ex.output)
+ return ex.output
+
+
+ def test_unhandled_nonstd_exception_aborts(self):
+ # verify that plain unhandled throw aborts
+ self._do_test_unhandled_exception(_test_extension_cpp.test_exception_throw_nonstd)
+
+ def test_unhandled_std_exception_aborts(self):
+ # verify that plain unhandled throw aborts
+ self._do_test_unhandled_exception(_test_extension_cpp.test_exception_throw_std)
+
+ @unittest.skipIf(WIN, "XXX: This does not crash on Windows")
+ # Meaning the exception is getting lost somewhere...
+ def test_unhandled_std_exception_as_greenlet_function_aborts(self):
+ # verify that plain unhandled throw aborts
+ output = self._do_test_unhandled_exception('run_as_greenlet_target')
+ self.assertIn(
+ # We really expect this to be prefixed with "greenlet: Unhandled C++ exception:"
+ # as added by our handler for std::exception (see TUserGreenlet.cpp), but
+ # that's not correct everywhere --- our handler never runs before std::terminate
+ # gets called (for example, on arm32).
+ 'Thrown from an extension.',
+ output
+ )
+
+ def test_unhandled_exception_in_greenlet_aborts(self):
+ # verify that unhandled throw called in greenlet aborts too
+ self._do_test_unhandled_exception('run_unhandled_exception_in_greenlet_aborts')
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/test_extension_interface.py b/venv/lib/python3.11/site-packages/greenlet/tests/test_extension_interface.py
new file mode 100644
index 0000000..34b6656
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/test_extension_interface.py
@@ -0,0 +1,115 @@
+from __future__ import print_function
+from __future__ import absolute_import
+
+import sys
+
+import greenlet
+from . import _test_extension
+from . import TestCase
+
+# pylint:disable=c-extension-no-member
+
+class CAPITests(TestCase):
+ def test_switch(self):
+ self.assertEqual(
+ 50, _test_extension.test_switch(greenlet.greenlet(lambda: 50)))
+
+ def test_switch_kwargs(self):
+ def adder(x, y):
+ return x * y
+ g = greenlet.greenlet(adder)
+ self.assertEqual(6, _test_extension.test_switch_kwargs(g, x=3, y=2))
+
+ def test_setparent(self):
+ # pylint:disable=disallowed-name
+ def foo():
+ def bar():
+ greenlet.getcurrent().parent.switch()
+
+ # This final switch should go back to the main greenlet, since
+ # the test_setparent() function in the C extension should have
+ # reparented this greenlet.
+ greenlet.getcurrent().parent.switch()
+ raise AssertionError("Should never have reached this code")
+ child = greenlet.greenlet(bar)
+ child.switch()
+ greenlet.getcurrent().parent.switch(child)
+ greenlet.getcurrent().parent.throw(
+ AssertionError("Should never reach this code"))
+ foo_child = greenlet.greenlet(foo).switch()
+ self.assertEqual(None, _test_extension.test_setparent(foo_child))
+
+ def test_getcurrent(self):
+ _test_extension.test_getcurrent()
+
+ def test_new_greenlet(self):
+ self.assertEqual(-15, _test_extension.test_new_greenlet(lambda: -15))
+
+ def test_raise_greenlet_dead(self):
+ self.assertRaises(
+ greenlet.GreenletExit, _test_extension.test_raise_dead_greenlet)
+
+ def test_raise_greenlet_error(self):
+ self.assertRaises(
+ greenlet.error, _test_extension.test_raise_greenlet_error)
+
+ def test_throw(self):
+ seen = []
+
+ def foo(): # pylint:disable=disallowed-name
+ try:
+ greenlet.getcurrent().parent.switch()
+ except ValueError:
+ seen.append(sys.exc_info()[1])
+ except greenlet.GreenletExit:
+ raise AssertionError
+ g = greenlet.greenlet(foo)
+ g.switch()
+ _test_extension.test_throw(g)
+ self.assertEqual(len(seen), 1)
+ self.assertTrue(
+ isinstance(seen[0], ValueError),
+ "ValueError was not raised in foo()")
+ self.assertEqual(
+ str(seen[0]),
+ 'take that sucka!',
+ "message doesn't match")
+
+ def test_non_traceback_param(self):
+ with self.assertRaises(TypeError) as exc:
+ _test_extension.test_throw_exact(
+ greenlet.getcurrent(),
+ Exception,
+ Exception(),
+ self
+ )
+ self.assertEqual(str(exc.exception),
+ "throw() third argument must be a traceback object")
+
+ def test_instance_of_wrong_type(self):
+ with self.assertRaises(TypeError) as exc:
+ _test_extension.test_throw_exact(
+ greenlet.getcurrent(),
+ Exception(),
+ BaseException(),
+ None,
+ )
+
+ self.assertEqual(str(exc.exception),
+ "instance exception may not have a separate value")
+
+ def test_not_throwable(self):
+ with self.assertRaises(TypeError) as exc:
+ _test_extension.test_throw_exact(
+ greenlet.getcurrent(),
+ "abc",
+ None,
+ None,
+ )
+ self.assertEqual(str(exc.exception),
+ "exceptions must be classes, or instances, not str")
+
+
+if __name__ == '__main__':
+ import unittest
+ unittest.main()
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/test_gc.py b/venv/lib/python3.11/site-packages/greenlet/tests/test_gc.py
new file mode 100644
index 0000000..994addb
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/test_gc.py
@@ -0,0 +1,86 @@
+import gc
+
+import weakref
+
+import greenlet
+
+
+from . import TestCase
+from .leakcheck import fails_leakcheck
+# These only work with greenlet gc support
+# which is no longer optional.
+assert greenlet.GREENLET_USE_GC
+
+class GCTests(TestCase):
+ def test_dead_circular_ref(self):
+ o = weakref.ref(greenlet.greenlet(greenlet.getcurrent).switch())
+ gc.collect()
+ if o() is not None:
+ import sys
+ print("O IS NOT NONE.", sys.getrefcount(o()))
+ self.assertIsNone(o())
+ self.assertFalse(gc.garbage, gc.garbage)
+
+ def test_circular_greenlet(self):
+ class circular_greenlet(greenlet.greenlet):
+ self = None
+ o = circular_greenlet()
+ o.self = o
+ o = weakref.ref(o)
+ gc.collect()
+ self.assertIsNone(o())
+ self.assertFalse(gc.garbage, gc.garbage)
+
+ def test_inactive_ref(self):
+ class inactive_greenlet(greenlet.greenlet):
+ def __init__(self):
+ greenlet.greenlet.__init__(self, run=self.run)
+
+ def run(self):
+ pass
+ o = inactive_greenlet()
+ o = weakref.ref(o)
+ gc.collect()
+ self.assertIsNone(o())
+ self.assertFalse(gc.garbage, gc.garbage)
+
+ @fails_leakcheck
+ def test_finalizer_crash(self):
+ # This test is designed to crash when active greenlets
+ # are made garbage collectable, until the underlying
+ # problem is resolved. How does it work:
+ # - order of object creation is important
+ # - array is created first, so it is moved to unreachable first
+ # - we create a cycle between a greenlet and this array
+ # - we create an object that participates in gc, is only
+ # referenced by a greenlet, and would corrupt gc lists
+ # on destruction, the easiest is to use an object with
+ # a finalizer
+ # - because array is the first object in unreachable it is
+ # cleared first, which causes all references to greenlet
+ # to disappear and causes greenlet to be destroyed, but since
+ # it is still live it causes a switch during gc, which causes
+ # an object with finalizer to be destroyed, which causes stack
+ # corruption and then a crash
+
+ class object_with_finalizer(object):
+ def __del__(self):
+ pass
+ array = []
+ parent = greenlet.getcurrent()
+ def greenlet_body():
+ greenlet.getcurrent().object = object_with_finalizer()
+ try:
+ parent.switch()
+ except greenlet.GreenletExit:
+ print("Got greenlet exit!")
+ finally:
+ del greenlet.getcurrent().object
+ g = greenlet.greenlet(greenlet_body)
+ g.array = array
+ array.append(g)
+ g.switch()
+ del array
+ del g
+ greenlet.getcurrent()
+ gc.collect()
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/test_generator.py b/venv/lib/python3.11/site-packages/greenlet/tests/test_generator.py
new file mode 100644
index 0000000..ca4a644
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/test_generator.py
@@ -0,0 +1,59 @@
+
+from greenlet import greenlet
+
+from . import TestCase
+
+class genlet(greenlet):
+ parent = None
+ def __init__(self, *args, **kwds):
+ self.args = args
+ self.kwds = kwds
+
+ def run(self):
+ fn, = self.fn
+ fn(*self.args, **self.kwds)
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ self.parent = greenlet.getcurrent()
+ result = self.switch()
+ if self:
+ return result
+
+ raise StopIteration
+
+ next = __next__
+
+
+def Yield(value):
+ g = greenlet.getcurrent()
+ while not isinstance(g, genlet):
+ if g is None:
+ raise RuntimeError('yield outside a genlet')
+ g = g.parent
+ g.parent.switch(value)
+
+
+def generator(func):
+ class Generator(genlet):
+ fn = (func,)
+ return Generator
+
+# ____________________________________________________________
+
+
+class GeneratorTests(TestCase):
+ def test_generator(self):
+ seen = []
+
+ def g(n):
+ for i in range(n):
+ seen.append(i)
+ Yield(i)
+ g = generator(g)
+ for _ in range(3):
+ for j in g(5):
+ seen.append(j)
+ self.assertEqual(seen, 3 * [0, 0, 1, 1, 2, 2, 3, 3, 4, 4])
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/test_generator_nested.py b/venv/lib/python3.11/site-packages/greenlet/tests/test_generator_nested.py
new file mode 100644
index 0000000..8d752a6
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/test_generator_nested.py
@@ -0,0 +1,168 @@
+
+from greenlet import greenlet
+from . import TestCase
+from .leakcheck import fails_leakcheck
+
+class genlet(greenlet):
+ parent = None
+ def __init__(self, *args, **kwds):
+ self.args = args
+ self.kwds = kwds
+ self.child = None
+
+ def run(self):
+ # Note the function is packed in a tuple
+ # to avoid creating a bound method for it.
+ fn, = self.fn
+ fn(*self.args, **self.kwds)
+
+ def __iter__(self):
+ return self
+
+ def set_child(self, child):
+ self.child = child
+
+ def __next__(self):
+ if self.child:
+ child = self.child
+ while child.child:
+ tmp = child
+ child = child.child
+ tmp.child = None
+
+ result = child.switch()
+ else:
+ self.parent = greenlet.getcurrent()
+ result = self.switch()
+
+ if self:
+ return result
+
+ raise StopIteration
+
+ next = __next__
+
+def Yield(value, level=1):
+ g = greenlet.getcurrent()
+
+ while level != 0:
+ if not isinstance(g, genlet):
+ raise RuntimeError('yield outside a genlet')
+ if level > 1:
+ g.parent.set_child(g)
+ g = g.parent
+ level -= 1
+
+ g.switch(value)
+
+
+def Genlet(func):
+ class TheGenlet(genlet):
+ fn = (func,)
+ return TheGenlet
+
+# ____________________________________________________________
+
+
+def g1(n, seen):
+ for i in range(n):
+ seen.append(i + 1)
+ yield i
+
+
+def g2(n, seen):
+ for i in range(n):
+ seen.append(i + 1)
+ Yield(i)
+
+g2 = Genlet(g2)
+
+
+def nested(i):
+ Yield(i)
+
+
+def g3(n, seen):
+ for i in range(n):
+ seen.append(i + 1)
+ nested(i)
+g3 = Genlet(g3)
+
+
+def a(n):
+ if n == 0:
+ return
+ for ii in ax(n - 1):
+ Yield(ii)
+ Yield(n)
+ax = Genlet(a)
+
+
+def perms(l):
+ if len(l) > 1:
+ for e in l:
+ # No syntactical sugar for generator expressions
+ x = [Yield([e] + p) for p in perms([x for x in l if x != e])]
+ assert x
+ else:
+ Yield(l)
+perms = Genlet(perms)
+
+
+def gr1(n):
+ for ii in range(1, n):
+ Yield(ii)
+ Yield(ii * ii, 2)
+
+gr1 = Genlet(gr1)
+
+
+def gr2(n, seen):
+ for ii in gr1(n):
+ seen.append(ii)
+
+gr2 = Genlet(gr2)
+
+
+class NestedGeneratorTests(TestCase):
+ def test_layered_genlets(self):
+ seen = []
+ for ii in gr2(5, seen):
+ seen.append(ii)
+ self.assertEqual(seen, [1, 1, 2, 4, 3, 9, 4, 16])
+
+ @fails_leakcheck
+ def test_permutations(self):
+ gen_perms = perms(list(range(4)))
+ permutations = list(gen_perms)
+ self.assertEqual(len(permutations), 4 * 3 * 2 * 1)
+ self.assertIn([0, 1, 2, 3], permutations)
+ self.assertIn([3, 2, 1, 0], permutations)
+ res = []
+ for ii in zip(perms(list(range(4))), perms(list(range(3)))):
+ res.append(ii)
+ self.assertEqual(
+ res,
+ [([0, 1, 2, 3], [0, 1, 2]), ([0, 1, 3, 2], [0, 2, 1]),
+ ([0, 2, 1, 3], [1, 0, 2]), ([0, 2, 3, 1], [1, 2, 0]),
+ ([0, 3, 1, 2], [2, 0, 1]), ([0, 3, 2, 1], [2, 1, 0])])
+ # XXX Test to make sure we are working as a generator expression
+
+ def test_genlet_simple(self):
+ for g in g1, g2, g3:
+ seen = []
+ for _ in range(3):
+ for j in g(5, seen):
+ seen.append(j)
+ self.assertEqual(seen, 3 * [1, 0, 2, 1, 3, 2, 4, 3, 5, 4])
+
+ def test_genlet_bad(self):
+ try:
+ Yield(10)
+ except RuntimeError:
+ pass
+
+ def test_nested_genlets(self):
+ seen = []
+ for ii in ax(5):
+ seen.append(ii)
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/test_greenlet.py b/venv/lib/python3.11/site-packages/greenlet/tests/test_greenlet.py
new file mode 100644
index 0000000..51849cd
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/test_greenlet.py
@@ -0,0 +1,1311 @@
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import gc
+import sys
+import time
+import threading
+
+from abc import ABCMeta, abstractmethod
+
+import greenlet
+from greenlet import greenlet as RawGreenlet
+from . import TestCase
+from .leakcheck import fails_leakcheck
+
+
+# We manually manage locks in many tests
+# pylint:disable=consider-using-with
+# pylint:disable=too-many-public-methods
+# This module is quite large.
+# TODO: Refactor into separate test files. For example,
+# put all the regression tests that used to produce
+# crashes in test_greenlet_no_crash; put tests that DO deliberately crash
+# the interpreter into test_greenlet_crash.
+# pylint:disable=too-many-lines
+
+class SomeError(Exception):
+ pass
+
+
+def fmain(seen):
+ try:
+ greenlet.getcurrent().parent.switch()
+ except:
+ seen.append(sys.exc_info()[0])
+ raise
+ raise SomeError
+
+
+def send_exception(g, exc):
+ # note: send_exception(g, exc) can be now done with g.throw(exc).
+ # the purpose of this test is to explicitly check the propagation rules.
+ def crasher(exc):
+ raise exc
+ g1 = RawGreenlet(crasher, parent=g)
+ g1.switch(exc)
+
+
+class TestGreenlet(TestCase):
+
+ def _do_simple_test(self):
+ lst = []
+
+ def f():
+ lst.append(1)
+ greenlet.getcurrent().parent.switch()
+ lst.append(3)
+ g = RawGreenlet(f)
+ lst.append(0)
+ g.switch()
+ lst.append(2)
+ g.switch()
+ lst.append(4)
+ self.assertEqual(lst, list(range(5)))
+
+ def test_simple(self):
+ self._do_simple_test()
+
+ def test_switch_no_run_raises_AttributeError(self):
+ g = RawGreenlet()
+ with self.assertRaises(AttributeError) as exc:
+ g.switch()
+
+ self.assertIn("run", str(exc.exception))
+
+ def test_throw_no_run_raises_AttributeError(self):
+ g = RawGreenlet()
+ with self.assertRaises(AttributeError) as exc:
+ g.throw(SomeError)
+
+ self.assertIn("run", str(exc.exception))
+
+ def test_parent_equals_None(self):
+ g = RawGreenlet(parent=None)
+ self.assertIsNotNone(g)
+ self.assertIs(g.parent, greenlet.getcurrent())
+
+ def test_run_equals_None(self):
+ g = RawGreenlet(run=None)
+ self.assertIsNotNone(g)
+ self.assertIsNone(g.run)
+
+ def test_two_children(self):
+ lst = []
+
+ def f():
+ lst.append(1)
+ greenlet.getcurrent().parent.switch()
+ lst.extend([1, 1])
+ g = RawGreenlet(f)
+ h = RawGreenlet(f)
+ g.switch()
+ self.assertEqual(len(lst), 1)
+ h.switch()
+ self.assertEqual(len(lst), 2)
+ h.switch()
+ self.assertEqual(len(lst), 4)
+ self.assertEqual(h.dead, True)
+ g.switch()
+ self.assertEqual(len(lst), 6)
+ self.assertEqual(g.dead, True)
+
+ def test_two_recursive_children(self):
+ lst = []
+
+ def f():
+ lst.append('b')
+ greenlet.getcurrent().parent.switch()
+
+ def g():
+ lst.append('a')
+ g = RawGreenlet(f)
+ g.switch()
+ lst.append('c')
+
+ g = RawGreenlet(g)
+ self.assertEqual(sys.getrefcount(g), 2)
+ g.switch()
+ self.assertEqual(lst, ['a', 'b', 'c'])
+ # Just the one in this frame, plus the one on the stack we pass to the function
+ self.assertEqual(sys.getrefcount(g), 2)
+
+ def test_threads(self):
+ success = []
+
+ def f():
+ self._do_simple_test()
+ success.append(True)
+ ths = [threading.Thread(target=f) for i in range(10)]
+ for th in ths:
+ th.start()
+ for th in ths:
+ th.join(10)
+ self.assertEqual(len(success), len(ths))
+
+ def test_exception(self):
+ seen = []
+ g1 = RawGreenlet(fmain)
+ g2 = RawGreenlet(fmain)
+ g1.switch(seen)
+ g2.switch(seen)
+ g2.parent = g1
+
+ self.assertEqual(seen, [])
+ #with self.assertRaises(SomeError):
+ # p("***Switching back")
+ # g2.switch()
+ # Creating this as a bound method can reveal bugs that
+ # are hidden on newer versions of Python that avoid creating
+ # bound methods for direct expressions; IOW, don't use the `with`
+ # form!
+ self.assertRaises(SomeError, g2.switch)
+ self.assertEqual(seen, [SomeError])
+
+ value = g2.switch()
+ self.assertEqual(value, ())
+ self.assertEqual(seen, [SomeError])
+
+ value = g2.switch(25)
+ self.assertEqual(value, 25)
+ self.assertEqual(seen, [SomeError])
+
+
+ def test_send_exception(self):
+ seen = []
+ g1 = RawGreenlet(fmain)
+ g1.switch(seen)
+ self.assertRaises(KeyError, send_exception, g1, KeyError)
+ self.assertEqual(seen, [KeyError])
+
+ def test_dealloc(self):
+ seen = []
+ g1 = RawGreenlet(fmain)
+ g2 = RawGreenlet(fmain)
+ g1.switch(seen)
+ g2.switch(seen)
+ self.assertEqual(seen, [])
+ del g1
+ gc.collect()
+ self.assertEqual(seen, [greenlet.GreenletExit])
+ del g2
+ gc.collect()
+ self.assertEqual(seen, [greenlet.GreenletExit, greenlet.GreenletExit])
+
+ def test_dealloc_catches_GreenletExit_throws_other(self):
+ def run():
+ try:
+ greenlet.getcurrent().parent.switch()
+ except greenlet.GreenletExit:
+ raise SomeError from None
+
+ g = RawGreenlet(run)
+ g.switch()
+ # Destroying the only reference to the greenlet causes it
+ # to get GreenletExit; when it in turn raises, even though we're the parent
+ # we don't get the exception, it just gets printed.
+ # When we run on 3.8 only, we can use sys.unraisablehook
+ oldstderr = sys.stderr
+ try:
+ from cStringIO import StringIO
+ except ImportError:
+ from io import StringIO
+ stderr = sys.stderr = StringIO()
+ try:
+ del g
+ finally:
+ sys.stderr = oldstderr
+
+ v = stderr.getvalue()
+ self.assertIn("Exception", v)
+ self.assertIn('ignored', v)
+ self.assertIn("SomeError", v)
+
+
+ def test_dealloc_other_thread(self):
+ seen = []
+ someref = []
+
+ bg_glet_created_running_and_no_longer_ref_in_bg = threading.Event()
+ fg_ref_released = threading.Event()
+ bg_should_be_clear = threading.Event()
+ ok_to_exit_bg_thread = threading.Event()
+
+ def f():
+ g1 = RawGreenlet(fmain)
+ g1.switch(seen)
+ someref.append(g1)
+ del g1
+ gc.collect()
+
+ bg_glet_created_running_and_no_longer_ref_in_bg.set()
+ fg_ref_released.wait(3)
+
+ RawGreenlet() # trigger release
+ bg_should_be_clear.set()
+ ok_to_exit_bg_thread.wait(3)
+ RawGreenlet() # One more time
+
+ t = threading.Thread(target=f)
+ t.start()
+ bg_glet_created_running_and_no_longer_ref_in_bg.wait(10)
+
+ self.assertEqual(seen, [])
+ self.assertEqual(len(someref), 1)
+ del someref[:]
+ gc.collect()
+ # g1 is not released immediately because it's from another thread
+ self.assertEqual(seen, [])
+ fg_ref_released.set()
+ bg_should_be_clear.wait(3)
+ try:
+ self.assertEqual(seen, [greenlet.GreenletExit])
+ finally:
+ ok_to_exit_bg_thread.set()
+ t.join(10)
+ del seen[:]
+ del someref[:]
+
+ def test_frame(self):
+ def f1():
+ f = sys._getframe(0) # pylint:disable=protected-access
+ self.assertEqual(f.f_back, None)
+ greenlet.getcurrent().parent.switch(f)
+ return "meaning of life"
+ g = RawGreenlet(f1)
+ frame = g.switch()
+ self.assertTrue(frame is g.gr_frame)
+ self.assertTrue(g)
+
+ from_g = g.switch()
+ self.assertFalse(g)
+ self.assertEqual(from_g, 'meaning of life')
+ self.assertEqual(g.gr_frame, None)
+
+ def test_thread_bug(self):
+ def runner(x):
+ g = RawGreenlet(lambda: time.sleep(x))
+ g.switch()
+ t1 = threading.Thread(target=runner, args=(0.2,))
+ t2 = threading.Thread(target=runner, args=(0.3,))
+ t1.start()
+ t2.start()
+ t1.join(10)
+ t2.join(10)
+
+ def test_switch_kwargs(self):
+ def run(a, b):
+ self.assertEqual(a, 4)
+ self.assertEqual(b, 2)
+ return 42
+ x = RawGreenlet(run).switch(a=4, b=2)
+ self.assertEqual(x, 42)
+
+ def test_switch_kwargs_to_parent(self):
+ def run(x):
+ greenlet.getcurrent().parent.switch(x=x)
+ greenlet.getcurrent().parent.switch(2, x=3)
+ return x, x ** 2
+ g = RawGreenlet(run)
+ self.assertEqual({'x': 3}, g.switch(3))
+ self.assertEqual(((2,), {'x': 3}), g.switch())
+ self.assertEqual((3, 9), g.switch())
+
+ def test_switch_to_another_thread(self):
+ data = {}
+ created_event = threading.Event()
+ done_event = threading.Event()
+
+ def run():
+ data['g'] = RawGreenlet(lambda: None)
+ created_event.set()
+ done_event.wait(10)
+ thread = threading.Thread(target=run)
+ thread.start()
+ created_event.wait(10)
+ with self.assertRaises(greenlet.error):
+ data['g'].switch()
+ done_event.set()
+ thread.join(10)
+ # XXX: Should handle this automatically
+ data.clear()
+
+ def test_exc_state(self):
+ def f():
+ try:
+ raise ValueError('fun')
+ except: # pylint:disable=bare-except
+ exc_info = sys.exc_info()
+ RawGreenlet(h).switch()
+ self.assertEqual(exc_info, sys.exc_info())
+
+ def h():
+ self.assertEqual(sys.exc_info(), (None, None, None))
+
+ RawGreenlet(f).switch()
+
+ def test_instance_dict(self):
+ def f():
+ greenlet.getcurrent().test = 42
+ def deldict(g):
+ del g.__dict__
+ def setdict(g, value):
+ g.__dict__ = value
+ g = RawGreenlet(f)
+ self.assertEqual(g.__dict__, {})
+ g.switch()
+ self.assertEqual(g.test, 42)
+ self.assertEqual(g.__dict__, {'test': 42})
+ g.__dict__ = g.__dict__
+ self.assertEqual(g.__dict__, {'test': 42})
+ self.assertRaises(TypeError, deldict, g)
+ self.assertRaises(TypeError, setdict, g, 42)
+
+ def test_running_greenlet_has_no_run(self):
+ has_run = []
+ def func():
+ has_run.append(
+ hasattr(greenlet.getcurrent(), 'run')
+ )
+
+ g = RawGreenlet(func)
+ g.switch()
+ self.assertEqual(has_run, [False])
+
+ def test_deepcopy(self):
+ import copy
+ self.assertRaises(TypeError, copy.copy, RawGreenlet())
+ self.assertRaises(TypeError, copy.deepcopy, RawGreenlet())
+
+ def test_parent_restored_on_kill(self):
+ hub = RawGreenlet(lambda: None)
+ main = greenlet.getcurrent()
+ result = []
+ def worker():
+ try:
+ # Wait to be killed by going back to the test.
+ main.switch()
+ except greenlet.GreenletExit:
+ # Resurrect and switch to parent
+ result.append(greenlet.getcurrent().parent)
+ result.append(greenlet.getcurrent())
+ hub.switch()
+ g = RawGreenlet(worker, parent=hub)
+ g.switch()
+ # delete the only reference, thereby raising GreenletExit
+ del g
+ self.assertTrue(result)
+ self.assertIs(result[0], main)
+ self.assertIs(result[1].parent, hub)
+ # Delete them, thereby breaking the cycle between the greenlet
+ # and the frame, which otherwise would never be collectable
+ # XXX: We should be able to automatically fix this.
+ del result[:]
+ hub = None
+ main = None
+
+ def test_parent_return_failure(self):
+ # No run causes AttributeError on switch
+ g1 = RawGreenlet()
+ # Greenlet that implicitly switches to parent
+ g2 = RawGreenlet(lambda: None, parent=g1)
+ # AttributeError should propagate to us, no fatal errors
+ with self.assertRaises(AttributeError):
+ g2.switch()
+
+ def test_throw_exception_not_lost(self):
+ class mygreenlet(RawGreenlet):
+ def __getattribute__(self, name):
+ try:
+ raise Exception # pylint:disable=broad-exception-raised
+ except: # pylint:disable=bare-except
+ pass
+ return RawGreenlet.__getattribute__(self, name)
+ g = mygreenlet(lambda: None)
+ self.assertRaises(SomeError, g.throw, SomeError())
+
+ @fails_leakcheck
+ def _do_test_throw_to_dead_thread_doesnt_crash(self, wait_for_cleanup=False):
+ result = []
+ def worker():
+ greenlet.getcurrent().parent.switch()
+
+ def creator():
+ g = RawGreenlet(worker)
+ g.switch()
+ result.append(g)
+ if wait_for_cleanup:
+ # Let this greenlet eventually be cleaned up.
+ g.switch()
+ greenlet.getcurrent()
+ t = threading.Thread(target=creator)
+ t.start()
+ t.join(10)
+ del t
+ # But, depending on the operating system, the thread
+ # deallocator may not actually have run yet! So we can't be
+ # sure about the error message unless we wait.
+ if wait_for_cleanup:
+ self.wait_for_pending_cleanups()
+ with self.assertRaises(greenlet.error) as exc:
+ result[0].throw(SomeError)
+
+ if not wait_for_cleanup:
+ self.assertIn(
+ str(exc.exception), [
+ "cannot switch to a different thread (which happens to have exited)",
+ "cannot switch to a different thread"
+ ]
+ )
+ else:
+ self.assertEqual(
+ str(exc.exception),
+ "cannot switch to a different thread (which happens to have exited)",
+ )
+
+ if hasattr(result[0].gr_frame, 'clear'):
+ # The frame is actually executing (it thinks), we can't clear it.
+ with self.assertRaises(RuntimeError):
+ result[0].gr_frame.clear()
+ # Unfortunately, this doesn't actually clear the references, they're in the
+ # fast local array.
+ if not wait_for_cleanup:
+ result[0].gr_frame.f_locals.clear()
+ else:
+ self.assertIsNone(result[0].gr_frame)
+
+ del creator
+ worker = None
+ del result[:]
+ # XXX: we ought to be able to automatically fix this.
+ # See issue 252
+ self.expect_greenlet_leak = True # direct us not to wait for it to go away
+
+ @fails_leakcheck
+ def test_throw_to_dead_thread_doesnt_crash(self):
+ self._do_test_throw_to_dead_thread_doesnt_crash()
+
+ def test_throw_to_dead_thread_doesnt_crash_wait(self):
+ self._do_test_throw_to_dead_thread_doesnt_crash(True)
+
+ @fails_leakcheck
+ def test_recursive_startup(self):
+ class convoluted(RawGreenlet):
+ def __init__(self):
+ RawGreenlet.__init__(self)
+ self.count = 0
+ def __getattribute__(self, name):
+ if name == 'run' and self.count == 0:
+ self.count = 1
+ self.switch(43)
+ return RawGreenlet.__getattribute__(self, name)
+ def run(self, value):
+ while True:
+ self.parent.switch(value)
+ g = convoluted()
+ self.assertEqual(g.switch(42), 43)
+ # Exits the running greenlet, otherwise it leaks
+ # XXX: We should be able to automatically fix this
+ #g.throw(greenlet.GreenletExit)
+ #del g
+ self.expect_greenlet_leak = True
+
+ def test_threaded_updatecurrent(self):
+ # released when main thread should execute
+ lock1 = threading.Lock()
+ lock1.acquire()
+ # released when another thread should execute
+ lock2 = threading.Lock()
+ lock2.acquire()
+ class finalized(object):
+ def __del__(self):
+ # happens while in green_updatecurrent() in main greenlet
+ # should be very careful not to accidentally call it again
+ # at the same time we must make sure another thread executes
+ lock2.release()
+ lock1.acquire()
+ # now ts_current belongs to another thread
+ def deallocator():
+ greenlet.getcurrent().parent.switch()
+ def fthread():
+ lock2.acquire()
+ greenlet.getcurrent()
+ del g[0]
+ lock1.release()
+ lock2.acquire()
+ greenlet.getcurrent()
+ lock1.release()
+ main = greenlet.getcurrent()
+ g = [RawGreenlet(deallocator)]
+ g[0].bomb = finalized()
+ g[0].switch()
+ t = threading.Thread(target=fthread)
+ t.start()
+ # let another thread grab ts_current and deallocate g[0]
+ lock2.release()
+ lock1.acquire()
+ # this is the corner stone
+ # getcurrent() will notice that ts_current belongs to another thread
+ # and start the update process, which would notice that g[0] should
+ # be deallocated, and that will execute an object's finalizer. Now,
+ # that object will let another thread run so it can grab ts_current
+ # again, which would likely crash the interpreter if there's no
+ # check for this case at the end of green_updatecurrent(). This test
+ # passes if getcurrent() returns correct result, but it's likely
+ # to randomly crash if it's not anyway.
+ self.assertEqual(greenlet.getcurrent(), main)
+ # wait for another thread to complete, just in case
+ t.join(10)
+
+ def test_dealloc_switch_args_not_lost(self):
+ seen = []
+ def worker():
+ # wait for the value
+ value = greenlet.getcurrent().parent.switch()
+ # delete all references to ourself
+ del worker[0]
+ initiator.parent = greenlet.getcurrent().parent
+ # switch to main with the value, but because
+ # ts_current is the last reference to us we
+ # return here immediately, where we resurrect ourself.
+ try:
+ greenlet.getcurrent().parent.switch(value)
+ finally:
+ seen.append(greenlet.getcurrent())
+ def initiator():
+ return 42 # implicitly falls thru to parent
+
+ worker = [RawGreenlet(worker)]
+
+ worker[0].switch() # prime worker
+ initiator = RawGreenlet(initiator, worker[0])
+ value = initiator.switch()
+ self.assertTrue(seen)
+ self.assertEqual(value, 42)
+
+ def test_tuple_subclass(self):
+ # The point of this test is to see what happens when a custom
+ # tuple subclass is used as an object passed directly to the C
+ # function ``green_switch``; part of ``green_switch`` checks
+ # the ``len()`` of the ``args`` tuple, and that can call back
+ # into Python. Here, when it calls back into Python, we
+ # recursively enter ``green_switch`` again.
+
+ # This test is really only relevant on Python 2. The builtin
+ # `apply` function directly passes the given args tuple object
+ # to the underlying function, whereas the Python 3 version
+ # unpacks and repacks into an actual tuple. This could still
+ # happen using the C API on Python 3 though. We should write a
+ # builtin version of apply() ourself.
+ def _apply(func, a, k):
+ func(*a, **k)
+
+ class mytuple(tuple):
+ def __len__(self):
+ greenlet.getcurrent().switch()
+ return tuple.__len__(self)
+ args = mytuple()
+ kwargs = dict(a=42)
+ def switchapply():
+ _apply(greenlet.getcurrent().parent.switch, args, kwargs)
+ g = RawGreenlet(switchapply)
+ self.assertEqual(g.switch(), kwargs)
+
+ def test_abstract_subclasses(self):
+ AbstractSubclass = ABCMeta(
+ 'AbstractSubclass',
+ (RawGreenlet,),
+ {'run': abstractmethod(lambda self: None)})
+
+ class BadSubclass(AbstractSubclass):
+ pass
+
+ class GoodSubclass(AbstractSubclass):
+ def run(self):
+ pass
+
+ GoodSubclass() # should not raise
+ self.assertRaises(TypeError, BadSubclass)
+
+ def test_implicit_parent_with_threads(self):
+ if not gc.isenabled():
+ return # cannot test with disabled gc
+ N = gc.get_threshold()[0]
+ if N < 50:
+ return # cannot test with such a small N
+ def attempt():
+ lock1 = threading.Lock()
+ lock1.acquire()
+ lock2 = threading.Lock()
+ lock2.acquire()
+ recycled = [False]
+ def another_thread():
+ lock1.acquire() # wait for gc
+ greenlet.getcurrent() # update ts_current
+ lock2.release() # release gc
+ t = threading.Thread(target=another_thread)
+ t.start()
+ class gc_callback(object):
+ def __del__(self):
+ lock1.release()
+ lock2.acquire()
+ recycled[0] = True
+ class garbage(object):
+ def __init__(self):
+ self.cycle = self
+ self.callback = gc_callback()
+ l = []
+ x = range(N*2)
+ current = greenlet.getcurrent()
+ g = garbage()
+ for _ in x:
+ g = None # lose reference to garbage
+ if recycled[0]:
+ # gc callback called prematurely
+ t.join(10)
+ return False
+ last = RawGreenlet()
+ if recycled[0]:
+ break # yes! gc called in green_new
+ l.append(last) # increase allocation counter
+ else:
+ # gc callback not called when expected
+ gc.collect()
+ if recycled[0]:
+ t.join(10)
+ return False
+ self.assertEqual(last.parent, current)
+ for g in l:
+ self.assertEqual(g.parent, current)
+ return True
+ for _ in range(5):
+ if attempt():
+ break
+
+ def test_issue_245_reference_counting_subclass_no_threads(self):
+ # https://github.com/python-greenlet/greenlet/issues/245
+ # Before the fix, this crashed pretty reliably on
+ # Python 3.10, at least on macOS; but much less reliably on other
+ # interpreters (memory layout must have changed).
+ # The threaded test crashed more reliably on more interpreters.
+ from greenlet import getcurrent
+ from greenlet import GreenletExit
+
+ class Greenlet(RawGreenlet):
+ pass
+
+ initial_refs = sys.getrefcount(Greenlet)
+ # This has to be an instance variable because
+ # Python 2 raises a SyntaxError if we delete a local
+ # variable referenced in an inner scope.
+ self.glets = [] # pylint:disable=attribute-defined-outside-init
+
+ def greenlet_main():
+ try:
+ getcurrent().parent.switch()
+ except GreenletExit:
+ self.glets.append(getcurrent())
+
+ # Before the
+ for _ in range(10):
+ Greenlet(greenlet_main).switch()
+
+ del self.glets
+ self.assertEqual(sys.getrefcount(Greenlet), initial_refs)
+
+ def test_issue_245_reference_counting_subclass_threads(self):
+ # https://github.com/python-greenlet/greenlet/issues/245
+ from threading import Thread
+ from threading import Event
+
+ from greenlet import getcurrent
+
+ class MyGreenlet(RawGreenlet):
+ pass
+
+ glets = []
+ ref_cleared = Event()
+
+ def greenlet_main():
+ getcurrent().parent.switch()
+
+ def thread_main(greenlet_running_event):
+ mine = MyGreenlet(greenlet_main)
+ glets.append(mine)
+ # The greenlets being deleted must be active
+ mine.switch()
+ # Don't keep any reference to it in this thread
+ del mine
+ # Let main know we published our greenlet.
+ greenlet_running_event.set()
+ # Wait for main to let us know the references are
+ # gone and the greenlet objects no longer reachable
+ ref_cleared.wait(10)
+ # The creating thread must call getcurrent() (or a few other
+ # greenlet APIs) because that's when the thread-local list of dead
+ # greenlets gets cleared.
+ getcurrent()
+
+ # We start with 3 references to the subclass:
+ # - This module
+ # - Its __mro__
+ # - The __subclassess__ attribute of greenlet
+ # - (If we call gc.get_referents(), we find four entries, including
+ # some other tuple ``(greenlet)`` that I'm not sure about but must be part
+ # of the machinery.)
+ #
+ # On Python 3.10 it's often enough to just run 3 threads; on Python 2.7,
+ # more threads are needed, and the results are still
+ # non-deterministic. Presumably the memory layouts are different
+ initial_refs = sys.getrefcount(MyGreenlet)
+ thread_ready_events = []
+ for _ in range(
+ initial_refs + 45
+ ):
+ event = Event()
+ thread = Thread(target=thread_main, args=(event,))
+ thread_ready_events.append(event)
+ thread.start()
+
+
+ for done_event in thread_ready_events:
+ done_event.wait(10)
+
+
+ del glets[:]
+ ref_cleared.set()
+ # Let any other thread run; it will crash the interpreter
+ # if not fixed (or silently corrupt memory and we possibly crash
+ # later).
+ self.wait_for_pending_cleanups()
+ self.assertEqual(sys.getrefcount(MyGreenlet), initial_refs)
+
+ def test_falling_off_end_switches_to_unstarted_parent_raises_error(self):
+ def no_args():
+ return 13
+
+ parent_never_started = RawGreenlet(no_args)
+
+ def leaf():
+ return 42
+
+ child = RawGreenlet(leaf, parent_never_started)
+
+ # Because the run function takes to arguments
+ with self.assertRaises(TypeError):
+ child.switch()
+
+ def test_falling_off_end_switches_to_unstarted_parent_works(self):
+ def one_arg(x):
+ return (x, 24)
+
+ parent_never_started = RawGreenlet(one_arg)
+
+ def leaf():
+ return 42
+
+ child = RawGreenlet(leaf, parent_never_started)
+
+ result = child.switch()
+ self.assertEqual(result, (42, 24))
+
+ def test_switch_to_dead_greenlet_with_unstarted_perverse_parent(self):
+ class Parent(RawGreenlet):
+ def __getattribute__(self, name):
+ if name == 'run':
+ raise SomeError
+
+
+ parent_never_started = Parent()
+ seen = []
+ child = RawGreenlet(lambda: seen.append(42), parent_never_started)
+ # Because we automatically start the parent when the child is
+ # finished
+ with self.assertRaises(SomeError):
+ child.switch()
+
+ self.assertEqual(seen, [42])
+
+ with self.assertRaises(SomeError):
+ child.switch()
+ self.assertEqual(seen, [42])
+
+ def test_switch_to_dead_greenlet_reparent(self):
+ seen = []
+ parent_never_started = RawGreenlet(lambda: seen.append(24))
+ child = RawGreenlet(lambda: seen.append(42))
+
+ child.switch()
+ self.assertEqual(seen, [42])
+
+ child.parent = parent_never_started
+ # This actually is the same as switching to the parent.
+ result = child.switch()
+ self.assertIsNone(result)
+ self.assertEqual(seen, [42, 24])
+
+ def test_can_access_f_back_of_suspended_greenlet(self):
+ # This tests our frame rewriting to work around Python 3.12+ having
+ # some interpreter frames on the C stack. It will crash in the absence
+ # of that logic.
+ main = greenlet.getcurrent()
+
+ def outer():
+ inner()
+
+ def inner():
+ main.switch(sys._getframe(0))
+
+ hub = RawGreenlet(outer)
+ # start it
+ hub.switch()
+
+ # start another greenlet to make sure we aren't relying on
+ # anything in `hub` still being on the C stack
+ unrelated = RawGreenlet(lambda: None)
+ unrelated.switch()
+
+ # now it is suspended
+ self.assertIsNotNone(hub.gr_frame)
+ self.assertEqual(hub.gr_frame.f_code.co_name, "inner")
+ self.assertIsNotNone(hub.gr_frame.f_back)
+ self.assertEqual(hub.gr_frame.f_back.f_code.co_name, "outer")
+ # The next line is what would crash
+ self.assertIsNone(hub.gr_frame.f_back.f_back)
+
+ def test_get_stack_with_nested_c_calls(self):
+ from functools import partial
+ from . import _test_extension_cpp
+
+ def recurse(v):
+ if v > 0:
+ return v * _test_extension_cpp.test_call(partial(recurse, v - 1))
+ return greenlet.getcurrent().parent.switch()
+
+ gr = RawGreenlet(recurse)
+ gr.switch(5)
+ frame = gr.gr_frame
+ for i in range(5):
+ self.assertEqual(frame.f_locals["v"], i)
+ frame = frame.f_back
+ self.assertEqual(frame.f_locals["v"], 5)
+ self.assertIsNone(frame.f_back)
+ self.assertEqual(gr.switch(10), 1200) # 1200 = 5! * 10
+
+ def test_frames_always_exposed(self):
+ # On Python 3.12 this will crash if we don't set the
+ # gr_frames_always_exposed attribute. More background:
+ # https://github.com/python-greenlet/greenlet/issues/388
+ main = greenlet.getcurrent()
+
+ def outer():
+ inner(sys._getframe(0))
+
+ def inner(frame):
+ main.switch(frame)
+
+ gr = RawGreenlet(outer)
+ frame = gr.switch()
+
+ # Do something else to clobber the part of the C stack used by `gr`,
+ # so we can't skate by on "it just happened to still be there"
+ unrelated = RawGreenlet(lambda: None)
+ unrelated.switch()
+
+ self.assertEqual(frame.f_code.co_name, "outer")
+ # The next line crashes on 3.12 if we haven't exposed the frames.
+ self.assertIsNone(frame.f_back)
+
+
+class TestGreenletSetParentErrors(TestCase):
+ def test_threaded_reparent(self):
+ data = {}
+ created_event = threading.Event()
+ done_event = threading.Event()
+
+ def run():
+ data['g'] = RawGreenlet(lambda: None)
+ created_event.set()
+ done_event.wait(10)
+
+ def blank():
+ greenlet.getcurrent().parent.switch()
+
+ thread = threading.Thread(target=run)
+ thread.start()
+ created_event.wait(10)
+ g = RawGreenlet(blank)
+ g.switch()
+ with self.assertRaises(ValueError) as exc:
+ g.parent = data['g']
+ done_event.set()
+ thread.join(10)
+
+ self.assertEqual(str(exc.exception), "parent cannot be on a different thread")
+
+ def test_unexpected_reparenting(self):
+ another = []
+ def worker():
+ g = RawGreenlet(lambda: None)
+ another.append(g)
+ g.switch()
+ t = threading.Thread(target=worker)
+ t.start()
+ t.join(10)
+ # The first time we switch (running g_initialstub(), which is
+ # when we look up the run attribute) we attempt to change the
+ # parent to one from another thread (which also happens to be
+ # dead). ``g_initialstub()`` should detect this and raise a
+ # greenlet error.
+ #
+ # EXCEPT: With the fix for #252, this is actually detected
+ # sooner, when setting the parent itself. Prior to that fix,
+ # the main greenlet from the background thread kept a valid
+ # value for ``run_info``, and appeared to be a valid parent
+ # until we actually started the greenlet. But now that it's
+ # cleared, this test is catching whether ``green_setparent``
+ # can detect the dead thread.
+ #
+ # Further refactoring once again changes this back to a greenlet.error
+ #
+ # We need to wait for the cleanup to happen, but we're
+ # deliberately leaking a main greenlet here.
+ self.wait_for_pending_cleanups(initial_main_greenlets=self.main_greenlets_before_test + 1)
+
+ class convoluted(RawGreenlet):
+ def __getattribute__(self, name):
+ if name == 'run':
+ self.parent = another[0] # pylint:disable=attribute-defined-outside-init
+ return RawGreenlet.__getattribute__(self, name)
+ g = convoluted(lambda: None)
+ with self.assertRaises(greenlet.error) as exc:
+ g.switch()
+ self.assertEqual(str(exc.exception),
+ "cannot switch to a different thread (which happens to have exited)")
+ del another[:]
+
+ def test_unexpected_reparenting_thread_running(self):
+ # Like ``test_unexpected_reparenting``, except the background thread is
+ # actually still alive.
+ another = []
+ switched_to_greenlet = threading.Event()
+ keep_main_alive = threading.Event()
+ def worker():
+ g = RawGreenlet(lambda: None)
+ another.append(g)
+ g.switch()
+ switched_to_greenlet.set()
+ keep_main_alive.wait(10)
+ class convoluted(RawGreenlet):
+ def __getattribute__(self, name):
+ if name == 'run':
+ self.parent = another[0] # pylint:disable=attribute-defined-outside-init
+ return RawGreenlet.__getattribute__(self, name)
+
+ t = threading.Thread(target=worker)
+ t.start()
+
+ switched_to_greenlet.wait(10)
+ try:
+ g = convoluted(lambda: None)
+
+ with self.assertRaises(greenlet.error) as exc:
+ g.switch()
+ self.assertEqual(str(exc.exception), "cannot switch to a different thread")
+ finally:
+ keep_main_alive.set()
+ t.join(10)
+ # XXX: Should handle this automatically.
+ del another[:]
+
+ def test_cannot_delete_parent(self):
+ worker = RawGreenlet(lambda: None)
+ self.assertIs(worker.parent, greenlet.getcurrent())
+
+ with self.assertRaises(AttributeError) as exc:
+ del worker.parent
+ self.assertEqual(str(exc.exception), "can't delete attribute")
+
+ def test_cannot_delete_parent_of_main(self):
+ with self.assertRaises(AttributeError) as exc:
+ del greenlet.getcurrent().parent
+ self.assertEqual(str(exc.exception), "can't delete attribute")
+
+
+ def test_main_greenlet_parent_is_none(self):
+ # assuming we're in a main greenlet here.
+ self.assertIsNone(greenlet.getcurrent().parent)
+
+ def test_set_parent_wrong_types(self):
+ def bg():
+ # Go back to main.
+ greenlet.getcurrent().parent.switch()
+
+ def check(glet):
+ for p in None, 1, self, "42":
+ with self.assertRaises(TypeError) as exc:
+ glet.parent = p
+
+ self.assertEqual(
+ str(exc.exception),
+ "GreenletChecker: Expected any type of greenlet, not " + type(p).__name__)
+
+ # First, not running
+ g = RawGreenlet(bg)
+ self.assertFalse(g)
+ check(g)
+
+ # Then when running.
+ g.switch()
+ self.assertTrue(g)
+ check(g)
+
+ # Let it finish
+ g.switch()
+
+
+ def test_trivial_cycle(self):
+ glet = RawGreenlet(lambda: None)
+ with self.assertRaises(ValueError) as exc:
+ glet.parent = glet
+ self.assertEqual(str(exc.exception), "cyclic parent chain")
+
+ def test_trivial_cycle_main(self):
+ # This used to produce a ValueError, but we catch it earlier than that now.
+ with self.assertRaises(AttributeError) as exc:
+ greenlet.getcurrent().parent = greenlet.getcurrent()
+ self.assertEqual(str(exc.exception), "cannot set the parent of a main greenlet")
+
+ def test_deeper_cycle(self):
+ g1 = RawGreenlet(lambda: None)
+ g2 = RawGreenlet(lambda: None)
+ g3 = RawGreenlet(lambda: None)
+
+ g1.parent = g2
+ g2.parent = g3
+ with self.assertRaises(ValueError) as exc:
+ g3.parent = g1
+ self.assertEqual(str(exc.exception), "cyclic parent chain")
+
+
+class TestRepr(TestCase):
+
+ def assertEndsWith(self, got, suffix):
+ self.assertTrue(got.endswith(suffix), (got, suffix))
+
+ def test_main_while_running(self):
+ r = repr(greenlet.getcurrent())
+ self.assertEndsWith(r, " current active started main>")
+
+ def test_main_in_background(self):
+ main = greenlet.getcurrent()
+ def run():
+ return repr(main)
+
+ g = RawGreenlet(run)
+ r = g.switch()
+ self.assertEndsWith(r, ' suspended active started main>')
+
+ def test_initial(self):
+ r = repr(RawGreenlet())
+ self.assertEndsWith(r, ' pending>')
+
+ def test_main_from_other_thread(self):
+ main = greenlet.getcurrent()
+
+ class T(threading.Thread):
+ original_main = thread_main = None
+ main_glet = None
+ def run(self):
+ self.original_main = repr(main)
+ self.main_glet = greenlet.getcurrent()
+ self.thread_main = repr(self.main_glet)
+
+ t = T()
+ t.start()
+ t.join(10)
+
+ self.assertEndsWith(t.original_main, ' suspended active started main>')
+ self.assertEndsWith(t.thread_main, ' current active started main>')
+ # give the machinery time to notice the death of the thread,
+ # and clean it up. Note that we don't use
+ # ``expect_greenlet_leak`` or wait_for_pending_cleanups,
+ # because at this point we know we have an extra greenlet
+ # still reachable.
+ for _ in range(3):
+ time.sleep(0.001)
+
+ # In the past, main greenlets, even from dead threads, never
+ # really appear dead. We have fixed that, and we also report
+ # that the thread is dead in the repr. (Do this multiple times
+ # to make sure that we don't self-modify and forget our state
+ # in the C++ code).
+ for _ in range(3):
+ self.assertTrue(t.main_glet.dead)
+ r = repr(t.main_glet)
+ self.assertEndsWith(r, ' (thread exited) dead>')
+
+ def test_dead(self):
+ g = RawGreenlet(lambda: None)
+ g.switch()
+ self.assertEndsWith(repr(g), ' dead>')
+ self.assertNotIn('suspended', repr(g))
+ self.assertNotIn('started', repr(g))
+ self.assertNotIn('active', repr(g))
+
+ def test_formatting_produces_native_str(self):
+ # https://github.com/python-greenlet/greenlet/issues/218
+ # %s formatting on Python 2 was producing unicode, not str.
+
+ g_dead = RawGreenlet(lambda: None)
+ g_not_started = RawGreenlet(lambda: None)
+ g_cur = greenlet.getcurrent()
+
+ for g in g_dead, g_not_started, g_cur:
+
+ self.assertIsInstance(
+ '%s' % (g,),
+ str
+ )
+ self.assertIsInstance(
+ '%r' % (g,),
+ str,
+ )
+
+
+class TestMainGreenlet(TestCase):
+ # Tests some implementation details, and relies on some
+ # implementation details.
+
+ def _check_current_is_main(self):
+ # implementation detail
+ assert 'main' in repr(greenlet.getcurrent())
+
+ t = type(greenlet.getcurrent())
+ assert 'main' not in repr(t)
+ return t
+
+ def test_main_greenlet_type_can_be_subclassed(self):
+ main_type = self._check_current_is_main()
+ subclass = type('subclass', (main_type,), {})
+ self.assertIsNotNone(subclass)
+
+ def test_main_greenlet_is_greenlet(self):
+ self._check_current_is_main()
+ self.assertIsInstance(greenlet.getcurrent(), RawGreenlet)
+
+
+
+class TestBrokenGreenlets(TestCase):
+ # Tests for things that used to, or still do, terminate the interpreter.
+ # This often means doing unsavory things.
+
+ def test_failed_to_initialstub(self):
+ def func():
+ raise AssertionError("Never get here")
+
+
+ g = greenlet._greenlet.UnswitchableGreenlet(func)
+ g.force_switch_error = True
+
+ with self.assertRaisesRegex(SystemError,
+ "Failed to switch stacks into a greenlet for the first time."):
+ g.switch()
+
+ def test_failed_to_switch_into_running(self):
+ runs = []
+ def func():
+ runs.append(1)
+ greenlet.getcurrent().parent.switch()
+ runs.append(2)
+ greenlet.getcurrent().parent.switch()
+ runs.append(3) # pragma: no cover
+
+ g = greenlet._greenlet.UnswitchableGreenlet(func)
+ g.switch()
+ self.assertEqual(runs, [1])
+ g.switch()
+ self.assertEqual(runs, [1, 2])
+ g.force_switch_error = True
+
+ with self.assertRaisesRegex(SystemError,
+ "Failed to switch stacks into a running greenlet."):
+ g.switch()
+
+ # If we stopped here, we would fail the leakcheck, because we've left
+ # the ``inner_bootstrap()`` C frame and its descendents hanging around,
+ # which have a bunch of Python references. They'll never get cleaned up
+ # if we don't let the greenlet finish.
+ g.force_switch_error = False
+ g.switch()
+ self.assertEqual(runs, [1, 2, 3])
+
+ def test_failed_to_slp_switch_into_running(self):
+ ex = self.assertScriptRaises('fail_slp_switch.py')
+
+ self.assertIn('fail_slp_switch is running', ex.output)
+ self.assertIn(ex.returncode, self.get_expected_returncodes_for_aborted_process())
+
+ def test_reentrant_switch_two_greenlets(self):
+ # Before we started capturing the arguments in g_switch_finish, this could crash.
+ output = self.run_script('fail_switch_two_greenlets.py')
+ self.assertIn('In g1_run', output)
+ self.assertIn('TRACE', output)
+ self.assertIn('LEAVE TRACE', output)
+ self.assertIn('Falling off end of main', output)
+ self.assertIn('Falling off end of g1_run', output)
+ self.assertIn('Falling off end of g2', output)
+
+ def test_reentrant_switch_three_greenlets(self):
+ # On debug builds of greenlet, this used to crash with an assertion error;
+ # on non-debug versions, it ran fine (which it should not do!).
+ # Now it always crashes correctly with a TypeError
+ ex = self.assertScriptRaises('fail_switch_three_greenlets.py', exitcodes=(1,))
+
+ self.assertIn('TypeError', ex.output)
+ self.assertIn('positional arguments', ex.output)
+
+ def test_reentrant_switch_three_greenlets2(self):
+ # This actually passed on debug and non-debug builds. It
+ # should probably have been triggering some debug assertions
+ # but it didn't.
+ #
+ # I think the fixes for the above test also kicked in here.
+ output = self.run_script('fail_switch_three_greenlets2.py')
+ self.assertIn(
+ "RESULTS: [('trace', 'switch'), "
+ "('trace', 'switch'), ('g2 arg', 'g2 from tracefunc'), "
+ "('trace', 'switch'), ('main g1', 'from g2_run'), ('trace', 'switch'), "
+ "('g1 arg', 'g1 from main'), ('trace', 'switch'), ('main g2', 'from g1_run'), "
+ "('trace', 'switch'), ('g1 from parent', 'g1 from main 2'), ('trace', 'switch'), "
+ "('main g1.2', 'g1 done'), ('trace', 'switch'), ('g2 from parent', ()), "
+ "('trace', 'switch'), ('main g2.2', 'g2 done')]",
+ output
+ )
+
+ def test_reentrant_switch_GreenletAlreadyStartedInPython(self):
+ output = self.run_script('fail_initialstub_already_started.py')
+
+ self.assertIn(
+ "RESULTS: ['Begin C', 'Switch to b from B.__getattribute__ in C', "
+ "('Begin B', ()), '_B_run switching to main', ('main from c', 'From B'), "
+ "'B.__getattribute__ back from main in C', ('Begin A', (None,)), "
+ "('A dead?', True, 'B dead?', True, 'C dead?', False), "
+ "'C done', ('main from c.2', None)]",
+ output
+ )
+
+ def test_reentrant_switch_run_callable_has_del(self):
+ output = self.run_script('fail_clearing_run_switches.py')
+ self.assertIn(
+ "RESULTS ["
+ "('G.__getattribute__', 'run'), ('RunCallable', '__del__'), "
+ "('main: g.switch()', 'from RunCallable'), ('run_func', 'enter')"
+ "]",
+ output
+ )
+
+if __name__ == '__main__':
+ import unittest
+ unittest.main()
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/test_greenlet_trash.py b/venv/lib/python3.11/site-packages/greenlet/tests/test_greenlet_trash.py
new file mode 100644
index 0000000..8d9716e
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/test_greenlet_trash.py
@@ -0,0 +1,178 @@
+# -*- coding: utf-8 -*-
+"""
+Tests for greenlets interacting with the CPython trash can API.
+
+The CPython trash can API is not designed to be re-entered from a
+single thread. But this can happen using greenlets, if something
+during the object deallocation process switches greenlets, and this second
+greenlet then causes the trash can to get entered again. Here, we do this
+very explicitly, but in other cases (like gevent) it could be arbitrarily more
+complicated: for example, a weakref callback might try to acquire a lock that's
+already held by another greenlet; that would allow a greenlet switch to occur.
+
+See https://github.com/gevent/gevent/issues/1909
+
+This test is fragile and relies on details of the CPython
+implementation (like most of the rest of this package):
+
+ - We enter the trashcan and deferred deallocation after
+ ``_PyTrash_UNWIND_LEVEL`` calls. This constant, defined in
+ CPython's object.c, is generally 50. That's basically how many objects are required to
+ get us into the deferred deallocation situation.
+
+ - The test fails by hitting an ``assert()`` in object.c; if the
+ build didn't enable assert, then we don't catch this.
+
+ - If the test fails in that way, the interpreter crashes.
+"""
+from __future__ import print_function, absolute_import, division
+
+import unittest
+
+class TestTrashCanReEnter(unittest.TestCase):
+
+ def test_it(self):
+ # Try several times to trigger it, because it isn't 100%
+ # reliable.
+ for _ in range(10):
+ self.check_it()
+
+ def check_it(self): # pylint:disable=too-many-statements
+ import greenlet
+ from greenlet._greenlet import get_tstate_trash_delete_nesting # pylint:disable=no-name-in-module
+
+ main = greenlet.getcurrent()
+
+ assert get_tstate_trash_delete_nesting() == 0
+
+ # We expect to be in deferred deallocation after this many
+ # deallocations have occurred. TODO: I wish we had a better way to do
+ # this --- that was before get_tstate_trash_delete_nesting; perhaps
+ # we can use that API to do better?
+ TRASH_UNWIND_LEVEL = 50
+ # How many objects to put in a container; it's the container that
+ # queues objects for deferred deallocation.
+ OBJECTS_PER_CONTAINER = 500
+
+ class Dealloc: # define the class here because we alter class variables each time we run.
+ """
+ An object with a ``__del__`` method. When it starts getting deallocated
+ from a deferred trash can run, it switches greenlets, allocates more objects
+ which then also go in the trash can. If we don't save state appropriately,
+ nesting gets out of order and we can crash the interpreter.
+ """
+
+ #: Has our deallocation actually run and switched greenlets?
+ #: When it does, this will be set to the current greenlet. This should
+ #: be happening in the main greenlet, so we check that down below.
+ SPAWNED = False
+
+ #: Has the background greenlet run?
+ BG_RAN = False
+
+ BG_GLET = None
+
+ #: How many of these things have ever been allocated.
+ CREATED = 0
+
+ #: How many of these things have ever been deallocated.
+ DESTROYED = 0
+
+ #: How many were destroyed not in the main greenlet. There should always
+ #: be some.
+ #: If the test is broken or things change in the trashcan implementation,
+ #: this may not be correct.
+ DESTROYED_BG = 0
+
+ def __init__(self, sequence_number):
+ """
+ :param sequence_number: The ordinal of this object during
+ one particular creation run. This is used to detect (guess, really)
+ when we have entered the trash can's deferred deallocation.
+ """
+ self.i = sequence_number
+ Dealloc.CREATED += 1
+
+ def __del__(self):
+ if self.i == TRASH_UNWIND_LEVEL and not self.SPAWNED:
+ Dealloc.SPAWNED = greenlet.getcurrent()
+ other = Dealloc.BG_GLET = greenlet.greenlet(background_greenlet)
+ x = other.switch()
+ assert x == 42
+ # It's important that we don't switch back to the greenlet,
+ # we leave it hanging there in an incomplete state. But we don't let it
+ # get collected, either. If we complete it now, while we're still
+ # in the scope of the initial trash can, things work out and we
+ # don't see the problem. We need this greenlet to complete
+ # at some point in the future, after we've exited this trash can invocation.
+ del other
+ elif self.i == 40 and greenlet.getcurrent() is not main:
+ Dealloc.BG_RAN = True
+ try:
+ main.switch(42)
+ except greenlet.GreenletExit as ex:
+ # We expect this; all references to us go away
+ # while we're still running, and we need to finish deleting
+ # ourself.
+ Dealloc.BG_RAN = type(ex)
+ del ex
+
+ # Record the fact that we're dead last of all. This ensures that
+ # we actually get returned too.
+ Dealloc.DESTROYED += 1
+ if greenlet.getcurrent() is not main:
+ Dealloc.DESTROYED_BG += 1
+
+
+ def background_greenlet():
+ # We direct through a second function, instead of
+ # directly calling ``make_some()``, so that we have complete
+ # control over when these objects are destroyed: we need them
+ # to be destroyed in the context of the background greenlet
+ t = make_some()
+ del t # Triggere deletion.
+
+ def make_some():
+ t = ()
+ i = OBJECTS_PER_CONTAINER
+ while i:
+ # Nest the tuples; it's the recursion that gets us
+ # into trash.
+ t = (Dealloc(i), t)
+ i -= 1
+ return t
+
+
+ some = make_some()
+ self.assertEqual(Dealloc.CREATED, OBJECTS_PER_CONTAINER)
+ self.assertEqual(Dealloc.DESTROYED, 0)
+
+ # If we're going to crash, it should be on the following line.
+ # We only crash if ``assert()`` is enabled, of course.
+ del some
+
+ # For non-debug builds of CPython, we won't crash. The best we can do is check
+ # the nesting level explicitly.
+ self.assertEqual(0, get_tstate_trash_delete_nesting())
+
+ # Discard this, raising GreenletExit into where it is waiting.
+ Dealloc.BG_GLET = None
+ # The same nesting level maintains.
+ self.assertEqual(0, get_tstate_trash_delete_nesting())
+
+ # We definitely cleaned some up in the background
+ self.assertGreater(Dealloc.DESTROYED_BG, 0)
+
+ # Make sure all the cleanups happened.
+ self.assertIs(Dealloc.SPAWNED, main)
+ self.assertTrue(Dealloc.BG_RAN)
+ self.assertEqual(Dealloc.BG_RAN, greenlet.GreenletExit)
+ self.assertEqual(Dealloc.CREATED, Dealloc.DESTROYED )
+ self.assertEqual(Dealloc.CREATED, OBJECTS_PER_CONTAINER * 2)
+
+ import gc
+ gc.collect()
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/test_leaks.py b/venv/lib/python3.11/site-packages/greenlet/tests/test_leaks.py
new file mode 100644
index 0000000..ed1fa71
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/test_leaks.py
@@ -0,0 +1,443 @@
+# -*- coding: utf-8 -*-
+"""
+Testing scenarios that may have leaked.
+"""
+from __future__ import print_function, absolute_import, division
+
+import sys
+import gc
+
+import time
+import weakref
+import threading
+
+
+import greenlet
+from . import TestCase
+from .leakcheck import fails_leakcheck
+from .leakcheck import ignores_leakcheck
+from .leakcheck import RUNNING_ON_MANYLINUX
+
+# pylint:disable=protected-access
+
+assert greenlet.GREENLET_USE_GC # Option to disable this was removed in 1.0
+
+class HasFinalizerTracksInstances(object):
+ EXTANT_INSTANCES = set()
+ def __init__(self, msg):
+ self.msg = sys.intern(msg)
+ self.EXTANT_INSTANCES.add(id(self))
+ def __del__(self):
+ self.EXTANT_INSTANCES.remove(id(self))
+ def __repr__(self):
+ return "<HasFinalizerTracksInstances at 0x%x %r>" % (
+ id(self), self.msg
+ )
+ @classmethod
+ def reset(cls):
+ cls.EXTANT_INSTANCES.clear()
+
+
+class TestLeaks(TestCase):
+
+ def test_arg_refs(self):
+ args = ('a', 'b', 'c')
+ refcount_before = sys.getrefcount(args)
+ # pylint:disable=unnecessary-lambda
+ g = greenlet.greenlet(
+ lambda *args: greenlet.getcurrent().parent.switch(*args))
+ for _ in range(100):
+ g.switch(*args)
+ self.assertEqual(sys.getrefcount(args), refcount_before)
+
+ def test_kwarg_refs(self):
+ kwargs = {}
+ # pylint:disable=unnecessary-lambda
+ g = greenlet.greenlet(
+ lambda **kwargs: greenlet.getcurrent().parent.switch(**kwargs))
+ for _ in range(100):
+ g.switch(**kwargs)
+ self.assertEqual(sys.getrefcount(kwargs), 2)
+
+
+ @staticmethod
+ def __recycle_threads():
+ # By introducing a thread that does sleep we allow other threads,
+ # that have triggered their __block condition, but did not have a
+ # chance to deallocate their thread state yet, to finally do so.
+ # The way it works is by requiring a GIL switch (different thread),
+ # which does a GIL release (sleep), which might do a GIL switch
+ # to finished threads and allow them to clean up.
+ def worker():
+ time.sleep(0.001)
+ t = threading.Thread(target=worker)
+ t.start()
+ time.sleep(0.001)
+ t.join(10)
+
+ def test_threaded_leak(self):
+ gg = []
+ def worker():
+ # only main greenlet present
+ gg.append(weakref.ref(greenlet.getcurrent()))
+ for _ in range(2):
+ t = threading.Thread(target=worker)
+ t.start()
+ t.join(10)
+ del t
+ greenlet.getcurrent() # update ts_current
+ self.__recycle_threads()
+ greenlet.getcurrent() # update ts_current
+ gc.collect()
+ greenlet.getcurrent() # update ts_current
+ for g in gg:
+ self.assertIsNone(g())
+
+ def test_threaded_adv_leak(self):
+ gg = []
+ def worker():
+ # main and additional *finished* greenlets
+ ll = greenlet.getcurrent().ll = []
+ def additional():
+ ll.append(greenlet.getcurrent())
+ for _ in range(2):
+ greenlet.greenlet(additional).switch()
+ gg.append(weakref.ref(greenlet.getcurrent()))
+ for _ in range(2):
+ t = threading.Thread(target=worker)
+ t.start()
+ t.join(10)
+ del t
+ greenlet.getcurrent() # update ts_current
+ self.__recycle_threads()
+ greenlet.getcurrent() # update ts_current
+ gc.collect()
+ greenlet.getcurrent() # update ts_current
+ for g in gg:
+ self.assertIsNone(g())
+
+ def assertClocksUsed(self):
+ used = greenlet._greenlet.get_clocks_used_doing_optional_cleanup()
+ self.assertGreaterEqual(used, 0)
+ # we don't lose the value
+ greenlet._greenlet.enable_optional_cleanup(True)
+ used2 = greenlet._greenlet.get_clocks_used_doing_optional_cleanup()
+ self.assertEqual(used, used2)
+ self.assertGreater(greenlet._greenlet.CLOCKS_PER_SEC, 1)
+
+ def _check_issue251(self,
+ manually_collect_background=True,
+ explicit_reference_to_switch=False):
+ # See https://github.com/python-greenlet/greenlet/issues/251
+ # Killing a greenlet (probably not the main one)
+ # in one thread from another thread would
+ # result in leaking a list (the ts_delkey list).
+ # We no longer use lists to hold that stuff, though.
+
+ # For the test to be valid, even empty lists have to be tracked by the
+ # GC
+
+ assert gc.is_tracked([])
+ HasFinalizerTracksInstances.reset()
+ greenlet.getcurrent()
+ greenlets_before = self.count_objects(greenlet.greenlet, exact_kind=False)
+
+ background_glet_running = threading.Event()
+ background_glet_killed = threading.Event()
+ background_greenlets = []
+
+ # XXX: Switching this to a greenlet subclass that overrides
+ # run results in all callers failing the leaktest; that
+ # greenlet instance is leaked. There's a bound method for
+ # run() living on the stack of the greenlet in g_initialstub,
+ # and since we don't manually switch back to the background
+ # greenlet to let it "fall off the end" and exit the
+ # g_initialstub function, it never gets cleaned up. Making the
+ # garbage collector aware of this bound method (making it an
+ # attribute of the greenlet structure and traversing into it)
+ # doesn't help, for some reason.
+ def background_greenlet():
+ # Throw control back to the main greenlet.
+ jd = HasFinalizerTracksInstances("DELETING STACK OBJECT")
+ greenlet._greenlet.set_thread_local(
+ 'test_leaks_key',
+ HasFinalizerTracksInstances("DELETING THREAD STATE"))
+ # Explicitly keeping 'switch' in a local variable
+ # breaks this test in all versions
+ if explicit_reference_to_switch:
+ s = greenlet.getcurrent().parent.switch
+ s([jd])
+ else:
+ greenlet.getcurrent().parent.switch([jd])
+
+ bg_main_wrefs = []
+
+ def background_thread():
+ glet = greenlet.greenlet(background_greenlet)
+ bg_main_wrefs.append(weakref.ref(glet.parent))
+
+ background_greenlets.append(glet)
+ glet.switch() # Be sure it's active.
+ # Control is ours again.
+ del glet # Delete one reference from the thread it runs in.
+ background_glet_running.set()
+ background_glet_killed.wait(10)
+
+ # To trigger the background collection of the dead
+ # greenlet, thus clearing out the contents of the list, we
+ # need to run some APIs. See issue 252.
+ if manually_collect_background:
+ greenlet.getcurrent()
+
+
+ t = threading.Thread(target=background_thread)
+ t.start()
+ background_glet_running.wait(10)
+ greenlet.getcurrent()
+ lists_before = self.count_objects(list, exact_kind=True)
+
+ assert len(background_greenlets) == 1
+ self.assertFalse(background_greenlets[0].dead)
+ # Delete the last reference to the background greenlet
+ # from a different thread. This puts it in the background thread's
+ # ts_delkey list.
+ del background_greenlets[:]
+ background_glet_killed.set()
+
+ # Now wait for the background thread to die.
+ t.join(10)
+ del t
+ # As part of the fix for 252, we need to cycle the ceval.c
+ # interpreter loop to be sure it has had a chance to process
+ # the pending call.
+ self.wait_for_pending_cleanups()
+
+ lists_after = self.count_objects(list, exact_kind=True)
+ greenlets_after = self.count_objects(greenlet.greenlet, exact_kind=False)
+
+ # On 2.7, we observe that lists_after is smaller than
+ # lists_before. No idea what lists got cleaned up. All the
+ # Python 3 versions match exactly.
+ self.assertLessEqual(lists_after, lists_before)
+ # On versions after 3.6, we've successfully cleaned up the
+ # greenlet references thanks to the internal "vectorcall"
+ # protocol; prior to that, there is a reference path through
+ # the ``greenlet.switch`` method still on the stack that we
+ # can't reach to clean up. The C code goes through terrific
+ # lengths to clean that up.
+ if not explicit_reference_to_switch \
+ and greenlet._greenlet.get_clocks_used_doing_optional_cleanup() is not None:
+ # If cleanup was disabled, though, we may not find it.
+ self.assertEqual(greenlets_after, greenlets_before)
+ if manually_collect_background:
+ # TODO: Figure out how to make this work!
+ # The one on the stack is still leaking somehow
+ # in the non-manually-collect state.
+ self.assertEqual(HasFinalizerTracksInstances.EXTANT_INSTANCES, set())
+ else:
+ # The explicit reference prevents us from collecting it
+ # and it isn't always found by the GC either for some
+ # reason. The entire frame is leaked somehow, on some
+ # platforms (e.g., MacPorts builds of Python (all
+ # versions!)), but not on other platforms (the linux and
+ # windows builds on GitHub actions and Appveyor). So we'd
+ # like to write a test that proves that the main greenlet
+ # sticks around, and we can on my machine (macOS 11.6,
+ # MacPorts builds of everything) but we can't write that
+ # same test on other platforms. However, hopefully iteration
+ # done by leakcheck will find it.
+ pass
+
+ if greenlet._greenlet.get_clocks_used_doing_optional_cleanup() is not None:
+ self.assertClocksUsed()
+
+ def test_issue251_killing_cross_thread_leaks_list(self):
+ self._check_issue251()
+
+ def test_issue251_with_cleanup_disabled(self):
+ greenlet._greenlet.enable_optional_cleanup(False)
+ try:
+ self._check_issue251()
+ finally:
+ greenlet._greenlet.enable_optional_cleanup(True)
+
+ @fails_leakcheck
+ def test_issue251_issue252_need_to_collect_in_background(self):
+ # Between greenlet 1.1.2 and the next version, this was still
+ # failing because the leak of the list still exists when we
+ # don't call a greenlet API before exiting the thread. The
+ # proximate cause is that neither of the two greenlets from
+ # the background thread are actually being destroyed, even
+ # though the GC is in fact visiting both objects. It's not
+ # clear where that leak is? For some reason the thread-local
+ # dict holding it isn't being cleaned up.
+ #
+ # The leak, I think, is in the CPYthon internal function that
+ # calls into green_switch(). The argument tuple is still on
+ # the C stack somewhere and can't be reached? That doesn't
+ # make sense, because the tuple should be collectable when
+ # this object goes away.
+ #
+ # Note that this test sometimes spuriously passes on Linux,
+ # for some reason, but I've never seen it pass on macOS.
+ self._check_issue251(manually_collect_background=False)
+
+ @fails_leakcheck
+ def test_issue251_issue252_need_to_collect_in_background_cleanup_disabled(self):
+ self.expect_greenlet_leak = True
+ greenlet._greenlet.enable_optional_cleanup(False)
+ try:
+ self._check_issue251(manually_collect_background=False)
+ finally:
+ greenlet._greenlet.enable_optional_cleanup(True)
+
+ @fails_leakcheck
+ def test_issue251_issue252_explicit_reference_not_collectable(self):
+ self._check_issue251(
+ manually_collect_background=False,
+ explicit_reference_to_switch=True)
+
+ UNTRACK_ATTEMPTS = 100
+
+ def _only_test_some_versions(self):
+ # We're only looking for this problem specifically on 3.11,
+ # and this set of tests is relatively fragile, depending on
+ # OS and memory management details. So we want to run it on 3.11+
+ # (obviously) but not every older 3.x version in order to reduce
+ # false negatives. At the moment, those false results seem to have
+ # resolved, so we are actually running this on 3.8+
+ assert sys.version_info[0] >= 3
+ if sys.version_info[:2] < (3, 8):
+ self.skipTest('Only observed on 3.11')
+ if RUNNING_ON_MANYLINUX:
+ self.skipTest("Slow and not worth repeating here")
+
+ @ignores_leakcheck
+ # Because we're just trying to track raw memory, not objects, and running
+ # the leakcheck makes an already slow test slower.
+ def test_untracked_memory_doesnt_increase(self):
+ # See https://github.com/gevent/gevent/issues/1924
+ # and https://github.com/python-greenlet/greenlet/issues/328
+ self._only_test_some_versions()
+ def f():
+ return 1
+
+ ITER = 10000
+ def run_it():
+ for _ in range(ITER):
+ greenlet.greenlet(f).switch()
+
+ # Establish baseline
+ for _ in range(3):
+ run_it()
+
+ # uss: (Linux, macOS, Windows): aka "Unique Set Size", this is
+ # the memory which is unique to a process and which would be
+ # freed if the process was terminated right now.
+ uss_before = self.get_process_uss()
+
+ for count in range(self.UNTRACK_ATTEMPTS):
+ uss_before = max(uss_before, self.get_process_uss())
+ run_it()
+
+ uss_after = self.get_process_uss()
+ if uss_after <= uss_before and count > 1:
+ break
+
+ self.assertLessEqual(uss_after, uss_before)
+
+ def _check_untracked_memory_thread(self, deallocate_in_thread=True):
+ self._only_test_some_versions()
+ # Like the above test, but what if there are a bunch of
+ # unfinished greenlets in a thread that dies?
+ # Does it matter if we deallocate in the thread or not?
+ EXIT_COUNT = [0]
+
+ def f():
+ try:
+ greenlet.getcurrent().parent.switch()
+ except greenlet.GreenletExit:
+ EXIT_COUNT[0] += 1
+ raise
+ return 1
+
+ ITER = 10000
+ def run_it():
+ glets = []
+ for _ in range(ITER):
+ # Greenlet starts, switches back to us.
+ # We keep a strong reference to the greenlet though so it doesn't
+ # get a GreenletExit exception.
+ g = greenlet.greenlet(f)
+ glets.append(g)
+ g.switch()
+
+ return glets
+
+ test = self
+
+ class ThreadFunc:
+ uss_before = uss_after = 0
+ glets = ()
+ ITER = 2
+ def __call__(self):
+ self.uss_before = test.get_process_uss()
+
+ for _ in range(self.ITER):
+ self.glets += tuple(run_it())
+
+ for g in self.glets:
+ test.assertIn('suspended active', str(g))
+ # Drop them.
+ if deallocate_in_thread:
+ self.glets = ()
+ self.uss_after = test.get_process_uss()
+
+ # Establish baseline
+ uss_before = uss_after = None
+ for count in range(self.UNTRACK_ATTEMPTS):
+ EXIT_COUNT[0] = 0
+ thread_func = ThreadFunc()
+ t = threading.Thread(target=thread_func)
+ t.start()
+ t.join(30)
+ self.assertFalse(t.is_alive())
+
+ if uss_before is None:
+ uss_before = thread_func.uss_before
+
+ uss_before = max(uss_before, thread_func.uss_before)
+ if deallocate_in_thread:
+ self.assertEqual(thread_func.glets, ())
+ self.assertEqual(EXIT_COUNT[0], ITER * thread_func.ITER)
+
+ del thread_func # Deallocate the greenlets; but this won't raise into them
+ del t
+ if not deallocate_in_thread:
+ self.assertEqual(EXIT_COUNT[0], 0)
+ if deallocate_in_thread:
+ self.wait_for_pending_cleanups()
+
+ uss_after = self.get_process_uss()
+ # See if we achieve a non-growth state at some point. Break when we do.
+ if uss_after <= uss_before and count > 1:
+ break
+
+ self.wait_for_pending_cleanups()
+ uss_after = self.get_process_uss()
+ self.assertLessEqual(uss_after, uss_before, "after attempts %d" % (count,))
+
+ @ignores_leakcheck
+ # Because we're just trying to track raw memory, not objects, and running
+ # the leakcheck makes an already slow test slower.
+ def test_untracked_memory_doesnt_increase_unfinished_thread_dealloc_in_thread(self):
+ self._check_untracked_memory_thread(deallocate_in_thread=True)
+
+ @ignores_leakcheck
+ # Because the main greenlets from the background threads do not exit in a timely fashion,
+ # we fail the object-based leakchecks.
+ def test_untracked_memory_doesnt_increase_unfinished_thread_dealloc_in_main(self):
+ self._check_untracked_memory_thread(deallocate_in_thread=False)
+
+if __name__ == '__main__':
+ __import__('unittest').main()
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/test_stack_saved.py b/venv/lib/python3.11/site-packages/greenlet/tests/test_stack_saved.py
new file mode 100644
index 0000000..b362bf9
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/test_stack_saved.py
@@ -0,0 +1,19 @@
+import greenlet
+from . import TestCase
+
+
+class Test(TestCase):
+
+ def test_stack_saved(self):
+ main = greenlet.getcurrent()
+ self.assertEqual(main._stack_saved, 0)
+
+ def func():
+ main.switch(main._stack_saved)
+
+ g = greenlet.greenlet(func)
+ x = g.switch()
+ self.assertGreater(x, 0)
+ self.assertGreater(g._stack_saved, 0)
+ g.switch()
+ self.assertEqual(g._stack_saved, 0)
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/test_throw.py b/venv/lib/python3.11/site-packages/greenlet/tests/test_throw.py
new file mode 100644
index 0000000..f4f9a14
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/test_throw.py
@@ -0,0 +1,128 @@
+import sys
+
+
+from greenlet import greenlet
+from . import TestCase
+
+def switch(*args):
+ return greenlet.getcurrent().parent.switch(*args)
+
+
+class ThrowTests(TestCase):
+ def test_class(self):
+ def f():
+ try:
+ switch("ok")
+ except RuntimeError:
+ switch("ok")
+ return
+ switch("fail")
+ g = greenlet(f)
+ res = g.switch()
+ self.assertEqual(res, "ok")
+ res = g.throw(RuntimeError)
+ self.assertEqual(res, "ok")
+
+ def test_val(self):
+ def f():
+ try:
+ switch("ok")
+ except RuntimeError:
+ val = sys.exc_info()[1]
+ if str(val) == "ciao":
+ switch("ok")
+ return
+ switch("fail")
+
+ g = greenlet(f)
+ res = g.switch()
+ self.assertEqual(res, "ok")
+ res = g.throw(RuntimeError("ciao"))
+ self.assertEqual(res, "ok")
+
+ g = greenlet(f)
+ res = g.switch()
+ self.assertEqual(res, "ok")
+ res = g.throw(RuntimeError, "ciao")
+ self.assertEqual(res, "ok")
+
+ def test_kill(self):
+ def f():
+ switch("ok")
+ switch("fail")
+ g = greenlet(f)
+ res = g.switch()
+ self.assertEqual(res, "ok")
+ res = g.throw()
+ self.assertTrue(isinstance(res, greenlet.GreenletExit))
+ self.assertTrue(g.dead)
+ res = g.throw() # immediately eaten by the already-dead greenlet
+ self.assertTrue(isinstance(res, greenlet.GreenletExit))
+
+ def test_throw_goes_to_original_parent(self):
+ main = greenlet.getcurrent()
+
+ def f1():
+ try:
+ main.switch("f1 ready to catch")
+ except IndexError:
+ return "caught"
+ return "normal exit"
+
+ def f2():
+ main.switch("from f2")
+
+ g1 = greenlet(f1)
+ g2 = greenlet(f2, parent=g1)
+ with self.assertRaises(IndexError):
+ g2.throw(IndexError)
+ self.assertTrue(g2.dead)
+ self.assertTrue(g1.dead)
+
+ g1 = greenlet(f1)
+ g2 = greenlet(f2, parent=g1)
+ res = g1.switch()
+ self.assertEqual(res, "f1 ready to catch")
+ res = g2.throw(IndexError)
+ self.assertEqual(res, "caught")
+ self.assertTrue(g2.dead)
+ self.assertTrue(g1.dead)
+
+ g1 = greenlet(f1)
+ g2 = greenlet(f2, parent=g1)
+ res = g1.switch()
+ self.assertEqual(res, "f1 ready to catch")
+ res = g2.switch()
+ self.assertEqual(res, "from f2")
+ res = g2.throw(IndexError)
+ self.assertEqual(res, "caught")
+ self.assertTrue(g2.dead)
+ self.assertTrue(g1.dead)
+
+ def test_non_traceback_param(self):
+ with self.assertRaises(TypeError) as exc:
+ greenlet.getcurrent().throw(
+ Exception,
+ Exception(),
+ self
+ )
+ self.assertEqual(str(exc.exception),
+ "throw() third argument must be a traceback object")
+
+ def test_instance_of_wrong_type(self):
+ with self.assertRaises(TypeError) as exc:
+ greenlet.getcurrent().throw(
+ Exception(),
+ BaseException()
+ )
+
+ self.assertEqual(str(exc.exception),
+ "instance exception may not have a separate value")
+
+ def test_not_throwable(self):
+ with self.assertRaises(TypeError) as exc:
+ greenlet.getcurrent().throw(
+ "abc"
+ )
+ self.assertEqual(str(exc.exception),
+ "exceptions must be classes, or instances, not str")
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/test_tracing.py b/venv/lib/python3.11/site-packages/greenlet/tests/test_tracing.py
new file mode 100644
index 0000000..c044d4b
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/test_tracing.py
@@ -0,0 +1,291 @@
+from __future__ import print_function
+import sys
+import greenlet
+import unittest
+
+from . import TestCase
+from . import PY312
+
+# https://discuss.python.org/t/cpython-3-12-greenlet-and-tracing-profiling-how-to-not-crash-and-get-correct-results/33144/2
+DEBUG_BUILD_PY312 = (
+ PY312 and hasattr(sys, 'gettotalrefcount'),
+ "Broken on debug builds of Python 3.12"
+)
+
+class SomeError(Exception):
+ pass
+
+class GreenletTracer(object):
+ oldtrace = None
+
+ def __init__(self, error_on_trace=False):
+ self.actions = []
+ self.error_on_trace = error_on_trace
+
+ def __call__(self, *args):
+ self.actions.append(args)
+ if self.error_on_trace:
+ raise SomeError
+
+ def __enter__(self):
+ self.oldtrace = greenlet.settrace(self)
+ return self.actions
+
+ def __exit__(self, *args):
+ greenlet.settrace(self.oldtrace)
+
+
+class TestGreenletTracing(TestCase):
+ """
+ Tests of ``greenlet.settrace()``
+ """
+
+ def test_a_greenlet_tracing(self):
+ main = greenlet.getcurrent()
+ def dummy():
+ pass
+ def dummyexc():
+ raise SomeError()
+
+ with GreenletTracer() as actions:
+ g1 = greenlet.greenlet(dummy)
+ g1.switch()
+ g2 = greenlet.greenlet(dummyexc)
+ self.assertRaises(SomeError, g2.switch)
+
+ self.assertEqual(actions, [
+ ('switch', (main, g1)),
+ ('switch', (g1, main)),
+ ('switch', (main, g2)),
+ ('throw', (g2, main)),
+ ])
+
+ def test_b_exception_disables_tracing(self):
+ main = greenlet.getcurrent()
+ def dummy():
+ main.switch()
+ g = greenlet.greenlet(dummy)
+ g.switch()
+ with GreenletTracer(error_on_trace=True) as actions:
+ self.assertRaises(SomeError, g.switch)
+ self.assertEqual(greenlet.gettrace(), None)
+
+ self.assertEqual(actions, [
+ ('switch', (main, g)),
+ ])
+
+ def test_set_same_tracer_twice(self):
+ # https://github.com/python-greenlet/greenlet/issues/332
+ # Our logic in asserting that the tracefunction should
+ # gain a reference was incorrect if the same tracefunction was set
+ # twice.
+ tracer = GreenletTracer()
+ with tracer:
+ greenlet.settrace(tracer)
+
+
+class PythonTracer(object):
+ oldtrace = None
+
+ def __init__(self):
+ self.actions = []
+
+ def __call__(self, frame, event, arg):
+ # Record the co_name so we have an idea what function we're in.
+ self.actions.append((event, frame.f_code.co_name))
+
+ def __enter__(self):
+ self.oldtrace = sys.setprofile(self)
+ return self.actions
+
+ def __exit__(self, *args):
+ sys.setprofile(self.oldtrace)
+
+def tpt_callback():
+ return 42
+
+class TestPythonTracing(TestCase):
+ """
+ Tests of the interaction of ``sys.settrace()``
+ with greenlet facilities.
+
+ NOTE: Most of this is probably CPython specific.
+ """
+
+ maxDiff = None
+
+ def test_trace_events_trivial(self):
+ with PythonTracer() as actions:
+ tpt_callback()
+ # If we use the sys.settrace instead of setprofile, we get
+ # this:
+
+ # self.assertEqual(actions, [
+ # ('call', 'tpt_callback'),
+ # ('call', '__exit__'),
+ # ])
+
+ self.assertEqual(actions, [
+ ('return', '__enter__'),
+ ('call', 'tpt_callback'),
+ ('return', 'tpt_callback'),
+ ('call', '__exit__'),
+ ('c_call', '__exit__'),
+ ])
+
+ def _trace_switch(self, glet):
+ with PythonTracer() as actions:
+ glet.switch()
+ return actions
+
+ def _check_trace_events_func_already_set(self, glet):
+ actions = self._trace_switch(glet)
+ self.assertEqual(actions, [
+ ('return', '__enter__'),
+ ('c_call', '_trace_switch'),
+ ('call', 'run'),
+ ('call', 'tpt_callback'),
+ ('return', 'tpt_callback'),
+ ('return', 'run'),
+ ('c_return', '_trace_switch'),
+ ('call', '__exit__'),
+ ('c_call', '__exit__'),
+ ])
+
+ def test_trace_events_into_greenlet_func_already_set(self):
+ def run():
+ return tpt_callback()
+
+ self._check_trace_events_func_already_set(greenlet.greenlet(run))
+
+ def test_trace_events_into_greenlet_subclass_already_set(self):
+ class X(greenlet.greenlet):
+ def run(self):
+ return tpt_callback()
+ self._check_trace_events_func_already_set(X())
+
+ def _check_trace_events_from_greenlet_sets_profiler(self, g, tracer):
+ g.switch()
+ tpt_callback()
+ tracer.__exit__()
+ self.assertEqual(tracer.actions, [
+ ('return', '__enter__'),
+ ('call', 'tpt_callback'),
+ ('return', 'tpt_callback'),
+ ('return', 'run'),
+ ('call', 'tpt_callback'),
+ ('return', 'tpt_callback'),
+ ('call', '__exit__'),
+ ('c_call', '__exit__'),
+ ])
+
+
+ def test_trace_events_from_greenlet_func_sets_profiler(self):
+ tracer = PythonTracer()
+ def run():
+ tracer.__enter__()
+ return tpt_callback()
+
+ self._check_trace_events_from_greenlet_sets_profiler(greenlet.greenlet(run),
+ tracer)
+
+ def test_trace_events_from_greenlet_subclass_sets_profiler(self):
+ tracer = PythonTracer()
+ class X(greenlet.greenlet):
+ def run(self):
+ tracer.__enter__()
+ return tpt_callback()
+
+ self._check_trace_events_from_greenlet_sets_profiler(X(), tracer)
+
+ @unittest.skipIf(*DEBUG_BUILD_PY312)
+ def test_trace_events_multiple_greenlets_switching(self):
+ tracer = PythonTracer()
+
+ g1 = None
+ g2 = None
+
+ def g1_run():
+ tracer.__enter__()
+ tpt_callback()
+ g2.switch()
+ tpt_callback()
+ return 42
+
+ def g2_run():
+ tpt_callback()
+ tracer.__exit__()
+ tpt_callback()
+ g1.switch()
+
+ g1 = greenlet.greenlet(g1_run)
+ g2 = greenlet.greenlet(g2_run)
+
+ x = g1.switch()
+ self.assertEqual(x, 42)
+ tpt_callback() # ensure not in the trace
+ self.assertEqual(tracer.actions, [
+ ('return', '__enter__'),
+ ('call', 'tpt_callback'),
+ ('return', 'tpt_callback'),
+ ('c_call', 'g1_run'),
+ ('call', 'g2_run'),
+ ('call', 'tpt_callback'),
+ ('return', 'tpt_callback'),
+ ('call', '__exit__'),
+ ('c_call', '__exit__'),
+ ])
+
+ @unittest.skipIf(*DEBUG_BUILD_PY312)
+ def test_trace_events_multiple_greenlets_switching_siblings(self):
+ # Like the first version, but get both greenlets running first
+ # as "siblings" and then establish the tracing.
+ tracer = PythonTracer()
+
+ g1 = None
+ g2 = None
+
+ def g1_run():
+ greenlet.getcurrent().parent.switch()
+ tracer.__enter__()
+ tpt_callback()
+ g2.switch()
+ tpt_callback()
+ return 42
+
+ def g2_run():
+ greenlet.getcurrent().parent.switch()
+
+ tpt_callback()
+ tracer.__exit__()
+ tpt_callback()
+ g1.switch()
+
+ g1 = greenlet.greenlet(g1_run)
+ g2 = greenlet.greenlet(g2_run)
+
+ # Start g1
+ g1.switch()
+ # And it immediately returns control to us.
+ # Start g2
+ g2.switch()
+ # Which also returns. Now kick of the real part of the
+ # test.
+ x = g1.switch()
+ self.assertEqual(x, 42)
+
+ tpt_callback() # ensure not in the trace
+ self.assertEqual(tracer.actions, [
+ ('return', '__enter__'),
+ ('call', 'tpt_callback'),
+ ('return', 'tpt_callback'),
+ ('c_call', 'g1_run'),
+ ('call', 'tpt_callback'),
+ ('return', 'tpt_callback'),
+ ('call', '__exit__'),
+ ('c_call', '__exit__'),
+ ])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/test_version.py b/venv/lib/python3.11/site-packages/greenlet/tests/test_version.py
new file mode 100644
index 0000000..96c17cf
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/test_version.py
@@ -0,0 +1,41 @@
+#! /usr/bin/env python
+from __future__ import absolute_import
+from __future__ import print_function
+
+import sys
+import os
+from unittest import TestCase as NonLeakingTestCase
+
+import greenlet
+
+# No reason to run this multiple times under leakchecks,
+# it doesn't do anything.
+class VersionTests(NonLeakingTestCase):
+ def test_version(self):
+ def find_dominating_file(name):
+ if os.path.exists(name):
+ return name
+
+ tried = []
+ here = os.path.abspath(os.path.dirname(__file__))
+ for i in range(10):
+ up = ['..'] * i
+ path = [here] + up + [name]
+ fname = os.path.join(*path)
+ fname = os.path.abspath(fname)
+ tried.append(fname)
+ if os.path.exists(fname):
+ return fname
+ raise AssertionError("Could not find file " + name + "; checked " + str(tried))
+
+ try:
+ setup_py = find_dominating_file('setup.py')
+ except AssertionError as e:
+ self.skipTest("Unable to find setup.py; must be out of tree. " + str(e))
+
+
+ invoke_setup = "%s %s --version" % (sys.executable, setup_py)
+ with os.popen(invoke_setup) as f:
+ sversion = f.read().strip()
+
+ self.assertEqual(sversion, greenlet.__version__)
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/test_weakref.py b/venv/lib/python3.11/site-packages/greenlet/tests/test_weakref.py
new file mode 100644
index 0000000..05a38a7
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/tests/test_weakref.py
@@ -0,0 +1,35 @@
+import gc
+import weakref
+
+
+import greenlet
+from . import TestCase
+
+class WeakRefTests(TestCase):
+ def test_dead_weakref(self):
+ def _dead_greenlet():
+ g = greenlet.greenlet(lambda: None)
+ g.switch()
+ return g
+ o = weakref.ref(_dead_greenlet())
+ gc.collect()
+ self.assertEqual(o(), None)
+
+ def test_inactive_weakref(self):
+ o = weakref.ref(greenlet.greenlet())
+ gc.collect()
+ self.assertEqual(o(), None)
+
+ def test_dealloc_weakref(self):
+ seen = []
+ def worker():
+ try:
+ greenlet.getcurrent().parent.switch()
+ finally:
+ seen.append(g())
+ g = greenlet.greenlet(worker)
+ g.switch()
+ g2 = greenlet.greenlet(lambda: None, g)
+ g = weakref.ref(g2)
+ g2 = None
+ self.assertEqual(seen, [None])