Browse Source

Added split, split_once, split_n variants accepting strings as

delimiters
More tests
Another minor fix for debug assertion failure in timer
master
Jessica James 3 years ago
parent
commit
e7e08719a0
  1. 2
      src/common/timer/timer_manager.cpp
  2. 328
      src/include/split.hpp
  3. 2
      src/test/CMakeLists.txt
  4. 257
      src/test/split.cpp
  5. 103
      src/test/split_compilation.cpp
  6. 135
      src/test/test_split.hpp

2
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

328
src/include/split.hpp

@ -16,11 +16,18 @@
* Written by Jessica James <jessica.aj@outlook.com>
*/
/**
* @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 <string_view>
#include <vector>
#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<ContainerT, A
template<typename MemberT, typename ItrT, typename EndT, typename std::enable_if<is_basic_string_view<MemberT>::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<size_t>(in_end - in_itr) };
if constexpr (std::is_pointer_v<MemberT>) {
return { in_itr, static_cast<size_t>(in_end - in_itr) };
}
auto& element = *in_itr;
return { &element, static_cast<size_t>(in_end - in_itr) };
}
template<typename MemberT, typename ItrT, typename EndT, typename std::enable_if<!is_basic_string_view<MemberT>::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<template<typename...> 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<ContainerArgsT..., std::basic_string<typename InputT::value_type>>::first_type;
template<template<typename...> 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<ContainerArgsT..., std::basic_string<ElementT>>::first_type;
using container_type = impl::split_container_helper_t<ContainerT, MemberT, ContainerArgsT...>;
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<template<typename...> 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<decltype(*begin)>;
using MemberT = typename impl::first_arg<ContainerArgsT..., std::basic_string<ElementT>>::first_type;
using container_type = impl::split_container_helper_t<ContainerT, MemberT, ContainerArgsT...>;
auto delim_length = std::distance(in_delim_begin, in_delim_end);
if (delim_length == 1) {
return split<ContainerT, ContainerArgsT...>(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<MemberT>(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<MemberT>(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<MemberT>(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<template<typename...> typename ContainerT = std::vector, typename... ContainerArgsT, typename InputT>
constexpr auto split(const InputT& in_string, typename InputT::value_type in_delim) {
return split<ContainerT, ContainerArgsT...>(in_string.begin(), in_string.end(), in_delim);
}
template<template<typename...> typename ContainerT = std::vector, typename... ContainerArgsT, typename InputT, typename DelimT,
typename std::enable_if<!std::is_same<DelimT, typename InputT::value_type>::value>::type* = nullptr>
constexpr auto split(const InputT& in_string, const DelimT& in_delim) {
return split<ContainerT, ContainerArgsT...>(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<typename InStringT, typename ResultMemberT = InStringT>
constexpr std::pair<ResultMemberT, ResultMemberT> split_once(const InStringT& in_string, typename InStringT::value_type in_delim) {
// Nothing in, nothing out
std::pair<ResultMemberT, ResultMemberT> result;
if (in_string.empty()) {
template<typename... OptionalMemberT, typename ItrT, typename EndT, typename ElementT>
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<OptionalMemberT..., std::basic_string<ElementT>>::first_type;
std::pair<MemberT, MemberT> 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<ResultMemberT>(begin, itr);
result.second = impl::member_from_range<ResultMemberT>(itr + 1, end);
result.first = impl::member_from_range<MemberT>(begin, itr);
result.second = impl::member_from_range<MemberT>(itr + 1, end);
return result;
}
}
// in_delim not found
result.first = in_string;
result.first = impl::member_from_range<MemberT>(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<typename... OptionalMemberT, typename ItrT, typename EndT, typename DelimItrT, typename DelimEndT>
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<decltype(*begin)>;
using MemberT = typename impl::first_arg<OptionalMemberT..., std::basic_string<ElementT>>::first_type;
auto delim_length = std::distance(in_delim_begin, in_delim_end);
if (delim_length == 1) {
return split_once<OptionalMemberT...>(begin, end, *in_delim_begin);
}
std::pair<MemberT, MemberT> 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<MemberT>(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<MemberT>(begin, itr);
result.second = impl::member_from_range<MemberT>(itr + delim_length, end);
return result;
}
}
// in_delim not found
result.first = impl::member_from_range<MemberT>(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<typename... OptionalMemberT, typename InputT>
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<OptionalMemberT..., std::basic_string<ElementT>>::first_type;
return split_once<MemberT>(in_string.begin(), in_string.end(), in_delim);
}
template<typename... OptionalMemberT, typename InputT, typename DelimT,
typename std::enable_if<!std::is_same<DelimT, typename InputT::value_type>::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<OptionalMemberT...>(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<template<typename...> 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<ContainerArgsT..., std::basic_string<typename InputT::value_type>>::first_type;
template<template<typename...> 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<ContainerArgsT..., std::basic_string<ElementT>>::first_type;
using container_type = impl::split_container_helper_t<ContainerT, MemberT, ContainerArgsT...>;
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<template<typename...> 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<decltype(*begin)>;
using MemberT = typename impl::first_arg<ContainerArgsT..., std::basic_string<ElementT>>::first_type;
using container_type = impl::split_container_helper_t<ContainerT, MemberT, ContainerArgsT...>;
auto delim_length = std::distance(in_delim_begin, in_delim_end);
if (delim_length == 1) {
return split_n<ContainerT, ContainerArgsT...>(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<MemberT>(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<MemberT>(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<MemberT>(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<template<typename...> 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<ContainerT, ContainerArgsT...>(in_string.begin(), in_string.end(), in_delim, in_limit);
}
template<template<typename...> typename ContainerT = std::vector, typename... ContainerArgsT, typename InputT, typename DelimT,
typename std::enable_if<!std::is_same<DelimT, typename InputT::value_type>::value>::type* = nullptr>
constexpr auto split_n(const InputT& in_string, const DelimT& in_delim, size_t in_limit) {
return split_n<ContainerT, ContainerArgsT...>(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<template<typename...> typename ContainerT = std::vector, typename... ContainerArgsT, typename InStringT, typename MemberT = std::basic_string_view<typename InStringT::value_type>>
constexpr ContainerT<MemberT, ContainerArgsT...> split_view(const InStringT& in_string, typename InStringT::value_type in_delim) {
template<template<typename...> 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<typename InputT::value_type>;
return split<ContainerT, MemberT, ContainerArgsT...>(in_string, in_delim);
}
/** Splits an input string into view substrings (same as split, but different default return type) */
template<typename InStringT, typename ResultT = std::basic_string_view<typename InStringT::value_type>>
constexpr std::pair<ResultT, ResultT> split_once_view(const InStringT& in_string, typename InStringT::value_type in_delim) {
return split_once<InStringT, ResultT>(in_string, in_delim);
/** Splits an input string into view substring pair */
template<typename... NothingT, typename InputT, typename DelimT>
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<typename InputT::value_type>;
return split_once<MemberT>(in_string, in_delim);
}
/** Splits an input string into a specified number of view substrings */
template<template<typename...> typename ContainerT = std::vector, typename... ContainerArgsT, typename InStringT, typename MemberT = std::basic_string_view<typename InStringT::value_type>>
constexpr auto split_n_view(const InStringT& in_string, typename InStringT::value_type in_delim, size_t in_limit) {
template<template<typename...> 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<typename InputT::value_type>;
return split_n<ContainerT, MemberT, ContainerArgsT...>(in_string, in_delim, in_limit);
}

