Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/Extend type formatting #171

Merged
merged 6 commits into from
Sep 24, 2024
Merged
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
78 changes: 48 additions & 30 deletions modules/types/include/hephaestus/types/type_formatting.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,66 @@

#include <fmt/chrono.h>
#include <fmt/format.h>
#include <magic_enum.hpp>
#include <range/v3/view/zip.hpp>

#include "hephaestus/utils/concepts.h"

namespace heph::types {

//=================================================================================================
// Timepoint
// Array and Vector
//=================================================================================================
// A system clock provides access to time and date.
template <typename T>
concept IsSystemClock = std::is_same_v<T, std::chrono::system_clock>;
concept ArrayOrVectorType = ArrayType<T> || VectorType<T>;

template <ArrayOrVectorType T>
[[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 <UnorderedMapType T>
[[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 <EnumType T>
[[nodiscard]] inline auto toString(T value) -> std::string_view {
return magic_enum::enum_name(value);
}

template <IsSystemClock T>
//=================================================================================================
// ChronoTimePoint
//=================================================================================================

template <ChronoSystemClockType T>
[[nodiscard]] inline auto toString(const std::chrono::time_point<T>& 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 <typename T>
concept IsSteadyClock = std::is_same_v<T, std::chrono::steady_clock>;

template <IsSteadyClock T>
template <ChronoSteadyClockType T>
[[nodiscard]] inline auto toString(const std::chrono::time_point<T>& timestamp) -> std::string {
auto duration_since_epoch = timestamp.time_since_epoch();
auto total_seconds = std::chrono::duration_cast<std::chrono::seconds>(duration_since_epoch).count();
Expand All @@ -48,24 +86,4 @@ template <IsSteadyClock T>
return fmt::format("{}d {:02}h:{:02}m:{:02}.{:09}s", days, hours, minutes, seconds, sub_seconds * SCALER);
}

//=================================================================================================
// Vector
//=================================================================================================
template <typename T>
concept IsVector = requires(T t) { std::is_same_v<T, std::vector<typename T::value_type>>; };

template <IsVector T>
[[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
147 changes: 119 additions & 28 deletions modules/types/tests/type_formatting_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
//=================================================================================================

#include <algorithm>
#include <array>
#include <chrono>
#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>

#include <fmt/core.h>
Expand All @@ -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<int, 0> 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<int, 3> 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<double, 3> 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<std::string, 3> 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<int> vec;
Expand Down Expand Up @@ -84,4 +92,87 @@ TEST(TypeFormattingTests, ConvertStringVector) {
EXPECT_EQ(result, expected);
}

//=================================================================================================
// UnorderedMap
//=================================================================================================

TEST(UnorderedMapTests, ToStringEmpty) {
const std::unordered_map<int, std::string> empty_umap;
EXPECT_EQ(toString(empty_umap), "");
}

TEST(UnorderedMapTests, ToStringNonEmpty) {
const std::unordered_map<int, std::string> umap{ { 1, "one" }, { 3, "three" }, { 2, "two" } };

const std::vector<std::string> 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
4 changes: 1 addition & 3 deletions modules/types_proto/proto/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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})
34 changes: 33 additions & 1 deletion modules/utils/include/hephaestus/utils/concepts.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,39 @@
namespace heph {

template <typename T>
concept ScalarType = requires(T a) { std::is_scalar_v<T>; };
concept ScalarType = std::is_scalar_v<T>;

template <typename T>
concept EnumType = std::is_enum_v<T>;

template <typename T>
concept ArrayType = requires {
typename T::value_type;
requires std::same_as<T, std::array<typename T::value_type, T().size()>>;
};

template <typename T>
concept VectorType = requires {
typename T::value_type;
typename T::allocator_type;
requires std::same_as<T, std::vector<typename T::value_type, typename T::allocator_type>>;
};

template <typename T>
concept UnorderedMapType = requires(T t) {
typename T::key_type;
typename T::mapped_type;
typename T::hasher;
typename T::key_equal;
std::is_same_v<T, std::unordered_map<typename T::key_type, typename T::mapped_type, typename T::hasher,
typename T::key_equal>>;
};

template <typename T>
concept ChronoSystemClockType = std::is_same_v<T, std::chrono::system_clock>;

template <typename T>
concept ChronoSteadyClockType = std::is_same_v<T, std::chrono::steady_clock>;

/// Types that are convertable to and from a string
template <typename T>
Expand Down
9 changes: 8 additions & 1 deletion modules/utils/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
# Copyright (C) 2023-2024 HEPHAESTUS Contributors
# =================================================================================================

define_module_test(
NAME concepts_tests
SOURCES concepts_tests.cpp
PUBLIC_INCLUDE_PATHS $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include>
PUBLIC_LINK_LIBS ""
)

define_module_test(
NAME exception_tests
SOURCES exception_tests.cpp
Expand Down Expand Up @@ -42,4 +49,4 @@ define_module_test(
SOURCES watchdog_tests.cpp
PUBLIC_INCLUDE_PATHS $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include>
PUBLIC_LINK_LIBS ""
)
)
Loading