More tests.

Fix for february.
Limit iterations in calculate_from.
This commit is contained in:
Per Malmberg 2018-03-10 21:01:20 +01:00
parent cb6a7958e8
commit 20667ae3c6
5 changed files with 184 additions and 52 deletions

View File

@ -78,9 +78,9 @@ namespace libcron
bool res = true;
// Verify that the available dates are possible based on the given months
if (months.find(static_cast<Months>(2)) != months.end())
if (months.size() == 1 && months.find(static_cast<Months>(2)) != months.end())
{
// February allowed, make sure that the allowed date(s) includes 29 and below.
// Only february allowed, make sure that the allowed date(s) includes 29 and below.
res = has_any_in_range(day_of_month, 1, 29);
}
@ -102,8 +102,7 @@ namespace libcron
res = false;
for(size_t i = 0; !res && i < months_with_31.size(); ++i)
{
auto month = months_with_31[i];
res = months.find(static_cast<Months>(month)) != months.end();
res = months.find(static_cast<Months>(months_with_31[i])) != months.end();
}
}
}

View File

@ -295,7 +295,7 @@ namespace libcron
{
bool res = false;
auto value_range = R"#((\d+)/(\d+))#";
auto value_range = R"#((\d+|\*)/(\d+))#";
std::regex range(value_range, std::regex_constants::ECMAScript);
@ -303,7 +303,16 @@ namespace libcron
if (std::regex_match(s.begin(), s.end(), match, range))
{
auto raw_start = std::stoi(match[1].str().c_str());
int raw_start;
if(match[1].str() == "*")
{
raw_start = value_of(T::First);
}
else
{
raw_start = std::stoi(match[1].str().c_str());
}
auto raw_step = std::stoi(match[2].str().c_str());
if (is_within_limits<T>(raw_start, raw_start) && raw_step > 0)

View File

@ -6,17 +6,17 @@ using namespace date;
namespace libcron
{
std::chrono::system_clock::time_point
CronSchedule::calculate_from(const std::chrono::system_clock::time_point& from)
std::tuple<bool, std::chrono::system_clock::time_point>
CronSchedule::calculate_from(const std::chrono::system_clock::time_point& from) const
{
//auto time_part = from - date::floor<days>(from);
auto curr = from;// - time_part;
//auto dt = to_calendar_time(curr);
auto curr = from;
bool done = false;
auto max_iterations = std::numeric_limits<uint16_t>::max();
while (!done)
while (!done && --max_iterations > 0)
{
bool date_changed = false;
year_month_day ymd = date::floor<days>(curr);
// Add months until one of the allowed days are found, or stay at the current one.
@ -25,7 +25,7 @@ namespace libcron
auto next_month = ymd + months{1};
sys_days s = next_month.year() / next_month.month() / 1;
curr = s;
continue;
date_changed = true;
}
// If all days are allowed, then the 'day of week' takes precedence, which also means that
// day of week only is ignored when specific days of months are specified.
@ -38,7 +38,7 @@ namespace libcron
sys_days s = ymd;
curr = s;
curr += days{1};
continue;
date_changed = true;
}
}
else
@ -52,33 +52,35 @@ namespace libcron
sys_days s = ymd;
curr = s;
curr += days{1};
continue;
date_changed = true;
}
}
//curr += time_part;
auto date_time = to_calendar_time(curr);
if (data.get_hours().find(static_cast<Hours>(date_time.hour)) == data.get_hours().end())
if(!date_changed)
{
curr += hours{1};
curr -= minutes{date_time.min};
curr -= seconds{date_time.sec};
}
else if (data.get_minutes().find(static_cast<Minutes >(date_time.min)) == data.get_minutes().end())
{
curr += minutes{1};
curr -= seconds{date_time.sec};
}
else if (data.get_seconds().find(static_cast<Seconds>(date_time.sec)) == data.get_seconds().end())
{
curr += seconds{1};
}
else
{
done = true;
auto date_time = to_calendar_time(curr);
if (data.get_hours().find(static_cast<Hours>(date_time.hour)) == data.get_hours().end())
{
curr += hours{1};
curr -= minutes{date_time.min};
curr -= seconds{date_time.sec};
}
else if (data.get_minutes().find(static_cast<Minutes >(date_time.min)) == data.get_minutes().end())
{
curr += minutes{1};
curr -= seconds{date_time.sec};
}
else if (data.get_seconds().find(static_cast<Seconds>(date_time.sec)) == data.get_seconds().end())
{
curr += seconds{1};
}
else
{
done = true;
}
}
}
return curr;
return std::make_tuple(max_iterations > 0, max_iterations > 0 ? curr : system_clock::now());
}
}

