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; bool res = true;
// Verify that the available dates are possible based on the given months // 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); res = has_any_in_range(day_of_month, 1, 29);
} }
@ -102,8 +102,7 @@ namespace libcron
res = false; res = false;
for(size_t i = 0; !res && i < months_with_31.size(); ++i) for(size_t i = 0; !res && i < months_with_31.size(); ++i)
{ {
auto month = months_with_31[i]; res = months.find(static_cast<Months>(months_with_31[i])) != months.end();
res = months.find(static_cast<Months>(month)) != months.end();
} }
} }
} }

View File

@ -295,7 +295,7 @@ namespace libcron
{ {
bool res = false; bool res = false;
auto value_range = R"#((\d+)/(\d+))#"; auto value_range = R"#((\d+|\*)/(\d+))#";
std::regex range(value_range, std::regex_constants::ECMAScript); 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)) 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()); auto raw_step = std::stoi(match[2].str().c_str());
if (is_within_limits<T>(raw_start, raw_start) && raw_step > 0) if (is_within_limits<T>(raw_start, raw_start) && raw_step > 0)

View File

@ -6,17 +6,17 @@ using namespace date;
namespace libcron namespace libcron
{ {
std::chrono::system_clock::time_point std::tuple<bool, std::chrono::system_clock::time_point>
CronSchedule::calculate_from(const std::chrono::system_clock::time_point& from) CronSchedule::calculate_from(const std::chrono::system_clock::time_point& from) const
{ {
//auto time_part = from - date::floor<days>(from); auto curr = from;
auto curr = from;// - time_part;
//auto dt = to_calendar_time(curr);
bool done = false; 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); year_month_day ymd = date::floor<days>(curr);
// Add months until one of the allowed days are found, or stay at the current one. // 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}; auto next_month = ymd + months{1};
sys_days s = next_month.year() / next_month.month() / 1; sys_days s = next_month.year() / next_month.month() / 1;
curr = s; curr = s;
continue; date_changed = true;
} }
// If all days are allowed, then the 'day of week' takes precedence, which also means that // 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. // day of week only is ignored when specific days of months are specified.
@ -38,7 +38,7 @@ namespace libcron
sys_days s = ymd; sys_days s = ymd;
curr = s; curr = s;
curr += days{1}; curr += days{1};
continue; date_changed = true;
} }
} }
else else
@ -52,33 +52,35 @@ namespace libcron
sys_days s = ymd; sys_days s = ymd;
curr = s; curr = s;
curr += days{1}; curr += days{1};
continue; date_changed = true;
} }
} }
//curr += time_part; if(!date_changed)
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}; auto date_time = to_calendar_time(curr);
curr -= minutes{date_time.min}; if (data.get_hours().find(static_cast<Hours>(date_time.hour)) == data.get_hours().end())
curr -= seconds{date_time.sec}; {
} curr += hours{1};
else if (data.get_minutes().find(static_cast<Minutes >(date_time.min)) == data.get_minutes().end()) curr -= minutes{date_time.min};
{ curr -= seconds{date_time.sec};
curr += minutes{1}; }
curr -= seconds{date_time.sec}; else if (data.get_minutes().find(static_cast<Minutes >(date_time.min)) == data.get_minutes().end())
} {
else if (data.get_seconds().find(static_cast<Seconds>(date_time.sec)) == data.get_seconds().end()) curr += minutes{1};
{ curr -= seconds{date_time.sec};
curr += seconds{1}; }
} else if (data.get_seconds().find(static_cast<Seconds>(date_time.sec)) == data.get_seconds().end())
else {
{ curr += seconds{1};
done = true; }
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 // 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) static DateTime to_calendar_time(std::chrono::system_clock::time_point time)
{ {
auto daypoint = date::floor<date::days>(time); auto daypoint = date::floor<date::days>(time);
auto ymd = date::year_month_day(daypoint); // calendar date 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 // Obtain individual components as integers
DateTime dt{ DateTime dt{
int(ymd.year()), int(ymd.year()),
unsigned(ymd.month()), unsigned(ymd.month()),
unsigned(ymd.day()), unsigned(ymd.day()),
static_cast<uint8_t>(tod.hours().count()), static_cast<uint8_t>(time_of_day.hours().count()),
static_cast<uint8_t>(tod.minutes().count()), static_cast<uint8_t>(time_of_day.minutes().count()),
static_cast<uint8_t>(tod.seconds().count())}; static_cast<uint8_t>(time_of_day.seconds().count())};
return dt; 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; 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) bool test(const std::string& schedule, system_clock::time_point from, system_clock::time_point expected_next)
{ {
auto c = CronData::create(schedule); auto c = CronData::create(schedule);
@ -22,8 +59,9 @@ bool test(const std::string& schedule, system_clock::time_point from, system_clo
if (res) if (res)
{ {
CronSchedule sched(c); CronSchedule sched(c);
auto run_time = sched.calculate_from(from); auto result = sched.calculate_from(from);
res &= expected_next == run_time; auto run_time = std::get<1>(result);
res &= std::get<0>(result) && expected_next == run_time;
if (!res) if (!res)
{ {
@ -31,32 +69,115 @@ bool test(const std::string& schedule, system_clock::time_point from, system_clo
<< "From: " << from << "\n" << "From: " << from << "\n"
<< "Expected: " << expected_next << "\n" << "Expected: " << expected_next << "\n"
<< "Calculated: " << run_time; << "Calculated: " << run_time;
} }
} }
return res; return res;
} }
SCENARIO("Calculating next runtime") 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), 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{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}), 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(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 * * * *", DT(2017_y / 12 / 31, hours{23}, minutes{59}, seconds{58}),
REQUIRE(test("0 0 10 * * *", DT(2017_y / 12 / 31, hours{9}, minutes{59}, seconds{58}), DT(2017_y / 12 / 31, hours{10}))); DT(2018_y / 1 / 1, hours{0})));
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 * * *", DT(2017_y / 12 / 31, hours{9}, minutes{59}, seconds{58}),
REQUIRE(test("0 0 10 * FEB *", DT(2017_y / 12 / 31, hours{23}, minutes{59}, seconds{58}), DT(2018_y / 2 / 1, hours{10}))); DT(2017_y / 12 / 31, 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 * * *", DT(2017_y / 12 / 31, hours{23}, minutes{59}, seconds{58}),
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}))); DT(2018_y / 1 / 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("0 0 10 * FEB *", DT(2017_y / 12 / 31, hours{23}, minutes{59}, seconds{58}),
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}))); 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))); REQUIRE(test("0 0 * 31 APR,MAY *", DT(2017_y / 6 / 1), DT(2018_y / may / 31)));
} }
SCENARIO("Leap year") 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(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)));
} }