Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/v/kafka/protocol/tests/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,15 @@ redpanda_cc_btest(
"field_parser_test.cc",
],
deps = [
"//src/v/base",
"//src/v/kafka/protocol",
"//src/v/random:generators",
"//src/v/test_utils:container_ostream",
"//src/v/test_utils:random_bytes",
"//src/v/test_utils:seastar_boost",
"//src/v/utils:base64",
"@boost//:iterator",
"@fmt",
"@seastar",
"@seastar//:testing",
],
Expand Down
6 changes: 3 additions & 3 deletions src/v/kafka/protocol/tests/field_parser_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* by the Apache License, Version 2.0
*/

#include "base/format_to.h"
#include "kafka/protocol/types.h"
#include "kafka/protocol/wire.h"
#include "random/generators.h"
Expand Down Expand Up @@ -79,9 +80,8 @@ struct test_struct {
return a.field_a == b.field_a && a.field_b == b.field_b;
}

friend std::ostream& operator<<(std::ostream& os, const test_struct& ts) {
os << "field_a: " << ts.field_a << " field_b: " << ts.field_b;
return os;
fmt::iterator format_to(fmt::iterator it) const {
return fmt::format_to(it, "field_a: {} field_b: {}", field_a, field_b);
}
};

Expand Down
2 changes: 2 additions & 0 deletions src/v/pandaproxy/parsing/test/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ redpanda_cc_btest(
"httpd.cc",
],
deps = [
"//src/v/base",
"//src/v/pandaproxy:json",
"//src/v/pandaproxy:parsing",
"//src/v/reflection:type_traits",
Expand All @@ -35,6 +36,7 @@ redpanda_cc_btest(
"@boost//:test",
"@boost//:tuple",
"@boost//:utility",
"@fmt",
"@seastar",
"@seastar//:testing",
],
Expand Down
5 changes: 3 additions & 2 deletions src/v/pandaproxy/parsing/test/httpd.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#include "pandaproxy/parsing/httpd.h"

#include "base/format_to.h"
#include "pandaproxy/json/types.h"
#include "test_utils/container_ostream.h" // IWYU pragma: keep

Expand All @@ -24,8 +25,8 @@ namespace pp = pandaproxy;
namespace ppj = pp::json;

namespace pandaproxy::json {
std::ostream& operator<<(std::ostream& os, serialization_format fmt) {
return os << name(fmt);
fmt::iterator format_to(serialization_format fmt, fmt::iterator it) {
return fmt::format_to(it, "{}", name(fmt));
}
} // namespace pandaproxy::json

Expand Down
3 changes: 3 additions & 0 deletions src/v/test_utils/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ redpanda_test_cc_library(
"container_ostream.h",
],
visibility = ["//visibility:public"],
deps = [
"@fmt",
],
)

redpanda_test_cc_library(
Expand Down
37 changes: 13 additions & 24 deletions src/v/test_utils/container_ostream.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,15 @@
/// rely on streaming containers, which is why the set is limited to those
/// two types rather than every standard container.
///
/// The overloads stream each element via operator<< so they work for any
/// element type that has streaming, regardless of whether it has a fmt
/// formatter. Include this only in test translation units to avoid
/// pulling a std-namespace overload into production code.
/// The overloads delegate to fmt's range formatter, so the element type
/// must be fmt-formattable (has a fmt::formatter specialization or a
/// format_to method/free function). Include this only in test
/// translation units to avoid pulling a std-namespace overload into
/// production code.

#include <fmt/format.h>
#include <fmt/ostream.h>
#include <fmt/ranges.h>

#include <ostream>
#include <unordered_map>
Expand All @@ -37,32 +42,16 @@ namespace std {
template<typename T, typename Alloc>
// NOLINTNEXTLINE(cert-dcl58-cpp): test-only operator<< overload for std types
ostream& operator<<(ostream& os, const vector<T, Alloc>& v) {
os << "{";
bool first = true;
for (const auto& e : v) {
if (!first) {
os << ", ";
}
first = false;
os << e;
}
return os << "}";
fmt::print(os, "{}", v);
return os;
}

template<typename K, typename V, typename Hash, typename Eq, typename Alloc>
// NOLINTNEXTLINE(cert-dcl58-cpp): test-only operator<< overload for std types
ostream&
operator<<(ostream& os, const unordered_map<K, V, Hash, Eq, Alloc>& m) {
os << "{";
bool first = true;
for (const auto& [k, v] : m) {
if (!first) {
os << ", ";
}
first = false;
os << "{" << k << " -> " << v << "}";
}
return os << "}";
fmt::print(os, "{}", m);
return os;
}

} // namespace std
32 changes: 7 additions & 25 deletions src/v/test_utils/tests/container_ostream_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,40 +25,22 @@ std::string stream(const T& v) {
return std::move(os).str();
}

} // namespace

// An element type that has operator<< but no fmt formatter, to verify the
// header does not require fmt-formattability. Defined at file scope so the
// streaming operator below can be a non-friend free function reachable via
// ADL from the test below.
struct only_stream {
int x;
};
inline std::ostream& operator<<(std::ostream& os, const only_stream& v) {
return os << "x=" << v.x;
}

namespace {

TEST(ContainerOstream, ElementOnlyHasOperatorStream) {
EXPECT_EQ(stream(std::vector<only_stream>{{1}, {2}}), "{x=1, x=2}");
}

TEST(ContainerOstream, EmptyVector) {
EXPECT_EQ(stream(std::vector<int>{}), "{}");
EXPECT_EQ(stream(std::vector<int>{}), "[]");
}

TEST(ContainerOstream, VectorOfInts) {
EXPECT_EQ(stream(std::vector<int>{1, 2, 3}), "{1, 2, 3}");
EXPECT_EQ(stream(std::vector<int>{1, 2, 3}), "[1, 2, 3]");
}

TEST(ContainerOstream, VectorOfStrings) {
EXPECT_EQ(stream(std::vector<std::string>{"a", "b"}), "{a, b}");
// fmt's range formatter wraps strings in quotes by default.
EXPECT_EQ(stream(std::vector<std::string>{"a", "b"}), "[\"a\", \"b\"]");
}

TEST(ContainerOstream, NestedVector) {
EXPECT_EQ(
stream(std::vector<std::vector<int>>{{1, 2}, {3}}), "{{1, 2}, {3}}");
stream(std::vector<std::vector<int>>{{1, 2}, {3}}), "[[1, 2], [3]]");
}

TEST(ContainerOstream, EmptyUnorderedMap) {
Expand All @@ -69,15 +51,15 @@ TEST(ContainerOstream, UnorderedMapSingleEntry) {
// Unordered iteration order is implementation-defined; pin the test to
// one entry so it stays deterministic.
EXPECT_EQ(
stream(std::unordered_map<int, std::string>{{1, "one"}}), "{{1 -> one}}");
stream(std::unordered_map<int, std::string>{{1, "one"}}), "{1: \"one\"}");
}

TEST(ContainerOstream, GTestStreamingMessage) {
// Smoke test the actual scenario this header exists for: chaining a
// container into a googletest assertion message.
std::vector<int> v{42};
testing::AssertionResult r = testing::AssertionFailure() << v;
EXPECT_EQ(std::string{r.message()}, "{42}");
EXPECT_EQ(std::string{r.message()}, "[42]");
}

} // namespace
Loading