SSP21-CPP
MockExecutor.h
1 /*
2  * Copyright (c) 2018, Automatak LLC
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
6  * following conditions are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
9  * disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
12  * disclaimer in the documentation and/or other materials provided with the distribution.
13  *
14  * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
15  * products derived from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
18  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
23  * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 #ifndef EXE4CPP_MOCKEXECUTOR_H
26 #define EXE4CPP_MOCKEXECUTOR_H
27 
28 #include "exe4cpp/IExecutor.h"
29 
30 #include <algorithm>
31 #include <cstddef>
32 #include <limits>
33 #include <memory>
34 #include <queue>
35 
36 namespace exe4cpp
37 {
38 
39 /**
40 * Mock implementation of IExecutor for testing
41 */
42 class MockExecutor final : public IExecutor
43 {
44 private:
45  class MockTimer final : public ITimer
46  {
47  friend class MockExecutor;
48 
49  public:
50  MockTimer(MockExecutor* source, const steady_time_t& time, const action_t& action) :
51  time{time},
52  source{source},
53  action{action}
54  {}
55 
56  // implement ITimer
57  void cancel() override
58  {
59  source->cancel(this);
60  }
61 
62  steady_time_t expires_at() override
63  {
64  return this->time;
65  }
66 
67  private:
68  steady_time_t time;
69  MockExecutor* source;
70  action_t action;
71  };
72 
73 public:
74  MockExecutor() = default;
75 
76  // ------ Implement IExecutor ------
77 
78  virtual Timer start(const duration_t& delay, const action_t& action) override
79  {
80  return start(current_time + delay, action);
81  }
82 
83  virtual Timer start(const steady_time_t& time, const action_t& action) override
84  {
85  const auto timer = std::make_shared<MockTimer>(this, time, action);
86  this->timers.push_back(timer);
87  return Timer{timer};
88  }
89 
90  virtual void post(const action_t& action) override
91  {
92  this->post_queue.push_back(action);
93  }
94 
95  virtual steady_time_t get_time() override
96  {
97  return current_time;
98  }
99 
100  /** @return true if an action was run. */
101  bool run_one()
102  {
103  this->check_for_expired_timers();
104 
105  if (this->post_queue.size() > 0)
106  {
107  auto runnable = post_queue.front();
108  this->post_queue.pop_front();
109  runnable();
110  return true;
111  }
112  else
113  {
114  return false;
115  }
116  }
117 
118  /** Calls RunOne() up to some maximum number of times continuing while
119  there are still events to dispatch
120 
121  @return the number of events dispatched
122  */
123  size_t run_many(size_t maximum = std::numeric_limits<size_t>::max())
124  {
125  size_t num = 0;
126  while (num < maximum && this->run_one()) ++num;
127  return num;
128  }
129 
130  /** @return The number of active, pending timers and post operations */
131  size_t num_active() const
132  {
133  return this->post_queue.size();
134  }
135 
136  size_t num_pending_timers() const
137  {
138  return this->timers.size();
139  }
140 
141  steady_time_t next_timer_expiration_abs() const
142  {
143  auto lt = [](const std::shared_ptr<MockTimer>& lhs, const std::shared_ptr<MockTimer>& rhs)
144  {
145  return lhs->expires_at() < rhs->expires_at();
146  };
147  auto min = std::min_element(this->timers.begin(), this->timers.end(), lt);
148  if (min == this->timers.end())
149  {
150  return steady_time_t();
151  }
152  else
153  {
154  return (*min)->expires_at();
155  }
156  }
157 
158  duration_t next_timer_expiration_rel() const
159  {
160  auto lt = [](const std::shared_ptr<MockTimer>& lhs, const std::shared_ptr<MockTimer>& rhs)
161  {
162  return lhs->expires_at() < rhs->expires_at();
163  };
164  auto min = std::min_element(this->timers.begin(), this->timers.end(), lt);
165  if (min == this->timers.end())
166  {
167  return duration_t::max();
168  }
169  else
170  {
171  return (*min)->expires_at() - this->current_time;
172  }
173  }
174 
175  size_t advance_time(duration_t duration)
176  {
177  this->add_time(duration);
178  return this->check_for_expired_timers();
179  }
180 
181  // doesn't check timers_
182  void add_time(duration_t duration)
183  {
184  this->current_time += duration;
185  }
186 
187  bool advance_to_next_timer()
188  {
189  if (this->timers.empty())
190  {
191  return false;
192  }
193  else
194  {
195  const auto timestamp = next_timer_expiration_abs();
196 
197  if (timestamp > this->current_time)
198  {
199  this->current_time = timestamp;
200  return true;
201  }
202  else
203  {
204  return false;
205  }
206  }
207  }
208 
209  size_t num_timer_cancel() const
210  {
211  return this->num_timer_cancel_;
212  }
213 
214 private:
215  size_t check_for_expired_timers()
216  {
217  size_t count = 0;
218  while (find_expired_timer())
219  {
220  ++count;
221  }
222  return count;
223  }
224 
225  bool find_expired_timer()
226  {
227  auto expired = [this](const std::shared_ptr<MockTimer>& timer)
228  {
229  return timer->expires_at() <= this->current_time;
230  };
231 
232  auto iter = std::find_if(this->timers.begin(), this->timers.end(), expired);
233 
234  if (iter == this->timers.end())
235  {
236  return false;
237  }
238  else
239  {
240  // keep the timer alive until it's callback is completed.
241  auto action = [timer = (*iter), action = (*iter)->action]() -> void { action(); };
242  this->post_queue.push_back(action);
243  this->timers.erase(iter);
244  return true;
245  }
246  }
247 
248  void cancel(ITimer* timer)
249  {
250  const auto result = std::find_if(this->timers.begin(), this->timers.end(), [timer](const std::shared_ptr<MockTimer>& item)
251  {
252  return item.get() == timer;
253  });
254 
255  if (result != this->timers.end())
256  {
257  ++num_timer_cancel_;
258  this->timers.erase(result);
259  }
260  }
261 
262  typedef std::deque<action_t> post_queue_t;
263  typedef std::vector<std::shared_ptr<MockTimer>> timer_vector_t;
264 
265  steady_time_t current_time;
266  size_t num_timer_cancel_ = 0;
267 
268  post_queue_t post_queue;
269  timer_vector_t timers;
270 };
271 
272 }
273 
274 #endif
exe4cpp header-only library namespace
Definition: AsioTimer.h:29
virtual Timer start(const duration_t &delay, const action_t &action) override
Definition: MockExecutor.h:78
virtual Timer start(const steady_time_t &time, const action_t &action) override
Definition: MockExecutor.h:83
size_t num_active() const
Definition: MockExecutor.h:131
virtual void post(const action_t &action) override
Definition: MockExecutor.h:90
size_t run_many(size_t maximum=std::numeric_limits< size_t >::max())
Definition: MockExecutor.h:123
virtual steady_time_t get_time() override
Definition: MockExecutor.h:95