Finish migrating to IOCore framework and its pre-built classes

This commit is contained in:
S David 2024-03-30 17:40:26 -04:00
parent 582d736ee5
commit 21a6286af3
15 changed files with 101 additions and 456 deletions

View File

@ -26,6 +26,7 @@ project(mdml
)
include(CPM)
# Detect if the current operating system is Linux
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
option(USE_BOOST_STACKTRACE "Use Boost for stacktraces, instead of execinfo.h" ON)
@ -86,8 +87,10 @@ CPMFindPackage(NAME nlohmann_json
"BUILD_TESTING OFF"
)
CPMAddPackage(NAME IOCore
GIT_REPOSITORY "https://gitea.beniquez.me/sdaveb/IOCore.git"
GIT_TAG v0.2.7
GIT_REPOSITORY "file://${CMAKE_CURRENT_SOURCE_DIR}/../IOCore"
GIT_TAG HEAD
#GIT_REPOSITORY "https://gitea.beniquez.me/sdaveb/IOCore.git"
#GIT_TAG v0.2.7
OPTIONS
"BUILD_SHARED_LIBS OFF"
"BUILD_TESTING OFF"
@ -125,6 +128,12 @@ if (BUILD_TESTING)
endif()
endif()
add_data_target(NAME prod-data
PATH "${mdml_CMAKE_SOURCE_DIR}/data"
DESTINATION ${CMAKE_BINARY_DIR}/tests/data
)
set_artifact_dir(${CMAKE_BINARY_DIR}/out)
add_subdirectory(src)
if (BUILD_TESTING)

View File

@ -5,6 +5,7 @@
# This module defines a function prevent_in_source_build() that prevents in-source builds
# and sets a policy for CMake version 3.24.0 and above.
function(prevent_in_source_build)
# Prevent in-source builds
if (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)
@ -12,6 +13,13 @@ function(prevent_in_source_build)
endif()
endfunction()
function(set_artifact_dir path)
set(ARTIFACT_DIR ${path})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${ARTIFACT_DIR}/lib PARENT_SCOPE)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${ARTIFACT_DIR}/lib PARENT_SCOPE)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${ARTIFACT_DIR}/bin PARENT_SCOPE)
endfunction()
function(disable_deprecated_features)
# Use new timestamp behavior when extracting files archives
if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
@ -27,10 +35,11 @@ else()
endif()
endfunction()
function(include_usr_local)
option(LOCAL_INCLUDES "Automatically add /usr/local/include to include search paths" ON)
option(LOCAL_LIBS "Automatically add /usr/local/lib to linker search paths" ON)
function(include_usr_local)
if (LOCAL_INCLUDES)
if(APPLE)
if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
@ -55,4 +64,53 @@ function(include_usr_local)
endif()
endfunction()
function(add_data_target)
set(options "")
set(oneValueArgs NAME PATH DESTINATION)
set(multiValueArgs "")
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
# Assign positional arguments if NAME, PATH, and DESTINATION were not named explicitly
list(LENGTH ARGN arg_len)
if(arg_len EQUAL 3)
list(GET ARGN 0 ARG_NAME)
list(GET ARGN 1 ARG_PATH)
list(GET ARGN 2 ARG_DESTINATION)
elseif(arg_len EQUAL 2)
list(GET ARGN 0 ARG_PATH)
list(GET ARGN 1 ARG_DESTINATION)
endif()
# Generate the target name
if(ARG_NAME)
set(target_name "copy_${ARG_NAME}")
else()
get_filename_component(filename "${ARG_PATH}" NAME_WE)
set(target_name "copy_${filename}")
endif()
# Create a custom target for the copy operation
add_custom_target(${target_name} ALL
COMMENT "Copying ${ARG_PATH} to ${ARG_DESTINATION}"
)
if (IS_DIRECTORY "${ARG_PATH}")
# Add a custom command to actually perform the file copy
add_custom_command(
TARGET ${target_name} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory "${ARG_PATH}" "${ARG_DESTINATION}"
DEPENDS "${ARG_PATH}"
COMMENT "Copied ${ARG_PATH} to ${ARG_DESTINATION}"
)
else()
add_custom_command(
TARGET ${target_name} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy "${ARG_PATH}" "${ARG_DESTINATION}"
DEPENDS "${ARG_PATH}"
COMMENT "Copied ${ARG_PATH} to ${ARG_DESTINATION}"
)
endif()
endfunction()
# vim: ts=4 sts=4 sw=4 noet :

