Skip to content

Commit 51cdc10

Browse files
committed
Stringify lipstick on a pig
1 parent 47ee308 commit 51cdc10

2 files changed

Lines changed: 119 additions & 34 deletions

File tree

src/test/util/common.h

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,19 @@
55
#ifndef BITCOIN_TEST_UTIL_COMMON_H
66
#define BITCOIN_TEST_UTIL_COMMON_H
77

8+
#include <crypto/hex_base.h>
9+
#include <key.h>
10+
#include <netaddress.h>
11+
#include <protocol.h>
12+
#include <pubkey.h>
13+
#include <streams.h>
14+
815
#include <chrono>
916
#include <optional>
1017
#include <ostream>
18+
#include <ranges>
1119
#include <string>
20+
#include <variant>
1221

1322
/**
1423
* BOOST_CHECK_EXCEPTION predicates to check the specific validation error.
@@ -49,14 +58,63 @@ inline std::ostream& operator<<(std::ostream& os, const std::optional<T>& v)
4958
} // namespace std
5059

5160
template <typename T>
52-
concept HasToString = requires(const T& t) { t.ToString(); };
61+
concept has_to_string = requires(const T& t) { t.ToString(); };
5362

54-
template <HasToString T>
63+
template <has_to_string T>
5564
inline std::ostream& operator<<(std::ostream& os, const T& obj)
5665
{
5766
return os << obj.ToString();
5867
}
5968

69+
template <typename T>
70+
concept has_hex_str = requires(const T& v) { { HexStr(v) } -> std::same_as<std::string>; };
71+
72+
template <typename T>
73+
concept has_serialize = requires(const T& v, DataStream& s) { v.Serialize(s); };
74+
75+
template <typename T>
76+
concept pointer =
77+
std::is_pointer_v<T> &&
78+
!std::same_as<std::decay_t<T>, char*> &&
79+
!std::same_as<std::decay_t<T>, const char*>;
80+
81+
template <typename T>
82+
concept pair_like = requires(const T& v) {
83+
v.first;
84+
v.second;
85+
};
86+
87+
template <typename T>
88+
concept non_string_range = std::ranges::range<T> && !std::is_convertible_v<T, std::string_view>;
89+
90+
template <typename T>
91+
concept is_variant = requires { std::variant_size<T>::value; };
92+
93+
94+
inline std::ostream& operator<<(std::ostream& os, const CService& s)
95+
{
96+
return os << s.ToStringAddrPort();
97+
}
98+
99+
inline std::ostream& operator<<(std::ostream& os, const CAddress& a)
100+
{
101+
return os << a.ToStringAddrPort();
102+
}
103+
104+
inline std::ostream& operator<<(std::ostream& os, const CExtKey& k)
105+
{
106+
unsigned char code[BIP32_EXTKEY_SIZE];
107+
k.Encode(code);
108+
return os << HexStr(code);
109+
}
110+
111+
inline std::ostream& operator<<(std::ostream& os, const CExtPubKey& k)
112+
{
113+
unsigned char code[BIP32_EXTKEY_SIZE];
114+
k.Encode(code);
115+
return os << HexStr(code);
116+
}
117+
60118
// @}
61119

62120
#endif // BITCOIN_TEST_UTIL_COMMON_H

src/test/util/framework.h

Lines changed: 59 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
#define BITCOIN_TEST_UTIL_FRAMEWORK_H
66
#pragma once
77

8+
#include <crypto/hex_base.h>
9+
#include <test/util/common.h>
810
#include <tinyformat.h>
911

1012
#include <algorithm>
@@ -113,31 +115,36 @@ inline void log(LogLevel level, const char* fmt, Args&&... args)
113115
}
114116
}
115117

116-
/** Types that implement the `<<` operator. Pointers are excluded: operator<< for
117-
* char-like pointers calls strlen on the pointee, which is unsafe for raw memory. */
118118
template <typename T>
119-
concept streamable = !std::is_pointer_v<T> && requires(std::ostream& os, const T& value) { os << value; };
120-
121-
/** Types that implement a `ToString()` member returning `std::string`. */
122-
template <typename T>
123-
concept has_to_string = requires(const T& value) {
124-
{ value.ToString() } -> std::same_as<std::string>;
125-
};
119+
concept streamable = requires(std::ostream& os, const T& value) { os << value; };
126120

127121
template <typename T>
128122
concept is_cstring = std::is_same_v<std::decay_t<T>, const char*> || std::is_same_v<std::decay_t<T>, char*>;
129123

130124
template <typename T>
131125
std::string stringify(const T& value)
132126
{
133-
if constexpr (streamable<T>) {
127+
if constexpr (pointer<T>) {
128+
if (!value) return "nullptr";
129+
std::ostringstream os;
130+
os << static_cast<const void*>(value);
131+
return os.str();
132+
} else if constexpr (streamable<T>) {
134133
std::ostringstream os;
135134
os << value;
136135
return os.str();
137-
} else if constexpr (has_to_string<T>) {
138-
return value.ToString();
136+
} else if constexpr (has_hex_str<T>) {
137+
return HexStr(value);
138+
} else if constexpr (has_serialize<T>) {
139+
DataStream ds;
140+
ds << value;
141+
return HexStr(ds);
142+
} else if constexpr (pair_like<T>) {
143+
return "(" + stringify(value.first) + ", " + stringify(value.second) + ")";
144+
} else if constexpr (is_variant<T>) {
145+
return std::visit([](const auto& v) { return stringify(v); }, value);
139146
} else {
140-
return typeid(T).name();
147+
static_assert(sizeof(T) == 0, "No stringify() for this type.");
141148
}
142149
}
143150

@@ -258,6 +265,10 @@ inline std::string current_test_full_name()
258265
}
259266

