From 9272bce3526731aee4eafaeed73b01fef7182d4f Mon Sep 17 00:00:00 2001 From: Lorenz Hruby Date: Tue, 24 Sep 2024 18:35:25 +0200 Subject: [PATCH] Feat/Extend type formatting (#171) # Description * Add enum, std::array and std::unordered_map to type formatting * Extend concepts ## Type of change - New feature (non-breaking change which adds functionality) - Breaking change (fix or feature that would cause existing functionality to not work as expected) ## Checklist before requesting a review - [x] I have performed a self-review of my code. - [x] If it is a core feature, I have added thorough tests. - [ ] If this is a new component I have added examples. - [ ] I updated the README and related documentation. --- .../hephaestus/types/type_formatting.h | 78 ++++++---- modules/types/tests/type_formatting_tests.cpp | 147 ++++++++++++++---- modules/types_proto/proto/CMakeLists.txt | 4 +- .../utils/include/hephaestus/utils/concepts.h | 34 +++- modules/utils/tests/CMakeLists.txt | 9 +- modules/utils/tests/concepts_tests.cpp | 82 ++++++++++ 6 files changed, 291 insertions(+), 63 deletions(-) create mode 100644 modules/utils/tests/concepts_tests.cpp diff --git a/modules/types/include/hephaestus/types/type_formatting.h b/modules/types/include/hephaestus/types/type_formatting.h index 5244bd6f..8e0b4a19 100644 --- a/modules/types/include/hephaestus/types/type_formatting.h +++ b/modules/types/include/hephaestus/types/type_formatting.h @@ -10,28 +10,66 @@ #include #include +#include #include +#include "hephaestus/utils/concepts.h" + namespace heph::types { //================================================================================================= -// Timepoint +// Array and Vector //================================================================================================= -// A system clock provides access to time and date. template -concept IsSystemClock = std::is_same_v; +concept ArrayOrVectorType = ArrayType || VectorType; + +template +[[nodiscard]] inline auto toString(const T& vec) -> std::string { + std::stringstream ss; + + const auto indices = std::views::iota(0); + const auto indexed_vec = ranges::views::zip(indices, vec); + + for (const auto& [index, value] : indexed_vec) { + const std::string str = fmt::format(" Index: {}, Value: {}\n", index, value); + ss << str; + } + + return ss.str(); +} + +//================================================================================================= +// UnorderedMap +//================================================================================================= +template +[[nodiscard]] inline auto toString(const T& umap) -> std::string { + std::stringstream ss; + + for (const auto& [key, value] : umap) { + ss << " Key: " << key << ", Value: " << value << '\n'; + } + + return ss.str(); +} + +//================================================================================================= +// Enum +//================================================================================================= +template +[[nodiscard]] inline auto toString(T value) -> std::string_view { + return magic_enum::enum_name(value); +} -template +//================================================================================================= +// ChronoTimePoint +//================================================================================================= + +template [[nodiscard]] inline auto toString(const std::chrono::time_point& timestamp) -> std::string { return fmt::format("{:%Y-%m-%d %H:%M:%S}", timestamp); } -// A steady clock does not have an anchor point in calendar time. It is only useful for measuring relative -// time intervals. -template -concept IsSteadyClock = std::is_same_v; - -template +template [[nodiscard]] inline auto toString(const std::chrono::time_point& timestamp) -> std::string { auto duration_since_epoch = timestamp.time_since_epoch(); auto total_seconds = std::chrono::duration_cast(duration_since_epoch).count(); @@ -48,24 +86,4 @@ template return fmt::format("{}d {:02}h:{:02}m:{:02}.{:09}s", days, hours, minutes, seconds, sub_seconds * SCALER); } -//================================================================================================= -// Vector -//================================================================================================= -template -concept IsVector = requires(T t) { std::is_same_v>; }; - -template -[[nodiscard]] inline auto toString(const T& vec) -> std::string { - std::stringstream ss; - - const auto indices = std::views::iota(0); - const auto indexed_vec = ranges::views::zip(indices, vec); - - for (const auto& [index, value] : indexed_vec) { - const std::string str = fmt::format(" Index: {}, Value: {}\n", index, value); - ss << str; - } - - return ss.str(); -} }; // namespace heph::types diff --git a/modules/types/tests/type_formatting_tests.cpp b/modules/types/tests/type_formatting_tests.cpp index 3a825bc1..88d82d16 100644 --- a/modules/types/tests/type_formatting_tests.cpp +++ b/modules/types/tests/type_formatting_tests.cpp @@ -3,8 +3,11 @@ //================================================================================================= #include +#include #include +#include #include +#include #include #include @@ -16,40 +19,45 @@ using namespace ::testing; // NOLINT(google-build-using-namespace) namespace heph::types::tests { -// Test assumes sub-second precision of at most nanoseconds. -TEST(TypeFormattingTests, TimestampFormattingSteadyClock) { - const auto timestamp = std::chrono::steady_clock::now(); - const auto str = fmt::format("{}", toString(timestamp)); - - ASSERT_LE(str.length(), 24); +//================================================================================================= +// Array +//================================================================================================= - const auto it = std::find(str.begin(), str.end(), 'd'); - ASSERT_TRUE(it != str.end()); - ASSERT_EQ(*(it + 1), ' '); - ASSERT_EQ(*(it + 4), 'h'); - ASSERT_EQ(*(it + 5), ':'); - ASSERT_EQ(*(it + 8), 'm'); - ASSERT_EQ(*(it + 9), ':'); - ASSERT_EQ(*(it + 12), '.'); - ASSERT_EQ(str.back(), 's'); +TEST(TypeFormattingTests, ConvertEmptyArray) { + const std::array arr = {}; + const std::string result = toString(arr); + EXPECT_EQ(result, ""); } -// Test assumes sub-second precision of at most nanoseconds. -TEST(TypeFormattingTests, TimestampFormattingSystemClock) { - const auto timestamp = std::chrono::system_clock::now(); - const auto str = fmt::format("{}", toString(timestamp)); +TEST(TypeFormattingTests, ConvertIntArray) { + const std::array arr = { 1, 2, 3 }; + const std::string result = toString(arr); + const std::string expected = " Index: 0, Value: 1\n" + " Index: 1, Value: 2\n" + " Index: 2, Value: 3\n"; + EXPECT_EQ(result, expected); +} - ASSERT_TRUE(str.length() <= 27); +TEST(TypeFormattingTests, ConvertDoubleArray) { + const std::array arr = { 1.1, 2.2, 3.3 }; // NOLINT(cppcoreguidelines-avoid-magic-numbers) + const std::string result = toString(arr); + const std::string expected = " Index: 0, Value: 1.1\n" + " Index: 1, Value: 2.2\n" + " Index: 2, Value: 3.3\n"; + EXPECT_EQ(result, expected); +} - ASSERT_EQ(str[0], '2'); - ASSERT_EQ(str[1], '0'); - ASSERT_EQ(str[4], '-'); - ASSERT_EQ(str[7], '-'); - ASSERT_EQ(str[10], ' '); - ASSERT_EQ(str[13], ':'); - ASSERT_EQ(str[16], ':'); - ASSERT_EQ(str[19], '.'); +TEST(TypeFormattingTests, ConvertStringArray) { + const std::array arr = { "one", "two", "three" }; + const std::string result = toString(arr); + const std::string expected = " Index: 0, Value: one\n" + " Index: 1, Value: two\n" + " Index: 2, Value: three\n"; + EXPECT_EQ(result, expected); } +//================================================================================================= +// Vector +//================================================================================================= TEST(TypeFormattingTests, ConvertEmptyVector) { const std::vector vec; @@ -84,4 +92,87 @@ TEST(TypeFormattingTests, ConvertStringVector) { EXPECT_EQ(result, expected); } +//================================================================================================= +// UnorderedMap +//================================================================================================= + +TEST(UnorderedMapTests, ToStringEmpty) { + const std::unordered_map empty_umap; + EXPECT_EQ(toString(empty_umap), ""); +} + +TEST(UnorderedMapTests, ToStringNonEmpty) { + const std::unordered_map umap{ { 1, "one" }, { 3, "three" }, { 2, "two" } }; + + const std::vector expected_outputs = { + " Key: 1, Value: one\n Key: 2, Value: two\n Key: 3, Value: three\n", + " Key: 1, Value: one\n Key: 3, Value: three\n Key: 2, Value: two\n", + " Key: 2, Value: two\n Key: 1, Value: one\n Key: 3, Value: three\n", + " Key: 2, Value: two\n Key: 3, Value: three\n Key: 1, Value: one\n", + " Key: 3, Value: three\n Key: 1, Value: one\n Key: 2, Value: two\n", + " Key: 3, Value: three\n Key: 2, Value: two\n Key: 1, Value: one\n" + }; + + const auto actual_output = toString(umap); + + int match_count = 0; + for (const auto& expected_output : expected_outputs) { + if (actual_output == expected_output) { + ++match_count; + } + } + + EXPECT_EQ(match_count, 1); +} + +//================================================================================================= +// Enum +//================================================================================================= + +TEST(EnumTests, ToString) { + enum class TestEnum : uint8_t { A, B, C }; + EXPECT_EQ(toString(TestEnum::A), "A"); + EXPECT_EQ(toString(TestEnum::B), "B"); + EXPECT_EQ(toString(TestEnum::C), "C"); +} + +//================================================================================================= +// ChronoTimePoint +//================================================================================================= + +// Test assumes sub-second precision of at most nanoseconds. +TEST(TypeFormattingTests, ChronoTimestampFormattingSteadyClock) { + const auto timestamp = std::chrono::steady_clock::now(); + const auto str = fmt::format("{}", toString(timestamp)); + + ASSERT_LE(str.length(), 24); + + const auto it = std::find(str.begin(), str.end(), 'd'); + ASSERT_TRUE(it != str.end()); + ASSERT_EQ(*(it + 1), ' '); + ASSERT_EQ(*(it + 4), 'h'); + ASSERT_EQ(*(it + 5), ':'); + ASSERT_EQ(*(it + 8), 'm'); + ASSERT_EQ(*(it + 9), ':'); + ASSERT_EQ(*(it + 12), '.'); + ASSERT_EQ(str.back(), 's'); +} + +// Test assumes sub-second precision of at most nanoseconds. +TEST(TypeFormattingTests, ChronoTimestampFormattingSystemClock) { + const auto timestamp = std::chrono::system_clock::now(); + const auto str = fmt::format("{}", toString(timestamp)); + + ASSERT_TRUE(str.length() <= 27); + + ASSERT_EQ(str[0], '2'); + ASSERT_EQ(str[1], '0'); + ASSERT_EQ(str[4], '-'); + ASSERT_EQ(str[7], '-'); + ASSERT_EQ(str[10], ' '); + ASSERT_EQ(str[13], ':'); + ASSERT_EQ(str[16], ':'); + ASSERT_EQ(str[19], '.'); +} + } // namespace heph::types::tests diff --git a/modules/types_proto/proto/CMakeLists.txt b/modules/types_proto/proto/CMakeLists.txt index da446c64..f12aef02 100644 --- a/modules/types_proto/proto/CMakeLists.txt +++ b/modules/types_proto/proto/CMakeLists.txt @@ -2,9 +2,7 @@ # Copyright (C) 2023-2024 HEPHAESTUS Contributors # ================================================================================================= -set(SOURCES - hephaestus/types/proto/dummy_type.proto -) +set(SOURCES hephaestus/types/proto/dummy_type.proto) # To create proto messages in another repository that import `hephaestus` proto messages, see readme. define_module_proto_library(NAME types_gen_proto SOURCES ${SOURCES}) diff --git a/modules/utils/include/hephaestus/utils/concepts.h b/modules/utils/include/hephaestus/utils/concepts.h index 372f976d..6cfffaf7 100644 --- a/modules/utils/include/hephaestus/utils/concepts.h +++ b/modules/utils/include/hephaestus/utils/concepts.h @@ -11,7 +11,39 @@ namespace heph { template -concept ScalarType = requires(T a) { std::is_scalar_v; }; +concept ScalarType = std::is_scalar_v; + +template +concept EnumType = std::is_enum_v; + +template +concept ArrayType = requires { + typename T::value_type; + requires std::same_as>; +}; + +template +concept VectorType = requires { + typename T::value_type; + typename T::allocator_type; + requires std::same_as>; +}; + +template +concept UnorderedMapType = requires(T t) { + typename T::key_type; + typename T::mapped_type; + typename T::hasher; + typename T::key_equal; + std::is_same_v>; +}; + +template +concept ChronoSystemClockType = std::is_same_v; + +template +concept ChronoSteadyClockType = std::is_same_v; /// Types that are convertable to and from a string template diff --git a/modules/utils/tests/CMakeLists.txt b/modules/utils/tests/CMakeLists.txt index 3249b87d..15ff5abc 100644 --- a/modules/utils/tests/CMakeLists.txt +++ b/modules/utils/tests/CMakeLists.txt @@ -2,6 +2,13 @@ # Copyright (C) 2023-2024 HEPHAESTUS Contributors # ================================================================================================= +define_module_test( + NAME concepts_tests + SOURCES concepts_tests.cpp + PUBLIC_INCLUDE_PATHS $ + PUBLIC_LINK_LIBS "" +) + define_module_test( NAME exception_tests SOURCES exception_tests.cpp @@ -42,4 +49,4 @@ define_module_test( SOURCES watchdog_tests.cpp PUBLIC_INCLUDE_PATHS $ PUBLIC_LINK_LIBS "" -) \ No newline at end of file +) diff --git a/modules/utils/tests/concepts_tests.cpp b/modules/utils/tests/concepts_tests.cpp new file mode 100644 index 00000000..78b19c64 --- /dev/null +++ b/modules/utils/tests/concepts_tests.cpp @@ -0,0 +1,82 @@ +//================================================================================================= +// Copyright (C) 2023-2024 HEPHAESTUS Contributors +//================================================================================================= + +#include // NOLINT(misc-include-cleaner) +#include // NOLINT(misc-include-cleaner) +#include // NOLINT(misc-include-cleaner) +#include // NOLINT(misc-include-cleaner) +#include // NOLINT(misc-include-cleaner) +#include // NOLINT(misc-include-cleaner) +#include // NOLINT(misc-include-cleaner) +#include // NOLINT(misc-include-cleaner) + +#include + +#include "hephaestus/utils/concepts.h" // NOLINT(misc-include-cleaner) + +// NOLINTNEXTLINE(google-build-using-namespace) +using namespace ::testing; + +namespace heph::utils::tests { + +TEST(ConceptTests, ScalarType) { + EXPECT_TRUE((ScalarType)); + EXPECT_TRUE((ScalarType)); + EXPECT_TRUE((ScalarType)); + EXPECT_TRUE((ScalarType)); + EXPECT_TRUE((ScalarType)); + EXPECT_TRUE((ScalarType)); + EXPECT_TRUE((ScalarType)); + EXPECT_TRUE((ScalarType)); + EXPECT_TRUE((ScalarType)); + + EXPECT_TRUE((ScalarType)); + EXPECT_TRUE((ScalarType)); + + EXPECT_TRUE((ScalarType)); + EXPECT_TRUE((ScalarType)); + EXPECT_TRUE((ScalarType)); + + EXPECT_FALSE((ScalarType>)); +} + +TEST(ConceptTests, EnumType) { + enum class TestEnum : uint8_t { A, B, C }; + EXPECT_TRUE((EnumType)); + EXPECT_FALSE((EnumType)); + EXPECT_FALSE((EnumType>)); +} + +TEST(ConceptTests, ArrayType) { + EXPECT_TRUE((ArrayType>)); + EXPECT_FALSE((ArrayType>)); + EXPECT_FALSE((ArrayType)); // NOLINT(cppcoreguidelines-avoid-c-arrays) +} + +TEST(ConceptTests, VectorType) { + EXPECT_TRUE((VectorType>)); + EXPECT_FALSE((VectorType>)); + EXPECT_FALSE((VectorType>)); + EXPECT_FALSE((VectorType)); +} + +TEST(ConceptTests, UnorderedMapType) { + EXPECT_TRUE((UnorderedMapType>)); + EXPECT_FALSE((UnorderedMapType>)); + EXPECT_FALSE((UnorderedMapType>>)); +} + +TEST(ConceptTests, ChronoSystemClockType) { + EXPECT_TRUE((ChronoSystemClockType)); + EXPECT_FALSE((ChronoSystemClockType)); + EXPECT_FALSE((ChronoSystemClockType)); +} + +TEST(ConceptTests, ChronoSteadyClockType) { + EXPECT_TRUE((ChronoSteadyClockType)); + EXPECT_FALSE((ChronoSteadyClockType)); + EXPECT_FALSE((ChronoSteadyClockType)); +} + +} // namespace heph::utils::tests