xo-umbrella2/xo-arena/utest/span.test.cpp

93 lines
3.1 KiB
C++

/** @file span.test.cpp
*
* @author Roland Conybeare, Aug 2026
*
* Pins span's std::ranges contract. The static_asserts prove the concepts
* are satisfied; the runtime cases prove the iterators actually walk the
* buffer -- a begin()/end() pair that both returned lo_ would satisfy every
* concept here and still be wrong.
**/
#include "span.hpp"
#include <catch2/catch.hpp>
#include <algorithm>
#include <numeric>
#include <ranges>
#include <string_view>
namespace xo {
using xo::mm::span;
namespace ut {
/* concept conformance. contiguous_range is what
* xo::pp::hex_view (and any generic range algorithm) constrains on.
*/
static_assert(std::ranges::range<span<char>>);
static_assert(std::ranges::sized_range<span<char>>);
static_assert(std::ranges::contiguous_range<span<char>>);
static_assert(std::ranges::borrowed_range<span<char>>);
static_assert(std::contiguous_iterator<std::ranges::iterator_t<span<char>>>);
static_assert(std::same_as<std::ranges::range_value_t<span<char>>, char>);
/* also for the byte-ish element types a hex dump would use */
static_assert(std::ranges::contiguous_range<span<const char>>);
static_assert(std::ranges::contiguous_range<span<std::uint8_t>>);
TEST_CASE("span-range-walk", "[arena][span][ranges]")
{
char buf[] = "abcde";
/* -1: exclude the trailing NUL */
span<char> s(buf, buf + sizeof(buf) - 1);
REQUIRE(s.size() == 5);
SECTION("ranged-for visits every element in order") {
std::string seen;
for (char c : s)
seen.push_back(c);
REQUIRE(seen == "abcde");
}
SECTION("begin/end/data agree with lo/hi") {
REQUIRE(s.begin() == s.lo());
REQUIRE(s.end() == s.hi());
REQUIRE(s.data() == s.lo());
REQUIRE(s.end() - s.begin() == static_cast<std::ptrdiff_t>(s.size()));
}
SECTION("std::ranges algorithms accept a span") {
REQUIRE(std::ranges::find(s, 'c') == s.begin() + 2);
REQUIRE(std::ranges::count(s, 'a') == 1);
}
SECTION("empty span yields no elements") {
span<char> e;
REQUIRE(e.empty());
REQUIRE(std::ranges::distance(e) == 0);
}
}
TEST_CASE("span-borrowed-range", "[arena][span][ranges]")
{
char buf[] = "xyz";
/* the point of enable_borrowed_range: begin() on an *rvalue* span
* returns a real iterator, not std::ranges::dangling. Without the
* specialization this fails to compile.
*/
auto make = [&buf]() { return span<char>(buf, buf + 3); };
auto it = std::ranges::begin(make());
static_assert(!std::same_as<decltype(it), std::ranges::dangling>);
REQUIRE(*it == 'x');
REQUIRE(std::ranges::find(make(), 'z') != std::ranges::end(make()));
}
} /*namespace ut*/
} /*namespace xo*/
/* end span.test.cpp */