602 lines
26 KiB
C++
602 lines
26 KiB
C++
/* @file printable_render.test.cpp
|
|
*
|
|
* author: Roland Conybeare, Aug 2026
|
|
*
|
|
* Phase C verification for xo-object2's printers: the leaves (DInteger,
|
|
* DBoolean, DFloat), then DRuntimeError (a struct), DArray (a sequence) and
|
|
* DDictionary (a keyed sequence).
|
|
*
|
|
* Follows the template in xo-stringtable2/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.
|
|
*
|
|
* DList is verified separately, in xo-gc/utest/Object2.test.cpp, because it
|
|
* needs a collector fixture and its elements exercise these leaves.
|
|
*
|
|
* The interesting one here is DFloat: legacy formatted a double through
|
|
* ppstate::write(double), the sink through Prettifier<double>. Those are
|
|
* different code paths and "prints a double" is not evidence they agree --
|
|
* hence the range of values below rather than a single 1.5.
|
|
*
|
|
* Expectations are OBSERVED, never predicted.
|
|
*/
|
|
|
|
#include <xo/object2/DArray.hpp>
|
|
#include <xo/object2/DBoolean.hpp>
|
|
#include <xo/object2/DDictionary.hpp>
|
|
#include <xo/object2/DFloat.hpp>
|
|
#include <xo/object2/DInteger.hpp>
|
|
#include <xo/object2/RuntimeError.hpp>
|
|
#include <xo/object2/array/IGCObject_DArray.hpp>
|
|
#include <xo/object2/array/IPrintable_DArray.hpp>
|
|
#include <xo/object2/dictionary/IGCObject_DDictionary.hpp>
|
|
#include <xo/object2/dictionary/IPrintable_DDictionary.hpp>
|
|
#include <xo/object2/number/IPrintable_DInteger.hpp>
|
|
#include <xo/object2/number/IPrintable_DFloat.hpp>
|
|
#include <xo/object2/number/IGCObject_DInteger.hpp>
|
|
#include <xo/object2/boolean/IPrintable_DBoolean.hpp>
|
|
#include <xo/object2/SetupObject2.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 <utility>
|
|
#include <vector>
|
|
|
|
namespace xo {
|
|
using xo::scm::DArray;
|
|
using xo::scm::DBoolean;
|
|
using xo::scm::DDictionary;
|
|
using xo::scm::DFloat;
|
|
using xo::scm::DInteger;
|
|
using xo::scm::DRuntimeError;
|
|
using xo::scm::DString;
|
|
using xo::print::APrintable;
|
|
using xo::mm::AAllocator;
|
|
using xo::mm::AGCObject;
|
|
using xo::mm::DArena;
|
|
using xo::mm::ArenaConfig;
|
|
using xo::mm::ArenaNameStr;
|
|
using xo::facet::with_facet;
|
|
using xo::pp::PpConfig;
|
|
using xo::pp::scope;
|
|
using xo::pp::xtag;
|
|
|
|
namespace ut {
|
|
namespace {
|
|
/** render @p x through pretty(PpSink&) **/
|
|
template <typename T>
|
|
std::string
|
|
render_pretty(const T & x, std::uint32_t margin) {
|
|
/* PpStyle::plain(): struct field names are yellow by default
|
|
* (PpStyle.hpp), and this test is about LAYOUT. Per-render via
|
|
* the config rather than by flipping a global, so it cannot
|
|
* leak into whatever runs next.
|
|
*/
|
|
return xo::pp::toppstr(PpConfig::scratch_plain(margin), x);
|
|
}
|
|
|
|
template <typename V>
|
|
struct Testcase_Leaf {
|
|
Testcase_Leaf(V value, const char * expect_pretty)
|
|
: value_{value},
|
|
expect_pretty_{expect_pretty} {}
|
|
|
|
V value_;
|
|
/** OBSERVED via pretty; outlives phase E **/
|
|
std::string expect_pretty_;
|
|
};
|
|
|
|
std::vector<Testcase_Leaf<long>>
|
|
s_integer_v = {
|
|
Testcase_Leaf<long>(0, "0"),
|
|
Testcase_Leaf<long>(1, "1"),
|
|
Testcase_Leaf<long>(-1, "-1"),
|
|
Testcase_Leaf<long>(1234567890, "1234567890"),
|
|
};
|
|
|
|
std::vector<Testcase_Leaf<bool>>
|
|
s_boolean_v = {
|
|
Testcase_Leaf<bool>(true, "true"),
|
|
Testcase_Leaf<bool>(false, "false"),
|
|
};
|
|
|
|
/* double formatting is where the two paths could diverge */
|
|
std::vector<Testcase_Leaf<double>>
|
|
s_float_v = {
|
|
Testcase_Leaf<double>(0.0, "0"),
|
|
Testcase_Leaf<double>(1.5, "1.5"),
|
|
Testcase_Leaf<double>(-2.25, "-2.25"),
|
|
Testcase_Leaf<double>(1.0/3.0, "0.333333"),
|
|
Testcase_Leaf<double>(1e20, "1e+20"),
|
|
Testcase_Leaf<double>(1e-20, "1e-20"),
|
|
};
|
|
/** DRuntimeError is the first STRUCTURED printer verified here:
|
|
* two DString fields inside a pretty_struct. Unlike the leaves
|
|
* above, its rendering DEPENDS ON THE MARGIN, so the margin is
|
|
* part of the case.
|
|
**/
|
|
struct Testcase_Error {
|
|
Testcase_Error(std::uint32_t margin,
|
|
const char * src, const char * err,
|
|
const char * expect_pretty)
|
|
: margin_{margin}, src_{src}, err_{err},
|
|
expect_pretty_{expect_pretty} {}
|
|
|
|
std::uint32_t margin_;
|
|
const char * src_;
|
|
const char * err_;
|
|
/** OBSERVED via pretty; outlives phase E **/
|
|
std::string expect_pretty_;
|
|
};
|
|
|
|
std::vector<Testcase_Error>
|
|
s_error_v = {
|
|
/* fits: one line. identical. */
|
|
Testcase_Error(80, "DRuntimeError::make", "bad argument",
|
|
"<DRuntimeError :src DRuntimeError::make :err bad argument>"),
|
|
/* struct breaks, each field still fits its line. identical --
|
|
* the struct-level indent agrees at 2.
|
|
*/
|
|
Testcase_Error(40, "DRuntimeError::make", "bad argument",
|
|
"<DRuntimeError\n"
|
|
" :src DRuntimeError::make\n"
|
|
" :err bad argument>"),
|
|
/* narrow enough that each field breaks from its own value.
|
|
*
|
|
* REVIEWED DIVERGENCE, deliberate: the continuation indent for
|
|
* a broken value is 4 under legacy and 3 under ppsink. Legacy
|
|
* adds a further indent_width_ (2); ppsink adds
|
|
* xo::pp::tag_config::value_offset, which is 1 (tag.hpp:46), so
|
|
* the value hangs one column past its ":name" rather than lining
|
|
* up with the next nesting level. Deliberate in ppsink and
|
|
* configurable there, where legacy's was neither.
|
|
*/
|
|
Testcase_Error(16, "DRuntimeError::make", "bad argument",
|
|
"<DRuntimeError\n"
|
|
" :src\n"
|
|
" DRuntimeError::make\n"
|
|
" :err\n"
|
|
" bad argument>"),
|
|
};
|
|
/** DArray is the first SEQUENCE printer verified here. Unlike
|
|
* DRuntimeError its arity is not fixed, so the margin decides both
|
|
* whether it breaks and how many elements share a line.
|
|
*
|
|
* Note this does NOT render like Prettifier<vector<T>>, which
|
|
* indents a broken sequence by a full nesting level
|
|
* ("[1,\n 2,\n 3]", pinned in
|
|
* xo-indentlog2/utest/PrettyVector.test.cpp). DArray aligns to
|
|
* element 0 instead -- RC's call, matching what legacy rendered.
|
|
*
|
|
* Elements are DIntegers -- leaves already pinned above, so any
|
|
* difference here is the array's own framing, not an element's.
|
|
**/
|
|
struct Testcase_Array {
|
|
Testcase_Array(std::uint32_t margin,
|
|
std::vector<long> elt_v,
|
|
const char * expect_pretty)
|
|
: margin_{margin}, elt_v_{std::move(elt_v)},
|
|
expect_pretty_{expect_pretty} {}
|
|
|
|
std::uint32_t margin_;
|
|
std::vector<long> elt_v_;
|
|
/** OBSERVED via pretty; outlives phase E **/
|
|
std::string expect_pretty_;
|
|
};
|
|
|
|
std::vector<Testcase_Array>
|
|
s_array_v = {
|
|
/* degenerate: empty group must not break, and must not emit a
|
|
* separator it has no elements to separate.
|
|
*/
|
|
Testcase_Array(80, {}, "[]"),
|
|
Testcase_Array(80, {1}, "[1]"),
|
|
/* fits: one line, space-separated. identical. */
|
|
Testcase_Array(80, {1, 2, 3}, "[1 2 3]"),
|
|
/* REVIEWED DIVERGENCE, deliberate: both stacks align elements
|
|
* 2..n under element 0, but they get there differently.
|
|
*
|
|
* Legacy pads -- it writes a space after "[" so that element 0
|
|
* starts at the continuation indent ("[ 100" / " 200").
|
|
* ppsink offsets instead: begin(1) credits the bracket, so
|
|
* element 0 follows "[" immediately and the continuation indent
|
|
* is 1 to match it. Same alignment, one column left, and no
|
|
* whitespace inside a rendering that claims to have none.
|
|
*/
|
|
Testcase_Array(8, {100, 200, 300},
|
|
"[100\n 200\n 300]"),
|
|
/* margin narrower than a single element: no further recourse,
|
|
* so the rendering is unchanged from margin 8 rather than
|
|
* degenerating.
|
|
*/
|
|
Testcase_Array(4, {100, 200, 300},
|
|
"[100\n 200\n 300]"),
|
|
};
|
|
/** DDictionary is a KEYED sequence: like DArray its arity is
|
|
* variable, but each element is itself two printers plus fixed
|
|
* punctuation, so the same margin breaks it sooner.
|
|
*
|
|
* Keys are DStrings, which render UNQUOTED (DString::pretty ->
|
|
* sink.pp(const char *)); values here are DIntegers. Both are
|
|
* pinned already, so any difference below is the dictionary's own
|
|
* framing.
|
|
*
|
|
* Each entry is its own group with a break point after the ":", so
|
|
* a value that will not fit folds onto the next line rather than
|
|
* starting inline wherever the key ended. RC's call: the key
|
|
* could just as well have been forty characters long, and the
|
|
* layout must not depend on that.
|
|
**/
|
|
struct Testcase_Dict {
|
|
using entry_type = std::pair<const char *, long>;
|
|
|
|
Testcase_Dict(std::uint32_t margin,
|
|
std::vector<entry_type> entry_v,
|
|
const char * expect_pretty)
|
|
: margin_{margin}, entry_v_{std::move(entry_v)},
|
|
expect_pretty_{expect_pretty} {}
|
|
|
|
std::uint32_t margin_;
|
|
std::vector<entry_type> entry_v_;
|
|
/** OBSERVED via pretty; outlives phase E **/
|
|
std::string expect_pretty_;
|
|
};
|
|
|
|
std::vector<Testcase_Dict>
|
|
s_dict_v = {
|
|
/* degenerate: empty group must not break, and must not emit a
|
|
* separator it has no entries to separate. Legacy still pads,
|
|
* so an empty dictionary is TWO characters there and one here.
|
|
*/
|
|
Testcase_Dict(80, {}, "{}"),
|
|
Testcase_Dict(80, {{"a", 1}}, "{a: 1;}"),
|
|
/* fits: one line. ";" TERMINATES each entry rather than
|
|
* separating them, so the last one carries it too -- legacy's
|
|
* shape, kept.
|
|
*/
|
|
Testcase_Dict(80, {{"a", 1}, {"bb", 22}}, "{a: 1; bb: 22;}"),
|
|
/* REVIEWED DIVERGENCE, deliberate, and the same one DArray
|
|
* settled: both stacks align entries 2..n under entry 0, but
|
|
* legacy gets there by PADDING (a space after "{", so entry 0
|
|
* starts at the continuation indent) where ppsink OFFSETS
|
|
* (begin(1) credits the brace). Same alignment, one column
|
|
* left, and no interior whitespace.
|
|
*/
|
|
Testcase_Dict(12, {{"a", 1}, {"bb", 22}},
|
|
"{a: 1;\n bb: 22;}"),
|
|
/* margin narrower than a single entry. Unlike DArray -- whose
|
|
* elements are atomic, so a too-narrow margin has no further
|
|
* recourse -- an entry CAN still fold after its ":", and does.
|
|
* Legacy has no such break point, so it is unchanged from
|
|
* margin 12 while ppsink degrades one step further.
|
|
*/
|
|
Testcase_Dict(4, {{"a", 1}, {"bb", 22}},
|
|
"{a:\n 1;\n bb:\n 22;}"),
|
|
/* A key wide enough to crowd the margin on its own. Each entry
|
|
* is its own group with a break point after the ":", so the
|
|
* value FOLDS onto the next line at entry-indent + 1 and the
|
|
* layout stops depending on how long the key was.
|
|
*
|
|
* REVIEWED DIVERGENCE, deliberate: legacy has no such break
|
|
* point, so it emits a 41-column line at a margin of 30 and
|
|
* renders margin 12 identically -- it simply cannot fold here.
|
|
*/
|
|
Testcase_Dict(30, {{"keep_going_until_close_to_margin", 12345}, {"n", 3}},
|
|
"{keep_going_until_close_to_margin:\n"
|
|
" 12345;\n"
|
|
" n: 3;}"),
|
|
};
|
|
} /*namespace*/
|
|
|
|
TEST_CASE("DInteger-render", "[printable][DInteger]")
|
|
{
|
|
REQUIRE(xo::scm::SetupObject2::register_facets());
|
|
|
|
UtestRehearser rh;
|
|
|
|
for (auto _ : rh) {
|
|
scope log(XO_DEBUG2_(rh.enable_debug(), "DInteger-render"));
|
|
|
|
for (std::size_t i_tc = 0, n_tc = s_integer_v.size(); i_tc < n_tc; ++i_tc) {
|
|
const auto & tc = s_integer_v[i_tc];
|
|
|
|
auto tmp = ArenaNameStr::sprintf("utest.int.%ld", i_tc);
|
|
|
|
ArenaConfig cfg { .name_ = tmp,
|
|
.size_ = 4*1024 };
|
|
DArena arena = DArena::map(cfg);
|
|
auto alloc = with_facet<AAllocator>::mkobj(&arena);
|
|
|
|
auto p = with_facet<APrintable>::mkobj(DInteger::_box(alloc, tc.value_));
|
|
std::string pretty = render_pretty(p, 80);
|
|
|
|
log && log(xtag("i_tc", i_tc), xtag("value", tc.value_), xtag("pretty", pretty));
|
|
|
|
REHEARSE(rh, pretty == tc.expect_pretty_);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("DBoolean-render", "[printable][DBoolean]")
|
|
{
|
|
REQUIRE(xo::scm::SetupObject2::register_facets());
|
|
|
|
UtestRehearser rh;
|
|
|
|
for (auto _ : rh) {
|
|
scope log(XO_DEBUG2_(rh.enable_debug(), "DBoolean-render"));
|
|
|
|
for (std::size_t i_tc = 0, n_tc = s_boolean_v.size(); i_tc < n_tc; ++i_tc) {
|
|
const auto & tc = s_boolean_v[i_tc];
|
|
|
|
auto tmp = ArenaNameStr::sprintf("utest.bool.%ld", i_tc);
|
|
|
|
ArenaConfig cfg { .name_ = tmp,
|
|
.size_ = 4*1024 };
|
|
DArena arena = DArena::map(cfg);
|
|
auto alloc = with_facet<AAllocator>::mkobj(&arena);
|
|
|
|
auto p = with_facet<APrintable>::mkobj(DBoolean::_box(alloc, tc.value_));
|
|
std::string pretty = render_pretty(p, 80);
|
|
|
|
log && log(xtag("i_tc", i_tc), xtag("value", tc.value_), xtag("pretty", pretty));
|
|
|
|
REHEARSE(rh, pretty == tc.expect_pretty_);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("DFloat-render", "[printable][DFloat]")
|
|
{
|
|
REQUIRE(xo::scm::SetupObject2::register_facets());
|
|
|
|
UtestRehearser rh;
|
|
|
|
for (auto _ : rh) {
|
|
scope log(XO_DEBUG2_(rh.enable_debug(), "DFloat-render"));
|
|
|
|
for (std::size_t i_tc = 0, n_tc = s_float_v.size(); i_tc < n_tc; ++i_tc) {
|
|
const auto & tc = s_float_v[i_tc];
|
|
|
|
auto tmp = ArenaNameStr::sprintf("utest.float.%ld", i_tc);
|
|
ArenaConfig cfg { .name_ = tmp,
|
|
.size_ = 4*1024 };
|
|
DArena arena = DArena::map(cfg);
|
|
auto alloc = with_facet<AAllocator>::mkobj(&arena);
|
|
|
|
auto p = with_facet<APrintable>::mkobj(DFloat::_box(alloc, tc.value_));
|
|
std::string pretty = render_pretty(p, 80);
|
|
|
|
log && log(xtag("i_tc", i_tc), xtag("value", tc.value_), xtag("pretty", pretty));
|
|
|
|
REHEARSE(rh, pretty == tc.expect_pretty_);
|
|
}
|
|
}
|
|
}
|
|
TEST_CASE("DRuntimeError-render", "[printable][DRuntimeError]")
|
|
{
|
|
REQUIRE(xo::scm::SetupObject2::register_facets());
|
|
|
|
UtestRehearser rh;
|
|
|
|
for (auto _ : rh) {
|
|
scope log(XO_DEBUG2_(rh.enable_debug(), "DRuntimeError-render"));
|
|
|
|
for (std::size_t i_tc = 0, n_tc = s_error_v.size(); i_tc < n_tc; ++i_tc) {
|
|
const auto & tc = s_error_v[i_tc];
|
|
|
|
auto tmp = ArenaNameStr::sprintf("utest.error.%ld", i_tc);
|
|
|
|
ArenaConfig cfg { .name_ = tmp,
|
|
.size_ = 4*1024 };
|
|
DArena arena = DArena::map(cfg);
|
|
auto alloc = with_facet<AAllocator>::mkobj(&arena);
|
|
|
|
DRuntimeError * e
|
|
= DRuntimeError::_make(alloc,
|
|
DString::from_cstr(alloc, tc.src_),
|
|
DString::from_cstr(alloc, tc.err_));
|
|
|
|
auto p = with_facet<APrintable>::mkobj(e);
|
|
std::string pretty = 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_);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("DArray-render", "[printable][DArray]")
|
|
{
|
|
REQUIRE(xo::scm::SetupObject2::register_facets());
|
|
|
|
UtestRehearser rh;
|
|
|
|
for (auto _ : rh) {
|
|
scope log(XO_DEBUG2_(rh.enable_debug(), "DArray-render"));
|
|
|
|
for (std::size_t i_tc = 0, n_tc = s_array_v.size(); i_tc < n_tc; ++i_tc) {
|
|
const auto & tc = s_array_v[i_tc];
|
|
|
|
auto tmp = ArenaNameStr::sprintf("utest.array.%ld", i_tc);
|
|
|
|
ArenaConfig cfg { .name_ = tmp,
|
|
.size_ = 4*1024 };
|
|
DArena arena = DArena::map(cfg);
|
|
auto alloc = with_facet<AAllocator>::mkobj(&arena);
|
|
|
|
DArray * arr = DArray::_empty(alloc, tc.elt_v_.size());
|
|
|
|
REQUIRE(arr != nullptr);
|
|
|
|
for (long elt : tc.elt_v_)
|
|
REQUIRE(arr->push_back(alloc, DInteger::box<AGCObject>(alloc, elt)));
|
|
|
|
auto p = with_facet<APrintable>::mkobj(arr);
|
|
std::string pretty = 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_);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("DDictionary-render", "[printable][DDictionary]")
|
|
{
|
|
REQUIRE(xo::scm::SetupObject2::register_facets());
|
|
|
|
UtestRehearser rh;
|
|
|
|
for (auto _ : rh) {
|
|
scope log(XO_DEBUG2_(rh.enable_debug(), "DDictionary-render"));
|
|
|
|
for (std::size_t i_tc = 0, n_tc = s_dict_v.size(); i_tc < n_tc; ++i_tc) {
|
|
const auto & tc = s_dict_v[i_tc];
|
|
|
|
auto tmp = ArenaNameStr::sprintf("utest.dict.%ld", i_tc);
|
|
|
|
ArenaConfig cfg { .name_ = tmp,
|
|
.size_ = 4*1024 };
|
|
DArena arena = DArena::map(cfg);
|
|
auto alloc = with_facet<AAllocator>::mkobj(&arena);
|
|
|
|
DDictionary * dict = DDictionary::empty(alloc, tc.entry_v_.size());
|
|
|
|
REQUIRE(dict != nullptr);
|
|
|
|
for (const auto & entry : tc.entry_v_) {
|
|
REQUIRE(dict->upsert_cstr(alloc, entry.first,
|
|
DInteger::box<AGCObject>(alloc, entry.second)));
|
|
}
|
|
|
|
auto p = with_facet<APrintable>::mkobj(dict);
|
|
std::string pretty = 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_);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** As with DArray, a flat dictionary shows only that the group breaks.
|
|
* Nesting one dictionary inside another is what shows the inner
|
|
* group's offset COMPOSING with the enclosing indent rather than
|
|
* resetting to column 0 -- and, at margin 16, the entry folding after
|
|
* its key before the value is asked to break at all.
|
|
**/
|
|
TEST_CASE("DDictionary-render-nested", "[printable][DDictionary]")
|
|
{
|
|
REQUIRE(xo::scm::SetupObject2::register_facets());
|
|
|
|
ArenaConfig cfg { .name_ = flatstring("utest.dict.nested"), .size_ = 4*1024 };
|
|
DArena arena = DArena::map(cfg);
|
|
auto alloc = with_facet<AAllocator>::mkobj(&arena);
|
|
|
|
/* {k: {a: 1; b: 2;}; n: 3;} */
|
|
DDictionary * inner = DDictionary::empty(alloc, 2);
|
|
REQUIRE(inner->upsert_cstr(alloc, "a", DInteger::box<AGCObject>(alloc, 1)));
|
|
REQUIRE(inner->upsert_cstr(alloc, "b", DInteger::box<AGCObject>(alloc, 2)));
|
|
|
|
DDictionary * outer = DDictionary::empty(alloc, 2);
|
|
REQUIRE(outer->upsert_cstr(alloc, "k",
|
|
obj<AGCObject,DDictionary>(inner)));
|
|
REQUIRE(outer->upsert_cstr(alloc, "n", DInteger::box<AGCObject>(alloc, 3)));
|
|
|
|
auto p = with_facet<APrintable>::mkobj(outer);
|
|
|
|
/* fits */
|
|
REQUIRE(render_pretty(p, 80) == "{k: {a: 1; b: 2;}; n: 3;}");
|
|
|
|
/* outer breaks, inner still fits: entry 1 lines up under entry 0,
|
|
* i.e. indent 1.
|
|
*/
|
|
REQUIRE(render_pretty(p, 20) == ("{k: {a: 1; b: 2;};\n"
|
|
" n: 3;}"));
|
|
|
|
/* the entry no longer fits, so it breaks after "k:" and the value
|
|
* starts a fresh line at entry-indent + 1 = 2. The nested value
|
|
* is thereby placed independently of the key's width -- which is
|
|
* the whole point of giving each entry its own group.
|
|
*
|
|
* Legacy has no break point after the key, so it keeps the value
|
|
* inline and lets the INNER dictionary break instead, indenting to
|
|
* that dictionary's own column (7).
|
|
*/
|
|
REQUIRE(render_pretty(p, 16) == ("{k:\n"
|
|
" {a: 1; b: 2;};\n"
|
|
" n: 3;}"));
|
|
|
|
/* Both levels break. The inner dictionary opens at the running
|
|
* indent (2), so its own begin(1) puts its entries at 3 -- the
|
|
* offset COMPOSES with the enclosing indent rather than resetting.
|
|
* That is the assertion a flat dictionary cannot make.
|
|
*
|
|
* Legacy is UNCHANGED from margin 16: with no fold after the key it
|
|
* has no further recourse.
|
|
*/
|
|
REQUIRE(render_pretty(p, 10) == ("{k:\n"
|
|
" {a: 1;\n"
|
|
" b: 2;};\n"
|
|
" n: 3;}"));
|
|
}
|
|
|
|
/** A flat array shows that the group breaks; only a NESTED one shows
|
|
* that an inner group indents relative to its parent rather than to
|
|
* column 0. That is the property a sequence printer can get wrong
|
|
* without any of the cases above noticing.
|
|
**/
|
|
TEST_CASE("DArray-render-nested", "[printable][DArray]")
|
|
{
|
|
REQUIRE(xo::scm::SetupObject2::register_facets());
|
|
|
|
ArenaConfig cfg { .name_ = flatstring("utest.array.nested"), .size_ = 4*1024 };
|
|
DArena arena = DArena::map(cfg);
|
|
auto alloc = with_facet<AAllocator>::mkobj(&arena);
|
|
|
|
/* [[100 200] [300]] */
|
|
DArray * inner1 = DArray::_empty(alloc, 2);
|
|
REQUIRE(inner1->push_back(alloc, DInteger::box<AGCObject>(alloc, 100)));
|
|
REQUIRE(inner1->push_back(alloc, DInteger::box<AGCObject>(alloc, 200)));
|
|
|
|
DArray * inner2 = DArray::_empty(alloc, 1);
|
|
REQUIRE(inner2->push_back(alloc, DInteger::box<AGCObject>(alloc, 300)));
|
|
|
|
DArray * outer = DArray::_empty(alloc, 2);
|
|
REQUIRE(outer->push_back(alloc, with_facet<AGCObject>::mkobj(inner1)));
|
|
REQUIRE(outer->push_back(alloc, with_facet<AGCObject>::mkobj(inner2)));
|
|
|
|
auto p = with_facet<APrintable>::mkobj(outer);
|
|
|
|
/* fits */
|
|
REQUIRE(render_pretty(p, 80) == "[[100 200] [300]]");
|
|
|
|
/* outer breaks, inner arrays still fit: element 1 lines up under
|
|
* element 0, i.e. indent 1 -- not column 0, and not the enclosing
|
|
* indent + a nesting level.
|
|
*/
|
|
REQUIRE(render_pretty(p, 12) == ("[[100 200]\n"
|
|
" [300]]"));
|
|
|
|
/* both levels break. The inner array's own "[" sits at column 1, so
|
|
* its elements align at column 2: the offset composes with the
|
|
* enclosing indent rather than resetting. This is the assertion a
|
|
* flat array cannot make.
|
|
*/
|
|
REQUIRE(render_pretty(p, 6) == ("[[100\n"
|
|
" 200]\n"
|
|
" [300]]"));
|
|
}
|
|
} /*namespace ut*/
|
|
} /*namespace xo*/
|
|
|
|
/* end printable_render.test.cpp */
|