diff --git a/include/Common.h b/include/Common.h index 8b806d5..fe5c4d5 100644 --- a/include/Common.h +++ b/include/Common.h @@ -15,6 +15,8 @@ extern TSentry Sentry; #include #include +#include + #include "Compat.h" #include "TConsole.h" @@ -139,88 +141,115 @@ void RegisterThread(const std::string& str); #define _line std::to_string(__LINE__) #define _in_lambda (std::string(__func__) == "operator()") -// we would like the full function signature 'void a::foo() const' -// on windows this is __FUNCSIG__, on GCC it's __PRETTY_FUNCTION__, -// feel free to add more -#if defined(WIN32) -#define _function_name std::string(__FUNCSIG__) -#elif defined(__unix) || defined(__unix__) -#define _function_name std::string(__PRETTY_FUNCTION__) -#else -#define _function_name std::string(__func__) -#endif - -#ifndef NDEBUG -#define DEBUG -#endif - -#if defined(DEBUG) - -// if this is defined, we will show the full function signature infront of -// each info/debug/warn... call instead of the 'filename:line' format. -#if defined(BMP_FULL_FUNCTION_NAMES) -#define _this_location (ThreadName() + _function_name + " ") -#else -#define _this_location (ThreadName() + _file_basename + ":" + _line + " ") -#endif -#define SU_RAW SSU_UNRAW - -#else // !defined(DEBUG) - -#define SU_RAW RAWIFY(SSU_UNRAW) -#define _this_location (ThreadName()) - -#endif // defined(DEBUG) - -#define beammp_warn(x) Application::Console().Write(_this_location + std::string("[WARN] ") + (x)) -#define beammp_info(x) Application::Console().Write(_this_location + std::string("[INFO] ") + (x)) -#define beammp_error(x) \ - do { \ - Application::Console().Write(_this_location + std::string("[ERROR] ") + (x)); \ - Sentry.AddErrorBreadcrumb((x), _file_basename, _line); \ - } while (false) -#define beammp_lua_error(x) \ - do { \ - Application::Console().Write(_this_location + std::string("[LUA ERROR] ") + (x)); \ - } while (false) -#define beammp_lua_warn(x) \ - do { \ - Application::Console().Write(_this_location + std::string("[LUA WARN] ") + (x)); \ - } while (false) -#define luaprint(x) Application::Console().Write(_this_location + std::string("[LUA] ") + (x)) -#define beammp_debug(x) \ - do { \ - if (Application::Settings.DebugModeEnabled) { \ - Application::Console().Write(_this_location + std::string("[DEBUG] ") + (x)); \ - } \ - } while (false) -#define beammp_event(x) \ - do { \ - if (Application::Settings.DebugModeEnabled) { \ - Application::Console().Write(_this_location + std::string("[EVENT] ") + (x)); \ - } \ - } while (false) // for those times when you just need to ignore something :^) // explicity disables a [[nodiscard]] warning #define beammp_ignore(x) (void)x -// trace() is a debug-build debug() -#if defined(DEBUG) -#define beammp_trace(x) \ - do { \ - if (Application::Settings.DebugModeEnabled) { \ - Application::Console().Write(_this_location + std::string("[TRACE] ") + (x)); \ - } \ - } while (false) -#else -#define beammp_trace(x) -#endif // defined(DEBUG) -#define beammp_errorf(...) beammp_error(fmt::format(__VA_ARGS__)) -#define beammp_infof(...) beammp_info(fmt::format(__VA_ARGS__)) -#define beammp_warnf(...) beammp_warn(fmt::format(__VA_ARGS__)) -#define beammp_tracef(...) beammp_trace(fmt::format(__VA_ARGS__)) -#define beammp_lua_errorf(...) beammp_lua_error(fmt::format(__VA_ARGS__)) -#define beammp_lua_warnf(...) beammp_lua_warn(fmt::format(__VA_ARGS__)) +// clang-format off +#ifdef DOCTEST_CONFIG_DISABLE + + // we would like the full function signature 'void a::foo() const' + // on windows this is __FUNCSIG__, on GCC it's __PRETTY_FUNCTION__, + // feel free to add more + #if defined(WIN32) + #define _function_name std::string(__FUNCSIG__) + #elif defined(__unix) || defined(__unix__) + #define _function_name std::string(__PRETTY_FUNCTION__) + #else + #define _function_name std::string(__func__) + #endif + + #ifndef NDEBUG + #define DEBUG + #endif + + #if defined(DEBUG) + + // if this is defined, we will show the full function signature infront of + // each info/debug/warn... call instead of the 'filename:line' format. + #if defined(BMP_FULL_FUNCTION_NAMES) + #define _this_location (ThreadName() + _function_name + " ") + #else + #define _this_location (ThreadName() + _file_basename + ":" + _line + " ") + #endif + + #endif // defined(DEBUG) + + #define beammp_warn(x) Application::Console().Write(_this_location + std::string("[WARN] ") + (x)) + #define beammp_info(x) Application::Console().Write(_this_location + std::string("[INFO] ") + (x)) + #define beammp_error(x) \ + do { \ + Application::Console().Write(_this_location + std::string("[ERROR] ") + (x)); \ + Sentry.AddErrorBreadcrumb((x), _file_basename, _line); \ + } while (false) + #define beammp_lua_error(x) \ + do { \ + Application::Console().Write(_this_location + std::string("[LUA ERROR] ") + (x)); \ + } while (false) + #define beammp_lua_warn(x) \ + do { \ + Application::Console().Write(_this_location + std::string("[LUA WARN] ") + (x)); \ + } while (false) + #define luaprint(x) Application::Console().Write(_this_location + std::string("[LUA] ") + (x)) + #define beammp_debug(x) \ + do { \ + if (Application::Settings.DebugModeEnabled) { \ + Application::Console().Write(_this_location + std::string("[DEBUG] ") + (x)); \ + } \ + } while (false) + #define beammp_event(x) \ + do { \ + if (Application::Settings.DebugModeEnabled) { \ + Application::Console().Write(_this_location + std::string("[EVENT] ") + (x)); \ + } \ + } while (false) + // trace() is a debug-build debug() + #if defined(DEBUG) + #define beammp_trace(x) \ + do { \ + if (Application::Settings.DebugModeEnabled) { \ + Application::Console().Write(_this_location + std::string("[TRACE] ") + (x)); \ + } \ + } while (false) + #else + #define beammp_trace(x) + #endif // defined(DEBUG) + + #define beammp_errorf(...) beammp_error(fmt::format(__VA_ARGS__)) + #define beammp_infof(...) beammp_info(fmt::format(__VA_ARGS__)) + #define beammp_warnf(...) beammp_warn(fmt::format(__VA_ARGS__)) + #define beammp_tracef(...) beammp_trace(fmt::format(__VA_ARGS__)) + #define beammp_lua_errorf(...) beammp_lua_error(fmt::format(__VA_ARGS__)) + #define beammp_lua_warnf(...) beammp_lua_warn(fmt::format(__VA_ARGS__)) + +#else // DOCTEST_CONFIG_DISABLE + + #define beammp_error(x) /* x */ + #define beammp_lua_error(x) /* x */ + #define beammp_warn(x) /* x */ + #define beammp_lua_warn(x) /* x */ + #define beammp_info(x) /* x */ + #define beammp_event(x) /* x */ + #define beammp_debug(x) /* x */ + #define beammp_trace(x) /* x */ + #define luaprint(x) /* x */ + #define beammp_errorf(...) beammp_error(fmt::format(__VA_ARGS__)) + #define beammp_infof(...) beammp_info(fmt::format(__VA_ARGS__)) + #define beammp_warnf(...) beammp_warn(fmt::format(__VA_ARGS__)) + #define beammp_tracef(...) beammp_trace(fmt::format(__VA_ARGS__)) + #define beammp_lua_errorf(...) beammp_lua_error(fmt::format(__VA_ARGS__)) + #define beammp_lua_warnf(...) beammp_lua_warn(fmt::format(__VA_ARGS__)) + +#endif // DOCTEST_CONFIG_DISABLE + +#if defined(DEBUG) + #define SU_RAW SSU_UNRAW +#else + #define SU_RAW RAWIFY(SSU_UNRAW) + #define _this_location (ThreadName()) +#endif + +// clang-format on void LogChatMessage(const std::string& name, int id, const std::string& msg); diff --git a/src/ArgsParser.cpp b/src/ArgsParser.cpp index f00548f..f0d6257 100644 --- a/src/ArgsParser.cpp +++ b/src/ArgsParser.cpp @@ -1,6 +1,7 @@ #include "ArgsParser.h" #include "Common.h" #include +#include void ArgsParser::Parse(const std::vector& ArgList) { for (const auto& Arg : ArgList) { @@ -92,3 +93,78 @@ void ArgsParser::ConsumeLongFlag(const std::string& Arg) { beammp_warn("Argument '" + Name + "' was supplied but isn't a known argument, so it is likely being ignored."); } } + +TEST_CASE("ArgsParser") { + ArgsParser parser; + + SUBCASE("Simple args") { + parser.RegisterArgument({ "a" }, ArgsParser::Flags::NONE); + parser.RegisterArgument({ "hello" }, ArgsParser::Flags::NONE); + parser.Parse({ "--a", "--hello" }); + CHECK(parser.Verify()); + CHECK(parser.FoundArgument({ "a" })); + CHECK(parser.FoundArgument({ "hello" })); + CHECK(parser.FoundArgument({ "a", "hello" })); + CHECK(!parser.FoundArgument({ "b" })); + CHECK(!parser.FoundArgument({ "goodbye" })); + } + + SUBCASE("No args") { + parser.RegisterArgument({ "a" }, ArgsParser::Flags::NONE); + parser.RegisterArgument({ "hello" }, ArgsParser::Flags::NONE); + parser.Parse({}); + CHECK(parser.Verify()); + CHECK(!parser.FoundArgument({ "a" })); + CHECK(!parser.FoundArgument({ "hello" })); + CHECK(!parser.FoundArgument({ "a", "hello" })); + CHECK(!parser.FoundArgument({ "b" })); + CHECK(!parser.FoundArgument({ "goodbye" })); + CHECK(!parser.FoundArgument({ "" })); + } + + SUBCASE("Value args") { + parser.RegisterArgument({ "a" }, ArgsParser::Flags::HAS_VALUE); + parser.RegisterArgument({ "hello" }, ArgsParser::Flags::HAS_VALUE); + parser.Parse({ "--a=5", "--hello=world" }); + CHECK(parser.Verify()); + REQUIRE(parser.FoundArgument({ "a" })); + REQUIRE(parser.FoundArgument({ "hello" })); + CHECK(parser.GetValueOfArgument({ "a" }).has_value()); + CHECK(parser.GetValueOfArgument({ "a" }).value() == "5"); + CHECK(parser.GetValueOfArgument({ "hello" }).has_value()); + CHECK(parser.GetValueOfArgument({ "hello" }).value() == "world"); + } + + SUBCASE("Mixed value & no-value args") { + parser.RegisterArgument({ "a" }, ArgsParser::Flags::HAS_VALUE); + parser.RegisterArgument({ "hello" }, ArgsParser::Flags::NONE); + parser.Parse({ "--a=5", "--hello" }); + CHECK(parser.Verify()); + REQUIRE(parser.FoundArgument({ "a" })); + REQUIRE(parser.FoundArgument({ "hello" })); + CHECK(parser.GetValueOfArgument({ "a" }).has_value()); + CHECK(parser.GetValueOfArgument({ "a" }).value() == "5"); + CHECK(!parser.GetValueOfArgument({ "hello" }).has_value()); + } + + SUBCASE("Required args") { + SUBCASE("Two required, two present") { + parser.RegisterArgument({ "a" }, ArgsParser::Flags::REQUIRED); + parser.RegisterArgument({ "hello" }, ArgsParser::Flags::REQUIRED); + parser.Parse({ "--a", "--hello" }); + CHECK(parser.Verify()); + } + SUBCASE("Two required, one present") { + parser.RegisterArgument({ "a" }, ArgsParser::Flags::REQUIRED); + parser.RegisterArgument({ "hello" }, ArgsParser::Flags::REQUIRED); + parser.Parse({ "--a" }); + CHECK(!parser.Verify()); + } + SUBCASE("Two required, none present") { + parser.RegisterArgument({ "a" }, ArgsParser::Flags::REQUIRED); + parser.RegisterArgument({ "hello" }, ArgsParser::Flags::REQUIRED); + parser.Parse({ "--b" }); + CHECK(!parser.Verify()); + } + } +} diff --git a/src/Common.cpp b/src/Common.cpp index 77933bb..1df9a35 100644 --- a/src/Common.cpp +++ b/src/Common.cpp @@ -64,6 +64,23 @@ std::array Application::VersionStrToInts(const std::string& str) { return Version; } +TEST_CASE("Application::VersionStrToInts") { + auto v = Application::VersionStrToInts("1.2.3"); + CHECK(v[0] == 1); + CHECK(v[1] == 2); + CHECK(v[2] == 3); + + v = Application::VersionStrToInts("10.20.30"); + CHECK(v[0] == 10); + CHECK(v[1] == 20); + CHECK(v[2] == 30); + + v = Application::VersionStrToInts("100.200.255"); + CHECK(v[0] == 100); + CHECK(v[1] == 200); + CHECK(v[2] == 255); +} + bool Application::IsOutdated(const Version& Current, const Version& Newest) { if (Newest.major > Current.major) { return true; @@ -76,6 +93,49 @@ bool Application::IsOutdated(const Version& Current, const Version& Newest) { } } +TEST_CASE("Application::IsOutdated (version check)") { + SUBCASE("Same version") { + CHECK(!Application::IsOutdated({ 1, 2, 3 }, { 1, 2, 3 })); + } + // we need to use over 1-2 digits to test against lexical comparisons + SUBCASE("Patch outdated") { + for (size_t Patch = 0; Patch < 10; ++Patch) { + for (size_t Minor = 0; Minor < 10; ++Minor) { + for (size_t Major = 0; Major < 10; ++Major) { + CHECK(Application::IsOutdated({ Major, Minor, Patch }, { Major, Minor, Patch + 1 })); + } + } + } + } + SUBCASE("Minor outdated") { + for (size_t Patch = 0; Patch < 10; ++Patch) { + for (size_t Minor = 0; Minor < 10; ++Minor) { + for (size_t Major = 0; Major < 10; ++Major) { + CHECK(Application::IsOutdated({ Major, Minor, Patch }, { Major, Minor + 1, Patch })); + } + } + } + } + SUBCASE("Major outdated") { + for (size_t Patch = 0; Patch < 10; ++Patch) { + for (size_t Minor = 0; Minor < 10; ++Minor) { + for (size_t Major = 0; Major < 10; ++Major) { + CHECK(Application::IsOutdated({ Major, Minor, Patch }, { Major + 1, Minor, Patch })); + } + } + } + } + SUBCASE("All outdated") { + for (size_t Patch = 0; Patch < 10; ++Patch) { + for (size_t Minor = 0; Minor < 10; ++Minor) { + for (size_t Major = 0; Major < 10; ++Major) { + CHECK(Application::IsOutdated({ Major, Minor, Patch }, { Major + 1, Minor + 1, Patch + 1 })); + } + } + } + } +} + void Application::SetSubsystemStatus(const std::string& Subsystem, Status status) { switch (status) { case Status::Good: @@ -98,6 +158,15 @@ void Application::SetSubsystemStatus(const std::string& Subsystem, Status status mSystemStatusMap[Subsystem] = status; } +TEST_CASE("Application::SetSubsystemStatus") { + Application::SetSubsystemStatus("Test", Application::Status::Good); + auto Map = Application::GetSubsystemStatuses(); + CHECK(Map.at("Test") == Application::Status::Good); + Application::SetSubsystemStatus("Test", Application::Status::Bad); + Map = Application::GetSubsystemStatuses(); + CHECK(Map.at("Test") == Application::Status::Bad); +} + void Application::CheckForUpdates() { Application::SetSubsystemStatus("UpdateCheck", Application::Status::Starting); static bool FirstTime = true; @@ -155,6 +224,25 @@ std::string ThreadName(bool DebugModeOverride) { return ""; } +TEST_CASE("ThreadName") { + RegisterThread("MyThread"); + auto OrigDebug = Application::Settings.DebugModeEnabled; + + // ThreadName adds a space at the end, legacy but we need it still + SUBCASE("Debug mode enabled") { + Application::Settings.DebugModeEnabled = true; + CHECK(ThreadName(true) == "MyThread "); + CHECK(ThreadName(false) == "MyThread "); + } + SUBCASE("Debug mode disabled") { + Application::Settings.DebugModeEnabled = false; + CHECK(ThreadName(true) == "MyThread "); + CHECK(ThreadName(false) == ""); + } + // cleanup + Application::Settings.DebugModeEnabled = OrigDebug; +} + void RegisterThread(const std::string& str) { std::string ThreadId; #ifdef BEAMMP_WINDOWS @@ -172,6 +260,11 @@ void RegisterThread(const std::string& str) { threadNameMap[std::this_thread::get_id()] = str; } +TEST_CASE("RegisterThread") { + RegisterThread("MyThread"); + CHECK(threadNameMap.at(std::this_thread::get_id()) == "MyThread"); +} + Version::Version(uint8_t major, uint8_t minor, uint8_t patch) : major(major) , minor(minor) @@ -185,6 +278,12 @@ std::string Version::AsString() { return fmt::format("{:d}.{:d}.{:d}", major, minor, patch); } +TEST_CASE("Version::AsString") { + CHECK(Version { 0, 0, 0 }.AsString() == "0.0.0"); + CHECK(Version { 1, 2, 3 }.AsString() == "1.2.3"); + CHECK(Version { 255, 255, 255 }.AsString() == "255.255.255"); +} + void LogChatMessage(const std::string& name, int id, const std::string& msg) { if (Application::Settings.LogChat) { std::stringstream ss; @@ -196,7 +295,9 @@ void LogChatMessage(const std::string& name, int id, const std::string& msg) { ss << name << ""; } ss << msg; +#ifdef DOCTEST_CONFIG_DISABLE Application::Console().Write(ss.str()); +#endif } } diff --git a/src/LuaAPI.cpp b/src/LuaAPI.cpp index 4a5df29..7ac632a 100644 --- a/src/LuaAPI.cpp +++ b/src/LuaAPI.cpp @@ -176,50 +176,57 @@ void LuaAPI::MP::Set(int ConfigID, sol::object NewValue) { if (NewValue.is()) { Application::Settings.DebugModeEnabled = NewValue.as(); beammp_info(std::string("Set `Debug` to ") + (Application::Settings.DebugModeEnabled ? "true" : "false")); - } else + } else { beammp_lua_error("set invalid argument [2] expected boolean"); + } break; case 1: // private if (NewValue.is()) { Application::Settings.Private = NewValue.as(); beammp_info(std::string("Set `Private` to ") + (Application::Settings.Private ? "true" : "false")); - } else + } else { beammp_lua_error("set invalid argument [2] expected boolean"); + } break; case 2: // max cars if (NewValue.is()) { Application::Settings.MaxCars = NewValue.as(); beammp_info(std::string("Set `MaxCars` to ") + std::to_string(Application::Settings.MaxCars)); - } else + } else { beammp_lua_error("set invalid argument [2] expected integer"); + } break; case 3: // max players if (NewValue.is()) { Application::Settings.MaxPlayers = NewValue.as(); beammp_info(std::string("Set `MaxPlayers` to ") + std::to_string(Application::Settings.MaxPlayers)); - } else + } else { beammp_lua_error("set invalid argument [2] expected integer"); + } break; case 4: // Map if (NewValue.is()) { Application::Settings.MapName = NewValue.as(); beammp_info(std::string("Set `Map` to ") + Application::Settings.MapName); - } else + } else { beammp_lua_error("set invalid argument [2] expected string"); + } break; case 5: // Name if (NewValue.is()) { Application::Settings.ServerName = NewValue.as(); beammp_info(std::string("Set `Name` to ") + Application::Settings.ServerName); - } else + } else { beammp_lua_error("set invalid argument [2] expected string"); + } break; case 6: // Desc if (NewValue.is()) { Application::Settings.ServerDesc = NewValue.as(); beammp_info(std::string("Set `Description` to ") + Application::Settings.ServerDesc); - } else + } else { beammp_lua_error("set invalid argument [2] expected string"); + } break; default: beammp_warn("Invalid config ID \"" + std::to_string(ConfigID) + "\". Use `MP.Settings.*` enum for this."); @@ -255,7 +262,9 @@ void LuaAPI::MP::PrintRaw(sol::variadic_args Args) { ToPrint += LuaToString(static_cast(Arg)); ToPrint += "\t"; } +#ifdef DOCTEST_CONFIG_DISABLE Application::Console().WriteRaw(ToPrint); +#endif } int LuaAPI::PanicHandler(lua_State* State) { diff --git a/src/TResourceManager.cpp b/src/TResourceManager.cpp index afb0e6b..e12ef17 100644 --- a/src/TResourceManager.cpp +++ b/src/TResourceManager.cpp @@ -28,8 +28,9 @@ TResourceManager::TResourceManager() { } } - if (mModsLoaded) + if (mModsLoaded) { beammp_info("Loaded " + std::to_string(mModsLoaded) + " Mods"); + } Application::SetSubsystemStatus("ResourceManager", Application::Status::Good); } diff --git a/test/test_main.cpp b/test/test_main.cpp index 0a3f254..1aba0a1 100644 --- a/test/test_main.cpp +++ b/test/test_main.cpp @@ -1,2 +1,22 @@ -#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#define DOCTEST_CONFIG_IMPLEMENT #include + +#include + +int main(int argc, char** argv) { + doctest::Context context; + + Application::InitializeConsole(); + + context.applyCommandLine(argc, argv); + + int res = context.run(); // run + + if (context.shouldExit()) // important - query flags (and --exit) rely on the user doing this + return res; // propagate the result of the tests + + int client_stuff_return_code = 0; + // your program - if the testing framework is integrated in your production code + + return res + client_stuff_return_code; // the result from doctest is propagated here as well +}