basic prng functionality with seeding via cli argument
This commit is contained in:
parent
833f548d5d
commit
421cace0c7
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
[submodule "extern/pcg"]
|
||||
path = extern/pcg
|
||||
url = https://github.com/imneme/pcg-c-basic.git
|
|
@ -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)
|
||||
|
|
|
@ -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
17
examples/prng.c
Normal 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
1
extern/pcg
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit bc39cd76ac3d541e618606bcc6e1e5ba5e5e6aa3
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
206
src/xtest.c
206
src/xtest.c
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue