git » libfiu » commit 734b19c

Add Python 3 bindings, based on the Python 2 bindings

author Alberto Bertogli
2009-03-27 04:44:22 UTC
committer Alberto Bertogli
2009-03-27 05:12:45 UTC
parent f63794c79e66586283adbfe68f01298e6dcd8849

Add Python 3 bindings, based on the Python 2 bindings

Signed-off-by: Alberto Bertogli <albertito@blitiri.com.ar>

.gitignore +3 -0
Makefile +11 -2
bindings/python3/fiu.py +75 -0
bindings/python3/fiu_ll.c +201 -0
bindings/python3/setup.py +17 -0

diff --git a/.gitignore b/.gitignore
index e0c7c3d..7e4eb4e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,9 @@
 bindings/python2/build
 bindings/python2/*.pyc
 bindings/python2/*.pyo
+bindings/python3/build
+bindings/python3/*.pyc
+bindings/python3/*.pyo
 libfiu/*.o
 libfiu/libfiu.a
 libfiu/libfiu.pc
diff --git a/Makefile b/Makefile
index a05295c..31ae8e0 100644
--- a/Makefile
+++ b/Makefile
@@ -19,12 +19,21 @@ python2_install:
 python2_clean:
 	cd bindings/python2 && rm -rf build/
 
+python3:
+	cd bindings/python3 && python3 setup.py build
 
-clean: python2_clean
+python3_install:
+	cd bindings/python3 && python3 setup.py install
+
+python3_clean:
+	cd bindings/python3 && rm -rf build/
+
+clean: python2_clean python3_clean
 	$(MAKE) -C libfiu clean
 
 
 .PHONY: default all clean libfiu utils \
-	python2 python2_install python2_clean
+	python2 python2_install python2_clean \
+	python3 python3_install python3_clean
 
 
diff --git a/bindings/python3/fiu.py b/bindings/python3/fiu.py
new file mode 100644
index 0000000..822ad9b
--- /dev/null
+++ b/bindings/python3/fiu.py
@@ -0,0 +1,75 @@
+
+"""
+libfiu python wrapper
+
+This module is a wrapper for the libfiu, the fault injection C library.
+
+It provides an almost one-to-one mapping of the libfiu functions, although its
+primary use is to be able to test C code from within Python.
+
+For fault injection in Python, a native library would be more suitable.
+
+See libfiu's manpage for more detailed documentation.
+"""
+
+import fiu_ll as _ll
+
+
+def fail(name):
+	"Returns the failure status of the given point of failure."
+	return _ll.fail(name)
+
+def failinfo(name):
+	"""Returns the information associated with the last failure. Use with
+	care, can be fatal if the point of failure was not enabled via
+	Python."""
+	return _ll.failinfo()
+
+
+# To be sure failinfo doesn't dissapear from under our feet, we keep a
+# name -> failinfo table. See fiu_ll's comments for more details.
+_fi_table = {}
+
+def enable(name, failnum = 1, failinfo = None, flags = 0):
+	"Enables the given point of failure."
+	_fi_table[name] = failnum
+	r = _ll.enable(name, failnum, failinfo, flags)
+	if r != 0:
+		del _fi_table[name]
+		raise RuntimeError(r)
+
+def enable_random(name, probability, failnum = 1, failinfo = None, flags = 0):
+	"Enables the given point of failure, with the given probability."
+	_fi_table[name] = failnum
+	r = _ll.enable_random(name, failnum, failinfo, flags, probability)
+	if r != 0:
+		del _fi_table[name]
+		raise RuntimeError(r)
+
+def enable_external(name, cb, failnum = 1, flags = 0):
+	"""Enables the given point of failure, leaving the decision whether to
+	fail or not to the given external function, which should return 0 if
+	it is not to fail, or 1 otherwise.
+
+	The cb parameter is a Python function that takes three parameters,
+	name, failnum and flags, with the same values that we receive.
+
+	For technical limitations, enable_external() cannot take
+	failinfo."""
+	# in this case, use the table to prevent the function from
+	# dissapearing
+	_fi_table[name] = cb
+	r = _ll.enable_external(name, failnum, flags, cb)
+	if r != 0:
+		raise RuntimeError(r)
+
+def disable(name):
+	"""Disables the given point of failure, undoing the actions of the
+	enable*() functions."""
+	if name in _fi_table:
+		del _fi_table[name]
+	r = _ll.disable(name)
+	if r != 0:
+		raise RuntimeError(r)
+
+
diff --git a/bindings/python3/fiu_ll.c b/bindings/python3/fiu_ll.c
new file mode 100644
index 0000000..cff30ed
--- /dev/null
+++ b/bindings/python3/fiu_ll.c
@@ -0,0 +1,201 @@
+
+/*
+ * Python bindings for libfiu
+ * Alberto Bertogli (albertito@blitiri.com.ar)
+ *
+ * This is the low-level module, used by the python one to construct
+ * friendlier objects.
+ */
+
+#include <Python.h>
+
+/* Unconditionally enable fiu, otherwise we get fake headers */
+#define FIU_ENABLE 1
+
+#include <fiu.h>
+#include <fiu-control.h>
+
+
+static PyObject *fail(PyObject *self, PyObject *args)
+{
+	char *name;
+	PyObject *rv, *err;
+
+	if (!PyArg_ParseTuple(args, "s:fail", &name))
+		return NULL;
+
+	rv = PyLong_FromLong(fiu_fail(name));
+	err = PyErr_Occurred();
+
+	if (rv == NULL || err != NULL) {
+		Py_XDECREF(rv);
+		return NULL;
+	}
+
+	return rv;
+}
+
+static PyObject *failinfo(PyObject *self, PyObject *args)
+{
+	if (!PyArg_ParseTuple(args, ":failinfo"))
+		return NULL;
+
+	/* We assume failinfo is a python object; but the caller must be
+	 * careful because if it's not, it can get into trouble.
+	 * Note that we DO NOT TOUCH THE RC OF THE OBJECT. It's entirely up to
+	 * the caller to make sure it's still alive. */
+	return (PyObject *) fiu_failinfo();
+}
+
+static PyObject *enable(PyObject *self, PyObject *args)
+{
+	char *name;
+	int failnum;
+	PyObject *failinfo;
+	unsigned int flags;
+
+	if (!PyArg_ParseTuple(args, "siOI:enable", &name, &failnum, &failinfo,
+				&flags))
+		return NULL;
+
+	/* See failinfo()'s comment regarding failinfo's RC */
+	return PyLong_FromLong(fiu_enable(name, failnum, failinfo, flags));
+}
+
+static PyObject *enable_random(PyObject *self, PyObject *args)
+{
+	char *name;
+	int failnum;
+	PyObject *failinfo;
+	unsigned int flags;
+	double probability;
+
+	if (!PyArg_ParseTuple(args, "siOId:enable_random", &name, &failnum,
+				&failinfo, &flags, &probability))
+		return NULL;
+
+	/* See failinfo()'s comment regarding failinfo's RC */
+	return PyLong_FromLong(fiu_enable_random(name, failnum, failinfo,
+				flags, probability));
+}
+
+
+static int external_callback(const char *name, int *failnum, void **failinfo,
+		unsigned int *flags)
+{
+	int rv;
+	PyObject *cbrv;
+	PyObject *args;
+	PyGILState_STATE gil_state;
+
+	/* We need to protect ourselves from the following case:
+	 *  - fiu.enable_callback('x', cb)  (where cb is obviously a Python
+	 *    function)
+	 *  - Later on, call a function p1() inside a python C module, that
+	 *    runs a C function c1() inside
+	 *    Py_BEGIN_ALLOW_THREADS/Py_END_ALLOW_THREADS
+	 *  - c1() calls fiu_fail("x")
+	 *  - fiu_fail("x") calls external_callback(), and it should run cb()
+	 *  - BUT! It can't run cb(), because it's inside
+	 *    Py_BEGIN_ALLOW_THREADS/Py_END_ALLOW_THREADS so it's not safe to
+	 *    execute any Python code!
+	 *
+	 * The solution is to ensure we're safe to run Python code using
+	 * PyGILState_Ensure()/PyGILState_Release().
+	 */
+
+	gil_state = PyGILState_Ensure();
+	args = Py_BuildValue("(siI)", name, *failnum, *flags);
+	if (args == NULL) {
+		PyGILState_Release(gil_state);
+		return 0;
+	}
+
+	cbrv = PyEval_CallObject(*failinfo, args);
+	Py_DECREF(args);
+
+	if (cbrv == NULL) {
+		PyGILState_Release(gil_state);
+		return 0;
+	}
+
+	/* If PyLong_AsLong() causes an error, it will be handled by the
+	 * PyErr_Occurred() check in fail(), so we don't need to worry about
+	 * it now. */
+	rv = PyLong_AsLong(cbrv);
+	Py_DECREF(cbrv);
+
+	PyGILState_Release(gil_state);
+
+	return rv;
+}
+
+static PyObject *enable_external(PyObject *self, PyObject *args)
+{
+	char *name;
+	int failnum;
+	unsigned int flags;
+	PyObject *py_external_cb;
+
+	if (!PyArg_ParseTuple(args, "siIO:enable_random", &name, &failnum,
+				&flags, &py_external_cb))
+		return NULL;
+
+	if (!PyCallable_Check(py_external_cb)) {
+		PyErr_SetString(PyExc_TypeError, "parameter must be callable");
+		return NULL;
+	}
+
+	/* We use failinfo to store Python's callback function. It'd be nice
+	 * if we could keep both, but it's not easy without keeping state
+	 * inside the C module.
+	 *
+	 * Similar to the way failinfo is handled, we DO NOT TOUCH THE RC OF
+	 * THE EXTERNAL CALLBACK, assuming the caller will take care of making
+	 * sure it doesn't dissapear from beneath us. */
+	return PyLong_FromLong(fiu_enable_external(name, failnum,
+				py_external_cb, flags, external_callback));
+}
+
+static PyObject *disable(PyObject *self, PyObject *args)
+{
+	char *name;
+
+	if (!PyArg_ParseTuple(args, "s:fail", &name))
+		return NULL;
+
+	return PyLong_FromLong(fiu_disable(name));
+}
+
+
+static PyMethodDef fiu_methods[] = {
+	{ "fail", (PyCFunction) fail, METH_VARARGS, NULL },
+	{ "failinfo", (PyCFunction) failinfo, METH_VARARGS, NULL },
+	{ "enable", (PyCFunction) enable, METH_VARARGS, NULL },
+	{ "enable_random", (PyCFunction) enable_random, METH_VARARGS, NULL },
+	{ "enable_external", (PyCFunction) enable_external, METH_VARARGS, NULL },
+	{ "disable", (PyCFunction) disable, METH_VARARGS, NULL },
+	{ NULL }
+};
+
+static PyModuleDef fiu_module = {
+	PyModuleDef_HEAD_INIT,
+	.m_name = "libfiu",
+	.m_size = -1,
+	.m_methods = fiu_methods,
+};
+
+
+PyMODINIT_FUNC PyInit_fiu_ll(void)
+{
+	PyObject *m;
+
+	m = PyModule_Create(&fiu_module);
+
+	PyModule_AddIntConstant(m, "FIU_ONETIME", FIU_ONETIME);
+
+	fiu_init(0);
+
+	return m;
+}
+
diff --git a/bindings/python3/setup.py b/bindings/python3/setup.py
new file mode 100644
index 0000000..dbdc256
--- /dev/null
+++ b/bindings/python3/setup.py
@@ -0,0 +1,17 @@
+
+from distutils.core import setup, Extension
+
+fiu_ll = Extension("fiu_ll",
+		libraries = ['fiu'],
+		sources = ['fiu_ll.c'])
+
+setup(
+	name = 'fiu',
+	description = "libfiu bindings",
+	author = "Alberto Bertogli",
+	author_email = "albertito@blitiri.com.ar",
+	url = "http://blitiri.com.ar/p/libfiu",
+	py_modules = ['fiu'],
+	ext_modules = [fiu_ll]
+)
+