Загрузить файлы в «venv/Lib/site-packages/greenlet/tests»
This commit is contained in:
248
venv/Lib/site-packages/greenlet/tests/__init__.py
Normal file
248
venv/Lib/site-packages/greenlet/tests/__init__.py
Normal file
@@ -0,0 +1,248 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Tests for greenlet.
|
||||
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import sysconfig
|
||||
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)
|
||||
PY313 = sys.version_info[:2] >= (3, 13)
|
||||
# XXX: First tested on 3.14a7. Revisit all uses of this on later versions to ensure they
|
||||
# are still valid.
|
||||
PY314 = sys.version_info[:2] >= (3, 14)
|
||||
|
||||
WIN = sys.platform.startswith("win")
|
||||
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')
|
||||
|
||||
# Is the current interpreter free-threaded?) Note that this
|
||||
# isn't the same as whether the GIL is enabled, this is the build-time
|
||||
# value. Certain CPython details, like the garbage collector,
|
||||
# work very differently on potentially-free-threaded builds than
|
||||
# standard builds.
|
||||
RUNNING_ON_FREETHREAD_BUILD = bool(sysconfig.get_config_var("Py_GIL_DISABLED"))
|
||||
|
||||
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(unittest.TestCase, metaclass=TestCaseMetaClass):
|
||||
|
||||
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
|
||||
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
|
||||
273
venv/Lib/site-packages/greenlet/tests/_test_extension.c
Normal file
273
venv/Lib/site-packages/greenlet/tests/_test_extension.c
Normal file
@@ -0,0 +1,273 @@
|
||||
/* 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"
|
||||
|
||||
// CAUTION: MSVC is stupidly picky:
|
||||
//
|
||||
// "The compiler ignores, without warning, any __declspec keywords
|
||||
// placed after * or & and in front of the variable identifier in a
|
||||
// declaration."
|
||||
// (https://docs.microsoft.com/en-us/cpp/cpp/declspec?view=msvc-160)
|
||||
//
|
||||
// So pointer return types must be handled differently (because of the
|
||||
// trailing *), or you get inscrutable compiler warnings like "error
|
||||
// C2059: syntax error: ''"
|
||||
//
|
||||
// In C23, there is a standard syntax for attributes, and
|
||||
// GCC defines an attribute to use with this: [[gnu:noinline]].
|
||||
// In the future, this is expected to become standard.
|
||||
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
/* We used to check for GCC 4+ or 3.4+, but those compilers are
|
||||
laughably out of date. Just assume they support it. */
|
||||
# define UNUSED(x) UNUSED_ ## x __attribute__((__unused__))
|
||||
#elif defined(_MSC_VER)
|
||||
/* We used to check for && (_MSC_VER >= 1300) but that's also out of date. */
|
||||
# define UNUSED(x) UNUSED_ ## x
|
||||
#endif
|
||||
|
||||
static PyObject*
|
||||
test_switch(PyObject* UNUSED(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;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
test_switch_kwargs(PyObject* UNUSED(self), PyObject* args, PyObject* kwargs)
|
||||
{
|
||||
PyGreenlet* g = NULL;
|
||||
PyObject* result = NULL;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O!", &PyGreenlet_Type, &g)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
test_getcurrent(PyObject* UNUSED(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* UNUSED(self), PyObject* arg)
|
||||
{
|
||||
PyGreenlet* current;
|
||||
PyGreenlet* greenlet = NULL;
|
||||
PyObject* switch_result = 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);
|
||||
|
||||
switch_result = PyGreenlet_Switch(greenlet, NULL, NULL);
|
||||
if (switch_result == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
Py_DECREF(switch_result);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
test_new_greenlet(PyObject* UNUSED(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;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
test_raise_dead_greenlet(PyObject* UNUSED(self))
|
||||
{
|
||||
PyErr_SetString(PyExc_GreenletExit, "test GreenletExit exception.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
test_raise_greenlet_error(PyObject* UNUSED(self))
|
||||
{
|
||||
PyErr_SetString(PyExc_GreenletError, "test greenlet.error exception");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
test_throw(PyObject* UNUSED(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* UNUSED(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 PyObject*
|
||||
getcurrent_api(PyObject* UNUSED(self))
|
||||
{
|
||||
return (PyObject*)PyGreenlet_GetCurrent();
|
||||
|
||||
}
|
||||
|
||||
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"},
|
||||
{
|
||||
"getcurrent_api",
|
||||
(PyCFunction)getcurrent_api,
|
||||
METH_NOARGS,
|
||||
"Direct call to the PyGreenlet_GetCurrent API."},
|
||||
{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();
|
||||
#ifdef Py_GIL_DISABLED
|
||||
PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED);
|
||||
#endif
|
||||
return module;
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
230
venv/Lib/site-packages/greenlet/tests/_test_extension_cpp.cpp
Normal file
230
venv/Lib/site-packages/greenlet/tests/_test_extension_cpp.cpp
Normal file
@@ -0,0 +1,230 @@
|
||||
/* 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* UNUSED(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* UNUSED(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* UNUSED(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* UNUSED(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) {
|
||||
Py_DECREF(g2);
|
||||
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;
|
||||
#ifdef Py_GIL_DISABLED
|
||||
PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED);
|
||||
#endif
|
||||
|
||||
return module;
|
||||
}
|
||||
Reference in New Issue
Block a user