diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d4dfc086..189a3a74 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -298,6 +298,7 @@ else() add_extra_test(upsert_alldups SOURCE extra/upsert_alldups.c) add_extra_test(dupfix_addodd SOURCE extra/dupfix_addodd.c) endif() + add_extra_test(details_rkl SOURCE extra/details_rkl.c) if(MDBX_BUILD_CXX) if(NOT WIN32 OR NOT MDBX_CXX_STANDARD LESS 17) add_extra_test(cursor_closing) diff --git a/test/extra/details_rkl.c b/test/extra/details_rkl.c new file mode 100644 index 00000000..49ef0428 --- /dev/null +++ b/test/extra/details_rkl.c @@ -0,0 +1,248 @@ +/// \copyright SPDX-License-Identifier: Apache-2.0 +/// \author Леонид Юрьев aka Leonid Yuriev \date 2025 + +#define debug_log debug_log_sub + +#include "../../src/rkl.c" +#include "../../src/txl.c" + +MDBX_MAYBE_UNUSED __cold void debug_log_sub(int level, const char *function, int line, const char *fmt, ...) { + (void)level; + (void)function; + (void)line; + (void)fmt; +} + +/*-----------------------------------------------------------------------------*/ + +static size_t tst_failed, tst_ok, tst_iterations, tst_cases; +#ifndef NDEBUG +static size_t tst_target; +#endif + +static bool check_bool(bool v, bool expect, const char *fn, unsigned line) { + if (unlikely(v != expect)) { + ++tst_failed; + fprintf(stderr, "iteration %zi: got %s, expected %s, at %s:%u\n", tst_iterations, v ? "true" : "false", + expect ? "true" : "false", fn, line); + fflush(nullptr); + return false; + } + ++tst_ok; + return true; +} + +static bool check_eq(uint64_t v, uint64_t expect, const char *fn, unsigned line) { + if (unlikely(v != expect)) { + ++tst_failed; + fprintf(stderr, "iteration %zi: %" PRIu64 " (got) != %" PRIu64 " (expected), at %s:%u\n", tst_iterations, v, expect, + fn, line); + fflush(nullptr); + return false; + } + ++tst_ok; + return true; +} + +#define CHECK_BOOL(T, EXPECT) check_bool((T), (EXPECT), __func__, __LINE__) +#define CHECK_TRUE(T) CHECK_BOOL(T, true) +#define CHECK_FALSE(T) CHECK_BOOL(T, false) +#define CHECK_EQ(T, EXPECT) check_eq((T), (EXPECT), __func__, __LINE__) + +void trivia(void) { + rkl_t x, y; + + rkl_init(&x); + rkl_init(&y); + CHECK_TRUE(rkl_check(&x)); + CHECK_TRUE(rkl_empty(&x)); + CHECK_EQ(rkl_len(&x), 0); + + rkl_iterator_t f, r; + rkl_iterator_init(&f, &x, false); + rkl_iterator_init(&r, &x, true); + CHECK_EQ(rkl_iterator_left(&f, &x, false), 0); + CHECK_EQ(rkl_iterator_left(&f, &x, true), 0); + CHECK_EQ(rkl_iterator_left(&r, &x, false), 0); + CHECK_EQ(rkl_iterator_left(&r, &x, true), 0); + CHECK_EQ(rkl_iterator_turn(&f, &x, false), 0); + CHECK_EQ(rkl_iterator_turn(&f, &x, true), 0); + CHECK_EQ(rkl_iterator_turn(&r, &x, false), 0); + CHECK_EQ(rkl_iterator_turn(&r, &x, true), 0); + CHECK_TRUE(rkl_check(&x)); + + CHECK_EQ(rkl_push(&x, 42, false), MDBX_SUCCESS); + CHECK_TRUE(rkl_check(&x)); + CHECK_FALSE(rkl_empty(&x)); + CHECK_EQ(rkl_len(&x), 1); + CHECK_TRUE(rkl_push(&x, 42, true) == MDBX_RESULT_TRUE); + CHECK_TRUE(rkl_check(&x)); + + rkl_iterator_init(&f, &x, false); + rkl_iterator_init(&r, &x, true); + CHECK_EQ(rkl_iterator_left(&f, &x, false), 1); + CHECK_EQ(rkl_iterator_left(&f, &x, true), 0); + CHECK_EQ(rkl_iterator_left(&r, &x, false), 0); + CHECK_EQ(rkl_iterator_left(&r, &x, true), 1); + + CHECK_EQ(rkl_iterator_turn(&f, &x, true), 0); + CHECK_EQ(rkl_iterator_turn(&f, &x, false), 42); + CHECK_EQ(rkl_iterator_turn(&f, &x, false), 0); + CHECK_EQ(rkl_iterator_turn(&f, &x, true), 42); + CHECK_EQ(rkl_iterator_turn(&f, &x, true), 0); + + CHECK_EQ(rkl_iterator_turn(&r, &x, false), 0); + CHECK_EQ(rkl_iterator_turn(&r, &x, true), 42); + CHECK_EQ(rkl_iterator_turn(&r, &x, true), 0); + CHECK_EQ(rkl_iterator_turn(&r, &x, false), 42); + CHECK_EQ(rkl_iterator_turn(&r, &x, false), 0); + + rkl_resize(&x, 222); + CHECK_FALSE(rkl_empty(&x)); + CHECK_TRUE(rkl_check(&x)); + + rkl_destructive_move(&x, &y); + CHECK_TRUE(rkl_check(&x)); + CHECK_TRUE(rkl_check(&y)); + rkl_destroy(&x); + rkl_destroy(&y); +} + +/*-----------------------------------------------------------------------------*/ + +uint64_t prng; + +static bool stochastic_pass(const unsigned start, const unsigned width, const unsigned n) { + rkl_t k, c; + txl_t l = txl_alloc(); + if (!CHECK_TRUE(l)) + return false; + + rkl_init(&k); + rkl_init(&c); + const size_t errors = tst_failed; + + rkl_iterator_t f, r; + rkl_iterator_init(&f, &k, false); + rkl_iterator_init(&r, &k, true); + + txnid_t lowest = UINT_MAX; + txnid_t highest = 0; + while (MDBX_PNL_GETSIZE(l) < n) { + prng = prng * UINT64_C(6364136223846793005) + 1; + txnid_t id = (txnid_t)(prng % width + start); + if (id < MIN_TXNID || id >= INVALID_TXNID) + continue; + if (txl_contain(l, id)) { + if (!CHECK_TRUE(rkl_contain(&k, id)) || !CHECK_TRUE(rkl_push(&k, id, false) == MDBX_RESULT_TRUE)) + break; + continue; + } + if (!CHECK_FALSE(rkl_contain(&k, id))) + break; + + if (tst_iterations % (1u << 24) == 0 && tst_iterations) { + printf("done %.3fM iteration, %zu cases\n", tst_iterations / 1000000.0, tst_cases); + fflush(nullptr); + } + tst_iterations += 1; + +#ifndef NDEBUG + if (tst_iterations == tst_target) { + printf("reach %zu iteration\n", tst_iterations); + fflush(nullptr); + } +#endif + + if (!CHECK_EQ(rkl_push(&k, id, false), MDBX_SUCCESS)) + break; + if (!CHECK_TRUE(rkl_check(&k))) + break; + if (!CHECK_EQ(txl_append(&l, id), MDBX_SUCCESS)) + break; + if (!CHECK_TRUE(rkl_contain(&k, id))) + break; + + lowest = (lowest < id) ? lowest : id; + highest = (highest > id) ? highest : id; + if (!CHECK_EQ(rkl_lowest(&k), lowest)) + break; + if (!CHECK_EQ(rkl_highest(&k), highest)) + break; + } + + txl_sort(l); + CHECK_EQ(rkl_len(&k), n); + CHECK_EQ(MDBX_PNL_GETSIZE(l), n); + + rkl_iterator_init(&f, &k, false); + rkl_iterator_init(&r, &k, true); + CHECK_EQ(rkl_iterator_left(&f, &k, false), n); + CHECK_EQ(rkl_iterator_left(&f, &k, true), 0); + CHECK_EQ(rkl_iterator_left(&r, &k, false), 0); + CHECK_EQ(rkl_iterator_left(&r, &k, true), n); + + for (size_t i = 0; i < n; ++i) { + CHECK_EQ(rkl_iterator_turn(&f, &k, false), l[n - i]); + CHECK_EQ(rkl_iterator_left(&f, &k, false), n - i - 1); + CHECK_EQ(rkl_iterator_left(&f, &k, true), i + 1); + + CHECK_EQ(rkl_iterator_turn(&r, &k, true), l[i + 1]); + r.pos += 1; + CHECK_EQ(rkl_iterator_turn(&r, &k, true), l[i + 1]); + CHECK_EQ(rkl_iterator_left(&r, &k, true), n - i - 1); + CHECK_EQ(rkl_iterator_left(&r, &k, false), i + 1); + } + + if (CHECK_EQ(rkl_copy(&k, &c), MDBX_SUCCESS)) { + for (size_t i = 1; i <= n; ++i) { + if (!CHECK_FALSE(rkl_empty(&k))) + break; + if (!CHECK_FALSE(rkl_empty(&c))) + break; + CHECK_EQ(rkl_pop(&k, true), l[i]); + CHECK_EQ(rkl_pop(&c, false), l[1 + n - i]); + } + } + + CHECK_TRUE(rkl_empty(&k)); + CHECK_TRUE(rkl_empty(&c)); + + rkl_destroy(&k); + rkl_destroy(&c); + txl_free(l); + + ++tst_cases; + return errors == tst_failed; +} + +static bool stochastic(const size_t limit_cases, const size_t limit_loops) { + for (unsigned loop = 0; tst_cases < limit_cases || loop < limit_loops; ++loop) + for (unsigned width = 2; width < 10; ++width) + for (unsigned n = 1; n < width; ++n) + for (unsigned prev = 1, start = 0, t; start < 4242; t = start + prev, prev = start, start = t) + if (!stochastic_pass(start, 1u << width, 1u << n) || tst_failed > 42) { + puts("bailout\n"); + return false; + } + return true; +} + +int main(int argc, const char *argv[]) { + (void)argc; + (void)argv; + +#ifndef NDEBUG + // tst_target = 281870; +#endif + prng = (uint64_t)time(nullptr); + printf("prng-seed %" PRIu64 "\n", prng); + fflush(nullptr); + + trivia(); + stochastic(42 * 42 * 42, 42); + printf("done: %zu cases, %zu iterations, %zu checks ok, %zu checks failed\n", tst_cases, tst_iterations, tst_ok, + tst_failed); + fflush(nullptr); + return tst_failed ? EXIT_FAILURE : EXIT_SUCCESS; +}