mirror of
https://github.com/PerMalmberg/libcron.git
synced 2025-04-22 08:23:04 -05:00
Work on DST handling.
This commit is contained in:
parent
5d60fa7133
commit
e99b049d2b
@ -1,6 +1,9 @@
|
|||||||
cmake_minimum_required(VERSION 3.6)
|
cmake_minimum_required(VERSION 3.6)
|
||||||
|
|
||||||
|
set(OUTPUT_LOCATION ${CMAKE_CURRENT_LIST_DIR}/out/)
|
||||||
|
|
||||||
add_subdirectory(libcron)
|
add_subdirectory(libcron)
|
||||||
add_subdirectory(test)
|
add_subdirectory(test)
|
||||||
|
|
||||||
add_dependencies(cron_test libcron)
|
add_dependencies(cron_test libcron)
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ include_directories(${CMAKE_CURRENT_LIST_DIR}/externals/date/include)
|
|||||||
|
|
||||||
add_library(${PROJECT_NAME}
|
add_library(${PROJECT_NAME}
|
||||||
Cron.h
|
Cron.h
|
||||||
Cron.cpp
|
|
||||||
Task.h
|
Task.h
|
||||||
CronData.h
|
CronData.h
|
||||||
TimeTypes.h
|
TimeTypes.h
|
||||||
@ -18,3 +17,8 @@ add_library(${PROJECT_NAME}
|
|||||||
DateTime.h
|
DateTime.h
|
||||||
Task.cpp
|
Task.cpp
|
||||||
CronClock.h)
|
CronClock.h)
|
||||||
|
|
||||||
|
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||||
|
ARCHIVE_OUTPUT_DIRECTORY "${OUTPUT_LOCATION}"
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY "${OUTPUT_LOCATION}"
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY "${OUTPUT_LOCATION}")
|
@ -1,87 +0,0 @@
|
|||||||
#include <functional>
|
|
||||||
#include "Cron.h"
|
|
||||||
|
|
||||||
using namespace std::chrono;
|
|
||||||
|
|
||||||
namespace libcron
|
|
||||||
{
|
|
||||||
bool libcron::Cron::add_schedule( std::string name, const std::string& schedule, std::function<void()> work)
|
|
||||||
{
|
|
||||||
auto cron = CronData::create(schedule);
|
|
||||||
bool res = cron.is_valid();
|
|
||||||
if (res)
|
|
||||||
{
|
|
||||||
|
|
||||||
Task t{std::move(name), CronSchedule{cron}, std::move(work)};
|
|
||||||
if (t.calculate_next(clock->now()))
|
|
||||||
{
|
|
||||||
tasks.push(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::chrono::system_clock::duration Cron::time_until_next() const
|
|
||||||
{
|
|
||||||
system_clock::duration d{};
|
|
||||||
if (tasks.empty())
|
|
||||||
{
|
|
||||||
d = std::numeric_limits<minutes>::max();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
d = tasks.top().time_until_expiry(clock->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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
152
libcron/Cron.h
152
libcron/Cron.h
@ -9,15 +9,17 @@
|
|||||||
|
|
||||||
namespace libcron
|
namespace libcron
|
||||||
{
|
{
|
||||||
|
template<typename ClockType>
|
||||||
|
class Cron;
|
||||||
|
|
||||||
|
template<typename ClockType>
|
||||||
|
std::ostream& operator<<(std::ostream& stream, const Cron<ClockType>& c);
|
||||||
|
|
||||||
|
template<typename ClockType = libcron::LocalClock>
|
||||||
class Cron
|
class Cron
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
explicit Cron(std::unique_ptr<ICronClock> clock = std::make_unique<LocalClock>())
|
|
||||||
: clock(std::move(clock))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool add_schedule(std::string name, 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
|
||||||
@ -25,24 +27,148 @@ namespace libcron
|
|||||||
return tasks.size();
|
return tasks.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tick is expected to be called at least once a second to prevent missing schedules.
|
||||||
size_t
|
size_t
|
||||||
execute_expired_tasks()
|
tick()
|
||||||
{
|
{
|
||||||
return execute_expired_tasks(clock->now());
|
return tick(clock.now());
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
execute_expired_tasks(std::chrono::system_clock::time_point now);
|
tick(std::chrono::system_clock::time_point now);
|
||||||
|
|
||||||
std::chrono::system_clock::duration
|
std::chrono::system_clock::duration
|
||||||
time_until_next() const;
|
time_until_next() const;
|
||||||
|
|
||||||
std::shared_ptr<ICronClock> get_clock() const { return clock; }
|
ClockType& get_clock()
|
||||||
|
{
|
||||||
|
return clock;
|
||||||
|
}
|
||||||
|
|
||||||
|
friend std::ostream& operator<<<>(std::ostream& stream, const Cron<ClockType>& c);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Priority queue placing smallest (i.e. nearest in time) items on top.
|
class Queue
|
||||||
std::priority_queue<Task, std::vector<Task>, std::greater<>> tasks{};
|
// Priority queue placing smallest (i.e. nearest in time) items on top.
|
||||||
void print_queue(std::priority_queue<Task, std::vector<Task>, std::greater<>> queue);
|
: public std::priority_queue<Task, std::vector<Task>, std::greater<>>
|
||||||
std::shared_ptr<ICronClock> clock{};
|
{
|
||||||
|
public:
|
||||||
|
// Inherit to allow access to the container.
|
||||||
|
const std::vector<Task>& get_tasks() const
|
||||||
|
{
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Task>& get_tasks()
|
||||||
|
{
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Queue tasks{};
|
||||||
|
ClockType clock{};
|
||||||
|
bool first_tick = true;
|
||||||
|
std::chrono::system_clock::time_point last_tick{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<typename ClockType>
|
||||||
|
bool Cron<ClockType>::add_schedule(std::string name, const std::string& schedule, std::function<void()> work)
|
||||||
|
{
|
||||||
|
auto cron = CronData::create(schedule);
|
||||||
|
bool res = cron.is_valid();
|
||||||
|
if (res)
|
||||||
|
{
|
||||||
|
|
||||||
|
Task t{std::move(name), CronSchedule{cron}, std::move(work)};
|
||||||
|
if (t.calculate_next(clock.now()))
|
||||||
|
{
|
||||||
|
tasks.push(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ClockType>
|
||||||
|
std::chrono::system_clock::duration Cron<ClockType>::time_until_next() const
|
||||||
|
{
|
||||||
|
system_clock::duration d{};
|
||||||
|
if (tasks.empty())
|
||||||
|
{
|
||||||
|
d = std::numeric_limits<minutes>::max();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
d = tasks.top().time_until_expiry(clock.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ClockType>
|
||||||
|
size_t Cron<ClockType>::tick(system_clock::time_point now)
|
||||||
|
{
|
||||||
|
size_t res = 0;
|
||||||
|
|
||||||
|
if (first_tick)
|
||||||
|
{
|
||||||
|
first_tick = false;
|
||||||
|
}
|
||||||
|
else if (now - last_tick < hours{3})
|
||||||
|
{
|
||||||
|
// Reschedule all tasks.
|
||||||
|
for (auto& t : tasks.get_tasks())
|
||||||
|
{
|
||||||
|
t.calculate_next(now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(now < last_tick && now >= last_tick - hours{3})
|
||||||
|
{
|
||||||
|
// Prevent tasks from running until the clock has reached current 'last_tick'.
|
||||||
|
for (auto& t : tasks.get_tasks())
|
||||||
|
{
|
||||||
|
//t.set_back_limit(last_tick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
last_tick = 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ClockType>
|
||||||
|
std::ostream& operator<<(std::ostream& stream, const Cron<ClockType>& c)
|
||||||
|
{
|
||||||
|
std::for_each(c.tasks.get_tasks().cbegin(), c.tasks.get_tasks().cend(),
|
||||||
|
[&stream, &c](const Task& t)
|
||||||
|
{
|
||||||
|
stream << t.get_status(c.clock.now()) << '\n';
|
||||||
|
});
|
||||||
|
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
}
|
}
|
@ -10,20 +10,20 @@ namespace libcron
|
|||||||
class ICronClock
|
class ICronClock
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual std::chrono::system_clock::time_point now() = 0;
|
virtual std::chrono::system_clock::time_point now() const = 0;
|
||||||
virtual std::chrono::seconds utc_offset(std::chrono::system_clock::time_point now) = 0;
|
virtual std::chrono::seconds utc_offset(std::chrono::system_clock::time_point now) const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class UTCClock
|
class UTCClock
|
||||||
: public ICronClock
|
: public ICronClock
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
std::chrono::system_clock::time_point now() override
|
std::chrono::system_clock::time_point now() const override
|
||||||
{
|
{
|
||||||
return std::chrono::system_clock::now();
|
return std::chrono::system_clock::now();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::chrono::seconds utc_offset(std::chrono::system_clock::time_point) override
|
std::chrono::seconds utc_offset(std::chrono::system_clock::time_point) const override
|
||||||
{
|
{
|
||||||
return 0s;
|
return 0s;
|
||||||
}
|
}
|
||||||
@ -33,13 +33,13 @@ namespace libcron
|
|||||||
: public ICronClock
|
: public ICronClock
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
std::chrono::system_clock::time_point now() override
|
std::chrono::system_clock::time_point now() const override
|
||||||
{
|
{
|
||||||
auto now = system_clock::now();
|
auto now = system_clock::now();
|
||||||
return now + utc_offset(now);
|
return now + utc_offset(now);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::chrono::seconds utc_offset(std::chrono::system_clock::time_point now) override
|
std::chrono::seconds utc_offset(std::chrono::system_clock::time_point now) const override
|
||||||
{
|
{
|
||||||
auto t = system_clock::to_time_t(now);
|
auto t = system_clock::to_time_t(now);
|
||||||
tm tm{};
|
tm tm{};
|
||||||
|
@ -56,7 +56,7 @@ namespace libcron
|
|||||||
s+= std::to_string(dt.day) + " ";
|
s+= std::to_string(dt.day) + " ";
|
||||||
s+= std::to_string(dt.hour) + ":";
|
s+= std::to_string(dt.hour) + ":";
|
||||||
s+= std::to_string(dt.min) + ":";
|
s+= std::to_string(dt.min) + ":";
|
||||||
s+= std::to_string(dt.sec) + " UTC";
|
s+= std::to_string(dt.sec);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -16,3 +16,8 @@ add_executable(
|
|||||||
CronScheduleTest.cpp CronTest.cpp)
|
CronScheduleTest.cpp CronTest.cpp)
|
||||||
|
|
||||||
target_link_libraries(${PROJECT_NAME} libcron)
|
target_link_libraries(${PROJECT_NAME} libcron)
|
||||||
|
|
||||||
|
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||||
|
ARCHIVE_OUTPUT_DIRECTORY "${OUTPUT_LOCATION}"
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY "${OUTPUT_LOCATION}"
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY "${OUTPUT_LOCATION}")
|
@ -19,190 +19,260 @@ std::string create_schedule_expiring_in(std::chrono::system_clock::time_point no
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//SCENARIO("Adding a task")
|
||||||
SCENARIO("Adding a task")
|
|
||||||
{
|
|
||||||
GIVEN("A Cron instance with no task")
|
|
||||||
{
|
|
||||||
Cron c;
|
|
||||||
auto expired = false;
|
|
||||||
|
|
||||||
THEN("Starts with no task")
|
|
||||||
{
|
|
||||||
REQUIRE(c.count() == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
WHEN("Adding a task that runs every second")
|
|
||||||
{
|
|
||||||
REQUIRE(c.add_schedule("A task", "* * * * * ?",
|
|
||||||
[&expired]()
|
|
||||||
{
|
|
||||||
expired = true;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
THEN("Count is 1 and task was not expired two seconds ago")
|
|
||||||
{
|
|
||||||
REQUIRE(c.count() == 1);
|
|
||||||
c.execute_expired_tasks(c.get_clock()->now() - 2s);
|
|
||||||
REQUIRE_FALSE(expired);
|
|
||||||
}
|
|
||||||
AND_THEN("Task is expired when calculating based on current time")
|
|
||||||
{
|
|
||||||
c.execute_expired_tasks();
|
|
||||||
THEN("Task is expired")
|
|
||||||
{
|
|
||||||
REQUIRE(expired);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SCENARIO("Adding a task that expires in the future")
|
|
||||||
{
|
|
||||||
GIVEN("A Cron instance with task expiring in 3 seconds")
|
|
||||||
{
|
|
||||||
auto expired = false;
|
|
||||||
|
|
||||||
Cron c;
|
|
||||||
REQUIRE(c.add_schedule("A task", create_schedule_expiring_in(c.get_clock()->now(), hours{0}, minutes{0}, seconds{3}),
|
|
||||||
[&expired]()
|
|
||||||
{
|
|
||||||
expired = true;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
THEN("Not yet expired")
|
|
||||||
{
|
|
||||||
REQUIRE_FALSE(expired);
|
|
||||||
}
|
|
||||||
AND_WHEN("When waiting one second")
|
|
||||||
{
|
|
||||||
std::this_thread::sleep_for(1s);
|
|
||||||
c.execute_expired_tasks();
|
|
||||||
THEN("Task has not yet expired")
|
|
||||||
{
|
|
||||||
REQUIRE_FALSE(expired);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AND_WHEN("When waiting three seconds")
|
|
||||||
{
|
|
||||||
std::this_thread::sleep_for(3s);
|
|
||||||
c.execute_expired_tasks();
|
|
||||||
THEN("Task has expired")
|
|
||||||
{
|
|
||||||
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(c.get_clock()->now(), hours{0}, minutes{0}, seconds{5}),
|
|
||||||
[&_5_second_expired]()
|
|
||||||
{
|
|
||||||
_5_second_expired++;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
REQUIRE(c.add_schedule("Three", create_schedule_expiring_in(c.get_clock()->now(), 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//SCENARIO("Clock changes")
|
|
||||||
//{
|
//{
|
||||||
// GIVEN("A Cron instance with a single task expiring in 4h")
|
// GIVEN("A Cron instance with no task")
|
||||||
// {
|
// {
|
||||||
// Cron c;
|
// Cron<> c;
|
||||||
// auto clock = c.get_clock();
|
// auto expired = false;
|
||||||
// system_clock::time_point time_from_task;
|
//
|
||||||
// auto now = clock->now();
|
// THEN("Starts with no task")
|
||||||
// REQUIRE(c.add_schedule("Task", create_schedule_expiring_in(now, hours{4}, minutes{0}, seconds{0}),
|
// {
|
||||||
// [clock, &time_from_task]()
|
// REQUIRE(c.count() == 0);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// WHEN("Adding a task that runs every second")
|
||||||
|
// {
|
||||||
|
// REQUIRE(c.add_schedule("A task", "* * * * * ?",
|
||||||
|
// [&expired]()
|
||||||
|
// {
|
||||||
|
// expired = true;
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// THEN("Count is 1 and task was not expired two seconds ago")
|
||||||
|
// {
|
||||||
|
// REQUIRE(c.count() == 1);
|
||||||
|
// c.tick(c.get_clock().now() - 2s);
|
||||||
|
// REQUIRE_FALSE(expired);
|
||||||
|
// }
|
||||||
|
// AND_THEN("Task is expired when calculating based on current time")
|
||||||
|
// {
|
||||||
|
// c.tick();
|
||||||
|
// THEN("Task is expired")
|
||||||
|
// {
|
||||||
|
// REQUIRE(expired);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//SCENARIO("Adding a task that expires in the future")
|
||||||
|
//{
|
||||||
|
// GIVEN("A Cron instance with task expiring in 3 seconds")
|
||||||
|
// {
|
||||||
|
// auto expired = false;
|
||||||
|
//
|
||||||
|
// Cron<> c;
|
||||||
|
// REQUIRE(c.add_schedule("A task",
|
||||||
|
// create_schedule_expiring_in(c.get_clock().now(), hours{0}, minutes{0}, seconds{3}),
|
||||||
|
// [&expired]()
|
||||||
// {
|
// {
|
||||||
// time_from_task = clock->now();
|
// expired = true;
|
||||||
// })
|
// })
|
||||||
// );
|
// );
|
||||||
//
|
//
|
||||||
//
|
// THEN("Not yet expired")
|
||||||
|
// {
|
||||||
|
// REQUIRE_FALSE(expired);
|
||||||
|
// }
|
||||||
|
// AND_WHEN("When waiting one second")
|
||||||
|
// {
|
||||||
|
// std::this_thread::sleep_for(1s);
|
||||||
|
// c.tick();
|
||||||
|
// THEN("Task has not yet expired")
|
||||||
|
// {
|
||||||
|
// REQUIRE_FALSE(expired);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// AND_WHEN("When waiting three seconds")
|
||||||
|
// {
|
||||||
|
// std::this_thread::sleep_for(3s);
|
||||||
|
// c.tick();
|
||||||
|
// THEN("Task has expired")
|
||||||
|
// {
|
||||||
|
// 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(c.get_clock().now(), hours{0}, minutes{0}, seconds{5}),
|
||||||
|
// [&_5_second_expired]()
|
||||||
|
// {
|
||||||
|
// _5_second_expired++;
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// REQUIRE(c.add_schedule("Three",
|
||||||
|
// create_schedule_expiring_in(c.get_clock().now(), 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.tick();
|
||||||
|
//
|
||||||
|
// 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.tick();
|
||||||
|
//
|
||||||
|
// 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.tick();
|
||||||
|
//
|
||||||
|
// 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.tick();
|
||||||
|
//
|
||||||
|
// 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.tick() == 1);
|
||||||
|
//
|
||||||
|
// std::this_thread::sleep_for(c.time_until_next());
|
||||||
|
// REQUIRE(c.tick() == 1);
|
||||||
|
//
|
||||||
|
// THEN("3 and 5 second task has each expired once")
|
||||||
|
// {
|
||||||
|
// REQUIRE(_3_second_expired == 1);
|
||||||
|
// REQUIRE(_5_second_expired == 1);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
class TestClock
|
||||||
|
: public ICronClock
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::chrono::system_clock::time_point now() const override
|
||||||
|
{
|
||||||
|
return current_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::chrono::seconds utc_offset(std::chrono::system_clock::time_point) const override
|
||||||
|
{
|
||||||
|
return 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(system_clock::duration time)
|
||||||
|
{
|
||||||
|
current_time += time;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(system_clock::time_point new_time)
|
||||||
|
{
|
||||||
|
current_time = new_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
system_clock::time_point current_time = system_clock::now();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
SCENARIO("Clock changes")
|
||||||
|
{
|
||||||
|
GIVEN("A Cron instance with a single task expiring in 4h")
|
||||||
|
{
|
||||||
|
Cron<TestClock> c{};
|
||||||
|
auto& clock = c.get_clock();
|
||||||
|
|
||||||
|
// Midnight
|
||||||
|
clock.set(sys_days{2018_y / 05 / 05});
|
||||||
|
|
||||||
|
// Every hour
|
||||||
|
REQUIRE(c.add_schedule("Clock change task", "0 0 * * * ?", []()
|
||||||
|
{
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// https://linux.die.net/man/8/cron
|
||||||
|
|
||||||
|
WHEN("Clock changes <3h forward")
|
||||||
|
{
|
||||||
|
THEN("Task expires accordingly")
|
||||||
|
{
|
||||||
|
REQUIRE(c.tick() == 1);
|
||||||
|
clock.add(minutes{30}); // 00:30
|
||||||
|
REQUIRE(c.tick() == 0);
|
||||||
|
clock.add(minutes{30}); // 01:00
|
||||||
|
REQUIRE(c.tick() == 1);
|
||||||
|
REQUIRE(c.tick() == 0);
|
||||||
|
REQUIRE(c.tick() == 0);
|
||||||
|
clock.add(minutes{30}); // 01:30
|
||||||
|
REQUIRE(c.tick() == 0);
|
||||||
|
clock.add(minutes{15}); // 01:45
|
||||||
|
REQUIRE(c.tick() == 0);
|
||||||
|
clock.add(minutes{15}); // 02:00
|
||||||
|
REQUIRE(c.tick() == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AND_WHEN("Clock is moved forward >= 3h")
|
||||||
|
{
|
||||||
|
THEN("Task are rescheduled, not run")
|
||||||
|
{
|
||||||
|
REQUIRE(c.tick() == 1);
|
||||||
|
std::cout << c << std::endl;
|
||||||
|
clock.add(hours{3}); // 03:00
|
||||||
|
REQUIRE(c.tick() == 1); // Rescheduled
|
||||||
|
std::cout << c << std::endl;
|
||||||
|
clock.add(minutes{15}); // 03:15
|
||||||
|
REQUIRE(c.tick() == 1);
|
||||||
|
std::cout << c << std::endl;
|
||||||
|
clock.add(minutes{45}); // 04:00
|
||||||
|
REQUIRE(c.tick() == 1);
|
||||||
|
std::cout << c << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user