Repository: CrowCpp/Crow (C++)

Issue:
Refactor task_timer to allow mocking the timer
The class `task_timer` uses `std::chrono::steady_clock`. Currently, in the tests the real time has to elapse which makes running the test suite take much longer than it could because of the tests regarding this component.

I propose refactoring `task_timer` to something like:
```
template <typename ClockType = std::chrono::steady_clock>
class task_timer {
...
}
```

This would allow instantiating the `task_timer` using a mock timer and would allow replacing `this_thread::sleep_for(chrono::seconds(4));` with something along the line of `timer.advance(4);` effectively making all the tests involving timers instant to execute.


Discussion:
What about giving the interval time to the constructor (with a default of 1s)?




Patch:
diff --git a/include/crow/task_timer.h b/include/crow/task_timer.h
index 15b65b639..5d7518ca7 100644
--- a/include/crow/task_timer.h
+++ b/include/crow/task_timer.h
@@ -29,7 +29,9 @@ namespace crow
     namespace detail
     {
 
-        /// A class for scheduling functions to be called after a specific amount of ticks. A tick is equal to 1 second.
+        /// A class for scheduling functions to be called after a specific
+        /// amount of ticks. Ther tick length can  be handed over in constructor, 
+        /// the default tick length is equal to 1 second.
         class task_timer
         {
         public:
@@ -39,66 +41,82 @@ namespace crow
         private:
             using clock_type = std::chrono::steady_clock;
             using time_type = clock_type::time_point;
-
         public:
-            task_timer(asio::io_service& io_service):
-              io_service_(io_service), timer_(io_service_)
+            task_timer(asio::io_service& io_service,
+                       const std::chrono::milliseconds tick_length =
+                            std::chrono::seconds(1)) :
+              io_service_(io_service), timer_(io_service_),
+              tick_length_ms_(tick_length)
             {
-                timer_.expires_after(std::chrono::seconds(1));
+                timer_.expires_after(tick_length_ms_);
                 timer_.async_wait(
-                  std::bind(&task_timer::tick_handler, this, std::placeholders::_1));
+                  std::bind(&task_timer::tick_handler, this,
+                  std::placeholders::_1));
             }
 
             ~task_timer() { timer_.cancel(); }
 
+            /// Cancel the scheduling of the given task 
+            ///
+            /// \param identifier_type task identifier of the task to cancel.
             void cancel(identifier_type id)
             {
                 tasks_.erase(id);
-                CROW_LOG_DEBUG << "task_timer cancelled: " << this << ' ' << id;
+                CROW_LOG_DEBUG << "task_timer task cancelled: " << this << ' ' << id;
             }
 
-            /// Schedule the given task to be executed after the default amount of ticks.
+            /// Schedule the given task to be executed after the default amount
+            /// of ticks.
 
             ///
             /// \return identifier_type Used to cancel the thread.
-            /// It is not bound to this task_timer instance and in some cases could lead to
-            /// undefined behavior if used with other task_timer objects or after the task
-            /// has been successfully executed.
+            /// It is not bound to this task_timer instance and in some cases
+            /// could lead to undefined behavior if used with other task_timer
+            /// objects or after the task has been successfully executed.
             identifier_type schedule(const task_type& task)
             {
-                tasks_.insert(
-                  {++highest_id_,
-                   {clock_type::now() + std::chrono::seconds(get_default_timeout()),
-                    task}});
-                CROW_LOG_DEBUG << "task_timer scheduled: " << this << ' ' << highest_id_;
-                return highest_id_;
+                return schedule(task, get_default_timeout());
             }
 
             /// Schedule the given task to be executed after the given time.
 
             ///
-            /// \param timeout The amount of ticks (seconds) to wait before execution.
+            /// \param timeout The amount of ticks to wait before execution.
             ///
             /// \return identifier_type Used to cancel the thread.
-            /// It is not bound to this task_timer instance and in some cases could lead to
-            /// undefined behavior if used with other task_timer objects or after the task
-            /// has been successfully executed.
-            identifier_type schedule(const task_type& task, std::uint8_t timeout)
+            /// It is not bound to this task_timer instance and in some cases
+            /// could lead to undefined behavior if used with other task_timer
+            /// objects or after the task has been successfully executed.
+            identifier_type schedule(const task_type& task, uint8_t timeout)
             {
                 tasks_.insert({++highest_id_,
-                               {clock_type::now() + std::chrono::seconds(timeout), task}});
-                CROW_LOG_DEBUG << "task_timer scheduled: " << this << ' ' << highest_id_;
+                               {clock_type::now() + (timeout * tick_length_ms_),
+                                task}});
+                CROW_LOG_DEBUG << "task_timer scheduled: " << this << ' ' <<
+                                  highest_id_;
                 return highest_id_;
             }
 
