rippled
Loading...
Searching...
No Matches
ClosureCounter_test.cpp
1#include <test/jtx/Env.h>
2
3#include <xrpl/beast/unit_test.h>
4#include <xrpl/core/ClosureCounter.h>
5
6#include <atomic>
7#include <chrono>
8#include <thread>
9
10namespace xrpl {
11namespace test {
12
13//------------------------------------------------------------------------------
14
16{
17 // We're only using Env for its Journal. That Journal gives better
18 // coverage in unit tests.
20 beast::Journal j{env_.app().journal("ClosureCounter_test")};
21
22 void
24 {
25 // Build different kinds of ClosureCounters.
26 {
27 // Count closures that return void and take no arguments.
28 ClosureCounter<void> voidCounter;
29 BEAST_EXPECT(voidCounter.count() == 0);
30
31 int evidence = 0;
32 // Make sure voidCounter.wrap works with an rvalue closure.
33 auto wrapped = voidCounter.wrap([&evidence]() { ++evidence; });
34 BEAST_EXPECT(voidCounter.count() == 1);
35 BEAST_EXPECT(evidence == 0);
36 BEAST_EXPECT(wrapped);
37
38 // wrapped() should be callable with no arguments.
39 (*wrapped)();
40 BEAST_EXPECT(evidence == 1);
41 (*wrapped)();
42 BEAST_EXPECT(evidence == 2);
43
44 // Destroying the contents of wrapped should decrement voidCounter.
45 wrapped = std::nullopt;
46 BEAST_EXPECT(voidCounter.count() == 0);
47 }
48 {
49 // Count closures that return void and take one int argument.
51 BEAST_EXPECT(setCounter.count() == 0);
52
53 int evidence = 0;
54 // Make sure setCounter.wrap works with a non-const lvalue closure.
55 auto setInt = [&evidence](int i) { evidence = i; };
56 auto wrapped = setCounter.wrap(setInt);
57
58 BEAST_EXPECT(setCounter.count() == 1);
59 BEAST_EXPECT(evidence == 0);
60 BEAST_EXPECT(wrapped);
61
62 // wrapped() should be callable with one integer argument.
63 (*wrapped)(5);
64 BEAST_EXPECT(evidence == 5);
65 (*wrapped)(11);
66 BEAST_EXPECT(evidence == 11);
67
68 // Destroying the contents of wrapped should decrement setCounter.
69 wrapped = std::nullopt;
70 BEAST_EXPECT(setCounter.count() == 0);
71 }
72 {
73 // Count closures that return int and take two int arguments.
75 BEAST_EXPECT(sumCounter.count() == 0);
76
77 // Make sure sumCounter.wrap works with a const lvalue closure.
78 auto const sum = [](int ii, int jj) { return ii + jj; };
79 auto wrapped = sumCounter.wrap(sum);
80
81 BEAST_EXPECT(sumCounter.count() == 1);
82 BEAST_EXPECT(wrapped);
83
84 // wrapped() should be callable with two integers.
85 BEAST_EXPECT((*wrapped)(5, 2) == 7);
86 BEAST_EXPECT((*wrapped)(2, -8) == -6);
87
88 // Destroying the contents of wrapped should decrement sumCounter.
89 wrapped = std::nullopt;
90 BEAST_EXPECT(sumCounter.count() == 0);
91 }
92 }
93
94 // A class used to test argument passing.
96 {
97 public:
98 int copies = {0};
99 int moves = {0};
101
102 TrackedString() = delete;
103
104 explicit TrackedString(char const* rhs) : str(rhs)
105 {
106 }
107
108 // Copy constructor
109 TrackedString(TrackedString const& rhs) : copies(rhs.copies + 1), moves(rhs.moves), str(rhs.str)
110 {
111 }
112
113 // Move constructor
114 TrackedString(TrackedString&& rhs) noexcept : copies(rhs.copies), moves(rhs.moves + 1), str(std::move(rhs.str))
115 {
116 }
117
118 // Delete copy and move assignment.
120 operator=(TrackedString const& rhs) = delete;
121
122 // String concatenation
124 operator+=(char const* rhs)
125 {
126 str += rhs;
127 return *this;
128 }
129
130 friend TrackedString
131 operator+(TrackedString const& s, char const* rhs)
132 {
133 TrackedString ret{s};
134 ret.str += rhs;
135 return ret;
136 }
137 };
138
139 void
141 {
142 // Make sure a wrapped closure handles rvalue reference arguments
143 // correctly.
144 {
145 // Pass by value.
147 BEAST_EXPECT(strCounter.count() == 0);
148
149 auto wrapped = strCounter.wrap([](TrackedString in) { return in += "!"; });
150
151 BEAST_EXPECT(strCounter.count() == 1);
152 BEAST_EXPECT(wrapped);
153
154 TrackedString const strValue("value");
155 TrackedString const result = (*wrapped)(strValue);
156 BEAST_EXPECT(result.copies == 2);
157 BEAST_EXPECT(result.moves == 1);
158 BEAST_EXPECT(result.str == "value!");
159 BEAST_EXPECT(strValue.str.size() == 5);
160 }
161 {
162 // Use a const lvalue argument.
164 BEAST_EXPECT(strCounter.count() == 0);
165
166 auto wrapped = strCounter.wrap([](TrackedString const& in) { return in + "!"; });
167
168 BEAST_EXPECT(strCounter.count() == 1);
169 BEAST_EXPECT(wrapped);
170
171 TrackedString const strConstLValue("const lvalue");
172 TrackedString const result = (*wrapped)(strConstLValue);
173 BEAST_EXPECT(result.copies == 1);
174 // BEAST_EXPECT (result.moves == ?); // moves VS == 1, gcc == 0
175 BEAST_EXPECT(result.str == "const lvalue!");
176 BEAST_EXPECT(strConstLValue.str.size() == 12);
177 }
178 {
179 // Use a non-const lvalue argument.
181 BEAST_EXPECT(strCounter.count() == 0);
182
183 auto wrapped = strCounter.wrap([](TrackedString& in) { return in += "!"; });
184
185 BEAST_EXPECT(strCounter.count() == 1);
186 BEAST_EXPECT(wrapped);
187
188 TrackedString strLValue("lvalue");
189 TrackedString const result = (*wrapped)(strLValue);
190 BEAST_EXPECT(result.copies == 1);
191 BEAST_EXPECT(result.moves == 0);
192 BEAST_EXPECT(result.str == "lvalue!");
193 BEAST_EXPECT(strLValue.str == result.str);
194 }
195 {
196 // Use an rvalue argument.
198 BEAST_EXPECT(strCounter.count() == 0);
199
200 auto wrapped = strCounter.wrap([](TrackedString&& in) {
201 // Note that none of the compilers noticed that in was
202 // leaving scope. So, without intervention, they would
203 // do a copy for the return (June 2017). An explicit
204 // std::move() was required.
205 return std::move(in += "!");
206 });
207
208 BEAST_EXPECT(strCounter.count() == 1);
209 BEAST_EXPECT(wrapped);
210
211 // Make the string big enough to (probably) avoid the small string
212 // optimization.
213 TrackedString strRValue("rvalue abcdefghijklmnopqrstuvwxyz");
214 TrackedString const result = (*wrapped)(std::move(strRValue));
215 BEAST_EXPECT(result.copies == 0);
216 BEAST_EXPECT(result.moves == 1);
217 BEAST_EXPECT(result.str == "rvalue abcdefghijklmnopqrstuvwxyz!");
218 BEAST_EXPECT(strRValue.str.size() == 0);
219 }
220 }
221
222 void
224 {
225 // Verify reference counting.
226 ClosureCounter<void> voidCounter;
227 BEAST_EXPECT(voidCounter.count() == 0);
228 {
229 auto wrapped1 = voidCounter.wrap([]() {});
230 BEAST_EXPECT(voidCounter.count() == 1);
231 {
232 // Copy should increase reference count.
233 auto wrapped2(wrapped1);
234 BEAST_EXPECT(voidCounter.count() == 2);
235 {
236 // Move should increase reference count.
237 auto wrapped3(std::move(wrapped2));
238 BEAST_EXPECT(voidCounter.count() == 3);
239 {
240 // An additional closure also increases count.
241 auto wrapped4 = voidCounter.wrap([]() {});
242 BEAST_EXPECT(voidCounter.count() == 4);
243 }
244 BEAST_EXPECT(voidCounter.count() == 3);
245 }
246 BEAST_EXPECT(voidCounter.count() == 2);
247 }
248 BEAST_EXPECT(voidCounter.count() == 1);
249 }
250 BEAST_EXPECT(voidCounter.count() == 0);
251
252 // Join with 0 count should not stall.
253 using namespace std::chrono_literals;
254 voidCounter.join("testWrap", 1ms, j);
255
256 // Wrapping a closure after join() should return std::nullopt.
257 BEAST_EXPECT(voidCounter.wrap([]() {}) == std::nullopt);
258 }
259
260 void
262 {
263 // Verify reference counting.
264 ClosureCounter<void> voidCounter;
265 BEAST_EXPECT(voidCounter.count() == 0);
266
267 auto wrapped = (voidCounter.wrap([]() {}));
268 BEAST_EXPECT(voidCounter.count() == 1);
269
270 // Calling join() now should stall, so do it on a different thread.
271 std::atomic<bool> threadExited{false};
272 std::thread localThread([&voidCounter, &threadExited, this]() {
273 // Should stall after calling join.
274 using namespace std::chrono_literals;
275 voidCounter.join("testWaitOnJoin", 1ms, j);
276 threadExited.store(true);
277 });
278
279 // Wait for the thread to call voidCounter.join().
280 while (!voidCounter.joined())
281 ;
282
283 // The thread should still be active after waiting 5 milliseconds.
284 // This is not a guarantee that join() stalled the thread, but it
285 // improves confidence.
286 using namespace std::chrono_literals;
288 BEAST_EXPECT(threadExited == false);
289
290 // Destroy the contents of wrapped and expect the thread to exit
291 // (asynchronously).
292 wrapped = std::nullopt;
293 BEAST_EXPECT(voidCounter.count() == 0);
294
295 // Wait for the thread to exit.
296 while (threadExited == false)
297 ;
298 localThread.join();
299 }
300
301public:
302 void
303 run() override
304 {
306 testArgs();
307 testWrap();
309 }
310};
311
312BEAST_DEFINE_TESTSUITE(ClosureCounter, core, xrpl);
313
314} // namespace test
315} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:41
A testsuite class.
Definition suite.h:52
virtual beast::Journal journal(std::string const &name)=0
The role of a ClosureCounter is to assist in shutdown by letting callers wait for the completion of c...
bool joined() const
Returns true if this has been joined.
std::optional< Substitute< Closure > > wrap(Closure &&closure)
Wrap the passed closure with a reference counter.
int count() const
Current number of Closures outstanding.
void join(char const *name, std::chrono::milliseconds wait, beast::Journal j)
Returns once all counted in-flight closures are destroyed.
friend TrackedString operator+(TrackedString const &s, char const *rhs)
TrackedString & operator=(TrackedString const &rhs)=delete
void run() override
Runs the suite.
A transaction testing environment.
Definition Env.h:98
Application & app()
Definition Env.h:230
T is_same_v
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:35
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
static auto sum(TCollection const &col)
Definition BookStep.cpp:872
T size(T... args)
T sleep_for(T... args)