Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ test/bedrocktest_*
test/clustertest/cluster_node_?_bedrocktest*
bedrock
.build
build
libbedrock.a
libstuff.a
sqlitecluster/sqlitecluster
Expand Down
2 changes: 1 addition & 1 deletion BedrockServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ void BedrockServer::sync()
_upgradeInProgress = false;
if (committingCommand) {
db.rollback();
committingCommand = false;
committingCommand = false; //NOLINT
}
}

Expand Down
151 changes: 151 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
cmake_minimum_required(VERSION 3.10)
project(Bedrock)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

link_libraries("-fuse-ld=mold")

# Find the clang-tidy executable first
find_program(CLANG_TIDY_EXECUTABLE NAMES clang-tidy-18)
if(CLANG_TIDY_EXECUTABLE)
# clang-tidy will automatically find and use the .clang-tidy config file
set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_EXECUTABLE}")
endif()

# Amalgamation flags
set(AMALGAMATION_FLAGS " -Wno-unused-but-set-variable -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_STAT4 -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_SESSION -DSQLITE_ENABLE_PREUPDATE_HOOK -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT -DSQLITE_ENABLE_NOOP_UPDATE -DSQLITE_MUTEX_ALERT_MILLISECONDS=20 -DHAVE_USLEEP=1 -DSQLITE_MAX_MMAP_SIZE=17592186044416ull -DSQLITE_SHARED_MAPPING -DSQLITE_ENABLE_NORMALIZE -DSQLITE_MAX_PAGE_COUNT=4294967294 -DSQLITE_DISABLE_PAGECACHE_OVERFLOW_STATS -DSQLITE_DEFAULT_CACHE_SIZE=-51200 -DSQLITE_MAX_FUNCTION_ARG=32767 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=0 -DSQLITE_ENABLE_WAL_BIGHASH -DSQLITE_ENABLE_WAL2NOCKSUM")

# Set our standard C++ compiler flags
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20 -DSQLITE_ENABLE_NORMALIZE -Wall -Werror -Wformat-security -Wno-unqualified-std-cast-call -Wno-sign-conversion -Wno-error=deprecated-declarations -Wunused-variable -fPIC")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c23 -fPIC ${AMALGAMATION_FLAGS}")

if(DEFINED ENV{BEDROCK_OPTIM_COMPILE_FLAG})
message(STATUS "Applying BEDROCK_OPTIM_COMPILE_FLAG compile flag: $ENV{BEDROCK_OPTIM_COMPILE_FLAG}")
# Adjust the optimization level without repeating it to avoid confusions
string(REGEX REPLACE "([\\/\\-]O3)" "$ENV{BEDROCK_OPTIM_COMPILE_FLAG}" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
string(REGEX REPLACE "([\\/\\-]O3)" "$ENV{BEDROCK_OPTIM_COMPILE_FLAG}" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
string(REGEX REPLACE "([\\/\\-]O2)" "$ENV{BEDROCK_OPTIM_COMPILE_FLAG}" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
string(REGEX REPLACE "([\\/\\-]O2)" "$ENV{BEDROCK_OPTIM_COMPILE_FLAG}" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}")
else()
# Modify compile flags to change optimization level from O3 to O2
string(REGEX REPLACE "([\\/\\-]O)3" "\\12" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
string(REGEX REPLACE "([\\/\\-]O)3" "\\12" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
endif()

# Check if Git is available. This is good practice.
find_package(Git QUIET)
if(Git_FOUND)
# Execute the git command and store the output in GIT_REVISION
execute_process(
COMMAND git rev-parse HEAD
OUTPUT_VARIABLE GIT_REVISION
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)

# Shorten the hash to the first 10 characters, similar to grep -o '^.\{10\}'
string(SUBSTRING "${GIT_REVISION}" 0 10 GIT_REVISION)
message(STATUS "Git Revision: ${GIT_REVISION}")
else()
message(WARNING "Git not found. Unable to get revision hash.")
set(GIT_REVISION 1)
endif()

option(ENABLE_BUILD_PROFILING "Enable build profiling" OFF)
if (ENABLE_BUILD_PROFILING)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ftime-trace")
endif()

# These files need GIT_REVISION
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/BedrockServer.cpp PROPERTIES COMPILE_OPTIONS "-DGIT_REVISION=${GIT_REVISION}")
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/main.cpp PROPERTIES COMPILE_OPTIONS "-DGIT_REVISION=${GIT_REVISION}")
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/plugins/MySQL.cpp PROPERTIES COMPILE_OPTIONS "-DGIT_REVISION=${GIT_REVISION}")

# These files are should ignore linting
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/test/lib/tpunit++.cpp PROPERTIES SKIP_LINTING ON)

# Set our include paths. We need this for the pre-processor to use to generate dependencies.
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/mbedtls/include)

