test: add testbench for string module

This commit is contained in:
yyc12345 2025-06-22 17:14:49 +08:00
parent df3b602110
commit ab8d74efe6
8 changed files with 211 additions and 97 deletions

View File

@ -71,7 +71,7 @@ You can simply return \c false to terminate join process.
The argument you assigned to argument will not be taken into join process when you return false.
Then, you can pass the created #JoinDataProvider object to #Join function.
And specify decilmer at the same time.
And specify delimiter at the same time.
Then you can get the final joined string.
There is an example:
@ -88,7 +88,7 @@ auto joined_string = YYCC::StringHelper::Join(
++iter;
return true;
},
decilmer
delimiter
);
\endcode
@ -105,7 +105,7 @@ Otherwise this overload will throw template error.
std::vector<yycc_u8string> data {
YYCC_U8(""), YYCC_U8("1"), YYCC_U8("2"), YYCC_U8("")
};
auto joined_string = YYCC::StringHelper::Join(data.begin(), data.end(), decilmer);
auto joined_string = YYCC::StringHelper::Join(data.begin(), data.end(), delimiter);
\endcode
\section string_helper__lower_upper Lower Upper
@ -134,14 +134,14 @@ std::vector<yycc_u8string_view> SplitView(const yycc_u8string_view&, const yycc_
\endcode
All these overloads take a string view as the first argument representing the string need to be split.
The second argument is a string view representing the decilmer for splitting.
The second argument is a string view representing the delimiter for splitting.
The only difference between these 2 split function are overt according to their names.
The first split function will return a list of copied string as its split result.
The second split function will return a list of string view as its split result,
and it will keep valid as long as the life time of your given string view argument.
It also means that the last overload will cost less memory if you don't need the copy of original string.
If the source string (the string need to be split) is empty, or the decilmer is empty,
If the source string (the string need to be split) is empty, or the delimiter is empty,
the result will only has 1 item and this item is source string itself.
There is no way that these methods return an empty list, except the code is buggy.

View File

@ -110,18 +110,18 @@ namespace YYCC::StringHelper {
#pragma region Join
yycc_u8string Join(JoinDataProvider fct_data, const yycc_u8string_view& decilmer) {
yycc_u8string Join(JoinDataProvider fct_data, const yycc_u8string_view& delimiter) {
yycc_u8string ret;
bool is_first = true;
yycc_u8string_view element;
// fetch element
while (fct_data(element)) {
// insert decilmer
// insert delimiter
if (is_first) is_first = false;
else {
// append decilmer.
ret.append(decilmer);
// append delimiter.
ret.append(delimiter);
}
// insert element if it is not empty
@ -175,9 +175,9 @@ namespace YYCC::StringHelper {
#pragma region Split
std::vector<yycc_u8string> Split(const yycc_u8string_view& strl, const yycc_u8string_view& _decilmer) {
std::vector<yycc_u8string> Split(const yycc_u8string_view& strl, const yycc_u8string_view& _delimiter) {
// call split view
auto view_result = SplitView(strl, _decilmer);
auto view_result = SplitView(strl, _delimiter);
// copy string view result to string
std::vector<yycc_u8string> elems;
@ -189,7 +189,7 @@ namespace YYCC::StringHelper {
return elems;
}
std::vector<yycc_u8string_view> SplitView(const yycc_u8string_view& strl, const yycc_u8string_view& _decilmer) {
std::vector<yycc_u8string_view> SplitView(const yycc_u8string_view& strl, const yycc_u8string_view& _delimiter) {
// Reference:
// https://stackoverflow.com/questions/14265581/parse-split-a-string-in-c-using-string-delimiter-standard-c
@ -197,18 +197,18 @@ namespace YYCC::StringHelper {
std::vector<yycc_u8string_view> elems;
// if string need to be splitted is empty, return original string (empty string).
// if decilmer is empty, return original string.
yycc_u8string decilmer(_decilmer);
if (strl.empty() || decilmer.empty()) {
// if delimiter is empty, return original string.
yycc_u8string delimiter(_delimiter);
if (strl.empty() || delimiter.empty()) {
elems.emplace_back(strl);
return elems;
}
// start spliting
std::size_t previous = 0, current;
while ((current = strl.find(decilmer.c_str(), previous)) != yycc_u8string::npos) {
while ((current = strl.find(delimiter.c_str(), previous)) != yycc_u8string::npos) {
elems.emplace_back(strl.substr(previous, current - previous));
previous = current + decilmer.size();
previous = current + delimiter.size();
}
// try insert last part but prevent possible out of range exception
if (previous <= strl.size()) {

View File

@ -82,10 +82,10 @@ namespace YYCC::StringHelper {
* You can use this universal join function for any custom container by
* using C++ lambda syntax to create a code block adapted to this function pointer.
* @param[in] fct_data The function pointer in JoinDataProvider type prividing the data to be joined.
* @param[in] decilmer The decilmer used for joining.
* @param[in] delimiter The delimiter used for joining.
* @return The result string of joining.
*/
yycc_u8string Join(JoinDataProvider fct_data, const yycc_u8string_view& decilmer);
yycc_u8string Join(JoinDataProvider fct_data, const yycc_u8string_view& delimiter);
/**
* @brief Specialized join function for standard library container.
* @tparam InputIt
@ -93,11 +93,11 @@ namespace YYCC::StringHelper {
* It also can be dereferenced and then implicitly converted to yycc_u8string_view.
* @param[in] first The beginning of the range of elements to join.
* @param[in] last The terminal of the range of elements to join (exclusive).
* @param[in] decilmer The decilmer used for joining.
* @param[in] delimiter The delimiter used for joining.
* @return The result string of joining.
*/
template<class InputIt>
yycc_u8string Join(InputIt first, InputIt last, const yycc_u8string_view& decilmer) {
yycc_u8string Join(InputIt first, InputIt last, const yycc_u8string_view& delimiter) {
return Join([&first, &last](yycc_u8string_view& view) -> bool {
// if we reach tail, return false to stop join process
if (first == last) return false;
@ -105,7 +105,7 @@ namespace YYCC::StringHelper {
view = *first;
++first;
return true;
}, decilmer);
}, delimiter);
}
/**
@ -132,28 +132,28 @@ namespace YYCC::StringHelper {
yycc_u8string Upper(const yycc_u8string_view& strl);
/**
* @brief Split given string with specified decilmer.
* @brief Split given string with specified delimiter.
* @param[in] strl The string need to be splitting.
* @param[in] _decilmer The decilmer for splitting.
* @param[in] _delimiter The delimiter for splitting.
* @return
* The split result.
* \par
* If given string or decilmer are empty,
* If given string or delimiter are empty,
* the result container will only contain 1 entry which is equal to given string.
*/
std::vector<yycc_u8string> Split(const yycc_u8string_view& strl, const yycc_u8string_view& _decilmer);
std::vector<yycc_u8string> Split(const yycc_u8string_view& strl, const yycc_u8string_view& _delimiter);
/**
* @brief Split given string with specified decilmer as string view.
* @brief Split given string with specified delimiter as string view.
* @param[in] strl The string need to be splitting.
* @param[in] _decilmer The decilmer for splitting.
* @param[in] _delimiter The delimiter for splitting.
* @return
* The split result with string view format.
* This will not produce any copy of original string.
* \par
* If given string or decilmer are empty,
* If given string or delimiter are empty,
* the result container will only contain 1 entry which is equal to given string.
* @see Split(const yycc_u8string_view&, const yycc_char8_t*)
*/
std::vector<yycc_u8string_view> SplitView(const yycc_u8string_view& strl, const yycc_u8string_view& _decilmer);
std::vector<yycc_u8string_view> SplitView(const yycc_u8string_view& strl, const yycc_u8string_view& _delimiter);
}

View File

@ -114,18 +114,18 @@ namespace yycc::string::op {
#pragma region Join
NS_YYCC_STRING::u8string join(JoinDataProvider fct_data, const NS_YYCC_STRING::u8string_view& decilmer) {
NS_YYCC_STRING::u8string join(JoinDataProvider fct_data, const NS_YYCC_STRING::u8string_view& delimiter) {
NS_YYCC_STRING::u8string ret;
bool is_first = true;
NS_YYCC_STRING::u8string_view element;
// fetch element
while (fct_data(element)) {
// insert decilmer
// insert delimiter
if (is_first) is_first = false;
else {
// append decilmer.
ret.append(decilmer);
// append delimiter.
ret.append(delimiter);
}
// insert element if it is not empty
@ -179,9 +179,37 @@ namespace yycc::string::op {
#pragma region Split
std::vector<NS_YYCC_STRING::u8string> split(const NS_YYCC_STRING::u8string_view& strl, const NS_YYCC_STRING::u8string_view& _decilmer) {
std::vector<NS_YYCC_STRING::u8string_view> split(const NS_YYCC_STRING::u8string_view& strl, const NS_YYCC_STRING::u8string_view& _delimiter) {
// Reference:
// https://stackoverflow.com/questions/14265581/parse-split-a-string-in-c-using-string-delimiter-standard-c
// prepare return value
std::vector<NS_YYCC_STRING::u8string_view> elems;
// if string need to be splitted is empty, return original string (empty string).
// if delimiter is empty, return original string.
NS_YYCC_STRING::u8string delimiter(_delimiter);
if (strl.empty() || delimiter.empty()) {
elems.emplace_back(strl);
return elems;
}
// start spliting
std::size_t previous = 0, current;
while ((current = strl.find(delimiter.c_str(), previous)) != NS_YYCC_STRING::u8string::npos) {
elems.emplace_back(strl.substr(previous, current - previous));
previous = current + delimiter.size();
}
// try insert last part but prevent possible out of range exception
if (previous <= strl.size()) {
elems.emplace_back(strl.substr(previous));
}
return elems;
}
std::vector<NS_YYCC_STRING::u8string> split_owned(const NS_YYCC_STRING::u8string_view& strl, const NS_YYCC_STRING::u8string_view& _delimiter) {
// call split view
auto view_result = split_view(strl, _decilmer);
auto view_result = split(strl, _delimiter);
// copy string view result to string
std::vector<NS_YYCC_STRING::u8string> elems;
@ -193,34 +221,6 @@ namespace yycc::string::op {
return elems;
}
std::vector<NS_YYCC_STRING::u8string_view> split_view(const NS_YYCC_STRING::u8string_view& strl, const NS_YYCC_STRING::u8string_view& _decilmer) {
// Reference:
// https://stackoverflow.com/questions/14265581/parse-split-a-string-in-c-using-string-delimiter-standard-c
// prepare return value
std::vector<NS_YYCC_STRING::u8string_view> elems;
// if string need to be splitted is empty, return original string (empty string).
// if decilmer is empty, return original string.
NS_YYCC_STRING::u8string decilmer(_decilmer);
if (strl.empty() || decilmer.empty()) {
elems.emplace_back(strl);
return elems;
}
// start spliting
std::size_t previous = 0, current;
while ((current = strl.find(decilmer.c_str(), previous)) != NS_YYCC_STRING::u8string::npos) {
elems.emplace_back(strl.substr(previous, current - previous));
previous = current + decilmer.size();
}
// try insert last part but prevent possible out of range exception
if (previous <= strl.size()) {
elems.emplace_back(strl.substr(previous));
}
return elems;
}
#pragma endregion
}

View File

@ -77,10 +77,10 @@ namespace yycc::string::op {
* You can use this universal join function for any custom container by
* using C++ lambda syntax to create a code block adapted to this function pointer.
* @param[in] fct_data The function pointer in JoinDataProvider type prividing the data to be joined.
* @param[in] decilmer The decilmer used for joining.
* @param[in] delimiter The delimiter used for joining.
* @return The result string of joining.
*/
NS_YYCC_STRING::u8string join(JoinDataProvider fct_data, const NS_YYCC_STRING::u8string_view& decilmer);
NS_YYCC_STRING::u8string join(JoinDataProvider fct_data, const NS_YYCC_STRING::u8string_view& delimiter);
/**
* @brief Specialized join function for standard library container.
* @tparam InputIt
@ -88,11 +88,11 @@ namespace yycc::string::op {
* It also can be dereferenced and then implicitly converted to NS_YYCC_STRING::u8string_view.
* @param[in] first The beginning of the range of elements to join.
* @param[in] last The terminal of the range of elements to join (exclusive).
* @param[in] decilmer The decilmer used for joining.
* @param[in] delimiter The delimiter used for joining.
* @return The result string of joining.
*/
template<class InputIt>
NS_YYCC_STRING::u8string join(InputIt first, InputIt last, const NS_YYCC_STRING::u8string_view& decilmer) {
NS_YYCC_STRING::u8string join(InputIt first, InputIt last, const NS_YYCC_STRING::u8string_view& delimiter) {
return join([&first, &last](NS_YYCC_STRING::u8string_view& view) -> bool {
// if we reach tail, return false to stop join process
if (first == last) return false;
@ -100,7 +100,7 @@ namespace yycc::string::op {
view = *first;
++first;
return true;
}, decilmer);
}, delimiter);
}
/**
@ -127,29 +127,30 @@ namespace yycc::string::op {
NS_YYCC_STRING::u8string upper(const NS_YYCC_STRING::u8string_view& strl);
/**
* @brief Split given string with specified decilmer.
* @brief Split given string with specified delimiter as string view.
* @param[in] strl The string need to be splitting.
* @param[in] _decilmer The decilmer for splitting.
* @return
* The split result.
* \par
* If given string or decilmer are empty,
* the result container will only contain 1 entry which is equal to given string.
*/
std::vector<NS_YYCC_STRING::u8string> split(const NS_YYCC_STRING::u8string_view& strl, const NS_YYCC_STRING::u8string_view& _decilmer);
/**
* @brief Split given string with specified decilmer as string view.
* @param[in] strl The string need to be splitting.
* @param[in] _decilmer The decilmer for splitting.
* @param[in] _delimiter The delimiter for splitting.
* @return
* The split result with string view format.
* This will not produce any copy of original string.
* \par
* If given string or decilmer are empty,
* If given string or delimiter are empty,
* the result container will only contain 1 entry which is equal to given string.
* @see Split(const NS_YYCC_STRING::u8string_view&, const NS_YYCC_STRING::u8char*)
*/
std::vector<NS_YYCC_STRING::u8string_view> split_view(const NS_YYCC_STRING::u8string_view& strl, const NS_YYCC_STRING::u8string_view& _decilmer);
std::vector<NS_YYCC_STRING::u8string_view> split(const NS_YYCC_STRING::u8string_view& strl, const NS_YYCC_STRING::u8string_view& _delimiter);
/**
* @brief Split given string with specified delimiter.
* @param[in] strl The string need to be splitting.
* @param[in] _delimiter The delimiter for splitting.
* @return
* The split result.
* \par
* If given string or delimiter are empty,
* the result container will only contain 1 entry which is equal to given string.
*/
std::vector<NS_YYCC_STRING::u8string> split_owned(const NS_YYCC_STRING::u8string_view& strl, const NS_YYCC_STRING::u8string_view& _delimiter);
// undefined lazy_split(const NS_YYCC_STRING::u8string_view& strl, const NS_YYCC_STRING::u8string_view& _delimiter);
}

View File

@ -224,10 +224,10 @@ namespace YYCCTestbench {
Assert(test_split[1] == YYCC_U8("1"), YYCC_U8("YYCC::StringHelper::Split"));
Assert(test_split[2] == YYCC_U8("2"), YYCC_U8("YYCC::StringHelper::Split"));
Assert(test_split[3] == YYCC_U8(""), YYCC_U8("YYCC::StringHelper::Split"));
test_split = YYCC::StringHelper::Split(YYCC_U8("test"), YYCC_U8("-")); // no matched decilmer
test_split = YYCC::StringHelper::Split(YYCC_U8("test"), YYCC_U8("-")); // no matched delimiter
Assert(test_split.size() == 1u, YYCC_U8("YYCC::StringHelper::Split"));
Assert(test_split[0] == YYCC_U8("test"), YYCC_U8("YYCC::StringHelper::Split"));
test_split = YYCC::StringHelper::Split(YYCC_U8("test"), YYCC::yycc_u8string_view()); // empty decilmer
test_split = YYCC::StringHelper::Split(YYCC_U8("test"), YYCC::yycc_u8string_view()); // empty delimiter
Assert(test_split.size() == 1u, YYCC_U8("YYCC::StringHelper::Split"));
Assert(test_split[0] == YYCC_U8("test"), YYCC_U8("YYCC::StringHelper::Split"));
test_split = YYCC::StringHelper::Split(YYCC::yycc_u8string_view(), YYCC_U8("")); // empty source string
@ -413,12 +413,12 @@ namespace YYCCTestbench {
YYCC::yycc_u8string test_slashed_path(YYCC::StdPatch::ToUTF8Path(test_path));
#if YYCC_OS == YYCC_OS_WINDOWS
std::wstring wdecilmer(1u, std::filesystem::path::preferred_separator);
YYCC::yycc_u8string decilmer(YYCC::EncodingHelper::WcharToUTF8(wdecilmer));
std::wstring wdelimiter(1u, std::filesystem::path::preferred_separator);
YYCC::yycc_u8string delimiter(YYCC::EncodingHelper::WcharToUTF8(wdelimiter));
#else
YYCC::yycc_u8string decilmer(1u, std::filesystem::path::preferred_separator);
YYCC::yycc_u8string delimiter(1u, std::filesystem::path::preferred_separator);
#endif
YYCC::yycc_u8string test_joined_path(YYCC::StringHelper::Join(c_UTF8TestStrTable.begin(), c_UTF8TestStrTable.end(), decilmer));
YYCC::yycc_u8string test_joined_path(YYCC::StringHelper::Join(c_UTF8TestStrTable.begin(), c_UTF8TestStrTable.end(), delimiter));
Assert(test_slashed_path == test_joined_path, YYCC_U8("YYCC::StdPatch::ToStdPath, YYCC::StdPatch::ToUTF8Path"));

View File

@ -3,6 +3,8 @@
#include <yycc/string/op.hpp>
#include <yycc/string/reinterpret.hpp>
#include <yycc/prelude/core.hpp>
#define OP ::yycc::string::op
namespace yycctest::string::op {
@ -13,7 +15,36 @@ namespace yycctest::string::op {
}
TEST(StringOp, Replace) {
// Normal case
{
auto rv = OP::replace(YYCC_U8("aabbcc"), YYCC_U8("bb"), YYCC_U8("dd"));
EXPECT_EQ(rv, YYCC_U8("aaddcc"));
}
// No matched expected string
{
auto rv = OP::replace(YYCC_U8("aabbcc"), YYCC_U8("zz"), YYCC_U8("yy"));
EXPECT_EQ(rv, YYCC_U8("aabbcc"));
}
// Empty expected string
{
auto rv = OP::replace(YYCC_U8("aabbcc"), u8string_view(), YYCC_U8("zz"));
EXPECT_EQ(rv, YYCC_U8("aabbcc"));
}
// Empty replace string
{
auto rv = OP::replace(YYCC_U8("aaaabbaa"), YYCC_U8("aa"), YYCC_U8(""));
EXPECT_EQ(rv, YYCC_U8("bb"));
}
// Nested replacing
{
auto rv = OP::replace(YYCC_U8("aaxcc"), YYCC_U8("x"), YYCC_U8("yx"));
EXPECT_EQ(rv, YYCC_U8("aayxcc"));
}
// Empty source string
{
auto rv = OP::replace(u8string_view(), YYCC_U8(""), YYCC_U8("xy"));
EXPECT_EQ(rv, YYCC_U8(""));
}
}
TEST(StringOp, Lower) {
@ -27,11 +58,39 @@ namespace yycctest::string::op {
}
TEST(StringOp, Join) {
std::vector<u8string> datas{YYCC_U8(""), YYCC_U8("1"), YYCC_U8("2"), YYCC_U8("")};
auto rv = OP::join(datas.begin(), datas.end(), YYCC_U8(", "));
EXPECT_EQ(rv, YYCC_U8(", 1, 2, "));
}
TEST(StringOp, Split) {
// Normal
{
auto rv = OP::split(YYCC_U8(", 1, 2, "), YYCC_U8(", "));
ASSERT_EQ(rv.size(), 4u);
EXPECT_EQ(rv[0], YYCC_U8(""));
EXPECT_EQ(rv[1], YYCC_U8("1"));
EXPECT_EQ(rv[2], YYCC_U8("2"));
EXPECT_EQ(rv[3], YYCC_U8(""));
}
// No matched delimiter
{
auto rv = OP::split(YYCC_U8("test"), YYCC_U8("-"));
ASSERT_EQ(rv.size(), 1u);
EXPECT_EQ(rv[0], YYCC_U8("test"));
}
// Empty delimiter
{
auto rv = OP::split(YYCC_U8("test"), u8string_view());
ASSERT_EQ(rv.size(), 1u);
EXPECT_EQ(rv[0], YYCC_U8("test"));
}
// Empty source string
{
auto rv = OP::split(u8string_view(), YYCC_U8(""));
ASSERT_EQ(rv.size(), 1u);
EXPECT_TRUE(rv[0].empty());
}
}
}
} // namespace yycctest::string::op

View File

@ -1,6 +1,60 @@
#include <cstring>
#include <gtest/gtest.h>
#include <yycc.hpp>
#include <yycc/string/reinterpret.hpp>
#include <yycc/prelude/core.hpp>
#define REINTERPRET ::yycc::string::reinterpret
#define CONST_VOID_PTR(p) reinterpret_cast<const void*>(p)
#define VOID_PTR(p) reinterpret_cast<void*>(p)
namespace yycctest::string::reinterpret {
static u8string PROBE(YYCC_U8("Test"));
TEST(StringReinterpret, ConstPointer) {
const auto* src = PROBE.data();
const auto* dst = REINTERPRET::as_ordinary(src);
const auto* new_src = REINTERPRET::as_utf8(dst);
// Pointer should point to the same address after casting.
EXPECT_EQ(CONST_VOID_PTR(src), CONST_VOID_PTR(dst));
EXPECT_EQ(CONST_VOID_PTR(src), CONST_VOID_PTR(new_src));
}
TEST(StringReinterpret, Pointer) {
auto* src = PROBE.data();
auto* dst = REINTERPRET::as_ordinary(src);
auto* new_src = REINTERPRET::as_utf8(dst);
// Pointer should point to the same address after casting.
EXPECT_EQ(VOID_PTR(src), VOID_PTR(dst));
EXPECT_EQ(VOID_PTR(src), VOID_PTR(new_src));
}
TEST(StringReinterpret, String) {
auto src = u8string(PROBE);
auto dst = REINTERPRET::as_ordinary(src);
auto new_src = REINTERPRET::as_utf8(dst);
// Check memory length and data.
ASSERT_EQ(src.length(), dst.length());
EXPECT_TRUE(std::memcmp(src.data(), dst.data(), src.length()) == 0);
ASSERT_EQ(src.length(), new_src.length());
EXPECT_TRUE(std::memcmp(src.data(), new_src.data(), src.length()) == 0);
}
TEST(StringReinterpret, StringView) {
auto src = u8string_view(PROBE);
auto dst = REINTERPRET::as_ordinary_view(src);
auto new_src = REINTERPRET::as_utf8_view(dst);
// Check memory length and data.
ASSERT_EQ(src.length(), dst.length());
EXPECT_TRUE(std::memcmp(src.data(), dst.data(), src.length()) == 0);
ASSERT_EQ(src.length(), new_src.length());
EXPECT_TRUE(std::memcmp(src.data(), new_src.data(), src.length()) == 0);
}
} // namespace yycctest::string::reinterpret