260267

268+
template <std::ranges::range R1, std::ranges::range R2>
269+
requires std::equality_comparable_with<std::ranges::range_value_t<R1>, std::ranges::range_value_t<R2>>
270+
Result check_equal_ranges(const R1& r1, const R2& r2);
271+
261272
/** A captured expression is used to stringify the result before evaluation.
262273
* When an expression evaluates to `false`, the string is reported to the user. */
263274
template <typename T>
@@ -280,16 +291,26 @@ struct CapturedExpression {
280291
template <typename U> \
281292
Result operator op(const U& rhs) const \
282293
{ \
283-
bool btc_test_result; \
284-
if constexpr (is_cstring<T> && is_cstring<U>) { \
285-
btc_test_result = (std::strcmp(lhs, rhs) op 0); \
286-
} else if constexpr (std::is_integral_v<T> && std::is_integral_v<U> && \
287-
std::is_signed_v<T> != std::is_signed_v<U>) { \
288-
btc_test_result = cmp(lhs, rhs); \
294+
if constexpr (((#op)[0] == '=' || (#op)[0] == '!') && \
295+
non_string_range<T> && non_string_range<U>) { \
296+
if constexpr ((#op)[0] == '=') { \
297+
return check_equal_ranges(lhs, rhs); \
298+
} else { \
299+
return Result::failed("!= on ranges is not supported"); \
300+
} \
289301
} else { \
290-
btc_test_result = static_cast<bool>(lhs op rhs); \
302+
bool btc_test_result; \
303+
if constexpr (is_cstring<T> && is_cstring<U>) { \
304+
btc_test_result = (std::strcmp(lhs, rhs) op 0); \
305+
} else if constexpr (std::is_integral_v<T> && std::is_integral_v<U> && \
306+
std::is_signed_v<T> != std::is_signed_v<U>) { \
307+
btc_test_result = cmp(lhs, rhs); \
308+
} else { \
309+
btc_test_result = static_cast<bool>(lhs op rhs); \
310+
} \
311+
return btc_test_result ? Result::ok() \
312+
: Result::failed(stringify(lhs) + " " #op " " + stringify(rhs)); \
291313
} \
292-
return btc_test_result ? Result::ok() : Result::failed(stringify(lhs) + " " #op " " + stringify(rhs)); \
293314
}
294315

295316
DECOMPOSE_OP(==, std::cmp_equal)
@@ -802,16 +823,6 @@ using btc_suite_fixture = ::framework::EmptyFixture;
802823
#expr, __FILE__, __LINE__); \
803824
} while (false)
804825

805-
/** Passes if two ranges contain equal elements. Accepts any two types that
806-
* satisfy std::ranges::range with comparable element types. */
807-
#define CHECK_EQUAL_RANGES(a, b) \
808-
do { \
809-
auto& btc_test_ctx_{::framework::test_context()}; \
810-
::framework::Result btc_test_res_{::framework::check_equal_ranges((a), (b))}; \
811-
::framework::record_check(btc_test_ctx_, btc_test_res_, "CHECK_EQUAL_RANGES", \
812-
#a " == " #b, __FILE__, __LINE__); \
813-
} while (false)
814-
815826
/** Unconditionally records a failure with `msg`. Does not abort the test. */
816827
#define RECORD_ERROR(msg) \
817828
do { \
@@ -845,6 +856,22 @@ using btc_suite_fixture = ::framework::EmptyFixture;
845856
} \
846857
} while (false)
847858

859+
/** Like CHECK, but the expression is evaluated as a plain bool: no
860+
* decomposition, no stringify of operands. On failure only the expression
861+
* text is reported. Use when either operand of `==` / `!=` lacks a
862+
* stringify path. */
863+
#define CHECK_NO_DISPLAY(expr, ...) \
864+
do { \
865+
auto& btc_test_ctx_{::framework::test_context()}; \
866+
::framework::Result btc_test_res_{static_cast<bool>(expr) ? ::framework::Result::ok() \
867+
: ::framework::Result::failed({})}; \
868+
std::ostringstream btc_test_os_; \
869+
__VA_OPT__(if (!btc_test_res_.is_ok()) btc_test_os_ << __VA_ARGS__;) \
870+
::framework::record_check(btc_test_ctx_, btc_test_res_, "CHECK_NO_DISPLAY", #expr, __FILE__, \
871+
__LINE__, btc_test_res_.is_ok() ? std::string{} : btc_test_os_.str()); \
872+
} while (false)
873+
874+
#define BOOST_CHECK_NO_DISPLAY(expr) CHECK_NO_DISPLAY(expr)
848875
#define BOOST_CHECK(expr) CHECK(expr)
849876
#define BOOST_REQUIRE(expr) REQUIRE(expr)
850877
#define BOOST_TEST(expr) CHECK(expr)
@@ -871,7 +898,7 @@ using btc_suite_fixture = ::framework::EmptyFixture;
871898
#define BOOST_TEST_MESSAGE(msg) TEST_MESSAGE(msg)
872899
#define BOOST_WARN_MESSAGE(expr, msg) WARN_MESSAGE(expr, msg)
873900
#define BOOST_CHECK_EQUAL_COLLECTIONS(ab, ae, bb, be) \
874-
CHECK_EQUAL_RANGES(std::ranges::subrange((ab), (ae)), std::ranges::subrange((bb), (be)))
901+
CHECK(std::ranges::subrange((ab), (ae)) == std::ranges::subrange((bb), (be)))
875902
#define BOOST_AUTO_TEST_CASE(name) FIXTURE_TEST_CASE(name, btc_suite_fixture)
876903
#define BOOST_FIXTURE_TEST_CASE(name, F) \
877904
using name = F; \

0 commit comments

Comments
 (0)