From 421cace0c705e3c0e1add5c622f2f9a18b6a4747 Mon Sep 17 00:00:00 2001 From: Gwendolyn Date: Thu, 6 Jan 2022 02:27:07 +0100 Subject: [PATCH] basic prng functionality with seeding via cli argument --- .gitmodules | 3 + CMakeLists.txt | 9 +- examples/CMakeLists.txt | 7 +- examples/prng.c | 17 ++++ extern/pcg | 1 + include/xtest/xtest.h | 26 +++-- src/CMakeLists.txt | 11 ++- src/xtest.c | 206 +++++++++++++++++++++++++++++++++++++++- 8 files changed, 262 insertions(+), 18 deletions(-) create mode 100644 .gitmodules create mode 100644 examples/prng.c create mode 160000 extern/pcg diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..13d30f7 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "extern/pcg"] + path = extern/pcg + url = https://github.com/imneme/pcg-c-basic.git diff --git a/CMakeLists.txt b/CMakeLists.txt index f122c0b..902d724 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 0b246e6..5ae8e71 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -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 () \ No newline at end of file diff --git a/examples/prng.c b/examples/prng.c new file mode 100644 index 0000000..9a7fb4c --- /dev/null +++ b/examples/prng.c @@ -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 diff --git a/extern/pcg b/extern/pcg new file mode 160000 index 0000000..bc39cd7 --- /dev/null +++ b/extern/pcg @@ -0,0 +1 @@ +Subproject commit bc39cd76ac3d541e618606bcc6e1e5ba5e5e6aa3 diff --git a/include/xtest/xtest.h b/include/xtest/xtest.h index 737600f..c3952a2 100644 --- a/include/xtest/xtest.h +++ b/include/xtest/xtest.h @@ -15,9 +15,9 @@ #include #include -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) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a9bf19f..25bbdce 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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) diff --git a/src/xtest.c b/src/xtest.c index 482287a..eb4a114 100644 --- a/src/xtest.c +++ b/src/xtest.c @@ -7,7 +7,21 @@ #include "xtest.h" #include +#ifdef XTEST_PRNG +#include +#include +#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