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