# We use the same library paths and required libraries for all binaries.
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/mbedtls/library)

# We use our project directory as a search path so we don't need "../../../.." all over the place.
set(CMAKE_INCLUDE_CURRENT_DIR "ON")

# # Disable mbed tests and programs
# set(ENABLE_TESTING OFF CACHE BOOL "Build Mbed TLS tests")
# set(ENABLE_PROGRAMS OFF CACHE BOOL "Build Mbed TLS programs")

# # Add the mbedtls directory as a subdirectory.
# # This executes its CMakeLists.txt and creates the necessary targets (like mbedcrypto, mbedtls, mbedx509).
# add_subdirectory(mbedtls)

file(GLOB_RECURSE LIBSTUFF_SRC "${CMAKE_CURRENT_SOURCE_DIR}/libstuff/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/libstuff/qrf.c" "${CMAKE_CURRENT_SOURCE_DIR}/libstuff/sqlite3.c")
add_library(libstuff STATIC ${LIBSTUFF_SRC})
set_target_properties(libstuff PROPERTIES PREFIX "")

# Make an explicif list of files since the use of globs can be considered a bad practice
set(BEDROCK_SRC
"${CMAKE_CURRENT_SOURCE_DIR}/BedrockBlockingCommandQueue.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/BedrockCommand.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/BedrockCommandQueue.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/BedrockConflictManager.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/BedrockCore.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/BedrockPlugin.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/BedrockServer.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/BedrockTimeoutCommandQueue.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/PageLockGuard.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/VMTouch.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/benchmarks/SDeburrBench.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/benchmarks/main.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/plugins/Cache.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/plugins/DB.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/plugins/Jobs.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/plugins/MySQL.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/sqlitecluster/SQLite.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/sqlitecluster/SQLiteClusterMessenger.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/sqlitecluster/SQLiteCommand.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/sqlitecluster/SQLiteCore.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/sqlitecluster/SQLiteNode.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/sqlitecluster/SQLitePeer.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/sqlitecluster/SQLitePool.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/sqlitecluster/SQLiteUtils.cpp"
)

add_library(libbedrock STATIC ${BEDROCK_SRC})
set_target_properties(libbedrock PROPERTIES PREFIX "")# OUTPUT_NAME auth LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/auth")
target_link_libraries(libbedrock PUBLIC libstuff)
target_link_libraries(libbedrock INTERFACE dl pcre2-8 pthread mbedtls mbedx509 mbedcrypto z m)

# Build bedrock executable
add_executable(bedrock "${CMAKE_CURRENT_SOURCE_DIR}/main.cpp")
target_link_libraries(bedrock PRIVATE libbedrock)

# Common files for tests and cluster tests (compile to object library and link after)
file(GLOB_RECURSE LIBTEST_SRC "${CMAKE_CURRENT_SOURCE_DIR}/test/lib/*.cpp")
add_library(libtest_objects OBJECT ${LIBTEST_SRC})

# Test only include main, tests and lib (should not include clustertest)
file(GLOB_RECURSE TESTS_SRC "${CMAKE_CURRENT_SOURCE_DIR}/test/tests/*.cpp")
add_executable(test "${TESTS_SRC}" "${CMAKE_CURRENT_SOURCE_DIR}/test/main.cpp")
target_link_libraries(test PRIVATE libbedrock libtest_objects)