View File

@ -1,55 +0,0 @@
/* Application.hpp
* Copyright © 2023 Saul D. Beniquez
* License: Mozilla Public License v. 2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v.2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "types.hpp"
#include <string>
#include <vector>
namespace mdml {
// NOLINTNEXTLINE(readability-identifier-naming)
class Application {
public:
virtual ~Application() = default;
// Disabled constructors and operators @{
Application(Application&&) = delete;
Application(const Application&) = delete;
auto operator=(const Application&) -> Application& = delete;
auto operator=(Application&&) -> Application& = delete;
// @}
// Getters
auto getArguments() -> const std::vector<std::string>&
{
return this->arguments;
}
auto getEnvironment() -> const Dictionary<const std::string>&
{
return this->environment_variables;
}
virtual auto run() -> int = 0;
protected:
Application(int argc, c::const_string argv[], c::const_string env[]);
void parse_arguments(int argc, c::const_string argv[]);
void create_env_dictionary(c::const_string envp[]);
std::vector<std::string> arguments;
Dictionary<const std::string> environment_variables;
};
}
// clang-format off
// vim: set foldmethod=marker foldmarker=@{,@} textwidth=81 ts=8 sts=0 sw=8 noexpandtab ft=cpp.doxygen :

View File

@ -20,6 +20,8 @@
namespace mdml {
using IOCore::Application;
class IRouteHandler;
class CgiApplication

View File

@ -1,46 +0,0 @@
/* exceptions.hpp
* Copyright © 2020 Saul D. Beniquez
* License: Mozilla Public License v. 2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "private/debuginfo.hpp"
#include "types.hpp"
namespace mdml {
namespace messages {
constexpr auto kExceptionDefault = "An exception has ocurred!";
}
class Exception : public std::exception {
public:
Exception(c::const_string _message = messages::kExceptionDefault);
Exception(const std::string& message);
Exception(const mdml::Exception& other) = default;
Exception(const std::exception& inner);
auto operator=(const Exception&) -> Exception& = delete;
auto what() const noexcept -> const char* override;
auto stacktrace() const noexcept -> const std::string&;
private:
void build_what_message();
std::string error_message;
std::string what_message;
std::string stack_trace;
std::exception_ptr inner_exception_ptr;
};
}
// clang-format off
// vim: set foldmethod=marker foldmarker=@{,@} textwidth=80 ts=8 sts=0 sw=8 noexpandtab ft=cpp.doxygen :

View File

@ -14,23 +14,13 @@
#include "Exception.hpp"
#include <cstring>
#include <sstream>
#define ASSERT(condition) \
if (condition == false) { \
mdml::assert_impl(#condition); \
}
#define ASSERT_MSG(condition, msg) \
if (condition == false) { \
mdml::assert_impl(#condition, msg); \
}
namespace mdml {
inline void
assert_impl(c::const_string failed_condition, c::const_string assert_reason = "")
{
std::stringstream assert_buffer;
assert_buffer << failed_condition << " is false!";
@ -39,7 +29,7 @@ assert_impl(c::const_string failed_condition, c::const_string assert_reason = ""
<< "\tassert_resion: " << assert_reason;
}
assert_buffer << std::flush;
throw Exception(assert_buffer.str());
throw IOCore::Exception(assert_buffer.str());
}
}

View File

@ -1,68 +0,0 @@
/* Application.cpp
* Copyright © 2023 Saul D. Beniquez
* License: Mozilla Public License v. 2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v.2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "Application.hpp"
#include "Exception.hpp"
#include "types.hpp"
#include "private/assert.hpp"
#include <string>
#include <string_view>
#include <vector>
using namespace mdml;
Application::Application(int argc, c::const_string argv[], c::const_string env[])
: arguments(), environment_variables()
{
ASSERT_MSG(
argv != nullptr, "Application constructor has null argv parameter"
);
ASSERT_MSG(
env != nullptr, "Application constructor has null env parameter"
);
this->parse_arguments(argc, argv);
this->create_env_dictionary(env);
}
void
Application::parse_arguments(int argc, c::const_string argv[])
{
for (count_t index = 0; index < argc; ++index) {
this->arguments.push_back(argv[index]);
}
}
void
Application::create_env_dictionary(c::const_string envp[])
{
for (count_t num = 0; envp[num] != nullptr; ++num) {
auto encoded_pair = std::string_view(envp[num]);
size_t equal_character_pos;
auto not_found = encoded_pair.npos;
equal_character_pos = encoded_pair.find('=');
if (equal_character_pos != not_found) {
auto key = encoded_pair.substr(0, equal_character_pos);
auto val = encoded_pair.substr(equal_character_pos + 1);
this->environment_variables.insert(
std::make_pair(key, val)
);
}
}
}
// clang-format off
// vim: set foldmethod=marker foldmarker=@{,@} textwidth=80 ts=8 sts=0 sw=8 noexpandtab ft=cpp.doxygen :

View File

@ -7,8 +7,8 @@
* obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "Application.hpp"
#include "CgiApplication.hpp"
#include "Application.hpp"
#include "IRouteHandler.hpp"
#include "Exception.hpp"
@ -88,7 +88,7 @@ CgiApplication::processRequest() -> Result<std::string>
return { Error, buffer.str() };
}
} catch (std::exception& stdexcept) {
throw Exception(stdexcept);
throw IOCore::Exception(stdexcept);
}
}

View File

@ -1,110 +0,0 @@
/* exceptions.hpp
* Copyright © 2020 Saul D. Beniquez
* License: Mozilla Public License v. 2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include "Exception.hpp"
#include "private/debuginfo.hpp"
#include "types.hpp"
#include <algorithm>
#include <exception>
#include <iostream>
#include <optional>
#include <sstream>
#include <stdexcept>
#include <string>
using namespace mdml;
constexpr unsigned kDefaultStackframesToStrip = 3;
// Helper classes and functions. @{
Exception::Exception(c::const_string error_message)
: std::exception()
, error_message(error_message)
, what_message()
, stack_trace(mdml::generate_stacktrace(kDefaultStackframesToStrip))
, inner_exception_ptr()
{
build_what_message();
}
Exception::Exception(const std::string& error_message)
: std::exception()
, error_message(error_message)
, what_message()
, stack_trace(mdml::generate_stacktrace(kDefaultStackframesToStrip))
, inner_exception_ptr()
{
build_what_message();
}
Exception::Exception(const std::exception& inner)
: std::exception(inner)
, error_message(inner.what())
, what_message()
, inner_exception_ptr(std::make_exception_ptr(&inner))
, stack_trace(mdml::generate_stacktrace(kDefaultStackframesToStrip))
{
build_what_message();
}
auto
Exception::what() const noexcept -> const char*
{
return this->what_message.c_str();
}
auto
Exception::stacktrace() const noexcept -> const std::string&
{
return this->stack_trace;
}
auto
prepend_tabs_to_lines(const std::string& input) -> std::string
{
std::ostringstream results_buffer;
std::istringstream input_buffer(input);
// Function to add a tab character before each line
auto add_tab_before_line = [&results_buffer](const std::string& line) {
results_buffer << '\t' << line << '\n';
};
// Process each line and add a tab character before it
std::string line;
while (std::getline(input_buffer, line)) {
add_tab_before_line(line);
}
return results_buffer.str();
}
void
Exception::build_what_message()
{
std::stringstream buffer;
this->what_message = error_message;
std::string indented_stacktrace =
prepend_tabs_to_lines(this->stack_trace);
buffer << "mdml::Exception::what(): { " << std::endl
<< "\terror: " << error_message.c_str() << std::endl
<< "\tstack_trace: " << std::endl
<< indented_stacktrace << std::endl
<< "};" << std::endl;
this->what_message = buffer.str().c_str();
}
// clang-format off
// vim: set foldmethod=marker foldmarker=@{,@} textwidth=80 ts=8 sts=0 sw=8 noexpandtab ft=cpp.doxygen :

View File

@ -8,14 +8,13 @@
*/
#include "MarkdownRouteHandler.hpp"
#include "Application.hpp"
#include "Exception.hpp"
#include "IRouteHandler.hpp"
#include "types.hpp"
#include "private/assert.hpp"
#include "cmark.h"
#include <cmark.h>
#include <filesystem>
#include <fstream>
@ -138,7 +137,7 @@ MarkdownRouteHandler::load_document(
{
if (!fs::exists(document_path) || !fs::is_regular_file(document_path)) {
auto error = "File not found: " + document_path.string();
throw Exception(error);
throw IOCore::Exception(error);
}
try {
@ -156,12 +155,12 @@ MarkdownRouteHandler::load_document(
<< document_path.filename() << "', at path "
<< document_path.parent_path() << ". ")
.str();
throw Exception(error_message);
throw IOCore::Exception(error_message);
}
} catch (const Exception& e) {
} catch (const IOCore::Exception& e) {
throw e;
} catch (const std::exception& e) {
throw Exception(e);
throw IOCore::Exception(e);
}
}