-            /// Set the default timeout for this task_timer instance. (Default: 5)
+            /// Set the default timeout for this task_timer instance.
+            /// (Default: 5)
 
             ///
-            /// \param timeout The amount of ticks (seconds) to wait before execution.
-            void set_default_timeout(std::uint8_t timeout) { default_timeout_ = timeout; }
+            /// \param timeout The amount of ticks to wait before
+            /// execution. 
+            /// For tick length \see tick_length_ms_ 
+            void set_default_timeout(uint8_t timeout) {
+                default_timeout_ = timeout;
+            }
 
             /// Get the default timeout. (Default: 5)
-            std::uint8_t get_default_timeout() const { return default_timeout_; }
+            uint8_t get_default_timeout() const {
+                return default_timeout_;
+            }
+
+            /// returns the length of one tick.
+            std::chrono::milliseconds get_tick_length() const {
+                return tick_length_ms_;
+            }
 
         private:
             void process_tasks()
@@ -112,14 +130,16 @@ namespace crow
                     {
                         (task.second.second)();
                         finished_tasks.push_back(task.first);
-                        CROW_LOG_DEBUG << "task_timer called: " << this << ' ' << task.first;
+                        CROW_LOG_DEBUG << "task_timer called: " << this <<
+                                          ' ' << task.first;
                     }
                 }
 
                 for (const auto& task : finished_tasks)
                     tasks_.erase(task);
 
-                // If no task is currently scheduled, reset the issued ids back to 0.
+                // If no task is currently scheduled, reset the issued ids back
+                // to 0.
                 if (tasks_.empty()) highest_id_ = 0;
             }
 
@@ -129,20 +149,22 @@ namespace crow
 
                 process_tasks();
 
-                timer_.expires_after(std::chrono::seconds(1));
+                timer_.expires_after(tick_length_ms_);
                 timer_.async_wait(
                   std::bind(&task_timer::tick_handler, this, std::placeholders::_1));
             }
 
         private:
-            std::uint8_t default_timeout_{5};
             asio::io_service& io_service_;
             asio::basic_waitable_timer<clock_type> timer_;
             std::map<identifier_type, std::pair<time_type, task_type>> tasks_;
 
-            // A continuosly increasing number to be issued to threads to identify them.
-            // If no tasks are scheduled, it will be reset to 0.
+            // A continuously increasing number to be issued to threads to
+            // identify them. If no tasks are scheduled, it will be reset to 0.
             identifier_type highest_id_{0};
+            std::chrono::milliseconds tick_length_ms_;
+            uint8_t default_timeout_{5};
+
         };
     } // namespace detail
 } // namespace crow


Test patch:
diff --git a/tests/unittest.cpp b/tests/unittest.cpp
index bbf402177..adc416ff4 100644
--- a/tests/unittest.cpp
+++ b/tests/unittest.cpp
@@ -3839,26 +3839,25 @@ TEST_CASE("task_timer")
     bool a = false;
     bool b = false;
 
-    crow::detail::task_timer timer(io_service);
+    crow::detail::task_timer timer(io_service, std::chrono::milliseconds(100));
     CHECK(timer.get_default_timeout() == 5);
     timer.set_default_timeout(7);
     CHECK(timer.get_default_timeout() == 7);
 
     timer.schedule([&a]() {
         a = true;
-    },
-                   5);
+    }, 5);
     timer.schedule([&b]() {
         b = true;
     });
 
-    this_thread::sleep_for(chrono::seconds(4));
+    this_thread::sleep_for(4 * timer.get_tick_length());
     CHECK(a == false);
     CHECK(b == false);
-    this_thread::sleep_for(chrono::seconds(2));
+    this_thread::sleep_for(2 * timer.get_tick_length());
     CHECK(a == true);
     CHECK(b == false);
-    this_thread::sleep_for(chrono::seconds(2));
+    this_thread::sleep_for(2 * timer.get_tick_length());
     CHECK(a == true);
     CHECK(b == true);