xo-umbrella2/xo-ppsink/utest/pretty_struct.test.cpp

178 lines
5.4 KiB
C++

/** @file pretty_struct.test.cpp
*
* exercise PpSink::pretty_struct + field() from xo/ppsink/pretty_struct.hpp.
*
* Two kinds of assertion here:
*
* - via FlatSink: the flat *text*, which is what a reader sees when the
* struct fits on one line.
* - via MarkSink (MarkSink.hpp): the *token stream*, which FlatSink
* deliberately discards. See
* xo-indentlog2/utest/pretty_struct.test.cpp for the rendered form.
**/
#include <xo/ppsink/pretty_struct.hpp>
#include <xo/ppsink/FlatSink.hpp>
#include <xo/ppsink/pretty_ostream.hpp>
#include "MarkSink.hpp"
#include <catch2/catch.hpp>
#include <sstream>
#include <string>
namespace ut {
using xo::pp::FlatSink;
using xo::pp::field;
using xo::pp::tag;
using std::stringstream;
namespace {
/** counts copies, to pin that field() does not copy its value **/
struct Watched {
Watched() = default;
Watched(const Watched &) { ++n_copy_; }
Watched & operator=(const Watched &) { ++n_copy_; return *this; }
static int n_copy_;
};
int Watched::n_copy_ = 0;
inline std::ostream & operator<<(std::ostream & os, const Watched &) {
os << "W"; return os;
}
} /*namespace*/
TEST_CASE("pretty_struct-flat", "[pretty_struct]") {
stringstream ss;
FlatSink sink(ss.rdbuf());
int a = 1, b = 2;
sink.pretty_struct("P", field("a", a), field("b", b));
sink.complete();
/* note the space before the FIRST field: pretty_struct emits split(1)
* ahead of every field, matching legacy, so the name never abuts.
*/
REQUIRE(ss.str() == "<P :a 1 :b 2>\n");
}
TEST_CASE("pretty_struct-no-fields", "[pretty_struct]") {
stringstream ss;
FlatSink sink(ss.rdbuf());
sink.pretty_struct("P");
sink.complete();
REQUIRE(ss.str() == "<P>\n");
}
TEST_CASE("pretty_struct-token-stream", "[pretty_struct]") {
/* the anti-flattening test. Fails if begin() is dropped, if put(name)
* moves inside the group, if a split between fields goes missing, or
* if ">" escapes the group.
*/
stringstream ss;
MarkSink sink(ss);
int a = 1, b = 2;
sink.pretty_struct("P", field("a", a), field("b", b));
REQUIRE(ss.str() ==
"<P"
"<G>"
"<S 1,0>" "<G 0>:a<S 1,1>1</G>"
"<S 1,0>" "<G 0>:b<S 1,1>2</G>"
">"
"</G>");
}
TEST_CASE("pretty_struct-begin-takes-indent-from-sink", "[pretty_struct]") {
/* "<G>" not "<G 2>": pretty_struct must call the no-arg begin(), so
* the indent comes from the sink's PpConfig and accumulates when
* structs nest. Passing an explicit offset here AND in split() is the
* double-count bug.
*/
stringstream ss;
MarkSink sink(ss);
int a = 1;
sink.pretty_struct("P", field("a", a));
REQUIRE(ss.str().find("<G>") != std::string::npos);
REQUIRE(ss.str().find("<G 2>") == std::string::npos);
}
TEST_CASE("pretty_struct-absent-field-skipped", "[pretty_struct]") {
stringstream ss;
FlatSink sink(ss.rdbuf());
int a = 1, b = 2;
sink.pretty_struct("P", field("a", a), field("b", b, false));
sink.complete();
REQUIRE(ss.str() == "<P :a 1>\n");
}
TEST_CASE("pretty_struct-absent-field-drops-separator", "[pretty_struct]") {
/* an absent field must take its split with it -- otherwise the group
* carries a break opportunity that renders as a stray blank column.
*/
stringstream ss;
MarkSink sink(ss);
int a = 1, b = 2;
sink.pretty_struct("P", field("a", a), field("b", b, false));
std::string s = ss.str();
/* exactly one separator, for the one present field */
std::size_t n = 0;
for (std::size_t i = s.find("<S 1,0>"); i != std::string::npos;
i = s.find("<S 1,0>", i + 1))
{
++n;
}
REQUIRE(n == 1);
}
TEST_CASE("pretty_struct-all-fields-absent", "[pretty_struct]") {
stringstream ss;
FlatSink sink(ss.rdbuf());
int a = 1;
sink.pretty_struct("P", field("a", a, false));
sink.complete();
REQUIRE(ss.str() == "<P>\n");
}
TEST_CASE("pretty_struct-tag-usable-as-field", "[pretty_struct]") {
/* anything renderable is a field; present() is only consulted when the
* field type has one. tag() copies, which is fine for a small value.
*/
stringstream ss;
FlatSink sink(ss.rdbuf());
sink.pretty_struct("P", tag("a", 1));
sink.complete();
REQUIRE(ss.str() == "<P :a 1>\n");
}
TEST_CASE("pretty_struct-field-does-not-copy", "[pretty_struct]") {
/* the reason field() exists alongside tag(): a struct printer names
* members, and copying them on every print would be a real cost for a
* std::string member.
*/
stringstream ss;
FlatSink sink(ss.rdbuf());
Watched w;
Watched::n_copy_ = 0;
sink.pretty_struct("P", field("w", w));
sink.complete();
REQUIRE(Watched::n_copy_ == 0);
REQUIRE(ss.str() == "<P :w W>\n");
}
} /*namespace ut*/
/* end pretty_struct.test.cpp */