2
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)

257
src/test/split.cpp

@ -18,18 +18,12 @@
#include "split.hpp"
#include <cassert>
#include <deque>
#include <list>
#include <random>
#include "test.hpp"
#include "test_split.hpp"
using namespace jessilib;
using namespace std::literals;
// empty strings
using char_types = ::testing::Types<char, unsigned char, signed char, wchar_t, char8_t, char16_t, char32_t>;
using text_char_types = ::testing::Types<char, unsigned char, signed char, wchar_t, char8_t, char16_t, char32_t>;
template<typename T>
class SplitSVTest : public ::testing::Test {
@ -38,22 +32,10 @@ public:
TYPED_TEST_SUITE(SplitSVTest, char_types);
template<typename T>
class SplitSringTest : public ::testing::Test {
public:
};
TYPED_TEST_SUITE(SplitSringTest, char_types);
template<typename T>
class SplitViewSVTest : public ::testing::Test {
class SplitStringTest : public ::testing::Test {
public:
};
TYPED_TEST_SUITE(SplitViewSVTest, char_types);
template<typename T>
class SplitViewStringTest : public ::testing::Test {
public:
};
TYPED_TEST_SUITE(SplitViewStringTest, char_types);
TYPED_TEST_SUITE(SplitStringTest, char_types);
template<typename T>
class SplitOnceTest : public ::testing::Test {
@ -67,100 +49,24 @@ public:
};
TYPED_TEST_SUITE(SplitNTest, char_types);
template<typename T, typename ResultT = std::basic_string<T>>
ResultT make_word(size_t length = 8, T delim = static_cast<T>(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<typename T>
std::basic_string_view<T> make_word_view(size_t length = 8, T delim = static_cast<T>(0)) {
static std::basic_string<T> s_result;
s_result = make_word(length, delim);
return s_result;
}
template<typename T, typename StringT = std::basic_string<T>>
struct RandomTestData {
RandomTestData(T in_delim)
: m_delim{ in_delim } {
operator()();
}
void operator()() {
m_tokens.clear();
m_str.clear();
std::mt19937 randgen(static_cast<unsigned int>(std::chrono::system_clock::now().time_since_epoch().count()));
std::uniform_int_distribution<uint32_t> word_count_distribution(5, 64);
std::uniform_int_distribution<uint32_t> word_length_distribution(0, 16);
auto random_words = word_count_distribution(randgen);
while (m_tokens.size() < random_words) {
m_tokens.push_back(make_word<T, StringT>(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<StringT> m_tokens;
};
TYPED_TEST(SplitSVTest, empty) {
std::basic_string_view<TypeParam> empty;
std::vector<std::basic_string<TypeParam>> split_result = split(empty, static_cast<TypeParam>(0));
std::vector<std::basic_string<TypeParam>> split_result = split(empty, default_delim<TypeParam>);
EXPECT_TRUE(split_result.empty());
}
TYPED_TEST(SplitSVTest, single_word) {
std::basic_string_view<TypeParam> single_word = make_word_view<TypeParam>();
std::vector<std::basic_string<TypeParam>> split_result = split(single_word, static_cast<TypeParam>(0));
std::vector<std::basic_string<TypeParam>> split_result = split(single_word, default_delim<TypeParam>);
EXPECT_EQ(split_result.size(), 1);
EXPECT_EQ(split_result[0].size(), 8);
}
TYPED_TEST(SplitSVTest, single_word_trailing_delim) {
auto word = make_word<TypeParam>();
word += static_cast<TypeParam>(0);
word += default_delim<TypeParam>;
std::basic_string_view<TypeParam> single_word = word;
std::vector<std::basic_string<TypeParam>> split_result = split(single_word, static_cast<TypeParam>(0));
std::vector<std::basic_string<TypeParam>> split_result = split(single_word, default_delim<TypeParam>);
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<TypeParam> word;
word += static_cast<TypeParam>(0);
word += default_delim<TypeParam>;
word += make_word<TypeParam>();
std::basic_string_view<TypeParam> single_word = word;
std::vector<std::basic_string<TypeParam>> split_result = split(single_word, static_cast<TypeParam>(0));
std::vector<std::basic_string<TypeParam>> split_result = split(single_word, default_delim<TypeParam>);
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<TypeParam> word;
word += static_cast<TypeParam>(0);
word += default_delim<TypeParam>;
word += make_word<TypeParam>();
word += static_cast<TypeParam>(0);
word += default_delim<TypeParam>;
std::basic_string_view<TypeParam> single_word = word;
std::vector<std::basic_string<TypeParam>> split_result = split(single_word, static_cast<TypeParam>(0));
std::vector<std::basic_string<TypeParam>> split_result = split(single_word, default_delim<TypeParam>);
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<TypeParam>();
word += static_cast<TypeParam>(0);
word += default_delim<TypeParam>;
word += make_word<TypeParam>();
std::basic_string_view<TypeParam> words = word;
std::vector<std::basic_string<TypeParam>> split_result = split(words, static_cast<TypeParam>(0));
std::vector<std::basic_string<TypeParam>> split_result = split(words, default_delim<TypeParam>);
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<TypeParam>(3);
word += static_cast<TypeParam>(0);
word += default_delim<TypeParam>;
word += make_word<TypeParam>(5);
word += static_cast<TypeParam>(0);
word += default_delim<TypeParam>;
word += make_word<TypeParam>(9);
std::basic_string_view<TypeParam> words = word;
std::vector<std::basic_string<TypeParam>> split_result = split(words, static_cast<TypeParam>(0));
std::vector<std::basic_string<TypeParam>> split_result = split(words, default_delim<TypeParam>);
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<TypeParam> data{ static_cast<TypeParam>(0) };
std::pair<std::basic_string<TypeParam>, std::basic_string<TypeParam>> split_result = split_once(data.m_str, data.m_delim);
RandomTestData<TypeParam> data{};
std::pair<std::basic_string<TypeParam>, std::basic_string<TypeParam>> split_result = split_once(data.m_str, default_delim<TypeParam>);
EXPECT_EQ(split_result.first, data.m_tokens[0]);
EXPECT_EQ(split_result.second, data.get_remainder(1));
}
TYPED_TEST(SplitOnceTest, random_vector) {
RandomTestData<TypeParam, std::vector<TypeParam>> data{ static_cast<TypeParam>(0) };
std::pair<std::vector<TypeParam>, std::vector<TypeParam>> split_result = split_once(data.m_str, data.m_delim);
using vector_type = std::vector<TypeParam>;
RandomTestData<TypeParam, vector_type> data{};
std::pair<vector_type, vector_type> split_result = split_once<vector_type>(data.m_str, default_delim<TypeParam>);
EXPECT_EQ(split_result.first, data.m_tokens[0]);
EXPECT_EQ(split_result.second, data.get_remainder(1));
}
TYPED_TEST(SplitOnceTest, random_view) {
RandomTestData<TypeParam> data{ static_cast<TypeParam>(0) };
std::pair<std::basic_string_view<TypeParam>, std::basic_string_view<TypeParam>> split_result = split_once_view(data.m_str, data.m_delim);
RandomTestData<TypeParam> data{};
std::pair<std::basic_string_view<TypeParam>, std::basic_string_view<TypeParam>> split_result = split_once_view(data.m_str, default_delim<TypeParam>);
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<TypeParam> data{ static_cast<TypeParam>(0) };
RandomTestData<TypeParam> data{};
constexpr size_t n = 4;
std::vector<std::basic_string<TypeParam>> split_result = split_n(data.m_str, data.m_delim, n);
std::vector<std::basic_string<TypeParam>> split_result = split_n(data.m_str, default_delim<TypeParam>, 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<TypeParam, std::vector<TypeParam>> data{ static_cast<TypeParam>(0) };
RandomTestData<TypeParam, std::vector<TypeParam>> data{};
constexpr size_t n = 4;
std::vector<std::vector<TypeParam>> split_result = split_n<std::vector, std::vector<TypeParam>>(data.m_str, data.m_delim, n);
std::vector<std::vector<TypeParam>> split_result = split_n<std::vector, std::vector<TypeParam>>(data.m_str, default_delim<TypeParam>, 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<TypeParam> data{ static_cast<TypeParam>(0) };
RandomTestData<TypeParam> data{};
constexpr size_t n = 4;
std::vector<std::basic_string_view<TypeParam>> split_result = split_n_view(data.m_str, data.m_delim, n);
std::vector<std::basic_string_view<TypeParam>> split_result = split_n_view(data.m_str, default_delim<TypeParam>, 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<TypeParam> empty;
std::vector<decltype(empty)> split_result = split(empty, static_cast<TypeParam>(0));
std::vector<decltype(empty)> split_result = split(empty, default_delim<TypeParam>);
EXPECT_TRUE(split_result.empty());
}
TYPED_TEST(SplitSringTest, single_word) {
TYPED_TEST(SplitStringTest, empty_long) {
std::basic_string<TypeParam> empty;
auto delim = make_delim_long<TypeParam>(8);
std::vector<decltype(empty)> split_result = split(empty, delim);
EXPECT_TRUE(split_result.empty());
}
TYPED_TEST(SplitStringTest, single_word) {
std::basic_string<TypeParam> single_word = make_word<TypeParam>();
std::vector<decltype(single_word)> split_result = split(single_word, static_cast<TypeParam>(0));
std::vector<decltype(single_word)> split_result = split(single_word, default_delim<TypeParam>);
EXPECT_EQ(split_result.size(), 1);
EXPECT_EQ(split_result[0].size(), 8);
}
TYPED_TEST(SplitSringTest, random) {
RandomTestData<TypeParam> data{ static_cast<TypeParam>(0) };
std::vector<std::basic_string<TypeParam>> 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<TypeParam> single_word = make_word<TypeParam>();
auto delim = make_delim_long<TypeParam>(8);
std::vector<decltype(single_word)> 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<TypeParam, std::vector<TypeParam>> data{ static_cast<TypeParam>(0) };
std::vector<std::vector<TypeParam>> split_result = split<std::vector, std::vector<TypeParam>>(data.m_str, data.m_delim);
TYPED_TEST(SplitStringTest, random) {
RandomTestData<TypeParam> data{};
std::vector<std::basic_string<TypeParam>> split_result = split(data.m_str, default_delim<TypeParam>);
EXPECT_EQ(split_result, data.m_tokens);
}
/** Some basic tests for compiling with different containers */
TYPED_TEST(SplitSVTest, empty_deque) {
std::basic_string_view<TypeParam> empty;
std::deque<std::basic_string<TypeParam>> split_result = split<std::deque>(empty, static_cast<TypeParam>(0));
EXPECT_TRUE(split_result.empty());
}
TYPED_TEST(SplitSVTest, empty_list) {
std::basic_string_view<TypeParam> empty;
std::list<std::basic_string<TypeParam>> split_result = split<std::list>(empty, static_cast<TypeParam>(0));
EXPECT_TRUE(split_result.empty());
TYPED_TEST(SplitStringTest, random_long) {
auto delim = make_delim_long<TypeParam>(8);
RandomTestData<TypeParam> data{ delim };
std::vector<std::basic_string<TypeParam>> 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<TypeParam> empty;
std::vector<decltype(empty)> split_result = split_view(empty, static_cast<TypeParam>(0));
EXPECT_TRUE(split_result.empty());
TYPED_TEST(SplitStringTest, random_vector) {
RandomTestData<TypeParam, std::vector<TypeParam>> data{};
std::vector<std::vector<TypeParam>> split_result = split<std::vector, std::vector<TypeParam>>(data.m_str, default_delim<TypeParam>);
EXPECT_EQ(split_result, data.m_tokens);
}
TYPED_TEST(SplitViewSVTest, single_word) {
std::basic_string_view<TypeParam> single_word = make_word_view<TypeParam>();
std::vector<decltype(single_word)> split_result = split_view(single_word, static_cast<TypeParam>(0));
EXPECT_EQ(split_result.size(), 1);
TYPED_TEST(SplitStringTest, random_long_trailing_delim) {
auto delim = make_delim_long<TypeParam>(8);
RandomTestData<TypeParam> data{ delim };
data.m_str += delim;
data.m_tokens.emplace_back();
std::vector<std::basic_string<TypeParam>> split_result = split(data.m_str, delim);
EXPECT_EQ(split_result, data.m_tokens);
}
TYPED_TEST(SplitViewStringTest, empty) {
std::basic_string<TypeParam> empty;
std::vector<std::basic_string_view<TypeParam>> split_result = split_view(empty, static_cast<TypeParam>(0));
EXPECT_TRUE(split_result.empty());
TYPED_TEST(SplitStringTest, random_long_prefix_delim) {
auto delim = make_delim_long<TypeParam>(8);
RandomTestData<TypeParam> data{ delim };
data.m_str = delim + data.m_str;
data.m_tokens.emplace(data.m_tokens.begin());
std::vector<std::basic_string<TypeParam>> split_result = split(data.m_str, delim);
EXPECT_EQ(split_result, data.m_tokens);
}
TYPED_TEST(SplitViewStringTest, single_word) {
std::basic_string<TypeParam> single_word = make_word<TypeParam>();
std::vector<std::basic_string_view<TypeParam>> split_result = split_view(single_word, static_cast<TypeParam>(0));
EXPECT_EQ(split_result.size(), 1);
TYPED_TEST(SplitStringTest, random_long_trailing_two_delim) {
auto delim = make_delim_long<TypeParam>(8);
RandomTestData<TypeParam> data{ delim };
data.m_str += delim + delim;
data.m_tokens.emplace_back();
data.m_tokens.emplace_back();
std::vector<std::basic_string<TypeParam>> split_result = split(data.m_str, delim);
EXPECT_EQ(split_result, data.m_tokens);
}
TYPED_TEST(SplitViewStringTest, random) {
RandomTestData<TypeParam> data{ static_cast<TypeParam>(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<TypeParam>(8);
RandomTestData<TypeParam> 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<std::basic_string<TypeParam>> split_result = split(data.m_str, delim);
EXPECT_EQ(split_result, data.m_tokens);
}

103
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 <jessica.aj@outlook.com>
*/
/**
* Tests which exist solely test test compilation for split.hpp, because MSVC demands /bigobj, and I don't want to
*/
#include "split.hpp"
#include <deque>
#include <list>
#include "test_split.hpp"
using namespace jessilib;
using namespace std::literals;
using char_types = ::testing::Types<char, unsigned char, signed char, wchar_t, char8_t, char16_t, char32_t>;
template<typename T>
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<TypeParam> empty;
auto delim = default_delim<TypeParam>;
std::vector<decltype(empty)> 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<TypeParam> empty;
auto delim = make_delim_long<TypeParam>();
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<TypeParam> empty;
auto delim = default_delim<TypeParam>;
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<TypeParam> empty;
auto delim = make_delim_long<TypeParam>();
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<TypeParam> empty;
std::deque<std::basic_string<TypeParam>> split_result = split<std::deque>(empty, default_delim<TypeParam>);
EXPECT_TRUE(split_result.empty());
}
TYPED_TEST(SplitCompilationTest, empty_deque_long) {
std::basic_string_view<TypeParam> empty;
auto delim = make_delim_long<TypeParam>();
EXPECT_TRUE(split<std::deque>(empty, delim).empty());
EXPECT_TRUE(split_n<std::deque>(empty, delim, 5).empty());
EXPECT_TRUE(split_view<std::deque>(empty, delim).empty());
EXPECT_TRUE(split_n_view<std::deque>(empty, delim, 5).empty());
}
TYPED_TEST(SplitCompilationTest, empty_list) {
std::basic_string_view<TypeParam> empty;
std::list<std::basic_string<TypeParam>> split_result = split<std::list>(empty, default_delim<TypeParam>);
EXPECT_TRUE(split_result.empty());
}
TYPED_TEST(SplitCompilationTest, empty_list_long) {
std::basic_string_view<TypeParam> empty;
auto delim = make_delim_long<TypeParam>();
EXPECT_TRUE(split<std::list>(empty, delim).empty());
EXPECT_TRUE(split_n<std::list>(empty, delim, 5).empty());
EXPECT_TRUE(split_view<std::list>(empty, delim).empty());
EXPECT_TRUE(split_n_view<std::list>(empty, delim, 5).empty());
}

135
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 <jessica.aj@outlook.com>
*/
#pragma once
#include "split.hpp"
#include "test.hpp"
#include <random>
template<typename T>
constexpr T default_delim{};
template<typename T, typename ResultT = std::basic_string<T>>
ResultT make_word(size_t length = 8, T delim = default_delim<T>) {
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<typename T, typename ResultT = std::basic_string<T>>
ResultT make_delim_long(size_t length = 8, T in_delim = default_delim<T>) {
// 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<T>(length, in_delim);
result += in_delim;
return result;
}
template<typename T>
std::basic_string_view<T> make_word_view(size_t length = 8, T delim = default_delim<T>) {
static std::basic_string<T> s_result;
s_result = make_word(length, delim);
return s_result;
}
template<typename T, typename StringT = std::basic_string<T>>
struct RandomTestData {
RandomTestData(T in_delim = default_delim<T>, 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<unsigned int>(std::chrono::system_clock::now().time_since_epoch().count()));
std::uniform_int_distribution<uint32_t> word_count_distribution(5, 64);
std::uniform_int_distribution<uint32_t> 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<T, StringT>(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<StringT> m_tokens;
};
Loading…
Cancel
Save