View File

@ -15,23 +15,24 @@ namespace libcron
{
}
std::chrono::system_clock::time_point calculate_from(const std::chrono::system_clock::time_point& from);
std::tuple<bool, std::chrono::system_clock::time_point>
calculate_from(const std::chrono::system_clock::time_point& from) const;
// https://github.com/HowardHinnant/date/wiki/Examples-and-Recipes#obtaining-ymd-hms-components-from-a-time_point
static DateTime to_calendar_time(std::chrono::system_clock::time_point time)
{
auto daypoint = date::floor<date::days>(time);
auto ymd = date::year_month_day(daypoint); // calendar date
auto tod = date::make_time(time - daypoint); // Yields time_of_day type
auto time_of_day = date::make_time(time - daypoint); // Yields time_of_day type
// Obtain individual components as integers
DateTime dt{
int(ymd.year()),
unsigned(ymd.month()),
unsigned(ymd.day()),
static_cast<uint8_t>(tod.hours().count()),
static_cast<uint8_t>(tod.minutes().count()),
static_cast<uint8_t>(tod.seconds().count())};
static_cast<uint8_t>(time_of_day.hours().count()),
static_cast<uint8_t>(time_of_day.minutes().count()),
static_cast<uint8_t>(time_of_day.seconds().count())};
return dt;
}

View File