View File

@ -1,143 +0,0 @@
/* debuginfo.h
* Copyright © 2020-2023 Saul D. Beniquez
* License: Mozilla Public License v. 2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include "private/platform.hpp"
#include "private/debuginfo.hpp"
#if defined(HAVE_EXECINFO_H) && !defined(__linux__)
#include <cxxabi.h>
#include <execinfo.h>
#else
#define BOOST_STACKTRACER
#include <boost/stacktrace.hpp>
#endif
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
namespace mdml {
namespace {
// Extracts the mangled symbol from a string like <func_name+0x34>
auto
extract_mangled_symbol(const std::string& input) -> std::string
{
std::string result;
bool inside_angle_brackets = false;
for (char c : input) {
if (c == '<') {
inside_angle_brackets = true;
continue;
}
if (c == '>') {
inside_angle_brackets = false;
continue;
}
if (c == '+') {
break;
}
if (inside_angle_brackets) {
result += c;
}
}
return result;
}
}
// There are a lot of C and platform-specific hacks contained within
// I am sorry. 🤡
auto
generate_stacktrace(unsigned short framesToRemove) -> std::string
{
std::stringstream buffer;
#ifndef BOOST_STACKTRACER
void* callstack[128];
int i, frames = backtrace(callstack, 128);
char** strs = backtrace_symbols(callstack, frames);
size_t columns_to_print = 0;
// preconfigure column length for certain platforms
if (Platform::kFreebsd) {
columns_to_print = 2;
} else if (Platform::kMacos) {
columns_to_print = 4;
}
if (framesToRemove == frames) {
framesToRemove = 0;
}
for (i = framesToRemove; i < frames; ++i) {
std::string word;
std::stringstream line_stream(strs[i]);
std::vector<std::string> wordlist;
// Create a list of words for this stack trace line
while (line_stream >> word) {
if (columns_to_print != 0 &&
(word.find('<') != word.npos &&
word.find('>') != word.npos)) {
auto extracted_symbol =
extract_mangled_symbol(word);
word = extracted_symbol;
}
wordlist.push_back(word);
}
// if columns_to_print is still 0, assign it to the list length
// It is only pre-configured for certain platforms, see above
if (!columns_to_print) {
columns_to_print = wordlist.size();
}
// Process the extracted words one at a time and format the
// stack trace string
for (unsigned pos = 0; pos < columns_to_print; ++pos) {
auto word = wordlist[pos];
int status;
char* demangled_symbol = abi::__cxa_demangle(
word.c_str(), nullptr, nullptr, &status
);
if (status == 0) {
buffer << demangled_symbol << '\t';
std::free(demangled_symbol);
} else {
buffer << word << '\t';
}
}
buffer << std::endl;
}
std::free(strs);
#else /*@{*/
buffer << boost::stacktrace::stacktrace() << std::flush;
#endif /*@}*/
return buffer.str();
}
void
print_cmdline(int argc, const char* argv[])
{
int i;
std::cout << "Command-line received" << std::endl;
for (i = 0; i < argc; ++i)
std::cout << argv[i] << " ";
std::cout << std::endl;
}
}
// clang-format off
// vim: set foldmethod=marker foldmarker=@{,@} textwidth=80 ts=8 sts=0 sw=8 noexpandtab ft=cpp.doxygen :

