TLDR
I can't get unittest to run a test where I am trying to check that my Python C extensions calls exit(1)
from stdlib.h
.
The setup
I have a Python unit test and C extension which looks like the following:
The Python test
import unittest
import binding
class TestBindings(unittest.TestCase):
def test_fail(self):
self.assertRaises(SystemExit, binding.fail)
if __name__ == '__main__':
unittest.main()
The C extension
core.h
// The main core C library declarations.
void fail(void);
core.c
// The main core C library implementations.
void fail(void)
{
fprintf(stderr, "We are failing now.\n");
exit(1);
}
bindings.c
// The main core C library bindings.
#include <Python.h>
#include <stdio.h>
#include <stdlib.h>
#include "core.h"
PyObject *_fail(PyObject *self, PyObject *args, PyObject *kwargs)
{
fail();
Py_RETURN_NONE;
}
static PyMethodDef binding_methods[] = {
{"fail", PyFunc(_fail), METH_VARARGS | METH_KEYWORDS,
"Fails and calls exit()."},
{NULL, NULL, 0, NULL} /* Sentinel */
};
static struct PyModuleDef binding_module = {
PyModuleDef_HEAD_INIT,
"binding",
"A simple module to demonstrate C bindings.",
-1,
binding_methods};
PyMODINIT_FUNC
PyInit_binding(void)
{
return PyModule_Create(&binding_module);
}
The tests don't run
The Python test always exits, and does not run the remaining tests nor report the results. I have tried to find ways to capture this exiting behaviour, but can't find anything that works.
My thoughts are to either:
- Add a more robust capture in the unit test.
- Add/declare some form of error handler in the module containing all the python bindings.
- Change the way the core library exits when something goes wrong and replace all my calls to
exit(1)
. I want to avoid any changes to thecore*
files.
EDIT
After some more digging around, there were two routes that looked promising:
Trying to add functionality when
exit
is called (without trying to mock/stub the C standard libraryexit
). This led me to functions such asatexit
(and the even nicer GNU extensionon_exit
). However, I couldn't figure out a way to add my custom exit without calling anotherexit
from withinexit
, and callingexit
twice is undefined behaviour.Launching the test in its own process which is allowed to die, and then checking the return code for this other process. This is the closest to the requirements I had, and seems to solve my problem with minimal code changes in the core C library, and has a solution outlined here.