xo-umbrella2/xo-indentlog2/utest/toppstr.test.cpp

184 lines
7.4 KiB
C++

/* @file toppstr.test.cpp
*
* author: Roland Conybeare, Aug 2026
*
* xo::pp::toppstr -- render through a PrettySink, i.e. WITH line breaking.
* Counterpart to xo::pp::tostr, which uses a FlatSink and never breaks.
*
* Expectations here were read off observed output, not predicted.
*/
#include "print/toppstr.hpp"
#include "print/tostr.hpp"
#include <xo/ppsink/PpStyle.hpp>
#include <xo/ppsink/pretty_struct.hpp>
#include <xo/ppsink/pretty_array.hpp>
#include <catch2/catch.hpp>
#include <array>
#include <cstdint>
#include <string>
#include <vector>
namespace xo {
namespace ut {
using xo::pp::PpConfig;
using xo::pp::PpStyle;
using xo::pp::toppstr;
using xo::pp::tostr;
using xo::pp::color_spec_type;
namespace {
/** an array is enough structure to break: Prettifier<std::array>
* puts a split() between elements.
**/
std::array<int, 8> s_value = { 111, 222, 333, 444, 555, 666, 777, 888 };
std::string
render_at(std::uint32_t margin) {
return toppstr(PpConfig::scratch_plain(margin), s_value);
}
struct Testcase_Toppstr {
Testcase_Toppstr(std::uint32_t margin, bool expect_break)
: margin_{margin}, expect_break_{expect_break} {}
/** soft right margin to render at **/
std::uint32_t margin_;
/** whether the rendering is expected to span >1 line **/
bool expect_break_;
};
std::vector<Testcase_Toppstr>
s_testcase_v = {
Testcase_Toppstr(200, false),
Testcase_Toppstr(100, false),
Testcase_Toppstr(20, true),
Testcase_Toppstr(8, true),
};
} /*namespace*/
TEST_CASE("toppstr-breaks-at-margin", "[toppstr]") {
/* the whole point of toppstr: unlike tostr (FlatSink), the margin
* governs whether the rendering breaks.
*/
for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) {
const Testcase_Toppstr & tc = s_testcase_v[i_tc];
std::string s = render_at(tc.margin_);
INFO("i_tc=" << i_tc << " margin=" << tc.margin_ << " rendered=[" << s << "]");
REQUIRE((s.find('\n') != std::string::npos) == tc.expect_break_);
/* breaking must not lose content */
for (int elt : s_value) {
INFO("element " << elt);
REQUIRE(s.find(std::to_string(elt)) != std::string::npos);
}
}
}
TEST_CASE("toppstr-wide-margin-matches-tostr", "[toppstr]") {
/* at a margin wide enough that nothing breaks, toppstr and tostr
* must agree -- they differ only in whether breaking is possible.
*/
REQUIRE(render_at(200) == tostr(s_value));
}
TEST_CASE("toppstr-is-repeatable", "[toppstr]") {
/* The hazard toppstr exists to make unreachable: two PrettySinks
* sharing an ArenaConfig name interfere, and the symptom is WRONG
* INDENTATION in whichever renders second -- a silent wrong answer,
* not an error. Six files carried a comment warning about it.
* Rendering the same input repeatedly must give the same result.
*/
constexpr int c_n = 8;
std::string first = render_at(20);
for (int i = 0; i < c_n; ++i) {
INFO("iteration " << i << " first=[" << first << "]");
REQUIRE(render_at(20) == first);
}
}
TEST_CASE("toppstr-carries-style", "[toppstr][PpStyle]") {
/* The seam PpStyle exists to close: the style is set on a PpConfig
* (xo-indentlog2) and consumed by a Prettifier that sees only a
* PpSink (xo-ppsink, one subsystem BELOW). PrettySink installs it
* on itself at construction; nothing else connects the two, so a
* dropped with_style() would silently render in the default.
*
* tag() and field() are rendered directly, since they ARE the two
* consumers -- and they take the two different colors, which is the
* distinction being checked.
*
* Styles are set explicitly rather than read from the ambient
* default, which the utest main has replaced with plain().
*/
PpStyle style;
style.tag_color = color_spec_type::ansi(31); /* red */
style.struct_tag_color = color_spec_type::ansi(32); /* green */
int x = 1; /* named: field() captures by reference */
{
auto ppc = PpConfig::plain().with_style(style);
REQUIRE(toppstr(ppc, xo::pp::tag("k", x))
== "\033[31m:k\033[0m 1");
REQUIRE(toppstr(ppc, xo::pp::field("k", x))
== "\033[32m:k\033[0m 1");
}
{
auto ppc = PpConfig::plain();
/* ... and both render bare under plain() */
REQUIRE(toppstr(ppc, xo::pp::tag("k", x))
== ":k 1");
REQUIRE(toppstr(ppc, xo::pp::field("k", x))
== ":k 1");
}
}
TEST_CASE("toppstr-no-config-is-colored", "[toppstr][PpStyle]") {
/* The no-config overload picks its OWN config, so nothing else in
* this file pins what style that is -- every other test passes one
* explicitly, in order to pin text. Without this case the overload
* can be switched between PpConfig::plain() and PpConfig::colored()
* and the suite stays green: exactly what happened in 522799d7,
* where it silently became plain(). See
* .xo-backlog/xo-indentlog2/issues/05.
*
* RC's call: a bare toppstr(x) COLOURS, matching toppstr2 and the
* PpConfig-taking overload, so migrating a call site off legacy
* never loses colour by itself.
*
* Values OBSERVED, not predicted. Note this is deliberately
* independent of PpStyle::default_style(), which the utest main has
* replaced with plain() -- PpConfig::colored() names its colours.
*/
int x = 1; /* named: field() captures by reference */
REQUIRE(toppstr(xo::pp::tag("k", x)) == "\033[38;5;245m:k\033[0m 1");
REQUIRE(toppstr(xo::pp::field("k", x)) == "\033[33m:k\033[0m 1");
/* ... and it is PpConfig::colored() specifically that it picks */
REQUIRE(toppstr(xo::pp::tag("k", x))
== toppstr(PpConfig::colored(), xo::pp::tag("k", x)));
REQUIRE(toppstr(xo::pp::field("k", x))
== toppstr(PpConfig::colored(), xo::pp::field("k", x)));
}
TEST_CASE("toppstr-overloads", "[toppstr]") {
/* the no-config overload must not be shadowed by the
* PpConfig-taking one, nor vice versa
*/
REQUIRE(toppstr(123) == "123");
REQUIRE(toppstr(PpConfig::plain(), 123) == "123");
}
} /*namespace ut*/
} /*namespace xo*/
/* end toppstr.test.cpp */