rippled
ClosureCounter_test.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2017 Ripple Labs Inc.
5 
6  Permission to use, copy, modify, and/or distribute this software for any
7  purpose with or without fee is hereby granted, provided that the above
8  copyright notice and this permission notice appear in all copies.
9 
10  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 //==============================================================================
19 
20 #include <ripple/beast/unit_test.h>
21 #include <ripple/core/ClosureCounter.h>
22 #include <atomic>
23 #include <chrono>
24 #include <test/jtx/Env.h>
25 #include <thread>
26 
27 namespace ripple {
28 namespace test {
29 
30 //------------------------------------------------------------------------------
31 
32 class ClosureCounter_test : public beast::unit_test::suite
33 {
34  // We're only using Env for its Journal.
35  jtx::Env env{*this};
36  beast::Journal j{env.app().journal("ClosureCounter_test")};
37 
38  void
40  {
41  // Build different kinds of ClosureCounters.
42  {
43  // Count closures that return void and take no arguments.
44  ClosureCounter<void> voidCounter;
45  BEAST_EXPECT(voidCounter.count() == 0);
46 
47  int evidence = 0;
48  // Make sure voidCounter.wrap works with an rvalue closure.
49  auto wrapped = voidCounter.wrap([&evidence]() { ++evidence; });
50  BEAST_EXPECT(voidCounter.count() == 1);
51  BEAST_EXPECT(evidence == 0);
52  BEAST_EXPECT(wrapped);
53 
54  // wrapped() should be callable with no arguments.
55  (*wrapped)();
56  BEAST_EXPECT(evidence == 1);
57  (*wrapped)();
58  BEAST_EXPECT(evidence == 2);
59 
60  // Destroying the contents of wrapped should decrement voidCounter.
61  wrapped = std::nullopt;
62  BEAST_EXPECT(voidCounter.count() == 0);
63  }
64  {
65  // Count closures that return void and take one int argument.
66  ClosureCounter<void, int> setCounter;
67  BEAST_EXPECT(setCounter.count() == 0);
68 
69  int evidence = 0;
70  // Make sure setCounter.wrap works with a non-const lvalue closure.
71  auto setInt = [&evidence](int i) { evidence = i; };
72  auto wrapped = setCounter.wrap(setInt);
73 
74  BEAST_EXPECT(setCounter.count() == 1);
75  BEAST_EXPECT(evidence == 0);
76  BEAST_EXPECT(wrapped);
77 
78  // wrapped() should be callable with one integer argument.
79  (*wrapped)(5);
80  BEAST_EXPECT(evidence == 5);
81  (*wrapped)(11);
82  BEAST_EXPECT(evidence == 11);
83 
84  // Destroying the contents of wrapped should decrement setCounter.
85  wrapped = std::nullopt;
86  BEAST_EXPECT(setCounter.count() == 0);
87  }
88  {
89  // Count closures that return int and take two int arguments.
91  BEAST_EXPECT(sumCounter.count() == 0);
92 
93  // Make sure sumCounter.wrap works with a const lvalue closure.
94  auto const sum = [](int ii, int jj) { return ii + jj; };
95  auto wrapped = sumCounter.wrap(sum);
96 
97  BEAST_EXPECT(sumCounter.count() == 1);
98  BEAST_EXPECT(wrapped);
99 
100  // wrapped() should be callable with two integers.
101  BEAST_EXPECT((*wrapped)(5, 2) == 7);
102  BEAST_EXPECT((*wrapped)(2, -8) == -6);
103 
104  // Destroying the contents of wrapped should decrement sumCounter.
105  wrapped = std::nullopt;
106  BEAST_EXPECT(sumCounter.count() == 0);
107  }
108  }
109 
110  // A class used to test argument passing.
112  {
113  public:
114  int copies = {0};
115  int moves = {0};
117 
118  TrackedString() = delete;
119 
120  explicit TrackedString(char const* rhs) : str(rhs)
121  {
122  }
123 
124  // Copy constructor
126  : copies(rhs.copies + 1), moves(rhs.moves), str(rhs.str)
127  {
128  }
129 
130  // Move constructor
131  TrackedString(TrackedString&& rhs) noexcept
132  : copies(rhs.copies), moves(rhs.moves + 1), str(std::move(rhs.str))
133  {
134  }
135 
136  // Delete copy and move assignment.
138  operator=(TrackedString const& rhs) = delete;
139 
140  // String concatenation
142  operator+=(char const* rhs)
143  {
144  str += rhs;
145  return *this;
146  }
147 
148  friend TrackedString
149  operator+(TrackedString const& s, char const* rhs)
150  {
151  TrackedString ret{s};
152  ret.str += rhs;
153  return ret;
154  }
155  };
156 
157  void
159  {
160  // Make sure a wrapped closure handles rvalue reference arguments
161  // correctly.
162  {
163  // Pass by value.
165  BEAST_EXPECT(strCounter.count() == 0);
166 
167  auto wrapped =
168  strCounter.wrap([](TrackedString in) { return in += "!"; });
169 
170  BEAST_EXPECT(strCounter.count() == 1);
171  BEAST_EXPECT(wrapped);
172 
173  TrackedString const strValue("value");
174  TrackedString const result = (*wrapped)(strValue);
175  BEAST_EXPECT(result.copies == 2);
176  BEAST_EXPECT(result.moves == 1);
177  BEAST_EXPECT(result.str == "value!");
178  BEAST_EXPECT(strValue.str.size() == 5);
179  }
180  {
181  // Use a const lvalue argument.
183  BEAST_EXPECT(strCounter.count() == 0);
184 
185  auto wrapped = strCounter.wrap(
186  [](TrackedString const& in) { return in + "!"; });
187 
188  BEAST_EXPECT(strCounter.count() == 1);
189  BEAST_EXPECT(wrapped);
190 
191  TrackedString const strConstLValue("const lvalue");
192  TrackedString const result = (*wrapped)(strConstLValue);
193  BEAST_EXPECT(result.copies == 1);
194  // BEAST_EXPECT (result.moves == ?); // moves VS == 1, gcc == 0
195  BEAST_EXPECT(result.str == "const lvalue!");
196  BEAST_EXPECT(strConstLValue.str.size() == 12);
197  }
198  {
199  // Use a non-const lvalue argument.
201  BEAST_EXPECT(strCounter.count() == 0);
202 
203  auto wrapped =
204  strCounter.wrap([](TrackedString& in) { return in += "!"; });
205 
206  BEAST_EXPECT(strCounter.count() == 1);
207  BEAST_EXPECT(wrapped);
208 
209  TrackedString strLValue("lvalue");
210  TrackedString const result = (*wrapped)(strLValue);
211  BEAST_EXPECT(result.copies == 1);
212  BEAST_EXPECT(result.moves == 0);
213  BEAST_EXPECT(result.str == "lvalue!");
214  BEAST_EXPECT(strLValue.str == result.str);
215  }
216  {
217  // Use an rvalue argument.
219  BEAST_EXPECT(strCounter.count() == 0);
220 
221  auto wrapped = strCounter.wrap([](TrackedString&& in) {
222  // Note that none of the compilers noticed that in was
223  // leaving scope. So, without intervention, they would
224  // do a copy for the return (June 2017). An explicit
225  // std::move() was required.
226  return std::move(in += "!");
227  });
228 
229  BEAST_EXPECT(strCounter.count() == 1);
230  BEAST_EXPECT(wrapped);
231 
232  // Make the string big enough to (probably) avoid the small string
233  // optimization.
234  TrackedString strRValue("rvalue abcdefghijklmnopqrstuvwxyz");
235  TrackedString const result = (*wrapped)(std::move(strRValue));
236  BEAST_EXPECT(result.copies == 0);
237  BEAST_EXPECT(result.moves == 1);
238  BEAST_EXPECT(result.str == "rvalue abcdefghijklmnopqrstuvwxyz!");
239  BEAST_EXPECT(strRValue.str.size() == 0);
240  }
241  }
242 
243  void
245  {
246  // Verify reference counting.
247  ClosureCounter<void> voidCounter;
248  BEAST_EXPECT(voidCounter.count() == 0);
249  {
250  auto wrapped1 = voidCounter.wrap([]() {});
251  BEAST_EXPECT(voidCounter.count() == 1);
252  {
253  // Copy should increase reference count.
254  auto wrapped2(wrapped1);
255  BEAST_EXPECT(voidCounter.count() == 2);
256  {
257  // Move should increase reference count.
258  auto wrapped3(std::move(wrapped2));
259  BEAST_EXPECT(voidCounter.count() == 3);
260  {
261  // An additional closure also increases count.
262  auto wrapped4 = voidCounter.wrap([]() {});
263  BEAST_EXPECT(voidCounter.count() == 4);
264  }
265  BEAST_EXPECT(voidCounter.count() == 3);
266  }
267  BEAST_EXPECT(voidCounter.count() == 2);
268  }
269  BEAST_EXPECT(voidCounter.count() == 1);
270  }
271  BEAST_EXPECT(voidCounter.count() == 0);
272 
273  // Join with 0 count should not stall.
274  using namespace std::chrono_literals;
275  voidCounter.join("testWrap", 1ms, j);
276 
277  // Wrapping a closure after join() should return std::nullopt.
278  BEAST_EXPECT(voidCounter.wrap([]() {}) == std::nullopt);
279  }
280 
281  void
283  {
284  // Verify reference counting.
285  ClosureCounter<void> voidCounter;
286  BEAST_EXPECT(voidCounter.count() == 0);
287 
288  auto wrapped = (voidCounter.wrap([]() {}));
289  BEAST_EXPECT(voidCounter.count() == 1);
290 
291  // Calling join() now should stall, so do it on a different thread.
292  std::atomic<bool> threadExited{false};
293  std::thread localThread([&voidCounter, &threadExited, this]() {
294  // Should stall after calling join.
295  using namespace std::chrono_literals;
296  voidCounter.join("testWaitOnJoin", 1ms, j);
297  threadExited.store(true);
298  });
299 
300  // Wait for the thread to call voidCounter.join().
301  while (!voidCounter.joined())
302  ;
303 
304  // The thread should still be active after waiting 5 milliseconds.
305  // This is not a guarantee that join() stalled the thread, but it
306  // improves confidence.
307  using namespace std::chrono_literals;
309  BEAST_EXPECT(threadExited == false);
310 
311  // Destroy the contents of wrapped and expect the thread to exit
312  // (asynchronously).
313  wrapped = std::nullopt;
314  BEAST_EXPECT(voidCounter.count() == 0);
315 
316  // Wait for the thread to exit.
317  while (threadExited == false)
318  ;
319  localThread.join();
320  }
321 
322 public:
323  void
324  run() override
325  {
327  testArgs();
328  testWrap();
329  testWaitOnJoin();
330  }
331 };
332 
334 
335 } // namespace test
336 } // namespace ripple
std::this_thread::sleep_for
T sleep_for(T... args)
ripple::test::ClosureCounter_test::TrackedString::copies
int copies
Definition: ClosureCounter_test.cpp:114
ripple::test::ClosureCounter_test::testWrap
void testWrap()
Definition: ClosureCounter_test.cpp:244
std::string
STL class.
ripple::test::ClosureCounter_test
Definition: ClosureCounter_test.cpp:32
ripple::test::ClosureCounter_test::TrackedString::TrackedString
TrackedString(char const *rhs)
Definition: ClosureCounter_test.cpp:120
ripple::test::ClosureCounter_test::env
jtx::Env env
Definition: ClosureCounter_test.cpp:35
std::string::size
T size(T... args)
ripple::test::ClosureCounter_test::TrackedString::TrackedString
TrackedString(TrackedString &&rhs) noexcept
Definition: ClosureCounter_test.cpp:131
ripple::QualityDirection::in
@ in
ripple::test::ClosureCounter_test::TrackedString::TrackedString
TrackedString()=delete
ripple::test::ClosureCounter_test::testArgs
void testArgs()
Definition: ClosureCounter_test.cpp:158
ripple::test::jtx::Env::app
Application & app()
Definition: Env.h:240
ripple::test::ClosureCounter_test::TrackedString::TrackedString
TrackedString(TrackedString const &rhs)
Definition: ClosureCounter_test.cpp:125
ripple::test::ClosureCounter_test::TrackedString::operator+
friend TrackedString operator+(TrackedString const &s, char const *rhs)
Definition: ClosureCounter_test.cpp:149
thread
chrono
ripple::test::ClosureCounter_test::TrackedString
Definition: ClosureCounter_test.cpp:111
ripple::test::ClosureCounter_test::testConstruction
void testConstruction()
Definition: ClosureCounter_test.cpp:39
ripple::test::ClosureCounter_test::TrackedString::moves
int moves
Definition: ClosureCounter_test.cpp:115
ripple::test::ClosureCounter_test::testWaitOnJoin
void testWaitOnJoin()
Definition: ClosureCounter_test.cpp:282
ripple::ClosureCounter
The role of a ClosureCounter is to assist in shutdown by letting callers wait for the completion of c...
Definition: ClosureCounter.h:54
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:58
atomic
ripple::test::ClosureCounter_test::TrackedString::operator+=
TrackedString & operator+=(char const *rhs)
Definition: ClosureCounter_test.cpp:142
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::Application::journal
virtual beast::Journal journal(std::string const &name)=0
ripple::ClosureCounter::join
void join(char const *name, std::chrono::milliseconds wait, beast::Journal j)
Returns once all counted in-flight closures are destroyed.
Definition: ClosureCounter.h:166
ripple::ClosureCounter::count
int count() const
Current number of Closures outstanding.
Definition: ClosureCounter.h:205
ripple::test::ClosureCounter_test::TrackedString::str
std::string str
Definition: ClosureCounter_test.cpp:116
ripple::sum
static auto sum(TCollection const &col)
Definition: BookStep.cpp:710
ripple::test::ClosureCounter_test::j
beast::Journal j
Definition: ClosureCounter_test.cpp:36
ripple::ClosureCounter::joined
bool joined() const
Returns true if this has been joined.
Definition: ClosureCounter.h:217
ripple::test::ClosureCounter_test::TrackedString::operator=
TrackedString & operator=(TrackedString const &rhs)=delete
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:115
ripple::test::ClosureCounter_test::run
void run() override
Definition: ClosureCounter_test.cpp:324
ripple::ClosureCounter::wrap
std::optional< Substitute< Closure > > wrap(Closure &&closure)
Wrap the passed closure with a reference counter.
Definition: ClosureCounter.h:192
ripple::test::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(DeliverMin, app, ripple)