git » libfiu » commit 780aa56

Add a function to manually set the PRNG seed (fiu_set_prng_seed())

author Alberto Bertogli
2018-05-13 14:48:42 UTC
committer Alberto Bertogli
2018-05-13 14:48:42 UTC
parent 527317f4a90045c061cada3adb40417efd489aa4

Add a function to manually set the PRNG seed (fiu_set_prng_seed())

When doing randomized tests, it can be useful to have a way of
reliably reproducing failures.  Currently this is not possible, because
the PRNG seed is set internally based on time, and users have no control
over it.

So this patch introduces a new function, fiu_set_prng_seed(), to allow
users to manually control the PRNG seed.

bindings/python/fiu.py +5 -0
bindings/python/fiu_ll.c +12 -0
libfiu/fiu.c +13 -0
libfiu/fiu.h +16 -0
libfiu/symbols.map +1 -0
tests/test-set_prng_seed.py +23 -0

diff --git a/bindings/python/fiu.py b/bindings/python/fiu.py
index 0b0a308..20f4a13 100644
--- a/bindings/python/fiu.py
+++ b/bindings/python/fiu.py
@@ -95,6 +95,11 @@ def disable(name):
 	if r != 0:
 		raise RuntimeError(r)
 
+def set_prng_seed(seed):
+	"""Sets the PRNG seed. Don't use this unless you know what you're
+	doing."""
+	return _ll.set_prng_seed(seed)
+
 def rc_fifo(basename):
 	"""Enables remote control over a named pipe that begins with the given
 	basename. The final path will be "basename-$PID"."""
diff --git a/bindings/python/fiu_ll.c b/bindings/python/fiu_ll.c
index e49874b..85ca93b 100644
--- a/bindings/python/fiu_ll.c
+++ b/bindings/python/fiu_ll.c
@@ -187,6 +187,17 @@ static PyObject *disable(PyObject *self, PyObject *args)
 	return PyLong_FromLong(fiu_disable(name));
 }
 
+static PyObject *set_prng_seed(PyObject *self, PyObject *args)
+{
+	int seed;
+
+	if (!PyArg_ParseTuple(args, "i:set_prng_seed", &seed))
+		return NULL;
+
+	fiu_set_prng_seed(seed);
+	Py_RETURN_NONE;
+}
+
 static PyObject *rc_fifo(PyObject *self, PyObject *args)
 {
 	char *basename;
@@ -207,6 +218,7 @@ static PyMethodDef fiu_methods[] = {
 	{ "enable_stack_by_name", (PyCFunction) enable_stack_by_name,
 		METH_VARARGS, NULL },
 	{ "disable", (PyCFunction) disable, METH_VARARGS, NULL },
+	{ "set_prng_seed", (PyCFunction) set_prng_seed, METH_VARARGS, NULL },
 	{ "rc_fifo", (PyCFunction) rc_fifo, METH_VARARGS, NULL },
 	{ NULL }
 };
diff --git a/libfiu/fiu.c b/libfiu/fiu.c
index 4f9de64..e49bfc2 100644
--- a/libfiu/fiu.c
+++ b/libfiu/fiu.c
@@ -191,11 +191,17 @@ static int should_stack_fail(struct pf_info *pf)
  * To seed it, we use the current microseconds. To prevent seed reuse, we
  * re-seed after each fork (see atfork_child()). */
 static unsigned int randd_xn = 0xA673F42D;
+static bool randd_xn_manual = false;
 
 static void prng_seed(void)
 {
 	struct timeval tv;
 
+	/* If the seed is being handled manually, don't interfere. */
+	if (randd_xn_manual) {
+		return;
+	}
+
 	gettimeofday(&tv, NULL);
 
 	randd_xn = tv.tv_usec;
@@ -255,6 +261,13 @@ int fiu_init(unsigned int flags)
 	return 0;
 }
 
+/* Sets the PRNG seed. */
+void fiu_set_prng_seed(unsigned int seed)
+{
+	randd_xn = seed;
+	randd_xn_manual = true;
+}
+
 /* Returns the failure status of the given name. Must work well even before
  * fiu_init() is called assuming no points of failure are enabled; although it
  * can (and does) assume fiu_init() will be called before enabling any. */
diff --git a/libfiu/fiu.h b/libfiu/fiu.h
index 7659b7f..8dd6bb7 100644
--- a/libfiu/fiu.h
+++ b/libfiu/fiu.h
@@ -30,6 +30,21 @@ extern "C" {
  */
 int fiu_init(unsigned int flags);
 
+/** Sets the PRNG seed.
+ *
+ * This function is called if you want to manually set the seed used to
+ * generate random numbers.  This allows more control over randomized tests.
+ *
+ * Must be called before fiu_init(), or at fork. Otherwise, races might occur
+ * as this function is not threadsafe wrt. other fiu functions.
+ *
+ * There's no need to call this function for normal operations. Don't use it
+ * unless you know what you're doing.
+ *
+ * @param seed  PRNG seed to use.
+ */
+void fiu_set_prng_seed(unsigned int seed);
+
 /** Returns the failure status of the given point of failure.
  *
  * @param name  Point of failure name.
@@ -73,6 +88,7 @@ void *fiu_failinfo(void);
  * don't include it to avoid a circular dependency. */
 
 #define fiu_init(flags) 0
+#define fiu_set_prng_seed(seed)
 #define fiu_fail(name) 0
 #define fiu_failinfo() NULL
 #define fiu_do_on(name, action)
diff --git a/libfiu/symbols.map b/libfiu/symbols.map
index 204acc1..bae73d5 100644
--- a/libfiu/symbols.map
+++ b/libfiu/symbols.map
@@ -10,6 +10,7 @@
 		fiu_fail;
 		fiu_failinfo;
 		fiu_init;
+		fiu_set_prng_seed;
 		fiu_rc_fifo;
 		fiu_rc_string;
 
diff --git a/tests/test-set_prng_seed.py b/tests/test-set_prng_seed.py
new file mode 100644
index 0000000..f1f3f5c
--- /dev/null
+++ b/tests/test-set_prng_seed.py
@@ -0,0 +1,23 @@
+"""
+Test that we get reproducible results with manually set PRNG seeds.
+"""
+
+import fiu
+
+
+fiu.set_prng_seed(1234)
+fiu.enable_random('p1', probability = 0.5)
+result = { True: 0, False: 0 }
+for i in range(1000):
+    result[fiu.fail('p1')] += 1
+
+assert result == {False: 516, True: 484}, result
+
+
+fiu.set_prng_seed(4321)
+fiu.enable_random('p1', probability = 0.5)
+result = { True: 0, False: 0 }
+for i in range(1000):
+    result[fiu.fail('p1')] += 1
+
+assert result == {False: 495, True: 505}, result