#include "test_str.hpp" extern "C" { #include "guf_alloc_libc.h" } /* StrTest: */ void StrTest::run() { if (done) { return; } const std::vector words = { "", "\0", "Hello", "Othell\0o", "f\0\0", "\0", "0", "a", "ab", "🌈 waow a rainboge!", "orange cat(1) :3", "xes yag", "Hello, world! This is a pretty darn long string I'd say...", "I want to eat crayons. I crave crayons because they are tasty, and everybody telling me crayons are not edible must be either lying or dumb. I like trains. 42 is a number. 3.14159265... is not a rational number, and it is called pi. I ate some pie (it was a crayon pie).", std::string(32, 'a'), std::string(64, 'b'), std::string(1024, 'a'), std::string(2048, 'a'), std::string(4096, 'a'), std::string(5001, 'a'), std::string(7121, 'a'), std::string(2000, 'a'), std::string(GUF_STR_SSO_BUF_CAP, 'a'), std::string(GUF_STR_SSO_BUF_CAP - 1, 'a'), std::string(GUF_STR_SSO_BUF_CAP + 1, 'a'), std::string(GUF_STR_SSO_BUF_CAP - 2, 'a'), std::string(GUF_STR_SSO_BUF_CAP + 2, 'a'), std::string(GUF_STR_SSO_BUF_CAP - 3, 'a'), std::string(GUF_STR_SSO_BUF_CAP + 3, 'a'), std::string(GUF_STR_SSO_BUF_CAP * 2, 'a'), std::string(GUF_STR_SSO_BUF_CAP * 3, 'a'), std::string(GUF_STR_SSO_BUF_CAP * 4, 'a'), std::string(GUF_STR_SSO_BUF_CAP * 5, 'a'), std::string(GUF_STR_SSO_BUF_CAP * 6, 'a'), std::string(GUF_STR_SSO_BUF_CAP * 7, 'a'), }; push_check_name("init_empy"); test_init_empty(); pop_check_name(); push_check_name("append_char"); for (const auto& word : words) { test_init_free(word); test_append_char(word); test_append_char(word, true); } pop_check_name(); push_check_name("append_str"); for (size_t i = 0; i < words.size(); ++i) { const auto& w1 = words.at(i); append_str(w1, w1); append_str(w1, w1); for (size_t j = i + 1; j < words.size(); ++j) { const auto& w2 = words.at(j); append_str(w1, w2); append_str(w2, w1); } } pop_check_name(); push_check_name("test_popsplit"); std::vector split = test_popsplit("1997-04-01", "-"); if (TEST_CHECK(split.size() == 3)) { TEST_CHECK(split.at(0) == "1997" && split.at(1) == "04" && split.at(2) == "01"); } split = test_popsplit("1997-04-01-", "-"); if (TEST_CHECK(split.size() == 3)) { TEST_CHECK(split.at(0) == "1997" && split.at(1) == "04" && split.at(2) == "01"); } split = test_popsplit("2025/05/08", "/"); if (TEST_CHECK(split.size() == 3)) { TEST_CHECK(split.at(0) == "2025" && split.at(1) == "05" && split.at(2) == "08"); } split = test_popsplit("2025/05/08/", "/"); if (TEST_CHECK(split.size() == 3)) { TEST_CHECK(split.at(0) == "2025" && split.at(1) == "05" && split.at(2) == "08"); } split = test_popsplit("2025/05/08//", "/"); if (TEST_CHECK(split.size() == 4)) { TEST_CHECK(split.at(0) == "2025" && split.at(1) == "05" && split.at(2) == "08" && split.at(3) == ""); } split = test_popsplit("/2025/05/08", "/"); if (TEST_CHECK(split.size() == 4)) { TEST_CHECK(split.at(0) == "" && split.at(1) == "2025" && split.at(2) == "05" && split.at(3) == "08"); } split = test_popsplit("//2025/05/08", "/"); if (TEST_CHECK(split.size() == 5)) { TEST_CHECK(split.at(0) == "" && split.at(1) == "" && split.at(2) == "2025" && split.at(3) == "05" && split.at(4) == "08"); } split = test_popsplit("I eat formidable crayons, oof, for real", "foo"); if (TEST_CHECK(split.size() == 1)) { TEST_CHECK(split.at(0) == "I eat formidable crayons, oof, for real"); } split = test_popsplit("Hej <<", "<<"); if (TEST_CHECK(split.size() == 1)) { TEST_CHECK(split.at(0) == "Hej "); } split = test_popsplit("Hej << verden", "<<"); if (TEST_CHECK(split.size() == 2)) { TEST_CHECK(split.at(0) == "Hej " && split.at(1) == " verden"); } split = test_popsplit("<< Hej << verden", "<<"); if (TEST_CHECK(split.size() == 3)) { TEST_CHECK(split.at(0) == "" && split.at(1) == " Hej " && split.at(2) == " verden"); } split = test_popsplit("<< Hej << verden <<< foo<>", "<<"); if (TEST_CHECK(split.size() == 4)) { TEST_CHECK(split.at(0) == "" && split.at(1) == " Hej " && split.at(2) == " verden " && split.at(3) == "< foo<>"); } split = test_popsplit("I eat tofu", ""); if (TEST_CHECK(split.size() == 1)) { TEST_CHECK(split.at(0) == "I eat tofu"); } split = test_popsplit("At 3 a.m. during FULL-moon FULL-STOP Next to the public-library's -STOP sign FULL-STOP", "FULL-STOP"); if (TEST_CHECK(split.size() == 2)) { TEST_CHECK(split.at(0) == "At 3 a.m. during FULL-moon " && split.at(1) == " Next to the public-library's -STOP sign "); } split = test_popsplit("At 3 a.m. during FULL-moon FULL-STOP Next to the public-library's -STOP sign FULL-STOPI like trains, FULL-STO", "FULL-STOP"); if (TEST_CHECK(split.size() == 3)) { TEST_CHECK(split.at(0) == "At 3 a.m. during FULL-moon " && split.at(1) == " Next to the public-library's -STOP sign " && split.at(2) == "I like trains, FULL-STO"); } split = test_popsplit("At 3 a.m. during FULL-moon FULL-STOP Next to the public-library's -STOP sign FULL-STOPI like trains, FULL-STO Poo", "FULL-STOP"); if (TEST_CHECK(split.size() == 3)) { TEST_CHECK(split.at(0) == "At 3 a.m. during FULL-moon " && split.at(1) == " Next to the public-library's -STOP sign " && split.at(2) == "I like trains, FULL-STO Poo"); } pop_check_name(); push_check_name("get_toks"); std::vector tok_words = {"hello", "world", "cat", "vertex", "normal", "pizza", "running", "mouse", "playing", "adjacent"}; std::vector delims = {",", " ", "\n", "\t", "\r"}; for (int is_trailing = 0; is_trailing < 2; ++is_trailing) { for (ptrdiff_t num_words = 1; num_words < std::ssize(tok_words); ++num_words) { std::string str = ""; for (ptrdiff_t j = 0; j < num_words; ++j) { str += tok_words.at(j); if (j < num_words - 1 || is_trailing) { str += ", "; } } std::vector toks = get_toks(std::string_view{str}, delims, false, GUF_STR_TOK_DELIM_OPT_MATCH_LONGEST); if (TEST_CHECK(std::ssize(toks) == num_words)) { for (ptrdiff_t i = 0; i < num_words; ++i) { TEST_CHECK(toks.at(i) == tok_words.at(i)); } } } } std::string_view tok_str = "age: 28, occupation: NULL, crayons_eaten: 256 "; delims = {"", "", ":", ",", " ", "\t", "", "" && tok_result.at(0) == "" && tok_result.at(1) == "age" && tok_result.at(2) == ":" && tok_result.at(3) == " " && tok_result.at(4) == "28"); tok_result = get_toks(tok_str, delims, false, GUF_STR_TOK_DELIM_OPT_MATCH_LONGEST); TEST_CHECK(tok_result.size() == 6); TEST_CHECK(tok_result.at(0) == "age" && tok_result.at(1) == "28" && tok_result.at(2) == "occupation" && tok_result.at(3) == "NULL" && tok_result.at(4) == "crayons_eaten" && tok_result.at(5) == "256"); pop_check_name(); } void StrTest::test_init_free(std::string str) { guf_str s0; guf_str_init(&s0, GUF_CSTR_TO_VIEW_CPP(str.c_str()), &guf_allocator_libc); guf_str s1 = guf_str_new(GUF_CSTR_TO_VIEW_CPP(str.c_str()), &guf_allocator_libc); guf_str s2; guf_str_init_from_cstr(&s2, str.c_str(), &guf_allocator_libc); TEST_CHECK(guf_str_equal(&s0, &s1)); TEST_CHECK(guf_str_equal(&s0, &s2)); TEST_CHECK(guf_str_equal(&s1, &s2)); TEST_CHECK((ptrdiff_t)str.size() == guf_str_len(&s0)); TEST_CHECK(str == guf_str_const_cstr(&s0)); TEST_CHECK(str == guf_str_cstr(&s0)); TEST_CHECK((ptrdiff_t)str.size() == guf_str_len(&s1)); TEST_CHECK(str == guf_str_const_cstr(&s1)); TEST_CHECK(str == guf_str_cstr(&s1)); TEST_CHECK((ptrdiff_t)str.size() == guf_str_len(&s2)); TEST_CHECK(str == guf_str_const_cstr(&s2)); TEST_CHECK(str == guf_str_cstr(&s2)); guf_str_free(&s0, NULL); guf_str_free(&s1, NULL); guf_str_free(&s2, NULL); TEST_CHECK(guf_str_is_uninit(&s0)); TEST_CHECK(guf_str_is_uninit(&s1)); TEST_CHECK(guf_str_is_uninit(&s2)); } void StrTest::test_init_empty() { std::string str = ""; guf_str s = GUF_STR_UNINITIALISED_CPP; guf_str_init_empty(&s, &guf_allocator_libc); TEST_CHECK(guf_str_len(&s) == 0); TEST_CHECK(str == guf_str_const_cstr(&s)); guf_str_append_char(&s, 'a', 1024); str.append(1024, 'a'); TEST_CHECK(guf_str_len(&s) == (ptrdiff_t)str.size()); TEST_CHECK(guf_str_const_cstr(&s) == str); guf_str_append_char(&s, 'b', 24); str.append(24, 'b'); TEST_CHECK(guf_str_len(&s) == (ptrdiff_t)str.size()); TEST_CHECK(guf_str_const_cstr(&s) == str); guf_str_append_char(&s, 'c', 255); str.append(255, 'c'); TEST_CHECK(guf_str_len(&s) == (ptrdiff_t)str.size()); TEST_CHECK(guf_str_const_cstr(&s) == str); *guf_str_at(&s, 0) = '<'; str.at(0) = '<'; TEST_CHECK(guf_str_len(&s) == (ptrdiff_t)str.size()); TEST_CHECK(guf_str_const_cstr(&s) == str); *guf_str_at(&s, guf_str_len(&s) - 1) = '>'; str.at(str.size() - 1) = '>'; TEST_CHECK(guf_str_len(&s) == (ptrdiff_t)str.size()); TEST_CHECK(guf_str_const_cstr(&s) == str); guf_err err = GUF_ERR_NONE; TEST_CHECK(NULL == guf_str_try_at(&s, guf_str_len(&s), &err)); TEST_CHECK(err != GUF_ERR_NONE && err == GUF_ERR_IDX_RANGE); err = GUF_ERR_NONE; TEST_CHECK(NULL == guf_str_try_at(&s, -1, &err)); TEST_CHECK(err != GUF_ERR_NONE && err == GUF_ERR_IDX_RANGE); guf_str_free(&s, NULL); TEST_CHECK(guf_str_is_uninit(&s)); } void StrTest::test_append_char(std::string str, bool include_null) { guf_str s0 = guf_str_new(guf_str_view{.str = str.c_str(), .len = (ptrdiff_t)str.size()}, &guf_allocator_libc); TEST_CHECK((ptrdiff_t)str.size() == guf_str_len(&s0)); TEST_CHECK((str == std::string_view{guf_str_const_cstr(&s0), (size_t)guf_str_len(&s0)})); for (int i = include_null ? 0 : 1; i < 128; ++i) { char ch = (char)i; guf_str_append_one_char(&s0, ch); str.append(1, ch); TEST_CHECK(guf_str_len(&s0) == (ptrdiff_t)str.size()); TEST_CHECK((str == std::string_view{guf_str_const_cstr(&s0), (size_t)guf_str_len(&s0)})); } for (int i = include_null ? 0 : 1; i < 128; ++i) { char ch = (char)i; guf_str_append_char(&s0, ch, i); str.append(i, ch); TEST_CHECK(guf_str_len(&s0) == (ptrdiff_t)str.size()); TEST_CHECK((str == std::string_view{guf_str_const_cstr(&s0), (size_t)guf_str_len(&s0)})); guf_str_append_char(&s0, ch, i * 16); str.append(i * 16, ch); TEST_CHECK(guf_str_len(&s0) == (ptrdiff_t)str.size()); TEST_CHECK((str == std::string_view{guf_str_const_cstr(&s0), (size_t)guf_str_len(&s0)})); } guf_str_free(&s0, NULL); TEST_CHECK(guf_str_is_uninit(&s0)); } void StrTest::append_str(const std::string& a, const std::string& b) { std::string str0 = a; guf_str s0 = guf_str_new(guf_str_view{.str = str0.c_str(), .len = (ptrdiff_t)str0.size()}, &guf_allocator_libc); TEST_CHECK(guf_str_len(&s0) == (ptrdiff_t)str0.size()); TEST_CHECK((str0 == std::string_view{guf_str_const_cstr(&s0), (size_t)guf_str_len(&s0)})); TEST_CHECK((str0 == std::string_view{guf_str_cstr(&s0), (size_t)guf_str_len(&s0)})); for (int i = 0; i <= 64; ++i) { str0.append(b); guf_str_append(&s0, guf_str_view{.str = b.c_str(), .len = (ptrdiff_t)b.size()}); TEST_CHECK(guf_str_len(&s0) == (ptrdiff_t)str0.size()); TEST_CHECK((str0 == std::string_view{guf_str_const_cstr(&s0), (size_t)guf_str_len(&s0)})); TEST_CHECK((str0 == std::string_view{guf_str_cstr(&s0), (size_t)guf_str_len(&s0)})); } guf_str_free(&s0, NULL); TEST_CHECK(guf_str_is_uninit(&s0)); } std::vector StrTest::test_popsplit(std::string_view str, std::string_view delim) { std::vector result = {}; if (delim.size() > 0) { // NOTE: str.find with an empty delimiter returns 0, not std::string::npos std::string_view src_cpp = str; for (size_t idx = src_cpp.find(delim, 0); src_cpp.size() > 0; idx = src_cpp.find(delim, 0)) { result.push_back(src_cpp.substr(0, idx)); if (idx == std::string::npos) { break; } src_cpp = src_cpp.substr(idx + delim.size()); } } else { result.push_back(str); } const guf_str_view delim_sv = guf_str_view{.len = (ptrdiff_t)delim.size(), .str = delim.data()}; guf_str_view src = guf_str_view{.len = (ptrdiff_t)str.size(), .str = str.data()}; size_t n = 0; do { const guf_str_view popped = guf_str_view_pop_split(&src, delim_sv); TEST_CHECK(n < result.size()); TEST_CHECK(std::string_view(popped.str, (size_t)popped.len) == result.at(n)); const guf_str_view res = {.str = result.at(n).data(), .len = (ptrdiff_t)result.at(n).size()}; TEST_CHECK(guf_str_view_equal(&popped, &res)); TEST_CHECK(guf_str_view_equal_val_arg(popped, res)); // std::cout << "guf: " << std::string_view{popped.str, (size_t)popped.len} << "\n"; // std::cout << "cpp: " << std::string_view{res.str, (size_t)res.len} << "\n"; ++n; } while (src.len > 0); TEST_CHECK(n == result.size()); return result; } std::vector StrTest::get_toks(std::string_view sv_in, const std::vector& delims_in, bool preserve_delims, guf_str_tok_delim_opt opt) { const guf_str_view sv = guf_str_view{.len = (ptrdiff_t)sv_in.size(), .str = sv_in.data()}; std::vector delims; for (const auto delim : delims_in) { delims.push_back(guf_str_view{.len = (ptrdiff_t)delim.size(), .str = delim.data()}); } guf_str_tok_state tok_state = guf_str_tok_state_new(sv, delims.data(), std::ssize(delims), opt); std::vector toks_out; while (guf_str_tok_next(&tok_state, preserve_delims)) { if (tok_state.cur_tok.len > 0) { toks_out.push_back( std::string_view{tok_state.cur_tok.str, (size_t)tok_state.cur_tok.len}); } if (preserve_delims && tok_state.cur_delim.len > 0) { toks_out.push_back( std::string_view{tok_state.cur_delim.str, (size_t)tok_state.cur_delim.len}); } } TEST_CHECK(tok_state.done); const ptrdiff_t num_toks = preserve_delims ? tok_state.num_delims_read + tok_state.num_toks_read : tok_state.num_toks_read; TEST_CHECK(num_toks == std::ssize(toks_out)); return toks_out; }