basic prng functionality with seeding via cli argument

This commit is contained in:
Gwendolyn 2022-01-06 02:27:07 +01:00
parent 833f548d5d
commit 421cace0c7
8 changed files with 262 additions and 18 deletions

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "extern/pcg"]
path = extern/pcg
url = https://github.com/imneme/pcg-c-basic.git

View file

@ -9,15 +9,20 @@ project(
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED TRUE)
set(XTEST_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
option(XTEST_ENABLE_PRNG "enable prng functions" ON)
add_subdirectory(src)
set(XTEST_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
function(add_xtest_executable TARGET)
add_executable(${TARGET} ${ARGN})
target_compile_definitions(${TARGET} PRIVATE XTEST)
set(XTEST_COMPILE_DEFS XTEST)
if (XTEST_ENABLE_PRNG)
set(XTEST_COMPILE_DEFS ${XTEST_COMPILE_DEFS} XTEST_PRNG)
endif ()
target_compile_definitions(${TARGET} PRIVATE ${XTEST_COMPILE_DEFS})
target_include_directories(${TARGET} AFTER PRIVATE "${XTEST_SOURCE_DIR}/include/xtest")
target_include_directories(${TARGET} SYSTEM BEFORE PRIVATE "${XTEST_SOURCE_DIR}/include/xtest-assert")
target_link_libraries(${TARGET} xtest)

View file

@ -2,19 +2,20 @@ function(xtest_define_example NAME)
set(TARGET_NAME "example-${NAME}")
add_xtest_executable(${TARGET_NAME} source.c ${NAME}.c ${ARGN})
if (NAME STREQUAL "all")
target_compile_definitions(${TARGET_NAME} PRIVATE XTEST_ALL_EXAMPLES)
set(EXAMPLE_COMPILE_DEFINITIONS ${EXAMPLE_COMPILE_DEFINITIONS} XTEST_ALL_EXAMPLES)
endif ()
target_compile_definitions(${TARGET_NAME} PRIVATE ${EXAMPLE_COMPILE_DEFINITIONS})
target_compile_options(${TARGET_NAME} PRIVATE -Wall -Wextra -pedantic)
endfunction()
if (XTEST_BUILD_EXAMPLES)
xtest_define_example(all assertions.c expect_assertions.c fail.c float.c groups.c parameterized.c skip.c)
xtest_define_example(all assertions.c expect_assertions.c fail.c float.c groups.c parameterized.c prng.c skip.c)
xtest_define_example(assertions)
xtest_define_example(expect_assertions)
xtest_define_example(fail)
xtest_define_example(float)
xtest_define_example(groups)
xtest_define_example(parameterized)
xtest_define_example(prng)
xtest_define_example(skip)
endif ()

17
examples/prng.c Normal file
View file

@ -0,0 +1,17 @@
#include "xtest.h"
#include "source.h"
void rand_int() {
int a = xtest_rand_int();
int b = xtest_rand_int();
int res = a + b;
xtest_assert_is(add(a, b), res);
}
#ifndef XTEST_ALL_EXAMPLES
XTEST_MAIN {
xtest_run(rand_int);
}
#endif

1
extern/pcg vendored Submodule

@ -0,0 +1 @@
Subproject commit bc39cd76ac3d541e618606bcc6e1e5ba5e5e6aa3

View file

@ -15,9 +15,9 @@
#include <string.h>
#include <stdint.h>
typedef void (*xtest_test_fn)(void *fixture, void **params);
typedef void (*xtest_setup_fn)(void **fixture);
typedef void (*xtest_teardown_fn)(void *fixture);
typedef void (*xtest_test_fn)(void * fixture, void ** params);
typedef void (*xtest_setup_fn)(void ** fixture);
typedef void (*xtest_teardown_fn)(void * fixture);
typedef struct xtest_param_s xtest_param;
@ -48,17 +48,15 @@ enum xtest_type {
xtest_type_other,
};
void xtest_init(int argc, char **argv);
void xtest_init(int argc, char**argv);
void xtest_fail_assert(const char *expression, const char *file, int line, void *expected,
void *actual, _Bool invert, enum xtest_type type);
void
xtest_assert_float(double expected, double actual, int precision, _Bool invert, const char *expression,
const char *file,
xtest_assert_float(double expected, double actual, int precision, _Bool invert, const char *expression, const char *file,
int line);
void xtest_assert_mem(const char *expected, const char *actual, size_t length, _Bool invert, const char *expression,
const char *file,
void xtest_assert_mem(const char *expected, const char *actual, size_t length, _Bool invert, const char *expression, const char *file,
int line);
void xtest_internal_run(xtest_test_fn fn, const char *name, xtest_param *params, xtest_setup_fn setup,
@ -71,6 +69,18 @@ void xtest_skip(const char *reason);
void xtest_expect_assertion_failure();
int xtest_complete();
#ifdef XTEST_PRNG
void xtest_rand_fill(char* buf, size_t len);
double xtest_rand_double();
double xtest_rand_double_range(double min, double max);
int xtest_rand_int();
int xtest_rand_int_range(int min, int max);
uint8_t xtest_rand_8();
uint16_t xtest_rand_16();
uint32_t xtest_rand_32();
uint64_t xtest_rand_64();
#endif
#define xtest_run(fn) xtest_internal_run(fn, #fn, (void*)0, (void*)0, (void*)0)
#define xtest_run_parameterized(fn, params) xtest_internal_run(fn, #fn, params, (void*)0, (void*)0)

View file

@ -1,6 +1,11 @@
add_library(xtest xtest.c)
target_compile_definitions(xtest PRIVATE XTEST)
if(XTEST_ENABLE_PRNG)
set(XTEST_ADDITIONAL_SOURCES ${XTEST_SOURCE_DIR}/extern/pcg/pcg_basic.c)
set(XTEST_ADDITIONAL_DEFINES XTEST_PRNG)
endif()
add_library(xtest xtest.c ${XTEST_ADDITIONAL_SOURCES})
target_compile_definitions(xtest PRIVATE XTEST ${XTEST_ADDITIONAL_DEFINES})
target_compile_options(xtest PRIVATE -Wall -Wextra -pedantic)
target_include_directories(xtest PRIVATE ../extern/pcg ../include/xtest)
target_include_directories(xtest PRIVATE ${XTEST_SOURCE_DIR}/extern/pcg ${XTEST_SOURCE_DIR}/include/xtest)

View file

@ -7,7 +7,21 @@
#include "xtest.h"
#include <assert.h>
#ifdef XTEST_PRNG
#include <time.h>
#include <math.h>
#include "pcg_basic.h"
#endif
#ifdef XTEST_PRNG
#define PRNG_SEED_LENGTH 22
pcg32_random_t rng;
typedef char prng_seed[PRNG_SEED_LENGTH + 1];
void xtest_rand_init(prng_seed seed);
#endif
struct xtest_assert_info {
const char *file;
@ -63,14 +77,18 @@ _Bool list_tests = 0;
#define USAGE_BASE "[-?|--help] [-l|--list] [-m|--match filter] [-f|--output-format format]"
#define USAGE_PRNG "[-s|--seed prng-seed]"
#define USAGE_HELP_LIST "-l|--list list the available tests"
#define USAGE_HELP_MATCH "-m|--match filter only run/list tests whose name matches the filter"
#define USAGE_HELP_FORMAT "-f|--output-format format specify the output format; supported formats are \"default\" and \"subunit\""
#define USAGE_HELP_SEED "-s|--seed prng-seed specify a seed for the prng"
#define USAGE_HELP_HELP "-?|--help show this message"
#ifdef XTEST_PRNG
#define USAGE "Usage: %s " USAGE_BASE " " USAGE_PRNG "\n " USAGE_HELP_LIST "\n " USAGE_HELP_MATCH "\n " USAGE_HELP_FORMAT "\n " USAGE_HELP_SEED "\n " USAGE_HELP_HELP
#else
#define USAGE "Usage: %s " USAGE_BASE "\n " USAGE_HELP_LIST "\n " USAGE_HELP_MATCH "\n " USAGE_HELP_FORMAT "\n " USAGE_HELP_HELP
#endif
static _Noreturn void usage(const char *progname) {
printf(USAGE, progname);
@ -111,6 +129,10 @@ void xtest_init(int argc, char **argv) {
int list_flag = 0;
int help_flag = 0;
const char *format = NULL;
#ifdef XTEST_PRNG
const char *seed_arg = NULL;
#endif
const char *progname = argv[0];
struct option long_options[] =
@ -119,6 +141,9 @@ void xtest_init(int argc, char **argv) {
{"help", no_argument, &help_flag, 1}, // list the tests instead of running them
{"match", required_argument, 0, 'm'}, // only run tests that match the provided filter
{"output-format", required_argument, 0, 'f'}, // select output format
#ifdef XTEST_PRNG
{"seed", required_argument, 0, 's'}, // provide a prng seed
#endif
{0, 0, 0, 0}
};
while (1) {
@ -138,6 +163,11 @@ void xtest_init(int argc, char **argv) {
case 'f':
format = optarg;
break;
#ifdef XTEST_PRNG
case 's':
seed_arg = optarg;
break;
#endif
case '?':
help_flag = 1;
break;
@ -164,6 +194,15 @@ void xtest_init(int argc, char **argv) {
printf("invalid output format '%s', allowed formats are 'default' and 'subunit'\n", format);
exit(1);
}
#ifdef XTEST_PRNG
char seed[PRNG_SEED_LENGTH + 1] = {0};
if (seed_arg != NULL) {
snprintf(seed, PRNG_SEED_LENGTH + 1, "%s", seed_arg);
}
xtest_rand_init(seed);
printf("[PRNG seed: %s]\n============\n", seed);
#endif
}
@ -480,3 +519,166 @@ void xtest_internal_end_group() {
void xtest_expect_assertion_failure() {
expecting_assertion = 1;
}
#ifdef XTEST_PRNG
#define SEED_ENC_0 2
#define SEED_ENC_A 12
#define SEED_ENC_a 38
union seed_encode_box {
struct {
uint64_t state;
uint64_t seq;
};
uint8_t bytes[PRNG_SEED_LENGTH * 6 / 8 + 1];
};
void encode_seed(prng_seed seed, uint64_t state, uint64_t seq) {
union seed_encode_box box;
memset(&box, 0, sizeof(box));
box.state = state;
box.seq = seq;
uint8_t cur;
for (int i = 0; i < PRNG_SEED_LENGTH; i += 1) {
switch (i % 4) {
case 0:
cur = box.bytes[i * 6 / 8] & 0x3F;
break;
case 1:
cur = (box.bytes[i * 6 / 8] >> 6) | ((box.bytes[i * 6 / 8 + 1] & 0xF) << 2);
break;
case 2:
cur = (box.bytes[i * 6 / 8] >> 4) | ((box.bytes[i * 6 / 8 + 1] & 0x3) << 4);
break;
case 3:
cur = (box.bytes[i * 6 / 8] >> 2);
break;
}
char c;
if (cur == 0) c = '_';
else if (cur == 1) c = '.';
else if (cur >= SEED_ENC_0 && cur <= SEED_ENC_0 + '9' - '0') c = (char) (cur - SEED_ENC_0 + '0');
else if (cur >= SEED_ENC_A && cur <= SEED_ENC_A + 'Z' - 'A') c = (char) (cur - SEED_ENC_A + 'A');
else if (cur >= SEED_ENC_a && cur <= SEED_ENC_a + 'z' - 'a') c = (char) (cur - SEED_ENC_a + 'a');
else
assert(0);
seed[i] = c;
}
seed[PRNG_SEED_LENGTH] = '\0';
}
void decode_seed(const prng_seed seed, uint64_t *state, uint64_t *seq) {
union seed_encode_box box;
memset(&box, 0, sizeof(box));
uint8_t cur;
for (int i = 0; i < PRNG_SEED_LENGTH; i += 1) {
char c = seed[i];
if (c == '_') cur = 0;
else if (c == '.') cur = 1;
else if (c >= '0' && c <= '9') cur = SEED_ENC_0 + c - '0';
else if (c >= 'A' && c <= 'Z') cur = SEED_ENC_A + c - 'A';
else if (c >= 'a' && c <= 'z') cur = SEED_ENC_a + c - 'a';
else cur = -1; // invalid seed character is just ignored and set to 0xFF so no error handling is necessary
switch (i % 4) {
case 0:
box.bytes[i * 6 / 8] |= cur;
break;
case 1:
box.bytes[i * 6 / 8] |= cur << 6;
box.bytes[i * 6 / 8 + 1] |= cur >> 2;
break;
case 2:
box.bytes[i * 6 / 8] |= cur << 4;
box.bytes[i * 6 / 8 + 1] |= cur >> 4;
break;
case 3:
box.bytes[i * 6 / 8] |= cur << 2;
break;
}
}
*state = box.state;
*seq = box.seq;
}
void xtest_rand_init(prng_seed seed) {
uint64_t state;
uint64_t seq;
if (seed[0] == '\0') {
int64_t t = time(NULL);
int shift = (int) (t & 0x3F);
if (t >> 6 & 0x1) shift = -shift;
t = t >> 7;
t = t << shift | t >> (64 - shift);
state = (intptr_t) &printf ^ (intptr_t) seed << 32 ^ t;
seq = (intptr_t) &longjmp << 32 ^ (intptr_t) &xtest_rand_init ^ (t << 32 | t >> 32);
// state = time(NULL) ^ (intptr_t) &printf << 32 ^ (intptr_t) &time;
// seq = time(NULL) << 32 ^ (intptr_t) seed ^ (intptr_t) &matches << 32;
encode_seed(seed, state, seq);
} else {
decode_seed(seed, &state, &seq);
}
pcg32_srandom_r(&rng, state, seq);
}
void xtest_rand_fill(char *buf, size_t len) {
uint32_t rand = 0;
for (size_t i = 0; i < len; ++i) {
if (i % 4 == 0) {
rand = pcg32_random_r(&rng);
}
buf[i] = (char) (rand >> (8 * (i % 4)));
}
}
double xtest_rand_double() {
return ldexp((double) (xtest_rand_64() & 0xFFFFFFFFFFFFF), -52);
}
double xtest_rand_double_range(double min, double max) {
assert(min < max);
return (max - min) * xtest_rand_double() + min;
}
int xtest_rand_int() {
uint32_t uval = pcg32_random_r(&rng);
int32_t *ival;
ival = (int32_t *) &uval;
return (int) *ival;
}
int xtest_rand_int_range(int min, int max) {
int bound = max - min;
uint32_t uval = pcg32_boundedrand_r(&rng, bound);
int32_t *ival;
ival = (int32_t *) &uval;
return (int) *ival + min;
}
uint8_t xtest_rand_8() {
return (uint8_t) pcg32_random_r(&rng);
}
uint16_t xtest_rand_16() {
return (uint16_t) pcg32_random_r(&rng);
}
uint32_t xtest_rand_32() {
return pcg32_random_r(&rng);
}
uint64_t xtest_rand_64() {
uint32_t l = pcg32_random_r(&rng);
uint32_t h = pcg32_random_r(&rng);
return ((uint64_t) h) << 32 | l;
}
#endif