Skip to content

Commit fd39335

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

2 files changed

Lines changed: 94 additions & 23 deletions

File tree

src/test/util/common.h

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@
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>
@@ -49,14 +56,57 @@ inline std::ostream& operator<<(std::ostream& os, const std::optional<T>& v)
4956
} // namespace std
5057

5158
template <typename T>
52-
concept HasToString = requires(const T& t) { t.ToString(); };
59+
concept has_to_string = requires(const T& t) { t.ToString(); };
5360

54-
template <HasToString T>
61+
template <has_to_string T>
5562
inline std::ostream& operator<<(std::ostream& os, const T& obj)
5663
{
5764
return os << obj.ToString();
5865
}
5966

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

62112
#endif // BITCOIN_TEST_UTIL_COMMON_H

src/test/util/framework.h

Lines changed: 42 additions & 21 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,34 @@ 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) + ")";
139144
} else {
140-
return typeid(T).name();
145+
static_assert(sizeof(T) == 0, "No stringify() for this type.");
141146
}
142147
}
143148

@@ -258,6 +263,10 @@ inline std::string current_test_full_name()
258263
}
259264

260265

266+
template <std::ranges::range R1, std::ranges::range R2>
267+
requires std::equality_comparable_with<std::ranges::range_value_t<R1>, std::ranges::range_value_t<R2>>
268+
Result check_equal_ranges(const R1& r1, const R2& r2);
269+
261270
/** A captured expression is used to stringify the result before evaluation.
262271
* When an expression evaluates to `false`, the string is reported to the user. */
263272
template <typename T>
@@ -280,16 +289,28 @@ struct CapturedExpression {
280289
template <typename U> \
281290
Result operator op(const U& rhs) const \
282291
{ \
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); \
292+
if constexpr (((#op)[0] == '=' || (#op)[0] == '!') && \
293+
std::ranges::range<T> && std::ranges::range<U> && \
294+
!std::is_convertible_v<T, std::string_view> && \
295+
!std::is_convertible_v<U, std::string_view>) { \
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)

0 commit comments

Comments
 (0)