diff --git a/test/hill.cc b/test/hill.cc index 41002a14..f65ec060 100644 --- a/test/hill.cc +++ b/test/hill.cc @@ -14,13 +14,131 @@ #include "test.h" +int testcase_hill::insert(const keygen::buffer &akey, + const keygen::buffer &adata, unsigned flags) { + int err = mdbx_put(txn_guard.get(), dbi, &akey->value, &adata->value, flags); + if (err == MDBX_SUCCESS) { + const auto S_key = S(akey); + const auto S_data = S(adata); + const bool inserted = mirror.emplace(S_key, S_data).second; + assert(inserted); + (void)inserted; + } + return err; +} + +int testcase_hill::replace(const keygen::buffer &akey, + const keygen::buffer &new_data, + const keygen::buffer &old_data, unsigned flags) { + const auto S_key = S(akey); + const auto S_old = S(old_data); + const auto S_new = S(new_data); + const auto removed = mirror.erase(set::key_type(S_key, S_old)); + assert(removed == 1); + (void)removed; + const bool inserted = mirror.emplace(S_key, S_new).second; + assert(inserted); + (void)inserted; + return mdbx_replace(txn_guard.get(), dbi, &akey->value, &new_data->value, + &old_data->value, flags); +} + +int testcase_hill::remove(const keygen::buffer &akey, + const keygen::buffer &adata) { + const auto S_key = S(akey); + const auto S_data = S(adata); + const auto removed = mirror.erase(set::key_type(S_key, S_data)); + assert(removed == 1); + (void)removed; + return mdbx_del(txn_guard.get(), dbi, &akey->value, &adata->value); +} + +bool testcase_hill::verify() const { + char dump_key[128], dump_value[128]; + char dump_mkey[128], dump_mvalue[128]; + + MDBX_cursor *cursor; + int err = mdbx_cursor_open(txn_guard.get(), dbi, &cursor); + if (err != MDBX_SUCCESS) + failure_perror("mdbx_cursor_open()", err); + + bool rc = true; + MDBX_val akey, avalue; + MDBX_val mkey, mvalue; + err = mdbx_cursor_get(cursor, &akey, &avalue, MDBX_FIRST); + + assert(std::is_sorted(mirror.cbegin(), mirror.cend(), ItemCompare(this))); + auto it = mirror.cbegin(); + while (true) { + if (err != MDBX_SUCCESS) { + akey.iov_len = avalue.iov_len = 0; + akey.iov_base = avalue.iov_base = nullptr; + } + const auto S_key = S(akey); + const auto S_data = S(avalue); + if (it != mirror.cend()) { + mkey.iov_base = (void *)it->first.c_str(); + mkey.iov_len = it->first.size(); + mvalue.iov_base = (void *)it->second.c_str(); + mvalue.iov_len = it->second.size(); + } + if (err == MDBX_SUCCESS && it != mirror.cend() && S_key == it->first && + S_data == it->second) { + ++it; + err = mdbx_cursor_get(cursor, &akey, &avalue, MDBX_NEXT); + } else if (err == MDBX_SUCCESS && + (it == mirror.cend() || S_key < it->first || + (S_key == it->first && S_data < it->second))) { + if (it != mirror.cend()) { + log_error("extra pair: db{%s, %s} < mi{%s, %s}", + mdbx_dump_val(&akey, dump_key, sizeof(dump_key)), + mdbx_dump_val(&avalue, dump_value, sizeof(dump_value)), + mdbx_dump_val(&mkey, dump_mkey, sizeof(dump_mkey)), + mdbx_dump_val(&mvalue, dump_mvalue, sizeof(dump_mvalue))); + } else { + log_error("extra pair: db{%s, %s} < mi.END", + mdbx_dump_val(&akey, dump_key, sizeof(dump_key)), + mdbx_dump_val(&avalue, dump_value, sizeof(dump_value))); + } + err = mdbx_cursor_get(cursor, &akey, &avalue, MDBX_NEXT); + rc = false; + } else if (it != mirror.cend() && + (err == MDBX_NOTFOUND || S_key > it->first || + (S_key == it->first && S_data > it->second))) { + if (err == MDBX_NOTFOUND) { + log_error("lost pair: db.END > mi{%s, %s}", + mdbx_dump_val(&mkey, dump_mkey, sizeof(dump_mkey)), + mdbx_dump_val(&mvalue, dump_mvalue, sizeof(dump_mvalue))); + } else { + log_error("lost pair: db{%s, %s} > mi{%s, %s}", + mdbx_dump_val(&akey, dump_key, sizeof(dump_key)), + mdbx_dump_val(&avalue, dump_value, sizeof(dump_value)), + mdbx_dump_val(&mkey, dump_mkey, sizeof(dump_mkey)), + mdbx_dump_val(&mvalue, dump_mvalue, sizeof(dump_mvalue))); + } + ++it; + rc = false; + } else if (err == MDBX_NOTFOUND && it == mirror.cend()) { + break; + } else if (err != MDBX_SUCCESS) { + failure_perror("mdbx_cursor_get()", err); + } else { + assert(!"WTF?"); + } + } + + mdbx_cursor_close(cursor); + return rc; +} + bool testcase_hill::run() { - MDBX_dbi dbi; int err = db_open__begin__table_create_open_clean(dbi); if (unlikely(err != MDBX_SUCCESS)) { log_notice("hill: bailout-prepare due '%s'", mdbx_strerror(err)); return true; } + mirror.clear(); + mirror_commited.clear(); /* LY: тест "холмиком": * - сначала наполняем таблицу циклическими CRUD-манипуляциями, @@ -63,6 +181,7 @@ bool testcase_hill::run() { uint64_t commited_serial = serial_count; unsigned txn_nops = 0; + bool rc = false; while (should_continue()) { const keygen::serial_t a_serial = serial_count; if (unlikely(!keyvalue_maker.increment(serial_count, 1))) { @@ -78,53 +197,74 @@ bool testcase_hill::run() { log_trace("uphill: insert-a (age %" PRIu64 ") %" PRIu64, age_shift, a_serial); generate_pair(a_serial, a_key, a_data_1, age_shift); - err = mdbx_put(txn_guard.get(), dbi, &a_key->value, &a_data_1->value, - insert_flags); + + err = insert(a_key, a_data_1, insert_flags); if (unlikely(err != MDBX_SUCCESS)) { if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) { log_notice("uphill: bailout at insert-a due '%s'", mdbx_strerror(err)); txn_restart(true, false); serial_count = commited_serial; + mirror = mirror_commited; break; } failure_perror("mdbx_put(insert-a.1)", err); } + if (!verify()) { + log_notice("uphill: bailout after insert-a, before commit"); + goto bailout; + } if (++txn_nops >= config.params.batch_write) { err = breakable_restart(); if (unlikely(err != MDBX_SUCCESS)) { log_notice("uphill: bailout at commit due '%s'", mdbx_strerror(err)); serial_count = commited_serial; + mirror = mirror_commited; break; } + mirror_commited = mirror; commited_serial = a_serial; txn_nops = 0; + if (!verify()) { + log_notice("uphill: bailout after insert-a, after commit"); + goto bailout; + } } // создаем вторую запись из пары log_trace("uphill: insert-b %" PRIu64, b_serial); generate_pair(b_serial, b_key, b_data, 0); - err = mdbx_put(txn_guard.get(), dbi, &b_key->value, &b_data->value, - insert_flags); + err = insert(b_key, b_data, insert_flags); if (unlikely(err != MDBX_SUCCESS)) { if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) { log_notice("uphill: bailout at insert-b due '%s'", mdbx_strerror(err)); txn_restart(true, false); serial_count = commited_serial; + mirror = mirror_commited; break; } failure_perror("mdbx_put(insert-b)", err); } + if (!verify()) { + log_notice("uphill: bailout after insert-b, before commit"); + goto bailout; + } if (++txn_nops >= config.params.batch_write) { err = breakable_restart(); if (unlikely(err != MDBX_SUCCESS)) { log_notice("uphill: bailout at commit due '%s'", mdbx_strerror(err)); serial_count = commited_serial; + mirror = mirror_commited; break; } + mirror_commited = mirror; commited_serial = a_serial; txn_nops = 0; + if (!verify()) { + log_notice("uphill: bailout after insert-b, after commit"); + goto bailout; + } } // обновляем данные в первой записи @@ -132,52 +272,73 @@ bool testcase_hill::run() { a_serial); generate_pair(a_serial, a_key, a_data_0, 0); checkdata("uphill: update-a", dbi, a_key->value, a_data_1->value); - err = mdbx_replace(txn_guard.get(), dbi, &a_key->value, &a_data_0->value, - &a_data_1->value, update_flags); + err = replace(a_key, a_data_0, a_data_1, update_flags); if (unlikely(err != MDBX_SUCCESS)) { if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) { log_notice("uphill: bailout at update-a due '%s'", mdbx_strerror(err)); txn_restart(true, false); serial_count = commited_serial; + mirror = mirror_commited; break; } failure_perror("mdbx_replace(update-a: 1->0)", err); } + if (!verify()) { + log_notice("uphill: bailout after update-a, before commit"); + goto bailout; + } if (++txn_nops >= config.params.batch_write) { err = breakable_restart(); if (unlikely(err != MDBX_SUCCESS)) { log_notice("uphill: bailout at commit due '%s'", mdbx_strerror(err)); serial_count = commited_serial; + mirror = mirror_commited; break; } + mirror_commited = mirror; commited_serial = a_serial; txn_nops = 0; + if (!verify()) { + log_notice("uphill: bailout after update-a, after commit"); + goto bailout; + } } // удаляем вторую запись log_trace("uphill: delete-b %" PRIu64, b_serial); checkdata("uphill: delete-b", dbi, b_key->value, b_data->value); - err = mdbx_del(txn_guard.get(), dbi, &b_key->value, &b_data->value); + err = remove(b_key, b_data); if (unlikely(err != MDBX_SUCCESS)) { if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) { log_notice("uphill: bailout at delete-b due '%s'", mdbx_strerror(err)); txn_restart(true, false); serial_count = commited_serial; + mirror = mirror_commited; break; } failure_perror("mdbx_del(b)", err); } + if (!verify()) { + log_notice("uphill: bailout after delete-b, before commit"); + goto bailout; + } if (++txn_nops >= config.params.batch_write) { err = breakable_restart(); if (unlikely(err != MDBX_SUCCESS)) { log_notice("uphill: bailout at commit due '%s'", mdbx_strerror(err)); serial_count = commited_serial; + mirror = mirror_commited; break; } + mirror_commited = mirror; commited_serial = a_serial; txn_nops = 0; + if (!verify()) { + log_notice("uphill: bailout after delete-b, after commit"); + goto bailout; + } } report(1); @@ -204,101 +365,145 @@ bool testcase_hill::run() { generate_pair(a_serial, a_key, a_data_0, 0); generate_pair(a_serial, a_key, a_data_1, age_shift); checkdata("downhill: update-a", dbi, a_key->value, a_data_0->value); - err = mdbx_replace(txn_guard.get(), dbi, &a_key->value, &a_data_1->value, - &a_data_0->value, update_flags); + err = replace(a_key, a_data_1, a_data_0, update_flags); if (unlikely(err != MDBX_SUCCESS)) { if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) { log_notice("downhill: bailout at update-a due '%s'", mdbx_strerror(err)); txn_end(true); + mirror = mirror_commited; break; } failure_perror("mdbx_put(update-a: 0->1)", err); } + if (!verify()) { + log_notice("downhill: bailout after update-a, before commit"); + break; + } if (++txn_nops >= config.params.batch_write) { err = breakable_restart(); if (unlikely(err != MDBX_SUCCESS)) { log_notice("downhill: bailout at commit due '%s'", mdbx_strerror(err)); + mirror = mirror_commited; break; } + mirror_commited = mirror; txn_nops = 0; + if (!verify()) { + log_notice("downhill: bailout after update-a, after commit"); + break; + } } // создаем вторую запись из пары log_trace("downhill: insert-b %" PRIu64, b_serial); generate_pair(b_serial, b_key, b_data, 0); - err = mdbx_put(txn_guard.get(), dbi, &b_key->value, &b_data->value, - insert_flags); + err = insert(b_key, b_data, insert_flags); if (unlikely(err != MDBX_SUCCESS)) { if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) { log_notice("downhill: bailout at insert-a due '%s'", mdbx_strerror(err)); txn_end(true); + mirror = mirror_commited; break; } failure_perror("mdbx_put(insert-b)", err); } + if (!verify()) { + log_notice("downhill: bailout after insert-b, before commit"); + break; + } if (++txn_nops >= config.params.batch_write) { err = breakable_restart(); if (unlikely(err != MDBX_SUCCESS)) { log_notice("downhill: bailout at commit due '%s'", mdbx_strerror(err)); + mirror = mirror_commited; break; } + mirror_commited = mirror; txn_nops = 0; + if (!verify()) { + log_notice("downhill: bailout after insert-b, after commit"); + break; + } } // удаляем первую запись log_trace("downhill: delete-a (age %" PRIu64 ") %" PRIu64, age_shift, a_serial); checkdata("downhill: delete-a", dbi, a_key->value, a_data_1->value); - err = mdbx_del(txn_guard.get(), dbi, &a_key->value, &a_data_1->value); + err = remove(a_key, a_data_1); if (unlikely(err != MDBX_SUCCESS)) { if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) { log_notice("downhill: bailout at delete-a due '%s'", mdbx_strerror(err)); txn_end(true); + mirror = mirror_commited; break; } failure_perror("mdbx_del(a)", err); } + if (!verify()) { + log_notice("downhill: bailout after delete-a, before commit"); + break; + } if (++txn_nops >= config.params.batch_write) { err = breakable_restart(); if (unlikely(err != MDBX_SUCCESS)) { log_notice("downhill: bailout at commit due '%s'", mdbx_strerror(err)); + mirror = mirror_commited; break; } + mirror_commited = mirror; txn_nops = 0; + if (!verify()) { + log_notice("downhill: bailout after delete-a, after commit"); + break; + } } // удаляем вторую запись log_trace("downhill: delete-b %" PRIu64, b_serial); checkdata("downhill: delete-b", dbi, b_key->value, b_data->value); - err = mdbx_del(txn_guard.get(), dbi, &b_key->value, &b_data->value); + err = remove(b_key, b_data); if (unlikely(err != MDBX_SUCCESS)) { if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) { log_notice("downhill: bailout at delete-b due '%s'", mdbx_strerror(err)); txn_end(true); + mirror = mirror_commited; break; } failure_perror("mdbx_del(b)", err); } + if (!verify()) { + log_notice("downhill: bailout after delete-b, before commit"); + break; + } if (++txn_nops >= config.params.batch_write) { err = breakable_restart(); if (unlikely(err != MDBX_SUCCESS)) { log_notice("downhill: bailout at commit due '%s'", mdbx_strerror(err)); + mirror = mirror_commited; break; } + mirror_commited = mirror; txn_nops = 0; + if (!verify()) { + log_notice("downhill: bailout after delete-b, after commit"); + goto bailout; + } } report(1); } + rc = verify(); +bailout: if (txn_guard) { err = breakable_commit(); if (unlikely(err != MDBX_SUCCESS)) @@ -312,10 +517,10 @@ bool testcase_hill::run() { err = breakable_commit(); if (unlikely(err != MDBX_SUCCESS)) { log_notice("hill: bailout-clean due '%s'", mdbx_strerror(err)); - return true; + return rc; } } else db_table_close(dbi); } - return true; + return rc; } diff --git a/test/test.h b/test/test.h index 9cfbc6a5..795a9992 100644 --- a/test/test.h +++ b/test/test.h @@ -22,6 +22,21 @@ #include "osal.h" #include "utils.h" +#include + +#ifndef HAVE_cxx17_std_string_view +#if __cplusplus >= 201703L && __has_include() +#include +#define HAVE_cxx17_std_string_view 1 +#else +#define HAVE_cxx17_std_string_view 0 +#endif +#endif /* HAVE_cxx17_std_string_view */ + +#if HAVE_cxx17_std_string_view +#include +#endif + bool test_execute(const actor_config &config); std::string thunk_param(const actor_config &config); void testcase_setup(const char *casename, actor_params ¶ms, @@ -165,10 +180,59 @@ public: }; class testcase_hill : public testcase { + using inherited = testcase; + +#if HAVE_cxx17_std_string_view + using data_view = std::string_view; +#else + using data_view = std::string; +#endif + + MDBX_dbi dbi; + using Item = std::pair; + struct ItemCompare { + const testcase_hill *context; + ItemCompare(const testcase_hill *owner) : context(owner) {} + + bool operator()(const Item &a, const Item &b) const { + MDBX_val va, vb; + va.iov_base = (void *)a.first.data(); + va.iov_len = a.first.size(); + vb.iov_base = (void *)b.first.data(); + vb.iov_len = b.first.size(); + int cmp = mdbx_cmp(context->txn_guard.get(), context->dbi, &va, &vb); + if (cmp == 0 && + (context->config.params.table_flags & MDBX_DUPSORT) != 0) { + va.iov_base = (void *)a.second.data(); + va.iov_len = a.second.size(); + vb.iov_base = (void *)b.second.data(); + vb.iov_len = b.second.size(); + cmp = mdbx_dcmp(context->txn_guard.get(), context->dbi, &va, &vb); + } + return cmp < 0; + } + }; + + using set = std::set; + set mirror, mirror_commited; + + bool verify() const; + int insert(const keygen::buffer &akey, const keygen::buffer &adata, + unsigned flags); + int replace(const keygen::buffer &akey, const keygen::buffer &new_value, + const keygen::buffer &old_value, unsigned flags); + int remove(const keygen::buffer &akey, const keygen::buffer &adata); + + static inline data_view S(const MDBX_val &v) { + return data_view(static_cast(v.iov_base), v.iov_len); + } + static inline data_view S(const keygen::buffer &b) { return S(b->value); } + public: testcase_hill(const actor_config &config, const mdbx_pid_t pid) - : testcase(config, pid) {} - bool run(); + : testcase(config, pid), mirror(ItemCompare(this)), + mirror_commited(ItemCompare(this)) {} + bool run() override; }; class testcase_append : public testcase {