/* @file String.test.cpp * * author: Roland Conybeare, Aug 2025 */ #include "xo/object/String.hpp" #include "xo/alloc/GC.hpp" #include "xo/alloc/ArenaAlloc.hpp" #include "xo/indentlog/scope.hpp" #include "xo/indentlog/print/quoted.hpp" #include #include #include namespace xo { using xo::gc::IAlloc; using xo::gc::GC; using xo::gc::ArenaAlloc; using xo::gc::generation; using xo::obj::String; namespace ut { namespace { struct Testcase_String { Testcase_String(std::size_t nz, std::size_t tz, const std::vector & v) : nursery_z_{nz}, tenured_z_{tz}, v_{v} {} std::size_t nursery_z_; std::size_t tenured_z_; std::vector v_; }; std::vector s_testcase_v = { Testcase_String(1024, 4096, {"hello"}), Testcase_String(1024, 4096, {"hello", ", world!"}) }; } TEST_CASE("String", "[String][gc]") { for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) { const Testcase_String & tc = s_testcase_v[i_tc]; up gc = GC::make( {.initial_nursery_z_ = tc.nursery_z_, .initial_tenured_z_ = tc.tenured_z_, .debug_flag_ = false}); REQUIRE(gc.get()); /* use gc for all Object allocs */ Object::mm = gc.get(); { std::size_t n_string = 0; std::size_t expected_alloc_z = 0; for (const std::string & s_str : tc.v_) { gp s1 = String::copy(s_str.c_str()); ++n_string; /* 1+ for mandatory null terminator */ expected_alloc_z += (IAlloc::with_padding(sizeof(String)) + IAlloc::with_padding(1 + s_str.length())); REQUIRE(gc->allocated() % sizeof(std::uintptr_t) == 0); REQUIRE(gc->allocated() == expected_alloc_z); REQUIRE(s1->length() == s_str.length()); REQUIRE(strcmp(s1->c_str(), s_str.c_str()) == 0); } /* gc has n_string objects. Nothing refers to them, so gc going to kill all */ gc->request_gc(generation::nursery); REQUIRE(gc->gc_in_progress() == false); REQUIRE(gc->native_gc_statistics().gen_v_[gen2int(generation::nursery)].n_gc_ == 1); REQUIRE(gc->native_gc_statistics().gen_v_[gen2int(generation::tenured)].n_gc_ == 0); REQUIRE(gc->allocated() == 0); REQUIRE(gc->native_gc_statistics().total_allocated_ == expected_alloc_z); } } } TEST_CASE("String2", "[String][gc]") { for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) { const Testcase_String & tc = s_testcase_v[i_tc]; up gc = GC::make( {.initial_nursery_z_ = tc.nursery_z_, .initial_tenured_z_ = tc.tenured_z_, .debug_flag_ = false}); REQUIRE(gc.get()); /* use gc for all Object allocs */ Object::mm = gc.get(); { scope log(XO_DEBUG(false)); std::size_t n_string = 0; std::size_t expected_alloc_z = 0; std::vector> sv(tc.v_.size()); std::size_t i = 0; for (const std::string & s_str : tc.v_) { sv[i] = String::copy(s_str.c_str()); ++n_string; /* 1+ for mandatory null terminator */ std::size_t alloc_z = (IAlloc::with_padding(sizeof(String)) + IAlloc::with_padding(1 + s_str.length())); expected_alloc_z += alloc_z; log && log(xtag("s_str", xo::print::unq(s_str)), xtag("s_str.length", s_str.length()), xtag("alloc_z", alloc_z)); log && log(xtag("expected_alloc_z", expected_alloc_z)); gc->add_gc_root(reinterpret_cast(sv[i].ptr_address())); REQUIRE(gc->allocated() % sizeof(std::uintptr_t) == 0); REQUIRE(gc->allocated() == expected_alloc_z); REQUIRE(sv[i]->length() == s_str.length()); REQUIRE(strcmp(sv[i]->c_str(), s_str.c_str()) == 0); ++i; } /* gc has a bunch of string objects; all are roots + should be preserved */ gc->request_gc(generation::nursery); REQUIRE(gc->native_gc_statistics().gen_v_[gen2int(generation::nursery)].n_gc_ == 1); REQUIRE(gc->native_gc_statistics().gen_v_[gen2int(generation::tenured)].n_gc_ == 0); REQUIRE(gc->allocated() == expected_alloc_z); for (std::size_t i = 0, n = sv.size(); i < n; ++i) { REQUIRE(gc->contains(reinterpret_cast(sv.at(i).ptr()))); REQUIRE(strcmp(sv.at(i)->c_str(), tc.v_[i].c_str()) == 0); } } } } TEST_CASE("String.append", "[String]") { const bool c_debug_flag = false; up arena = ArenaAlloc::make("testarena", 0, 16*1024, c_debug_flag); Object::mm = arena.get(); gp s1 = String::share("the"); gp s2 = String::share(" quick"); gp s3 = String::append(s1, s2); REQUIRE(::strcmp(s1->c_str(), "the") == 0); REQUIRE(::strcmp(s2->c_str(), " quick") == 0); REQUIRE(s3.ptr()); REQUIRE(s3->length() == s1->length() + s2->length()); REQUIRE(::strcmp(s1->c_str(), "the quick")); } } /*namespace ut*/ } /*namespace xo*/ /* end String.test.cpp */