Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to specify the random number generator seed in unit tests #1751

Merged
merged 8 commits into from
Feb 20, 2025
2 changes: 0 additions & 2 deletions src/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -2584,8 +2584,6 @@ void populateCommandLegacyRangeSpec(struct serverCommand *c);
long long ustime(void);
mstime_t mstime(void);
mstime_t commandTimeSnapshot(void);
void getRandomHexChars(char *p, size_t len);
void getRandomBytes(unsigned char *p, size_t len);
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
void exitFromChild(int retcode);
long long serverPopcount(void *s, long count);
Expand Down
32 changes: 32 additions & 0 deletions src/unit/test_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
*/

#include <strings.h>
#include <string.h>
#include <stdio.h>
#include "test_files.h"
#include "test_help.h"
#include "../util.h"
#include "../mt19937-64.h"
#include "../hashtable.h"

/* We override the default assertion mechanism, so that it prints out info and then dies. */
void _serverAssert(const char *estr, const char *file, int line) {
Expand Down Expand Up @@ -40,6 +44,7 @@ int runTestSuite(struct unitTestSuite *test, int argc, char **argv, int flags) {
int main(int argc, char **argv) {
int flags = 0;
char *file = NULL;
char *seed = NULL;
for (int j = 1; j < argc; j++) {
char *arg = argv[j];
if (!strcasecmp(arg, "--accurate"))
Expand All @@ -51,13 +56,40 @@ int main(int argc, char **argv) {
file = argv[j + 1];
} else if (!strcasecmp(arg, "--valgrind")) {
flags |= UNIT_TEST_VALGRIND;
} else if (!strcasecmp(arg, "--seed")) {
seed = argv[j + 1];
}
}

if (seed) {
setRandomSeedCString(seed, strlen(seed));
}

char seed_cstr[129];
getRandomSeedCString(seed_cstr, 129);

printf("Tests will run with seed=%s\n", seed_cstr);

unsigned long long genrandseed;
getRandomBytes((void *)&genrandseed, sizeof(genrandseed));

uint8_t hashseed[16];
getRandomBytes(hashseed, sizeof(hashseed));


int numtests = sizeof(unitTestSuite) / sizeof(struct unitTestSuite);
int failed_num = 0, suites_executed = 0;
for (int j = 0; j < numtests; j++) {
if (file && strcasecmp(file, unitTestSuite[j].filename)) continue;

/* We need to explicitly set the seed in the several random numbers
* generator that valkey server uses so that the unit tests reproduce
* the random values in a deterministic way. */
setRandomSeedCString(seed_cstr, strlen(seed_cstr));
init_genrand64(genrandseed);
srandom((unsigned)genrandseed);
hashtableSetHashFunctionSeed(hashseed);

if (!runTestSuite(&unitTestSuite[j], argc, argv, flags)) {
failed_num++;
}
Expand Down
76 changes: 54 additions & 22 deletions src/util.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
#include "sha256.h"
#include "config.h"
#include "zmalloc.h"
#include "serverassert.h"

#include "valkey_strtod.h"

Expand Down Expand Up @@ -905,36 +906,67 @@ int version2num(const char *version) {
return v;
}

/* Global state. */
static int seed_initialized = 0;
static unsigned char seed[64]; /* 512 bit internal block size. */

static void initializeRandomSeed(void) {
assert(seed_initialized == 0);
/* Initialize a seed and use SHA1 in counter mode, where we hash
* the same seed with a progressive counter. For the goals of this
* function we just need non-colliding strings, there are no
* cryptographic security needs. */
FILE *fp = fopen("/dev/urandom", "r");
if (fp == NULL || fread(seed, sizeof(seed), 1, fp) != 1) {
/* Revert to a weaker seed, and in this case reseed again
* at every call.*/
for (unsigned int j = 0; j < sizeof(seed); j++) {
struct timeval tv;
gettimeofday(&tv, NULL);
pid_t pid = getpid();
seed[j] = tv.tv_sec ^ tv.tv_usec ^ pid ^ (long)fp;
}
} else {
seed_initialized = 1;
}
if (fp) fclose(fp);
}

/* This function receives a string with 64 bytes encoded as 128 hexadecimal
* digits, and sets the 64 byte random seed. */
void setRandomSeedCString(char *seed_str, size_t len) {
assert(len == sizeof(seed) * 2);
for (size_t i = 0; i < sizeof(seed); i++) {
sscanf(seed_str + (i * 2), "%02hhX", &seed[i]);
}
seed_initialized = 1;
}

/* This function populates a char buffer with 129 bytes with the 64 bytes
* random seed encoded as 128 hexadecimal digits and a null terminator. */
void getRandomSeedCString(char *buff, size_t len) {
assert(len == (sizeof(seed) * 2 + 1));

if (!seed_initialized) {
initializeRandomSeed();
}

for (size_t i = 0; i < sizeof(seed); i++) {
snprintf(buff + (i * 2), 3, "%02hhX", seed[i]);
}
buff[sizeof(seed) * 2] = '\0';
}

/* Get random bytes, attempts to get an initial seed from /dev/urandom and
* the uses a one way hash function in counter mode to generate a random
* stream. However if /dev/urandom is not available, a weaker seed is used.
*
* This function is not thread safe, since the state is global. */
void getRandomBytes(unsigned char *p, size_t len) {
/* Global state. */
static int seed_initialized = 0;
static unsigned char seed[64]; /* 512 bit internal block size. */
static uint64_t counter = 0; /* The counter we hash with the seed. */
static uint64_t counter = 0; /* The counter we hash with the seed. */

if (!seed_initialized) {
/* Initialize a seed and use SHA1 in counter mode, where we hash
* the same seed with a progressive counter. For the goals of this
* function we just need non-colliding strings, there are no
* cryptographic security needs. */
FILE *fp = fopen("/dev/urandom", "r");
if (fp == NULL || fread(seed, sizeof(seed), 1, fp) != 1) {
/* Revert to a weaker seed, and in this case reseed again
* at every call.*/
for (unsigned int j = 0; j < sizeof(seed); j++) {
struct timeval tv;
gettimeofday(&tv, NULL);
pid_t pid = getpid();
seed[j] = tv.tv_sec ^ tv.tv_usec ^ pid ^ (long)fp;
}
} else {
seed_initialized = 1;
}
if (fp) fclose(fp);
initializeRandomSeed();
}

while (len) {
Expand Down
4 changes: 4 additions & 0 deletions src/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,5 +99,9 @@ int snprintf_async_signal_safe(char *to, size_t n, const char *fmt, ...);
#endif
size_t valkey_strlcpy(char *dst, const char *src, size_t dsize);
size_t valkey_strlcat(char *dst, const char *src, size_t dsize);
void getRandomSeedCString(char *buff, size_t len);
void setRandomSeedCString(char *seed_str, size_t len);
void getRandomHexChars(char *p, size_t len);
void getRandomBytes(unsigned char *p, size_t len);

#endif
Loading