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