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

117 lines
3.9 KiB
C++

/** @file Prettifier.test.cpp
*
* @author Roland Conybeare, Jul 2026
**/
#include "print/PrettySink.hpp"
#include <xo/arena/ArenaConfig.hpp>
#include <xo/ppsink/FlatSink.hpp>
#include <xo/ppsink/pretty_ostream.hpp> /* Fallback_int exercises the operator<< fallback */
#include <catch2/catch.hpp>
#include <sstream>
#include <string>
namespace ut {
/* A leaf type OWNED by this TU: no Prettifier specialization, plus an
* operator<<, so pretty() exercises the operator<< fallback path.
*
* Deliberately a bespoke wrapper rather than a built-in scalar (int,
* double, ...): ppsink is free to add Prettifier<int> and friends, which
* would silently turn a built-in fallback example into a Prettifier type and
* break this test. A type we own can't be reclassified out from under us.
*/
struct Fallback_int { int v_; };
inline std::ostream &
operator<<(std::ostream & os, const Fallback_int & x) {
return os << x.v_;
}
/** a test type that opts in to structured pretty-printing **/
struct Point_Pretty { Fallback_int x_; Fallback_int y_; };
}
/* Prettifier specialization for ut::Point_Pretty.
* Structure: "(" x "," <split> y ")". The x/y members are Fallback_int --
* they have no Prettifier, so pretty() renders them via operator<< (atomic
* leaves), exercising a Prettifier type that delegates to fallback members.
*/
namespace xo::pp {
template <>
struct Prettifier<ut::Point_Pretty> {
static void print(PpSink & sink, const ut::Point_Pretty & p) {
sink.put("(");
sink.begin();
pretty(sink, p.x_);
sink.put(",");
sink.split();
pretty(sink, p.y_);
sink.end();
sink.put(")");
}
};
} /*namespace xo::pp*/
namespace ut {
using xo::pp::has_prettifier;
using xo::pp::pretty;
using xo::pp::PpSink;
using xo::pp::FlatSink;
using xo::pp::PrettySink;
using xo::pp::PpConfig;
using xo::mm::ArenaConfig;
/* compile-time: the concept distinguishes opted-in from fallback types.
* Assert only on types this TU owns -- whether ppsink gives int/double/
* std::string a Prettifier is ppsink's business, tested in ppsink's own suite.
*/
static_assert(has_prettifier<Point_Pretty>);
static_assert(!has_prettifier<Fallback_int>);
/** render @p fn's output through a FlatSink **/
template <typename Fn>
static std::string flat_of(Fn && fn) {
std::ostringstream ss;
FlatSink sink(ss.rdbuf());
fn(sink);
return ss.str();
}
/** render @p fn's output through a PrettySink.
* @p margin 0 => use the default soft right margin.
**/
template <typename Fn>
static std::string pretty_of(std::uint32_t margin, Fn && fn) {
PrettySink pp(PpConfig::scratch_plain(margin), nullptr);
fn(pp);
return std::string(pp.output());
}
TEST_CASE("pretty.fallback", "[Pretty]")
{
/* a type with neither Pretty nor string-conversion routes through
* operator<< */
REQUIRE(flat_of([](PpSink & s) { pretty(s, Fallback_int{42}); }) == "42");
REQUIRE(flat_of([](PpSink & s) { pretty(s, Fallback_int{-7}); }) == "-7");
REQUIRE(pretty_of(135, [](PpSink & s) { pretty(s, Fallback_int{42}); }) == "42");
}
TEST_CASE("pretty.pretty", "[Pretty]")
{
Point_Pretty p{ {3}, {4} };
/* Prettifier<Point_Pretty> drives the structure; int members fall back */
REQUIRE(flat_of([&](PpSink & s) { pretty(s, p); }) == "(3,4)");
/* wide margin: group fits, split collapses -> same as flat */
REQUIRE(pretty_of(135, [&](PpSink & s) { pretty(s, p); }) == "(3,4)");
/* narrow margin: group doesn't fit -> split becomes newline + indent
* (indent_width default 2, nesting depth 1 => 2 spaces)
*/
REQUIRE(pretty_of(2, [&](PpSink & s) { pretty(s, p); }) == "(3,\n 4)");
}
}
/* end Prettifier.test.cpp */