diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index 30aa626..0000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 94a25f7..7d34eb9 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,5 +2,6 @@ + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c6e4df..051ba98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,5 @@ cmake_minimum_required(VERSION 3.6) -set(OUTPUT_LOCATION ${CMAKE_CURRENT_LIST_DIR}/out/) - add_subdirectory(libcron) add_subdirectory(test) diff --git a/README.md b/README.md index 24f4f1b..8efbbde 100644 --- a/README.md +++ b/README.md @@ -56,18 +56,33 @@ Each part is separated by one or more whitespaces. It is thus important to keep * 0, 3, 40-50 * * * * ? `Day of month` and `day of week` are mutually exclusive so one of them must at always be ignored using -the '?'-character unless one field already is something other than '*'. +the '?'-character to ensure that it is not possible to specify a statement which results in an impossible mix of these fields. -# Examples +## Examples |Expression | Meaning | --- | --- | | * * * * * ? | Every second -|0 0 12 * * MON-FRI | Every Weekday at noon -|0 0 12 1/2 * ? | Every 2 days, starting on the 1st at noon +| 0 0 12 * * MON-FRI | Every Weekday at noon +| 0 0 12 1/2 * ? | Every 2 days, starting on the 1st at noon | 0 0 */12 ? * * | Every twelve hours -# Third party libraries +# Randomization + +The standard cron format does not allow for randomization, but with the use of `CronRandomization` you can generate random +schedules using the following format: `R(range_start-range_end)`, where `range_start` and `range_end` follow the same rules +as for a regular cron range with the addition that only numbers are allowed. All the rules for a regular cron expression +still applies when using randomization, i.e. mutual exclusiveness and not extra spaces. + +## Examples +|Expression | Meaning +| --- | --- | +| 0 0 R(13-20) * * ? | On the hour, on a random hour 13-20, inclusive. +| 0 0 0 ? * R(0-6) | A random weekday, every week, at midnight. +| 0 R(45-15) */12 ? * * | A random minute between 45-15, inclusive, every 12 hours. + + +# Used Third party libraries Howard Hinnant's [date libraries](https://github.com/HowardHinnant/date/) diff --git a/libcron/CMakeLists.txt b/libcron/CMakeLists.txt index 7623cf1..51992ba 100644 --- a/libcron/CMakeLists.txt +++ b/libcron/CMakeLists.txt @@ -9,22 +9,26 @@ else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic") endif() -include_directories(${CMAKE_CURRENT_LIST_DIR}/externals/date/include) - add_library(${PROJECT_NAME} - Cron.h - Task.h - CronData.h - TimeTypes.h - CronData.cpp - CronSchedule.cpp - CronSchedule.h - DateTime.h - Task.cpp - CronClock.h - CronClock.cpp) + 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 include) set_target_properties(${PROJECT_NAME} PROPERTIES - ARCHIVE_OUTPUT_DIRECTORY "${OUTPUT_LOCATION}" - LIBRARY_OUTPUT_DIRECTORY "${OUTPUT_LOCATION}" - RUNTIME_OUTPUT_DIRECTORY "${OUTPUT_LOCATION}") \ No newline at end of file + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out") \ No newline at end of file diff --git a/libcron/TimeTypes.h b/libcron/TimeTypes.h deleted file mode 100644 index a3238d3..0000000 --- a/libcron/TimeTypes.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include - -namespace libcron -{ - enum class Seconds : int8_t - { - First = 0, - Last = 59 - }; - - enum class Minutes : int8_t - { - First = 0, - Last = 59 - }; - - enum class Hours : int8_t - { - First = 0, - Last = 23 - }; - - enum class DayOfMonth : uint8_t - { - First = 1, - Last = 31 - }; - - enum class Months : uint8_t - { - First = 1, - Last = 12 - }; - - enum class DayOfWeek : uint8_t - { - // Sunday = 0 ... Saturday = 6 - First = 0, - Last = 6, - }; -} diff --git a/libcron/Cron.h b/libcron/include/libcron/Cron.h similarity index 100% rename from libcron/Cron.h rename to libcron/include/libcron/Cron.h diff --git a/libcron/CronClock.h b/libcron/include/libcron/CronClock.h similarity index 100% rename from libcron/CronClock.h rename to libcron/include/libcron/CronClock.h diff --git a/libcron/CronData.h b/libcron/include/libcron/CronData.h similarity index 76% rename from libcron/CronData.h rename to libcron/include/libcron/CronData.h index 4f83c65..5437b87 100644 --- a/libcron/CronData.h +++ b/libcron/include/libcron/CronData.h @@ -4,16 +4,20 @@ #include #include #include -#include "TimeTypes.h" +#include namespace libcron { class CronData { public: + static const int NUMBER_OF_LONG_MONTHS = 7; + static const libcron::Months months_with_31[NUMBER_OF_LONG_MONTHS]; + static CronData create(const std::string& cron_expression); CronData(); + CronData(const CronData&) = default; bool is_valid() const @@ -61,6 +65,7 @@ namespace libcron static bool has_any_in_range(const std::set& set, uint8_t low, uint8_t high) { bool found = false; + for (auto i = low; !found && i <= high; ++i) { found |= set.find(static_cast(i)) != set.end(); @@ -69,8 +74,10 @@ namespace libcron return found; } - private: + template + bool convert_from_string_range_to_number_range(const std::string& range, std::set& numbers); + private: void parse(const std::string& cron_expression); template @@ -142,20 +149,20 @@ namespace libcron for (const auto& name : names) { std::regex m(name, std::regex_constants::ECMAScript | std::regex_constants::icase); - for (size_t i = 0; i < parts.size(); ++i) + + for (auto& part : parts) { std::string replaced; - std::regex_replace(std::back_inserter(replaced), parts[i].begin(), parts[i].end(), m, + std::regex_replace(std::back_inserter(replaced), part.begin(), part.end(), m, std::to_string(value_of_first_name)); - parts[i] = replaced; + part = replaced; } value_of_first_name++; } return process_parts(parts, numbers); - } template @@ -163,60 +170,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(numbers); - } - else if (is_number(p)) - { - res &= add_number(numbers, std::stoi(p)); - } - else if (get_range(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(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; @@ -235,8 +191,8 @@ namespace libcron if (std::regex_match(s.begin(), s.end(), match, range)) { - auto left = std::stoi(match[1].str().c_str()); - auto right = std::stoi(match[2].str().c_str()); + auto left = std::stoi(match[1].str()); + auto right = std::stoi(match[2].str()); if (is_within_limits(left, right)) { @@ -263,16 +219,17 @@ 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); } else { - raw_start = std::stoi(match[1].str().c_str()); + raw_start = std::stoi(match[1].str()); } - auto raw_step = std::stoi(match[2].str().c_str()); + auto raw_step = std::stoi(match[2].str()); if (is_within_limits(raw_start, raw_start) && raw_step > 0) { @@ -326,5 +283,64 @@ namespace libcron && is_between(high, value_of(T::First), value_of(T::Last)); } + template + bool CronData::convert_from_string_range_to_number_range(const std::string& range, std::set& 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(numbers); + } + else if (is_number(range)) + { + res = add_number(numbers, std::stoi(range)); + } + else if (get_range(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(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; + } } diff --git a/libcron/include/libcron/CronRandomization.h b/libcron/include/libcron/CronRandomization.h new file mode 100644 index 0000000..759995e --- /dev/null +++ b/libcron/include/libcron/CronRandomization.h @@ -0,0 +1,100 @@ +#pragma once + +#include +#include +#include +#include +#include "CronData.h" + +namespace libcron +{ + class CronRandomization + { + public: + std::tuple parse(const std::string& cron_schedule); + + CronRandomization(); + + CronRandomization(const CronRandomization&) = delete; + + CronRandomization & operator=(const CronRandomization &) = delete; + + private: + template + std::pair get_random_in_range(const std::string& section, + int& selected_value, + std::pair limit = std::make_pair(-1, -1)); + + std::pair day_limiter(const std::set& 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 + std::pair CronRandomization::get_random_in_range(const std::string& section, + int& selected_value, + std::pair 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 numbers; + res.first = cd.convert_from_string_range_to_number_range( + std::to_string(left) + "-" + std::to_string(right), numbers); + + // Remove items outside limits. + if (limit.first != -1 && limit.second != -1) + { + 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(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; + } +} diff --git a/libcron/CronSchedule.h b/libcron/include/libcron/CronSchedule.h similarity index 96% rename from libcron/CronSchedule.h rename to libcron/include/libcron/CronSchedule.h index e619167..1c6fe97 100644 --- a/libcron/CronSchedule.h +++ b/libcron/include/libcron/CronSchedule.h @@ -1,17 +1,17 @@ #pragma once -#include "CronData.h" +#include "libcron/CronData.h" #include -#ifdef _WIN32 -#pragma warning(push) -#pragma warning(disable:4244) -#endif -#include -#ifdef _WIN32 -#pragma warning(pop) +#ifdef _WIN32 +#pragma warning(push) +#pragma warning(disable:4244) +#endif +#include +#ifdef _WIN32 +#pragma warning(pop) #endif -#include "DateTime.h" +#include "libcron/DateTime.h" namespace libcron { diff --git a/libcron/DateTime.h b/libcron/include/libcron/DateTime.h similarity index 100% rename from libcron/DateTime.h rename to libcron/include/libcron/DateTime.h diff --git a/libcron/Task.h b/libcron/include/libcron/Task.h similarity index 100% rename from libcron/Task.h rename to libcron/include/libcron/Task.h diff --git a/libcron/include/libcron/TimeTypes.h b/libcron/include/libcron/TimeTypes.h new file mode 100644 index 0000000..18aa133 --- /dev/null +++ b/libcron/include/libcron/TimeTypes.h @@ -0,0 +1,55 @@ +#pragma once + +#include + +namespace libcron +{ + enum class Seconds : int8_t + { + First = 0, + Last = 59 + }; + + enum class Minutes : int8_t + { + First = 0, + Last = 59 + }; + + enum class Hours : int8_t + { + First = 0, + Last = 23 + }; + + enum class DayOfMonth : uint8_t + { + First = 1, + Last = 31 + }; + + enum class Months : uint8_t + { + 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, + }; +} diff --git a/libcron/CronClock.cpp b/libcron/src/CronClock.cpp similarity index 96% rename from libcron/CronClock.cpp rename to libcron/src/CronClock.cpp index 2933777..9aeb00f 100644 --- a/libcron/CronClock.cpp +++ b/libcron/src/CronClock.cpp @@ -1,8 +1,8 @@ -#include "CronClock.h" +#include "libcron/CronClock.h" #ifdef WIN32 -#ifndef NOMINMAX -#define NOMINMAX +#ifndef NOMINMAX +#define NOMINMAX #endif #define WIN32_LEAN_AND_MEAN #include diff --git a/libcron/CronData.cpp b/libcron/src/CronData.cpp similarity index 72% rename from libcron/CronData.cpp rename to libcron/src/CronData.cpp index e7f4082..6ee9dbc 100644 --- a/libcron/CronData.cpp +++ b/libcron/src/CronData.cpp @@ -1,10 +1,17 @@ #include -#include "CronData.h" +#include "libcron/CronData.h" using namespace date; namespace libcron { + const constexpr Months CronData::months_with_31[NUMBER_OF_LONG_MONTHS] = { 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,25 +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()) { - std::vector months_with_31; - for (int32_t i = 1; i <= 12; ++i) - { - auto ymd = 2018_y / i / date::last; - if (unsigned(ymd.day()) == 31) - { - months_with_31.push_back(i); - } - } - res = false; - for (size_t i = 0; !res && i < months_with_31.size(); ++i) + + for (size_t i = 0; !res && i < NUMBER_OF_LONG_MONTHS; ++i) { - res = months.find(static_cast(months_with_31[i])) != months.end(); + res = months.find(months_with_31[i]) != months.end(); } } } - return res; } @@ -122,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); } -} \ No newline at end of file +} diff --git a/libcron/src/CronRandomization.cpp b/libcron/src/CronRandomization.cpp new file mode 100644 index 0000000..144fbcb --- /dev/null +++ b/libcron/src/CronRandomization.cpp @@ -0,0 +1,108 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace libcron +{ + CronRandomization::CronRandomization() + : twister(rd()) + { + } + + std::tuple 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(all_sections[1].str(), selected_value); + res = second.first; + final_cron_schedule = second.second; + + auto minute = get_random_in_range(all_sections[2].str(), selected_value); + res &= minute.first; + final_cron_schedule += " " + minute.second; + + auto hour = get_random_in_range(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(all_sections[5].str(), selected_value); + res &= month.first; + + std::set month_range{}; + + if (selected_value == -1) + { + // Month is not specific, get the range. + CronData cr; + res &= cr.convert_from_string_range_to_number_range(all_sections[5].str(), month_range); + } + else + { + month_range.emplace(static_cast(selected_value)); + } + + auto limits = day_limiter(month_range); + + auto day_of_month = get_random_in_range(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(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 CronRandomization::day_limiter(const std::set& months) + { + int max = CronData::value_of(DayOfMonth::Last); + + 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(std::begin(CronData::months_with_31), + std::end(CronData::months_with_31), + month) == std::end(CronData::months_with_31)) + { + // Not among the months with 31 days + max = std::min(max, 30); + } + } + + auto res = std::pair{ 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); + } +} diff --git a/libcron/CronSchedule.cpp b/libcron/src/CronSchedule.cpp similarity index 98% rename from libcron/CronSchedule.cpp rename to libcron/src/CronSchedule.cpp index f2a5971..5da19d6 100644 --- a/libcron/CronSchedule.cpp +++ b/libcron/src/CronSchedule.cpp @@ -1,4 +1,4 @@ -#include "CronSchedule.h" +#include "libcron/CronSchedule.h" #include using namespace std::chrono; diff --git a/libcron/Task.cpp b/libcron/src/Task.cpp similarity index 98% rename from libcron/Task.cpp rename to libcron/src/Task.cpp index fc0b038..69171c0 100644 --- a/libcron/Task.cpp +++ b/libcron/src/Task.cpp @@ -1,5 +1,5 @@ #include -#include "Task.h" +#include "libcron/Task.h" using namespace std::chrono; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4dbdcda..da2349b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -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}") \ No newline at end of file + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out") \ No newline at end of file diff --git a/test/CronDataTest.cpp b/test/CronDataTest.cpp index 92a93c1..74d7b83 100644 --- a/test/CronDataTest.cpp +++ b/test/CronDataTest.cpp @@ -2,8 +2,8 @@ #include #include -#include -#include +#include +#include using namespace libcron; using namespace date; diff --git a/test/CronRandomizationTest.cpp b/test/CronRandomizationTest.cpp new file mode 100644 index 0000000..7f9b8d1 --- /dev/null +++ b/test/CronRandomizationTest.cpp @@ -0,0 +1,105 @@ +#include +#include +#include +#include +#include +#include +#include + +using namespace libcron; + +void test(const char* const random_schedule) +{ + libcron::CronRandomization cr; + std::unordered_map> results{}; + + for (int i = 0; i < 5000; ++i) + { + auto res = cr.parse(random_schedule); + REQUIRE(std::get<0>(res)); + auto schedule = std::get<1>(res); + + INFO("schedule:" << schedule); + Cron<> cron; + REQUIRE(cron.add_schedule("validate schedule", schedule, []() {})); + } +} + +SCENARIO("Randomize all the things") +{ + const char* random_schedule = "R(0-59) R(0-59) R(0-23) R(1-31) R(1-12) ?"; + + GIVEN(random_schedule) + { + THEN("Only valid schedules generated") + { + test(random_schedule); + } + } +} + +SCENARIO("Randomize all the things with reverse ranges") +{ + const char* random_schedule = "R(45-15) R(30-0) R(18-2) R(28-15) R(8-3) ?"; + + GIVEN(random_schedule) + { + THEN("Only valid schedules generated") + { + test(random_schedule); + } + } +} + +SCENARIO("Randomize all the things - day of week") +{ + const char* random_schedule = "R(0-59) R(0-59) R(0-23) ? R(1-12) R(0-6)"; + + GIVEN(random_schedule) + { + THEN("Only valid schedules generated") + { + test(random_schedule); + } + } +} + +SCENARIO("Randomize all the things with reverse ranges - day of week") +{ + const char* random_schedule = "R(45-15) R(30-0) R(18-2) ? R(8-3) R(4-1)"; + + GIVEN(random_schedule) + { + THEN("Only valid schedules generated") + { + test(random_schedule); + } + } +} + +SCENARIO("Test readme examples") +{ + GIVEN("0 0 R(13-20) * * ?") + { + THEN("Valid schedule generated") + { + test("0 0 R(13-20) * * ?"); + } + } + + GIVEN("0 0 0 ? * R(0-6)") + { + THEN("Valid schedule generated") + { + test("0 0 0 ? * R(0-6)"); + } + } + + GIVEN("0 R(45-15) */12 ? * *") + { + THEN("Valid schedule generated") + { + test("0 R(45-15) */12 ? * *"); + } + } +} diff --git a/test/CronScheduleTest.cpp b/test/CronScheduleTest.cpp index 6b109b3..c7347af 100644 --- a/test/CronScheduleTest.cpp +++ b/test/CronScheduleTest.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include using namespace libcron; diff --git a/test/CronTest.cpp b/test/CronTest.cpp index b1ee940..18f1f58 100644 --- a/test/CronTest.cpp +++ b/test/CronTest.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include diff --git a/test/out/cron_test b/test/out/cron_test new file mode 100755 index 0000000..4ef17cd Binary files /dev/null and b/test/out/cron_test differ diff --git a/uncrustify.cfg b/uncrustify.cfg new file mode 100644 index 0000000..c131b05 --- /dev/null +++ b/uncrustify.cfg @@ -0,0 +1,214 @@ +# Uncrustify-0.67-87-d75a44aa9 +newlines = lf +input_tab_size = 4 +output_tab_size = 4 +string_replace_tab_chars = true +utf8_bom = remove +sp_arith = force +sp_arith_additive = force +sp_assign = force +sp_cpp_lambda_assign = remove +sp_cpp_lambda_paren = remove +sp_assign_default = force +sp_after_assign = force +sp_enum_paren = force +sp_enum_assign = force +sp_enum_before_assign = force +sp_enum_after_assign = force +sp_pp_stringify = remove +sp_before_pp_stringify = remove +sp_bool = force +sp_compare = force +sp_inside_paren = remove +sp_paren_paren = remove +sp_before_ptr_star = remove +sp_before_unnamed_ptr_star = remove +sp_between_ptr_star = remove +sp_after_ptr_star = force +sp_after_ptr_block_caret = remove +sp_after_ptr_star_qualifier = force +sp_after_ptr_star_func = force +sp_ptr_star_paren = force +sp_before_ptr_star_func = force +sp_before_byref = remove +sp_before_unnamed_byref = remove +sp_after_byref = force +sp_after_byref_func = force +sp_before_byref_func = force +sp_before_angle = remove +sp_inside_angle = remove +sp_angle_colon = force +sp_after_angle = force +sp_angle_paren_empty = remove +sp_angle_word = force +sp_angle_shift = remove +sp_permit_cpp11_shift = true +sp_before_sparen = force +sp_inside_sparen = remove +sp_inside_sparen_close = remove +sp_inside_sparen_open = remove +sp_after_sparen = force +sp_sparen_brace = force +sp_special_semi = remove +sp_before_semi_for = remove +sp_before_semi_for_empty = remove +sp_after_semi = remove +sp_after_semi_for_empty = force +sp_after_comma = force +sp_before_ellipsis = remove +sp_after_class_colon = force +sp_before_class_colon = force +sp_after_constr_colon = force +sp_before_constr_colon = force +sp_after_operator = remove +sp_after_operator_sym = remove +sp_after_cast = remove +sp_inside_paren_cast = remove +sp_cpp_cast_paren = remove +sp_sizeof_paren = remove +sp_inside_braces_enum = force +sp_inside_braces_struct = force +sp_after_type_brace_init_lst_open = force +sp_before_type_brace_init_lst_close = force +sp_inside_type_brace_init_lst = force +sp_inside_braces_empty = remove +sp_type_func = force +sp_type_brace_init_lst = remove +sp_func_proto_paren = remove +sp_func_proto_paren_empty = remove +sp_func_def_paren = remove +sp_inside_tparen = remove +sp_after_tparen_close = remove +sp_square_fparen = remove +sp_fparen_brace = force +sp_fparen_dbrace = force +sp_func_call_paren = remove +sp_func_class_paren_empty = remove +sp_return_paren = remove +sp_attribute_paren = remove +sp_defined_paren = remove +sp_throw_paren = remove +sp_after_throw = force +sp_catch_paren = force +sp_oc_catch_paren = force +sp_else_brace = force +sp_brace_else = force +sp_before_dc = remove +sp_after_dc = remove +sp_before_nl_cont = force +sp_cond_question = force +sp_after_new = force +sp_between_new_paren = remove +sp_inside_newop_paren = force +sp_inside_newop_paren_open = remove +sp_inside_newop_paren_close = remove +indent_columns = 4 +indent_with_tabs = 0 +indent_align_string = true +indent_namespace = true +indent_namespace_level = 4 +indent_class = true +indent_constr_colon = true +indent_ctor_init = 4 +indent_access_spec = 0 +indent_access_spec_body = true +indent_cpp_lambda_body = true +indent_cpp_lambda_only_once = true +nl_assign_leave_one_liners = true +nl_class_leave_one_liners = true +nl_enum_leave_one_liners = true +nl_getset_leave_one_liners = true +nl_func_leave_one_liners = true +nl_cpp_lambda_leave_one_liners = true +nl_start_of_file = remove +nl_end_of_file = force +nl_end_of_file_min = 1 +nl_enum_brace = force +nl_enum_class = remove +nl_enum_class_identifier = remove +nl_if_brace = force +nl_brace_else = force +nl_elseif_brace = force +nl_else_brace = force +nl_else_if = remove +nl_before_if_closing_paren = remove +nl_brace_finally = force +nl_finally_brace = force +nl_try_brace = force +nl_for_brace = force +nl_catch_brace = force +nl_while_brace = force +nl_do_brace = force +nl_brace_while = force +nl_enum_own_lines = force +nl_func_type_name = remove +nl_func_decl_empty = remove +nl_func_def_empty = remove +nl_func_call_empty = remove +nl_return_expr = remove +nl_after_semicolon = true +nl_after_brace_close = true +nl_before_if = force +nl_after_if = force +nl_before_for = force +nl_after_for = force +nl_before_while = force +nl_after_while = force +nl_before_switch = force +nl_after_switch = force +nl_before_synchronized = force +nl_after_synchronized = force +nl_before_do = force +nl_after_do = force +nl_max = 2 +nl_after_func_proto = 2 +nl_after_func_proto_group = 2 +nl_after_func_class_proto = 2 +nl_after_func_class_proto_group = 2 +nl_before_func_body_def = 2 +nl_before_func_body_proto = 2 +nl_after_func_body = 2 +nl_after_func_body_class = 2 +nl_after_func_body_one_liner = 1 +nl_before_block_comment = 2 +nl_before_c_comment = 2 +nl_before_cpp_comment = 2 +nl_after_multiline_comment = true +nl_after_label_colon = true +nl_after_struct = 2 +nl_before_class = 2 +nl_after_class = 2 +nl_before_access_spec = 1 +nl_after_access_spec = 1 +nl_comment_func_def = 1 +nl_after_try_catch_finally = 2 +nl_around_cs_property = 2 +nl_between_get_set = 2 +eat_blanks_after_open_brace = true +eat_blanks_before_close_brace = true +nl_before_return = true +pos_arith = lead +code_width = 120 +ls_for_split_full = true +ls_func_split_full = true +cmt_width = 120 +cmt_reflow_mode = 2 +cmt_indent_multi = false +cmt_c_group = true +cmt_sp_after_star_cont = 1 +mod_full_brace_do = force +mod_full_brace_for = force +mod_full_brace_function = force +mod_full_brace_if = force +mod_full_brace_nl_block_rem_mlcond = true +mod_full_brace_while = force +mod_full_brace_using = force +mod_paren_on_return = remove +mod_remove_extra_semicolon = true +mod_sort_using = true +mod_case_brace = force +mod_remove_empty_return = true +pp_ignore_define_body = true +use_indent_func_call_param = false +# option(s) with 'not default' value: 209 +#