mirror of
https://github.com/PerMalmberg/libcron.git
synced 2025-04-22 00:13:01 -05:00
#1 - Randomization WiP.
This commit is contained in:
parent
6ed4bc3b2e
commit
70f55b8ce6
@ -1,7 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.6)
|
||||
|
||||
set(OUTPUT_LOCATION ${CMAKE_CURRENT_LIST_DIR}/out/)
|
||||
|
||||
add_subdirectory(libcron)
|
||||
add_subdirectory(test)
|
||||
|
||||
|
@ -13,18 +13,20 @@ add_library(${PROJECT_NAME}
|
||||
include/libcron/Cron.h
|
||||
include/libcron/CronClock.h
|
||||
include/libcron/CronData.h
|
||||
include/libcron/CronRandomization.h
|
||||
include/libcron/CronSchedule.h
|
||||
include/libcron/DateTime.h
|
||||
include/libcron/Task.h
|
||||
include/libcron/TimeTypes.h
|
||||
src/CronClock.cpp
|
||||
src/CronData.cpp
|
||||
src/CronRandomization.cpp
|
||||
src/CronSchedule.cpp
|
||||
src/Task.cpp)
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PRIVATE ${CMAKE_CURRENT_LIST_DIR}/externals/date/include
|
||||
PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include)
|
||||
PUBLIC include)
|
||||
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out"
|
||||
|
@ -11,9 +11,12 @@ namespace libcron
|
||||
class CronData
|
||||
{
|
||||
public:
|
||||
static const std::array<Months, 7> months_with_31;
|
||||
|
||||
static CronData create(const std::string& cron_expression);
|
||||
|
||||
CronData();
|
||||
|
||||
CronData(const CronData&) = default;
|
||||
|
||||
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)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
for (auto i = low; !found && i <= high; ++i)
|
||||
{
|
||||
found |= set.find(static_cast<T>(i)) != set.end();
|
||||
@ -69,8 +73,10 @@ namespace libcron
|
||||
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);
|
||||
|
||||
template<typename T>
|
||||
@ -142,6 +148,7 @@ namespace libcron
|
||||
for (const auto& name : names)
|
||||
{
|
||||
std::regex m(name, std::regex_constants::ECMAScript | std::regex_constants::icase);
|
||||
|
||||
for (auto& part : parts)
|
||||
{
|
||||
std::string replaced;
|
||||
@ -155,7 +162,6 @@ namespace libcron
|
||||
}
|
||||
|
||||
return process_parts(parts, numbers);
|
||||
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
@ -163,60 +169,9 @@ namespace libcron
|
||||
{
|
||||
bool res = true;
|
||||
|
||||
T left;
|
||||
T right;
|
||||
uint8_t step_start;
|
||||
uint8_t step;
|
||||
|
||||
for (const auto& p : parts)
|
||||
{
|
||||
if (p == "*" || p == "?")
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
res &= convert_from_string_range_to_number_range(p, numbers);
|
||||
}
|
||||
|
||||
return res;
|
||||
@ -263,7 +218,8 @@ namespace libcron
|
||||
if (std::regex_match(s.begin(), s.end(), match, range))
|
||||
{
|
||||
int raw_start;
|
||||
if(match[1].str() == "*")
|
||||
|
||||
if (match[1].str() == "*")
|
||||
{
|
||||
raw_start = value_of(T::First);
|
||||
}
|
||||
@ -326,5 +282,64 @@ namespace libcron
|
||||
&& 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;
|
||||
}
|
||||
}
|
@ -6,38 +6,50 @@ namespace libcron
|
||||
{
|
||||
enum class Seconds : int8_t
|
||||
{
|
||||
First = 0,
|
||||
Last = 59
|
||||
First = 0,
|
||||
Last = 59
|
||||
};
|
||||
|
||||
enum class Minutes : int8_t
|
||||
{
|
||||
First = 0,
|
||||
Last = 59
|
||||
First = 0,
|
||||
Last = 59
|
||||
};
|
||||
|
||||
enum class Hours : int8_t
|
||||
{
|
||||
First = 0,
|
||||
Last = 23
|
||||
First = 0,
|
||||
Last = 23
|
||||
};
|
||||
|
||||
enum class DayOfMonth : uint8_t
|
||||
{
|
||||
First = 1,
|
||||
Last = 31
|
||||
First = 1,
|
||||
Last = 31
|
||||
};
|
||||
|
||||
enum class Months : uint8_t
|
||||
{
|
||||
First = 1,
|
||||
Last = 12
|
||||
First = 1,
|
||||
January = First,
|
||||
February,
|
||||
March,
|
||||
April,
|
||||
May,
|
||||
June,
|
||||
July,
|
||||
August,
|
||||
September,
|
||||
October,
|
||||
November,
|
||||
December = 12,
|
||||
Last = December
|
||||
};
|
||||
|
||||
enum class DayOfWeek : uint8_t
|
||||
{
|
||||
// Sunday = 0 ... Saturday = 6
|
||||
First = 0,
|
||||
Last = 6,
|
||||
// Sunday = 0 ... Saturday = 6
|
||||
First = 0,
|
||||
Last = 6,
|
||||
};
|
||||
}
|
||||
|
@ -5,6 +5,13 @@ using namespace date;
|
||||
|
||||
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)
|
||||
{
|
||||
@ -15,16 +22,16 @@ namespace libcron
|
||||
}
|
||||
|
||||
CronData::CronData()
|
||||
: month_names({"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"}),
|
||||
day_names({"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"})
|
||||
: month_names({ "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }),
|
||||
day_names({ "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" })
|
||||
{
|
||||
}
|
||||
|
||||
void CronData::parse(const std::string& cron_expression)
|
||||
{
|
||||
// First, split on white-space. We expect six parts.
|
||||
std::regex split{R"#(^\s*(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s*$)#",
|
||||
std::regex_constants::ECMAScript};
|
||||
std::regex split{ R"#(^\s*(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s*$)#",
|
||||
std::regex_constants::ECMAScript };
|
||||
|
||||
std::smatch match;
|
||||
|
||||
@ -48,13 +55,12 @@ namespace libcron
|
||||
std::string r = "[";
|
||||
r += token;
|
||||
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::sregex_token_iterator(),
|
||||
std::back_inserter(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.
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -114,12 +118,12 @@ namespace libcron
|
||||
// '?' as the ignore flag, although it is functionally equivalent to '*'.
|
||||
|
||||
auto check = [](const std::string& l, std::string r)
|
||||
{
|
||||
return l == "*" && (r != "*" || r == "?");
|
||||
};
|
||||
{
|
||||
return l == "*" && (r != "*" || r == "?");
|
||||
};
|
||||
|
||||
return (dom == "?" || dow == "?")
|
||||
|| check(dom, dow)
|
||||
|| check(dow, dom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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(
|
||||
${PROJECT_NAME}
|
||||
CronDataTest.cpp
|
||||
CronScheduleTest.cpp CronTest.cpp)
|
||||
CronRandomizationTest.cpp
|
||||
CronScheduleTest.cpp
|
||||
CronTest.cpp)
|
||||
|
||||
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}")
|
||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out"
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out"
|
||||
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