# And the same for cluster tests minus the ordinary tests
file(GLOB_RECURSE CLUSTERTEST_SRC "${CMAKE_CURRENT_SOURCE_DIR}/test/clustertest/*.cpp")
add_executable(clustertest "${CLUSTERTEST_SRC}" "${CMAKE_CURRENT_SOURCE_DIR}/test/tests/jobs/JobTestHelper.cpp")
target_link_libraries(clustertest PRIVATE libbedrock libtest_objects)

# Benchmarks binary (separate from unit tests) under top-level benchmarks/
file(GLOB_RECURSE BENCH_SRC "${CMAKE_CURRENT_SOURCE_DIR}/benchmarks/*.cpp")
add_executable(bench "${BENCH_SRC}" "${CMAKE_CURRENT_SOURCE_DIR}/test/lib/tpunit++.cpp")
target_link_libraries(bench PRIVATE libbedrock)

# The rule to build TestPlugin
add_library(testplugin SHARED "test/clustertest/testplugin/TestPlugin.cpp" "test/clustertest/testplugin/ExternPointer.cpp")
set_target_properties(testplugin PROPERTIES PREFIX "" OUTPUT_NAME testplugin LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
target_compile_options(testplugin PRIVATE -Wall -Werror -Wformat-security -Wno-unqualified-std-cast-call -Wno-sign-conversion -fPIC)
target_link_libraries(testplugin INTERFACE libbedrock)

4 changes: 2 additions & 2 deletions libstuff/SDeburr.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ class SDeburr {
static constexpr array<const char*, 0x0180> UNICODE_TO_ASCII_MAP = []() constexpr {
array<const char*, 0x0180> map = {};

auto mapCodePoints = [&map](const char* value, vector<char32_t> codePoints) {
for (auto codePoint : codePoints) {
auto mapCodePoints = [&map](const char* value, const vector<char32_t>& codePoints) {
for (const auto& codePoint : codePoints) {
map[codePoint] = value;
}
};
Expand Down
14 changes: 11 additions & 3 deletions libstuff/libstuff.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ void SSyslogSocketDirect(int priority, const char *format, ...) {
// and treats them as part of the message.
string messageHeader = "<" + to_string(8 + priority) + ">" + SProcessName + ": ";
thread_local char messageBuffer[MAX_MESSAGE_SIZE];
strcpy(messageBuffer, messageHeader.c_str());
memcpy(messageBuffer, messageHeader.c_str(), min(messageHeader.size(), (size_t) MAX_MESSAGE_SIZE));
va_list argptr;
va_start(argptr, format);
int bytesWritten = vsnprintf(messageBuffer + messageHeader.size(), MAX_MESSAGE_SIZE - messageHeader.size(), format, argptr);
Expand Down Expand Up @@ -2946,15 +2946,23 @@ string SREReplace(const string& regExp, const string& input, const string& repla
output = (char*)malloc(outSize);
} else if (result < 0) {
SHMMM("Regex replacement failed with result " << result << ", returning nothing.");
if (output) {
free(output);
}
output = (char*)malloc(1);
*output = 0;
break;
}
}
string outputString(output);

string outputString;
if (output) {
outputString = output;
free(output);
}

pcre2_code_free(re);
pcre2_match_context_free(matchContext);
free(output);

return outputString;
}
Expand Down
2 changes: 1 addition & 1 deletion test/clustertest/tests/ConflictSpamTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ struct ConflictSpamTest : tpunit::TestFixture {
query["value"] = "sent-" + to_string(cmdNum);

// Ok, send.
string result = brtester.executeWaitVerifyContent(query);
brtester.executeWaitVerifyContent(query);
}
}

Expand Down
4 changes: 2 additions & 2 deletions test/clustertest/tests/FutureExecutionTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ struct FutureExecutionTest : tpunit::TestFixture {
// Three seconds from now.
query["commandExecuteTime"] = to_string(STimeNow() + 3000000);
query["Query"] = "INSERT INTO test VALUES(" + SQ(50011) + ", " + SQ("sent_by_leader") + ");";
string result = brtester.executeWaitVerifyContent(query, "202");
string result = brtester.executeWaitVerifyContent(query, "202");

// Ok, Now let's wait a second
sleep(1);
Expand Down Expand Up @@ -75,7 +75,7 @@ struct FutureExecutionTest : tpunit::TestFixture {

// And, there's a query to run, too, I guess.
query["Query"] = "SELECT 1;";
string result = brtester.executeWaitVerifyContent(query, "555 Timeout");
brtester.executeWaitVerifyContent(query, "555 Timeout");
}

} __FutureExecutionTest;
5 changes: 4 additions & 1 deletion test/clustertest/tests/JobIDTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ struct JobIDTest : tpunit::TestFixture {
sleep(1);
}

// make sure it actually succeeded.
// Make sure it actually succeeded.
ASSERT_TRUE(success);

// Create a job in the follower
Expand All @@ -77,6 +77,9 @@ struct JobIDTest : tpunit::TestFixture {
sleep(1);
}

// Make sure it also succeeded.
ASSERT_TRUE(success);

// Create a new job in leader.
response = leader.executeWaitVerifyContentTable(createCmd);

Expand Down
2 changes: 1 addition & 1 deletion test/clustertest/tests/LeadingTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ struct LeadingTest : tpunit::TestFixture {
BedrockTester& newLeader = tester->getTester(1);
SData cmd("httpstimeout");
cmd["Connection"] = "forget";
auto result = newLeader.executeWaitVerifyContent(cmd, "202");
newLeader.executeWaitVerifyContent(cmd, "202");
}

void restoreLeader()
Expand Down
2 changes: 1 addition & 1 deletion test/clustertest/tests/UpgradeDBTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ struct UpgradeDBTest : tpunit::TestFixture {
// This just verifies that the dbupgrade table was created by TestPlugin.
SData query("Query");
query["Query"] = "INSERT INTO dbupgrade VALUES(" + SQ(1 + i) + ", " + SQ("val") + ");";
string result = brtester.executeWaitVerifyContent(query, "200");
brtester.executeWaitVerifyContent(query, "200");
}
}
} __UpgradeDBTest;
Expand Down
5 changes: 3 additions & 2 deletions test/lib/BedrockTester.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ BedrockTester::~BedrockTester() {
delete _db;
}
if (_serverPID) {
stopServer();
// Note that we cannot call the subclass override in destructors
BedrockTester::stopServer();
}

SFileExists(_args["-db"].c_str()) && unlink(_args["-db"].c_str());
Expand Down Expand Up @@ -304,7 +305,7 @@ string BedrockTester::startServer(bool wait) {

// We've successfully opened a socket, so let's try and send a command.
SData status("Status");
auto result = executeWaitMultipleData({status}, 1, !wait);
auto result = executeWaitMultipleData({status}, 1, !wait); //NOLINT(clang-analyzer-optin.cplusplus.VirtualCall)
if (result[0].methodLine == "200 OK") {
return result[0].content;
}
Expand Down
25 changes: 9 additions & 16 deletions test/tests/LibStuffTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ struct LibStuff : tpunit::TestFixture {
"Content-Length: 5\r"
"\r\n"
"too short"; // only 'too s' read.
processed = SParseHTTP(recvBuffer.c_str(), recvBuffer.length(), methodLine, headers, content);
SParseHTTP(recvBuffer.c_str(), recvBuffer.length(), methodLine, headers, content);
ASSERT_EQUAL(methodLine, "some method line");
ASSERT_EQUAL(content, "too s");

Expand All @@ -253,7 +253,7 @@ struct LibStuff : tpunit::TestFixture {
"0123456789\r\n"
"0\r\n"
"\r\n";
processed = SParseHTTP(recvBuffer.c_str(), recvBuffer.length(), methodLine, headers, content);
SParseHTTP(recvBuffer.c_str(), recvBuffer.length(), methodLine, headers, content);
ASSERT_EQUAL(methodLine, "some method line");
ASSERT_EQUAL(content, "abcde0123456789");

Expand All @@ -262,7 +262,7 @@ struct LibStuff : tpunit::TestFixture {
"\r\n"
"6\r\n" // one too long.
"abcde";
processed = SParseHTTP(recvBuffer.c_str(), recvBuffer.length(), methodLine, headers, content);
SParseHTTP(recvBuffer.c_str(), recvBuffer.length(), methodLine, headers, content);
ASSERT_EQUAL(methodLine, "");
ASSERT_EQUAL(content, "");

Expand All @@ -271,7 +271,7 @@ struct LibStuff : tpunit::TestFixture {
"\r\n"
"5\r\n" // exact without end.
"abcde";
processed = SParseHTTP(recvBuffer.c_str(), recvBuffer.length(), methodLine, headers, content);
SParseHTTP(recvBuffer.c_str(), recvBuffer.length(), methodLine, headers, content);
ASSERT_EQUAL(methodLine, "");
ASSERT_EQUAL(content, "");

Expand All @@ -288,7 +288,7 @@ struct LibStuff : tpunit::TestFixture {
"header2: value2a\n"
"header3: value3\n"
"\r\n";
processed = SParseHTTP(recvBuffer.c_str(), recvBuffer.length(), methodLine, headers, content);
SParseHTTP(recvBuffer.c_str(), recvBuffer.length(), methodLine, headers, content);
ASSERT_EQUAL(methodLine, "some method line");
ASSERT_EQUAL(headers["header1"], "value1");
ASSERT_EQUAL(headers["header2"], "value2a");
Expand All @@ -304,7 +304,7 @@ struct LibStuff : tpunit::TestFixture {
"0123456789\r\n"
"0\r\n"
"\r\n";
processed = SParseHTTP(recvBuffer.c_str(), recvBuffer.length(), methodLine, headers, content);
SParseHTTP(recvBuffer.c_str(), recvBuffer.length(), methodLine, headers, content);
ASSERT_EQUAL(methodLine, "");
ASSERT_EQUAL(content, "");

Expand All @@ -317,7 +317,7 @@ struct LibStuff : tpunit::TestFixture {
"0123456789\r\n"
"0\r\n"
"\r\n";
processed = SParseHTTP(recvBuffer.c_str(), recvBuffer.length(), methodLine, headers, content);
SParseHTTP(recvBuffer.c_str(), recvBuffer.length(), methodLine, headers, content);
ASSERT_EQUAL(methodLine, "some method line y");
ASSERT_EQUAL(content, "abcde"); // partial fail.

Expand All @@ -330,7 +330,7 @@ struct LibStuff : tpunit::TestFixture {
"0123456789\r\n"
"0\r\n";
// "\r\n"; // missing last new line.
processed = SParseHTTP(recvBuffer.c_str(), recvBuffer.length(), methodLine, headers, content);
SParseHTTP(recvBuffer.c_str(), recvBuffer.length(), methodLine, headers, content);
ASSERT_EQUAL(methodLine, "");
ASSERT_EQUAL(content, "");
}
Expand Down Expand Up @@ -644,7 +644,6 @@ struct LibStuff : tpunit::TestFixture {
string timeStamp3 = "2020-06-17";
string timeStamp4 = "2020-07-07";
string timeStamp5 = "2020-11-11";
string timeStamp6 = "2020-01-01";
string octalTimestamp = "2019-09-03";
string notATimeStamp = "this is not a timestamp";

Expand Down Expand Up @@ -761,13 +760,7 @@ struct LibStuff : tpunit::TestFixture {
ASSERT_EQUAL(result[2]["value"], "value3");

// Validate our exception handling.
bool threw = false;
try {
string s = result[0]["notacolumn"];
} catch (const SException& e) {
threw = true;
}
ASSERT_TRUE(threw);
ASSERT_ANY_THROW(result[0]["notacolumn"]);

// Test aliased names.
db.beginTransaction(SQLite::TRANSACTION_TYPE::SHARED);
Expand Down
Loading