]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib/lib-test: restore DOVECOT_SRAND feature in DEBUG builds
authorPhil Carmody <phil@dovecot.fi>
Mon, 11 Dec 2017 12:03:13 +0000 (14:03 +0200)
committerAki Tuomi <aki.tuomi@open-xchange.com>
Thu, 14 Feb 2019 09:38:05 +0000 (11:38 +0200)
Add a deterministic PRNG, an ability to force its use, and an
ability to re-use the same sequence later.

Since proper random numbers have been forced into use, making
reproducable tests isn't quite as easy as it used to be, it's 3 steps
rather than 2. When seeing an intermittent test failure:
 - rerun the tests with environmental variable DOVECOT_SRAND=kiss
 - upon seeing a new failure case, note the seed logged at the failure
 - debug using DOVECOT_SRAND=<that number>

In non-DEBUG builds, there's no trace of this code, and the
randomisation that is an inherent part of many tests remains
non-reproduceable.

Works with all of the RNG preferences, getrandom/urandom/arc4.

Signed-off-by: Phil Carmody <phil@dovecot.fi>
src/lib-test/test-common.c
src/lib/randgen.c
src/lib/randgen.h

index 4bb8adb2fb58dd9c0eabf48103c6860ab613eebe..f711713e2d1eb696fab5ea55383c82519012bf9b 100644 (file)
@@ -61,6 +61,29 @@ void test_assert_failed_strcmp(const char *code, const char *file, unsigned int
        test_success = FALSE;
 }
 
+#ifdef DEBUG
+#include "randgen.h"
+static void
+test_dump_rand_state(void)
+{
+       static int64_t seen_seed = -1;
+       unsigned int seed;
+       if (rand_get_last_seed(&seed) < 0) {
+               if (seen_seed == -1) {
+                       printf("test: random sequence not reproduceable, use DOVECOT_SRAND=kiss\n");
+                       seen_seed = -2;
+               }
+               return;
+       }
+       if (seed == seen_seed)
+               return;
+       seen_seed = seed;
+       printf("test: DOVECOT_SRAND random seed was %u\n", seed);
+}
+#else
+static inline void test_dump_rand_state(void) { }
+#endif
+
 void test_end(void)
 {
        if (!expecting_fatal)
@@ -69,6 +92,8 @@ void test_end(void)
                test_assert(test_prefix != NULL);
 
        test_out("", test_success);
+       if (!test_success)
+               test_dump_rand_state();
        i_free_and_null(test_prefix);
        test_success = FALSE;
 }
@@ -178,6 +203,7 @@ test_error_handler(const struct failure_context *ctx,
        }
 
        if (!suppress) {
+               test_dump_rand_state();
                default_error_handler(ctx, format, args);
        }
 }
index 10f12695c0a5692422224550e7476e0095745548..6808a8432c2a750c0abd0ef9d496e96ecf8f5e52 100644 (file)
@@ -5,6 +5,41 @@
 #include <unistd.h>
 #include <fcntl.h>
 
+#ifdef DEBUG
+/* For reproducing tests, fall back onto using a simple deterministic PRNG */
+/* Marsaglia's 1999 KISS, de-macro-ified */
+static bool kiss_in_use;
+static unsigned int kiss_seed;
+static uint32_t kiss_z, kiss_w, kiss_jsr, kiss_jcong;
+static void
+kiss_init(unsigned int seed)
+{
+       /* i_info("Random numbers are PRNG using kiss, seeded with %u", seed); */
+       kiss_seed = seed;
+       kiss_jsr = 0x5eed5eed; /* simply musn't be 0 */
+       kiss_z = kiss_w = kiss_jcong = seed;
+       kiss_in_use = TRUE;
+}
+static unsigned int
+kiss_rand(void)
+{
+       kiss_z = 36969 * (kiss_z&65535) + (kiss_z>>16);
+       kiss_w = 18000 * (kiss_w&65535) + (kiss_w>>16);
+       kiss_jcong = 69069 * kiss_jcong + 1234567;
+       kiss_jsr^=(kiss_jsr<<17);
+       kiss_jsr^=(kiss_jsr>>13);
+       kiss_jsr^=(kiss_jsr<<5);
+       return (((kiss_z<<16) + kiss_w) ^ kiss_jcong) + kiss_jsr;
+}
+int rand_get_last_seed(unsigned int *seed_r)
+{
+       if (!kiss_in_use)
+               return -1; /* not using a deterministic PRNG, seed is irrelevant */
+       *seed_r = kiss_seed;
+       return 0;
+}
+#endif
+
 /* get randomness from either getrandom, arc4random or /dev/urandom */
 
 #if defined(HAVE_GETRANDOM) && HAVE_DECL_GETRANDOM != 0
@@ -81,6 +116,14 @@ void random_fill(void *buf, size_t size)
        i_assert(init_refcount > 0);
        i_assert(size < SSIZE_T_MAX);
 
+#ifdef DEBUG
+       if (kiss_in_use) {
+               for (size_t pos = 0; pos < size; pos++)
+                       ((unsigned char*)buf)[pos] = kiss_rand();
+               return;
+       }
+#endif
+
 #if defined(USE_ARC4RANDOM)
        arc4random_buf(buf, size);
 #else
@@ -101,6 +144,16 @@ void random_init(void)
 
        if (init_refcount++ > 0)
                return;
+
+#ifdef DEBUG
+       const char *env_seed = getenv("DOVECOT_SRAND");
+       if (env_seed != NULL && str_to_uint(env_seed, &seed) >= 0) {
+               kiss_init(seed);
+               /* getrandom_present = FALSE; not needed, only used in random_read() */
+               goto normal_exit;
+       }
+#endif /* DEBUG */
+
 #if defined(USE_RANDOM_DEV)
        random_open_urandom();
 #endif
@@ -108,6 +161,16 @@ void random_init(void)
           needed to make sure getrandom really works.
        */
        random_fill(&seed, sizeof(seed));
+#ifdef DEBUG
+       if (env_seed != NULL) {
+               if (strcmp(env_seed, "kiss") != 0)
+                       i_fatal("DOVECOT_SRAND not a number or 'kiss'");
+               kiss_init(seed);
+               i_close_fd(&urandom_fd);
+       }
+
+normal_exit:
+#endif
        srand(seed);
 }
 
index 498613cbde0f36800acaa12d3edd9d4115e8585c..d532880fb3fe9ec663651912241e202fd0439d69 100644 (file)
@@ -9,4 +9,9 @@ void random_fill(void *buf, size_t size);
 void random_init(void);
 void random_deinit(void);
 
+#ifdef DEBUG
+/* Debug helper to make random tests reproduceable. 0=got seed, -1=failure. */
+int rand_get_last_seed(unsigned int *seed_r);
+#endif
+
 #endif