diff --git a/examples/ex_4.c b/examples/ex_4.c index ac1bc84..e0cca27 100644 --- a/examples/ex_4.c +++ b/examples/ex_4.c @@ -8,261 +8,19 @@ #include "../include/sparsemap.h" -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wvariadic-macros" -#define __diag(...) \ - do { \ - fprintf(stderr, "%s:%d:%s(): ", __FILE__, __LINE__, __func__); \ - fprintf(stderr, __VA_ARGS__); \ - } while (0) -#pragma GCC diagnostic pop - -#define SEED - -uint32_t -xorshift(int *state) -{ - if (!state) { - return 0; - } - // Xorshift algorithm - uint32_t x = *state; // Dereference state to get the current state value - if (x == 0) x = 123456789; // Ensure the state is never zero; use a default seed if so - x ^= x << 13; - x ^= x >> 17; - x ^= x << 5; - *state = x; // Update the state - return x; // Return the new state as the next pseudo-random number -} - -void -shuffle(int *array, size_t n, int *prng) -{ - size_t i, j; - - if (n > 1) { - for (i = n - 1; i > 0; i--) { - j = (unsigned int)(xorshift(prng) % (i + 1)); - // XOR swap algorithm - if (i != j) { // avoid self-swap leading to zero-ing the element - array[i] = array[i] ^ array[j]; - array[j] = array[i] ^ array[j]; - array[i] = array[i] ^ array[j]; - } - } - } -} - -int -compare_ints(const void *a, const void *b) -{ - return *(const int *)a - *(const int *)b; -} - -// Check if there's already a sequence of 'r' sequential integers -int has_sequential_set(int *a, size_t l, int r) { - int count = 1; // Start with a count of 1 for the first number - for (size_t i = 1; i < l; ++i) { - if (a[i] - a[i - 1] == 1) { // Check if the current and previous elements are sequential - count++; - if (count >= r) return 1; // Found a sequential set of length 'r' - } else { - count = 1; // Reset count if the sequence breaks - } - } - return 0; // No sequential set of length 'r' found -} - -// Function to ensure an array contains a set of 'r' sequential integers -void ensure_sequential_set(int *a, size_t l, int r, uint32_t *prng) { - if (r > l) return; // If 'r' is greater than array length, cannot satisfy the condition - - // Sort the array to check for existing sequences - qsort(a, l, sizeof(int), compare_ints); - - // Check if a sequential set of length 'r' already exists - if (has_sequential_set(a, l, r)) { - return; // Sequence already exists, no modification needed - } - - // Find the minimum and maximum values in the array - int min_value = a[0]; - int max_value = a[l - 1]; - - // Generate a random value between min_value and max_value - int value = xorshift(prng) % (max_value - min_value - r + 1); - - // Generate a random location between 0 and l - r - int offset = xorshift(prng) % (l + r + 1); - - // Adjust the array to include a sequential set of 'r' integers at the random offset - for (int i = 0; i < r; ++i) { - a[i + offset] = value + i; - } -} - -void -print_array(int *array, size_t l) -{ - int a[l]; - memcpy(a, array, sizeof(int) * l); - qsort(a, l, sizeof(int), compare_ints); - - printf("int a[] = {"); - for (int i = 0; i < l; i++) { - printf("%d", a[i]); - if (i != l) { - printf(", "); - } - } - printf("};\n"); -} - -bool -has_span(sparsemap_t *map, int *array, size_t l, size_t n) -{ - if (n == 0 || l == 0 || n > l) { - return false; - } - - int sorted[l]; - memcpy(sorted, array, sizeof(int) * l); - qsort(sorted, l, sizeof(int), compare_ints); - - for (size_t i = 0; i <= l - n; i++) { - if (sorted[i] + n - 1 == sorted[i + n - 1]) { -#if 0 - fprintf(stderr, "Found span: "); - for (size_t j = i; j < i + n; j++) { - fprintf(stderr, "%d ", sorted[j]); - } - fprintf(stderr, "\n"); -#endif - for (size_t j = 0; j < n; j++) { - size_t pos = sorted[j + i]; - bool set = sparsemap_is_set(map, pos); - assert(set); - } - __diag("Found span: [%d, %d], length: %zu\n", sorted[i], sorted[i + n - 1], n); - return true; - } - } - - return false; -} - -bool -is_span(int *array, size_t n, int x, int l) -{ - if (n == 0 || l < 0) { - return false; - } - - int a[n]; - memcpy(a, array, sizeof(int) * n); - qsort(a, n, sizeof(int), compare_ints); - - // Iterate through the array to find a span starting at x of length l - for (size_t i = 0; i < n; i++) { - if (a[i] == x) { - // Check if the span can fit in the array - if (i + l - 1 < n && a[i + l - 1] == x + l - 1) { - return true; // Found the span - } - } - } - return false; // Span not found -} - -void -print_spans(int *array, size_t n) -{ - int a[n]; - size_t start = 0, end = 0; - - if (n == 0) { - fprintf(stderr, "Array is empty\n"); - return; - } - - memcpy(a, array, sizeof(int) * n); - qsort(a, n, sizeof(int), compare_ints); - - for (size_t i = 1; i < n; i++) { - if (a[i] == a[i - 1] + 1) { - end = i; // Extend the span - } else { - // Print the current span - if (start == end) { - fprintf(stderr, "[%d] ", a[start]); - } else { - fprintf(stderr, "[%d, %d] ", a[start], a[end]); - } - // Move to the next span - start = i; - end = i; - } - } - - // Print the last span if needed - if (start == end) { - fprintf(stderr, "[%d]\n", a[start]); - } else { - fprintf(stderr, "[%d, %d]\n", a[start], a[end]); - } -} - -bool -was_set(size_t bit, const int array[]) -{ - for (int i = 0; i < 1024; i++) { - if (array[i] == (int)bit) { - return true; - } - } - return false; -} +#define EXAMPLE_CODE +#include "../tests/common.c" #define TEST_ARRAY_SIZE 1024 -int -is_unique(int a[], size_t l, int value) { - for (size_t i = 0; i < l; ++i) { - if (a[i] == value) { - return 0; // Not unique - } - } - return 1; // Unique -} - -void -setup_test_array(int a[], size_t l, int max_value, int *prng) -{ - if (a == NULL || prng == NULL || max_value < 0) return; // Basic error handling and validation - - for (size_t i = 0; i < l; ++i) { - int candidate; - do { - candidate = xorshift(prng) % (max_value + 1); // Generate a new value within the specified range - } while (!is_unique(a, i, candidate)); // Repeat until a unique value is found - a[i] = candidate; // Assign the unique value to the array - } -} - int main(void) { int i = 0; size_t rank; int array[TEST_ARRAY_SIZE]; - int prng; -// seed the PRNG -#ifdef SEED - prng = 8675309; -#else - prng = (unsigned int)time(NULL) ^ getpid(); -#endif + xorshift32_seed(); // disable buffering setvbuf(stdout, NULL, _IONBF, 0); // Disable buffering for stdout @@ -275,10 +33,10 @@ main(void) sparsemap_t *map = sparsemap(buf, sizeof(uint8_t) * 3 * 1024, 0); // create an array of ints - setup_test_array(array, TEST_ARRAY_SIZE, 1024 * 3, &prng); + setup_test_array(array, TEST_ARRAY_SIZE, 1024 * 3); // randomize setting the bits on - shuffle(array, TEST_ARRAY_SIZE, &prng); + shuffle(array, TEST_ARRAY_SIZE); //print_array(array, TEST_ARRAY_SIZE); //print_spans(array, TEST_ARRAY_SIZE); @@ -300,8 +58,8 @@ main(void) __diag("================> %lu\n", len); sparsemap_clear(map); // set all the bits on in a random order - ensure_sequential_set(array, TEST_ARRAY_SIZE, len, &prng); - shuffle(array, TEST_ARRAY_SIZE, &prng); + ensure_sequential_set(array, TEST_ARRAY_SIZE, len); + shuffle(array, TEST_ARRAY_SIZE); print_spans(array, TEST_ARRAY_SIZE); for (i = 0; i < TEST_ARRAY_SIZE; i++) { sparsemap_set(map, array[i], true); diff --git a/tests/api.c b/tests/api.c new file mode 100644 index 0000000..ae83ac1 --- /dev/null +++ b/tests/api.c @@ -0,0 +1,196 @@ +static void * +test_api_setup(const MunitParameter params[], void *user_data) +{ + struct test_info *info = (struct test_info *)user_data; + (void)info; + (void)params; + + ex_sl_t *slist = calloc(sizeof(ex_sl_t), 1); + if (slist == NULL) + return NULL; + sl_init(slist, uint32_key_cmp); + return (void *)(uintptr_t)slist; +} + +static void +test_api_tear_down(void *fixture) +{ + ex_sl_t *slist = (ex_sl_t *)fixture; + assert_ptr_not_null(slist); + sl_node *cursor = sl_begin(slist); + while (cursor) { + assert_ptr_not_null(cursor); + ex_node_t *entry = sl_get_entry(cursor, ex_node_t, snode); + assert_ptr_not_null(entry); + assert_uint32(entry->key, ==, entry->value); + cursor = sl_next(slist, cursor); + sl_erase_node(slist, &entry->snode); + sl_release_node(&entry->snode); + sl_wait_for_free(&entry->snode); + sl_free_node(&entry->snode); + free(entry); + } + sl_free(slist); + free(fixture); +} + +static void * +test_api_insert_setup(const MunitParameter params[], void *user_data) +{ + return test_api_setup(params, user_data); +} +static void +test_api_insert_tear_down(void *fixture) +{ + test_api_tear_down(fixture); +} +static MunitResult +test_api_insert(const MunitParameter params[], void *data) +{ + sl_raw *slist = (sl_raw *)data; + (void)params; + assert_ptr_not_null(data); + int n = munit_rand_int_range(128, 4096); + int key = munit_rand_int_range(0, (((uint32_t)0) - 1) / 10); + while (n--) { + ex_node_t *node = (ex_node_t *)calloc(sizeof(ex_node_t), 1); + sl_init_node(&node->snode); + node->key = key; + node->value = key; + sl_insert(slist, &node->snode); + } + return MUNIT_OK; +} + +static void * +test_api_remove_setup(const MunitParameter params[], void *user_data) +{ + return test_api_setup(params, user_data); +} +static void +test_api_remove_tear_down(void *fixture) +{ + test_api_tear_down(fixture); +} +static MunitResult +test_api_remove(const MunitParameter params[], void *data) +{ + sl_raw *slist = (sl_raw *)data; + (void)params; + (void)slist; + return MUNIT_OK; +} + +static void * +test_api_find_setup(const MunitParameter params[], void *user_data) +{ + return test_api_setup(params, user_data); +} +static void +test_api_find_tear_down(void *fixture) +{ + test_api_tear_down(fixture); +} +static MunitResult +test_api_find(const MunitParameter params[], void *data) +{ + sl_raw *slist = (sl_raw *)data; + (void)params; + (void)slist; + return MUNIT_OK; +} + +static void * +test_api_update_setup(const MunitParameter params[], void *user_data) +{ + return test_api_setup(params, user_data); +} +static void +test_api_update_tear_down(void *fixture) +{ + test_api_tear_down(fixture); +} +static MunitResult +test_api_update(const MunitParameter params[], void *data) +{ + sl_raw *slist = (sl_raw *)data; + (void)params; + (void)slist; + return MUNIT_OK; +} + +static void * +test_api_delete_setup(const MunitParameter params[], void *user_data) +{ + return test_api_setup(params, user_data); +} +static void +test_api_delete_tear_down(void *fixture) +{ + test_api_tear_down(fixture); +} +static MunitResult +test_api_delete(const MunitParameter params[], void *data) +{ + sl_raw *slist = (sl_raw *)data; + (void)params; + (void)slist; + return MUNIT_OK; +} + +static void * +test_api_duplicates_setup(const MunitParameter params[], void *user_data) +{ + return test_api_setup(params, user_data); +} +static void +test_api_duplicates_tear_down(void *fixture) +{ + test_api_tear_down(fixture); +} +static MunitResult +test_api_duplicates(const MunitParameter params[], void *data) +{ + sl_raw *slist = (sl_raw *)data; + (void)params; + (void)slist; + return MUNIT_OK; +} + +static void * +test_api_size_setup(const MunitParameter params[], void *user_data) +{ + return test_api_setup(params, user_data); +} +static void +test_api_size_tear_down(void *fixture) +{ + test_api_tear_down(fixture); +} +static MunitResult +test_api_size(const MunitParameter params[], void *data) +{ + sl_raw *slist = (sl_raw *)data; + (void)params; + (void)slist; + return MUNIT_OK; +} + +static void * +test_api_iterators_setup(const MunitParameter params[], void *user_data) +{ + return test_api_setup(params, user_data); +} +static void +test_api_iterators_tear_down(void *fixture) +{ + test_api_tear_down(fixture); +} +static MunitResult +test_api_iterators(const MunitParameter params[], void *data) +{ + sl_raw *slist = (sl_raw *)data; + (void)params; + (void)slist; + return MUNIT_OK; +} diff --git a/tests/common.c b/tests/common.c new file mode 100644 index 0000000..1164f3f --- /dev/null +++ b/tests/common.c @@ -0,0 +1,251 @@ + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wvariadic-macros" +#define __diag(...) \ + do { \ + fprintf(stderr, "%s:%d:%s(): ", __FILE__, __LINE__, __func__); \ + fprintf(stderr, __VA_ARGS__); \ + } while (0) +#pragma GCC diagnostic pop + +#ifdef EXAMPLE_CODE +int __prng = 0; + +// Xorshift algorithm for PRNG +uint32_t +xorshift32() +{ + uint32_t x = *state = &__prng; + if (x == 0) x = 123456789; + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + *state = x; + return x; +} + +void +xorshift32_seed() { + // Seed the PRNG +#ifdef STABLE_SEED + __prng = 8675309; +#else + __prng = (unsigned int)time(NULL) ^ getpid(); +#endif +} +#else +#define xorshift32 munit_rand_uint32 +#endif + +void +shuffle(int *array, size_t n) +{ + size_t i, j; + + if (n > 1) { + for (i = n - 1; i > 0; i--) { + j = (unsigned int)(xorshift32() % (i + 1)); + // XOR swap algorithm + if (i != j) { // avoid self-swap leading to zero-ing the element + array[i] = array[i] ^ array[j]; + array[j] = array[i] ^ array[j]; + array[i] = array[i] ^ array[j]; + } + } + } +} + +int +compare_ints(const void *a, const void *b) +{ + return *(const int *)a - *(const int *)b; +} + +// Check if there's already a sequence of 'r' sequential integers +int has_sequential_set(int *a, size_t l, int r) { + int count = 1; // Start with a count of 1 for the first number + for (size_t i = 1; i < l; ++i) { + if (a[i] - a[i - 1] == 1) { // Check if the current and previous elements are sequential + count++; + if (count >= r) return 1; // Found a sequential set of length 'r' + } else { + count = 1; // Reset count if the sequence breaks + } + } + return 0; // No sequential set of length 'r' found +} + +// Function to ensure an array contains a set of 'r' sequential integers +void ensure_sequential_set(int *a, size_t l, int r) { + if (r > l) return; // If 'r' is greater than array length, cannot satisfy the condition + + // Sort the array to check for existing sequences + qsort(a, l, sizeof(int), compare_ints); + + // Check if a sequential set of length 'r' already exists + if (has_sequential_set(a, l, r)) { + return; // Sequence already exists, no modification needed + } + + // Find the minimum and maximum values in the array + int min_value = a[0]; + int max_value = a[l - 1]; + + // Generate a random value between min_value and max_value + int value = xorshift32() % (max_value - min_value - r + 1); + + // Generate a random location between 0 and l - r + int offset = xorshift32() % (l + r + 1); + + // Adjust the array to include a sequential set of 'r' integers at the random offset + for (int i = 0; i < r; ++i) { + a[i + offset] = value + i; + } +} + +void +print_array(int *array, size_t l) +{ + int a[l]; + memcpy(a, array, sizeof(int) * l); + qsort(a, l, sizeof(int), compare_ints); + + printf("int a[] = {"); + for (int i = 0; i < l; i++) { + printf("%d", a[i]); + if (i != l) { + printf(", "); + } + } + printf("};\n"); +} + +bool +has_span(sparsemap_t *map, int *array, size_t l, size_t n) +{ + if (n == 0 || l == 0 || n > l) { + return false; + } + + int sorted[l]; + memcpy(sorted, array, sizeof(int) * l); + qsort(sorted, l, sizeof(int), compare_ints); + + for (size_t i = 0; i <= l - n; i++) { + if (sorted[i] + n - 1 == sorted[i + n - 1]) { +#if 0 + fprintf(stderr, "Found span: "); + for (size_t j = i; j < i + n; j++) { + fprintf(stderr, "%d ", sorted[j]); + } + fprintf(stderr, "\n"); +#endif + for (size_t j = 0; j < n; j++) { + size_t pos = sorted[j + i]; + bool set = sparsemap_is_set(map, pos); + assert(set); + } + __diag("Found span: [%d, %d], length: %zu\n", sorted[i], sorted[i + n - 1], n); + return true; + } + } + + return false; +} + +bool +is_span(int *array, size_t n, int x, int l) +{ + if (n == 0 || l < 0) { + return false; + } + + int a[n]; + memcpy(a, array, sizeof(int) * n); + qsort(a, n, sizeof(int), compare_ints); + + // Iterate through the array to find a span starting at x of length l + for (size_t i = 0; i < n; i++) { + if (a[i] == x) { + // Check if the span can fit in the array + if (i + l - 1 < n && a[i + l - 1] == x + l - 1) { + return true; // Found the span + } + } + } + return false; // Span not found +} + +void +print_spans(int *array, size_t n) +{ + int a[n]; + size_t start = 0, end = 0; + + if (n == 0) { + fprintf(stderr, "Array is empty\n"); + return; + } + + memcpy(a, array, sizeof(int) * n); + qsort(a, n, sizeof(int), compare_ints); + + for (size_t i = 1; i < n; i++) { + if (a[i] == a[i - 1] + 1) { + end = i; // Extend the span + } else { + // Print the current span + if (start == end) { + fprintf(stderr, "[%d] ", a[start]); + } else { + fprintf(stderr, "[%d, %d] ", a[start], a[end]); + } + // Move to the next span + start = i; + end = i; + } + } + + // Print the last span if needed + if (start == end) { + fprintf(stderr, "[%d]\n", a[start]); + } else { + fprintf(stderr, "[%d, %d]\n", a[start], a[end]); + } +} + +bool +was_set(size_t bit, const int array[]) +{ + for (int i = 0; i < 1024; i++) { + if (array[i] == (int)bit) { + return true; + } + } + return false; +} + +int +is_unique(int a[], size_t l, int value) { + for (size_t i = 0; i < l; ++i) { + if (a[i] == value) { + return 0; // Not unique + } + } + return 1; // Unique +} + +void +setup_test_array(int a[], size_t l, int max_value) +{ + if (a == NULL || max_value < 0) return; // Basic error handling and validation + + for (size_t i = 0; i < l; ++i) { + int candidate; + do { + candidate = xorshift32() % (max_value + 1); // Generate a new value within the specified range + } while (!is_unique(a, i, candidate)); // Repeat until a unique value is found + a[i] = candidate; // Assign the unique value to the array + } +} + diff --git a/tests/munit.c b/tests/munit.c new file mode 100644 index 0000000..f42f8e9 --- /dev/null +++ b/tests/munit.c @@ -0,0 +1,2255 @@ +/* Copyright (c) 2013-2018 Evan Nemerson + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/*** Configuration ***/ + +/* This is just where the output from the test goes. It's really just + * meant to let you choose stdout or stderr, but if anyone really want + * to direct it to a file let me know, it would be fairly easy to + * support. */ +#if !defined(MUNIT_OUTPUT_FILE) +#define MUNIT_OUTPUT_FILE stdout +#endif + +/* This is a bit more useful; it tells µnit how to format the seconds in + * timed tests. If your tests run for longer you might want to reduce + * it, and if your computer is really fast and your tests are tiny you + * can increase it. */ +#if !defined(MUNIT_TEST_TIME_FORMAT) +#define MUNIT_TEST_TIME_FORMAT "0.8f" +#endif + +/* If you have long test names you might want to consider bumping + * this. The result information takes 43 characters. */ +#if !defined(MUNIT_TEST_NAME_LEN) +#define MUNIT_TEST_NAME_LEN 37 +#endif + +/* If you don't like the timing information, you can disable it by + * defining MUNIT_DISABLE_TIMING. */ +#if !defined(MUNIT_DISABLE_TIMING) +#define MUNIT_ENABLE_TIMING +#endif + +/*** End configuration ***/ + +#if defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE < 200809L) +#undef _POSIX_C_SOURCE +#endif +#if !defined(_POSIX_C_SOURCE) +#define _POSIX_C_SOURCE 200809L +#endif + +/* Solaris freaks out if you try to use a POSIX or SUS standard without + * the "right" C standard. */ +#if defined(_XOPEN_SOURCE) +#undef _XOPEN_SOURCE +#endif + +#if defined(__STDC_VERSION__) +#if __STDC_VERSION__ >= 201112L +#define _XOPEN_SOURCE 700 +#elif __STDC_VERSION__ >= 199901L +#define _XOPEN_SOURCE 600 +#endif +#endif + +/* Because, according to Microsoft, POSIX is deprecated. You've got + * to appreciate the chutzpah. */ +#if defined(_MSC_VER) && !defined(_CRT_NONSTDC_NO_DEPRECATE) +#define _CRT_NONSTDC_NO_DEPRECATE +#endif + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +#include +#elif defined(_WIN32) +/* https://msdn.microsoft.com/en-us/library/tf4dy80a.aspx */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#if !defined(MUNIT_NO_NL_LANGINFO) && !defined(_WIN32) +#define MUNIT_NL_LANGINFO +#include +#include +#include +#endif + +#if !defined(_WIN32) +#include +#include + +#include +#else +#include +#include +#include +#if !defined(STDERR_FILENO) +#define STDERR_FILENO _fileno(stderr) +#endif +#endif + +#include "munit.h" + +#define MUNIT_STRINGIFY(x) #x +#define MUNIT_XSTRINGIFY(x) MUNIT_STRINGIFY(x) + +#if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__SUNPRO_CC) || \ + defined(__IBMCPP__) +#define MUNIT_THREAD_LOCAL __thread +#elif (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201102L)) || \ + defined(_Thread_local) +#define MUNIT_THREAD_LOCAL _Thread_local +#elif defined(_WIN32) +#define MUNIT_THREAD_LOCAL __declspec(thread) +#endif + +/* MSVC 12.0 will emit a warning at /W4 for code like 'do { ... } + * while (0)', or 'do { ... } while (1)'. I'm pretty sure nobody + * at Microsoft compiles with /W4. */ +#if defined(_MSC_VER) && (_MSC_VER <= 1800) +#pragma warning(disable : 4127) +#endif + +#if defined(_WIN32) || defined(__EMSCRIPTEN__) +#define MUNIT_NO_FORK +#endif + +#if defined(__EMSCRIPTEN__) +#define MUNIT_NO_BUFFER +#endif + +/*** Logging ***/ + +static MunitLogLevel munit_log_level_visible = MUNIT_LOG_INFO; +static MunitLogLevel munit_log_level_fatal = MUNIT_LOG_ERROR; + +#if defined(MUNIT_THREAD_LOCAL) +static MUNIT_THREAD_LOCAL munit_bool munit_error_jmp_buf_valid = 0; +static MUNIT_THREAD_LOCAL jmp_buf munit_error_jmp_buf; +#endif + +/* At certain warning levels, mingw will trigger warnings about + * suggesting the format attribute, which we've explicity *not* set + * because it will then choke on our attempts to use the MS-specific + * I64 modifier for size_t (which we have to use since MSVC doesn't + * support the C99 z modifier). */ + +#if defined(__MINGW32__) || defined(__MINGW64__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsuggest-attribute=format" +#endif + +MUNIT_PRINTF(5, 0) +static void +munit_logf_exv(MunitLogLevel level, FILE *fp, const char *filename, int line, + const char *format, va_list ap) +{ + if (level < munit_log_level_visible) + return; + + switch (level) { + case MUNIT_LOG_DEBUG: + fputs("Debug", fp); + break; + case MUNIT_LOG_INFO: + fputs("Info", fp); + break; + case MUNIT_LOG_WARNING: + fputs("Warning", fp); + break; + case MUNIT_LOG_ERROR: + fputs("Error", fp); + break; + default: + munit_logf_ex(MUNIT_LOG_ERROR, filename, line, "Invalid log level (%d)", + level); + return; + } + + fputs(": ", fp); + if (filename != NULL) + fprintf(fp, "%s:%d: ", filename, line); + vfprintf(fp, format, ap); + fputc('\n', fp); +} + +MUNIT_PRINTF(3, 4) +static void +munit_logf_internal(MunitLogLevel level, FILE *fp, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + munit_logf_exv(level, fp, NULL, 0, format, ap); + va_end(ap); +} + +static void +munit_log_internal(MunitLogLevel level, FILE *fp, const char *message) +{ + munit_logf_internal(level, fp, "%s", message); +} + +void +munit_logf_ex(MunitLogLevel level, const char *filename, int line, + const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + munit_logf_exv(level, stderr, filename, line, format, ap); + va_end(ap); + + if (level >= munit_log_level_fatal) { +#if defined(MUNIT_THREAD_LOCAL) + if (munit_error_jmp_buf_valid) + longjmp(munit_error_jmp_buf, 1); +#endif + abort(); + } +} + +void +munit_errorf_ex(const char *filename, int line, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + munit_logf_exv(MUNIT_LOG_ERROR, stderr, filename, line, format, ap); + va_end(ap); + +#if defined(MUNIT_THREAD_LOCAL) + if (munit_error_jmp_buf_valid) + longjmp(munit_error_jmp_buf, 1); +#endif + abort(); +} + +#if defined(__MINGW32__) || defined(__MINGW64__) +#pragma GCC diagnostic pop +#endif + +#if !defined(MUNIT_STRERROR_LEN) +#define MUNIT_STRERROR_LEN 80 +#endif + +static void +munit_log_errno(MunitLogLevel level, FILE *fp, const char *msg) +{ +#if defined(MUNIT_NO_STRERROR_R) || \ + (defined(__MINGW32__) && !defined(MINGW_HAS_SECURE_API)) + munit_logf_internal(level, fp, "%s: %s (%d)", msg, strerror(errno), errno); +#else + char munit_error_str[MUNIT_STRERROR_LEN]; + munit_error_str[0] = '\0'; + +#if !defined(_WIN32) + strerror_r(errno, munit_error_str, MUNIT_STRERROR_LEN); +#else + strerror_s(munit_error_str, MUNIT_STRERROR_LEN, errno); +#endif + + munit_logf_internal(level, fp, "%s: %s (%d)", msg, munit_error_str, errno); +#endif +} + +/*** Memory allocation ***/ + +void * +munit_malloc_ex(const char *filename, int line, size_t size) +{ + void *ptr; + + if (size == 0) + return NULL; + + ptr = calloc(1, size); + if (MUNIT_UNLIKELY(ptr == NULL)) { + munit_logf_ex(MUNIT_LOG_ERROR, filename, line, + "Failed to allocate %" MUNIT_SIZE_MODIFIER "u bytes.", size); + } + + return ptr; +} + +/*** Timer code ***/ + +#if defined(MUNIT_ENABLE_TIMING) + +#define psnip_uint64_t munit_uint64_t +#define psnip_uint32_t munit_uint32_t + +/* Code copied from portable-snippets + * . If you need to + * change something, please do it there so we can keep the code in + * sync. */ + +/* Clocks (v1) + * Portable Snippets - https://gitub.com/nemequ/portable-snippets + * Created by Evan Nemerson + * + * To the extent possible under law, the authors have waived all + * copyright and related or neighboring rights to this code. For + * details, see the Creative Commons Zero 1.0 Universal license at + * https://creativecommons.org/publicdomain/zero/1.0/ + */ + +#if !defined(PSNIP_CLOCK_H) +#define PSNIP_CLOCK_H + +#if !defined(psnip_uint64_t) +#include "../exact-int/exact-int.h" +#endif + +#if !defined(PSNIP_CLOCK_STATIC_INLINE) +#if defined(__GNUC__) +#define PSNIP_CLOCK__COMPILER_ATTRIBUTES __attribute__((__unused__)) +#else +#define PSNIP_CLOCK__COMPILER_ATTRIBUTES +#endif + +#define PSNIP_CLOCK__FUNCTION PSNIP_CLOCK__COMPILER_ATTRIBUTES static +#endif + +enum PsnipClockType { + /* This clock provides the current time, in units since 1970-01-01 + * 00:00:00 UTC not including leap seconds. In other words, UNIX + * time. Keep in mind that this clock doesn't account for leap + * seconds, and can go backwards (think NTP adjustments). */ + PSNIP_CLOCK_TYPE_WALL = 1, + /* The CPU time is a clock which increases only when the current + * process is active (i.e., it doesn't increment while blocking on + * I/O). */ + PSNIP_CLOCK_TYPE_CPU = 2, + /* Monotonic time is always running (unlike CPU time), but it only + ever moves forward unless you reboot the system. Things like NTP + adjustments have no effect on this clock. */ + PSNIP_CLOCK_TYPE_MONOTONIC = 3 +}; + +struct PsnipClockTimespec { + psnip_uint64_t seconds; + psnip_uint64_t nanoseconds; +}; + +/* Methods we support: */ + +#define PSNIP_CLOCK_METHOD_CLOCK_GETTIME 1 +#define PSNIP_CLOCK_METHOD_TIME 2 +#define PSNIP_CLOCK_METHOD_GETTIMEOFDAY 3 +#define PSNIP_CLOCK_METHOD_QUERYPERFORMANCECOUNTER 4 +#define PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME 5 +#define PSNIP_CLOCK_METHOD_CLOCK 6 +#define PSNIP_CLOCK_METHOD_GETPROCESSTIMES 7 +#define PSNIP_CLOCK_METHOD_GETRUSAGE 8 +#define PSNIP_CLOCK_METHOD_GETSYSTEMTIMEPRECISEASFILETIME 9 +#define PSNIP_CLOCK_METHOD_GETTICKCOUNT64 10 + +#include + +#if defined(HEDLEY_UNREACHABLE) +#define PSNIP_CLOCK_UNREACHABLE() HEDLEY_UNREACHABLE() +#else +#define PSNIP_CLOCK_UNREACHABLE() assert(0) +#endif + +/* Choose an implementation */ + +/* #undef PSNIP_CLOCK_WALL_METHOD */ +/* #undef PSNIP_CLOCK_CPU_METHOD */ +/* #undef PSNIP_CLOCK_MONOTONIC_METHOD */ + +/* We want to be able to detect the libc implementation, so we include + ( isn't available everywhere). */ + +#if defined(__unix__) || defined(__unix) || defined(__linux__) +#include +#include +#endif + +#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0) +/* These are known to work without librt. If you know of others + * please let us know so we can add them. */ +#if (defined(__GLIBC__) && \ + (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 17))) || \ + (defined(__FreeBSD__)) +#define PSNIP_CLOCK_HAVE_CLOCK_GETTIME +#elif !defined(PSNIP_CLOCK_NO_LIBRT) +#define PSNIP_CLOCK_HAVE_CLOCK_GETTIME +#endif +#endif + +#if defined(_WIN32) +#if !defined(PSNIP_CLOCK_CPU_METHOD) +#define PSNIP_CLOCK_CPU_METHOD PSNIP_CLOCK_METHOD_GETPROCESSTIMES +#endif +#if !defined(PSNIP_CLOCK_MONOTONIC_METHOD) +#define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_QUERYPERFORMANCECOUNTER +#endif +#endif + +#if defined(__MACH__) && !defined(__gnu_hurd__) +#if !defined(PSNIP_CLOCK_MONOTONIC_METHOD) +#define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME +#endif +#endif + +#if defined(PSNIP_CLOCK_HAVE_CLOCK_GETTIME) +#include +#if !defined(PSNIP_CLOCK_WALL_METHOD) +#if defined(CLOCK_REALTIME_PRECISE) +#define PSNIP_CLOCK_WALL_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME +#define PSNIP_CLOCK_CLOCK_GETTIME_WALL CLOCK_REALTIME_PRECISE +#elif !defined(__sun) +#define PSNIP_CLOCK_WALL_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME +#define PSNIP_CLOCK_CLOCK_GETTIME_WALL CLOCK_REALTIME +#endif +#endif +#if !defined(PSNIP_CLOCK_CPU_METHOD) +#if defined(_POSIX_CPUTIME) || defined(CLOCK_PROCESS_CPUTIME_ID) +#define PSNIP_CLOCK_CPU_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME +#define PSNIP_CLOCK_CLOCK_GETTIME_CPU CLOCK_PROCESS_CPUTIME_ID +#elif defined(CLOCK_VIRTUAL) +#define PSNIP_CLOCK_CPU_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME +#define PSNIP_CLOCK_CLOCK_GETTIME_CPU CLOCK_VIRTUAL +#endif +#endif +#if !defined(PSNIP_CLOCK_MONOTONIC_METHOD) +#if defined(CLOCK_MONOTONIC_RAW) +#define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME +#define PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC CLOCK_MONOTONIC +#elif defined(CLOCK_MONOTONIC_PRECISE) +#define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME +#define PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC CLOCK_MONOTONIC_PRECISE +#elif defined(_POSIX_MONOTONIC_CLOCK) || defined(CLOCK_MONOTONIC) +#define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME +#define PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC CLOCK_MONOTONIC +#endif +#endif +#endif + +#if defined(_POSIX_VERSION) && (_POSIX_VERSION >= 200112L) +#if !defined(PSNIP_CLOCK_WALL_METHOD) +#define PSNIP_CLOCK_WALL_METHOD PSNIP_CLOCK_METHOD_GETTIMEOFDAY +#endif +#endif + +#if !defined(PSNIP_CLOCK_WALL_METHOD) +#define PSNIP_CLOCK_WALL_METHOD PSNIP_CLOCK_METHOD_TIME +#endif + +#if !defined(PSNIP_CLOCK_CPU_METHOD) +#define PSNIP_CLOCK_CPU_METHOD PSNIP_CLOCK_METHOD_CLOCK +#endif + +/* Primarily here for testing. */ +#if !defined(PSNIP_CLOCK_MONOTONIC_METHOD) && \ + defined(PSNIP_CLOCK_REQUIRE_MONOTONIC) +#error No monotonic clock found. +#endif + +/* Implementations */ + +#if (defined(PSNIP_CLOCK_CPU_METHOD) && \ + (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \ + (defined(PSNIP_CLOCK_WALL_METHOD) && \ + (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \ + (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && \ + (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \ + (defined(PSNIP_CLOCK_CPU_METHOD) && \ + (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK)) || \ + (defined(PSNIP_CLOCK_WALL_METHOD) && \ + (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK)) || \ + (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && \ + (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK)) || \ + (defined(PSNIP_CLOCK_CPU_METHOD) && \ + (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_TIME)) || \ + (defined(PSNIP_CLOCK_WALL_METHOD) && \ + (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_TIME)) || \ + (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && \ + (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_TIME)) +#include +#endif + +#if (defined(PSNIP_CLOCK_CPU_METHOD) && \ + (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY)) || \ + (defined(PSNIP_CLOCK_WALL_METHOD) && \ + (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY)) || \ + (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && \ + (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY)) +#include +#endif + +#if (defined(PSNIP_CLOCK_CPU_METHOD) && \ + (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES)) || \ + (defined(PSNIP_CLOCK_WALL_METHOD) && \ + (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES)) || \ + (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && \ + (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES)) || \ + (defined(PSNIP_CLOCK_CPU_METHOD) && \ + (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64)) || \ + (defined(PSNIP_CLOCK_WALL_METHOD) && \ + (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64)) || \ + (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && \ + (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64)) +#include +#endif + +#if (defined(PSNIP_CLOCK_CPU_METHOD) && \ + (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETRUSAGE)) || \ + (defined(PSNIP_CLOCK_WALL_METHOD) && \ + (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETRUSAGE)) || \ + (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && \ + (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETRUSAGE)) +#include +#include +#endif + +#if (defined(PSNIP_CLOCK_CPU_METHOD) && \ + (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME)) || \ + (defined(PSNIP_CLOCK_WALL_METHOD) && \ + (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME)) || \ + (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && \ + (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME)) +#include +#include +#include +#endif + +/*** Implementations ***/ + +#define PSNIP_CLOCK_NSEC_PER_SEC ((psnip_uint32_t)(1000000000ULL)) + +#if (defined(PSNIP_CLOCK_CPU_METHOD) && \ + (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \ + (defined(PSNIP_CLOCK_WALL_METHOD) && \ + (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \ + (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && \ + (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) +PSNIP_CLOCK__FUNCTION psnip_uint32_t +psnip_clock__clock_getres(clockid_t clk_id) +{ + struct timespec res; + int r; + + r = clock_getres(clk_id, &res); + if (r != 0) + return 0; + + return (psnip_uint32_t)(PSNIP_CLOCK_NSEC_PER_SEC / res.tv_nsec); +} + +PSNIP_CLOCK__FUNCTION int +psnip_clock__clock_gettime(clockid_t clk_id, struct PsnipClockTimespec *res) +{ + struct timespec ts; + + if (clock_gettime(clk_id, &ts) != 0) + return -10; + + res->seconds = (psnip_uint64_t)(ts.tv_sec); + res->nanoseconds = (psnip_uint64_t)(ts.tv_nsec); + + return 0; +} +#endif + +PSNIP_CLOCK__FUNCTION psnip_uint32_t +psnip_clock_wall_get_precision(void) +{ +#if !defined(PSNIP_CLOCK_WALL_METHOD) + return 0; +#elif defined(PSNIP_CLOCK_WALL_METHOD) && \ + PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME + return psnip_clock__clock_getres(PSNIP_CLOCK_CLOCK_GETTIME_WALL); +#elif defined(PSNIP_CLOCK_WALL_METHOD) && \ + PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY + return 1000000; +#elif defined(PSNIP_CLOCK_WALL_METHOD) && \ + PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_TIME + return 1; +#else + return 0; +#endif +} + +PSNIP_CLOCK__FUNCTION int +psnip_clock_wall_get_time(struct PsnipClockTimespec *res) +{ + (void)res; + +#if !defined(PSNIP_CLOCK_WALL_METHOD) + return -2; +#elif defined(PSNIP_CLOCK_WALL_METHOD) && \ + PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME + return psnip_clock__clock_gettime(PSNIP_CLOCK_CLOCK_GETTIME_WALL, res); +#elif defined(PSNIP_CLOCK_WALL_METHOD) && \ + PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_TIME + res->seconds = time(NULL); + res->nanoseconds = 0; +#elif defined(PSNIP_CLOCK_WALL_METHOD) && \ + PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY + struct timeval tv; + + if (gettimeofday(&tv, NULL) != 0) + return -6; + + res->seconds = tv.tv_sec; + res->nanoseconds = tv.tv_usec * 1000; +#else + return -2; +#endif + + return 0; +} + +PSNIP_CLOCK__FUNCTION psnip_uint32_t +psnip_clock_cpu_get_precision(void) +{ +#if !defined(PSNIP_CLOCK_CPU_METHOD) + return 0; +#elif defined(PSNIP_CLOCK_CPU_METHOD) && \ + PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME + return psnip_clock__clock_getres(PSNIP_CLOCK_CLOCK_GETTIME_CPU); +#elif defined(PSNIP_CLOCK_CPU_METHOD) && \ + PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK + return CLOCKS_PER_SEC; +#elif defined(PSNIP_CLOCK_CPU_METHOD) && \ + PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES + return PSNIP_CLOCK_NSEC_PER_SEC / 100; +#else + return 0; +#endif +} + +PSNIP_CLOCK__FUNCTION int +psnip_clock_cpu_get_time(struct PsnipClockTimespec *res) +{ +#if !defined(PSNIP_CLOCK_CPU_METHOD) + (void)res; + return -2; +#elif defined(PSNIP_CLOCK_CPU_METHOD) && \ + PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME + return psnip_clock__clock_gettime(PSNIP_CLOCK_CLOCK_GETTIME_CPU, res); +#elif defined(PSNIP_CLOCK_CPU_METHOD) && \ + PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK + clock_t t = clock(); + if (t == ((clock_t)-1)) + return -5; + res->seconds = t / CLOCKS_PER_SEC; + res->nanoseconds = (t % CLOCKS_PER_SEC) * + (PSNIP_CLOCK_NSEC_PER_SEC / CLOCKS_PER_SEC); +#elif defined(PSNIP_CLOCK_CPU_METHOD) && \ + PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES + FILETIME CreationTime, ExitTime, KernelTime, UserTime; + LARGE_INTEGER date, adjust; + + if (!GetProcessTimes(GetCurrentProcess(), &CreationTime, &ExitTime, + &KernelTime, &UserTime)) + return -7; + + /* http://www.frenk.com/2009/12/convert-filetime-to-unix-timestamp/ */ + date.HighPart = UserTime.dwHighDateTime; + date.LowPart = UserTime.dwLowDateTime; + adjust.QuadPart = 11644473600000 * 10000; + date.QuadPart -= adjust.QuadPart; + + res->seconds = date.QuadPart / 10000000; + res->nanoseconds = (date.QuadPart % 10000000) * + (PSNIP_CLOCK_NSEC_PER_SEC / 100); +#elif PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETRUSAGE + struct rusage usage; + if (getrusage(RUSAGE_SELF, &usage) != 0) + return -8; + + res->seconds = usage.ru_utime.tv_sec; + res->nanoseconds = tv.tv_usec * 1000; +#else + (void)res; + return -2; +#endif + + return 0; +} + +PSNIP_CLOCK__FUNCTION psnip_uint32_t +psnip_clock_monotonic_get_precision(void) +{ +#if !defined(PSNIP_CLOCK_MONOTONIC_METHOD) + return 0; +#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && \ + PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME + return psnip_clock__clock_getres(PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC); +#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && \ + PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME + static mach_timebase_info_data_t tbi = { + 0, + }; + if (tbi.denom == 0) + mach_timebase_info(&tbi); + return (psnip_uint32_t)(tbi.numer / tbi.denom); +#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && \ + PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64 + return 1000; +#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && \ + PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_QUERYPERFORMANCECOUNTER + LARGE_INTEGER Frequency; + QueryPerformanceFrequency(&Frequency); + return (psnip_uint32_t)((Frequency.QuadPart > PSNIP_CLOCK_NSEC_PER_SEC) ? + PSNIP_CLOCK_NSEC_PER_SEC : + Frequency.QuadPart); +#else + return 0; +#endif +} + +PSNIP_CLOCK__FUNCTION int +psnip_clock_monotonic_get_time(struct PsnipClockTimespec *res) +{ +#if !defined(PSNIP_CLOCK_MONOTONIC_METHOD) + (void)res; + return -2; +#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && \ + PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME + return psnip_clock__clock_gettime(PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC, res); +#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && \ + PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME + psnip_uint64_t nsec = mach_absolute_time(); + static mach_timebase_info_data_t tbi = { + 0, + }; + if (tbi.denom == 0) + mach_timebase_info(&tbi); + nsec *= ((psnip_uint64_t)tbi.numer) / ((psnip_uint64_t)tbi.denom); + res->seconds = nsec / PSNIP_CLOCK_NSEC_PER_SEC; + res->nanoseconds = nsec % PSNIP_CLOCK_NSEC_PER_SEC; +#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && \ + PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_QUERYPERFORMANCECOUNTER + LARGE_INTEGER t, f; + if (QueryPerformanceCounter(&t) == 0) + return -12; + + QueryPerformanceFrequency(&f); + res->seconds = t.QuadPart / f.QuadPart; + res->nanoseconds = t.QuadPart % f.QuadPart; + if (f.QuadPart > PSNIP_CLOCK_NSEC_PER_SEC) + res->nanoseconds /= f.QuadPart / PSNIP_CLOCK_NSEC_PER_SEC; + else + res->nanoseconds *= PSNIP_CLOCK_NSEC_PER_SEC / f.QuadPart; +#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && \ + PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64 + const ULONGLONG msec = GetTickCount64(); + res->seconds = msec / 1000; + res->nanoseconds = sec % 1000; +#else + return -2; +#endif + + return 0; +} + +/* Returns the number of ticks per second for the specified clock. + * For example, a clock with millisecond precision would return 1000, + * and a clock with 1 second (such as the time() function) would + * return 1. + * + * If the requested clock isn't available, it will return 0. + * Hopefully this will be rare, but if it happens to you please let us + * know so we can work on finding a way to support your system. + * + * Note that different clocks on the same system often have a + * different precisions. + */ +PSNIP_CLOCK__FUNCTION psnip_uint32_t +psnip_clock_get_precision(enum PsnipClockType clock_type) +{ + switch (clock_type) { + case PSNIP_CLOCK_TYPE_MONOTONIC: + return psnip_clock_monotonic_get_precision(); + case PSNIP_CLOCK_TYPE_CPU: + return psnip_clock_cpu_get_precision(); + case PSNIP_CLOCK_TYPE_WALL: + return psnip_clock_wall_get_precision(); + } + + PSNIP_CLOCK_UNREACHABLE(); + return 0; +} + +/* Set the provided timespec to the requested time. Returns 0 on + * success, or a negative value on failure. */ +PSNIP_CLOCK__FUNCTION int +psnip_clock_get_time(enum PsnipClockType clock_type, + struct PsnipClockTimespec *res) +{ + assert(res != NULL); + + switch (clock_type) { + case PSNIP_CLOCK_TYPE_MONOTONIC: + return psnip_clock_monotonic_get_time(res); + case PSNIP_CLOCK_TYPE_CPU: + return psnip_clock_cpu_get_time(res); + case PSNIP_CLOCK_TYPE_WALL: + return psnip_clock_wall_get_time(res); + } + + return -1; +} + +#endif /* !defined(PSNIP_CLOCK_H) */ + +static psnip_uint64_t +munit_clock_get_elapsed(struct PsnipClockTimespec *start, + struct PsnipClockTimespec *end) +{ + psnip_uint64_t r = (end->seconds - start->seconds) * PSNIP_CLOCK_NSEC_PER_SEC; + if (end->nanoseconds < start->nanoseconds) { + r -= (start->nanoseconds - end->nanoseconds); + } else { + r += (end->nanoseconds - start->nanoseconds); + } + return r; +} + +#else +#include +#endif /* defined(MUNIT_ENABLE_TIMING) */ + +/*** PRNG stuff ***/ + +/* This is (unless I screwed up, which is entirely possible) the + * version of PCG with 32-bit state. It was chosen because it has a + * small enough state that we should reliably be able to use CAS + * instead of requiring a lock for thread-safety. + * + * If I did screw up, I probably will not bother changing it unless + * there is a significant bias. It's really not important this be + * particularly strong, as long as it is fairly random it's much more + * important that it be reproducible, so bug reports have a better + * chance of being reproducible. */ + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \ + !defined(__STDC_NO_ATOMICS__) && !defined(__EMSCRIPTEN__) && \ + (!defined(__GNUC_MINOR__) || (__GNUC__ > 4) || \ + (__GNUC__ == 4 && __GNUC_MINOR__ > 8)) +#define HAVE_STDATOMIC +#elif defined(__clang__) +#if __has_extension(c_atomic) +#define HAVE_CLANG_ATOMICS +#endif +#endif + +/* Workaround for http://llvm.org/bugs/show_bug.cgi?id=26911 */ +#if defined(__clang__) && defined(_WIN32) +#undef HAVE_STDATOMIC +#if defined(__c2__) +#undef HAVE_CLANG_ATOMICS +#endif +#endif + +#if defined(_OPENMP) +#define ATOMIC_UINT32_T uint32_t +#define ATOMIC_UINT32_INIT(x) (x) +#elif defined(HAVE_STDATOMIC) +#include +#define ATOMIC_UINT32_T _Atomic uint32_t +#define ATOMIC_UINT32_INIT(x) ATOMIC_VAR_INIT(x) +#elif defined(HAVE_CLANG_ATOMICS) +#define ATOMIC_UINT32_T _Atomic uint32_t +#define ATOMIC_UINT32_INIT(x) (x) +#elif defined(_WIN32) +#define ATOMIC_UINT32_T volatile LONG +#define ATOMIC_UINT32_INIT(x) (x) +#else +#define ATOMIC_UINT32_T volatile uint32_t +#define ATOMIC_UINT32_INIT(x) (x) +#endif + +static ATOMIC_UINT32_T munit_rand_state = ATOMIC_UINT32_INIT(42); + +#if defined(_OPENMP) +static inline void +munit_atomic_store(ATOMIC_UINT32_T *dest, ATOMIC_UINT32_T value) +{ +#pragma omp critical(munit_atomics) + *dest = value; +} + +static inline uint32_t +munit_atomic_load(ATOMIC_UINT32_T *src) +{ + int ret; +#pragma omp critical(munit_atomics) + ret = *src; + return ret; +} + +static inline uint32_t +munit_atomic_cas(ATOMIC_UINT32_T *dest, ATOMIC_UINT32_T *expected, + ATOMIC_UINT32_T desired) +{ + munit_bool ret; + +#pragma omp critical(munit_atomics) + { + if (*dest == *expected) { + *dest = desired; + ret = 1; + } else { + ret = 0; + } + } + + return ret; +} +#elif defined(HAVE_STDATOMIC) +#define munit_atomic_store(dest, value) atomic_store(dest, value) +#define munit_atomic_load(src) atomic_load(src) +#define munit_atomic_cas(dest, expected, value) \ + atomic_compare_exchange_weak(dest, expected, value) +#elif defined(HAVE_CLANG_ATOMICS) +#define munit_atomic_store(dest, value) \ + __c11_atomic_store(dest, value, __ATOMIC_SEQ_CST) +#define munit_atomic_load(src) __c11_atomic_load(src, __ATOMIC_SEQ_CST) +#define munit_atomic_cas(dest, expected, value) \ + __c11_atomic_compare_exchange_weak(dest, expected, value, __ATOMIC_SEQ_CST, \ + __ATOMIC_SEQ_CST) +#elif defined(__GNUC__) && (__GNUC__ > 4) || \ + (__GNUC__ == 4 && __GNUC_MINOR__ >= 7) +#define munit_atomic_store(dest, value) \ + __atomic_store_n(dest, value, __ATOMIC_SEQ_CST) +#define munit_atomic_load(src) __atomic_load_n(src, __ATOMIC_SEQ_CST) +#define munit_atomic_cas(dest, expected, value) \ + __atomic_compare_exchange_n(dest, expected, value, 1, __ATOMIC_SEQ_CST, \ + __ATOMIC_SEQ_CST) +#elif defined(__GNUC__) && (__GNUC__ >= 4) +#define munit_atomic_store(dest, value) \ + do { \ + *(dest) = (value); \ + } while (0) +#define munit_atomic_load(src) (*(src)) +#define munit_atomic_cas(dest, expected, value) \ + __sync_bool_compare_and_swap(dest, *expected, value) +#elif defined(_WIN32) /* Untested */ +#define munit_atomic_store(dest, value) \ + do { \ + *(dest) = (value); \ + } while (0) +#define munit_atomic_load(src) (*(src)) +#define munit_atomic_cas(dest, expected, value) \ + InterlockedCompareExchange((dest), (value), *(expected)) +#else +#warning No atomic implementation, PRNG will not be thread-safe +#define munit_atomic_store(dest, value) \ + do { \ + *(dest) = (value); \ + } while (0) +#define munit_atomic_load(src) (*(src)) +static inline munit_bool +munit_atomic_cas(ATOMIC_UINT32_T *dest, ATOMIC_UINT32_T *expected, + ATOMIC_UINT32_T desired) +{ + if (*dest == *expected) { + *dest = desired; + return 1; + } else { + return 0; + } +} +#endif + +#define MUNIT_PRNG_MULTIPLIER (747796405U) +#define MUNIT_PRNG_INCREMENT (1729U) + +static munit_uint32_t +munit_rand_next_state(munit_uint32_t state) +{ + return state * MUNIT_PRNG_MULTIPLIER + MUNIT_PRNG_INCREMENT; +} + +static munit_uint32_t +munit_rand_from_state(munit_uint32_t state) +{ + munit_uint32_t res = ((state >> ((state >> 28) + 4)) ^ state) * (277803737U); + res ^= res >> 22; + return res; +} + +void +munit_rand_seed(munit_uint32_t seed) +{ + munit_uint32_t state = munit_rand_next_state(seed + MUNIT_PRNG_INCREMENT); + munit_atomic_store(&munit_rand_state, state); +} + +static munit_uint32_t +munit_rand_generate_seed(void) +{ + munit_uint32_t seed, state; +#if defined(MUNIT_ENABLE_TIMING) + struct PsnipClockTimespec wc = { + 0, + }; + + psnip_clock_get_time(PSNIP_CLOCK_TYPE_WALL, &wc); + seed = (munit_uint32_t)wc.nanoseconds; +#else + seed = (munit_uint32_t)time(NULL); +#endif + + state = munit_rand_next_state(seed + MUNIT_PRNG_INCREMENT); + return munit_rand_from_state(state); +} + +static munit_uint32_t +munit_rand_state_uint32(munit_uint32_t *state) +{ + const munit_uint32_t old = *state; + *state = munit_rand_next_state(old); + return munit_rand_from_state(old); +} + +munit_uint32_t +munit_rand_uint32(void) +{ + munit_uint32_t old, state; + + do { + old = munit_atomic_load(&munit_rand_state); + state = munit_rand_next_state(old); + } while (!munit_atomic_cas(&munit_rand_state, &old, state)); + + return munit_rand_from_state(old); +} + +static void +munit_rand_state_memory(munit_uint32_t *state, size_t size, + munit_uint8_t data[MUNIT_ARRAY_PARAM(size)]) +{ + size_t members_remaining = size / sizeof(munit_uint32_t); + size_t bytes_remaining = size % sizeof(munit_uint32_t); + munit_uint8_t *b = data; + munit_uint32_t rv; + while (members_remaining-- > 0) { + rv = munit_rand_state_uint32(state); + memcpy(b, &rv, sizeof(munit_uint32_t)); + b += sizeof(munit_uint32_t); + } + if (bytes_remaining != 0) { + rv = munit_rand_state_uint32(state); + memcpy(b, &rv, bytes_remaining); + } +} + +void +munit_rand_memory(size_t size, munit_uint8_t data[MUNIT_ARRAY_PARAM(size)]) +{ + munit_uint32_t old, state; + + do { + state = old = munit_atomic_load(&munit_rand_state); + munit_rand_state_memory(&state, size, data); + } while (!munit_atomic_cas(&munit_rand_state, &old, state)); +} + +static munit_uint32_t +munit_rand_state_at_most(munit_uint32_t *state, munit_uint32_t salt, + munit_uint32_t max) +{ + /* We want (UINT32_MAX + 1) % max, which in unsigned arithmetic is the same + * as (UINT32_MAX + 1 - max) % max = -max % max. We compute -max using not + * to avoid compiler warnings. + */ + const munit_uint32_t min = (~max + 1U) % max; + munit_uint32_t x; + + if (max == (~((munit_uint32_t)0U))) + return munit_rand_state_uint32(state) ^ salt; + + max++; + + do { + x = munit_rand_state_uint32(state) ^ salt; + } while (x < min); + + return x % max; +} + +static munit_uint32_t +munit_rand_at_most(munit_uint32_t salt, munit_uint32_t max) +{ + munit_uint32_t old, state; + munit_uint32_t retval; + + do { + state = old = munit_atomic_load(&munit_rand_state); + retval = munit_rand_state_at_most(&state, salt, max); + } while (!munit_atomic_cas(&munit_rand_state, &old, state)); + + return retval; +} + +int +munit_rand_int_range(int min, int max) +{ + munit_uint64_t range = (munit_uint64_t)max - (munit_uint64_t)min; + + if (min > max) + return munit_rand_int_range(max, min); + + if (range > (~((munit_uint32_t)0U))) + range = (~((munit_uint32_t)0U)); + + return min + munit_rand_at_most(0, (munit_uint32_t)range); +} + +double +munit_rand_double(void) +{ + munit_uint32_t old, state; + double retval = 0.0; + + do { + state = old = munit_atomic_load(&munit_rand_state); + + /* See http://mumble.net/~campbell/tmp/random_real.c for how to do + * this right. Patches welcome if you feel that this is too + * biased. */ + retval = munit_rand_state_uint32(&state) / ((~((munit_uint32_t)0U)) + 1.0); + } while (!munit_atomic_cas(&munit_rand_state, &old, state)); + + return retval; +} + +/*** Test suite handling ***/ + +typedef struct { + unsigned int successful; + unsigned int skipped; + unsigned int failed; + unsigned int errored; +#if defined(MUNIT_ENABLE_TIMING) + munit_uint64_t cpu_clock; + munit_uint64_t wall_clock; +#endif +} MunitReport; + +typedef struct { + const char *prefix; + const MunitSuite *suite; + const char **tests; + munit_uint32_t seed; + unsigned int iterations; + MunitParameter *parameters; + munit_bool single_parameter_mode; + void *user_data; + MunitReport report; + munit_bool colorize; + munit_bool fork; + munit_bool show_stderr; + munit_bool fatal_failures; +} MunitTestRunner; + +const char * +munit_parameters_get(const MunitParameter params[], const char *key) +{ + const MunitParameter *param; + + for (param = params; param != NULL && param->name != NULL; param++) + if (strcmp(param->name, key) == 0) + return param->value; + return NULL; +} + +#if defined(MUNIT_ENABLE_TIMING) +static void +munit_print_time(FILE *fp, munit_uint64_t nanoseconds) +{ + fprintf(fp, "%" MUNIT_TEST_TIME_FORMAT, + ((double)nanoseconds) / ((double)PSNIP_CLOCK_NSEC_PER_SEC)); +} +#endif + +/* Add a paramter to an array of parameters. */ +static MunitResult +munit_parameters_add(size_t *params_size, + MunitParameter *params[MUNIT_ARRAY_PARAM(*params_size)], char *name, + char *value) +{ + *params = realloc(*params, sizeof(MunitParameter) * (*params_size + 2)); + if (*params == NULL) + return MUNIT_ERROR; + + (*params)[*params_size].name = name; + (*params)[*params_size].value = value; + (*params_size)++; + (*params)[*params_size].name = NULL; + (*params)[*params_size].value = NULL; + + return MUNIT_OK; +} + +/* Concatenate two strings, but just return one of the components + * unaltered if the other is NULL or "". */ +static char * +munit_maybe_concat(size_t *len, char *prefix, char *suffix) +{ + char *res; + size_t res_l; + const size_t prefix_l = prefix != NULL ? strlen(prefix) : 0; + const size_t suffix_l = suffix != NULL ? strlen(suffix) : 0; + if (prefix_l == 0 && suffix_l == 0) { + res = NULL; + res_l = 0; + } else if (prefix_l == 0 && suffix_l != 0) { + res = suffix; + res_l = suffix_l; + } else if (prefix_l != 0 && suffix_l == 0) { + res = prefix; + res_l = prefix_l; + } else { + res_l = prefix_l + suffix_l; + res = malloc(res_l + 1); + memcpy(res, prefix, prefix_l); + memcpy(res + prefix_l, suffix, suffix_l); + res[res_l] = 0; + } + + if (len != NULL) + *len = res_l; + + return res; +} + +/* Possbily free a string returned by munit_maybe_concat. */ +static void +munit_maybe_free_concat(char *s, const char *prefix, const char *suffix) +{ + if (prefix != s && suffix != s) + free(s); +} + +/* Cheap string hash function, just used to salt the PRNG. */ +static munit_uint32_t +munit_str_hash(const char *name) +{ + const char *p; + munit_uint32_t h = 5381U; + + for (p = name; *p != '\0'; p++) + h = (h << 5) + h + *p; + + return h; +} + +static void +munit_splice(int from, int to) +{ + munit_uint8_t buf[1024]; +#if !defined(_WIN32) + ssize_t len; + ssize_t bytes_written; + ssize_t write_res; +#else + int len; + int bytes_written; + int write_res; +#endif + do { + len = read(from, buf, sizeof(buf)); + if (len > 0) { + bytes_written = 0; + do { + write_res = write(to, buf + bytes_written, len - bytes_written); + if (write_res < 0) + break; + bytes_written += write_res; + } while (bytes_written < len); + } else + break; + } while (1); +} + +/* This is the part that should be handled in the child process */ +static MunitResult +munit_test_runner_exec(MunitTestRunner *runner, const MunitTest *test, + const MunitParameter params[], MunitReport *report) +{ + unsigned int iterations = runner->iterations; + MunitResult result = MUNIT_FAIL; +#if defined(MUNIT_ENABLE_TIMING) + struct PsnipClockTimespec wall_clock_begin = { 0, }, wall_clock_end = { 0, }; + struct PsnipClockTimespec cpu_clock_begin = { 0, }, cpu_clock_end = { 0, }; +#endif + unsigned int i = 0; + + if ((test->options & MUNIT_TEST_OPTION_SINGLE_ITERATION) == + MUNIT_TEST_OPTION_SINGLE_ITERATION) + iterations = 1; + else if (iterations == 0) + iterations = runner->suite->iterations; + + munit_rand_seed(runner->seed); + + do { + void *data = (test->setup == NULL) ? runner->user_data : + test->setup(params, runner->user_data); + +#if defined(MUNIT_ENABLE_TIMING) + psnip_clock_get_time(PSNIP_CLOCK_TYPE_WALL, &wall_clock_begin); + psnip_clock_get_time(PSNIP_CLOCK_TYPE_CPU, &cpu_clock_begin); +#endif + + result = test->test(params, data); + +#if defined(MUNIT_ENABLE_TIMING) + psnip_clock_get_time(PSNIP_CLOCK_TYPE_WALL, &wall_clock_end); + psnip_clock_get_time(PSNIP_CLOCK_TYPE_CPU, &cpu_clock_end); +#endif + + if (test->tear_down != NULL) + test->tear_down(data); + + if (MUNIT_LIKELY(result == MUNIT_OK)) { + report->successful++; +#if defined(MUNIT_ENABLE_TIMING) + report->wall_clock += munit_clock_get_elapsed(&wall_clock_begin, + &wall_clock_end); + report->cpu_clock += munit_clock_get_elapsed(&cpu_clock_begin, + &cpu_clock_end); +#endif + } else { + switch ((int)result) { + case MUNIT_SKIP: + report->skipped++; + break; + case MUNIT_FAIL: + report->failed++; + break; + case MUNIT_ERROR: + report->errored++; + break; + default: + break; + } + break; + } + } while (++i < iterations); + + return result; +} + +#if defined(MUNIT_EMOTICON) +#define MUNIT_RESULT_STRING_OK ":)" +#define MUNIT_RESULT_STRING_SKIP ":|" +#define MUNIT_RESULT_STRING_FAIL ":(" +#define MUNIT_RESULT_STRING_ERROR ":o" +#define MUNIT_RESULT_STRING_TODO ":/" +#else +#define MUNIT_RESULT_STRING_OK "OK " +#define MUNIT_RESULT_STRING_SKIP "SKIP " +#define MUNIT_RESULT_STRING_FAIL "FAIL " +#define MUNIT_RESULT_STRING_ERROR "ERROR" +#define MUNIT_RESULT_STRING_TODO "TODO " +#endif + +static void +munit_test_runner_print_color(const MunitTestRunner *runner, const char *string, + char color) +{ + if (runner->colorize) + fprintf(MUNIT_OUTPUT_FILE, "\x1b[3%cm%s\x1b[39m", color, string); + else + fputs(string, MUNIT_OUTPUT_FILE); +} + +#if !defined(MUNIT_NO_BUFFER) +static int +munit_replace_stderr(FILE *stderr_buf) +{ + if (stderr_buf != NULL) { + const int orig_stderr = dup(STDERR_FILENO); + + int errfd = fileno(stderr_buf); + if (MUNIT_UNLIKELY(errfd == -1)) { + exit(EXIT_FAILURE); + } + + dup2(errfd, STDERR_FILENO); + + return orig_stderr; + } + + return -1; +} + +static void +munit_restore_stderr(int orig_stderr) +{ + if (orig_stderr != -1) { + dup2(orig_stderr, STDERR_FILENO); + close(orig_stderr); + } +} +#endif /* !defined(MUNIT_NO_BUFFER) */ + +/* Run a test with the specified parameters. */ +static void +munit_test_runner_run_test_with_params(MunitTestRunner *runner, + const MunitTest *test, const MunitParameter params[]) +{ + MunitResult result = MUNIT_OK; + MunitReport report = { + 0, + 0, + 0, + 0, +#if defined(MUNIT_ENABLE_TIMING) + 0, + 0 +#endif + }; + unsigned int output_l; + munit_bool first; + const MunitParameter *param; + FILE *stderr_buf; +#if !defined(MUNIT_NO_FORK) + int pipefd[2]; + pid_t fork_pid; + int orig_stderr; + ssize_t bytes_written = 0; + ssize_t write_res; + ssize_t bytes_read = 0; + ssize_t read_res; + int status = 0; + pid_t changed_pid; +#endif + + if (params != NULL) { + output_l = 2; + fputs(" ", MUNIT_OUTPUT_FILE); + first = 1; + for (param = params; param != NULL && param->name != NULL; param++) { + if (!first) { + fputs(", ", MUNIT_OUTPUT_FILE); + output_l += 2; + } else { + first = 0; + } + + output_l += fprintf(MUNIT_OUTPUT_FILE, "%s=%s", param->name, + param->value); + } + while (output_l++ < MUNIT_TEST_NAME_LEN) { + fputc(' ', MUNIT_OUTPUT_FILE); + } + } + + fflush(MUNIT_OUTPUT_FILE); + + stderr_buf = NULL; +#if !defined(_WIN32) || defined(__MINGW32__) + stderr_buf = tmpfile(); +#else + tmpfile_s(&stderr_buf); +#endif + if (stderr_buf == NULL) { + munit_log_errno(MUNIT_LOG_ERROR, stderr, + "unable to create buffer for stderr"); + result = MUNIT_ERROR; + goto print_result; + } + +#if !defined(MUNIT_NO_FORK) + if (runner->fork) { + pipefd[0] = -1; + pipefd[1] = -1; + if (pipe(pipefd) != 0) { + munit_log_errno(MUNIT_LOG_ERROR, stderr, "unable to create pipe"); + result = MUNIT_ERROR; + goto print_result; + } + + fork_pid = fork(); + if (fork_pid == 0) { + close(pipefd[0]); + + orig_stderr = munit_replace_stderr(stderr_buf); + munit_test_runner_exec(runner, test, params, &report); + + /* Note that we don't restore stderr. This is so we can buffer + * things written to stderr later on (such as by + * asan/tsan/ubsan, valgrind, etc.) */ + close(orig_stderr); + + do { + write_res = write(pipefd[1], + ((munit_uint8_t *)(&report)) + bytes_written, + sizeof(report) - bytes_written); + if (write_res < 0) { + if (stderr_buf != NULL) { + munit_log_errno(MUNIT_LOG_ERROR, stderr, "unable to write to pipe"); + } + exit(EXIT_FAILURE); + } + bytes_written += write_res; + } while ((size_t)bytes_written < sizeof(report)); + + if (stderr_buf != NULL) + fclose(stderr_buf); + close(pipefd[1]); + + exit(EXIT_SUCCESS); + } else if (fork_pid == -1) { + close(pipefd[0]); + close(pipefd[1]); + if (stderr_buf != NULL) { + munit_log_errno(MUNIT_LOG_ERROR, stderr, "unable to fork"); + } + report.errored++; + result = MUNIT_ERROR; + } else { + close(pipefd[1]); + do { + read_res = read(pipefd[0], ((munit_uint8_t *)(&report)) + bytes_read, + sizeof(report) - bytes_read); + if (read_res < 1) + break; + bytes_read += read_res; + } while (bytes_read < (ssize_t)sizeof(report)); + + changed_pid = waitpid(fork_pid, &status, 0); + + if (MUNIT_LIKELY(changed_pid == fork_pid) && + MUNIT_LIKELY(WIFEXITED(status))) { + if (bytes_read != sizeof(report)) { + munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, + "child exited unexpectedly with status %d", WEXITSTATUS(status)); + report.errored++; + } else if (WEXITSTATUS(status) != EXIT_SUCCESS) { + munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, + "child exited with status %d", WEXITSTATUS(status)); + report.errored++; + } + } else { + if (WIFSIGNALED(status)) { +#if defined(_XOPEN_VERSION) && (_XOPEN_VERSION >= 700) + munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, + "child killed by signal %d (%s)", WTERMSIG(status), + strsignal(WTERMSIG(status))); +#else + munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, + "child killed by signal %d", WTERMSIG(status)); +#endif + } else if (WIFSTOPPED(status)) { + munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, + "child stopped by signal %d", WSTOPSIG(status)); + } + report.errored++; + } + + close(pipefd[0]); + waitpid(fork_pid, NULL, 0); + } + } else +#endif + { +#if !defined(MUNIT_NO_BUFFER) + const volatile int orig_stderr = munit_replace_stderr(stderr_buf); +#endif + +#if defined(MUNIT_THREAD_LOCAL) + if (MUNIT_UNLIKELY(setjmp(munit_error_jmp_buf) != 0)) { + result = MUNIT_FAIL; + report.failed++; + } else { + munit_error_jmp_buf_valid = 1; + result = munit_test_runner_exec(runner, test, params, &report); + } +#else + result = munit_test_runner_exec(runner, test, params, &report); +#endif + +#if !defined(MUNIT_NO_BUFFER) + munit_restore_stderr(orig_stderr); +#endif + + /* Here just so that the label is used on Windows and we don't get + * a warning */ + goto print_result; + } + +print_result: + + fputs("[ ", MUNIT_OUTPUT_FILE); + if ((test->options & MUNIT_TEST_OPTION_TODO) == MUNIT_TEST_OPTION_TODO) { + if (report.failed != 0 || report.errored != 0 || report.skipped != 0) { + munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_TODO, '3'); + result = MUNIT_OK; + } else { + munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_ERROR, '1'); + if (MUNIT_LIKELY(stderr_buf != NULL)) + munit_log_internal(MUNIT_LOG_ERROR, stderr_buf, + "Test marked TODO, but was successful."); + runner->report.failed++; + result = MUNIT_ERROR; + } + } else if (report.failed > 0) { + munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_FAIL, '1'); + runner->report.failed++; + result = MUNIT_FAIL; + } else if (report.errored > 0) { + munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_ERROR, '1'); + runner->report.errored++; + result = MUNIT_ERROR; + } else if (report.skipped > 0) { + munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_SKIP, '3'); + runner->report.skipped++; + result = MUNIT_SKIP; + } else if (report.successful > 1) { + munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_OK, '2'); +#if defined(MUNIT_ENABLE_TIMING) + fputs(" ] [ ", MUNIT_OUTPUT_FILE); + munit_print_time(MUNIT_OUTPUT_FILE, report.wall_clock / report.successful); + fputs(" / ", MUNIT_OUTPUT_FILE); + munit_print_time(MUNIT_OUTPUT_FILE, report.cpu_clock / report.successful); + fprintf(MUNIT_OUTPUT_FILE, + " CPU ]\n %-" MUNIT_XSTRINGIFY(MUNIT_TEST_NAME_LEN) "s Total: [ ", ""); + munit_print_time(MUNIT_OUTPUT_FILE, report.wall_clock); + fputs(" / ", MUNIT_OUTPUT_FILE); + munit_print_time(MUNIT_OUTPUT_FILE, report.cpu_clock); + fputs(" CPU", MUNIT_OUTPUT_FILE); +#endif + runner->report.successful++; + result = MUNIT_OK; + } else if (report.successful > 0) { + munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_OK, '2'); +#if defined(MUNIT_ENABLE_TIMING) + fputs(" ] [ ", MUNIT_OUTPUT_FILE); + munit_print_time(MUNIT_OUTPUT_FILE, report.wall_clock); + fputs(" / ", MUNIT_OUTPUT_FILE); + munit_print_time(MUNIT_OUTPUT_FILE, report.cpu_clock); + fputs(" CPU", MUNIT_OUTPUT_FILE); +#endif + runner->report.successful++; + result = MUNIT_OK; + } + fputs(" ]\n", MUNIT_OUTPUT_FILE); + + if (stderr_buf != NULL) { + if (result == MUNIT_FAIL || result == MUNIT_ERROR || runner->show_stderr) { + fflush(MUNIT_OUTPUT_FILE); + + rewind(stderr_buf); + munit_splice(fileno(stderr_buf), STDERR_FILENO); + + fflush(stderr); + } + + fclose(stderr_buf); + } +} + +static void +munit_test_runner_run_test_wild(MunitTestRunner *runner, const MunitTest *test, + const char *test_name, MunitParameter *params, MunitParameter *p) +{ + const MunitParameterEnum *pe; + char **values; + MunitParameter *next; + + for (pe = test->parameters; pe != NULL && pe->name != NULL; pe++) { + if (p->name == pe->name) + break; + } + + if (pe == NULL) + return; + + for (values = pe->values; *values != NULL; values++) { + next = p + 1; + p->value = *values; + if (next->name == NULL) { + munit_test_runner_run_test_with_params(runner, test, params); + } else { + munit_test_runner_run_test_wild(runner, test, test_name, params, next); + } + if (runner->fatal_failures && + (runner->report.failed != 0 || runner->report.errored != 0)) + break; + } +} + +/* Run a single test, with every combination of parameters + * requested. */ +static void +munit_test_runner_run_test(MunitTestRunner *runner, const MunitTest *test, + const char *prefix) +{ + char *test_name = munit_maybe_concat(NULL, (char *)prefix, + (char *)test->name); + /* The array of parameters to pass to + * munit_test_runner_run_test_with_params */ + MunitParameter *params = NULL; + size_t params_l = 0; + /* Wildcard parameters are parameters which have possible values + * specified in the test, but no specific value was passed to the + * CLI. That means we want to run the test once for every + * possible combination of parameter values or, if --single was + * passed to the CLI, a single time with a random set of + * parameters. */ + MunitParameter *wild_params = NULL; + size_t wild_params_l = 0; + const MunitParameterEnum *pe; + const MunitParameter *cli_p; + munit_bool filled; + unsigned int possible; + char **vals; + size_t first_wild; + const MunitParameter *wp; + int pidx; + + munit_rand_seed(runner->seed); + + fprintf(MUNIT_OUTPUT_FILE, "%-" MUNIT_XSTRINGIFY(MUNIT_TEST_NAME_LEN) "s", + test_name); + + if (test->parameters == NULL) { + /* No parameters. Simple, nice. */ + munit_test_runner_run_test_with_params(runner, test, NULL); + } else { + fputc('\n', MUNIT_OUTPUT_FILE); + + for (pe = test->parameters; pe != NULL && pe->name != NULL; pe++) { + /* Did we received a value for this parameter from the CLI? */ + filled = 0; + for (cli_p = runner->parameters; cli_p != NULL && cli_p->name != NULL; + cli_p++) { + if (strcmp(cli_p->name, pe->name) == 0) { + if (MUNIT_UNLIKELY(munit_parameters_add(¶ms_l, ¶ms, pe->name, + cli_p->value) != MUNIT_OK)) + goto cleanup; + filled = 1; + break; + } + } + if (filled) + continue; + + /* Nothing from CLI, is the enum NULL/empty? We're not a + * fuzzer… */ + if (pe->values == NULL || pe->values[0] == NULL) + continue; + + /* If --single was passed to the CLI, choose a value from the + * list of possibilities randomly. */ + if (runner->single_parameter_mode) { + possible = 0; + for (vals = pe->values; *vals != NULL; vals++) + possible++; + /* We want the tests to be reproducible, even if you're only + * running a single test, but we don't want every test with + * the same number of parameters to choose the same parameter + * number, so use the test name as a primitive salt. */ + pidx = munit_rand_at_most(munit_str_hash(test_name), possible - 1); + if (MUNIT_UNLIKELY(munit_parameters_add(¶ms_l, ¶ms, pe->name, + pe->values[pidx]) != MUNIT_OK)) + goto cleanup; + } else { + /* We want to try every permutation. Put in a placeholder + * entry, we'll iterate through them later. */ + if (MUNIT_UNLIKELY(munit_parameters_add(&wild_params_l, &wild_params, + pe->name, NULL) != MUNIT_OK)) + goto cleanup; + } + } + + if (wild_params_l != 0) { + first_wild = params_l; + for (wp = wild_params; wp != NULL && wp->name != NULL; wp++) { + for (pe = test->parameters; + pe != NULL && pe->name != NULL && pe->values != NULL; pe++) { + if (strcmp(wp->name, pe->name) == 0) { + if (MUNIT_UNLIKELY(munit_parameters_add(¶ms_l, ¶ms, + pe->name, pe->values[0]) != MUNIT_OK)) + goto cleanup; + } + } + } + + munit_test_runner_run_test_wild(runner, test, test_name, params, + params + first_wild); + } else { + munit_test_runner_run_test_with_params(runner, test, params); + } + + cleanup: + free(params); + free(wild_params); + } + + munit_maybe_free_concat(test_name, prefix, test->name); +} + +/* Recurse through the suite and run all the tests. If a list of + * tests to run was provied on the command line, run only those + * tests. */ +static void +munit_test_runner_run_suite(MunitTestRunner *runner, const MunitSuite *suite, + const char *prefix) +{ + size_t pre_l; + char *pre = munit_maybe_concat(&pre_l, (char *)prefix, (char *)suite->prefix); + const MunitTest *test; + const char **test_name; + const MunitSuite *child_suite; + + /* Run the tests. */ + for (test = suite->tests; test != NULL && test->test != NULL; test++) { + if (runner->tests != NULL) { /* Specific tests were requested on the CLI */ + for (test_name = runner->tests; test_name != NULL && *test_name != NULL; + test_name++) { + if ((pre_l == 0 || strncmp(pre, *test_name, pre_l) == 0) && + strncmp(test->name, *test_name + pre_l, strlen(*test_name + pre_l)) == + 0) { + munit_test_runner_run_test(runner, test, pre); + if (runner->fatal_failures && + (runner->report.failed != 0 || runner->report.errored != 0)) + goto cleanup; + } + } + } else { /* Run all tests */ + munit_test_runner_run_test(runner, test, pre); + } + } + + if (runner->fatal_failures && + (runner->report.failed != 0 || runner->report.errored != 0)) + goto cleanup; + + /* Run any child suites. */ + for (child_suite = suite->suites; + child_suite != NULL && child_suite->prefix != NULL; child_suite++) { + munit_test_runner_run_suite(runner, child_suite, pre); + } + +cleanup: + + munit_maybe_free_concat(pre, prefix, suite->prefix); +} + +static void +munit_test_runner_run(MunitTestRunner *runner) +{ + munit_test_runner_run_suite(runner, runner->suite, NULL); +} + +static void +munit_print_help(int argc, char *const argv[MUNIT_ARRAY_PARAM(argc + 1)], + void *user_data, const MunitArgument arguments[]) +{ + const MunitArgument *arg; + (void)argc; + + printf("USAGE: %s [OPTIONS...] [TEST...]\n\n", argv[0]); + puts( + " --seed SEED\n" + " Value used to seed the PRNG. Must be a 32-bit integer in decimal\n" + " notation with no separators (commas, decimals, spaces, etc.), or\n" + " hexidecimal prefixed by \"0x\".\n" + " --iterations N\n" + " Run each test N times. 0 means the default number.\n" + " --param name value\n" + " A parameter key/value pair which will be passed to any test with\n" + " takes a parameter of that name. If not provided, the test will be\n" + " run once for each possible parameter value.\n" + " --list Write a list of all available tests.\n" + " --list-params\n" + " Write a list of all available tests and their possible parameters.\n" + " --single Run each parameterized test in a single configuration instead of\n" + " every possible combination\n" + " --log-visible debug|info|warning|error\n" + " --log-fatal debug|info|warning|error\n" + " Set the level at which messages of different severities are visible,\n" + " or cause the test to terminate.\n" +#if !defined(MUNIT_NO_FORK) + " --no-fork Do not execute tests in a child process. If this option is supplied\n" + " and a test crashes (including by failing an assertion), no further\n" + " tests will be performed.\n" +#endif + " --fatal-failures\n" + " Stop executing tests as soon as a failure is found.\n" + " --show-stderr\n" + " Show data written to stderr by the tests, even if the test succeeds.\n" + " --color auto|always|never\n" + " Colorize (or don't) the output.\n" + /* 12345678901234567890123456789012345678901234567890123456789012345678901234567890 + */ + " --help Print this help message and exit.\n"); +#if defined(MUNIT_NL_LANGINFO) + setlocale(LC_ALL, ""); + fputs((strcasecmp("UTF-8", nl_langinfo(CODESET)) == 0) ? "µnit" : "munit", + stdout); +#else + puts("munit"); +#endif + printf(" %d.%d.%d\n" + "Full documentation at: https://nemequ.github.io/munit/\n", + (MUNIT_CURRENT_VERSION >> 16) & 0xff, (MUNIT_CURRENT_VERSION >> 8) & 0xff, + (MUNIT_CURRENT_VERSION >> 0) & 0xff); + for (arg = arguments; arg != NULL && arg->name != NULL; arg++) + arg->write_help(arg, user_data); +} + +static const MunitArgument * +munit_arguments_find(const MunitArgument arguments[], const char *name) +{ + const MunitArgument *arg; + + for (arg = arguments; arg != NULL && arg->name != NULL; arg++) + if (strcmp(arg->name, name) == 0) + return arg; + + return NULL; +} + +static void +munit_suite_list_tests(const MunitSuite *suite, munit_bool show_params, + const char *prefix) +{ + size_t pre_l; + char *pre = munit_maybe_concat(&pre_l, (char *)prefix, (char *)suite->prefix); + const MunitTest *test; + const MunitParameterEnum *params; + munit_bool first; + char **val; + const MunitSuite *child_suite; + + for (test = suite->tests; test != NULL && test->name != NULL; test++) { + if (pre != NULL) + fputs(pre, stdout); + puts(test->name); + + if (show_params) { + for (params = test->parameters; params != NULL && params->name != NULL; + params++) { + fprintf(stdout, " - %s: ", params->name); + if (params->values == NULL) { + puts("Any"); + } else { + first = 1; + for (val = params->values; *val != NULL; val++) { + if (!first) { + fputs(", ", stdout); + } else { + first = 0; + } + fputs(*val, stdout); + } + putc('\n', stdout); + } + } + } + } + + for (child_suite = suite->suites; + child_suite != NULL && child_suite->prefix != NULL; child_suite++) { + munit_suite_list_tests(child_suite, show_params, pre); + } + + munit_maybe_free_concat(pre, prefix, suite->prefix); +} + +static munit_bool +munit_stream_supports_ansi(FILE *stream) +{ +#if !defined(_WIN32) + return isatty(fileno(stream)); +#else + +#if !defined(__MINGW32__) + size_t ansicon_size = 0; +#endif + + if (isatty(fileno(stream))) { +#if !defined(__MINGW32__) + getenv_s(&ansicon_size, NULL, 0, "ANSICON"); + return ansicon_size != 0; +#else + return getenv("ANSICON") != NULL; +#endif + } + return 0; +#endif +} + +int +munit_suite_main_custom(const MunitSuite *suite, void *user_data, int argc, + char *const argv[MUNIT_ARRAY_PARAM(argc + 1)], + const MunitArgument arguments[]) +{ + int result = EXIT_FAILURE; + MunitTestRunner runner; + size_t parameters_size = 0; + size_t tests_size = 0; + int arg; + + char *envptr; + unsigned long ts; + char *endptr; + unsigned long long iterations; + MunitLogLevel level; + const MunitArgument *argument; + const char **runner_tests; + unsigned int tests_run; + unsigned int tests_total; + + runner.prefix = NULL; + runner.suite = NULL; + runner.tests = NULL; + runner.seed = 0; + runner.iterations = 0; + runner.parameters = NULL; + runner.single_parameter_mode = 0; + runner.user_data = NULL; + + runner.report.successful = 0; + runner.report.skipped = 0; + runner.report.failed = 0; + runner.report.errored = 0; +#if defined(MUNIT_ENABLE_TIMING) + runner.report.cpu_clock = 0; + runner.report.wall_clock = 0; +#endif + + runner.colorize = 0; +#if !defined(_WIN32) + runner.fork = 1; +#else + runner.fork = 0; +#endif + runner.show_stderr = 0; + runner.fatal_failures = 0; + runner.suite = suite; + runner.user_data = user_data; + runner.seed = munit_rand_generate_seed(); + runner.colorize = munit_stream_supports_ansi(MUNIT_OUTPUT_FILE); + + for (arg = 1; arg < argc; arg++) { + if (strncmp("--", argv[arg], 2) == 0) { + if (strcmp("seed", argv[arg] + 2) == 0) { + if (arg + 1 >= argc) { + munit_logf_internal(MUNIT_LOG_ERROR, stderr, + "%s requires an argument", argv[arg]); + goto cleanup; + } + + envptr = argv[arg + 1]; + ts = strtoul(argv[arg + 1], &envptr, 0); + if (*envptr != '\0' || ts > (~((munit_uint32_t)0U))) { + munit_logf_internal(MUNIT_LOG_ERROR, stderr, + "invalid value ('%s') passed to %s", argv[arg + 1], argv[arg]); + goto cleanup; + } + runner.seed = (munit_uint32_t)ts; + + arg++; + } else if (strcmp("iterations", argv[arg] + 2) == 0) { + if (arg + 1 >= argc) { + munit_logf_internal(MUNIT_LOG_ERROR, stderr, + "%s requires an argument", argv[arg]); + goto cleanup; + } + + endptr = argv[arg + 1]; + iterations = strtoul(argv[arg + 1], &endptr, 0); + if (*endptr != '\0' || iterations > UINT_MAX) { + munit_logf_internal(MUNIT_LOG_ERROR, stderr, + "invalid value ('%s') passed to %s", argv[arg + 1], argv[arg]); + goto cleanup; + } + + runner.iterations = (unsigned int)iterations; + + arg++; + } else if (strcmp("param", argv[arg] + 2) == 0) { + if (arg + 2 >= argc) { + munit_logf_internal(MUNIT_LOG_ERROR, stderr, + "%s requires two arguments", argv[arg]); + goto cleanup; + } + + runner.parameters = realloc(runner.parameters, + sizeof(MunitParameter) * (parameters_size + 2)); + if (runner.parameters == NULL) { + munit_log_internal(MUNIT_LOG_ERROR, stderr, + "failed to allocate memory"); + goto cleanup; + } + runner.parameters[parameters_size].name = (char *)argv[arg + 1]; + runner.parameters[parameters_size].value = (char *)argv[arg + 2]; + parameters_size++; + runner.parameters[parameters_size].name = NULL; + runner.parameters[parameters_size].value = NULL; + arg += 2; + } else if (strcmp("color", argv[arg] + 2) == 0) { + if (arg + 1 >= argc) { + munit_logf_internal(MUNIT_LOG_ERROR, stderr, + "%s requires an argument", argv[arg]); + goto cleanup; + } + + if (strcmp(argv[arg + 1], "always") == 0) + runner.colorize = 1; + else if (strcmp(argv[arg + 1], "never") == 0) + runner.colorize = 0; + else if (strcmp(argv[arg + 1], "auto") == 0) + runner.colorize = munit_stream_supports_ansi(MUNIT_OUTPUT_FILE); + else { + munit_logf_internal(MUNIT_LOG_ERROR, stderr, + "invalid value ('%s') passed to %s", argv[arg + 1], argv[arg]); + goto cleanup; + } + + arg++; + } else if (strcmp("help", argv[arg] + 2) == 0) { + munit_print_help(argc, argv, user_data, arguments); + result = EXIT_SUCCESS; + goto cleanup; + } else if (strcmp("single", argv[arg] + 2) == 0) { + runner.single_parameter_mode = 1; + } else if (strcmp("show-stderr", argv[arg] + 2) == 0) { + runner.show_stderr = 1; +#if !defined(_WIN32) + } else if (strcmp("no-fork", argv[arg] + 2) == 0) { + runner.fork = 0; +#endif + } else if (strcmp("fatal-failures", argv[arg] + 2) == 0) { + runner.fatal_failures = 1; + } else if (strcmp("log-visible", argv[arg] + 2) == 0 || + strcmp("log-fatal", argv[arg] + 2) == 0) { + if (arg + 1 >= argc) { + munit_logf_internal(MUNIT_LOG_ERROR, stderr, + "%s requires an argument", argv[arg]); + goto cleanup; + } + + if (strcmp(argv[arg + 1], "debug") == 0) + level = MUNIT_LOG_DEBUG; + else if (strcmp(argv[arg + 1], "info") == 0) + level = MUNIT_LOG_INFO; + else if (strcmp(argv[arg + 1], "warning") == 0) + level = MUNIT_LOG_WARNING; + else if (strcmp(argv[arg + 1], "error") == 0) + level = MUNIT_LOG_ERROR; + else { + munit_logf_internal(MUNIT_LOG_ERROR, stderr, + "invalid value ('%s') passed to %s", argv[arg + 1], argv[arg]); + goto cleanup; + } + + if (strcmp("log-visible", argv[arg] + 2) == 0) + munit_log_level_visible = level; + else + munit_log_level_fatal = level; + + arg++; + } else if (strcmp("list", argv[arg] + 2) == 0) { + munit_suite_list_tests(suite, 0, NULL); + result = EXIT_SUCCESS; + goto cleanup; + } else if (strcmp("list-params", argv[arg] + 2) == 0) { + munit_suite_list_tests(suite, 1, NULL); + result = EXIT_SUCCESS; + goto cleanup; + } else { + argument = munit_arguments_find(arguments, argv[arg] + 2); + if (argument == NULL) { + munit_logf_internal(MUNIT_LOG_ERROR, stderr, + "unknown argument ('%s')", argv[arg]); + goto cleanup; + } + + if (!argument->parse_argument(suite, user_data, &arg, argc, argv)) + goto cleanup; + } + } else { + runner_tests = realloc((void *)runner.tests, + sizeof(char *) * (tests_size + 2)); + if (runner_tests == NULL) { + munit_log_internal(MUNIT_LOG_ERROR, stderr, + "failed to allocate memory"); + goto cleanup; + } + runner.tests = runner_tests; + runner.tests[tests_size++] = argv[arg]; + runner.tests[tests_size] = NULL; + } + } + + fflush(stderr); + fprintf(MUNIT_OUTPUT_FILE, + "Running test suite with seed 0x%08" PRIx32 "...\n", runner.seed); + + munit_test_runner_run(&runner); + + tests_run = runner.report.successful + runner.report.failed + + runner.report.errored; + tests_total = tests_run + runner.report.skipped; + if (tests_run == 0) { + fprintf(stderr, "No tests run, %d (100%%) skipped.\n", + runner.report.skipped); + } else { + fprintf(MUNIT_OUTPUT_FILE, + "%d of %d (%0.0f%%) tests successful, %d (%0.0f%%) test skipped.\n", + runner.report.successful, tests_run, + (((double)runner.report.successful) / ((double)tests_run)) * 100.0, + runner.report.skipped, + (((double)runner.report.skipped) / ((double)tests_total)) * 100.0); + } + + if (runner.report.failed == 0 && runner.report.errored == 0) { + result = EXIT_SUCCESS; + } + +cleanup: + free(runner.parameters); + free((void *)runner.tests); + + return result; +} + +int +munit_suite_main(const MunitSuite *suite, void *user_data, int argc, + char *const argv[MUNIT_ARRAY_PARAM(argc + 1)]) +{ + return munit_suite_main_custom(suite, user_data, argc, argv, NULL); +} diff --git a/tests/munit.h b/tests/munit.h new file mode 100644 index 0000000..28fbf8b --- /dev/null +++ b/tests/munit.h @@ -0,0 +1,527 @@ +/* µnit Testing Framework + * Copyright (c) 2013-2017 Evan Nemerson + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#if !defined(MUNIT_H) +#define MUNIT_H + +#include +#include + +#define MUNIT_VERSION(major, minor, revision) \ + (((major) << 16) | ((minor) << 8) | (revision)) + +#define MUNIT_CURRENT_VERSION MUNIT_VERSION(0, 4, 1) + +#if defined(_MSC_VER) && (_MSC_VER < 1600) +#define munit_int8_t __int8 +#define munit_uint8_t unsigned __int8 +#define munit_int16_t __int16 +#define munit_uint16_t unsigned __int16 +#define munit_int32_t __int32 +#define munit_uint32_t unsigned __int32 +#define munit_int64_t __int64 +#define munit_uint64_t unsigned __int64 +#else +#include +#define munit_int8_t int8_t +#define munit_uint8_t uint8_t +#define munit_int16_t int16_t +#define munit_uint16_t uint16_t +#define munit_int32_t int32_t +#define munit_uint32_t uint32_t +#define munit_int64_t int64_t +#define munit_uint64_t uint64_t +#endif + +#if defined(_MSC_VER) && (_MSC_VER < 1800) +#if !defined(PRIi8) +#define PRIi8 "i" +#endif +#if !defined(PRIi16) +#define PRIi16 "i" +#endif +#if !defined(PRIi32) +#define PRIi32 "i" +#endif +#if !defined(PRIi64) +#define PRIi64 "I64i" +#endif +#if !defined(PRId8) +#define PRId8 "d" +#endif +#if !defined(PRId16) +#define PRId16 "d" +#endif +#if !defined(PRId32) +#define PRId32 "d" +#endif +#if !defined(PRId64) +#define PRId64 "I64d" +#endif +#if !defined(PRIx8) +#define PRIx8 "x" +#endif +#if !defined(PRIx16) +#define PRIx16 "x" +#endif +#if !defined(PRIx32) +#define PRIx32 "x" +#endif +#if !defined(PRIx64) +#define PRIx64 "I64x" +#endif +#if !defined(PRIu8) +#define PRIu8 "u" +#endif +#if !defined(PRIu16) +#define PRIu16 "u" +#endif +#if !defined(PRIu32) +#define PRIu32 "u" +#endif +#if !defined(PRIu64) +#define PRIu64 "I64u" +#endif +#else +#include +#endif + +#if !defined(munit_bool) +#if defined(bool) +#define munit_bool bool +#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +#define munit_bool _Bool +#else +#define munit_bool int +#endif +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +#if defined(__GNUC__) +#define MUNIT_LIKELY(expr) (__builtin_expect((expr), 1)) +#define MUNIT_UNLIKELY(expr) (__builtin_expect((expr), 0)) +#define MUNIT_UNUSED __attribute__((__unused__)) +#else +#define MUNIT_LIKELY(expr) (expr) +#define MUNIT_UNLIKELY(expr) (expr) +#define MUNIT_UNUSED +#endif + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ + !defined(__PGI) +#define MUNIT_ARRAY_PARAM(name) name +#else +#define MUNIT_ARRAY_PARAM(name) +#endif + +#if !defined(_WIN32) +#define MUNIT_SIZE_MODIFIER "z" +#define MUNIT_CHAR_MODIFIER "hh" +#define MUNIT_SHORT_MODIFIER "h" +#else +#if defined(_M_X64) || defined(__amd64__) +#define MUNIT_SIZE_MODIFIER "I64" +#else +#define MUNIT_SIZE_MODIFIER "" +#endif +#define MUNIT_CHAR_MODIFIER "" +#define MUNIT_SHORT_MODIFIER "" +#endif + +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L +#define MUNIT_NO_RETURN _Noreturn +#elif defined(__GNUC__) +#define MUNIT_NO_RETURN __attribute__((__noreturn__)) +#elif defined(_MSC_VER) +#define MUNIT_NO_RETURN __declspec(noreturn) +#else +#define MUNIT_NO_RETURN +#endif + +#if defined(_MSC_VER) && (_MSC_VER >= 1500) +#define MUNIT_PUSH_DISABLE_MSVC_C4127_ \ + __pragma(warning(push)) __pragma(warning(disable : 4127)) +#define MUNIT_POP_DISABLE_MSVC_C4127_ __pragma(warning(pop)) +#else +#define MUNIT_PUSH_DISABLE_MSVC_C4127_ +#define MUNIT_POP_DISABLE_MSVC_C4127_ +#endif + +typedef enum { + MUNIT_LOG_DEBUG, + MUNIT_LOG_INFO, + MUNIT_LOG_WARNING, + MUNIT_LOG_ERROR +} MunitLogLevel; + +#if defined(__GNUC__) && !defined(__MINGW32__) +#define MUNIT_PRINTF(string_index, first_to_check) \ + __attribute__((format(printf, string_index, first_to_check))) +#else +#define MUNIT_PRINTF(string_index, first_to_check) +#endif + +MUNIT_PRINTF(4, 5) +void munit_logf_ex(MunitLogLevel level, const char *filename, int line, + const char *format, ...); + +#define munit_logf(level, format, ...) \ + munit_logf_ex(level, __FILE__, __LINE__, format, __VA_ARGS__) + +#define munit_log(level, msg) munit_logf(level, "%s", msg) + +MUNIT_NO_RETURN +MUNIT_PRINTF(3, 4) +void munit_errorf_ex(const char *filename, int line, const char *format, ...); + +#define munit_errorf(format, ...) \ + munit_errorf_ex(__FILE__, __LINE__, format, __VA_ARGS__) + +#define munit_error(msg) munit_errorf("%s", msg) + +#define munit_assert(expr) \ + do { \ + if (!MUNIT_LIKELY(expr)) { \ + munit_error("assertion failed: " #expr); \ + } \ + MUNIT_PUSH_DISABLE_MSVC_C4127_ \ + } while (0) MUNIT_POP_DISABLE_MSVC_C4127_ + +#define munit_assert_true(expr) \ + do { \ + if (!MUNIT_LIKELY(expr)) { \ + munit_error("assertion failed: " #expr " is not true"); \ + } \ + MUNIT_PUSH_DISABLE_MSVC_C4127_ \ + } while (0) MUNIT_POP_DISABLE_MSVC_C4127_ + +#define munit_assert_false(expr) \ + do { \ + if (!MUNIT_LIKELY(!(expr))) { \ + munit_error("assertion failed: " #expr " is not false"); \ + } \ + MUNIT_PUSH_DISABLE_MSVC_C4127_ \ + } while (0) MUNIT_POP_DISABLE_MSVC_C4127_ + +#define munit_assert_type_full(prefix, suffix, T, fmt, a, op, b) \ + do { \ + T munit_tmp_a_ = (a); \ + T munit_tmp_b_ = (b); \ + if (!(munit_tmp_a_ op munit_tmp_b_)) { \ + munit_errorf("assertion failed: %s %s %s (" prefix "%" fmt suffix \ + " %s " prefix "%" fmt suffix ")", \ + #a, #op, #b, munit_tmp_a_, #op, munit_tmp_b_); \ + } \ + MUNIT_PUSH_DISABLE_MSVC_C4127_ \ + } while (0) MUNIT_POP_DISABLE_MSVC_C4127_ + +#define munit_assert_type(T, fmt, a, op, b) \ + munit_assert_type_full("", "", T, fmt, a, op, b) + +#define munit_assert_char(a, op, b) \ + munit_assert_type_full("'\\x", "'", char, "02" MUNIT_CHAR_MODIFIER "x", a, \ + op, b) +#define munit_assert_uchar(a, op, b) \ + munit_assert_type_full("'\\x", "'", unsigned char, \ + "02" MUNIT_CHAR_MODIFIER "x", a, op, b) +#define munit_assert_short(a, op, b) \ + munit_assert_type(short, MUNIT_SHORT_MODIFIER "d", a, op, b) +#define munit_assert_ushort(a, op, b) \ + munit_assert_type(unsigned short, MUNIT_SHORT_MODIFIER "u", a, op, b) +#define munit_assert_int(a, op, b) munit_assert_type(int, "d", a, op, b) +#define munit_assert_uint(a, op, b) \ + munit_assert_type(unsigned int, "u", a, op, b) +#define munit_assert_long(a, op, b) munit_assert_type(long int, "ld", a, op, b) +#define munit_assert_ulong(a, op, b) \ + munit_assert_type(unsigned long int, "lu", a, op, b) +#define munit_assert_llong(a, op, b) \ + munit_assert_type(long long int, "lld", a, op, b) +#define munit_assert_ullong(a, op, b) \ + munit_assert_type(unsigned long long int, "llu", a, op, b) + +#define munit_assert_size(a, op, b) \ + munit_assert_type(size_t, MUNIT_SIZE_MODIFIER "u", a, op, b) + +#define munit_assert_float(a, op, b) munit_assert_type(float, "f", a, op, b) +#define munit_assert_double(a, op, b) munit_assert_type(double, "g", a, op, b) +#define munit_assert_ptr(a, op, b) \ + munit_assert_type(const void *, "p", a, op, b) + +#define munit_assert_int8(a, op, b) \ + munit_assert_type(munit_int8_t, PRIi8, a, op, b) +#define munit_assert_uint8(a, op, b) \ + munit_assert_type(munit_uint8_t, PRIu8, a, op, b) +#define munit_assert_int16(a, op, b) \ + munit_assert_type(munit_int16_t, PRIi16, a, op, b) +#define munit_assert_uint16(a, op, b) \ + munit_assert_type(munit_uint16_t, PRIu16, a, op, b) +#define munit_assert_int32(a, op, b) \ + munit_assert_type(munit_int32_t, PRIi32, a, op, b) +#define munit_assert_uint32(a, op, b) \ + munit_assert_type(munit_uint32_t, PRIu32, a, op, b) +#define munit_assert_int64(a, op, b) \ + munit_assert_type(munit_int64_t, PRIi64, a, op, b) +#define munit_assert_uint64(a, op, b) \ + munit_assert_type(munit_uint64_t, PRIu64, a, op, b) + +#define munit_assert_double_equal(a, b, precision) \ + do { \ + const double munit_tmp_a_ = (a); \ + const double munit_tmp_b_ = (b); \ + const double munit_tmp_diff_ = ((munit_tmp_a_ - munit_tmp_b_) < 0) ? \ + -(munit_tmp_a_ - munit_tmp_b_) : \ + (munit_tmp_a_ - munit_tmp_b_); \ + if (MUNIT_UNLIKELY(munit_tmp_diff_ > 1e-##precision)) { \ + munit_errorf("assertion failed: %s == %s (%0." #precision \ + "g == %0." #precision "g)", \ + #a, #b, munit_tmp_a_, munit_tmp_b_); \ + } \ + MUNIT_PUSH_DISABLE_MSVC_C4127_ \ + } while (0) MUNIT_POP_DISABLE_MSVC_C4127_ + +#include +#define munit_assert_string_equal(a, b) \ + do { \ + const char *munit_tmp_a_ = a; \ + const char *munit_tmp_b_ = b; \ + if (MUNIT_UNLIKELY(strcmp(munit_tmp_a_, munit_tmp_b_) != 0)) { \ + munit_errorf("assertion failed: string %s == %s (\"%s\" == \"%s\")", #a, \ + #b, munit_tmp_a_, munit_tmp_b_); \ + } \ + MUNIT_PUSH_DISABLE_MSVC_C4127_ \ + } while (0) MUNIT_POP_DISABLE_MSVC_C4127_ + +#define munit_assert_string_not_equal(a, b) \ + do { \ + const char *munit_tmp_a_ = a; \ + const char *munit_tmp_b_ = b; \ + if (MUNIT_UNLIKELY(strcmp(munit_tmp_a_, munit_tmp_b_) == 0)) { \ + munit_errorf("assertion failed: string %s != %s (\"%s\" == \"%s\")", #a, \ + #b, munit_tmp_a_, munit_tmp_b_); \ + } \ + MUNIT_PUSH_DISABLE_MSVC_C4127_ \ + } while (0) MUNIT_POP_DISABLE_MSVC_C4127_ + +#define munit_assert_memory_equal(size, a, b) \ + do { \ + const unsigned char *munit_tmp_a_ = (const unsigned char *)(a); \ + const unsigned char *munit_tmp_b_ = (const unsigned char *)(b); \ + const size_t munit_tmp_size_ = (size); \ + if (MUNIT_UNLIKELY(memcmp(munit_tmp_a_, munit_tmp_b_, munit_tmp_size_)) != \ + 0) { \ + size_t munit_tmp_pos_; \ + for (munit_tmp_pos_ = 0; munit_tmp_pos_ < munit_tmp_size_; \ + munit_tmp_pos_++) { \ + if (munit_tmp_a_[munit_tmp_pos_] != munit_tmp_b_[munit_tmp_pos_]) { \ + munit_errorf( \ + "assertion failed: memory %s == %s, at offset %" MUNIT_SIZE_MODIFIER \ + "u", \ + #a, #b, munit_tmp_pos_); \ + break; \ + } \ + } \ + } \ + MUNIT_PUSH_DISABLE_MSVC_C4127_ \ + } while (0) MUNIT_POP_DISABLE_MSVC_C4127_ + +#define munit_assert_memory_not_equal(size, a, b) \ + do { \ + const unsigned char *munit_tmp_a_ = (const unsigned char *)(a); \ + const unsigned char *munit_tmp_b_ = (const unsigned char *)(b); \ + const size_t munit_tmp_size_ = (size); \ + if (MUNIT_UNLIKELY(memcmp(munit_tmp_a_, munit_tmp_b_, munit_tmp_size_)) == \ + 0) { \ + munit_errorf("assertion failed: memory %s != %s (%zu bytes)", #a, #b, \ + munit_tmp_size_); \ + } \ + MUNIT_PUSH_DISABLE_MSVC_C4127_ \ + } while (0) MUNIT_POP_DISABLE_MSVC_C4127_ + +#define munit_assert_ptr_equal(a, b) munit_assert_ptr(a, ==, b) +#define munit_assert_ptr_not_equal(a, b) munit_assert_ptr(a, !=, b) +#define munit_assert_null(ptr) munit_assert_ptr(ptr, ==, NULL) +#define munit_assert_not_null(ptr) munit_assert_ptr(ptr, !=, NULL) +#define munit_assert_ptr_null(ptr) munit_assert_ptr(ptr, ==, NULL) +#define munit_assert_ptr_not_null(ptr) munit_assert_ptr(ptr, !=, NULL) + +/*** Memory allocation ***/ + +void *munit_malloc_ex(const char *filename, int line, size_t size); + +#define munit_malloc(size) munit_malloc_ex(__FILE__, __LINE__, (size)) + +#define munit_new(type) ((type *)munit_malloc(sizeof(type))) + +#define munit_calloc(nmemb, size) munit_malloc((nmemb) * (size)) + +#define munit_newa(type, nmemb) ((type *)munit_calloc((nmemb), sizeof(type))) + +/*** Random number generation ***/ + +void munit_rand_seed(munit_uint32_t seed); +munit_uint32_t munit_rand_uint32(void); +int munit_rand_int_range(int min, int max); +double munit_rand_double(void); +void munit_rand_memory(size_t size, + munit_uint8_t buffer[MUNIT_ARRAY_PARAM(size)]); + +/*** Tests and Suites ***/ + +typedef enum { + /* Test successful */ + MUNIT_OK, + /* Test failed */ + MUNIT_FAIL, + /* Test was skipped */ + MUNIT_SKIP, + /* Test failed due to circumstances not intended to be tested + * (things like network errors, invalid parameter value, failure to + * allocate memory in the test harness, etc.). */ + MUNIT_ERROR +} MunitResult; + +typedef struct { + char *name; + char **values; +} MunitParameterEnum; + +typedef struct { + char *name; + char *value; +} MunitParameter; + +const char *munit_parameters_get(const MunitParameter params[], + const char *key); + +typedef enum { + MUNIT_TEST_OPTION_NONE = 0, + MUNIT_TEST_OPTION_SINGLE_ITERATION = 1 << 0, + MUNIT_TEST_OPTION_TODO = 1 << 1 +} MunitTestOptions; + +typedef MunitResult ( + *MunitTestFunc)(const MunitParameter params[], void *user_data_or_fixture); +typedef void *(*MunitTestSetup)(const MunitParameter params[], void *user_data); +typedef void (*MunitTestTearDown)(void *fixture); + +typedef struct { + char *name; + MunitTestFunc test; + MunitTestSetup setup; + MunitTestTearDown tear_down; + MunitTestOptions options; + MunitParameterEnum *parameters; +} MunitTest; + +typedef enum { MUNIT_SUITE_OPTION_NONE = 0 } MunitSuiteOptions; + +typedef struct MunitSuite_ MunitSuite; + +struct MunitSuite_ { + char *prefix; + MunitTest *tests; + MunitSuite *suites; + unsigned int iterations; + MunitSuiteOptions options; +}; + +int munit_suite_main(const MunitSuite *suite, void *user_data, int argc, + char *const argv[MUNIT_ARRAY_PARAM(argc + 1)]); + +/* Note: I'm not very happy with this API; it's likely to change if I + * figure out something better. Suggestions welcome. */ + +typedef struct MunitArgument_ MunitArgument; + +struct MunitArgument_ { + char *name; + munit_bool (*parse_argument)(const MunitSuite *suite, void *user_data, + int *arg, int argc, char *const argv[MUNIT_ARRAY_PARAM(argc + 1)]); + void (*write_help)(const MunitArgument *argument, void *user_data); +}; + +int munit_suite_main_custom(const MunitSuite *suite, void *user_data, int argc, + char *const argv[MUNIT_ARRAY_PARAM(argc + 1)], + const MunitArgument arguments[]); + +#if defined(MUNIT_ENABLE_ASSERT_ALIASES) + +#define assert_true(expr) munit_assert_true(expr) +#define assert_false(expr) munit_assert_false(expr) +#define assert_char(a, op, b) munit_assert_char(a, op, b) +#define assert_uchar(a, op, b) munit_assert_uchar(a, op, b) +#define assert_short(a, op, b) munit_assert_short(a, op, b) +#define assert_ushort(a, op, b) munit_assert_ushort(a, op, b) +#define assert_int(a, op, b) munit_assert_int(a, op, b) +#define assert_uint(a, op, b) munit_assert_uint(a, op, b) +#define assert_long(a, op, b) munit_assert_long(a, op, b) +#define assert_ulong(a, op, b) munit_assert_ulong(a, op, b) +#define assert_llong(a, op, b) munit_assert_llong(a, op, b) +#define assert_ullong(a, op, b) munit_assert_ullong(a, op, b) +#define assert_size(a, op, b) munit_assert_size(a, op, b) +#define assert_float(a, op, b) munit_assert_float(a, op, b) +#define assert_double(a, op, b) munit_assert_double(a, op, b) +#define assert_ptr(a, op, b) munit_assert_ptr(a, op, b) + +#define assert_int8(a, op, b) munit_assert_int8(a, op, b) +#define assert_uint8(a, op, b) munit_assert_uint8(a, op, b) +#define assert_int16(a, op, b) munit_assert_int16(a, op, b) +#define assert_uint16(a, op, b) munit_assert_uint16(a, op, b) +#define assert_int32(a, op, b) munit_assert_int32(a, op, b) +#define assert_uint32(a, op, b) munit_assert_uint32(a, op, b) +#define assert_int64(a, op, b) munit_assert_int64(a, op, b) +#define assert_uint64(a, op, b) munit_assert_uint64(a, op, b) + +#define assert_double_equal(a, b, precision) \ + munit_assert_double_equal(a, b, precision) +#define assert_string_equal(a, b) munit_assert_string_equal(a, b) +#define assert_string_not_equal(a, b) munit_assert_string_not_equal(a, b) +#define assert_memory_equal(size, a, b) munit_assert_memory_equal(size, a, b) +#define assert_memory_not_equal(size, a, b) \ + munit_assert_memory_not_equal(size, a, b) +#define assert_ptr_equal(a, b) munit_assert_ptr_equal(a, b) +#define assert_ptr_not_equal(a, b) munit_assert_ptr_not_equal(a, b) +#define assert_ptr_null(ptr) munit_assert_null_equal(ptr) +#define assert_ptr_not_null(ptr) munit_assert_not_null(ptr) + +#define assert_null(ptr) munit_assert_null(ptr) +#define assert_not_null(ptr) munit_assert_not_null(ptr) + +#endif /* defined(MUNIT_ENABLE_ASSERT_ALIASES) */ + +#if defined(__cplusplus) +} +#endif + +#endif /* !defined(MUNIT_H) */ + +#if defined(MUNIT_ENABLE_ASSERT_ALIASES) +#if defined(assert) +#undef assert +#endif +#define assert(expr) munit_assert(expr) +#endif diff --git a/tests/test.c b/tests/test.c new file mode 100644 index 0000000..e9a9e02 --- /dev/null +++ b/tests/test.c @@ -0,0 +1,249 @@ +/* + * smartmap is MIT-licensed, but for this file: + * + * To the extent possible under law, the author(s) of this file have + * waived all copyright and related or neighboring rights to this + * work. See for + * details. + */ + +#define MUNIT_NO_FORK (1) +#define MUNIT_ENABLE_ASSERT_ALIASES (1) + +#include +#include +#include +#include +#include +#include +#include + +#include "../include/sparsemap.h" +#include "munit.h" + +#if defined(_MSC_VER) +#pragma warning(disable : 4127) +#endif + +#include "common.c" + +struct user_data { +}; + +void +__populate_map(sparsemap_t *map, size_t size, size_t max_value) +{ + int array[size]; + + setup_test_array(array, size, max_value); + shuffle(array, size); + for (int i = 0; i < size; i++) { + sparsemap_set(map, array[i], true); + munit_assert_true(sparsemap_is_set(map, array[i])); + } +} + +static void * +test_api_setup(const MunitParameter params[], void *user_data) +{ + struct test_info *info = (struct test_info *)user_data; + (void)params; + sparsemap_t *map = munit_calloc(1, sizeof(sparsemap)); + assert_ptr_not_null(map); + return (void *)(uintptr_t)map; +} + +static void +test_api_tear_down(void *fixture) +{ + sparsemap_t *map = (sparsemap_t *)fixture; + assert_ptr_not_null(map); + free(map); +} + +static MunitResult +test_api_static_init(const MunitParameter params[], void *data) +{ + sparsemap_t a_map, *map = &a_map; + uint8_t buf[1024]; + + (void)params; + (void)data; + + assert_ptr_not_null(map); + sparsemap_init(map, buf, sizeof(buf) / sizeof(buf[0]), 0); + assert_ptr_equal(&buf, map->m_data); + assert_true(map->m_data_size == 1024); + assert_true(map->m_data_used == sizeof(uint32_t)); + + return MUNIT_OK; +} + +static void * +test_api_clear_setup(const MunitParameter params[], void *user_data) +{ + uint8_t *buf = munit_calloc(1024, sizeof(uint8_t)); + sparsemap_t *map = (sparsemap_t *)test_api_setup(params, user_data); + + sparsemap_init(map, buf, 1024, 0); + __populate_map(map, 1024, 3 * 1024); + + return (void *)map; +} +static void +test_api_clear_tear_down(void *fixture) +{ + sparsemap_t *map = (sparsemap_t *)fixture; + free(map->m_data); + test_api_tear_down(fixture); +} +static MunitResult +test_api_clear(const MunitParameter params[], void *data) +{ + sparsemap_t *map = (sparsemap_t *)data; + (void)params; + + assert_ptr_not_null(map); + + assert_true(map->m_data_size == 1024); + assert_true(map->m_data_used == 412); + + sparsemap_clear(map); + + assert_true(map->m_data_size == 1024); + assert_true(map->m_data_used == sizeof(uint32_t)); + + return MUNIT_OK; +} + +static void * +test_api_open_setup(const MunitParameter params[], void *user_data) +{ + uint8_t *buf = munit_calloc(1024, sizeof(uint8_t)); + sparsemap_t *map = (sparsemap_t *)test_api_setup(params, user_data); + + sparsemap_init(map, buf, 1024, 0); + __populate_map(map, 1024, 3 * 1024); + assert_true(map->m_data_used == 1024 + sizeof(uint32_t)); + + return (void *)map; +} +static void +test_api_open_tear_down(void *fixture) +{ + sparsemap_t *map = (sparsemap_t *)fixture; + free(map->m_data); + test_api_tear_down(fixture); +} +static MunitResult +test_api_open(const MunitParameter params[], void *data) +{ + sparsemap_t _sm, *sm = &_sm, *map = (sparsemap_t *)data; + (void)params; + + assert_ptr_not_null(map); + + sparsemap_open(sm, map->m_data, map->m_data_size); + assert_true(map->m_data_used == sm->m_data_used); + for (int i = 0; i < 3 * 1024; i++) { + assert_true(sparsemap_is_set(sm, i) == sparsemap_is_set(map, i)); + } + + return MUNIT_OK; +} + +static void * +test_api_set_data_size_setup(const MunitParameter params[], void *user_data) +{ + uint8_t *buf = munit_calloc(1024, sizeof(uint8_t)); + sparsemap_t *map = (sparsemap_t *)test_api_setup(params, user_data); + + sparsemap_init(map, buf, 1024, 0); + __populate_map(map, 1024, 3 * 1024); + + return (void *)map; +} +static void +test_api_set_data_size_tear_down(void *fixture) +{ + sparsemap_t *map = (sparsemap_t *)fixture; + free(map->m_data); + test_api_tear_down(fixture); +} +static MunitResult +test_api_set_data_size(const MunitParameter params[], void *data) +{ + sparsemap_t *map = (sparsemap_t *)data; + (void)params; + + assert_ptr_not_null(map); + assert_true(map->m_data_size == 1024); + assert_true(map->m_data_size == sparsemap_get_range_size(map)); + sparsemap_set_data_size(map, 512); + assert_true(map->m_data_size == 512); + assert_true(map->m_data_size == sparsemap_get_range_size(map)); + return MUNIT_OK; +} + +static void * +test_api_is_set_setup(const MunitParameter params[], void *user_data) +{ + uint8_t *buf = munit_calloc(1024, sizeof(uint8_t)); + sparsemap_t *map = (sparsemap_t *)test_api_setup(params, user_data); + + sparsemap_init(map, buf, 1024, 0); + __populate_map(map, 1024, 3 * 1024); + + return (void *)map; +} +static void +test_api_is_set_tear_down(void *fixture) +{ + sparsemap_t *map = (sparsemap_t *)fixture; + free(map->m_data); + test_api_tear_down(fixture); +} +static MunitResult +test_api_is_set(const MunitParameter params[], void *data) +{ + sparsemap_t *map = (sparsemap_t *)data; + (void)params; + + assert_ptr_not_null(map); + + sparsemap_set(map, 42, true); + assert_true(sparsemap_is_set(map, 42)); + + return MUNIT_OK; +} + + +static MunitTest api_test_suite[] = { + { (char *)"/api/static_init", test_api_static_init, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL }, + { (char *)"/api/clear", test_api_clear, test_api_clear_setup, + test_api_clear_tear_down, MUNIT_TEST_OPTION_NONE, NULL }, + { (char *)"/api/set_data_size", test_api_set_data_size, test_api_set_data_size_setup, + test_api_set_data_size_tear_down, MUNIT_TEST_OPTION_NONE, NULL }, + { (char *)"/api/is_set", test_api_is_set, test_api_is_set_setup, + test_api_is_set_tear_down, MUNIT_TEST_OPTION_NONE, NULL }, + { NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL } +}; + +static MunitTest scale_tests[] = { { NULL, NULL, NULL, NULL, + MUNIT_TEST_OPTION_NONE, NULL } }; + +static MunitSuite other_test_suite[] = { + { "/scale", scale_tests, NULL, 1, MUNIT_SUITE_OPTION_NONE }, + { NULL, NULL, NULL, 0, MUNIT_SUITE_OPTION_NONE } }; + +static const MunitSuite main_test_suite = { (char *)"/api", api_test_suite, + other_test_suite, 1, MUNIT_SUITE_OPTION_NONE }; + +int +main(int argc, char *argv[MUNIT_ARRAY_PARAM(argc + 1)]) +{ + struct user_data info; + return munit_suite_main(&main_test_suite, (void *)&info, argc, argv); +} + +/* ARGS: --no-fork --seed 8675309 */