Skip to content

Commit

Permalink
Add an optional test for greenlet C++ exceptions
Browse files Browse the repository at this point in the history
  • Loading branch information
snaury committed Dec 17, 2011
1 parent db5e6d4 commit d9cb12a
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 1 deletion.
13 changes: 12 additions & 1 deletion tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@
include_dirs=[os.path.curdir]),
]

if os.environ.get('GREENLET_TEST_CPP'):
TEST_EXTENSIONS_CPP = [
Extension('_test_extension_cpp',
[os.path.join('tests', '_test_extension_cpp.cpp')],
language="c++",
include_dirs=[os.path.curdir]),
]
else:
TEST_EXTENSIONS_CPP = []

def test_collector():
"""Collect all tests under the tests directory and return a
Expand All @@ -29,6 +38,8 @@ def test_collector():
test_module_list = [
'tests.%s' % os.path.splitext(os.path.basename(t))[0]
for t in glob.glob(os.path.join(tests_dir, 'test_*.py'))]
if not TEST_EXTENSIONS_CPP:
test_module_list.remove('tests.test_cpp')
return unittest.TestLoader().loadTestsFromNames(test_module_list)

def build_test_extensions():
Expand All @@ -46,4 +57,4 @@ def build_test_extensions():
'build': {'build_base': os.path.join('build', 'tests')},
},
script_args=['build_ext', '-i'],
ext_modules=TEST_EXTENSIONS)
ext_modules=TEST_EXTENSIONS + TEST_EXTENSIONS_CPP)
118 changes: 118 additions & 0 deletions tests/_test_extension_cpp.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/* This is a set of functions used to test C++ exceptions are not
* broken during greenlet switches
*/

#include "../greenlet.h"

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)(int depth);
static PyObject* (*p_test_exception_switch_recurse)(int depth, int left);

static void test_exception_throw(int depth)
{
throw exception_t(depth);
}

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 {
PyGreenlet_Switch(self->parent, NULL, NULL);
p_test_exception_throw(depth);
PyErr_SetString(PyExc_RuntimeError, "throwing C++ exception didn't work");
} catch(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 *self, PyObject *args)
{
int depth;
if (!PyArg_ParseTuple(args, "i", &depth))
return NULL;
return p_test_exception_switch_recurse(depth, depth);
}

static PyMethodDef test_methods[] = {
{"test_exception_switch", (PyCFunction)&test_exception_switch, METH_VARARGS,
"Switches to parent twice, to test exception handling and greenlet switching."},
{NULL, NULL, 0, NULL}
};


#if PY_MAJOR_VERSION >= 3
#define INITERROR return NULL

static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
"_test_extension_cpp",
NULL,
0,
test_methods,
NULL,
NULL,
NULL,
NULL
};

PyMODINIT_FUNC
PyInit__test_extension_cpp(void)
#else
#define INITERROR return
PyMODINIT_FUNC
init_test_extension_cpp(void)
#endif
{
PyObject *module = NULL;

#if PY_MAJOR_VERSION >= 3
module = PyModule_Create(&moduledef);
#else
module = Py_InitModule("_test_extension_cpp", test_methods);
#endif

if (module == NULL) {
INITERROR;
}

PyGreenlet_Import();
if (_PyGreenlet_API == NULL) {
INITERROR;
}

p_test_exception_throw = test_exception_throw;
p_test_exception_switch_recurse = test_exception_switch_recurse;

#if PY_MAJOR_VERSION >= 3
return module;
#endif
}
14 changes: 14 additions & 0 deletions tests/test_cpp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import unittest

import greenlet
import _test_extension_cpp

class CPPTests(unittest.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)

0 comments on commit d9cb12a

Please sign in to comment.