summaryrefslogtreecommitdiff
path: root/src/third_party/wiredtiger/test/cppsuite
diff options
context:
space:
mode:
Diffstat (limited to 'src/third_party/wiredtiger/test/cppsuite')
-rw-r--r--src/third_party/wiredtiger/test/cppsuite/Makefile.am12
-rw-r--r--src/third_party/wiredtiger/test/cppsuite/configs/search_near_01_default.txt22
-rw-r--r--src/third_party/wiredtiger/test/cppsuite/configs/search_near_02_default.txt31
-rw-r--r--src/third_party/wiredtiger/test/cppsuite/test_harness/connection_manager.cxx6
-rw-r--r--src/third_party/wiredtiger/test/cppsuite/test_harness/connection_manager.h6
-rw-r--r--src/third_party/wiredtiger/test/cppsuite/test_harness/runtime_monitor.cxx28
-rw-r--r--src/third_party/wiredtiger/test/cppsuite/test_harness/runtime_monitor.h3
-rw-r--r--src/third_party/wiredtiger/test/cppsuite/test_harness/util/logger.h4
-rw-r--r--src/third_party/wiredtiger/test/cppsuite/test_harness/util/scoped_types.h4
-rw-r--r--src/third_party/wiredtiger/test/cppsuite/test_harness/workload/random_generator.cxx3
-rw-r--r--src/third_party/wiredtiger/test/cppsuite/test_harness/workload/random_generator.h4
-rw-r--r--src/third_party/wiredtiger/test/cppsuite/test_harness/workload/thread_context.h1
-rw-r--r--src/third_party/wiredtiger/test/cppsuite/test_harness/workload/workload_tracking.cxx11
-rw-r--r--src/third_party/wiredtiger/test/cppsuite/tests/csuite_style_example_test.cxx169
-rw-r--r--src/third_party/wiredtiger/test/cppsuite/tests/example_test.cxx7
-rwxr-xr-xsrc/third_party/wiredtiger/test/cppsuite/tests/run.cxx34
-rw-r--r--src/third_party/wiredtiger/test/cppsuite/tests/search_near_01.cxx262
-rw-r--r--src/third_party/wiredtiger/test/cppsuite/tests/search_near_02.cxx425
18 files changed, 1002 insertions, 30 deletions
diff --git a/src/third_party/wiredtiger/test/cppsuite/Makefile.am b/src/third_party/wiredtiger/test/cppsuite/Makefile.am
index 82c30442482..5e896ce2e04 100644
--- a/src/third_party/wiredtiger/test/cppsuite/Makefile.am
+++ b/src/third_party/wiredtiger/test/cppsuite/Makefile.am
@@ -11,7 +11,7 @@ all:
all_TESTS=
noinst_PROGRAMS=
-run_SOURCES = test_harness/core/component.cxx \
+test_harness = test_harness/core/component.cxx \
test_harness/core/configuration.cxx \
test_harness/core/throttle.cxx \
test_harness/util/logger.cxx \
@@ -28,9 +28,15 @@ run_SOURCES = test_harness/core/component.cxx \
test_harness/test.cxx \
test_harness/thread_manager.cxx \
test_harness/timestamp_manager.cxx \
- test_harness/workload_generator.cxx \
- tests/run.cxx
+ test_harness/workload_generator.cxx
+# If you prefer to not use the run binary you can add a test via this
+# mechanism but it is generally frowned upon.
+csuite_style_example_test_SOURCES = $(test_harness) tests/csuite_style_example_test.cxx
+noinst_PROGRAMS += csuite_style_example_test
+all_TESTS += csuite_style_example_test
+
+run_SOURCES = $(test_harness) tests/run.cxx
noinst_PROGRAMS += run
all_TESTS += run
diff --git a/src/third_party/wiredtiger/test/cppsuite/configs/search_near_01_default.txt b/src/third_party/wiredtiger/test/cppsuite/configs/search_near_01_default.txt
new file mode 100644
index 00000000000..b3b3d6c668a
--- /dev/null
+++ b/src/third_party/wiredtiger/test/cppsuite/configs/search_near_01_default.txt
@@ -0,0 +1,22 @@
+# Configuration for search_near_01.
+# The test will generate key_count_per_collection number of keys for each prefix in aaa -> zzz.
+# This config will have a 3 minute duration, with 3 tables of an entry for each prefix.
+duration_seconds=180,
+cache_size_mb=1000,
+timestamp_manager=
+(
+ enabled=false,
+),
+workload_generator=
+(
+ populate_config=
+ (
+ collection_count=3,
+ key_count_per_collection=1,
+ key_size=5,
+ ),
+ read_config=
+ (
+ thread_count=10
+ )
+), \ No newline at end of file
diff --git a/src/third_party/wiredtiger/test/cppsuite/configs/search_near_02_default.txt b/src/third_party/wiredtiger/test/cppsuite/configs/search_near_02_default.txt
new file mode 100644
index 00000000000..34ab79e1980
--- /dev/null
+++ b/src/third_party/wiredtiger/test/cppsuite/configs/search_near_02_default.txt
@@ -0,0 +1,31 @@
+# Configuration for search_near_02.
+# The configuration creates:
+# - threads that continuously insert random keys and values.
+# - threads that continuously perform prefix search_near calls on random keys.
+duration_seconds=180,
+cache_size_mb=500,
+timestamp_manager=
+(
+ # This will let us randomly pick a read timestamp in a bigger range to trigger visibility
+ # checks.
+ oldest_lag=10,
+),
+workload_generator=
+(
+ populate_config=
+ (
+ collection_count=10,
+ ),
+ insert_config=
+ (
+ key_size=5,
+ op_rate=100ms,
+ thread_count=10
+ ),
+ read_config=
+ (
+ key_size=5,
+ op_rate=250ms,
+ thread_count=10
+ )
+)
diff --git a/src/third_party/wiredtiger/test/cppsuite/test_harness/connection_manager.cxx b/src/third_party/wiredtiger/test/cppsuite/test_harness/connection_manager.cxx
index 6ff134c7c96..8a8b75b7b8f 100644
--- a/src/third_party/wiredtiger/test/cppsuite/test_harness/connection_manager.cxx
+++ b/src/third_party/wiredtiger/test/cppsuite/test_harness/connection_manager.cxx
@@ -80,6 +80,12 @@ connection_manager::create_session()
return (session);
}
+WT_CONNECTION *
+connection_manager::get_connection()
+{
+ return (_conn);
+}
+
/*
* set_timestamp calls into the connection API in a thread safe manner to set global timestamps.
*/
diff --git a/src/third_party/wiredtiger/test/cppsuite/test_harness/connection_manager.h b/src/third_party/wiredtiger/test/cppsuite/test_harness/connection_manager.h
index c6245160df1..a5d44903717 100644
--- a/src/third_party/wiredtiger/test/cppsuite/test_harness/connection_manager.h
+++ b/src/third_party/wiredtiger/test/cppsuite/test_harness/connection_manager.h
@@ -30,8 +30,12 @@
#define CONN_API_H
/* Following definitions are required in order to use printing format specifiers in C++. */
+#ifndef __STDC_LIMIT_MACROS
#define __STDC_LIMIT_MACROS
+#endif
+#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
+#endif
#include <mutex>
@@ -60,6 +64,8 @@ class connection_manager {
void create(const std::string &config, const std::string &home = DEFAULT_DIR);
scoped_session create_session();
+ WT_CONNECTION *get_connection();
+
/*
* set_timestamp calls into the connection API in a thread safe manner to set global timestamps.
*/
diff --git a/src/third_party/wiredtiger/test/cppsuite/test_harness/runtime_monitor.cxx b/src/third_party/wiredtiger/test/cppsuite/test_harness/runtime_monitor.cxx
index ebb6520b465..0e454a4f4f0 100644
--- a/src/third_party/wiredtiger/test/cppsuite/test_harness/runtime_monitor.cxx
+++ b/src/third_party/wiredtiger/test/cppsuite/test_harness/runtime_monitor.cxx
@@ -36,16 +36,6 @@
namespace test_harness {
/* Static methods implementation. */
-static void
-get_stat(scoped_cursor &cursor, int stat_field, int64_t *valuep)
-{
- const char *desc, *pvalue;
- cursor->set_key(cursor.get(), stat_field);
- testutil_check(cursor->search(cursor.get()));
- testutil_check(cursor->get_value(cursor.get(), &desc, &pvalue, valuep));
- testutil_check(cursor->reset(cursor.get()));
-}
-
static std::string
collection_name_to_file_name(const std::string &collection_name)
{
@@ -100,9 +90,9 @@ cache_limit_statistic::check(scoped_cursor &cursor)
int64_t cache_bytes_image, cache_bytes_other, cache_bytes_max;
double use_percent;
/* Three statistics are required to compute cache use percentage. */
- get_stat(cursor, WT_STAT_CONN_CACHE_BYTES_IMAGE, &cache_bytes_image);
- get_stat(cursor, WT_STAT_CONN_CACHE_BYTES_OTHER, &cache_bytes_other);
- get_stat(cursor, WT_STAT_CONN_CACHE_BYTES_MAX, &cache_bytes_max);
+ runtime_monitor::get_stat(cursor, WT_STAT_CONN_CACHE_BYTES_IMAGE, &cache_bytes_image);
+ runtime_monitor::get_stat(cursor, WT_STAT_CONN_CACHE_BYTES_OTHER, &cache_bytes_other);
+ runtime_monitor::get_stat(cursor, WT_STAT_CONN_CACHE_BYTES_MAX, &cache_bytes_max);
/*
* Assert that we never exceed our configured limit for cache usage. Add 0.0 to avoid floating
* point conversion errors.
@@ -222,7 +212,7 @@ postrun_statistic_check::check_stat(scoped_cursor &cursor, const postrun_statist
int64_t stat_value;
testutil_assert(cursor.get() != nullptr);
- get_stat(cursor, stat.field, &stat_value);
+ runtime_monitor::get_stat(cursor, stat.field, &stat_value);
if (stat_value < stat.min_limit || stat_value > stat.max_limit) {
const std::string error_string = "runtime_monitor: Postrun stat \"" + stat.name +
"\" was outside of the specified limits. Min=" + std::to_string(stat.min_limit) +
@@ -236,6 +226,16 @@ postrun_statistic_check::check_stat(scoped_cursor &cursor, const postrun_statist
}
/* runtime_monitor class implementation */
+void
+runtime_monitor::get_stat(scoped_cursor &cursor, int stat_field, int64_t *valuep)
+{
+ const char *desc, *pvalue;
+ cursor->set_key(cursor.get(), stat_field);
+ testutil_check(cursor->search(cursor.get()));
+ testutil_check(cursor->get_value(cursor.get(), &desc, &pvalue, valuep));
+ testutil_check(cursor->reset(cursor.get()));
+}
+
runtime_monitor::runtime_monitor(configuration *config, database &database)
: component("runtime_monitor", config), _postrun_stats(config), _database(database)
{
diff --git a/src/third_party/wiredtiger/test/cppsuite/test_harness/runtime_monitor.h b/src/third_party/wiredtiger/test/cppsuite/test_harness/runtime_monitor.h
index e7e69302d1d..0f63585290d 100644
--- a/src/third_party/wiredtiger/test/cppsuite/test_harness/runtime_monitor.h
+++ b/src/third_party/wiredtiger/test/cppsuite/test_harness/runtime_monitor.h
@@ -116,6 +116,9 @@ class postrun_statistic_check {
*/
class runtime_monitor : public component {
public:
+ static void get_stat(scoped_cursor &, int, int64_t *);
+
+ public:
runtime_monitor(configuration *config, database &database);
~runtime_monitor();
diff --git a/src/third_party/wiredtiger/test/cppsuite/test_harness/util/logger.h b/src/third_party/wiredtiger/test/cppsuite/test_harness/util/logger.h
index 8d67bfd7e27..2c35ef0d162 100644
--- a/src/third_party/wiredtiger/test/cppsuite/test_harness/util/logger.h
+++ b/src/third_party/wiredtiger/test/cppsuite/test_harness/util/logger.h
@@ -30,8 +30,12 @@
#define DEBUG_UTILS_H
/* Following definitions are required in order to use printing format specifiers in C++. */
+#ifndef __STDC_LIMIT_MACROS
#define __STDC_LIMIT_MACROS
+#endif
+#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
+#endif
#include <chrono>
#include <iostream>
diff --git a/src/third_party/wiredtiger/test/cppsuite/test_harness/util/scoped_types.h b/src/third_party/wiredtiger/test/cppsuite/test_harness/util/scoped_types.h
index 47b8592ede0..edb38e3e22c 100644
--- a/src/third_party/wiredtiger/test/cppsuite/test_harness/util/scoped_types.h
+++ b/src/third_party/wiredtiger/test/cppsuite/test_harness/util/scoped_types.h
@@ -30,8 +30,12 @@
#define SCOPED_TYPES_H
/* Following definitions are required in order to use printing format specifiers in C++. */
+#ifndef __STDC_LIMIT_MACROS
#define __STDC_LIMIT_MACROS
+#endif
+#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
+#endif
extern "C" {
#include "test_util.h"
diff --git a/src/third_party/wiredtiger/test/cppsuite/test_harness/workload/random_generator.cxx b/src/third_party/wiredtiger/test/cppsuite/test_harness/workload/random_generator.cxx
index c539fbc34fc..3973af7242c 100644
--- a/src/third_party/wiredtiger/test/cppsuite/test_harness/workload/random_generator.cxx
+++ b/src/third_party/wiredtiger/test/cppsuite/test_harness/workload/random_generator.cxx
@@ -44,10 +44,11 @@ random_generator::instance()
std::string
random_generator::generate_random_string(std::size_t length, characters_type type)
{
+ const std::string characters = get_characters(type);
std::string str;
while (str.size() < length)
- str += get_characters(type);
+ str += characters;
std::shuffle(str.begin(), str.end(), _generator);
return (str.substr(0, length));
diff --git a/src/third_party/wiredtiger/test/cppsuite/test_harness/workload/random_generator.h b/src/third_party/wiredtiger/test/cppsuite/test_harness/workload/random_generator.h
index 31f44bbe98e..967d5566ce1 100644
--- a/src/third_party/wiredtiger/test/cppsuite/test_harness/workload/random_generator.h
+++ b/src/third_party/wiredtiger/test/cppsuite/test_harness/workload/random_generator.h
@@ -30,8 +30,12 @@
#define RANDOM_GENERATOR_H
/* Following definitions are required in order to use printing format specifiers in C++. */
+#ifndef __STDC_LIMIT_MACROS
#define __STDC_LIMIT_MACROS
+#endif
+#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
+#endif
#include <random>
#include <string>
diff --git a/src/third_party/wiredtiger/test/cppsuite/test_harness/workload/thread_context.h b/src/third_party/wiredtiger/test/cppsuite/test_harness/workload/thread_context.h
index 61e1b99f28a..28c1ee4265b 100644
--- a/src/third_party/wiredtiger/test/cppsuite/test_harness/workload/thread_context.h
+++ b/src/third_party/wiredtiger/test/cppsuite/test_harness/workload/thread_context.h
@@ -159,6 +159,7 @@ class thread_context {
public:
scoped_session session;
scoped_cursor op_track_cursor;
+ scoped_cursor stat_cursor;
transaction_context transaction;
timestamp_manager *tsm;
workload_tracking *tracking;
diff --git a/src/third_party/wiredtiger/test/cppsuite/test_harness/workload/workload_tracking.cxx b/src/third_party/wiredtiger/test/cppsuite/test_harness/workload/workload_tracking.cxx
index 1211749ec28..e0e7738590d 100644
--- a/src/third_party/wiredtiger/test/cppsuite/test_harness/workload/workload_tracking.cxx
+++ b/src/third_party/wiredtiger/test/cppsuite/test_harness/workload/workload_tracking.cxx
@@ -99,7 +99,8 @@ workload_tracking::do_work()
/* Take a copy of the oldest so that we sweep with a consistent timestamp. */
oldest_ts = _tsm.get_oldest_ts();
- while ((ret = _sweep_cursor->prev(_sweep_cursor.get())) == 0) {
+ /* We need to check if the component is still running to avoid unecessary iterations. */
+ while (_running && (ret = _sweep_cursor->prev(_sweep_cursor.get())) == 0) {
testutil_check(_sweep_cursor->get_key(_sweep_cursor.get(), &collection_id, &key, &ts));
testutil_check(_sweep_cursor->get_value(_sweep_cursor.get(), &op_type, &value));
/*
@@ -137,7 +138,13 @@ workload_tracking::do_work()
free(sweep_key);
- if (ret != WT_NOTFOUND)
+ /*
+ * If we get here and the test is still running, it means we must have reached the end of the
+ * table. We can also get here because the test is no longer running. In this case, the cursor
+ * can either be at the end of the table or still on a valid entry since we interrupted the
+ * work.
+ */
+ if (ret != 0 && ret != WT_NOTFOUND)
testutil_die(LOG_ERROR,
"Tracking table sweep failed: cursor->next() returned an unexpected error %d.", ret);
diff --git a/src/third_party/wiredtiger/test/cppsuite/tests/csuite_style_example_test.cxx b/src/third_party/wiredtiger/test/cppsuite/tests/csuite_style_example_test.cxx
new file mode 100644
index 00000000000..d0059880446
--- /dev/null
+++ b/src/third_party/wiredtiger/test/cppsuite/tests/csuite_style_example_test.cxx
@@ -0,0 +1,169 @@
+/*-
+ * Public Domain 2014-present MongoDB, Inc.
+ * Public Domain 2008-2014 WiredTiger, Inc.
+ *
+ * This is free and unencumbered software released into the public domain.
+ *
+ * Anyone is free to copy, modify, publish, use, compile, sell, or
+ * distribute this software, either in source code form or as a compiled
+ * binary, for any purpose, commercial or non-commercial, and by any
+ * means.
+ *
+ * In jurisdictions that recognize copyright laws, the author or authors
+ * of this software dedicate any and all copyright interest in the
+ * software to the public domain. We make this dedication for the benefit
+ * of the public at large and to the detriment of our heirs and
+ * successors. We intend this dedication to be an overt act of
+ * relinquishment in perpetuity of all present and future rights to this
+ * software under copyright law.
+ *
+ * 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 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.
+ */
+
+/*
+ * This file provides an example of how to create a test in C++ using a few features from the
+ * framework if any. This file can be used as a template for quick testing and/or when stress
+ * testing is not required. For any stress testing, it is encouraged to use the framework, see
+ * example_test.cxx and create_script.sh.
+ */
+
+#include "test_harness/connection_manager.h"
+#include "test_harness/thread_manager.h"
+#include "test_harness/util/api_const.h"
+#include "test_harness/util/logger.h"
+#include "test_harness/workload/random_generator.h"
+
+extern "C" {
+#include "wiredtiger.h"
+}
+
+using namespace test_harness;
+
+bool do_inserts = false;
+bool do_reads = false;
+
+void
+insert_op(WT_CURSOR *cursor, int key_size, int value_size)
+{
+ logger::log_msg(LOG_INFO, "called insert_op");
+
+ /* Insert random data. */
+ std::string key, value;
+ while (do_inserts) {
+ key = random_generator::instance().generate_random_string(key_size);
+ value = random_generator::instance().generate_random_string(value_size);
+ cursor->set_key(cursor, key.c_str());
+ cursor->set_value(cursor, value.c_str());
+ testutil_check(cursor->insert(cursor));
+ }
+}
+
+void
+read_op(WT_CURSOR *cursor, int key_size)
+{
+ logger::log_msg(LOG_INFO, "called read_op");
+
+ /* Read random data. */
+ std::string key;
+ while (do_reads) {
+ key = random_generator::instance().generate_random_string(key_size);
+ cursor->set_key(cursor, key.c_str());
+ cursor->search(cursor);
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ /* Set the program name for error messages. */
+ const std::string progname = testutil_set_progname(argv);
+
+ /* Set the tracing level for the logger component. */
+ logger::trace_level = LOG_INFO;
+
+ /* Printing some messages. */
+ logger::log_msg(LOG_INFO, "Starting " + progname);
+ logger::log_msg(LOG_ERROR, "This could be an error.");
+
+ /* Create a connection, set the cache size and specify the home directory. */
+ const std::string conn_config = std::string(CONNECTION_CREATE) + ",cache_size=500MB";
+ const std::string home_dir = std::string(DEFAULT_DIR) + '_' + progname;
+ connection_manager::instance().create(conn_config, home_dir);
+ WT_CONNECTION *conn = connection_manager::instance().get_connection();
+
+ /* Open different sessions. */
+ WT_SESSION *insert_session, *read_session;
+ testutil_check(conn->open_session(conn, nullptr, nullptr, &insert_session));
+ testutil_check(conn->open_session(conn, nullptr, nullptr, &read_session));
+
+ /* Create a collection. */
+ const std::string collection_name = "table:my_collection";
+ testutil_check(
+ insert_session->create(insert_session, collection_name.c_str(), DEFAULT_FRAMEWORK_SCHEMA));
+
+ /* Open different cursors. */
+ WT_CURSOR *insert_cursor, *read_cursor;
+ const std::string cursor_config = "";
+ testutil_check(insert_session->open_cursor(
+ insert_session, collection_name.c_str(), nullptr, cursor_config.c_str(), &insert_cursor));
+ testutil_check(read_session->open_cursor(
+ read_session, collection_name.c_str(), nullptr, cursor_config.c_str(), &read_cursor));
+
+ /* Store cursors. */
+ std::vector<WT_CURSOR *> cursors;
+ cursors.push_back(insert_cursor);
+ cursors.push_back(read_cursor);
+
+ /* Insert some data. */
+ std::string key = "a";
+ const std::string value = "b";
+ insert_cursor->set_key(insert_cursor, key.c_str());
+ insert_cursor->set_value(insert_cursor, value.c_str());
+ testutil_check(insert_cursor->insert(insert_cursor));
+
+ /* Read some data. */
+ key = "b";
+ read_cursor->set_key(read_cursor, key.c_str());
+ testutil_assert(read_cursor->search(read_cursor) == WT_NOTFOUND);
+
+ key = "a";
+ read_cursor->set_key(read_cursor, key.c_str());
+ testutil_check(read_cursor->search(read_cursor));
+
+ /* Create a thread manager and spawn some threads that will work. */
+ thread_manager t;
+ int key_size = 1, value_size = 2;
+
+ do_inserts = true;
+ t.add_thread(insert_op, insert_cursor, key_size, value_size);
+
+ do_reads = true;
+ t.add_thread(read_op, read_cursor, key_size);
+
+ /* Sleep for the test duration. */
+ int test_duration_s = 5;
+ std::this_thread::sleep_for(std::chrono::seconds(test_duration_s));
+
+ /* Stop the threads. */
+ do_reads = false;
+ do_inserts = false;
+ t.join();
+
+ /* Close cursors. */
+ for (auto c : cursors)
+ testutil_check(c->close(c));
+
+ /* Close the connection. */
+ connection_manager::instance().close();
+
+ /* Another message. */
+ logger::log_msg(LOG_INFO, "End of test.");
+
+ return (0);
+}
diff --git a/src/third_party/wiredtiger/test/cppsuite/tests/example_test.cxx b/src/third_party/wiredtiger/test/cppsuite/tests/example_test.cxx
index 5a1996b45a1..4b49ad2b148 100644
--- a/src/third_party/wiredtiger/test/cppsuite/tests/example_test.cxx
+++ b/src/third_party/wiredtiger/test/cppsuite/tests/example_test.cxx
@@ -37,6 +37,13 @@ class example_test : public test_harness::test {
example_test(const test_harness::test_args &args) : test(args) {}
void
+ run()
+ {
+ /* You can remove the call to the base class to fully customized your test. */
+ test::run();
+ }
+
+ void
populate(test_harness::database &, test_harness::timestamp_manager *,
test_harness::configuration *, test_harness::workload_tracking *) override final
{
diff --git a/src/third_party/wiredtiger/test/cppsuite/tests/run.cxx b/src/third_party/wiredtiger/test/cppsuite/tests/run.cxx
index 0c231e6568d..627921a9aa4 100755
--- a/src/third_party/wiredtiger/test/cppsuite/tests/run.cxx
+++ b/src/third_party/wiredtiger/test/cppsuite/tests/run.cxx
@@ -34,9 +34,11 @@
#include "test_harness/test.h"
#include "base_test.cxx"
-#include "example_test.cxx"
#include "burst_inserts.cxx"
+#include "example_test.cxx"
#include "hs_cleanup.cxx"
+#include "search_near_01.cxx"
+#include "search_near_02.cxx"
std::string
parse_configuration_from_file(const std::string &filename)
@@ -72,7 +74,7 @@ print_help()
std::cout << "\trun -C [WIREDTIGER_OPEN_CONFIGURATION]" << std::endl;
std::cout << "\trun -c [TEST_FRAMEWORK_CONFIGURATION]" << std::endl;
std::cout << "\trun -f [FILE]" << std::endl;
- std::cout << "\trun -l [TRACEL_LEVEL]" << std::endl;
+ std::cout << "\trun -l [TRACE_LEVEL]" << std::endl;
std::cout << "\trun -t [TEST_NAME]" << std::endl;
std::cout << std::endl;
std::cout << "DESCRIPTION" << std::endl;
@@ -113,10 +115,14 @@ run_test(const std::string &test_name, const std::string &config, const std::str
base_test(test_harness::test_args{config, test_name, wt_open_config}).run();
else if (test_name == "example_test")
example_test(test_harness::test_args{config, test_name, wt_open_config}).run();
+ else if (test_name == "search_near_02")
+ search_near_02(test_harness::test_args{config, test_name, wt_open_config}).run();
else if (test_name == "hs_cleanup")
hs_cleanup(test_harness::test_args{config, test_name, wt_open_config}).run();
else if (test_name == "burst_inserts")
burst_inserts(test_harness::test_args{config, test_name, wt_open_config}).run();
+ else if (test_name == "search_near_01")
+ search_near_01(test_harness::test_args{config, test_name, wt_open_config}).run();
else {
test_harness::logger::log_msg(LOG_ERROR, "Test not found: " + test_name);
error_code = -1;
@@ -139,8 +145,8 @@ main(int argc, char *argv[])
{
std::string cfg, config_filename, current_cfg, current_test_name, test_name, wt_open_config;
int64_t error_code = 0;
- const std::vector<std::string> all_tests = {
- "example_test", "burst_inserts", "hs_cleanup", "base_test"};
+ const std::vector<std::string> all_tests = {"base_test", "burst_inserts", "example_test",
+ "hs_cleanup", "search_near_01", "search_near_02"};
/* Set the program name for error messages. */
(void)testutil_set_progname(argv);
@@ -219,12 +225,20 @@ main(int argc, char *argv[])
}
} else {
current_test_name = test_name;
- /* Configuration parsing. */
- if (!config_filename.empty())
- cfg = parse_configuration_from_file(config_filename);
- else if (cfg.empty())
- cfg = parse_configuration_from_file(get_default_config_path(current_test_name));
- error_code = run_test(current_test_name, cfg, wt_open_config);
+ /* Check the test exists. */
+ if (std::find(all_tests.begin(), all_tests.end(), current_test_name) ==
+ all_tests.end()) {
+ test_harness::logger::log_msg(
+ LOG_ERROR, "The test " + current_test_name + " was not found.");
+ error_code = -1;
+ } else {
+ /* Configuration parsing. */
+ if (!config_filename.empty())
+ cfg = parse_configuration_from_file(config_filename);
+ else if (cfg.empty())
+ cfg = parse_configuration_from_file(get_default_config_path(current_test_name));
+ error_code = run_test(current_test_name, cfg, wt_open_config);
+ }
}
if (error_code != 0)
diff --git a/src/third_party/wiredtiger/test/cppsuite/tests/search_near_01.cxx b/src/third_party/wiredtiger/test/cppsuite/tests/search_near_01.cxx
new file mode 100644
index 00000000000..abacf3dc196
--- /dev/null
+++ b/src/third_party/wiredtiger/test/cppsuite/tests/search_near_01.cxx
@@ -0,0 +1,262 @@
+/*-
+ * Public Domain 2014-present MongoDB, Inc.
+ * Public Domain 2008-2014 WiredTiger, Inc.
+ *
+ * This is free and unencumbered software released into the public domain.
+ *
+ * Anyone is free to copy, modify, publish, use, compile, sell, or
+ * distribute this software, either in source code form or as a compiled
+ * binary, for any purpose, commercial or non-commercial, and by any
+ * means.
+ *
+ * In jurisdictions that recognize copyright laws, the author or authors
+ * of this software dedicate any and all copyright interest in the
+ * software to the public domain. We make this dedication for the benefit
+ * of the public at large and to the detriment of our heirs and
+ * successors. We intend this dedication to be an overt act of
+ * relinquishment in perpetuity of all present and future rights to this
+ * software under copyright law.
+ *
+ * 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 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.
+ */
+
+#include "test_harness/util/api_const.h"
+#include "test_harness/workload/random_generator.h"
+#include "test_harness/workload/thread_context.h"
+#include "test_harness/test.h"
+#include "test_harness/thread_manager.h"
+
+using namespace test_harness;
+/*
+ * In this test, we want to verify that search_near with prefix enabled only traverses the portion
+ * of the tree that follows the prefix portion of the search key. The test is composed of a populate
+ * phase followed by a read phase. The populate phase will insert a set of random generated keys
+ * with a prefix of aaa -> zzz. The read phase will continuously perform prefix search near calls,
+ * and validate that the number of entries traversed is within bounds of the search key.
+ */
+class search_near_01 : public test_harness::test {
+ uint64_t keys_per_prefix = 0;
+ uint64_t srchkey_len = 0;
+ const std::string ALPHABET{"abcdefghijklmnopqrstuvwxyz"};
+ const uint64_t PREFIX_KEY_LEN = 3;
+
+ static void
+ populate_worker(thread_context *tc, const std::string &ALPHABET, uint64_t PREFIX_KEY_LEN)
+ {
+ logger::log_msg(LOG_INFO, "Populate with thread id: " + std::to_string(tc->id));
+
+ std::string prefix_key;
+ uint64_t collections_per_thread = tc->collection_count;
+ const uint64_t MAX_ROLLBACKS = 100;
+ uint32_t rollback_retries = 0;
+ int cmpp;
+
+ /*
+ * Generate a table of data with prefix keys aaa -> zzz. We have 26 threads from ids
+ * starting from 0 to 26. Each populate thread will insert separate prefix keys based on the
+ * id.
+ */
+ for (int64_t i = 0; i < collections_per_thread; ++i) {
+ collection &coll = tc->db.get_collection(i);
+ scoped_cursor cursor = tc->session.open_scoped_cursor(coll.name.c_str());
+ for (uint64_t j = 0; j < ALPHABET.size(); ++j) {
+ for (uint64_t k = 0; k < ALPHABET.size(); ++k) {
+ for (uint64_t count = 0; count < tc->key_count; ++count) {
+ tc->transaction.begin();
+ /*
+ * Generate the prefix key, and append a random generated key string based
+ * on the key size configuration.
+ */
+ prefix_key = {ALPHABET.at(tc->id), ALPHABET.at(j), ALPHABET.at(k)};
+ prefix_key += random_generator::instance().generate_random_string(
+ tc->key_size - PREFIX_KEY_LEN);
+ if (!tc->insert(cursor, coll.id, prefix_key)) {
+ testutil_assert(rollback_retries < MAX_ROLLBACKS);
+ /* We failed to insert, rollback our transaction and retry. */
+ tc->transaction.rollback();
+ ++rollback_retries;
+ --count;
+ } else {
+ /* Commit txn at commit timestamp 100. */
+ tc->transaction.commit(
+ "commit_timestamp=" + tc->tsm->decimal_to_hex(100));
+ rollback_retries = 0;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public:
+ search_near_01(const test_harness::test_args &args) : test(args) {}
+
+ void
+ populate(test_harness::database &database, test_harness::timestamp_manager *tsm,
+ test_harness::configuration *config, test_harness::workload_tracking *tracking) override final
+ {
+ uint64_t collection_count, key_size;
+ std::vector<thread_context *> workers;
+ thread_manager tm;
+
+ /* Validate our config. */
+ collection_count = config->get_int(COLLECTION_COUNT);
+ keys_per_prefix = config->get_int(KEY_COUNT_PER_COLLECTION);
+ key_size = config->get_int(KEY_SIZE);
+ testutil_assert(collection_count > 0);
+ testutil_assert(keys_per_prefix > 0);
+ /* Check the prefix length is not greater than the key size. */
+ testutil_assert(key_size >= PREFIX_KEY_LEN);
+
+ logger::log_msg(LOG_INFO,
+ "Populate configuration with key size: " + std::to_string(key_size) +
+ " key count: " + std::to_string(keys_per_prefix) +
+ " number of collections: " + std::to_string(collection_count));
+
+ /* Create n collections as per the configuration. */
+ for (uint64_t i = 0; i < collection_count; ++i)
+ /*
+ * The database model will call into the API and create the collection, with its own
+ * session.
+ */
+ database.add_collection();
+
+ /* Spawn 26 threads to populate the database. */
+ for (uint64_t i = 0; i < ALPHABET.size(); ++i) {
+ thread_context *tc = new thread_context(i, thread_type::INSERT, config,
+ connection_manager::instance().create_session(), tsm, tracking, database);
+ workers.push_back(tc);
+ tm.add_thread(populate_worker, tc, ALPHABET, PREFIX_KEY_LEN);
+ }
+
+ /* Wait for our populate threads to finish and then join them. */
+ logger::log_msg(LOG_INFO, "Populate: waiting for threads to complete.");
+ tm.join();
+
+ /* Cleanup our workers. */
+ for (auto &it : workers) {
+ delete it;
+ it = nullptr;
+ }
+
+ /* Force evict all the populated keys in all of the collections. */
+ int cmpp;
+ scoped_session session = connection_manager::instance().create_session();
+ for (uint64_t count = 0; count < collection_count; ++count) {
+ collection &coll = database.get_collection(count);
+ scoped_cursor evict_cursor =
+ session.open_scoped_cursor(coll.name.c_str(), "debug=(release_evict=true)");
+
+ for (uint64_t i = 0; i < ALPHABET.size(); ++i) {
+ for (uint64_t j = 0; j < ALPHABET.size(); ++j) {
+ for (uint64_t k = 0; k < ALPHABET.size(); ++k) {
+ std::string key = {ALPHABET.at(i), ALPHABET.at(j), ALPHABET.at(k)};
+ evict_cursor->set_key(evict_cursor.get(), key.c_str());
+ evict_cursor->search_near(evict_cursor.get(), &cmpp);
+ testutil_check(evict_cursor->reset(evict_cursor.get()));
+ }
+ }
+ }
+ }
+ srchkey_len =
+ random_generator::instance().generate_integer(static_cast<uint64_t>(1), PREFIX_KEY_LEN);
+ logger::log_msg(LOG_INFO, "Populate: finished.");
+ }
+
+ void
+ read_operation(test_harness::thread_context *tc) override final
+ {
+ /* Make sure that thread statistics cursor is null before we open it. */
+ testutil_assert(tc->stat_cursor.get() == nullptr);
+ logger::log_msg(
+ LOG_INFO, type_string(tc->type) + " thread {" + std::to_string(tc->id) + "} commencing.");
+ std::map<uint64_t, scoped_cursor> cursors;
+ tc->stat_cursor = tc->session.open_scoped_cursor(STATISTICS_URI);
+ std::string srch_key;
+ int64_t entries_stat, prefix_stat, prev_entries_stat, prev_prefix_stat, expected_entries;
+ int cmpp;
+
+ cmpp = 0;
+ prev_entries_stat = 0;
+ prev_prefix_stat = 0;
+
+ /*
+ * The number of expected entries is calculated to account for the maximum allowed entries
+ * per search near function call. The key we search near can be different in length, which
+ * will increase the number of entries search by a factor of 26.
+ */
+ expected_entries = tc->thread_count * keys_per_prefix * 2 *
+ pow(ALPHABET.size(), PREFIX_KEY_LEN - srchkey_len);
+
+ /*
+ * Read at timestamp 10, so that no keys are visible to this transaction. This allows prefix
+ * search near to early exit out of it's prefix range when it's trying to search for a
+ * visible key in the tree.
+ */
+ tc->transaction.begin("read_timestamp=" + tc->tsm->decimal_to_hex(10));
+ while (tc->running()) {
+
+ /* Get a collection and find a cached cursor. */
+ collection &coll = tc->db.get_random_collection();
+ if (cursors.find(coll.id) == cursors.end()) {
+ scoped_cursor cursor = tc->session.open_scoped_cursor(coll.name.c_str());
+ cursor->reconfigure(cursor.get(), "prefix_search=true");
+ cursors.emplace(coll.id, std::move(cursor));
+ }
+
+ /* Generate search prefix key of random length between a -> zzz. */
+ srch_key = random_generator::instance().generate_random_string(
+ srchkey_len, characters_type::ALPHABET);
+ logger::log_msg(LOG_INFO,
+ "Read thread {" + std::to_string(tc->id) +
+ "} performing prefix search near with key: " + srch_key);
+
+ /* Do a second lookup now that we know it exists. */
+ auto &cursor = cursors[coll.id];
+ if (tc->transaction.active()) {
+ runtime_monitor::get_stat(
+ tc->stat_cursor, WT_STAT_CONN_CURSOR_NEXT_SKIP_LT_100, &prev_entries_stat);
+ runtime_monitor::get_stat(tc->stat_cursor,
+ WT_STAT_CONN_CURSOR_SEARCH_NEAR_PREFIX_FAST_PATHS, &prev_prefix_stat);
+
+ cursor->set_key(cursor.get(), srch_key.c_str());
+ testutil_assert(cursor->search_near(cursor.get(), &cmpp) == WT_NOTFOUND);
+
+ runtime_monitor::get_stat(
+ tc->stat_cursor, WT_STAT_CONN_CURSOR_NEXT_SKIP_LT_100, &entries_stat);
+ runtime_monitor::get_stat(
+ tc->stat_cursor, WT_STAT_CONN_CURSOR_SEARCH_NEAR_PREFIX_FAST_PATHS, &prefix_stat);
+ logger::log_msg(LOG_INFO,
+ "Read thread {" + std::to_string(tc->id) +
+ "} skipped entries: " + std::to_string(entries_stat - prev_entries_stat) +
+ " prefix fash path: " + std::to_string(prefix_stat - prev_prefix_stat));
+
+ /*
+ * It is possible that WiredTiger increments the entries skipped stat irrelevant to
+ * prefix search near. This is dependent on how many read threads are present in the
+ * test. Account for this by creating a small buffer using thread count. Assert that
+ * the number of expected entries is the upper limit which the prefix search near
+ * can traverse and the prefix fast path is incremented.
+ */
+ testutil_assert(
+ (expected_entries + (2 * tc->thread_count)) >= entries_stat - prev_entries_stat);
+ testutil_assert(prefix_stat > prev_prefix_stat);
+
+ tc->transaction.add_op();
+ tc->sleep();
+ }
+ /* Reset our cursor to avoid pinning content. */
+ testutil_check(cursor->reset(cursor.get()));
+ }
+ tc->transaction.commit();
+ /* Make sure the last transaction is rolled back now the work is finished. */
+ if (tc->transaction.active())
+ tc->transaction.rollback();
+ }
+};
diff --git a/src/third_party/wiredtiger/test/cppsuite/tests/search_near_02.cxx b/src/third_party/wiredtiger/test/cppsuite/tests/search_near_02.cxx
new file mode 100644
index 00000000000..1df75a83bc0
--- /dev/null
+++ b/src/third_party/wiredtiger/test/cppsuite/tests/search_near_02.cxx
@@ -0,0 +1,425 @@
+/*-
+ * Public Domain 2014-present MongoDB, Inc.
+ * Public Domain 2008-2014 WiredTiger, Inc.
+ *
+ * This is free and unencumbered software released into the public domain.
+ *
+ * Anyone is free to copy, modify, publish, use, compile, sell, or
+ * distribute this software, either in source code form or as a compiled
+ * binary, for any purpose, commercial or non-commercial, and by any
+ * means.
+ *
+ * In jurisdictions that recognize copyright laws, the author or authors
+ * of this software dedicate any and all copyright interest in the
+ * software to the public domain. We make this dedication for the benefit
+ * of the public at large and to the detriment of our heirs and
+ * successors. We intend this dedication to be an overt act of
+ * relinquishment in perpetuity of all present and future rights to this
+ * software under copyright law.
+ *
+ * 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 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.
+ */
+
+#include "test_harness/test.h"
+#include "test_harness/util/api_const.h"
+#include "test_harness/workload/random_generator.h"
+
+using namespace test_harness;
+
+/*
+ * In this test, we want to verify search_near with prefix enabled returns the correct key.
+ * During the test duration:
+ * - N threads will keep inserting new random keys
+ * - M threads will execute search_near calls with prefix enabled using random prefixes as well.
+ * Each search_near call with prefix enabled is verified using the default search_near.
+ */
+class search_near_02 : public test_harness::test {
+ public:
+ search_near_02(const test_harness::test_args &args) : test(args) {}
+
+ void
+ populate(test_harness::database &database, test_harness::timestamp_manager *,
+ test_harness::configuration *config, test_harness::workload_tracking *) override final
+ {
+ /*
+ * The populate phase only creates empty collections. The number of collections is defined
+ * in the configuration.
+ */
+ int64_t collection_count = config->get_int(COLLECTION_COUNT);
+
+ logger::log_msg(
+ LOG_INFO, "Populate: " + std::to_string(collection_count) + " creating collections.");
+
+ for (uint64_t i = 0; i < collection_count; ++i)
+ database.add_collection();
+
+ logger::log_msg(LOG_INFO, "Populate: finished.");
+ }
+
+ void
+ insert_operation(test_harness::thread_context *tc) override final
+ {
+ /* Each insert operation will insert new keys in the collections. */
+ logger::log_msg(
+ LOG_INFO, type_string(tc->type) + " thread {" + std::to_string(tc->id) + "} commencing.");
+
+ /* Helper struct which stores a pointer to a collection and a cursor associated with it. */
+ struct collection_cursor {
+ collection_cursor(collection &coll, scoped_cursor &&cursor)
+ : coll(coll), cursor(std::move(cursor))
+ {
+ }
+ collection &coll;
+ scoped_cursor cursor;
+ };
+
+ /* Collection cursor vector. */
+ std::vector<collection_cursor> ccv;
+ int64_t collection_count = tc->db.get_collection_count();
+ int64_t collections_per_thread = collection_count / tc->thread_count;
+
+ /* Must have unique collections for each thread. */
+ testutil_assert(collection_count % tc->thread_count == 0);
+ const uint64_t thread_offset = tc->id * collections_per_thread;
+ for (uint64_t i = thread_offset;
+ i < thread_offset + collections_per_thread && tc->running(); ++i) {
+ collection &coll = tc->db.get_collection(i);
+ scoped_cursor cursor = tc->session.open_scoped_cursor(coll.name.c_str());
+ ccv.push_back({coll, std::move(cursor)});
+ }
+
+ std::string key;
+ uint64_t counter = 0;
+
+ while (tc->running()) {
+
+ auto &cc = ccv[counter];
+ tc->transaction.begin();
+
+ while (tc->transaction.active() && tc->running()) {
+
+ /* Generate a random key. */
+ key = random_generator::instance().generate_random_string(tc->key_size);
+
+ /* Insert a key value pair. */
+ if (tc->insert(cc.cursor, cc.coll.id, key)) {
+ if (tc->transaction.can_commit())
+ /* We are not checking the result of commit as it is not necessary. */
+ tc->transaction.commit();
+ } else {
+ tc->transaction.rollback();
+ }
+
+ /* Sleep the duration defined by the configuration. */
+ tc->sleep();
+ }
+
+ /* Rollback any transaction that could not commit before the end of the test. */
+ if (tc->transaction.active())
+ tc->transaction.rollback();
+
+ /* Reset our cursor to avoid pinning content. */
+ testutil_check(cc.cursor->reset(cc.cursor.get()));
+ if (++counter == ccv.size())
+ counter = 0;
+ testutil_assert(counter < collections_per_thread);
+ }
+ }
+
+ void
+ read_operation(test_harness::thread_context *tc) override final
+ {
+ /*
+ * Each read operation performs search_near calls with and without prefix enabled on random
+ * collections. Each prefix is randomly generated. The result of the seach_near call with
+ * prefix enabled is then validated using the search_near call without prefix enabled.
+ */
+ logger::log_msg(
+ LOG_INFO, type_string(tc->type) + " thread {" + std::to_string(tc->id) + "} commencing.");
+
+ const char *key_prefix;
+ int exact_prefix, ret;
+ int64_t prefix_size;
+ std::map<uint64_t, scoped_cursor> cursors;
+ std::string generated_prefix, key_prefix_str;
+
+ while (tc->running()) {
+ /* Get a random collection to work on. */
+ collection &coll = tc->db.get_random_collection();
+
+ /* Find a cached cursor or create one if none exists. */
+ if (cursors.find(coll.id) == cursors.end()) {
+ cursors.emplace(
+ coll.id, std::move(tc->session.open_scoped_cursor(coll.name.c_str())));
+ auto &cursor_prefix = cursors[coll.id];
+ /* The cached cursors have the prefix configuration enabled. */
+ testutil_check(
+ cursor_prefix.get()->reconfigure(cursor_prefix.get(), "prefix_search=true"));
+ }
+
+ auto &cursor_prefix = cursors[coll.id];
+
+ /*
+ * Pick a random timestamp between the oldest and now. Get rid of the last 32 bits as
+ * they represent an increment for uniqueness.
+ */
+ wt_timestamp_t ts = random_generator::instance().generate_integer(
+ (tc->tsm->get_oldest_ts() >> 32), (tc->tsm->get_next_ts() >> 32));
+ /* Put back the timestamp in the correct format. */
+ ts <<= 32;
+
+ /*
+ * The oldest timestamp might move ahead and the reading timestamp might become invalid.
+ * To tackle this issue, we round the timestamp to the oldest timestamp value.
+ */
+ tc->transaction.begin(
+ "roundup_timestamps=(read=true),read_timestamp=" + tc->tsm->decimal_to_hex(ts));
+
+ while (tc->transaction.active() && tc->running()) {
+ /*
+ * Generate a random prefix. For this, we start by generating a random size and then
+ * its value.
+ */
+ prefix_size = random_generator::instance().generate_integer(
+ static_cast<int64_t>(1), tc->key_size);
+ generated_prefix = random_generator::instance().generate_random_string(
+ prefix_size, characters_type::ALPHABET);
+
+ /* Call search near with the prefix cursor. */
+ cursor_prefix->set_key(cursor_prefix.get(), generated_prefix.c_str());
+ ret = cursor_prefix->search_near(cursor_prefix.get(), &exact_prefix);
+ testutil_assert(ret == 0 || ret == WT_NOTFOUND);
+ if (ret == 0) {
+ testutil_check(cursor_prefix->get_key(cursor_prefix.get(), &key_prefix));
+ key_prefix_str = key_prefix;
+ } else {
+ key_prefix_str = "";
+ }
+
+ /* Open a cursor with the default configuration on the selected collection. */
+ scoped_cursor cursor_default(tc->session.open_scoped_cursor(coll.name.c_str()));
+
+ /* Verify the prefix search_near output using the default cursor. */
+ validate_prefix_search_near(
+ ret, exact_prefix, key_prefix_str, cursor_default, generated_prefix);
+
+ tc->transaction.add_op();
+ tc->transaction.try_rollback();
+ tc->sleep();
+ }
+ testutil_check(cursor_prefix->reset(cursor_prefix.get()));
+ }
+ /* Roll back the last transaction if still active now the work is finished. */
+ if (tc->transaction.active())
+ tc->transaction.rollback();
+ }
+
+ private:
+ /* Validate prefix search_near call outputs using a cursor without prefix key enabled. */
+ void
+ validate_prefix_search_near(int ret_prefix, int exact_prefix, const std::string &key_prefix,
+ scoped_cursor &cursor_default, const std::string &prefix)
+ {
+ /* Call search near with the default cursor using the given prefix. */
+ int exact_default;
+ cursor_default->set_key(cursor_default.get(), prefix.c_str());
+ int ret_default = cursor_default->search_near(cursor_default.get(), &exact_default);
+
+ /*
+ * It is not possible to have a prefix search near call successful and the default search
+ * near call unsuccessful.
+ */
+ testutil_assert(
+ ret_default == ret_prefix || (ret_default == 0 && ret_prefix == WT_NOTFOUND));
+
+ /* We only have to perform validation when the default search near call is successful. */
+ if (ret_default == 0) {
+ /* Both calls are successful. */
+ if (ret_prefix == 0)
+ validate_successful_calls(
+ ret_prefix, exact_prefix, key_prefix, cursor_default, exact_default, prefix);
+ /* The prefix search near call failed. */
+ else
+ validate_unsuccessful_prefix_call(cursor_default, prefix, exact_default);
+ }
+ }
+
+ /*
+ * Validate a successful prefix enabled search near call using a successful default search near
+ * call.
+ * The exact value set by the prefix search near call has to be either 0 or 1. Indeed, it cannot
+ * be -1 as the key needs to contain the prefix.
+ * - If it is 0, both search near calls should return the same outputs and both cursors should
+ * be positioned on the prefix we are looking for.
+ * - If it is 1, it will depend on the exact value set by the default search near call which can
+ * be -1 or 1. If it is -1, calling next on the default cursor should get us ti the key found by
+ * the prefix search near call. If it is 1, it means both search near calls have found the same
+ * key that is lexicographically greater than the prefix but still contains the prefix.
+ */
+ void
+ validate_successful_calls(int ret_prefix, int exact_prefix, const std::string &key_prefix,
+ scoped_cursor &cursor_default, int exact_default, const std::string &prefix)
+ {
+ const char *k;
+ std::string k_str;
+ int ret;
+
+ /*
+ * The prefix search near call cannot retrieve a key with a smaller value than the prefix we
+ * searched.
+ */
+ testutil_assert(exact_prefix >= 0);
+
+ /* The key at the prefix cursor should contain the prefix. */
+ testutil_assert(key_prefix.substr(0, prefix.size()) == prefix);
+
+ /* Retrieve the key the default cursor is pointing at. */
+ const char *key_default;
+ testutil_check(cursor_default->get_key(cursor_default.get(), &key_default));
+ std::string key_default_str = key_default;
+
+ logger::log_msg(LOG_TRACE,
+ "search_near (normal) exact " + std::to_string(exact_default) + " key " + key_default);
+ logger::log_msg(LOG_TRACE,
+ "search_near (prefix) exact " + std::to_string(exact_prefix) + " key " + key_prefix);
+
+ /* Example: */
+ /* keys: a, bb, bba. */
+ /* Only bb is not visible. */
+ /* Default search_near(bb) returns a, exact < 0. */
+ /* Prefix search_near(bb) returns bba, exact > 0. */
+ if (exact_default < 0) {
+ /* The key at the default cursor should not contain the prefix. */
+ testutil_assert((key_default_str.substr(0, prefix.size()) != prefix));
+
+ /*
+ * The prefix cursor should be positioned at a key lexicographically greater than the
+ * prefix.
+ */
+ testutil_assert(exact_prefix > 0);
+
+ /*
+ * The next key of the default cursor should be equal to the key pointed by the prefix
+ * cursor.
+ */
+ testutil_assert(cursor_default->next(cursor_default.get()) == 0);
+ testutil_check(cursor_default->get_key(cursor_default.get(), &k));
+ testutil_assert(k == key_prefix);
+ }
+ /* Example: */
+ /* keys: a, bb, bba */
+ /* Case 1: all keys are visible. */
+ /* Default search_near(bb) returns bb, exact = 0 */
+ /* Prefix search_near(bb) returns bb, exact = 0 */
+ /* Case 2: only bb is not visible. */
+ /* Default search_near(bb) returns bba, exact > 0. */
+ /* Prefix search_near(bb) returns bba, exact > 0. */
+ else {
+ /* Both cursors should be pointing at the same key. */
+ testutil_assert(exact_prefix == exact_default);
+ testutil_assert(key_default_str == key_prefix);
+ /* Both cursors should have found the exact key. */
+ if (exact_default == 0)
+ testutil_assert(key_default_str == prefix);
+ /* Both cursors have found a key that is lexicographically greater than the prefix. */
+ else
+ testutil_assert(key_default_str != prefix);
+ }
+ }
+
+ /*
+ * Validate that no keys with the prefix used for the search have been found.
+ * To validate this, we can use the exact value set by the default search near. Since the prefix
+ * search near failed, the exact value set by the default search near call has to be either -1
+ * or 1:
+ * - If it is -1, we need to check the next key, if it exists, is lexicographically greater than
+ * the prefix we looked for.
+ * - If it is 1, we need to check the previous keys, if it exists, if lexicographically smaller
+ * than the prefix we looked for.
+ */
+ void
+ validate_unsuccessful_prefix_call(
+ scoped_cursor &cursor_default, const std::string &prefix, int exact_default)
+ {
+ int ret;
+ const char *k;
+ std::string k_str;
+
+ /*
+ * The exact value from the default search near call cannot be 0, otherwise the prefix
+ * search near should be successful too.
+ */
+ testutil_assert(exact_default != 0);
+
+ /* Retrieve the key at the default cursor. */
+ const char *key_default;
+ testutil_check(cursor_default->get_key(cursor_default.get(), &key_default));
+ std::string key_default_str = key_default;
+
+ /* The key at the default cursor should not contain the prefix. */
+ testutil_assert(key_default_str.substr(0, prefix.size()) != prefix);
+
+ /* Example: */
+ /* keys: a, bb, bbb. */
+ /* All keys are visible. */
+ /* Default search_near(bba) returns bb, exact < 0. */
+ /* Prefix search_near(bba) returns WT_NOTFOUND. */
+ if (exact_default < 0) {
+ /*
+ * The current key of the default cursor should be lexicographically smaller than the
+ * prefix.
+ */
+ testutil_assert(std::lexicographical_compare(
+ key_default_str.begin(), key_default_str.end(), prefix.begin(), prefix.end()));
+
+ /*
+ * The next key of the default cursor should be lexicographically greater than the
+ * prefix if it exists.
+ */
+ ret = cursor_default->next(cursor_default.get());
+ if (ret == 0) {
+ testutil_check(cursor_default->get_key(cursor_default.get(), &k));
+ k_str = k;
+ testutil_assert(!std::lexicographical_compare(
+ k_str.begin(), k_str.end(), prefix.begin(), prefix.end()));
+ } else {
+ /* End of the table. */
+ testutil_assert(ret == WT_NOTFOUND);
+ }
+ }
+ /* Example: */
+ /* keys: a, bb, bbb. */
+ /* All keys are visible. */
+ /* Default search_near(bba) returns bbb, exact > 0. */
+ /* Prefix search_near(bba) returns WT_NOTFOUND. */
+ else {
+ /*
+ * The current key of the default cursor should be lexicographically greater than the
+ * prefix.
+ */
+ testutil_assert(!std::lexicographical_compare(
+ key_default_str.begin(), key_default_str.end(), prefix.begin(), prefix.end()));
+
+ /*
+ * The next key of the default cursor should be lexicographically smaller than the
+ * prefix if it exists.
+ */
+ ret = cursor_default->prev(cursor_default.get());
+ if (ret == 0) {
+ testutil_check(cursor_default->get_key(cursor_default.get(), &k));
+ k_str = k;
+ testutil_assert(std::lexicographical_compare(
+ k_str.begin(), k_str.end(), prefix.begin(), prefix.end()));
+ } else {
+ /* End of the table. */
+ testutil_assert(ret == WT_NOTFOUND);
+ }
+ }
+ }
+};