From e7e08719a09fac53d00ddea902a448e6eeafce67 Mon Sep 17 00:00:00 2001 From: Jessica James Date: Wed, 10 Nov 2021 19:15:56 -0600 Subject: [PATCH] Added split, split_once, split_n variants accepting strings as delimiters More tests Another minor fix for debug assertion failure in timer --- src/common/timer/timer_manager.cpp | 2 +- src/include/split.hpp | 328 +++++++++++++++++++++++++---- src/test/CMakeLists.txt | 2 +- src/test/split.cpp | 257 ++++++++-------------- src/test/split_compilation.cpp | 103 +++++++++ src/test/test_split.hpp | 135 ++++++++++++ 6 files changed, 619 insertions(+), 208 deletions(-) create mode 100644 src/test/split_compilation.cpp create mode 100644 src/test/test_split.hpp diff --git a/src/common/timer/timer_manager.cpp b/src/common/timer/timer_manager.cpp index 5488d79..96f0f97 100644 --- a/src/common/timer/timer_manager.cpp +++ b/src/common/timer/timer_manager.cpp @@ -45,7 +45,7 @@ void timer_manager::loop() { if (itr != m_active_timers.end()) { // Wait until the next timer is ready to fire if (m_cvar.wait_until(lock, (*itr)->next()) == std::cv_status::timeout - && itr == m_active_timers.begin() && itr != m_active_timers.end()) { + && !m_active_timers.empty() && itr == m_active_timers.begin() && itr != m_active_timers.end()) { // Due to a race condition, we may still receive timeout when another thread has notified m_cvar too late // Notifying the thread before releasing the lock does not resolve this, because wait_until's return diff --git a/src/include/split.hpp b/src/include/split.hpp index 60b6e8b..6647c26 100644 --- a/src/include/split.hpp +++ b/src/include/split.hpp @@ -16,11 +16,18 @@ * Written by Jessica James */ +/** + * @file split.hpp + * @author Jessica James + * + * Over-engineered and over-genericized versions of split, split_once, and split_n, with lots of syntactical sugar + */ + #pragma once #include #include -#include "type_traits.hpp" // is_string_view; remove when compilers don't suck +#include "type_traits.hpp" // is_basic_string_view; remove when compilers don't suck namespace jessilib { namespace impl { @@ -49,7 +56,12 @@ using split_container_helper_t = decltype(split_container_helper_f::value>::type* = nullptr> MemberT member_from_range(ItrT in_itr, EndT in_end) { // Workaround due to C++20 iterator constructor being inconsistently available - return { &*in_itr, static_cast(in_end - in_itr) }; + if constexpr (std::is_pointer_v) { + return { in_itr, static_cast(in_end - in_itr) }; + } + + auto& element = *in_itr; + return { &element, static_cast(in_end - in_itr) }; } template::value>::type* = nullptr> @@ -65,22 +77,22 @@ MemberT member_from_range(ItrT in_itr, EndT in_end) { * * @tparam ContainerT Container type to store the results in * @tparam ContainerArgsT Optional template parameters for ContainerT - * @param in_string String to split + * @param begin Start of range of elements to split + * @param end End of range of elements to split * @param in_delim Delimiter to split upon * @return Container populated with */ -template typename ContainerT = std::vector, typename... ContainerArgsT, typename InputT> -constexpr auto split(const InputT& in_string, typename InputT::value_type in_delim) { - using MemberT = typename impl::first_arg>::first_type; +template typename ContainerT = std::vector, typename... ContainerArgsT, typename ItrT, typename EndT, typename ElementT> +constexpr auto split(ItrT begin, EndT end, ElementT in_delim) { + using MemberT = typename impl::first_arg>::first_type; using container_type = impl::split_container_helper_t; + container_type result; - // Nothing to return - if (in_string.empty()) { + if (begin >= end) { + // Nothing to split return result; } - auto begin = in_string.begin(); - auto end = in_string.end(); for (auto itr = begin; itr != end; ++itr) { if (*itr == in_delim) { // Push token to result @@ -95,64 +107,219 @@ constexpr auto split(const InputT& in_string, typename InputT::value_type in_del return result; } +/** + * Splits an input string into substrings + * + * @tparam ContainerT Container type to store the results in + * @tparam ContainerArgsT Optional template parameters for ContainerT + * @param begin Start of range of elements to split + * @param end End of range of elements to split + * @param in_delim_begin Start of range containing the delimiter + * @param in_delim_end End of range containing the delimiter + * @return Container populated with + */ +template typename ContainerT = std::vector, typename... ContainerArgsT, typename ItrT, typename EndT, typename DelimItrT, typename DelimEndT> +constexpr auto split(ItrT begin, EndT end, DelimItrT in_delim_begin, DelimEndT in_delim_end) { + using ElementT = std::remove_cvref_t; + using MemberT = typename impl::first_arg>::first_type; + using container_type = impl::split_container_helper_t; + + auto delim_length = std::distance(in_delim_begin, in_delim_end); + if (delim_length == 1) { + return split(begin, end, *in_delim_begin); + } + + container_type result{}; + if (begin >= end) { + // Nothing to split + return result; + } + + if (in_delim_begin >= in_delim_end + || (end - begin) < delim_length) { + // Absent or impossible to match delimiter, therefore no match, therefore return input as single token + result.push_back(impl::member_from_range(begin, end)); + return result; + } + + auto itr_end = end - (delim_length - 1); + for (auto itr = begin; itr < itr_end;) { + if (std::equal(in_delim_begin, in_delim_end, itr)) { + // Push token to result + result.push_back(impl::member_from_range(begin, itr)); + itr += delim_length; + begin = itr; + continue; + } + + ++itr; + } + + // Push final token to the end; may be empty + result.push_back(impl::member_from_range(begin, end)); + + return result; +} + +/** + * Splits an input string into substrings + * + * @tparam ContainerT Container type to store the results in + * @tparam ContainerArgsT Optional template parameters for ContainerT + * @param in_string String to split + * @param in_delim Delimiter to split upon + * @return Container populated with + */ +template typename ContainerT = std::vector, typename... ContainerArgsT, typename InputT> +constexpr auto split(const InputT& in_string, typename InputT::value_type in_delim) { + return split(in_string.begin(), in_string.end(), in_delim); +} + +template typename ContainerT = std::vector, typename... ContainerArgsT, typename InputT, typename DelimT, + typename std::enable_if::value>::type* = nullptr> +constexpr auto split(const InputT& in_string, const DelimT& in_delim) { + return split(in_string.begin(), in_string.end(), in_delim.begin(), in_delim.end()); +} + /** * Splits an input string into 2 substrings at and omitting an input delimiter. Returns: * An empty pair if in_string is empty, * otherwise if the delimiter is not present, a pair who's `second` member is empty and `first` member is equal to `in_string`, * otherwise, a pair split at first instance of the delimiter * - * @tparam InStringT String type being passed into split_once * @tparam ResultMemberT String type used to populate the result - * @param in_string string to split + * @param begin Start of range of elements to split + * @param end End of range of elements to split * @param in_delim delimiter to split on * @return A pair representing `in_string` split at a delimiter, with first half stored in `first` and second in `last` */ -template -constexpr std::pair split_once(const InStringT& in_string, typename InStringT::value_type in_delim) { - // Nothing in, nothing out - std::pair result; - if (in_string.empty()) { +template +constexpr auto split_once(ItrT begin, EndT end, ElementT in_delim) { + static_assert(sizeof...(OptionalMemberT) <= 1, "Too many member types specified for OptionalMemberT"); + using MemberT = typename impl::first_arg>::first_type; + + std::pair result; + if (begin >= end) { + // Nothing to split return result; } - auto begin = in_string.begin(); - auto end = in_string.end(); for (auto itr = begin; itr != end; ++itr) { if (*itr == in_delim) { // in_delim found; split upon it - result.first = impl::member_from_range(begin, itr); - result.second = impl::member_from_range(itr + 1, end); + result.first = impl::member_from_range(begin, itr); + result.second = impl::member_from_range(itr + 1, end); return result; } } // in_delim not found - result.first = in_string; + result.first = impl::member_from_range(begin, end); return result; } /** - * Splits a string up to a specified number of times and returns the result + * Splits an input string into 2 substrings at and omitting an input delimiter. Returns: + * An empty pair if in_string is empty, + * otherwise if the delimiter is not present, a pair who's `second` member is empty and `first` member is equal to `in_string`, + * otherwise, a pair split at first instance of the delimiter + * + * @tparam ResultMemberT String type used to populate the result + * @param begin Start of range of elements to split + * @param end End of range of elements to split + * @param in_delim_begin Start of range containing the delimiter + * @param in_delim_end End of range containing the delimiter + * @return A pair representing `in_string` split at a delimiter, with first half stored in `first` and second in `last` + */ +template +constexpr auto split_once(ItrT begin, EndT end, DelimItrT in_delim_begin, DelimEndT in_delim_end) { + static_assert(sizeof...(OptionalMemberT) <= 1, "Too many member types specified for OptionalMemberT"); + using ElementT = std::remove_cvref_t; + using MemberT = typename impl::first_arg>::first_type; + + auto delim_length = std::distance(in_delim_begin, in_delim_end); + if (delim_length == 1) { + return split_once(begin, end, *in_delim_begin); + } + + std::pair result; + if (begin >= end) { + // Nothing to split + return result; + } + + if (in_delim_begin >= in_delim_end + || (end - begin) < delim_length) { + // Absent or impossible to match delimiter, therefore no match, therefore return input as single token + result.first = impl::member_from_range(begin, end); + return result; + } + + auto itr_end = end - (delim_length - 1); + for (auto itr = begin; itr < itr_end;) { + if (std::equal(in_delim_begin, in_delim_end, itr)) { + // in_delim found; split upon it + result.first = impl::member_from_range(begin, itr); + result.second = impl::member_from_range(itr + delim_length, end); + return result; + } + } + + // in_delim not found + result.first = impl::member_from_range(begin, end); + return result; +} + +/** + * Splits an input string into 2 substrings at and omitting an input delimiter. Returns: + * An empty pair if in_string is empty, + * otherwise if the delimiter is not present, a pair who's `second` member is empty and `first` member is equal to `in_string`, + * otherwise, a pair split at first instance of the delimiter + * + * @tparam InputT String type being passed into split_once + * @tparam ResultMemberT String type used to populate the result + * @param in_string string to split + * @param in_delim delimiter to split on + * @return A pair representing `in_string` split at a delimiter, with first half stored in `first` and second in `last` + */ +template +constexpr auto split_once(const InputT& in_string, typename InputT::value_type in_delim) { + static_assert(sizeof...(OptionalMemberT) <= 1, "Too many member types specified for OptionalMemberT"); + using ElementT = typename InputT::value_type; + using MemberT = typename impl::first_arg>::first_type; + + return split_once(in_string.begin(), in_string.end(), in_delim); +} + +template::value>::type* = nullptr> +constexpr auto split_once(const InputT& in_string, const DelimT& in_delim) { + static_assert(sizeof...(OptionalMemberT) <= 1, "Too many member types specified for OptionalMemberT"); + return split_once(in_string.begin(), in_string.end(), in_delim.begin(), in_delim.end()); +} + +/** + * Splits a range of elements up to a specified number of times and returns the result * * @tparam ContainerT Container type to store the results in * @tparam ContainerArgsT Optional template parameters for ContainerT - * @param in_string String to split + * @param begin Start of range of elements to split + * @param end End of range of elements to split * @param in_delim Delimiter to split upon * @param in_limit Maximum number of times to split * @return Container containing to up `in_limit` + 1 substrings; result[in_limit] is the unprocessed remainder */ -template typename ContainerT = std::vector, typename... ContainerArgsT, typename InputT> -constexpr auto split_n(const InputT& in_string, typename InputT::value_type in_delim, size_t in_limit) { - using MemberT = typename impl::first_arg>::first_type; +template typename ContainerT = std::vector, typename... ContainerArgsT, typename ItrT, typename EndT, typename ElementT> +constexpr auto split_n(ItrT begin, EndT end, ElementT in_delim, size_t in_limit) { + using MemberT = typename impl::first_arg>::first_type; using container_type = impl::split_container_helper_t; container_type result; - if (in_string.empty()) { + if (begin >= end) { + // Nothing to split return result; } - auto begin = in_string.begin(); - auto end = in_string.end(); for (auto itr = begin; itr != end && in_limit != 0; ++itr) { if (*itr == in_delim) { // Push token to result @@ -168,21 +335,106 @@ constexpr auto split_n(const InputT& in_string, typename InputT::value_type in_d return result; } +/** + * Splits a range of elements up to a specified number of times and returns the result + * + * @tparam ContainerT Container type to store the results in + * @tparam ContainerArgsT Optional template parameters for ContainerT + * @param begin Start of range of elements to split + * @param end End of range of elements to split + * @param in_delim Delimiter to split upon + * @param in_delim_begin Start of range containing the delimiter + * @param in_delim_end End of range containing the delimiter + * @return Container containing to up `in_limit` + 1 substrings; result[in_limit] is the unprocessed remainder + */ +template typename ContainerT = std::vector, typename... ContainerArgsT, typename ItrT, typename EndT, typename DelimItrT, typename DelimEndT> +constexpr auto split_n(ItrT begin, EndT end, DelimItrT in_delim_begin, DelimEndT in_delim_end, size_t in_limit) { + using ElementT = std::remove_cvref_t; + using MemberT = typename impl::first_arg>::first_type; + using container_type = impl::split_container_helper_t; + + auto delim_length = std::distance(in_delim_begin, in_delim_end); + if (delim_length == 1) { + return split_n(begin, end, *in_delim_begin, in_limit); + } + + container_type result{}; + if (begin >= end) { + // Nothing to split + return result; + } + + if (in_delim_begin >= in_delim_end + || (end - begin) < delim_length) { + // Absent or impossible to match delimiter, therefore no match, therefore return input as single token + result.push_back(impl::member_from_range(begin, end)); + return result; + } + + auto itr_end = end - (delim_length - 1); + for (auto itr = begin; itr < itr_end && in_limit != 0;) { + if (std::equal(in_delim_begin, in_delim_end, itr)) { + // Push token to result + result.push_back(impl::member_from_range(begin, itr)); + itr += delim_length; + begin = itr; + --in_limit; + continue; + } + + ++itr; + } + + // Push final token to the end; may be empty + result.push_back(impl::member_from_range(begin, end)); + + return result; +} + +/** + * Splits a string up to a specified number of times and returns the result + * + * @tparam ContainerT Container type to store the results in + * @tparam ContainerArgsT Optional template parameters for ContainerT + * @param in_string String to split + * @param in_delim Delimiter to split upon + * @param in_limit Maximum number of times to split + * @return Container containing to up `in_limit` + 1 substrings; result[in_limit] is the unprocessed remainder + */ +template typename ContainerT = std::vector, typename... ContainerArgsT, typename InputT> +constexpr auto split_n(const InputT& in_string, typename InputT::value_type in_delim, size_t in_limit) { + return split_n(in_string.begin(), in_string.end(), in_delim, in_limit); +} + +template typename ContainerT = std::vector, typename... ContainerArgsT, typename InputT, typename DelimT, + typename std::enable_if::value>::type* = nullptr> +constexpr auto split_n(const InputT& in_string, const DelimT& in_delim, size_t in_limit) { + return split_n(in_string.begin(), in_string.end(), in_delim.begin(), in_delim.end(), in_limit); +} + /** Splits an input string into view substrings; cannot specify element return type */ -template typename ContainerT = std::vector, typename... ContainerArgsT, typename InStringT, typename MemberT = std::basic_string_view> -constexpr ContainerT split_view(const InStringT& in_string, typename InStringT::value_type in_delim) { +template typename ContainerT = std::vector, typename... ContainerArgsT, typename InputT, typename DelimT> +constexpr auto split_view(const InputT& in_string, const DelimT& in_delim) { + using MemberT = std::basic_string_view; return split(in_string, in_delim); } -/** Splits an input string into view substrings (same as split, but different default return type) */ -template> -constexpr std::pair split_once_view(const InStringT& in_string, typename InStringT::value_type in_delim) { - return split_once(in_string, in_delim); +/** Splits an input string into view substring pair */ +template +constexpr auto split_once_view(const InputT& in_string, const DelimT& in_delim) { + // Using a static assertion instead of `auto` for inputs, because this is the only place it would be used, and it'd + // require -fconcepts which isn't currently used. Replace inputs with `auto` later and remove template parameters + // if -fconcepts is ever added. + static_assert(sizeof...(NothingT) == 0, "split_once_view does not accept template parameters"); + using MemberT = std::basic_string_view; + return split_once(in_string, in_delim); } /** Splits an input string into a specified number of view substrings */ -template typename ContainerT = std::vector, typename... ContainerArgsT, typename InStringT, typename MemberT = std::basic_string_view> -constexpr auto split_n_view(const InStringT& in_string, typename InStringT::value_type in_delim, size_t in_limit) { +template typename ContainerT = std::vector, typename... ContainerArgsT, + typename InputT, typename DelimT> +constexpr auto split_n_view(const InputT& in_string, const DelimT& in_delim, size_t in_limit) { + using MemberT = std::basic_string_view; return split_n(in_string, in_delim, in_limit); } diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 648fa69..19c7dae 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -1,6 +1,6 @@ # Setup source files set(SOURCE_FILES - timer.cpp thread_pool.cpp util.cpp object.cpp parser.cpp config.cpp parsers/json.cpp unicode.cpp app_parameters.cpp io/color.cpp duration.cpp split.cpp) + timer.cpp thread_pool.cpp util.cpp object.cpp parser.cpp config.cpp parsers/json.cpp unicode.cpp app_parameters.cpp io/color.cpp duration.cpp split.cpp split_compilation.cpp) # Setup gtest set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) diff --git a/src/test/split.cpp b/src/test/split.cpp index 3e0101f..23f6581 100644 --- a/src/test/split.cpp +++ b/src/test/split.cpp @@ -18,18 +18,12 @@ #include "split.hpp" #include -#include -#include -#include -#include "test.hpp" +#include "test_split.hpp" using namespace jessilib; using namespace std::literals; -// empty strings - using char_types = ::testing::Types; -using text_char_types = ::testing::Types; template class SplitSVTest : public ::testing::Test { @@ -38,22 +32,10 @@ public: TYPED_TEST_SUITE(SplitSVTest, char_types); template -class SplitSringTest : public ::testing::Test { -public: -}; -TYPED_TEST_SUITE(SplitSringTest, char_types); - -template -class SplitViewSVTest : public ::testing::Test { +class SplitStringTest : public ::testing::Test { public: }; -TYPED_TEST_SUITE(SplitViewSVTest, char_types); - -template -class SplitViewStringTest : public ::testing::Test { -public: -}; -TYPED_TEST_SUITE(SplitViewStringTest, char_types); +TYPED_TEST_SUITE(SplitStringTest, char_types); template class SplitOnceTest : public ::testing::Test { @@ -67,100 +49,24 @@ public: }; TYPED_TEST_SUITE(SplitNTest, char_types); -template> -ResultT make_word(size_t length = 8, T delim = static_cast(0)) { - ResultT result; - - if (length == 0) { - return {}; - } - - result.push_back(delim + 1); - while (result.size() < length) { - auto chr = result.back() + 1; - if (chr == delim) { - ++chr; - } - result.push_back(chr); - } - - if (result.size() != length) { - std::string errmsg = std::to_string(result.size()) + " != " + std::to_string(length) + "; result.size() != length"; - throw std::runtime_error{ errmsg }; - } - - return result; -} - -template -std::basic_string_view make_word_view(size_t length = 8, T delim = static_cast(0)) { - static std::basic_string s_result; - s_result = make_word(length, delim); - return s_result; -} - -template> -struct RandomTestData { - RandomTestData(T in_delim) - : m_delim{ in_delim } { - operator()(); - } - - void operator()() { - m_tokens.clear(); - m_str.clear(); - - std::mt19937 randgen(static_cast(std::chrono::system_clock::now().time_since_epoch().count())); - std::uniform_int_distribution word_count_distribution(5, 64); - std::uniform_int_distribution word_length_distribution(0, 16); - - auto random_words = word_count_distribution(randgen); - while (m_tokens.size() < random_words) { - m_tokens.push_back(make_word(word_length_distribution(randgen))); - m_str.insert(m_str.end(), m_tokens.back().begin(), m_tokens.back().end()); - if (m_tokens.size() < random_words) { - m_str.insert(m_str.end(), m_delim); - } - } - } - - StringT get_remainder(size_t in_times_split) { - StringT result; - while (in_times_split < m_tokens.size()) { - auto& token = m_tokens[in_times_split]; - result.insert(result.end(), token.begin(), token.end()); - ++in_times_split; - if (in_times_split < m_tokens.size()) { - result.insert(result.end(), m_delim); - } - } - - return result; - } - - T m_delim; - StringT m_str; - std::vector m_tokens; -}; - TYPED_TEST(SplitSVTest, empty) { std::basic_string_view empty; - std::vector> split_result = split(empty, static_cast(0)); + std::vector> split_result = split(empty, default_delim); EXPECT_TRUE(split_result.empty()); } TYPED_TEST(SplitSVTest, single_word) { std::basic_string_view single_word = make_word_view(); - std::vector> split_result = split(single_word, static_cast(0)); + std::vector> split_result = split(single_word, default_delim); EXPECT_EQ(split_result.size(), 1); EXPECT_EQ(split_result[0].size(), 8); } TYPED_TEST(SplitSVTest, single_word_trailing_delim) { auto word = make_word(); - word += static_cast(0); + word += default_delim; std::basic_string_view single_word = word; - std::vector> split_result = split(single_word, static_cast(0)); + std::vector> split_result = split(single_word, default_delim); EXPECT_EQ(split_result.size(), 2); EXPECT_EQ(split_result[0].size(), 8); EXPECT_EQ(split_result[1].size(), 0); @@ -168,10 +74,10 @@ TYPED_TEST(SplitSVTest, single_word_trailing_delim) { TYPED_TEST(SplitSVTest, single_word_prefix_delim) { std::basic_string word; - word += static_cast(0); + word += default_delim; word += make_word(); std::basic_string_view single_word = word; - std::vector> split_result = split(single_word, static_cast(0)); + std::vector> split_result = split(single_word, default_delim); EXPECT_EQ(split_result.size(), 2); EXPECT_EQ(split_result[0].size(), 0); EXPECT_EQ(split_result[1].size(), 8); @@ -179,11 +85,11 @@ TYPED_TEST(SplitSVTest, single_word_prefix_delim) { TYPED_TEST(SplitSVTest, single_word_surround_delim) { std::basic_string word; - word += static_cast(0); + word += default_delim; word += make_word(); - word += static_cast(0); + word += default_delim; std::basic_string_view single_word = word; - std::vector> split_result = split(single_word, static_cast(0)); + std::vector> split_result = split(single_word, default_delim); EXPECT_EQ(split_result.size(), 3); EXPECT_EQ(split_result[0].size(), 0); EXPECT_EQ(split_result[1].size(), 8); @@ -192,10 +98,10 @@ TYPED_TEST(SplitSVTest, single_word_surround_delim) { TYPED_TEST(SplitSVTest, two_words) { auto word = make_word(); - word += static_cast(0); + word += default_delim; word += make_word(); std::basic_string_view words = word; - std::vector> split_result = split(words, static_cast(0)); + std::vector> split_result = split(words, default_delim); EXPECT_EQ(split_result.size(), 2); EXPECT_EQ(split_result[0].size(), 8); EXPECT_EQ(split_result[1].size(), 8); @@ -203,12 +109,12 @@ TYPED_TEST(SplitSVTest, two_words) { TYPED_TEST(SplitSVTest, three_words) { auto word = make_word(3); - word += static_cast(0); + word += default_delim; word += make_word(5); - word += static_cast(0); + word += default_delim; word += make_word(9); std::basic_string_view words = word; - std::vector> split_result = split(words, static_cast(0)); + std::vector> split_result = split(words, default_delim); EXPECT_EQ(split_result.size(), 3); EXPECT_EQ(split_result[0].size(), 3); EXPECT_EQ(split_result[1].size(), 5); @@ -218,24 +124,25 @@ TYPED_TEST(SplitSVTest, three_words) { /** SplitOnceTest */ TYPED_TEST(SplitOnceTest, random) { - RandomTestData data{ static_cast(0) }; - std::pair, std::basic_string> split_result = split_once(data.m_str, data.m_delim); + RandomTestData data{}; + std::pair, std::basic_string> split_result = split_once(data.m_str, default_delim); EXPECT_EQ(split_result.first, data.m_tokens[0]); EXPECT_EQ(split_result.second, data.get_remainder(1)); } TYPED_TEST(SplitOnceTest, random_vector) { - RandomTestData> data{ static_cast(0) }; - std::pair, std::vector> split_result = split_once(data.m_str, data.m_delim); + using vector_type = std::vector; + RandomTestData data{}; + std::pair split_result = split_once(data.m_str, default_delim); EXPECT_EQ(split_result.first, data.m_tokens[0]); EXPECT_EQ(split_result.second, data.get_remainder(1)); } TYPED_TEST(SplitOnceTest, random_view) { - RandomTestData data{ static_cast(0) }; - std::pair, std::basic_string_view> split_result = split_once_view(data.m_str, data.m_delim); + RandomTestData data{}; + std::pair, std::basic_string_view> split_result = split_once_view(data.m_str, default_delim); EXPECT_EQ(split_result.first, data.m_tokens[0]); EXPECT_EQ(split_result.second, data.get_remainder(1)); @@ -244,9 +151,9 @@ TYPED_TEST(SplitOnceTest, random_view) { /** SplitNTest */ TYPED_TEST(SplitNTest, random) { - RandomTestData data{ static_cast(0) }; + RandomTestData data{}; constexpr size_t n = 4; - std::vector> split_result = split_n(data.m_str, data.m_delim, n); + std::vector> split_result = split_n(data.m_str, default_delim, n); // Tokens shall be same up until last one (n + 1) EXPECT_EQ(split_result.size(), n + 1); @@ -258,9 +165,9 @@ TYPED_TEST(SplitNTest, random) { } TYPED_TEST(SplitNTest, random_vector) { - RandomTestData> data{ static_cast(0) }; + RandomTestData> data{}; constexpr size_t n = 4; - std::vector> split_result = split_n>(data.m_str, data.m_delim, n); + std::vector> split_result = split_n>(data.m_str, default_delim, n); // Tokens shall be same up until last one (n + 1) EXPECT_EQ(split_result.size(), n + 1); @@ -272,9 +179,9 @@ TYPED_TEST(SplitNTest, random_vector) { } TYPED_TEST(SplitNTest, random_view) { - RandomTestData data{ static_cast(0) }; + RandomTestData data{}; constexpr size_t n = 4; - std::vector> split_result = split_n_view(data.m_str, data.m_delim, n); + std::vector> split_result = split_n_view(data.m_str, default_delim, n); // Tokens shall be same up until last one (n + 1) EXPECT_EQ(split_result.size(), n + 1); @@ -287,73 +194,87 @@ TYPED_TEST(SplitNTest, random_view) { /** std::string split test, really just testing compilation and returned types */ -TYPED_TEST(SplitSringTest, empty) { +TYPED_TEST(SplitStringTest, empty) { std::basic_string empty; - std::vector split_result = split(empty, static_cast(0)); + std::vector split_result = split(empty, default_delim); EXPECT_TRUE(split_result.empty()); } -TYPED_TEST(SplitSringTest, single_word) { +TYPED_TEST(SplitStringTest, empty_long) { + std::basic_string empty; + auto delim = make_delim_long(8); + std::vector split_result = split(empty, delim); + EXPECT_TRUE(split_result.empty()); +} + +TYPED_TEST(SplitStringTest, single_word) { std::basic_string single_word = make_word(); - std::vector split_result = split(single_word, static_cast(0)); + std::vector split_result = split(single_word, default_delim); EXPECT_EQ(split_result.size(), 1); EXPECT_EQ(split_result[0].size(), 8); } -TYPED_TEST(SplitSringTest, random) { - RandomTestData data{ static_cast(0) }; - std::vector> split_result = split(data.m_str, data.m_delim); - EXPECT_EQ(split_result, data.m_tokens); +TYPED_TEST(SplitStringTest, single_word_long) { + std::basic_string single_word = make_word(); + auto delim = make_delim_long(8); + std::vector split_result = split(single_word, delim); + EXPECT_EQ(split_result.size(), 1); + EXPECT_EQ(split_result[0].size(), 8); } -TYPED_TEST(SplitSringTest, random_vector) { - RandomTestData> data{ static_cast(0) }; - std::vector> split_result = split>(data.m_str, data.m_delim); +TYPED_TEST(SplitStringTest, random) { + RandomTestData data{}; + std::vector> split_result = split(data.m_str, default_delim); EXPECT_EQ(split_result, data.m_tokens); } -/** Some basic tests for compiling with different containers */ - -TYPED_TEST(SplitSVTest, empty_deque) { - std::basic_string_view empty; - std::deque> split_result = split(empty, static_cast(0)); - EXPECT_TRUE(split_result.empty()); -} - -TYPED_TEST(SplitSVTest, empty_list) { - std::basic_string_view empty; - std::list> split_result = split(empty, static_cast(0)); - EXPECT_TRUE(split_result.empty()); +TYPED_TEST(SplitStringTest, random_long) { + auto delim = make_delim_long(8); + RandomTestData data{ delim }; + std::vector> split_result = split(data.m_str, delim); + EXPECT_EQ(split_result, data.m_tokens); } -/** SplitViewSVTest, really just compilation tests */ - -TYPED_TEST(SplitViewSVTest, empty) { - std::basic_string_view empty; - std::vector split_result = split_view(empty, static_cast(0)); - EXPECT_TRUE(split_result.empty()); +TYPED_TEST(SplitStringTest, random_vector) { + RandomTestData> data{}; + std::vector> split_result = split>(data.m_str, default_delim); + EXPECT_EQ(split_result, data.m_tokens); } -TYPED_TEST(SplitViewSVTest, single_word) { - std::basic_string_view single_word = make_word_view(); - std::vector split_result = split_view(single_word, static_cast(0)); - EXPECT_EQ(split_result.size(), 1); +TYPED_TEST(SplitStringTest, random_long_trailing_delim) { + auto delim = make_delim_long(8); + RandomTestData data{ delim }; + data.m_str += delim; + data.m_tokens.emplace_back(); + std::vector> split_result = split(data.m_str, delim); + EXPECT_EQ(split_result, data.m_tokens); } -TYPED_TEST(SplitViewStringTest, empty) { - std::basic_string empty; - std::vector> split_result = split_view(empty, static_cast(0)); - EXPECT_TRUE(split_result.empty()); +TYPED_TEST(SplitStringTest, random_long_prefix_delim) { + auto delim = make_delim_long(8); + RandomTestData data{ delim }; + data.m_str = delim + data.m_str; + data.m_tokens.emplace(data.m_tokens.begin()); + std::vector> split_result = split(data.m_str, delim); + EXPECT_EQ(split_result, data.m_tokens); } -TYPED_TEST(SplitViewStringTest, single_word) { - std::basic_string single_word = make_word(); - std::vector> split_result = split_view(single_word, static_cast(0)); - EXPECT_EQ(split_result.size(), 1); +TYPED_TEST(SplitStringTest, random_long_trailing_two_delim) { + auto delim = make_delim_long(8); + RandomTestData data{ delim }; + data.m_str += delim + delim; + data.m_tokens.emplace_back(); + data.m_tokens.emplace_back(); + std::vector> split_result = split(data.m_str, delim); + EXPECT_EQ(split_result, data.m_tokens); } -TYPED_TEST(SplitViewStringTest, random) { - RandomTestData data{ static_cast(0) }; - auto split_result = split_view(data.m_str, data.m_delim); - EXPECT_EQ(split_result.size(), data.m_tokens.size()); +TYPED_TEST(SplitStringTest, random_long_prefix_two_delim) { + auto delim = make_delim_long(8); + RandomTestData data{ delim }; + data.m_str = delim + delim + data.m_str; + data.m_tokens.emplace(data.m_tokens.begin()); + data.m_tokens.emplace(data.m_tokens.begin()); + std::vector> split_result = split(data.m_str, delim); + EXPECT_EQ(split_result, data.m_tokens); } diff --git a/src/test/split_compilation.cpp b/src/test/split_compilation.cpp new file mode 100644 index 0000000..715e0a2 --- /dev/null +++ b/src/test/split_compilation.cpp @@ -0,0 +1,103 @@ +/** + * Copyright (C) 2021 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +/** +* Tests which exist solely test test compilation for split.hpp, because MSVC demands /bigobj, and I don't want to +*/ + +#include "split.hpp" +#include +#include +#include "test_split.hpp" + +using namespace jessilib; +using namespace std::literals; + +using char_types = ::testing::Types; + +template +class SplitCompilationTest : public ::testing::Test { +public: +}; +TYPED_TEST_SUITE(SplitCompilationTest, char_types); + +/** Some basic tests for _view variants */ + +TYPED_TEST(SplitCompilationTest, empty) { + // string_view w/ short delimiter + std::basic_string_view empty; + auto delim = default_delim; + std::vector split_result = split_view(empty, delim); + EXPECT_TRUE(split_result.empty()); +} + +TYPED_TEST(SplitCompilationTest, empty_long) { + // string_view w/ long delimiter + std::basic_string_view empty; + auto delim = make_delim_long(); + EXPECT_TRUE(split_view(empty, delim).empty()); + EXPECT_TRUE(split_once_view(empty, delim).first.empty()); +} + +TYPED_TEST(SplitCompilationTest, empty_str) { + // string w/ short delimiter + std::basic_string empty; + auto delim = default_delim; + EXPECT_TRUE(split_view(empty, delim).empty()); + EXPECT_TRUE(split_once_view(empty, delim).first.empty()); +} + +TYPED_TEST(SplitCompilationTest, empty_str_long) { + // string w/ long delimiter + std::basic_string empty; + auto delim = make_delim_long(); + EXPECT_TRUE(split_view(empty, delim).empty()); + EXPECT_TRUE(split_once_view(empty, delim).first.empty()); +} + +/** Some basic tests for compiling with different containers */ + +TYPED_TEST(SplitCompilationTest, empty_deque) { + std::basic_string_view empty; + std::deque> split_result = split(empty, default_delim); + EXPECT_TRUE(split_result.empty()); +} + +TYPED_TEST(SplitCompilationTest, empty_deque_long) { + std::basic_string_view empty; + auto delim = make_delim_long(); + EXPECT_TRUE(split(empty, delim).empty()); + EXPECT_TRUE(split_n(empty, delim, 5).empty()); + EXPECT_TRUE(split_view(empty, delim).empty()); + EXPECT_TRUE(split_n_view(empty, delim, 5).empty()); +} + +TYPED_TEST(SplitCompilationTest, empty_list) { + std::basic_string_view empty; + std::list> split_result = split(empty, default_delim); + EXPECT_TRUE(split_result.empty()); +} + +TYPED_TEST(SplitCompilationTest, empty_list_long) { + std::basic_string_view empty; + auto delim = make_delim_long(); + EXPECT_TRUE(split(empty, delim).empty()); + EXPECT_TRUE(split_n(empty, delim, 5).empty()); + EXPECT_TRUE(split_view(empty, delim).empty()); + EXPECT_TRUE(split_n_view(empty, delim, 5).empty()); +} diff --git a/src/test/test_split.hpp b/src/test/test_split.hpp new file mode 100644 index 0000000..2efcb4e --- /dev/null +++ b/src/test/test_split.hpp @@ -0,0 +1,135 @@ +/** + * Copyright (C) 2021 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +#pragma once + +#include "split.hpp" +#include "test.hpp" +#include + +template +constexpr T default_delim{}; + +template> +ResultT make_word(size_t length = 8, T delim = default_delim) { + ResultT result; + + if (length == 0) { + return {}; + } + + result.push_back(delim + 1); + while (result.size() < length) { + auto chr = result.back() + 1; + if (chr == delim) { + ++chr; + } + result.push_back(chr); + } + + if (result.size() != length) { + std::string errmsg = std::to_string(result.size()) + " != " + std::to_string(length) + "; result.size() != length"; + throw std::runtime_error{ errmsg }; + } + + return result; +} + +template> +ResultT make_delim_long(size_t length = 8, T in_delim = default_delim) { + // in this context, in_delim should be whatever was previously passed to make_word + // doing so will ensure the value returned by this method is distinctly different from others + ResultT result; + result += in_delim; + result += make_word(length, in_delim); + result += in_delim; + return result; +} + +template +std::basic_string_view make_word_view(size_t length = 8, T delim = default_delim) { + static std::basic_string s_result; + s_result = make_word(length, delim); + return s_result; +} + +template> +struct RandomTestData { + RandomTestData(T in_delim = default_delim, size_t in_fixed_word_count = 0, size_t in_fixed_word_length = 0) + : m_fixed_word_count{ in_fixed_word_count }, + m_fixed_word_length{ in_fixed_word_length } { + m_delim.insert(m_delim.end(), in_delim); + operator()(); + } + + RandomTestData(StringT in_delim, size_t in_fixed_word_count = 0, size_t in_fixed_word_length = 0) + : m_delim{ in_delim }, + m_fixed_word_count{ in_fixed_word_count }, + m_fixed_word_length{ in_fixed_word_length } { + operator()(); + } + + void operator()() { + m_tokens.clear(); + m_str.clear(); + + std::mt19937 randgen(static_cast(std::chrono::system_clock::now().time_since_epoch().count())); + std::uniform_int_distribution word_count_distribution(5, 64); + std::uniform_int_distribution word_length_distribution(0, 16); + + auto word_count = m_fixed_word_count; + if (word_count == 0) { + word_count = word_count_distribution(randgen); + } + + while (m_tokens.size() < word_count) { + auto word_length = m_fixed_word_length; + if (word_length == 0) { + word_length = word_length_distribution(randgen); + } + m_tokens.push_back(make_word(word_length, m_delim.back())); + m_str.insert(m_str.end(), m_tokens.back().begin(), m_tokens.back().end()); + if (m_tokens.size() < word_count) { + m_str.insert(m_str.end(), m_delim.begin(), m_delim.end()); + } + } + } + + StringT get_remainder(size_t in_times_split) { + StringT result; + while (in_times_split < m_tokens.size()) { + auto& token = m_tokens[in_times_split]; + result.insert(result.end(), token.begin(), token.end()); + ++in_times_split; + if (in_times_split < m_tokens.size()) { + result.insert(result.end(), m_delim.begin(), m_delim.end()); + } + } + + return result; + } + + // Inputs + StringT m_delim; + size_t m_fixed_word_count{}; + size_t m_fixed_word_length{}; + + // Outputs + StringT m_str; + std::vector m_tokens; +};