libmdbx/test/test.cc

467 lines
12 KiB
C++
Raw Normal View History

/*
* Copyright 2017 Leonid Yuriev <leo@yuriev.ru>
* and other libmdbx authors: please see AUTHORS file.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
#include "test.h"
const char *testcase2str(const actor_testcase testcase) {
switch (testcase) {
default:
2017-05-18 01:10:56 +08:00
assert(false);
return "?!";
case ac_none:
return "none";
case ac_hill:
return "hill";
case ac_deadread:
return "deadread";
case ac_deadwrite:
return "deadwrite";
case ac_jitter:
return "jitter";
}
}
const char *status2str(actor_status status) {
switch (status) {
default:
assert(false);
return "?!";
case as_debuging:
return "debuging";
case as_running:
return "running";
case as_successful:
return "successful";
case as_killed:
return "killed";
case as_failed:
return "failed";
}
}
2017-05-18 01:10:56 +08:00
const char *keygencase2str(const keygen_case keycase) {
switch (keycase) {
default:
assert(false);
return "?!";
case kc_random:
return "random";
case kc_dashes:
return "dashes";
case kc_custom:
return "custom";
}
}
//-----------------------------------------------------------------------------
static void mdbx_debug_logger(int type, const char *function, int line,
const char *msg, va_list args) {
logging::loglevel level = logging::info;
if (type & MDBX_DBG_EXTRA)
level = logging::extra;
if (type & MDBX_DBG_TRACE)
level = logging::trace;
if (type & MDBX_DBG_PRINT)
level = logging::verbose;
if (type & MDBX_DBG_ASSERT) {
log_error("mdbx: assertion failure: %s, %d",
function ? function : "unknown", line);
2017-04-11 17:55:16 +08:00
level = logging::failure;
}
2017-05-18 01:10:56 +08:00
if (logging::output(level, strncmp(function, "mdbx_", 5) == 0 ? "%s: "
: "mdbx: %s: ",
function))
logging::feed(msg, args);
if (type & MDBX_DBG_ASSERT)
abort();
}
2017-05-24 06:42:10 +08:00
int testcase::oom_callback(MDBX_env *env, int pid, mdbx_tid_t tid, uint64_t txn,
2017-05-24 03:08:18 +08:00
unsigned gap, int retry) {
testcase *self = (testcase *)mdbx_env_get_userctx(env);
if (retry == 0)
log_notice("oom_callback: waitfor pid %u, thread %" PRIuPTR
", txn #%" PRIu64 ", gap %d",
pid, (size_t)tid, txn, gap);
2017-05-25 14:51:26 +08:00
if (self->should_continue(true)) {
2017-05-25 00:22:48 +08:00
osal_yield();
2017-05-25 14:51:26 +08:00
if (retry > 0)
osal_udelay(retry * 100);
2017-05-24 03:08:18 +08:00
return 1 /* always retry */;
}
return -1;
}
void testcase::db_prepare() {
log_trace(">> db_prepare");
assert(!db_guard);
int mdbx_dbg_opts = MDBX_DBG_ASSERT;
if (config.params.loglevel <= logging::trace)
mdbx_dbg_opts |= MDBX_DBG_TRACE;
if (config.params.loglevel <= logging::verbose)
mdbx_dbg_opts |= MDBX_DBG_PRINT;
int rc = mdbx_setup_debug(mdbx_dbg_opts, mdbx_debug_logger, MDBX_DBG_DNT);
log_info("set mdbx debug-opts: 0x%02x", rc);
2017-05-24 06:42:10 +08:00
MDBX_env *env = nullptr;
rc = mdbx_env_create(&env);
2017-05-24 06:42:10 +08:00
if (unlikely(rc != MDBX_SUCCESS))
failure_perror("mdbx_env_create()", rc);
assert(env != nullptr);
db_guard.reset(env);
rc = mdbx_env_set_userctx(env, this);
2017-05-24 06:42:10 +08:00
if (unlikely(rc != MDBX_SUCCESS))
failure_perror("mdbx_env_set_userctx()", rc);
rc = mdbx_env_set_maxreaders(env, config.params.max_readers);
2017-05-24 06:42:10 +08:00
if (unlikely(rc != MDBX_SUCCESS))
failure_perror("mdbx_env_set_maxreaders()", rc);
rc = mdbx_env_set_maxdbs(env, config.params.max_tables);
2017-05-24 06:42:10 +08:00
if (unlikely(rc != MDBX_SUCCESS))
failure_perror("mdbx_env_set_maxdbs()", rc);
2017-05-24 03:08:18 +08:00
rc = mdbx_env_set_oomfunc(env, testcase::oom_callback);
2017-05-24 06:42:10 +08:00
if (unlikely(rc != MDBX_SUCCESS))
2017-05-24 03:08:18 +08:00
failure_perror("mdbx_env_set_oomfunc()", rc);
rc = mdbx_env_set_mapsize(env, (size_t)config.params.size);
2017-05-24 06:42:10 +08:00
if (unlikely(rc != MDBX_SUCCESS))
failure_perror("mdbx_env_set_mapsize()", rc);
log_trace("<< db_prepare");
}
void testcase::db_open() {
log_trace(">> db_open");
if (!db_guard)
db_prepare();
int rc = mdbx_env_open(db_guard.get(), config.params.pathname_db.c_str(),
(unsigned)config.params.mode_flags, 0640);
2017-05-24 06:42:10 +08:00
if (unlikely(rc != MDBX_SUCCESS))
failure_perror("mdbx_env_open()", rc);
log_trace("<< db_open");
}
void testcase::db_close() {
log_trace(">> db_close");
cursor_guard.reset();
txn_guard.reset();
db_guard.reset();
log_trace("<< db_close");
}
void testcase::txn_begin(bool readonly) {
log_trace(">> txn_begin(%s)", readonly ? "read-only" : "read-write");
assert(!txn_guard);
2017-05-24 02:36:09 +08:00
MDBX_txn *txn = nullptr;
int rc =
2017-05-24 06:42:10 +08:00
mdbx_txn_begin(db_guard.get(), nullptr, readonly ? MDBX_RDONLY : 0, &txn);
if (unlikely(rc != MDBX_SUCCESS))
failure_perror("mdbx_txn_begin()", rc);
txn_guard.reset(txn);
log_trace("<< txn_begin(%s)", readonly ? "read-only" : "read-write");
}
void testcase::txn_end(bool abort) {
log_trace(">> txn_end(%s)", abort ? "abort" : "commit");
assert(txn_guard);
2017-05-24 02:36:09 +08:00
MDBX_txn *txn = txn_guard.release();
if (abort) {
int rc = mdbx_txn_abort(txn);
2017-05-24 06:42:10 +08:00
if (unlikely(rc != MDBX_SUCCESS))
failure_perror("mdbx_txn_abort()", rc);
} else {
int rc = mdbx_txn_commit(txn);
2017-05-24 06:42:10 +08:00
if (unlikely(rc != MDBX_SUCCESS))
failure_perror("mdbx_txn_commit()", rc);
}
log_trace("<< txn_end(%s)", abort ? "abort" : "commit");
}
2017-05-18 01:10:56 +08:00
void testcase::txn_restart(bool abort, bool readonly) {
if (txn_guard)
txn_end(abort);
txn_begin(readonly);
}
bool testcase::wait4start() {
if (config.wait4id) {
log_trace(">> wait4start(%u)", config.wait4id);
assert(!global::singlemode);
int rc = osal_waitfor(config.wait4id);
if (rc) {
2017-04-11 17:55:16 +08:00
log_trace("<< wait4start(%u), failed %s", config.wait4id,
test_strerror(rc));
return false;
}
} else {
log_trace("== skip wait4start: not needed");
}
if (config.params.delaystart) {
int rc = osal_delay(config.params.delaystart);
if (rc) {
log_trace("<< delay(%u), failed %s", config.params.delaystart,
test_strerror(rc));
return false;
}
} else {
log_trace("== skip delay: not needed");
}
return true;
}
2017-05-24 07:16:25 +08:00
void testcase::kick_progress(bool active) const {
static chrono::time last;
chrono::time now = chrono::now_motonic();
if (active) {
static int last_point = -1;
int point = (now.fixedpoint >> 29) & 3;
if (point != last_point) {
last = now;
fprintf(stderr, "%c\b", "-\\|/"[last_point = point]);
fflush(stderr);
}
} else if (now.fixedpoint - last.fixedpoint >
chrono::from_seconds(2).fixedpoint) {
last = now;
fprintf(stderr, "%c\b", "@*"[now.utc & 1]);
fflush(stderr);
}
}
void testcase::report(size_t nops_done) {
2017-05-24 07:16:25 +08:00
assert(nops_done > 0);
if (!nops_done)
return;
nops_completed += nops_done;
log_verbose("== complete +%" PRIuPTR " iteration, total %" PRIuPTR " done",
nops_done, nops_completed);
2017-05-24 07:16:25 +08:00
if (global::config::progress_indicator)
kick_progress(true);
if (config.signal_nops && !signalled &&
config.signal_nops <= nops_completed) {
log_trace(">> signal(n-ops %" PRIuPTR ")", nops_completed);
if (!global::singlemode)
osal_broadcast(config.actor_id);
signalled = true;
log_trace("<< signal(n-ops %" PRIuPTR ")", nops_completed);
}
}
void testcase::signal() {
if (!signalled) {
log_trace(">> signal(forced)");
if (!global::singlemode)
osal_broadcast(config.actor_id);
signalled = true;
log_trace("<< signal(forced)");
}
}
bool testcase::setup() {
db_prepare();
if (!wait4start())
return false;
start_timestamp = chrono::now_motonic();
return true;
}
bool testcase::teardown() {
log_trace(">> testcase::teardown");
signal();
db_close();
log_trace("<< testcase::teardown");
return true;
}
2017-05-25 14:51:26 +08:00
bool testcase::should_continue(bool check_timeout_only) const {
bool result = true;
if (config.params.test_duration) {
chrono::time since;
since.fixedpoint =
chrono::now_motonic().fixedpoint - start_timestamp.fixedpoint;
if (since.seconds() >= config.params.test_duration)
result = false;
}
2017-05-25 14:51:26 +08:00
if (!check_timeout_only && config.params.test_nops &&
nops_completed >= config.params.test_nops)
result = false;
2017-05-24 07:16:25 +08:00
if (result && global::config::progress_indicator)
kick_progress(false);
return result;
}
void testcase::fetch_canary() {
mdbx_canary canary_now;
log_trace(">> fetch_canary");
int rc = mdbx_canary_get(txn_guard.get(), &canary_now);
2017-05-24 06:42:10 +08:00
if (unlikely(rc != MDBX_SUCCESS))
failure_perror("mdbx_canary_get()", rc);
if (canary_now.v < last.canary.v)
2017-04-26 23:15:09 +08:00
failure("fetch_canary: %" PRIu64 "(canary-now.v) < %" PRIu64
"(canary-last.v)",
canary_now.v, last.canary.v);
if (canary_now.y < last.canary.y)
failure("fetch_canary: %" PRIu64 "(canary-now.y) < %" PRIu64
"(canary-last.y)",
canary_now.y, last.canary.y);
last.canary = canary_now;
log_trace("<< fetch_canary: db-sequence %" PRIu64
", db-sequence.txnid %" PRIu64,
last.canary.y, last.canary.v);
}
void testcase::update_canary(uint64_t increment) {
mdbx_canary canary_now = last.canary;
log_trace(">> update_canary: sequence %" PRIu64 " += %" PRIu64, canary_now.y,
increment);
canary_now.y += increment;
int rc = mdbx_canary_put(txn_guard.get(), &canary_now);
2017-05-24 06:42:10 +08:00
if (unlikely(rc != MDBX_SUCCESS))
failure_perror("mdbx_canary_put()", rc);
2017-05-18 01:10:56 +08:00
log_trace("<< update_canary: sequence = %" PRIu64, canary_now.y);
}
2017-05-24 06:42:10 +08:00
MDBX_dbi testcase::db_table_open(bool create) {
2017-05-18 01:10:56 +08:00
log_trace(">> testcase::db_table_create");
char tablename_buf[16];
const char *tablename = nullptr;
if (config.space_id) {
int rc = snprintf(tablename_buf, sizeof(tablename_buf), "TBL%04u",
config.space_id);
if (rc < 4 || rc >= (int)sizeof(tablename_buf) - 1)
failure("snprintf(tablename): %d", rc);
tablename = tablename_buf;
}
log_verbose("use %s table", tablename ? tablename : "MAINDB");
2017-05-24 06:42:10 +08:00
MDBX_dbi handle = 0;
2017-05-18 01:10:56 +08:00
int rc = mdbx_dbi_open(txn_guard.get(), tablename,
2017-05-24 06:42:10 +08:00
(create ? MDBX_CREATE : 0) | config.params.table_flags,
2017-05-18 01:10:56 +08:00
&handle);
2017-05-24 06:42:10 +08:00
if (unlikely(rc != MDBX_SUCCESS))
2017-05-18 01:10:56 +08:00
failure_perror("mdbx_dbi_open()", rc);
log_trace("<< testcase::db_table_create, handle %u", handle);
return handle;
}
2017-05-24 06:42:10 +08:00
void testcase::db_table_drop(MDBX_dbi handle) {
2017-05-18 01:10:56 +08:00
log_trace(">> testcase::db_table_drop, handle %u", handle);
if (config.params.drop_table) {
int rc = mdbx_drop(txn_guard.get(), handle, true);
2017-05-24 06:42:10 +08:00
if (unlikely(rc != MDBX_SUCCESS))
2017-05-18 01:10:56 +08:00
failure_perror("mdbx_drop()", rc);
log_trace("<< testcase::db_table_drop");
} else {
log_trace("<< testcase::db_table_drop: not needed");
}
}
2017-05-24 06:42:10 +08:00
void testcase::db_table_close(MDBX_dbi handle) {
2017-05-18 01:10:56 +08:00
log_trace(">> testcase::db_table_close, handle %u", handle);
assert(!txn_guard);
int rc = mdbx_dbi_close(db_guard.get(), handle);
2017-05-24 06:42:10 +08:00
if (unlikely(rc != MDBX_SUCCESS))
2017-05-18 01:10:56 +08:00
failure_perror("mdbx_dbi_close()", rc);
log_trace("<< testcase::db_table_close");
}
//-----------------------------------------------------------------------------
bool test_execute(const actor_config &config) {
const mdbx_pid_t pid = osal_getpid();
if (global::singlemode) {
logging::setup(format("single_%s", testcase2str(config.testcase)));
} else {
logging::setup((logging::loglevel)config.params.loglevel,
format("child_%u.%u", config.actor_id, config.space_id));
log_trace(">> wait4barrier");
osal_wait4barrier();
log_trace("<< wait4barrier");
}
try {
std::unique_ptr<testcase> test;
switch (config.testcase) {
case ac_hill:
test.reset(new testcase_hill(config, pid));
break;
case ac_deadread:
test.reset(new testcase_deadread(config, pid));
break;
case ac_deadwrite:
test.reset(new testcase_deadwrite(config, pid));
break;
case ac_jitter:
test.reset(new testcase_jitter(config, pid));
break;
default:
test.reset(new testcase(config, pid));
break;
}
if (!test->setup())
log_notice("test setup failed");
else if (!test->run())
log_notice("test failed");
else if (!test->teardown())
log_notice("test teardown failed");
else {
log_info("test successed");
return true;
}
} catch (const std::exception &pipets) {
failure("***** Exception: %s *****", pipets.what());
}
return false;
}