View File

@ -27,9 +27,9 @@ inline const char* LaunchParameters::env[] = {
BEGIN_TEST_SUITE("Application-test")
{
using namespace mdml;
using IOCore::Application;
struct DerivedApplication : public mdml::Application {
struct DerivedApplication : public Application {
DerivedApplication(
int argc = 0, c::const_string argv[] = {},
c::const_string env[] = {}
@ -71,7 +71,9 @@ BEGIN_TEST_SUITE("Application-test")
DerivedApplication obj(0, nullptr, nullptr);
};
REQUIRE_THROWS_AS(incorrect_construction(), Exception);
REQUIRE_THROWS_AS(
incorrect_construction(), IOCore::Exception
);
}
}

View File

@ -31,12 +31,18 @@ include(Catch)
catch_discover_tests(test-runner)
# @}
add_data_target(NAME test-data
PATH "${mdml_CMAKE_SOURCE_DIR}/data"
DESTINATION ${CMAKE_BINARY_DIR}/tests/data
)
# Add a custom ctest target to the build system
add_custom_target(ctest
COMMAND ctest -C $CONFIGURATION --test-dir . --output-on-failure
)
add_dependencies(ctest
test-runner
copy_assets
copy_test-data
)
# vim: ft=cmake sw=4 ts=4 noet sts=4 foldmethod=marker foldmarker=@{,@} :

View File

@ -92,7 +92,7 @@ BEGIN_TEST_SUITE("CgiApplication")
1, SimulatedLaunch::argv, no_request_env
);
REQUIRE_THROWS_AS(
[&]() { test_app.processRequest(); }(), mdml::Exception
[&]() { test_app.processRequest(); }(), IOCore::Exception
);
}
@ -148,7 +148,7 @@ BEGIN_TEST_SUITE("CgiApplication")
auto handler = std::make_shared<BadRouteHandler>();
cgi_app.Routes["markdown"] = handler;
REQUIRE_THROWS_AS(cgi_app.processRequest(), mdml::Exception);
REQUIRE_THROWS_AS(cgi_app.processRequest(), IOCore::Exception);
// REQUIRE(cgi_app.processRequest().isError());
}
}

View File

@ -28,12 +28,12 @@ throw_local_exception()
auto local = std::logic_error("This is a stack variable");
// Stack variables should not be used outside their stack frames.
throw mdml::Exception(local);
throw IOCore::Exception(local);
}
BEGIN_TEST_SUITE("Exception")
{
using namespace mdml;
using namespace IOCore;
auto throw_an_exception = []() {
throw Exception("An error occurred!!");
@ -91,7 +91,8 @@ BEGIN_TEST_SUITE("Exception")
REQUIRE_THAT(
obj.what(),
Match::ContainsSubstring(
messages::kExceptionDefault, CaseSensitive::Yes
IOCore::Exception::kDefault_Error,
CaseSensitive::Yes
)
);
}
@ -108,7 +109,7 @@ BEGIN_TEST_SUITE("Exception")
REQUIRE_THAT(
test_object_one.what(),
!Match::ContainsSubstring(
messages::kExceptionDefault
IOCore::Exception::kDefault_Error
)
);
}