@ -15,6 +15,43 @@ system_clock::time_point DT(year_month_day ymd, hours h = hours{0}, minutes m =
return sum;
}
bool test(const std::string& schedule, system_clock::time_point from,
std::vector<system_clock::time_point> expected_next)
{
auto c = CronData::create(schedule);
bool res = c.is_valid();
if (res)
{
CronSchedule sched(c);
auto curr_from = from;
for (size_t i = 0; res && i < expected_next.size(); ++i)
{
auto result = sched.calculate_from(curr_from);
auto calculated = std::get<1>(result);
res = std::get<0>(result) && calculated == expected_next[i];
if (res)
{
// Add a second to the time so that we move on to the next expected time
// and don't get locked on the current one.
curr_from = expected_next[i] + seconds{1};
}
else
{
std::cout
<< "From: " << curr_from << "\n"
<< "Expected: " << expected_next[i] << "\n"
<< "Calculated: " << calculated;
}
}
}
return res;
}
bool test(const std::string& schedule, system_clock::time_point from, system_clock::time_point expected_next)
{
auto c = CronData::create(schedule);
@ -22,8 +59,9 @@ bool test(const std::string& schedule, system_clock::time_point from, system_clo
if (res)
{
CronSchedule sched(c);
auto run_time = sched.calculate_from(from);
res &= expected_next == run_time;
auto result = sched.calculate_from(from);
auto run_time = std::get<1>(result);
res &= std::get<0>(result) && expected_next == run_time;
if (!res)
{
@ -31,32 +69,115 @@ bool test(const std::string& schedule, system_clock::time_point from, system_clo
<< "From: " << from << "\n"
<< "Expected: " << expected_next << "\n"
<< "Calculated: " << run_time;
}
}
return res;
}
SCENARIO("Calculating next runtime")
{
REQUIRE(test("0 0 * * * *", DT(2010_y / 1 / 1), DT(2010_y / 1 / 1, hours{0})));
REQUIRE(test("0 0 * * * *", DT(2010_y / 1 / 1, hours{0}, minutes{0}, seconds{1}), DT(2010_y / 1 / 1, hours{1})));
REQUIRE(test("0 0 * * * *", DT(2010_y / 1 / 1, hours{5}), DT(2010_y / 1 / 1, hours{5})));
REQUIRE(test("0 0 * * * *", DT(2010_y / 1 / 1, hours{5}, minutes{1}), DT(2010_y / 1 / 1, hours{6})));
REQUIRE(test("0 0 * * * *", DT(2017_y / 12 / 31, hours{23}, minutes{59}, seconds{58}), DT(2018_y / 1 / 1, hours{0})));
REQUIRE(test("0 0 10 * * *", DT(2017_y / 12 / 31, hours{9}, minutes{59}, seconds{58}), DT(2017_y / 12 / 31, hours{10})));
REQUIRE(test("0 0 10 * * *", DT(2017_y / 12 / 31, hours{23}, minutes{59}, seconds{58}), DT(2018_y / 1 / 1, hours{10})));
REQUIRE(test("0 0 10 * FEB *", DT(2017_y / 12 / 31, hours{23}, minutes{59}, seconds{58}), DT(2018_y / 2 / 1, hours{10})));
REQUIRE(test("0 0 10 25 FEB *", DT(2017_y / 12 / 31, hours{23}, minutes{59}, seconds{58}), DT(2018_y / 2 / 25, hours{10})));
REQUIRE(test("0 0 10 * FEB 1", DT(2017_y / 12 / 31, hours{23}, minutes{59}, seconds{58}), DT(year_month_day{2018_y/2/mon[1]}, hours{10})));
REQUIRE(test("0 0 10 * FEB 6", DT(2017_y / 12 / 31, hours{23}, minutes{59}, seconds{58}), DT(year_month_day{2018_y/2/sat[1]}, hours{10})));
REQUIRE(test("* * * 10-12 NOV *", DT(2018_y / 11 / 11, hours{10}, minutes{11}, seconds{12}), DT(year_month_day{2018_y/11/11}, hours{10}, minutes{11}, seconds{12})));
REQUIRE(test("0 0 * * * *", DT(2017_y / 12 / 31, hours{23}, minutes{59}, seconds{58}),
DT(2018_y / 1 / 1, hours{0})));
REQUIRE(test("0 0 10 * * *", DT(2017_y / 12 / 31, hours{9}, minutes{59}, seconds{58}),
DT(2017_y / 12 / 31, hours{10})));
REQUIRE(test("0 0 10 * * *", DT(2017_y / 12 / 31, hours{23}, minutes{59}, seconds{58}),
DT(2018_y / 1 / 1, hours{10})));
REQUIRE(test("0 0 10 * FEB *", DT(2017_y / 12 / 31, hours{23}, minutes{59}, seconds{58}),
DT(2018_y / 2 / 1, hours{10})));
REQUIRE(test("0 0 10 25 FEB *", DT(2017_y / 12 / 31, hours{23}, minutes{59}, seconds{58}),
DT(2018_y / 2 / 25, hours{10})));
REQUIRE(test("0 0 10 * FEB 1", DT(2017_y / 12 / 31, hours{23}, minutes{59}, seconds{58}),
DT(year_month_day{2018_y / 2 / mon[1]}, hours{10})));
REQUIRE(test("0 0 10 * FEB 6", DT(2017_y / 12 / 31, hours{23}, minutes{59}, seconds{58}),
DT(year_month_day{2018_y / 2 / sat[1]}, hours{10})));
REQUIRE(test("* * * 10-12 NOV *", DT(2018_y / 11 / 11, hours{10}, minutes{11}, seconds{12}),
DT(year_month_day{2018_y / 11 / 11}, hours{10}, minutes{11}, seconds{12})));
REQUIRE(test("0 0 * 31 APR,MAY *", DT(2017_y / 6 / 1), DT(2018_y / may / 31)));
}
SCENARIO("Leap year")
{
REQUIRE(test("0 0 * 29 FEB *", DT(2015_y / 1 / 1), DT(2016_y / 2 / 29)));
REQUIRE(test("0 0 * 29 FEB *", DT(2018_y / 1 / 1), DT(2020_y / 2 / 29)));
REQUIRE(test("0 0 * 29 FEB *", DT(2020_y / 2 / 29, hours{15}, minutes{13}, seconds{13}),
DT(2020_y / 2 / 29, hours{16})));
}
SCENARIO("Multiple calculations")
{
WHEN("Every 15 minutes, every 2nd hour")
{
REQUIRE(test("0 0/15 0/2 * * *", DT(2018_y / 1 / 1, hours{13}, minutes{14}, seconds{59}),
{DT(2018_y / 1 / 1, hours{14}, minutes{00}),
DT(2018_y / 1 / 1, hours{14}, minutes{15}),
DT(2018_y / 1 / 1, hours{14}, minutes{30}),
DT(2018_y / 1 / 1, hours{14}, minutes{45}),
DT(2018_y / 1 / 1, hours{16}, minutes{00}),
DT(2018_y / 1 / 1, hours{16}, minutes{15})}));
}
WHEN("Every top of the hour, every 12th hour, during 12 and 13:th July")
{
REQUIRE(test("0 0 0/12 12-13 JUL *", DT(2018_y / 1 / 1),
{DT(2018_y / 7 / 12, hours{0}),
DT(2018_y / 7 / 12, hours{12}),
DT(2018_y / 7 / 13, hours{0}),
DT(2018_y / 7 / 13, hours{12}),
DT(2019_y / 7 / 12, hours{0}),
DT(2019_y / 7 / 12, hours{12})}));
}
WHEN("Every first of the month, 15h, every second month, 22m")
{
REQUIRE(test("0 22 15 1 * *", DT(2018_y / 1 / 1),
{DT(2018_y / 1 / 1, hours{15}, minutes{22}),
DT(2018_y / 2 / 1, hours{15}, minutes{22}),
DT(2018_y / 3 / 1, hours{15}, minutes{22}),
DT(2018_y / 4 / 1, hours{15}, minutes{22}),
DT(2018_y / 5 / 1, hours{15}, minutes{22}),
DT(2018_y / 6 / 1, hours{15}, minutes{22}),
DT(2018_y / 7 / 1, hours{15}, minutes{22}),
DT(2018_y / 8 / 1, hours{15}, minutes{22}),
DT(2018_y / 9 / 1, hours{15}, minutes{22}),
DT(2018_y / 10 / 1, hours{15}, minutes{22}),
DT(2018_y / 11 / 1, hours{15}, minutes{22}),
DT(2018_y / 12 / 1, hours{15}, minutes{22}),
DT(2019_y / 1 / 1, hours{15}, minutes{22})}));
}
WHEN("“At minute 0 past hour 0 and 12 on day-of-month 1 in every 2nd month")
{
// Note that day-of-week, 5, is not in effect in this schedule.
REQUIRE(test("0 0 0,12 1 */2 5", DT(2018_y / 3 / 10, hours{16}, minutes{51}), DT(2018_y / 5 / 1)));
}
WHEN("“At 00:05 in August")
{
// Note that day-of-week, 5, is not in effect in this schedule.
REQUIRE(test("0 5 0 * 8 *", DT(2018_y / 3 / 10, hours{16}, minutes{51}),
DT(2018_y / 8 / 1, hours{0}, minutes{5})));
}
WHEN("At 22:00 on every day-of-week from Monday through Friday")
{
// Note that day-of-week, 5, is not in effect in this schedule.
REQUIRE(test("0 0 22 * * 1-5", DT(2021_y / 12 / 15, hours{16}, minutes{51}),
{DT(2021_y / 12 / 15, hours{22}),
DT(2021_y / 12 / 16, hours{22}),
DT(2021_y / 12 / 17, hours{22}),
// 18-19 are weekend
DT(2021_y / 12 / 20, hours{22}),
DT(2021_y / 12 / 21, hours{22})}));
}
}
SCENARIO("Unable to calculate time point")
{
// TODO: Find a
//REQUIRE_FALSE(test("0 0 0 1 1 0", DT(2021_y / 12 / 15), DT(2022_y / 1 / 1)));
}