mirror of
https://github.com/PerMalmberg/libcron.git
synced 2025-04-22 08:23:04 -05:00
Moce functionality in main Cron class with added tests.
This commit is contained in:
parent
0db05ac71b
commit
d2ec26f494
@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.6)
|
|||||||
project(libcron)
|
project(libcron)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 14)
|
set(CMAKE_CXX_STANDARD 14)
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wpedantic -fsanitize=address")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic")
|
||||||
|
|
||||||
include_directories(externals/date)
|
include_directories(externals/date)
|
||||||
|
|
||||||
@ -15,4 +15,6 @@ add_library(${PROJECT_NAME}
|
|||||||
CronData.cpp
|
CronData.cpp
|
||||||
CronSchedule.cpp
|
CronSchedule.cpp
|
||||||
CronSchedule.h
|
CronSchedule.h
|
||||||
externals/date/date.h DateTime.h Task.cpp)
|
externals/date/date.h
|
||||||
|
DateTime.h
|
||||||
|
Task.cpp)
|
||||||
|
@ -1,29 +1,87 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include "Cron.h"
|
#include "Cron.h"
|
||||||
|
|
||||||
|
using namespace std::chrono;
|
||||||
|
|
||||||
namespace libcron
|
namespace libcron
|
||||||
{
|
{
|
||||||
|
bool libcron::Cron::add_schedule( std::string name, const std::string& schedule, std::function<void()> work)
|
||||||
bool libcron::Cron::add_schedule(const std::string& schedule, std::function<void()> work)
|
|
||||||
{
|
{
|
||||||
auto cron = CronData::create(schedule);
|
auto cron = CronData::create(schedule);
|
||||||
bool res = cron.is_valid();
|
bool res = cron.is_valid();
|
||||||
if (res)
|
if (res)
|
||||||
{
|
{
|
||||||
CronSchedule s(cron);
|
|
||||||
Task t(std::move(s), std::move(work));
|
Task t{std::move(name), CronSchedule{cron}, std::move(work)};
|
||||||
if (t.calculate_next())
|
if (t.calculate_next())
|
||||||
{
|
{
|
||||||
items.emplace(t);
|
tasks.push(t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool libcron::Cron::has_expired_task(const std::chrono::system_clock::time_point now) const
|
std::chrono::system_clock::duration Cron::time_until_next(std::chrono::system_clock::time_point now) const
|
||||||
{
|
{
|
||||||
return !items.empty() && items.top().is_expired(now);
|
system_clock::duration d{};
|
||||||
|
if (tasks.empty())
|
||||||
|
{
|
||||||
|
d = std::numeric_limits<minutes>::max();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
d = tasks.top().time_until_expiry(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Cron::execute_expired_tasks(system_clock::time_point now)
|
||||||
|
{
|
||||||
|
std::vector<Task> executed{};
|
||||||
|
|
||||||
|
while(!tasks.empty()
|
||||||
|
&& tasks.top().is_expired(now))
|
||||||
|
{
|
||||||
|
executed.push_back(tasks.top());
|
||||||
|
tasks.pop();
|
||||||
|
auto& t = executed[executed.size()-1];
|
||||||
|
t.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto res = executed.size();
|
||||||
|
|
||||||
|
// Place executed tasks back onto the priority queue.
|
||||||
|
std::for_each(executed.begin(), executed.end(), [this, &now](Task& task)
|
||||||
|
{
|
||||||
|
// Must calculate new schedules using second after 'now', otherwise
|
||||||
|
// we'll run the same task over and over if it takes less than 1s to execute.
|
||||||
|
if(task.calculate_next(now + 1s))
|
||||||
|
{
|
||||||
|
tasks.push(task);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
print_queue(tasks);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cron::print_queue(std::priority_queue<Task, std::vector<Task>, std::greater<>> queue)
|
||||||
|
{
|
||||||
|
std::vector<Task> v{};
|
||||||
|
|
||||||
|
while( !queue.empty())
|
||||||
|
{
|
||||||
|
auto t = queue.top();
|
||||||
|
queue.pop();
|
||||||
|
v.push_back(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::for_each(v.begin(), v.end(), [&queue](auto& task){
|
||||||
|
queue.push(task);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -3,6 +3,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
#include <memory>
|
||||||
#include "Task.h"
|
#include "Task.h"
|
||||||
|
|
||||||
namespace libcron
|
namespace libcron
|
||||||
@ -11,16 +12,22 @@ namespace libcron
|
|||||||
{
|
{
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool add_schedule(const std::string& schedule, std::function<void()> work);
|
bool add_schedule(std::string name, const std::string& schedule, std::function<void()> work);
|
||||||
|
|
||||||
size_t count() const
|
size_t count() const
|
||||||
{
|
{
|
||||||
return items.size();
|
return tasks.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool has_expired_task(std::chrono::system_clock::time_point now = std::chrono::system_clock::now()) const;
|
size_t
|
||||||
|
execute_expired_tasks(std::chrono::system_clock::time_point now = std::chrono::system_clock::now());
|
||||||
|
|
||||||
|
std::chrono::system_clock::duration
|
||||||
|
time_until_next(std::chrono::system_clock::time_point now = std::chrono::system_clock::now()) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::priority_queue<Task> items{};
|
// Priority queue placing smallest (i.e. nearest in time) items on top.
|
||||||
|
std::priority_queue<Task, std::vector<Task>, std::greater<>> tasks{};
|
||||||
|
void print_queue(std::priority_queue<Task, std::vector<Task>, std::greater<>> queue);
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -59,6 +59,7 @@ namespace libcron
|
|||||||
static CronData create(const std::string& cron_expression);
|
static CronData create(const std::string& cron_expression);
|
||||||
|
|
||||||
CronData();
|
CronData();
|
||||||
|
CronData(const CronData&) = default;
|
||||||
|
|
||||||
bool is_valid() const
|
bool is_valid() const
|
||||||
{
|
{
|
||||||
|
@ -15,6 +15,8 @@ namespace libcron
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CronSchedule(const CronSchedule&) = default;
|
||||||
|
|
||||||
std::tuple<bool, std::chrono::system_clock::time_point>
|
std::tuple<bool, std::chrono::system_clock::time_point>
|
||||||
calculate_from(const std::chrono::system_clock::time_point& from) const;
|
calculate_from(const std::chrono::system_clock::time_point& from) const;
|
||||||
|
|
||||||
|
@ -1,17 +1,62 @@
|
|||||||
|
#include <iostream>
|
||||||
#include "Task.h"
|
#include "Task.h"
|
||||||
|
|
||||||
|
using namespace std::chrono;
|
||||||
|
|
||||||
namespace libcron
|
namespace libcron
|
||||||
{
|
{
|
||||||
|
|
||||||
bool Task::calculate_next(std::chrono::system_clock::time_point from)
|
bool Task::calculate_next(std::chrono::system_clock::time_point from)
|
||||||
{
|
{
|
||||||
auto result = schedule.calculate_from(from);
|
auto result = schedule.calculate_from(from);
|
||||||
auto res = std::get<0>(result);
|
|
||||||
if(res)
|
// In case the calculation fails, the task will no longer expire.
|
||||||
|
valid = std::get<0>(result);
|
||||||
|
if (valid)
|
||||||
{
|
{
|
||||||
next_schedule = std::get<1>(result);
|
next_schedule = std::get<1>(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Task::is_expired(std::chrono::system_clock::time_point now) const
|
||||||
|
{
|
||||||
|
return valid && time_until_expiry(now) == 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::chrono::system_clock::duration Task::time_until_expiry(std::chrono::system_clock::time_point now) const
|
||||||
|
{
|
||||||
|
system_clock::duration d{};
|
||||||
|
|
||||||
|
// Explicitly return 0s instead of a possibly negative duration when it has expired.
|
||||||
|
if (now >= next_schedule)
|
||||||
|
{
|
||||||
|
d = 0s;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
d = next_schedule - now;
|
||||||
|
}
|
||||||
|
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Task::get_status(std::chrono::system_clock::time_point now) const
|
||||||
|
{
|
||||||
|
std::string s = "'";
|
||||||
|
s+= get_name();
|
||||||
|
s += "' expires in ";
|
||||||
|
s += std::to_string(duration_cast<milliseconds>(time_until_expiry(now)).count());
|
||||||
|
s += "ms => ";
|
||||||
|
|
||||||
|
auto dt = CronSchedule::to_calendar_time(next_schedule);
|
||||||
|
s+= std::to_string(dt.year) + "-";
|
||||||
|
s+= std::to_string(dt.month) + "-";
|
||||||
|
s+= std::to_string(dt.day) + " ";
|
||||||
|
s+= std::to_string(dt.hour) + ":";
|
||||||
|
s+= std::to_string(dt.min) + ":";
|
||||||
|
s+= std::to_string(dt.sec) + " UTC";
|
||||||
|
return s;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -11,30 +11,44 @@ namespace libcron
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
Task(CronSchedule schedule, std::function<void()> task)
|
Task(const std::string name, const CronSchedule schedule, std::function<void()> task)
|
||||||
: schedule(std::move(schedule)), task(std::move(task))
|
: name(std::move(name)), schedule(std::move(schedule)), task(std::move(task))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
Task(const Task&) = default;
|
void execute() const
|
||||||
|
{
|
||||||
|
task();
|
||||||
|
}
|
||||||
|
|
||||||
|
Task(const Task& other) = default;
|
||||||
|
|
||||||
Task& operator=(const Task&) = default;
|
Task& operator=(const Task&) = default;
|
||||||
|
|
||||||
bool calculate_next(std::chrono::system_clock::time_point from = std::chrono::system_clock::now());
|
bool calculate_next(std::chrono::system_clock::time_point from = std::chrono::system_clock::now());
|
||||||
|
|
||||||
bool operator<(const Task& other) const
|
bool operator>(const Task& other) const
|
||||||
{
|
{
|
||||||
return next_schedule < other.next_schedule;
|
return next_schedule > other.next_schedule;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_expired(std::chrono::system_clock::time_point now = std::chrono::system_clock::now()) const
|
bool is_expired(std::chrono::system_clock::time_point now = std::chrono::system_clock::now()) const;
|
||||||
|
|
||||||
|
std::chrono::system_clock::duration
|
||||||
|
time_until_expiry(std::chrono::system_clock::time_point now = std::chrono::system_clock::now()) const;
|
||||||
|
|
||||||
|
std::string get_name() const
|
||||||
{
|
{
|
||||||
return now >= next_schedule;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string get_status(std::chrono::system_clock::time_point now) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::string name;
|
||||||
CronSchedule schedule;
|
CronSchedule schedule;
|
||||||
std::chrono::system_clock::time_point next_schedule;
|
std::chrono::system_clock::time_point next_schedule;
|
||||||
std::function<void()> task;
|
std::function<void()> task;
|
||||||
|
bool valid = false;
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.6)
|
|||||||
project(cron_test)
|
project(cron_test)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 14)
|
set(CMAKE_CXX_STANDARD 14)
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wpedantic -fsanitize=address -lasan")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wpedantic -Wextra")
|
||||||
|
|
||||||
include_directories(
|
include_directories(
|
||||||
externals/Catch2/single_include/
|
externals/Catch2/single_include/
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#include <catch.hpp>
|
#include <catch.hpp>
|
||||||
#include <libcron/Cron.h>
|
#include <libcron/Cron.h>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
using namespace libcron;
|
using namespace libcron;
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
@ -10,7 +11,7 @@ std::string create_schedule_expiring_in(hours h, minutes m, seconds s)
|
|||||||
auto now = system_clock::now() + h + m + s;
|
auto now = system_clock::now() + h + m + s;
|
||||||
auto dt = CronSchedule::to_calendar_time(now);
|
auto dt = CronSchedule::to_calendar_time(now);
|
||||||
|
|
||||||
std::string res = "";
|
std::string res{};
|
||||||
res += std::to_string(dt.sec) + " ";
|
res += std::to_string(dt.sec) + " ";
|
||||||
res += std::to_string(dt.min) + " ";
|
res += std::to_string(dt.min) + " ";
|
||||||
res += std::to_string(dt.hour) + " * * *";
|
res += std::to_string(dt.hour) + " * * *";
|
||||||
@ -24,6 +25,7 @@ SCENARIO("Adding a task")
|
|||||||
GIVEN("A Cron instance with no task")
|
GIVEN("A Cron instance with no task")
|
||||||
{
|
{
|
||||||
Cron c;
|
Cron c;
|
||||||
|
auto expired = false;
|
||||||
|
|
||||||
THEN("Starts with no task")
|
THEN("Starts with no task")
|
||||||
{
|
{
|
||||||
@ -32,23 +34,25 @@ SCENARIO("Adding a task")
|
|||||||
|
|
||||||
WHEN("Adding a task that runs every second")
|
WHEN("Adding a task that runs every second")
|
||||||
{
|
{
|
||||||
REQUIRE(c.add_schedule("* * * * * *",
|
REQUIRE(c.add_schedule("A task", "* * * * * *",
|
||||||
[]()
|
[&expired]()
|
||||||
{
|
{
|
||||||
return;
|
expired = true;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
THEN("Count is 1 and task was not expired two seconds ago")
|
THEN("Count is 1 and task was not expired two seconds ago")
|
||||||
{
|
{
|
||||||
REQUIRE(c.count() == 1);
|
REQUIRE(c.count() == 1);
|
||||||
REQUIRE_FALSE(c.has_expired_task(system_clock::now() - 2s));
|
c.execute_expired_tasks(system_clock::now() - 2s);
|
||||||
|
REQUIRE_FALSE(expired);
|
||||||
}
|
}
|
||||||
AND_THEN("Task is expired when calculating based on current time")
|
AND_THEN("Task is expired when calculating based on current time")
|
||||||
{
|
{
|
||||||
|
c.execute_expired_tasks();
|
||||||
THEN("Task is expired")
|
THEN("Task is expired")
|
||||||
{
|
{
|
||||||
REQUIRE(c.has_expired_task());
|
REQUIRE(expired);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -59,34 +63,128 @@ SCENARIO("Adding a task that expires in the future")
|
|||||||
{
|
{
|
||||||
GIVEN("A Cron instance with task expiring in 3 seconds")
|
GIVEN("A Cron instance with task expiring in 3 seconds")
|
||||||
{
|
{
|
||||||
|
auto expired = false;
|
||||||
|
|
||||||
Cron c;
|
Cron c;
|
||||||
REQUIRE(c.add_schedule(create_schedule_expiring_in(hours{0}, minutes{0}, seconds{3}),
|
REQUIRE(c.add_schedule("A task", create_schedule_expiring_in(hours{0}, minutes{0}, seconds{3}),
|
||||||
[]()
|
[&expired]()
|
||||||
{
|
{
|
||||||
return;
|
expired = true;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
THEN("Not yet expired")
|
THEN("Not yet expired")
|
||||||
{
|
{
|
||||||
REQUIRE_FALSE(c.has_expired_task());
|
REQUIRE_FALSE(expired);
|
||||||
}
|
}
|
||||||
AND_WHEN("When waiting one second")
|
AND_WHEN("When waiting one second")
|
||||||
{
|
{
|
||||||
std::this_thread::sleep_for(1s);
|
std::this_thread::sleep_for(1s);
|
||||||
|
c.execute_expired_tasks();
|
||||||
THEN("Task has not yet expired")
|
THEN("Task has not yet expired")
|
||||||
{
|
{
|
||||||
REQUIRE_FALSE(c.has_expired_task());
|
REQUIRE_FALSE(expired);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AND_WHEN("When waiting three seconds")
|
AND_WHEN("When waiting three seconds")
|
||||||
{
|
{
|
||||||
std::this_thread::sleep_for(3s);
|
std::this_thread::sleep_for(3s);
|
||||||
|
c.execute_expired_tasks();
|
||||||
THEN("Task has expired")
|
THEN("Task has expired")
|
||||||
{
|
{
|
||||||
REQUIRE(c.has_expired_task());
|
REQUIRE(expired);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SCENARIO("Task priority")
|
||||||
|
{
|
||||||
|
GIVEN("A Cron instance with two tasks expiring in 3 and 5 seconds, added in 'reverse' order")
|
||||||
|
{
|
||||||
|
auto _3_second_expired = 0;
|
||||||
|
auto _5_second_expired = 0;
|
||||||
|
|
||||||
|
|
||||||
|
Cron c;
|
||||||
|
REQUIRE(c.add_schedule("Five", create_schedule_expiring_in(hours{0}, minutes{0}, seconds{5}),
|
||||||
|
[&_5_second_expired]()
|
||||||
|
{
|
||||||
|
_5_second_expired++;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
REQUIRE(c.add_schedule("Three", create_schedule_expiring_in(hours{0}, minutes{0}, seconds{3}),
|
||||||
|
[&_3_second_expired]()
|
||||||
|
{
|
||||||
|
_3_second_expired++;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
THEN("Not yet expired")
|
||||||
|
{
|
||||||
|
REQUIRE_FALSE(_3_second_expired);
|
||||||
|
REQUIRE_FALSE(_5_second_expired);
|
||||||
|
}
|
||||||
|
|
||||||
|
WHEN("Waiting 1 seconds")
|
||||||
|
{
|
||||||
|
std::this_thread::sleep_for(1s);
|
||||||
|
c.execute_expired_tasks();
|
||||||
|
|
||||||
|
THEN("Task has not yet expired")
|
||||||
|
{
|
||||||
|
REQUIRE(_3_second_expired == 0);
|
||||||
|
REQUIRE(_5_second_expired == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AND_WHEN("Waiting 3 seconds")
|
||||||
|
{
|
||||||
|
std::this_thread::sleep_for(3s);
|
||||||
|
c.execute_expired_tasks();
|
||||||
|
|
||||||
|
THEN("3 second task has expired")
|
||||||
|
{
|
||||||
|
REQUIRE(_3_second_expired == 1);
|
||||||
|
REQUIRE(_5_second_expired == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AND_WHEN("Waiting 5 seconds")
|
||||||
|
{
|
||||||
|
std::this_thread::sleep_for(5s);
|
||||||
|
c.execute_expired_tasks();
|
||||||
|
|
||||||
|
THEN("3 and 5 second task has expired")
|
||||||
|
{
|
||||||
|
REQUIRE(_3_second_expired == 1);
|
||||||
|
REQUIRE(_5_second_expired == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AND_WHEN("Waiting based on the time given by the Cron instance")
|
||||||
|
{
|
||||||
|
std::this_thread::sleep_for(c.time_until_next());
|
||||||
|
c.execute_expired_tasks();
|
||||||
|
|
||||||
|
THEN("3 second task has expired")
|
||||||
|
{
|
||||||
|
REQUIRE(_3_second_expired == 1);
|
||||||
|
REQUIRE(_5_second_expired == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AND_WHEN("Waiting based on the time given by the Cron instance")
|
||||||
|
{
|
||||||
|
std::this_thread::sleep_for(c.time_until_next());
|
||||||
|
REQUIRE(c.execute_expired_tasks() == 1);
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(c.time_until_next());
|
||||||
|
REQUIRE(c.execute_expired_tasks() == 1);
|
||||||
|
|
||||||
|
THEN("3 and 5 second task has each expired once")
|
||||||
|
{
|
||||||
|
REQUIRE(_3_second_expired == 1);
|
||||||
|
REQUIRE(_5_second_expired == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user