xo-umbrella2/xo-procedure2/utest/printable_render.test.cpp

211 lines
8.7 KiB
C++

/* @file printable_render.test.cpp
*
* author: Roland Conybeare, Aug 2026
*
* Phase C verification for xo-procedure2's printer: Primitive<Fn>, i.e. the
* DPrimitive_gco_* family.
*
* Follows the template in xo-object2/utest/printable_render.test.cpp -- see
* .xo-backlog/xo-printable2/issues/01-aprintable-pretty-ppsink.md for why both
* renderings are pinned rather than only asserted equal.
*
* Primitive is the first converted STRUCT whose field values are wide enough
* to leave the line, so it is the first place two things become visible:
*
* - a broken field's value indents by tag_value_offset (1) under ppsink and
* by a full indent_width (2) under legacy, so the value sits one column
* left of where legacy put it;
* - the :td field is a TypeDescr, whose LEGACY rendering already goes through
* ppsink -- TypeDescrBase::display(ostream) streams xo::pp::xtag through a
* FlatSink -- so it cannot break at all, at any margin, while the ppsink
* rendering folds it.
*
* Expectations are OBSERVED, never predicted.
*/
#include <xo/procedure2/ObjectPrimitives.hpp>
#include <xo/procedure2/detail/IPrintable_DPrimitive_gco_0.hpp>
#include <xo/procedure2/init_procedure2.hpp>
#include <xo/stringtable2/StringTable.hpp>
#include <xo/alloc2/arena/IAllocator_DArena.hpp>
#include <xo/printable2/Printable.hpp>
#include <xo/indentlog2/print/toppstr.hpp>
#include <xo/testutil/UtestRehearser.hpp>
#include <xo/ppsink/PpStyle.hpp>
#include <xo/ppsink/scope.hpp>
#include <xo/ppsink/scope_macros.hpp>
#include <catch2/catch.hpp>
#include <cstdint>
#include <string>
#include <vector>
namespace xo {
using xo::scm::ObjectPrimitives;
using xo::scm::DPrimitive_gco_0;
using xo::scm::StringTable;
using xo::print::APrintable;
using xo::mm::AAllocator;
using xo::mm::DArena;
using xo::mm::ArenaConfig;
using xo::facet::with_facet;
using xo::facet::obj;
namespace ut {
using xo::pp::PpConfig;
using xo::pp::toppstr;
using xo::pp::scope;
using xo::pp::xtag;
namespace {
/** render @p x through pretty(PpSink&) **/
template <typename T>
std::string
render_pretty(const T & x, std::uint32_t margin) {
return toppstr(PpConfig::scratch_plain(margin), x);
}
/** replace the digits of ":id <n>" with "N".
*
* TypeId is a process-wide counter handed out in reflection order,
* so the :id inside :td depends on how many OTHER types this
* binary happens to reflect first -- adding an unrelated test file
* would change it. It is xo-reflect's field, pinned by
* xo-reflect's own tests; scrubbing it here keeps every other
* column of the layout pinned exactly.
**/
std::string scrub_type_id(std::string s) {
const std::string key = ":id ";
for (std::size_t i = s.find(key); i != std::string::npos; i = s.find(key, i+1)) {
std::size_t b = i + key.size();
std::size_t e = b;
while (e < s.size() && ::isdigit((unsigned char)s[e]))
++e;
if (e > b)
s.replace(b, e - b, "N");
}
return s;
}
/** the primitive is fixed; MARGIN is the case variable **/
struct Testcase_Primitive {
Testcase_Primitive(std::uint32_t margin,
const char * expect_pretty)
: margin_{margin},
expect_pretty_{expect_pretty} {}
std::uint32_t margin_;
/** OBSERVED via pretty; outlives phase E **/
std::string expect_pretty_;
};
/** ObjectPrimitives::make_cwd_pm: name "cwd", Fn a plain function
* pointer. The :canonical_name of that function type is 78
* columns wide all by itself, which is what makes the margins
* below interesting.
*
* :fn 1 in BOTH renderings is not a typo and not a divergence: a
* function pointer has no operator<<, so it converts to bool.
* Legacy printed 1 too. Recorded here so the next reader does not
* "fix" it as ppsink damage.
**/
static std::vector<Testcase_Primitive> s_primitive_v = {
/* fits: identical, character for character */
Testcase_Primitive(200,
"<Primitive<Fn> :name cwd"
" :td <TypeDescr :id N :canonical_name"
" xo::facet::obj<xo::mm::AGCObject>"
" (*)(xo::facet::obj<xo::scm::ARuntimeContext>)"
" :complete true :metatype function> :fn 1>"),
/* REVIEWED DIVERGENCE, two of them, both deliberate:
*
* 1. the value of a broken field lands in column 4 under
* legacy (indent 2 + indent_width 2) and column 3 under
* ppsink (indent 2 + tag_value_offset 1).
* 2. legacy's :td stays on one 143-column line because its
* legacy path is already a FlatSink and has no break points
* to offer; ppsink folds it.
*/
Testcase_Primitive(80,
"<Primitive<Fn>\n"
" :name cwd\n"
" :td\n"
" <TypeDescr\n"
" :id N\n"
" :canonical_name\n"
" xo::facet::obj<xo::mm::AGCObject>"
" (*)(xo::facet::obj<xo::scm::ARuntimeContext>)\n"
" :complete true\n"
" :metatype function>\n"
" :fn 1>"),
/* margin 20: legacy is UNCHANGED from margin 80 -- it has
* nothing left to give. ppsink degrades one step further,
* folding :metatype's value onto its own line. The
* :canonical_name value never folds in either: it is one
* atomic token, wider than any margin here.
*/
Testcase_Primitive(20,
"<Primitive<Fn>\n"
" :name cwd\n"
" :td\n"
" <TypeDescr\n"
" :id N\n"
" :canonical_name\n"
" xo::facet::obj<xo::mm::AGCObject>"
" (*)(xo::facet::obj<xo::scm::ARuntimeContext>)\n"
" :complete true\n"
" :metatype\n"
" function>\n"
" :fn 1>"),
};
}
static InitEvidence s_init_render = InitSubsys<S_procedure2_tag>::require();
TEST_CASE("DPrimitive-render", "[printable][DPrimitive]")
{
REQUIRE(s_init_render.evidence());
UtestRehearser rh;
for (auto _ : rh) {
scope log(XO_DEBUG2_(rh.enable_debug(), "DPrimitive-render"));
for (std::size_t i_tc = 0, n_tc = s_primitive_v.size(); i_tc < n_tc; ++i_tc) {
const auto & tc = s_primitive_v[i_tc];
auto tmp = ArenaNameStr::sprintf("utest.primitive.%ld", i_tc);
ArenaConfig cfg { .name_ = tmp,
.size_ = 64*1024 };
DArena arena = DArena::map(cfg);
auto alloc = with_facet<AAllocator>::mkobj(&arena);
auto stbl = StringTable(1024 /*hint_max_capacity*/,
false /*!debug_flag*/);
DPrimitive_gco_0 * pm = ObjectPrimitives::make_cwd_pm(alloc, &stbl);
REQUIRE(pm != nullptr);
auto p = with_facet<APrintable>::mkobj(pm);
std::string pretty = scrub_type_id(render_pretty(p, tc.margin_));
log && log(xtag("i_tc", i_tc), xtag("margin", tc.margin_), xtag("pretty", pretty));
REHEARSE(rh, pretty == tc.expect_pretty_);
}
}
}
} /*namespace ut*/
} /*namespace xo*/
/* end printable_render.test.cpp */