diff --git a/Makefile b/Makefile index 730d855a..9d31aba8 100644 --- a/Makefile +++ b/Makefile @@ -66,7 +66,7 @@ clean: rm -rf $(TOOLS) test/test @* *.[ao] *.[ls]o *~ tmp.db/* *.gcov *.log *.err check: test/test - test/test --pathname=tmp.db --basic --dont-cleanup-after && ./mdbx_chk -vn tmp.db + test/test --pathname=tmp.db --dont-cleanup-after basic && ./mdbx_chk -vn tmp.db mdbx.o: $(MDBX_SRC) Makefile $(CC) $(CFLAGS) -c src/mdbx.c -o $@ diff --git a/test/cases.cc b/test/cases.cc index a24838c6..09da2103 100644 --- a/test/cases.cc +++ b/test/cases.cc @@ -23,8 +23,8 @@ void configure_actor(unsigned &lastid, const actor_testcase testcase, if (i->is_waitable(params.waitfor_nops)) { if (i->signal_nops && i->signal_nops != params.waitfor_nops) failure("Previous waitable actor (id=%u) already linked on %u-ops\n", - i->id, i->signal_nops); - wait4id = i->id; + i->actor_id, i->signal_nops); + wait4id = i->actor_id; i->signal_nops = params.waitfor_nops; break; } @@ -33,7 +33,7 @@ void configure_actor(unsigned &lastid, const actor_testcase testcase, failure("No previous waitable actor for %u-ops\n", params.waitfor_nops); } - unsigned long id = 0; + unsigned id = 0; if (!id_cstr || strcmp(id_cstr, "auto") == 0) id = lastid + 1; else { @@ -47,23 +47,26 @@ void configure_actor(unsigned &lastid, const actor_testcase testcase, } if (id < 1 || id > ACTOR_ID_MAX) - failure("Invalid actor-id %lu\n", id); + failure("Invalid actor-id %u\n", id); lastid = id; + log_trace("configure_actor: %u for %s", id, testcase2str(testcase)); global::actors.emplace_back(actor_config(testcase, params, id, wait4id)); global::databases.insert(params.pathname_db); } -bool testcase_setup(const char *casename, const actor_params ¶ms, +void testcase_setup(const char *casename, actor_params ¶ms, unsigned &lastid) { - log_notice("testcase_setup(%s): TODO", casename); - if (strcmp(casename, "basic") == 0) { + log_notice(">>> testcase_setup(%s)", casename); configure_actor(lastid, ac_hill, nullptr, params); - return true; + configure_actor(lastid, ac_jitter, nullptr, params); + configure_actor(lastid, ac_jitter, nullptr, params); + configure_actor(lastid, ac_jitter, nullptr, params); + log_notice("<<< testcase_setup(%s): done", casename); + } else { + failure("unknown testcase `%s`", casename); } - - return false; } /* TODO */ diff --git a/test/config.cc b/test/config.cc index b1f73368..92e078b2 100644 --- a/test/config.cc +++ b/test/config.cc @@ -32,26 +32,23 @@ bool parse_option(int argc, char *const argv[], int &narg, const char *option, if (!value) { if (current[optlen + 2] == '=') failure("Option '--%s' doen't accept any value\n", option); - narg += 1; return true; } *value = nullptr; if (current[optlen + 2] == '=') { *value = ¤t[optlen + 3]; - narg += 1; return true; } - if (narg + 1 < argc && strncmp("--", argv[narg + 1], 2)) { + if (narg + 1 < argc && strncmp("--", argv[narg + 1], 2) != 0) { *value = argv[narg + 1]; - narg += 2; + ++narg; return true; } if (default_value) { *value = default_value; - narg += 1; return true; } @@ -184,11 +181,16 @@ bool parse_option(int argc, char *const argv[], int &narg, const char *option, const char *value_cstr = NULL; if (!parse_option(argc, argv, narg, option, &value_cstr, "yes")) { const char *current = argv[narg]; - if (strncmp(current, "--no-", 5) || strcmp(current + 5, option)) - return false; - value = false; - narg += 1; - return true; + if (strncmp(current, "--no-", 5) == 0 && strcmp(current + 5, option) == 0) { + value = false; + return true; + } + if (strncmp(current, "--dont-", 7) == 0 && + strcmp(current + 7, option) == 0) { + value = false; + return true; + } + return false; } if (!value_cstr) { @@ -266,8 +268,8 @@ void dump(const char *title) { logging::local_suffix indent(title); for (auto i = global::actors.begin(); i != global::actors.end(); ++i) { - log_info("#%u, testcase %s, id/table %u\n", i->order, - testcase2str(i->testcase), i->id); + log_info("#%u, testcase %s, space_id/table %u\n", i->actor_id, + testcase2str(i->testcase), i->space_id); indent.push(); if (i->params.loglevel) { @@ -284,8 +286,8 @@ void dump(const char *title) { log_info("seed %u\n", i->params.seed); - if (i->params.test_nrecords) - log_info("records %u\n", i->params.test_nrecords); + if (i->params.test_nops) + log_info("iterations/records %u\n", i->params.test_nops); else dump_duration("duration", i->params.test_duration); @@ -319,10 +321,10 @@ void dump(const char *title) { indent.pop(); } - dump_duration("timeout", global::config::timeout); + dump_duration("timeout", global::config::timeout_duration_seconds); log_info("cleanup: before %s, after %s\n", - global::config::dont_cleanup_before ? "No" : "Yes", - global::config::dont_cleanup_after ? "No" : "Yes"); + global::config::cleanup_before ? "Yes" : "No", + global::config::cleanup_after ? "Yes" : "No"); } } /* namespace config */ @@ -332,10 +334,10 @@ void dump(const char *title) { using namespace config; actor_config::actor_config(actor_testcase testcase, const actor_params ¶ms, - unsigned id, unsigned wait4id) + unsigned space_id, unsigned wait4id) : params(params) { - this->id = id; - this->order = (unsigned)global::actors.size(); + this->space_id = space_id; + this->actor_id = 1 + (unsigned)global::actors.size(); this->testcase = testcase; this->wait4id = wait4id; signal_nops = 0; diff --git a/test/config.h b/test/config.h index 38bc0b22..c0a04f93 100644 --- a/test/config.h +++ b/test/config.h @@ -78,7 +78,7 @@ struct actor_params_pod { unsigned seed; unsigned test_duration; - unsigned test_nrecords; + unsigned test_nops; unsigned nrepeat; unsigned nthreads; @@ -98,7 +98,7 @@ struct actor_params_pod { }; struct actor_config_pod { - unsigned id, order; + unsigned actor_id, space_id; actor_testcase testcase; unsigned wait4id; unsigned signal_nops; @@ -123,8 +123,8 @@ struct actor_config : public config::actor_config_pod { bool wanna_event4signalling() const { return true /* TODO ? */; } - actor_config(actor_testcase testcase, const actor_params ¶ms, unsigned id, - unsigned wait4id); + actor_config(actor_testcase testcase, const actor_params ¶ms, + unsigned space_id, unsigned wait4id); actor_config(const char *str) { if (!deserialize(str, *this)) @@ -140,7 +140,7 @@ struct actor_config : public config::actor_config_pod { bool is_waitable(size_t nops) const { switch (testcase) { case ac_hill: - if (!params.test_nrecords || params.test_nrecords >= nops) + if (!params.test_nops || params.test_nops >= nops) return true; default: return false; diff --git a/test/dead.cc b/test/dead.cc index 7afa042d..f713b8b3 100644 --- a/test/dead.cc +++ b/test/dead.cc @@ -24,7 +24,8 @@ bool testcase_deadread::setup() { } bool testcase_deadread::run() { - /* TODO */ + db_open(); + txn_begin(true); return true; } @@ -33,7 +34,7 @@ bool testcase_deadread::teardown() { cursor_guard.release(); txn_guard.release(); db_guard.release(); - return true; + return inherited::teardown(); } //----------------------------------------------------------------------------- @@ -48,7 +49,8 @@ bool testcase_deadwrite::setup() { } bool testcase_deadwrite::run() { - /* TODO */ + db_open(); + txn_begin(false); return true; } @@ -57,5 +59,5 @@ bool testcase_deadwrite::teardown() { cursor_guard.release(); txn_guard.release(); db_guard.release(); - return true; + return inherited::teardown(); } diff --git a/test/hill.cc b/test/hill.cc index 98a7b82c..0a7d2fd7 100644 --- a/test/hill.cc +++ b/test/hill.cc @@ -26,7 +26,7 @@ bool testcase_hill::setup() { } bool testcase_hill::run() { - mdbx_open(); + db_open(); /* TODO */ return true; } diff --git a/test/jitter.cc b/test/jitter.cc index 00385362..4129de75 100644 --- a/test/jitter.cc +++ b/test/jitter.cc @@ -19,13 +19,47 @@ bool testcase_jitter::setup() { if (!inherited::setup()) return false; - /* TODO */ - log_trace("<< setup"); return true; } -bool testcase_jitter::run() { return true; } +bool testcase_jitter::run() { + while (should_continue()) { + jitter_delay(); + db_open(); + + if (flipcoin()) { + jitter_delay(); + txn_begin(true); + jitter_delay(); + txn_end(false); + } + + jitter_delay(); + txn_begin(mode_readonly()); + jitter_delay(); + if (!mode_readonly()) { + /* TODO: + * - db_sequence() + * - db_setsize() + * ... + */ + } + txn_end(false); + + if (flipcoin()) { + jitter_delay(); + txn_begin(true); + jitter_delay(); + txn_end(false); + } + + jitter_delay(); + db_close(); + report(1); + } + return true; +} bool testcase_jitter::teardown() { log_trace(">> teardown"); diff --git a/test/main.cc b/test/main.cc index 8853ea2f..385b5050 100644 --- a/test/main.cc +++ b/test/main.cc @@ -44,7 +44,7 @@ void actor_params::set_defaults(void) { seed = 1; test_duration = 0; - test_nrecords = 1000; + test_nops = 1000; nrepeat = 1; nthreads = 1; @@ -63,6 +63,11 @@ void actor_params::set_defaults(void) { max_readers = 42; max_tables = 42; + + global::config::timeout_duration_seconds = 0 /* infinite */; + global::config::dump_config = true; + global::config::cleanup_before = true; + global::config::cleanup_after = true; } namespace global { @@ -72,12 +77,15 @@ std::unordered_map events; std::unordered_map pid2actor; std::set databases; unsigned nactors; +chrono::time start_motonic; +chrono::time deadline_motonic; +bool singlemode; namespace config { -unsigned timeout; +unsigned timeout_duration_seconds; bool dump_config; -bool dont_cleanup_before; -bool dont_cleanup_after; +bool cleanup_before; +bool cleanup_after; } /* namespace config */ } /* namespace global */ @@ -121,122 +129,154 @@ int main(int argc, char *const argv[]) { logging::setup((logging::loglevel)params.loglevel, "main"); unsigned lastid = 0; - if (argc == 2 && strncmp(argv[1], "--case=", 7) == 0) { - const char *casename = argv[1] + 7; - if (!testcase_setup(casename, params, lastid)) - failure("unknown testcase `%s`", casename); - } else { - for (int i = 1; i < argc;) { - const char *value = nullptr; - if (config::parse_option(argc, argv, i, "basic", nullptr)) { - bool ok = testcase_setup("basic", params, lastid); - assert(ok); - (void)ok; - } else if (config::parse_option(argc, argv, i, "race", nullptr)) { - bool ok = testcase_setup("race", params, lastid); - assert(ok); - (void)ok; - } else if (config::parse_option(argc, argv, i, "bench", nullptr)) { - bool ok = testcase_setup("bench", params, lastid); - assert(ok); - (void)ok; - } else if (config::parse_option(argc, argv, i, "pathname", - params.pathname_db) || - config::parse_option(argc, argv, i, "mode", params.mode_flags, - config::mode_bits) || - config::parse_option(argc, argv, i, "table", - params.table_flags, config::table_bits) || - config::parse_option(argc, argv, i, "size", params.size, - config::binary, 4096 * 4) || - config::parse_option(argc, argv, i, "seed", params.seed, - config::no_scale) || - config::parse_option(argc, argv, i, "repeat", params.nrepeat, - config::no_scale) || - config::parse_option(argc, argv, i, "threads", params.nthreads, - config::no_scale, 1, 64) || - config::parse_option(argc, argv, i, "timeout", - global::config::timeout, config::duration, - 1) || - config::parse_option(argc, argv, i, "keylen.min", - params.keylen_min, config::no_scale, 0, - params.keylen_max) || - config::parse_option(argc, argv, i, "keylen.max", - params.keylen_max, config::no_scale, - params.keylen_min, - mdbx_get_maxkeysize(0)) || - config::parse_option(argc, argv, i, "datalen.min", - params.datalen_min, config::no_scale, 0, - params.datalen_max) || - config::parse_option(argc, argv, i, "datalen.max", - params.datalen_max, config::no_scale, - params.datalen_min, MDBX_MAXDATASIZE) || - config::parse_option(argc, argv, i, "batch.read", - params.batch_read, config::no_scale, 1) || - config::parse_option(argc, argv, i, "batch.write", - params.batch_write, config::no_scale, - 1) || - config::parse_option(argc, argv, i, "delay", params.delaystart, - config::duration) || - config::parse_option(argc, argv, i, "wait4ops", - params.waitfor_nops, config::decimal) || - config::parse_option(argc, argv, i, "drop", - params.drop_table) || - config::parse_option(argc, argv, i, "dump-config", - global::config::dump_config) || - config::parse_option(argc, argv, i, "dont-cleanup-before", - global::config::dont_cleanup_before) || - config::parse_option(argc, argv, i, "dont-cleanup-after", - global::config::dont_cleanup_after) || - config::parse_option(argc, argv, i, "max-readers", - params.max_readers, config::no_scale, 1, - 255) || - config::parse_option(argc, argv, i, "max-tables", - params.max_tables, config::no_scale, 1, - INT16_MAX) || - false) { - continue; - } else if (config::parse_option(argc, argv, i, "no-delay", nullptr)) { - params.delaystart = 0; - } else if (config::parse_option(argc, argv, i, "no-wait", nullptr)) { - params.waitfor_nops = 0; - } else if (config::parse_option(argc, argv, i, "duration", - params.test_duration, config::duration, - 1)) { - params.test_nrecords = 0; - continue; - } else if (config::parse_option(argc, argv, i, "records", - params.test_nrecords, config::decimal, - 1)) { - params.test_duration = 0; - continue; - } else if (config::parse_option(argc, argv, i, "hill", &value)) { - configure_actor(lastid, ac_hill, value, params); - continue; - } else if (config::parse_option(argc, argv, i, "jitter", nullptr)) { - configure_actor(lastid, ac_jitter, value, params); - continue; - } else if (config::parse_option(argc, argv, i, "dead.reader", nullptr)) { - configure_actor(lastid, ac_deadread, value, params); - continue; - } else if (config::parse_option(argc, argv, i, "dead.writer", nullptr)) { - configure_actor(lastid, ac_deadwrite, value, params); - continue; - } else { - failure("Unknown option '%s'\n", argv[i]); - } + for (int narg = 1; narg < argc; ++narg) { + const char *value = nullptr; + + if (config::parse_option(argc, argv, narg, "case", &value)) { + testcase_setup(value, params, lastid); + continue; } + if (config::parse_option(argc, argv, narg, "pathname", params.pathname_db)) + continue; + if (config::parse_option(argc, argv, narg, "mode", params.mode_flags, + config::mode_bits)) + continue; + if (config::parse_option(argc, argv, narg, "table", params.table_flags, + config::table_bits)) + continue; + if (config::parse_option(argc, argv, narg, "size", params.size, + config::binary, 4096 * 4)) + continue; + if (config::parse_option(argc, argv, narg, "seed", params.seed, + config::no_scale)) + continue; + if (config::parse_option(argc, argv, narg, "repeat", params.nrepeat, + config::no_scale)) + continue; + if (config::parse_option(argc, argv, narg, "threads", params.nthreads, + config::no_scale, 1, 64)) + continue; + if (config::parse_option(argc, argv, narg, "timeout", + global::config::timeout_duration_seconds, + config::duration, 1)) + continue; + if (config::parse_option(argc, argv, narg, "keylen.min", params.keylen_min, + config::no_scale, 0, params.keylen_max)) + continue; + if (config::parse_option(argc, argv, narg, "keylen.max", params.keylen_max, + config::no_scale, params.keylen_min, + mdbx_get_maxkeysize(0))) + continue; + if (config::parse_option(argc, argv, narg, "datalen.min", + params.datalen_min, config::no_scale, 0, + params.datalen_max)) + continue; + if (config::parse_option(argc, argv, narg, "datalen.max", + params.datalen_max, config::no_scale, + params.datalen_min, MDBX_MAXDATASIZE)) + continue; + if (config::parse_option(argc, argv, narg, "batch.read", params.batch_read, + config::no_scale, 1)) + continue; + if (config::parse_option(argc, argv, narg, "batch.write", + params.batch_write, config::no_scale, 1)) + continue; + if (config::parse_option(argc, argv, narg, "delay", params.delaystart, + config::duration)) + continue; + if (config::parse_option(argc, argv, narg, "wait4ops", params.waitfor_nops, + config::decimal)) + continue; + if (config::parse_option(argc, argv, narg, "drop", params.drop_table)) + continue; + if (config::parse_option(argc, argv, narg, "dump-config", + global::config::dump_config)) + continue; + if (config::parse_option(argc, argv, narg, "cleanup-before", + global::config::cleanup_before)) + continue; + if (config::parse_option(argc, argv, narg, "cleanup-after", + global::config::cleanup_after)) + continue; + if (config::parse_option(argc, argv, narg, "max-readers", + params.max_readers, config::no_scale, 1, 255)) + continue; + if (config::parse_option(argc, argv, narg, "max-tables", params.max_tables, + config::no_scale, 1, INT16_MAX)) + continue; + + if (config::parse_option(argc, argv, narg, "no-delay", nullptr)) { + params.delaystart = 0; + continue; + } + if (config::parse_option(argc, argv, narg, "no-wait", nullptr)) { + params.waitfor_nops = 0; + continue; + } + if (config::parse_option(argc, argv, narg, "duration", params.test_duration, + config::duration, 1)) { + params.test_nops = 0; + continue; + } + if (config::parse_option(argc, argv, narg, "nops", params.test_nops, + config::decimal, 1)) { + params.test_duration = 0; + continue; + } + if (config::parse_option(argc, argv, narg, "hill", &value)) { + configure_actor(lastid, ac_hill, value, params); + continue; + } + if (config::parse_option(argc, argv, narg, "jitter", nullptr)) { + configure_actor(lastid, ac_jitter, value, params); + continue; + } + if (config::parse_option(argc, argv, narg, "dead.reader", nullptr)) { + configure_actor(lastid, ac_deadread, value, params); + continue; + } + if (config::parse_option(argc, argv, narg, "dead.writer", nullptr)) { + configure_actor(lastid, ac_deadwrite, value, params); + continue; + } + + if (*argv[narg] != '-') + testcase_setup(argv[narg], params, lastid); + else + failure("Unknown option '%s'\n", argv[narg]); } if (global::config::dump_config) config::dump(); + //-------------------------------------------------------------------------- + + if (global::actors.empty()) { + log_notice("no testcase(s) configured, exiting"); + return EXIT_SUCCESS; + } + bool failed = false; - if (global::actors.size()) { + global::start_motonic = chrono::now_motonic(); + global::deadline_motonic.fixedpoint = + (global::config::timeout_duration_seconds == 0) + ? chrono::infinite().fixedpoint + : global::start_motonic.fixedpoint + + chrono::from_seconds(global::config::timeout_duration_seconds) + .fixedpoint; + + if (global::config::cleanup_before) + cleanup(); + + if (global::actors.size() == 1) { + logging::setup("main"); + global::singlemode = true; + if (!test_execute(global::actors.front())) + failed = true; + } else { logging::setup("overlord"); - if (!global::config::dont_cleanup_before) - cleanup(); - + log_trace("=== preparing..."); log_trace(">> osal_setup"); osal_setup(global::actors); log_trace("<< osal_setup"); @@ -250,58 +290,60 @@ int main(int argc, char *const argv[]) { log_trace(">> killall_actors"); osal_killall_actors(); log_trace("<< killall_actors"); - failure("Failed to start actor #%u (%s)\n", a.order, test_strerror(rc)); + failure("Failed to start actor #%u (%s)\n", a.actor_id, + test_strerror(rc)); } global::pid2actor[pid] = &a; } + log_trace("=== ready to start..."); atexit(osal_killall_actors); log_trace(">> wait4barrier"); osal_wait4barrier(); log_trace("<< wait4barrier"); - } - time_t timestamp_start = time(nullptr); - size_t left = global::actors.size(); - - while (left > 0) { - unsigned timeout = INT_MAX; - if (global::config::timeout) { - time_t timestamp_now = time(nullptr); - if (timestamp_now - timestamp_start > global::config::timeout) - timeout = 0; - else - timeout = global::config::timeout - - (unsigned)(timestamp_now - timestamp_start); - } - - mdbx_pid_t pid; - int rc = osal_actor_poll(pid, timeout); - if (rc) - failure("Poll error: %s (%d)\n", test_strerror(rc), rc); - - if (pid) { - actor_status status = osal_actor_info(pid); - actor_config *actor = global::pid2actor.at(pid); - if (!actor) - continue; - - log_info("actor #%u, id %d, pid %u: %s\n", actor->order, actor->id, pid, - status2str(status)); - if (status > as_running) { - left -= 1; - if (status != as_successful) - failed = true; + size_t left = global::actors.size(); + log_trace("=== polling..."); + while (left > 0) { + unsigned timeout_seconds_left = INT_MAX; + chrono::time now_motonic = chrono::now_motonic(); + if (now_motonic.fixedpoint >= global::deadline_motonic.fixedpoint) + timeout_seconds_left = 0; + else { + chrono::time left_motonic; + left_motonic.fixedpoint = + global::deadline_motonic.fixedpoint - now_motonic.fixedpoint; + timeout_seconds_left = left_motonic.seconds(); + } + + mdbx_pid_t pid; + int rc = osal_actor_poll(pid, timeout_seconds_left); + if (rc) + failure("Poll error: %s (%d)\n", test_strerror(rc), rc); + + if (pid) { + actor_status status = osal_actor_info(pid); + actor_config *actor = global::pid2actor.at(pid); + if (!actor) + continue; + + log_info("actor #%u, id %d, pid %u: %s\n", actor->actor_id, + actor->space_id, pid, status2str(status)); + if (status > as_running) { + left -= 1; + if (status != as_successful) + failed = true; + } + } else { + if (timeout_seconds_left == 0) + failure("Timeout\n"); } - } else { - if (global::config::timeout && - time(nullptr) - timestamp_start > global::config::timeout) - failure("Timeout\n"); } + log_trace("=== done..."); } - log_notice("OVERALL: %s\n", failed ? "Failed" : "Successful"); - if (!global::config::dont_cleanup_before) { + log_notice("RESULT: %s\n", failed ? "Failed" : "Successful"); + if (global::config::cleanup_before) { if (failed) log_info("skip cleanup"); else diff --git a/test/osal-unix.cc b/test/osal-unix.cc index 5e475705..2ab3a7aa 100644 --- a/test/osal-unix.cc +++ b/test/osal-unix.cc @@ -24,11 +24,11 @@ struct shared_t { pthread_barrier_t barrier; pthread_mutex_t mutex; - pthread_cond_t conds[0]; + size_t conds_size; + pthread_cond_t conds[1]; }; static shared_t *shared; -static std::unordered_map events; void osal_wait4barrier(void) { assert(shared != nullptr && shared != MAP_FAILED); @@ -65,13 +65,8 @@ void osal_setup(const std::vector &actors) { if (rc) failure_perror("pthread_condattr_setpshared()", rc); - size_t n = 0; - for (const auto &a : actors) - if (a.wanna_event4signalling()) - ++n; - shared = (shared_t *)mmap( - nullptr, sizeof(shared_t) + n * sizeof(pthread_cond_t), + nullptr, sizeof(shared_t) + actors.size() * sizeof(pthread_cond_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); if (MAP_FAILED == (void *)shared) failure_perror("mmap(shared_conds)", errno); @@ -84,24 +79,15 @@ void osal_setup(const std::vector &actors) { if (rc) failure_perror("pthread_barrier_init(shared)", rc); - auto a = actors.begin(); + const size_t n = actors.size() + 1; for (size_t i = 0; i < n; ++i) { pthread_cond_t *event = &shared->conds[i]; rc = pthread_cond_init(event, &condattr); if (rc) failure_perror("pthread_cond_init(shared)", rc); - - unsigned id = 0; - while (a != actors.end()) { - if (a->wanna_event4signalling()) { - id = a->id; - break; - } - ++a; - } - assert(id != 0); - events[id] = event; + log_trace("osal_setup: event(shared pthread_cond) %zu -> %p", i, event); } + shared->conds_size = actors.size() + 1; pthread_barrierattr_destroy(&barrierattr); pthread_condattr_destroy(&condattr); @@ -110,7 +96,10 @@ void osal_setup(const std::vector &actors) { void osal_broadcast(unsigned id) { assert(shared != nullptr && shared != MAP_FAILED); - int rc = pthread_cond_broadcast(events.at(id)); + log_trace("osal_broadcast: event %u", id); + if (id >= shared->conds_size) + failure("osal_broadcast: id > limit"); + int rc = pthread_cond_broadcast(shared->conds + id); if (rc) failure_perror("sem_post(shared)", rc); } @@ -118,11 +107,15 @@ void osal_broadcast(unsigned id) { int osal_waitfor(unsigned id) { assert(shared != nullptr && shared != MAP_FAILED); + log_trace("osal_waitfor: event %u", id); + if (id >= shared->conds_size) + failure("osal_waitfor: id > limit"); + int rc = pthread_mutex_lock(&shared->mutex); if (rc != 0) failure_perror("pthread_mutex_lock(shared)", rc); - rc = pthread_cond_wait(events.at(id), &shared->mutex); + rc = pthread_cond_wait(shared->conds + id, &shared->mutex); if (rc && rc != EINTR) failure_perror("pthread_cond_wait(shared)", rc); @@ -173,6 +166,7 @@ int osal_actor_start(const actor_config &config, mdbx_pid_t &pid) { if (pid < 0) return errno; + log_trace("osal_actor_start: fork pid %i for %u", pid, config.actor_id); childs[pid] = as_running; return 0; } diff --git a/test/osal-windows.cc b/test/osal-windows.cc index fc3445cc..8a93b247 100644 --- a/test/osal-windows.cc +++ b/test/osal-windows.cc @@ -14,7 +14,7 @@ #include "test.h" -static std::unordered_map events; +static std::vector events; static HANDLE hBarrierSemaphore, hBarrierEvent; static int waitstatus2errcode(DWORD result) { @@ -63,15 +63,17 @@ static HANDLE make_inharitable(HANDLE hHandle) { } void osal_setup(const std::vector &actors) { - size_t n = 0; - for (const auto &a : actors) { - if (a.wanna_event4signalling()) { - HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - if (!hEvent) - failure_perror("CreateEvent()", GetLastError()); - hEvent = make_inharitable(hEvent); - events[a.id] = hEvent; - } + assert(events.empty()); + const size_t n = actors.size() + 1; + events.reserve(n); + + for (size_t i = 0; i < n; ++i) { + HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!hEvent) + failure_perror("CreateEvent()", GetLastError()); + hEvent = make_inharitable(hEvent); + log_trace("osal_setup: event %zu -> %p", i, hEvent); + events.push_back(hEvent); } hBarrierSemaphore = CreateSemaphore(NULL, 0, (LONG)actors.size(), NULL); @@ -86,11 +88,13 @@ void osal_setup(const std::vector &actors) { } void osal_broadcast(unsigned id) { + log_trace("osal_broadcast: event %u", id); if (!SetEvent(events.at(id))) failure_perror("SetEvent()", GetLastError()); } int osal_waitfor(unsigned id) { + log_trace("osal_waitfor: event %u", id); DWORD rc = WaitForSingleObject(events.at(id), INFINITE); return waitstatus2errcode(rc); } diff --git a/test/test.cc b/test/test.cc index 45e59ceb..28290c46 100644 --- a/test/test.cc +++ b/test/test.cc @@ -53,25 +53,37 @@ const char *status2str(actor_status status) { static void mdbx_debug_logger(int type, const char *function, int line, const char *msg, va_list args) { - logging::loglevel level = logging::trace; + 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::info; + level = logging::verbose; + if (type & MDBX_DBG_ASSERT) { - log_error("libmdbx assertion failure: %s, %d", + log_error("mdbx: assertion failure: %s, %d", function ? function : "unknown", line); level = logging::failure; } - output(level, msg, args); + if (logging::output(level, "mdbx: ")) + logging::feed(msg, args); if (type & MDBX_DBG_ASSERT) abort(); } -void testcase::mdbx_prepare() { - log_trace(">> mdbx_prepare"); +void testcase::db_prepare() { + log_trace(">> db_prepare"); + assert(!db_guard); - int rc = mdbx_setup_debug(MDBX_DBG_DNT, mdbx_debug_logger, MDBX_DBG_DNT); - log_info("libmdbx debug-flags: 0x%02x", rc); + 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); MDB_env *env = nullptr; rc = mdbx_env_create(&env); @@ -97,83 +109,163 @@ void testcase::mdbx_prepare() { if (rc != MDB_SUCCESS) failure_perror("mdbx_env_set_mapsize()", rc); - log_trace("<< mdbx_prepare"); + log_trace("<< db_prepare"); } -void testcase::mdbx_open() { - log_trace(">> mdbx_open"); +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); if (rc != MDB_SUCCESS) failure_perror("mdbx_env_open()", rc); - log_trace("<< mdbx_open"); + + log_trace("<< db_open"); } -void testcase::mdbx_close() { - log_trace(">> mdbx_close"); +void testcase::db_close() { + log_trace(">> db_close"); cursor_guard.reset(); txn_guard.reset(); db_guard.reset(); - log_trace("<< mdbx_close"); + log_trace("<< db_close"); +} + +void testcase::txn_begin(bool readonly) { + log_trace(">> txn_begin(%s)", readonly ? "read-only" : "read-write"); + assert(!txn_guard); + + MDB_txn *txn = nullptr; + int rc = + mdbx_txn_begin(db_guard.get(), nullptr, readonly ? MDB_RDONLY : 0, &txn); + if (rc != MDB_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); + + MDB_txn *txn = txn_guard.release(); + if (abort) { + int rc = mdbx_txn_abort(txn); + if (rc != MDB_SUCCESS) + failure_perror("mdbx_txn_abort()", rc); + } else { + int rc = mdbx_txn_commit(txn); + if (rc != MDB_SUCCESS) + failure_perror("mdbx_txn_commit()", rc); + } + + log_trace("<< txn_end(%s)", abort ? "abort" : "commit"); } bool testcase::wait4start() { if (config.wait4id) { log_trace(">> wait4start(%u)", config.wait4id); + assert(!global::singlemode); int rc = osal_waitfor(config.wait4id); if (rc) { log_trace("<< wait4start(%u), failed %s", config.wait4id, test_strerror(rc)); return false; } - return true; } else { - log_trace("== wait4start(not needed)"); - return true; + 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; } void testcase::report(size_t nops_done) { - if (config.signal_nops && !signalled && config.signal_nops <= nops_done) { - log_trace(">> signal(n-ops %zu)", nops_done); - osal_broadcast(config.id); + nops_completed += nops_done; + log_verbose("== complete +%zu iteration, total %zu done", nops_done, + nops_completed); + + if (config.signal_nops && !signalled && + config.signal_nops <= nops_completed) { + log_trace(">> signal(n-ops %zu)", nops_completed); + if (!global::singlemode) + osal_broadcast(config.actor_id); signalled = true; - log_trace("<< signal(n-ops %zu)", nops_done); + log_trace("<< signal(n-ops %zu)", nops_completed); } } void testcase::signal() { if (!signalled) { log_trace(">> signal(forced)"); - osal_broadcast(config.id); + if (!global::singlemode) + osal_broadcast(config.actor_id); signalled = true; log_trace("<< signal(forced)"); } } bool testcase::setup() { - mdbx_prepare(); - return wait4start(); + db_prepare(); + if (!wait4start()) + return false; + + start_timestamp = chrono::now_motonic(); + return true; } bool testcase::teardown() { log_trace(">> testcase::teardown"); signal(); - mdbx_close(); + db_close(); log_trace("<< testcase::teardown"); return true; } +bool testcase::should_continue() 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; + } + + if (config.params.test_nops && nops_completed >= config.params.test_nops) + result = false; + + return result; +} + //----------------------------------------------------------------------------- bool test_execute(const actor_config &config) { const mdbx_pid_t pid = osal_getpid(); - logging::setup((logging::loglevel)config.params.loglevel, - format("child_%u.%u", config.order, config.id)); - log_trace(">> wait4barrier"); - osal_wait4barrier(); - log_trace("<< wait4barrier"); + 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 test; @@ -206,7 +298,7 @@ bool test_execute(const actor_config &config) { return true; } } catch (const std::exception &pipets) { - failure("Exception: %s", pipets.what()); + failure("***** Exception: %s *****", pipets.what()); } return false; } diff --git a/test/test.h b/test/test.h index 093b9251..bb7764ba 100644 --- a/test/test.h +++ b/test/test.h @@ -24,7 +24,7 @@ bool test_execute(const actor_config &config); std::string thunk_param(const actor_config &config); -bool testcase_setup(const char *casename, const actor_params ¶ms, +void testcase_setup(const char *casename, actor_params ¶ms, unsigned &lastid); void configure_actor(unsigned &lastid, const actor_testcase testcase, const char *id_cstr, const actor_params ¶ms); @@ -36,12 +36,16 @@ extern std::vector actors; extern std::unordered_map events; extern std::unordered_map pid2actor; extern std::set databases; +extern unsigned nactors; +extern chrono::time start_motonic; +extern chrono::time deadline_motonic; +extern bool singlemode; namespace config { -extern unsigned timeout; +extern unsigned timeout_duration_seconds; extern bool dump_config; -extern bool dont_cleanup_before; -extern bool dont_cleanup_after; +extern bool cleanup_before; +extern bool cleanup_after; } /* namespace config */ } /* namespace global */ @@ -80,19 +84,28 @@ protected: scoped_cursor_guard cursor_guard; bool signalled; - void mdbx_prepare(); - void mdbx_open(); - void mdbx_close(); + size_t nops_completed; + chrono::time start_timestamp; + + void db_prepare(); + void db_open(); + void db_close(); + void txn_begin(bool readonly); + void txn_end(bool abort); bool wait4start(); void report(size_t nops_done); void signal(); + bool should_continue() const; + + bool mode_readonly() const { + return (config.params.mode_flags & MDB_RDONLY) ? true : false; + } public: testcase(const actor_config &config, const mdbx_pid_t pid) - : config(config), pid(pid) { - logging::setup(format("%s_%u.%u", testcase2str(config.testcase), - config.order, config.id)); + : config(config), pid(pid), nops_completed(0) { + start_timestamp.reset(); } virtual bool setup();