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

183 lines
6.5 KiB
C++

/** @file struct_scope.test.cpp
*
* exercise PpSink::struct_open() (xo/ppsink/pretty_struct.hpp) through the
* real line-breaking sink.
*
* Two things can only be asserted here, not in xo-ppsink/utest:
*
* 1. that a dynamically-built struct wraps to the SAME indent as a variadic
* one. begin(offset) and split(spaces,offset) compound in PpState, so a
* builder that passed the indent to both would wrap to twice the intended
* column -- invisible in the flat text and in the token stream.
*
* 2. that force_break actually forces. newline()'s contract is "forcing
* every enclosing group to break"; whether that propagates through the
* enclosing groups is a rendering property. The ppsink-side test can only
* see that a newline token was emitted, not what it did.
**/
#include <xo/ppsink/pretty_struct.hpp>
#include "print/PrettySink.hpp"
#include <xo/arena/ArenaConfig.hpp>
#include <catch2/catch.hpp>
#include <string>
#include <vector>
namespace ut {
using xo::pp::PrettySink;
using xo::pp::PpConfig;
using xo::pp::PpStyle;
using xo::pp::PpSink;
using xo::pp::field;
using xo::mm::ArenaConfig;
namespace {
/** render whatever @p fn writes, at right margin @p margin.
*
* NB unique arena name per call -- see the note in
* pretty_struct.test.cpp; sharing one produces wrong indentation in
* whichever case runs second.
**/
template <typename Fn>
std::string render(std::uint32_t margin, Fn && fn) {
PrettySink pp(PpConfig::scratch_plain(margin), nullptr);
fn(pp);
return std::string(pp.output());
}
} /*namespace*/
/* ------------------------------------------------------------------
* indent: a builder-built struct must wrap exactly like a variadic one
*/
TEST_CASE("struct_scope-wraps-to-indent-2", "[struct_scope]") {
const int alpha = 1;
const int beta = 2;
std::string out = render(12, [&alpha, &beta](PpSink & sink) {
auto st = sink.struct_open("Point");
st.field("alpha", alpha);
st.field("beta", beta);
});
REQUIRE(out == "<Point\n :alpha 1\n :beta 2>");
/* indent is 2, not 4 -- the compounding bug */
REQUIRE(out.find("\n :alpha") != std::string::npos);
REQUIRE(out.find("\n :alpha") == std::string::npos);
}
TEST_CASE("struct_scope-indent-matches-pretty_struct", "[struct_scope]") {
const int alpha = 1;
const int beta = 2;
std::string via_builder = render(12, [&alpha, &beta](PpSink & sink) {
auto st = sink.struct_open("Point");
st.field("alpha", alpha);
st.field("beta", beta);
});
std::string via_variadic = render(12, [&alpha, &beta](PpSink & sink) {
sink.pretty_struct("Point", field("alpha", alpha), field("beta", beta));
});
REQUIRE(via_builder == via_variadic);
}
TEST_CASE("struct_scope-fits-one-line", "[struct_scope]") {
const int alpha = 1;
const int beta = 2;
REQUIRE(render(135, [&alpha, &beta](PpSink & sink) {
auto st = sink.struct_open("Point");
st.field("alpha", alpha);
st.field("beta", beta);
}) == "<Point :alpha 1 :beta 2>");
}
/* ------------------------------------------------------------------
* force_break: breaks even at a margin wide enough to fit.
* This is the assertion that exists only here.
*/
TEST_CASE("struct_scope-force-break-breaks-when-it-would-fit", "[struct_scope]") {
const std::size_t size = 2;
const int a = 7;
const int b = 8;
auto build = [&size, &a, &b](bool force) {
return [&size, &a, &b, force](PpSink & sink) {
auto st = sink.struct_open("stack", force);
st.field("size", size);
st.field("[1]", a);
st.field("[0]", b);
};
};
/* margin 135: comfortably fits on one line */
REQUIRE(render(135, build(false)) == "<stack :size 2 :[1] 7 :[0] 8>");
/* same margin, force_break: must break anyway */
std::string forced = render(135, build(true));
REQUIRE(forced == "<stack\n :size 2\n :[1] 7\n :[0] 8>");
}
/* a forced break inside a nested struct forces the ENCLOSING group open
* too -- newline()'s documented contract, and unobservable flat.
*/
TEST_CASE("struct_scope-force-break-propagates-outward", "[struct_scope]") {
const int x = 1;
const int y = 2;
std::string out = render(135, [&x, &y](PpSink & sink) {
auto outer = sink.struct_open("Out"); /* no force here */
outer.field("a", x);
{
/* inner forces: the outer group must break as well */
auto inner = sink.struct_open("In", true /*force_break*/);
inner.field("b", y);
}
});
/* outer broke even though everything would have fitted at margin 135 */
REQUIRE(out.find('\n') != std::string::npos);
REQUIRE(out.find("<Out\n") != std::string::npos);
}
/* ------------------------------------------------------------------
* dynamic arity: a loop-built struct renders like a hand-listed one
*/
TEST_CASE("struct_scope-loop-wraps-correctly", "[struct_scope]") {
const std::vector<int> v = {10, 20, 30};
std::string out = render(12, [&v](PpSink & sink) {
auto st = sink.struct_open("V");
for (std::size_t i = 0; i < v.size(); ++i)
st.field(std::string("[") + std::to_string(i) + "]", v[i]);
});
REQUIRE(out == "<V\n :[0] 10\n :[1] 20\n :[2] 30>");
}
/* nested builders accumulate indent: inner fields sit one level deeper */
TEST_CASE("struct_scope-nested-accumulates-indent", "[struct_scope]") {
const int alpha = 1;
const int x = 1;
std::string out = render(12, [&alpha, &x](PpSink & sink) {
auto outer = sink.struct_open("Out");
outer.field("alpha", alpha);
{
auto inner = sink.struct_open("In");
inner.field("x", x);
}
});
/* outer fields at 2 */
REQUIRE(out.find("\n :alpha") != std::string::npos);
/* inner field deeper than the outer one */
REQUIRE(out.find("\n :x") != std::string::npos);
}
} /*namespace ut*/
/* end struct_scope.test.cpp */