xo-umbrella2/xo-ppsink/utest/concat.test.cpp
Roland Conybeare 74ea52af71
Some checks failed
cmake-docker / cmake-build (clang, clang++) (push) Successful in 24m59s
cmake-docker / cmake-build (gcc, g++) (push) Successful in 25m21s
CI / smoke-test (push) Failing after 4m25s
refactor: split pp-sink tostr0 / indentlog2 tostr [WIP] [REFACTOR]
2026-08-15 11:00:06 -04:00

140 lines
4.4 KiB
C++

/** @file concat.test.cpp
*
* exercise concat() from xo/ppsink/concat.hpp.
*
* Two things worth pinning beyond "the text comes out right":
*
* - concat emits NO structure of its own (no begin/split/end), so it is safe
* in a position that must stay one unbreakable token -- e.g. a field name.
* Checked against the token stream, where FlatSink would hide it.
* - it does not copy its arguments; that is the reason it exists.
**/
#include <xo/ppsink/concat.hpp>
#include <xo/ppsink/FlatSink.hpp>
#include <xo/ppsink/tostr0.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::concat;
using std::stringstream;
namespace {
template <typename Fn>
std::string flat_of(Fn && fn) {
stringstream ss; FlatSink sink(ss.rdbuf()); fn(sink); return ss.str();
}
template <typename Fn>
std::string marks_of(Fn && fn) {
stringstream ss; MarkSink sink(ss); fn(sink); return ss.str();
}
/** counts copies, to pin that concat captures by reference **/
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("concat-basic", "[concat]") {
const int i = 7;
REQUIRE(flat_of([&i](xo::pp::PpSink & sink) {
sink.pp(concat("[", i, "]"));
}) == "[7]");
}
TEST_CASE("concat-single-and-many", "[concat]") {
const int a = 1, b = 2, c = 3;
REQUIRE(flat_of([&a](xo::pp::PpSink & sink) { sink.pp(concat(a)); }) == "1");
REQUIRE(flat_of([&a,&b,&c](xo::pp::PpSink & sink) {
sink.pp(concat("<", a, ",", b, ",", c, ">"));
}) == "<1,2,3>");
}
/* the reason it exists: byte-identical to the tostr() idiom it replaces,
* without building a std::string
*/
TEST_CASE("concat-matches-tostr", "[concat]") {
for (int i : {0, 7, 42, 1234}) {
std::string via_concat = flat_of([&i](xo::pp::PpSink & sink) {
sink.pp(concat("[", i, "]"));
});
REQUIRE(via_concat == xo::pp::tostr0("[", i, "]"));
}
}
/* concat must contribute no group structure of its own: it has to be safe
* where the output must stay one unbreakable token. FlatSink cannot show
* this -- its begin/split/end are no-ops -- so check the token stream.
*/
TEST_CASE("concat-emits-no-structure", "[concat]") {
const int i = 7;
std::string marks = marks_of([&i](xo::pp::PpSink & sink) {
sink.pp(concat("[", i, "]"));
});
REQUIRE(marks == "[7]");
REQUIRE(marks.find("<G") == std::string::npos);
REQUIRE(marks.find("<S ") == std::string::npos);
REQUIRE(marks.find("<N ") == std::string::npos);
}
TEST_CASE("concat-does-not-copy", "[concat]") {
Watched w;
const int before = Watched::n_copy_;
REQUIRE(flat_of([&w](xo::pp::PpSink & sink) {
sink.pp(concat("(", w, ")"));
}) == "(W)");
REQUIRE(Watched::n_copy_ == before);
}
/* a prvalue argument lives to the end of the full-expression, so building
* concat in the expression that consumes it is safe
*/
TEST_CASE("concat-prvalue-argument", "[concat]") {
const int i = 20;
REQUIRE(flat_of([&i](xo::pp::PpSink & sink) {
sink.pp(concat("[", i + 1, "]"));
}) == "[21]");
}
TEST_CASE("concat-nested", "[concat]") {
const int a = 1, b = 2;
REQUIRE(flat_of([&a,&b](xo::pp::PpSink & sink) {
sink.pp(concat("<", concat(a, ":", b), ">"));
}) == "<1:2>");
}
/* std::string and string_view arguments render as their text, not through
* any quoting path
*/
TEST_CASE("concat-string-arguments", "[concat]") {
const std::string s = "abc";
const std::string_view sv = "def";
REQUIRE(flat_of([&s,&sv](xo::pp::PpSink & sink) {
sink.pp(concat(s, "-", sv));
}) == "abc-def");
}
} /*namespace ut*/
/* end concat.test.cpp */