From 6e1313bd31e781948db21f8b0700335f03801db1 Mon Sep 17 00:00:00 2001 From: Alex Dewar Date: Thu, 10 Aug 2023 09:30:43 +0100 Subject: [PATCH] Write tests for some config code --- src/HealthGPS.Tests/CMakeLists.txt | 2 + src/HealthGPS.Tests/Configuration.Test.cpp | 248 +++++++++++++++++++++ 2 files changed, 250 insertions(+) create mode 100644 src/HealthGPS.Tests/Configuration.Test.cpp diff --git a/src/HealthGPS.Tests/CMakeLists.txt b/src/HealthGPS.Tests/CMakeLists.txt index 19adaaa3f..54e64716b 100644 --- a/src/HealthGPS.Tests/CMakeLists.txt +++ b/src/HealthGPS.Tests/CMakeLists.txt @@ -18,6 +18,7 @@ target_sources(HealthGPS.Tests "data_config.cpp" "AgeGenderTable.Test.cpp" "Channel.Test.cpp" + "Configuration.Test.cpp" "Core.Array2DTest.cpp" "Core.Test.cpp" "Core.UnivariateSummary.Test.cpp" @@ -45,6 +46,7 @@ target_link_libraries(HealthGPS.Tests PRIVATE HealthGPS.Core HealthGPS.Datastore + HealthGPS.LibConsole HealthGPS fmt::fmt Threads::Threads diff --git a/src/HealthGPS.Tests/Configuration.Test.cpp b/src/HealthGPS.Tests/Configuration.Test.cpp new file mode 100644 index 000000000..0add39c18 --- /dev/null +++ b/src/HealthGPS.Tests/Configuration.Test.cpp @@ -0,0 +1,248 @@ +#include "pch.h" + +#include "HealthGPS.Console/configuration_parsing.h" +#include "HealthGPS.Console/configuration_parsing_helpers.h" + +#include +#include +#include + +using json = nlohmann::json; +using namespace host; + +#define TYPE_OF(x) std::remove_cvref_t + +namespace { +const std::string TEST_KEY = "my_key"; +const std::string TEST_KEY2 = "other_key"; + +#ifdef _WIN32 +const std::filesystem::path TEST_PATH_ABSOLUTE = R"(C:\Users\hgps)"; +#else +const std::filesystem::path TEST_PATH_ABSOLUTE = "/home/hgps"; +#endif + +class TempDir { + public: + TempDir() : rnd_{std::random_device()()} { + path_ = std::filesystem::path{::testing::TempDir()} / "hgps" / random_string(); + if (!std::filesystem::create_directories(path_)) { + throw std::runtime_error{"Could not create temp dir"}; + } + + path_ = std::filesystem::absolute(path_); + } + + ~TempDir() { + if (std::filesystem::exists(path_)) { + std::filesystem::remove_all(path_); + } + } + + std::string random_string() const { return std::to_string(rnd_()); } + + const auto &path() const { return path_; } + + private: + mutable std::mt19937 rnd_; + std::filesystem::path path_; + + std::filesystem::path createTempDir() { + const auto rnd = std::random_device()(); + const auto path = + std::filesystem::path{::testing::TempDir()} / "hgps" / std::to_string(rnd); + if (!std::filesystem::create_directories(path)) { + throw std::runtime_error{"Could not create temp dir"}; + } + + return std::filesystem::absolute(path); + } +}; + +class PathHelper : public ::testing::Test { + public: + const auto &tmp_path() const { return dir_.path(); } + + std::filesystem::path random_filename() const { return dir_.random_string(); } + + std::filesystem::path create_file_relative() const { + auto file_path = random_filename(); + std::ofstream ofs{dir_.path() / file_path}; + return file_path; + } + + std::filesystem::path create_file_absolute() const { + auto file_path = tmp_path() / random_filename(); + std::ofstream ofs{file_path}; + return file_path; + } + + private: + TempDir dir_; +}; + +} // anonymous namespace + +TEST(ConfigParsing, Get) { + json j; + + // Null object + EXPECT_THROW(get(j, TEST_KEY), ConfigurationError); + + // Key missing + j[TEST_KEY2] = 1; + EXPECT_THROW(get(j, TEST_KEY), ConfigurationError); + + // Key present + j[TEST_KEY] = 2; + EXPECT_NO_THROW(get(j, TEST_KEY)); +} + +template void testGetTo(const Func &f) { + f(1); + f(std::string{"hello"}); +} + +TEST(ConfigParsing, GetToGood) { + testGetTo([](const auto &exp) { + json j; + j[TEST_KEY] = exp; + + TYPE_OF(exp) out; + EXPECT_TRUE(get_to(j, TEST_KEY, out)); + EXPECT_EQ(out, exp); + }); +} + +TEST(ConfigParsing, GetToGoodSetFlag) { + testGetTo([](const auto &exp) { + json j; + j[TEST_KEY] = exp; + + const auto check = [&exp, &j](bool initial) { + bool success = initial; + TYPE_OF(exp) out; + EXPECT_TRUE(get_to(j, TEST_KEY, out, success)); + EXPECT_EQ(success, initial); // flag shouldn't have been modified + EXPECT_EQ(out, exp); + }; + + check(true); + check(false); + }); +} + +TEST(ConfigParsing, GetToBadKey) { + testGetTo([](const auto &exp) { + json j; + j[TEST_KEY] = exp; + + // Try reading a different, non-existent key + TYPE_OF(exp) out; + EXPECT_FALSE(get_to(j, TEST_KEY2, out)); + }); +} + +TEST(ConfigParsing, GetToBadKeySetFlag) { + testGetTo([](const auto &exp) { + json j; + j[TEST_KEY] = exp; + + // Try reading a different, non-existent key + bool success = true; + TYPE_OF(exp) out; + EXPECT_FALSE(get_to(j, TEST_KEY2, out, success)); + EXPECT_FALSE(success); + }); +} + +TEST(ConfigParsing, GetToWrongType) { + testGetTo([](const auto &exp) { + json j; + j[TEST_KEY] = exp; + + // Deliberately choose a different type from the inputs so we get a mismatch + std::vector out; + EXPECT_FALSE(get_to(j, TEST_KEY, out)); + }); +} + +TEST(ConfigParsing, GetToWrongTypeSetFlag) { + testGetTo([](const auto &exp) { + json j; + j[TEST_KEY] = exp; + + // Deliberately choose a different type from the inputs so we get a mismatch + std::vector out; + bool success = true; + EXPECT_FALSE(get_to(j, TEST_KEY, out, success)); + EXPECT_FALSE(success); + }); +} + +TEST_F(PathHelper, RebaseValidPathGood) { + { + const auto absPath = create_file_absolute(); + auto path = absPath; + ASSERT_TRUE(path.is_absolute()); + EXPECT_NO_THROW(rebase_valid_path(path, tmp_path())); + + // As path is absolute, it shouldn't be modified + EXPECT_EQ(path, absPath); + } + + { + const auto relPath = create_file_relative(); + auto path = relPath; + ASSERT_TRUE(path.is_relative()); + EXPECT_NO_THROW(rebase_valid_path(path, tmp_path())); + + // Path should have been rebased + EXPECT_EQ(path, tmp_path() / relPath); + } +} + +TEST_F(PathHelper, RebaseValidPathBad) { + { + auto path = TEST_PATH_ABSOLUTE; + ASSERT_TRUE(path.is_absolute()); + EXPECT_THROW(rebase_valid_path(path, tmp_path()), ConfigurationError); + } + + { + auto path = random_filename(); + ASSERT_TRUE(path.is_relative()); + EXPECT_THROW(rebase_valid_path(path, tmp_path()), ConfigurationError); + } +} + +TEST_F(PathHelper, RebaseValidPathTo) { + + { + // Should fail because key doesn't exist + std::filesystem::path out; + EXPECT_FALSE(rebase_valid_path_to(json{}, TEST_KEY, out, tmp_path())); + } + + { + const auto relPath = create_file_relative(); + ASSERT_TRUE(relPath.is_relative()); + + json j; + j[TEST_KEY] = relPath; + std::filesystem::path out; + EXPECT_TRUE(rebase_valid_path_to(j, TEST_KEY, out, tmp_path())); + + // Path should have been rebased + EXPECT_EQ(out, tmp_path() / relPath); + } + + { + json j; + j[TEST_KEY] = TEST_PATH_ABSOLUTE; + std::filesystem::path out; + EXPECT_FALSE(rebase_valid_path_to(j, TEST_KEY, out, tmp_path())); + } +} + +TEST_F(PathHelper, RebaseValidPathToSetFlag) {} \ No newline at end of file