mirror of
https://github.com/PerMalmberg/libcron.git
synced 2025-04-22 08:23:04 -05:00
#1 - Randomization WiP.
This commit is contained in:
parent
6ed4bc3b2e
commit
70f55b8ce6
@ -1,7 +1,5 @@
|
|||||||
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)
|
||||||
|
|
||||||
|
@ -13,18 +13,20 @@ add_library(${PROJECT_NAME}
|
|||||||
include/libcron/Cron.h
|
include/libcron/Cron.h
|
||||||
include/libcron/CronClock.h
|
include/libcron/CronClock.h
|
||||||
include/libcron/CronData.h
|
include/libcron/CronData.h
|
||||||
|
include/libcron/CronRandomization.h
|
||||||
include/libcron/CronSchedule.h
|
include/libcron/CronSchedule.h
|
||||||
include/libcron/DateTime.h
|
include/libcron/DateTime.h
|
||||||
include/libcron/Task.h
|
include/libcron/Task.h
|
||||||
include/libcron/TimeTypes.h
|
include/libcron/TimeTypes.h
|
||||||
src/CronClock.cpp
|
src/CronClock.cpp
|
||||||
src/CronData.cpp
|
src/CronData.cpp
|
||||||
|
src/CronRandomization.cpp
|
||||||
src/CronSchedule.cpp
|
src/CronSchedule.cpp
|
||||||
src/Task.cpp)
|
src/Task.cpp)
|
||||||
|
|
||||||
target_include_directories(${PROJECT_NAME}
|
target_include_directories(${PROJECT_NAME}
|
||||||
PRIVATE ${CMAKE_CURRENT_LIST_DIR}/externals/date/include
|
PRIVATE ${CMAKE_CURRENT_LIST_DIR}/externals/date/include
|
||||||
PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include)
|
PUBLIC include)
|
||||||
|
|
||||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out"
|
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out"
|
||||||
|
@ -11,9 +11,12 @@ namespace libcron
|
|||||||
class CronData
|
class CronData
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
static const std::array<Months, 7> months_with_31;
|
||||||
|
|
||||||
static CronData create(const std::string& cron_expression);
|
static CronData create(const std::string& cron_expression);
|
||||||
|
|
||||||
CronData();
|
CronData();
|
||||||
|
|
||||||
CronData(const CronData&) = default;
|
CronData(const CronData&) = default;
|
||||||
|
|
||||||
bool is_valid() const
|
bool is_valid() const
|
||||||
@ -61,6 +64,7 @@ namespace libcron
|
|||||||
static bool has_any_in_range(const std::set<T>& set, uint8_t low, uint8_t high)
|
static bool has_any_in_range(const std::set<T>& set, uint8_t low, uint8_t high)
|
||||||
{
|
{
|
||||||
bool found = false;
|
bool found = false;
|
||||||
|
|
||||||
for (auto i = low; !found && i <= high; ++i)
|
for (auto i = low; !found && i <= high; ++i)
|
||||||
{
|
{
|
||||||
found |= set.find(static_cast<T>(i)) != set.end();
|
found |= set.find(static_cast<T>(i)) != set.end();
|
||||||
@ -69,8 +73,10 @@ namespace libcron
|
|||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
template<typename T>
|
||||||
|
bool convert_from_string_range_to_number_range(const std::string& range, std::set<T>& numbers);
|
||||||
|
|
||||||
|
private:
|
||||||
void parse(const std::string& cron_expression);
|
void parse(const std::string& cron_expression);
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
@ -142,6 +148,7 @@ namespace libcron
|
|||||||
for (const auto& name : names)
|
for (const auto& name : names)
|
||||||
{
|
{
|
||||||
std::regex m(name, std::regex_constants::ECMAScript | std::regex_constants::icase);
|
std::regex m(name, std::regex_constants::ECMAScript | std::regex_constants::icase);
|
||||||
|
|
||||||
for (auto& part : parts)
|
for (auto& part : parts)
|
||||||
{
|
{
|
||||||
std::string replaced;
|
std::string replaced;
|
||||||
@ -155,7 +162,6 @@ namespace libcron
|
|||||||
}
|
}
|
||||||
|
|
||||||
return process_parts(parts, numbers);
|
return process_parts(parts, numbers);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
@ -163,60 +169,9 @@ namespace libcron
|
|||||||
{
|
{
|
||||||
bool res = true;
|
bool res = true;
|
||||||
|
|
||||||
T left;
|
|
||||||
T right;
|
|
||||||
uint8_t step_start;
|
|
||||||
uint8_t step;
|
|
||||||
|
|
||||||
for (const auto& p : parts)
|
for (const auto& p : parts)
|
||||||
{
|
{
|
||||||
if (p == "*" || p == "?")
|
res &= convert_from_string_range_to_number_range(p, numbers);
|
||||||
{
|
|
||||||
// We treat the ignore-character '?' the same as the full range being allowed.
|
|
||||||
add_full_range<T>(numbers);
|
|
||||||
}
|
|
||||||
else if (is_number(p))
|
|
||||||
{
|
|
||||||
res &= add_number<T>(numbers, std::stoi(p));
|
|
||||||
}
|
|
||||||
else if (get_range<T>(p, left, right))
|
|
||||||
{
|
|
||||||
// A range can be written as both 1-22 or 22-1, meaning totally different ranges.
|
|
||||||
// First case is 1...22 while 22-1 is only four hours: 22, 23, 0, 1.
|
|
||||||
if (left <= right)
|
|
||||||
{
|
|
||||||
for (auto v = value_of(left); v <= value_of(right); ++v)
|
|
||||||
{
|
|
||||||
res &= add_number(numbers, v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 'left' and 'right' are not in value order. First, get values between 'left' and T::Last, inclusive
|
|
||||||
for (auto v = value_of(left); v <= value_of(T::Last); ++v)
|
|
||||||
{
|
|
||||||
res &= add_number(numbers, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next, get values between T::First and 'right', inclusive.
|
|
||||||
for (auto v = value_of(T::First); v <= value_of(right); ++v)
|
|
||||||
{
|
|
||||||
res &= add_number(numbers, v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (get_step<T>(p, step_start, step))
|
|
||||||
{
|
|
||||||
// Add from step_start to T::Last with a step of 'step'
|
|
||||||
for (auto v = step_start; v <= value_of(T::Last); v += step)
|
|
||||||
{
|
|
||||||
res &= add_number(numbers, v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
res = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
@ -263,7 +218,8 @@ namespace libcron
|
|||||||
if (std::regex_match(s.begin(), s.end(), match, range))
|
if (std::regex_match(s.begin(), s.end(), match, range))
|
||||||
{
|
{
|
||||||
int raw_start;
|
int raw_start;
|
||||||
if(match[1].str() == "*")
|
|
||||||
|
if (match[1].str() == "*")
|
||||||
{
|
{
|
||||||
raw_start = value_of(T::First);
|
raw_start = value_of(T::First);
|
||||||
}
|
}
|
||||||
@ -326,5 +282,64 @@ namespace libcron
|
|||||||
&& is_between(high, value_of(T::First), value_of(T::Last));
|
&& is_between(high, value_of(T::First), value_of(T::Last));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
bool CronData::convert_from_string_range_to_number_range(const std::string& range, std::set<T>& numbers)
|
||||||
|
{
|
||||||
|
T left;
|
||||||
|
T right;
|
||||||
|
uint8_t step_start;
|
||||||
|
uint8_t step;
|
||||||
|
|
||||||
|
bool res = true;
|
||||||
|
|
||||||
|
if (range == "*" || range == "?")
|
||||||
|
{
|
||||||
|
// We treat the ignore-character '?' the same as the full range being allowed.
|
||||||
|
add_full_range<T>(numbers);
|
||||||
|
}
|
||||||
|
else if (is_number(range))
|
||||||
|
{
|
||||||
|
res = add_number<T>(numbers, std::stoi(range));
|
||||||
|
}
|
||||||
|
else if (get_range<T>(range, left, right))
|
||||||
|
{
|
||||||
|
// A range can be written as both 1-22 or 22-1, meaning totally different ranges.
|
||||||
|
// First case is 1...22 while 22-1 is only four hours: 22, 23, 0, 1.
|
||||||
|
if (left <= right)
|
||||||
|
{
|
||||||
|
for (auto v = value_of(left); v <= value_of(right); ++v)
|
||||||
|
{
|
||||||
|
res &= add_number(numbers, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 'left' and 'right' are not in value order. First, get values between 'left' and T::Last, inclusive
|
||||||
|
for (auto v = value_of(left); v <= value_of(T::Last); ++v)
|
||||||
|
{
|
||||||
|
res = add_number(numbers, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, get values between T::First and 'right', inclusive.
|
||||||
|
for (auto v = value_of(T::First); v <= value_of(right); ++v)
|
||||||
|
{
|
||||||
|
res = add_number(numbers, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (get_step<T>(range, step_start, step))
|
||||||
|
{
|
||||||
|
// Add from step_start to T::Last with a step of 'step'
|
||||||
|
for (auto v = step_start; v <= value_of(T::Last); v += step)
|
||||||
|
{
|
||||||
|
res = add_number(numbers, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
res = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
96
libcron/include/libcron/CronRandomization.h
Normal file
96
libcron/include/libcron/CronRandomization.h
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <tuple>
|
||||||
|
#include <random>
|
||||||
|
#include <regex>
|
||||||
|
#include <functional>
|
||||||
|
#include "CronData.h"
|
||||||
|
|
||||||
|
namespace libcron
|
||||||
|
{
|
||||||
|
class CronRandomization
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::tuple<bool, std::string> parse(const std::string& cron_schedule);
|
||||||
|
|
||||||
|
CronRandomization();
|
||||||
|
|
||||||
|
CronRandomization(const CronRandomization&) = delete;
|
||||||
|
|
||||||
|
CronRandomization & operator=(const CronRandomization &) = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
template<typename T>
|
||||||
|
std::pair<bool, std::string> get_random_in_range(const std::string& section,
|
||||||
|
int& selected_value,
|
||||||
|
std::pair<int, int> limit = std::make_pair(-1, -1));
|
||||||
|
|
||||||
|
std::pair<int, int> day_limiter(const std::set<Months>& month);
|
||||||
|
int cap(int value, int lower, int upper);
|
||||||
|
|
||||||
|
std::regex const rand_expression{ R"#([rR]\((\d+)\-(\d+)\))#", std::regex_constants::ECMAScript };
|
||||||
|
std::random_device rd{};
|
||||||
|
std::mt19937 twister;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
std::pair<bool, std::string> CronRandomization::get_random_in_range(const std::string& section,
|
||||||
|
int& selected_value,
|
||||||
|
std::pair<int, int> limit)
|
||||||
|
{
|
||||||
|
auto res = std::make_pair(true, std::string{});
|
||||||
|
selected_value = -1;
|
||||||
|
|
||||||
|
std::smatch random_match;
|
||||||
|
|
||||||
|
if (std::regex_match(section.begin(), section.end(), random_match, rand_expression))
|
||||||
|
{
|
||||||
|
// Random range, get left and right numbers.
|
||||||
|
auto left = std::stoi(random_match[1].str());
|
||||||
|
auto right = std::stoi(random_match[2].str());
|
||||||
|
|
||||||
|
if (limit.first != -1 && limit.second != -1)
|
||||||
|
{
|
||||||
|
left = cap(left, limit.first, limit.second);
|
||||||
|
right = cap(right, limit.first, limit.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
libcron::CronData cd;
|
||||||
|
std::set<T> numbers;
|
||||||
|
res.first = cd.convert_from_string_range_to_number_range<T>(
|
||||||
|
std::to_string(left) + "-" + std::to_string(right), numbers);
|
||||||
|
|
||||||
|
// Remove items outside limits.
|
||||||
|
// for(auto it = numbers.begin(); it != numbers.end();)
|
||||||
|
// {
|
||||||
|
// if(CronData::value_of(*it) < limit.first || CronData::value_of(*it) > limit.second)
|
||||||
|
// {
|
||||||
|
// it = numbers.erase(it);
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// ++it;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (res.first)
|
||||||
|
{
|
||||||
|
// Generate random indexes to select one of the numbers in the range.
|
||||||
|
std::uniform_int_distribution<> dis(0, static_cast<int>(numbers.size() - 1));
|
||||||
|
|
||||||
|
// Select the random number to use as the schedule
|
||||||
|
auto it = numbers.begin();
|
||||||
|
std::advance(it, dis(twister));
|
||||||
|
selected_value = CronData::value_of(*it);
|
||||||
|
res.second = std::to_string(selected_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Not random, just append input to output.
|
||||||
|
res.second = section;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
@ -31,7 +31,19 @@ namespace libcron
|
|||||||
enum class Months : uint8_t
|
enum class Months : uint8_t
|
||||||
{
|
{
|
||||||
First = 1,
|
First = 1,
|
||||||
Last = 12
|
January = First,
|
||||||
|
February,
|
||||||
|
March,
|
||||||
|
April,
|
||||||
|
May,
|
||||||
|
June,
|
||||||
|
July,
|
||||||
|
August,
|
||||||
|
September,
|
||||||
|
October,
|
||||||
|
November,
|
||||||
|
December = 12,
|
||||||
|
Last = December
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class DayOfWeek : uint8_t
|
enum class DayOfWeek : uint8_t
|
||||||
|
@ -5,6 +5,13 @@ using namespace date;
|
|||||||
|
|
||||||
namespace libcron
|
namespace libcron
|
||||||
{
|
{
|
||||||
|
const constexpr std::array<Months, 7> CronData::months_with_31{ Months::January,
|
||||||
|
Months::March,
|
||||||
|
Months::May,
|
||||||
|
Months::July,
|
||||||
|
Months::August,
|
||||||
|
Months::October,
|
||||||
|
Months::December };
|
||||||
|
|
||||||
CronData CronData::create(const std::string& cron_expression)
|
CronData CronData::create(const std::string& cron_expression)
|
||||||
{
|
{
|
||||||
@ -15,16 +22,16 @@ namespace libcron
|
|||||||
}
|
}
|
||||||
|
|
||||||
CronData::CronData()
|
CronData::CronData()
|
||||||
: month_names({"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"}),
|
: month_names({ "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }),
|
||||||
day_names({"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"})
|
day_names({ "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" })
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void CronData::parse(const std::string& cron_expression)
|
void CronData::parse(const std::string& cron_expression)
|
||||||
{
|
{
|
||||||
// First, split on white-space. We expect six parts.
|
// First, split on white-space. We expect six parts.
|
||||||
std::regex split{R"#(^\s*(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s*$)#",
|
std::regex split{ R"#(^\s*(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s*$)#",
|
||||||
std::regex_constants::ECMAScript};
|
std::regex_constants::ECMAScript };
|
||||||
|
|
||||||
std::smatch match;
|
std::smatch match;
|
||||||
|
|
||||||
@ -48,13 +55,12 @@ namespace libcron
|
|||||||
std::string r = "[";
|
std::string r = "[";
|
||||||
r += token;
|
r += token;
|
||||||
r += "]";
|
r += "]";
|
||||||
std::regex splitter{r, std::regex_constants::ECMAScript};
|
std::regex splitter{ r, std::regex_constants::ECMAScript };
|
||||||
|
|
||||||
std::copy(std::sregex_token_iterator(s.begin(), s.end(), splitter, -1),
|
std::copy(std::sregex_token_iterator(s.begin(), s.end(), splitter, -1),
|
||||||
std::sregex_token_iterator(),
|
std::sregex_token_iterator(),
|
||||||
std::back_inserter(res));
|
std::back_inserter(res));
|
||||||
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,17 +96,15 @@ namespace libcron
|
|||||||
// Make sure that if the days contains only 31, at least one month allows that date.
|
// Make sure that if the days contains only 31, at least one month allows that date.
|
||||||
if (day_of_month.size() == 1 && day_of_month.find(DayOfMonth::Last) != day_of_month.end())
|
if (day_of_month.size() == 1 && day_of_month.find(DayOfMonth::Last) != day_of_month.end())
|
||||||
{
|
{
|
||||||
constexpr std::array<uint32_t, 7> months_with_31{1, 3, 5, 7, 8, 10, 12};
|
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
res = months.find(static_cast<Months>(months_with_31[i])) != months.end();
|
res = months.find(months_with_31[i]) != months.end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
110
libcron/src/CronRandomization.cpp
Normal file
110
libcron/src/CronRandomization.cpp
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
#include <libcron/CronRandomization.h>
|
||||||
|
|
||||||
|
#include <regex>
|
||||||
|
#include <map>
|
||||||
|
#include <array>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iterator>
|
||||||
|
#include <libcron/TimeTypes.h>
|
||||||
|
#include <libcron/CronData.h>
|
||||||
|
|
||||||
|
namespace libcron
|
||||||
|
{
|
||||||
|
CronRandomization::CronRandomization()
|
||||||
|
: twister(rd())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<bool, std::string> CronRandomization::parse(const std::string& cron_schedule)
|
||||||
|
{
|
||||||
|
// Split on space to get each separate part, six parts expected
|
||||||
|
std::regex split{ R"#(^\s*(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s*$)#",
|
||||||
|
std::regex_constants::ECMAScript };
|
||||||
|
|
||||||
|
std::smatch all_sections;
|
||||||
|
|
||||||
|
std::string final_cron_schedule;
|
||||||
|
|
||||||
|
auto res = std::regex_match(cron_schedule.begin(), cron_schedule.end(), all_sections, split);
|
||||||
|
|
||||||
|
if (res)
|
||||||
|
{
|
||||||
|
int selected_value = -1;
|
||||||
|
auto second = get_random_in_range<Seconds>(all_sections[1].str(), selected_value);
|
||||||
|
res = second.first;
|
||||||
|
final_cron_schedule = second.second;
|
||||||
|
|
||||||
|
auto minute = get_random_in_range<Minutes>(all_sections[2].str(), selected_value);
|
||||||
|
res &= minute.first;
|
||||||
|
final_cron_schedule += " " + minute.second;
|
||||||
|
|
||||||
|
auto hour = get_random_in_range<Hours>(all_sections[3].str(), selected_value);
|
||||||
|
res &= hour.first;
|
||||||
|
final_cron_schedule += " " + hour.second;
|
||||||
|
|
||||||
|
// Do Month before DayOfMonth to allow capping the allowed range.
|
||||||
|
auto month = get_random_in_range<Months>(all_sections[5].str(), selected_value);
|
||||||
|
res &= month.first;
|
||||||
|
|
||||||
|
std::set<Months> month_range{};
|
||||||
|
if (selected_value == -1)
|
||||||
|
{
|
||||||
|
// Month is not specific, we need to get the 'max minimum' day of month to use.
|
||||||
|
CronData cr;
|
||||||
|
res &= cr.convert_from_string_range_to_number_range<Months>(all_sections[5].str(), month_range);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
month_range.emplace(static_cast<Months>(selected_value));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto limits = day_limiter(month_range);
|
||||||
|
|
||||||
|
auto day_of_month = get_random_in_range<DayOfMonth>(all_sections[4].str(),
|
||||||
|
selected_value,
|
||||||
|
limits);
|
||||||
|
|
||||||
|
res &= day_of_month.first;
|
||||||
|
final_cron_schedule += " " + day_of_month.second + " " + month.second;
|
||||||
|
|
||||||
|
auto day_of_week = get_random_in_range<DayOfWeek>(all_sections[6].str(), selected_value);
|
||||||
|
res &= day_of_week.first;
|
||||||
|
final_cron_schedule += " " + day_of_week.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { res, final_cron_schedule };
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<int, int> CronRandomization::day_limiter(const std::set<Months>& months)
|
||||||
|
{
|
||||||
|
auto max = 31;
|
||||||
|
|
||||||
|
for (auto month : months)
|
||||||
|
{
|
||||||
|
if (month == Months::February)
|
||||||
|
{
|
||||||
|
// Limit to 29 days, possibly causing delaying schedule until next leap year.
|
||||||
|
max = std::min(max, 29);
|
||||||
|
}
|
||||||
|
else if (std::find(CronData::months_with_31.begin(),
|
||||||
|
CronData::months_with_31.end(),
|
||||||
|
month) == CronData::months_with_31.end())
|
||||||
|
{
|
||||||
|
max = std::min(max, 30);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
max = std::min(max, 31);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto res = std::pair<int, int>{ CronData::value_of(DayOfMonth::First), max };
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CronRandomization::cap(int value, int lower, int upper)
|
||||||
|
{
|
||||||
|
return std::max(std::min(value, upper), lower);
|
||||||
|
}
|
||||||
|
}
|
@ -18,11 +18,13 @@ include_directories(
|
|||||||
add_executable(
|
add_executable(
|
||||||
${PROJECT_NAME}
|
${PROJECT_NAME}
|
||||||
CronDataTest.cpp
|
CronDataTest.cpp
|
||||||
CronScheduleTest.cpp CronTest.cpp)
|
CronRandomizationTest.cpp
|
||||||
|
CronScheduleTest.cpp
|
||||||
|
CronTest.cpp)
|
||||||
|
|
||||||
target_link_libraries(${PROJECT_NAME} libcron)
|
target_link_libraries(${PROJECT_NAME} libcron)
|
||||||
|
|
||||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||||
ARCHIVE_OUTPUT_DIRECTORY "${OUTPUT_LOCATION}"
|
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out"
|
||||||
LIBRARY_OUTPUT_DIRECTORY "${OUTPUT_LOCATION}"
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out"
|
||||||
RUNTIME_OUTPUT_DIRECTORY "${OUTPUT_LOCATION}")
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out")
|
88
test/CronRandomizationTest.cpp
Normal file
88
test/CronRandomizationTest.cpp
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
#include <catch.hpp>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <libcron/CronRandomization.h>
|
||||||
|
#include <libcron/Cron.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace libcron;
|
||||||
|
|
||||||
|
SCENARIO("Randomize all the things")
|
||||||
|
{
|
||||||
|
const char* full_random = "R(0-59) R(0-59) R(0-23) R(1-31) R(1-12) ?";
|
||||||
|
Cron<> cron;
|
||||||
|
|
||||||
|
GIVEN(full_random)
|
||||||
|
{
|
||||||
|
THEN("Only valid schedules generated")
|
||||||
|
{
|
||||||
|
libcron::CronRandomization cr;
|
||||||
|
std::unordered_map<int, std::unordered_map<int, int>> results{};
|
||||||
|
|
||||||
|
for (int i = 0; i < 1000; ++i)
|
||||||
|
{
|
||||||
|
auto res = cr.parse(full_random);
|
||||||
|
REQUIRE(std::get<0>(res));
|
||||||
|
auto schedule = std::get<1>(res);
|
||||||
|
|
||||||
|
auto start = schedule.begin();
|
||||||
|
auto space = std::find(schedule.begin(), schedule.end(), ' ');
|
||||||
|
|
||||||
|
for (int section = 0; start != schedule.end() && section < 5; ++section)
|
||||||
|
{
|
||||||
|
auto& map = results[section];
|
||||||
|
auto s = std::string{start, space};
|
||||||
|
auto value = std::stoi(s);
|
||||||
|
map[value]++;
|
||||||
|
start = space + 1;
|
||||||
|
space = std::find(start, schedule.end(), ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
REQUIRE(results.size() == 5);
|
||||||
|
INFO("schedule:" << schedule);
|
||||||
|
REQUIRE(cron.add_schedule("validate schedule", schedule, []() {}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SCENARIO("Randomize all the things with reverse ranges")
|
||||||
|
{
|
||||||
|
// Only generate DayOfMonth up to 28 to prevent failing tests where the month doesn't have more days.
|
||||||
|
const char* full_random = "R(45-15) R(30-0) R(18-2) R(28-15) 2 ?";
|
||||||
|
Cron<> cron;
|
||||||
|
|
||||||
|
GIVEN(full_random)
|
||||||
|
{
|
||||||
|
THEN("Only valid schedules generated")
|
||||||
|
{
|
||||||
|
libcron::CronRandomization cr;
|
||||||
|
std::unordered_map<int, std::unordered_map<int, int>> results{};
|
||||||
|
|
||||||
|
for (int i = 0; i < 1000; ++i)
|
||||||
|
{
|
||||||
|
auto res = cr.parse(full_random);
|
||||||
|
REQUIRE(std::get<0>(res));
|
||||||
|
auto schedule = std::get<1>(res);
|
||||||
|
|
||||||
|
auto start = schedule.begin();
|
||||||
|
auto space = std::find(schedule.begin(), schedule.end(), ' ');
|
||||||
|
|
||||||
|
for (int section = 0; start != schedule.end() && section < 5; ++section)
|
||||||
|
{
|
||||||
|
auto& map = results[section];
|
||||||
|
auto s = std::string{start, space};
|
||||||
|
auto value = std::stoi(s);
|
||||||
|
map[value]++;
|
||||||
|
start = space + 1;
|
||||||
|
space = std::find(start, schedule.end(), ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
REQUIRE(results.size() == 5);
|
||||||
|
INFO("schedule:" << schedule);
|
||||||
|
REQUIRE(cron.add_schedule("validate schedule", schedule, []() {}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
test/out/cron_test
Executable file
BIN
test/out/cron_test
Executable file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user