ecflow-5.19.0/0000775000175000017500000000000015250005526013251 5ustar alastairalastairecflow-5.19.0/NOTICE0000664000175000017500000000216615250005516014161 0ustar alastairalastair The following third-party software is included in the ecFlow source tree, and is licensed as described below. cereal (C++ header-only serialisation library) https://uscilab.github.io/cereal/ Licensed under the BSD license, included at `3rdparty/cereal/`. cpp-httplib (HTTP/HTTPS C++ header-only server and client library) https://github.com/yhirose/cpp-httplib Licensed under the MIT license, included at `3rdparty/cpp-httplib/`. nlohmann-json (C++ header-only library for JSON parsing and serialisation) https://github.com/nlohmann/json Licensed under the MIT license, included at `3rdparty/json/`. Certificate.hpp (C++ header-only library for handling X.509 certificates) https://gist.github.com/nathan-osman/5041136 Licensed under the MIT license, included at `libs/rest/test/Certificate.hpp`. LazyTextEdit (Qt-based text editor that lazily loads data from disk when necessary) https://github.com/Andersbakken/LazyTextEdit Licensed under the Apache License Version 2.0, included at `Viewer/ecflowUI/src/TextPager/`. spinning_wheel.gif Generated using the website http://www.ajaxload.info/ Freely licensed, as described there. ecflow-5.19.0/libs/0000775000175000017500000000000015250005516014201 5ustar alastairalastairecflow-5.19.0/libs/attribute/0000775000175000017500000000000015250005516016204 5ustar alastairalastairecflow-5.19.0/libs/attribute/src/0000775000175000017500000000000015250005516016773 5ustar alastairalastairecflow-5.19.0/libs/attribute/src/ecflow/0000775000175000017500000000000015250005516020252 5ustar alastairalastairecflow-5.19.0/libs/attribute/src/ecflow/attribute/0000775000175000017500000000000015250005516022255 5ustar alastairalastairecflow-5.19.0/libs/attribute/src/ecflow/attribute/DayAttr.hpp0000664000175000017500000001257415250005516024347 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #ifndef ecflow_attribute_DayAttr_HPP #define ecflow_attribute_DayAttr_HPP #include #include "ecflow/core/Chrono.hpp" namespace cereal { class access; } namespace ecf { class Calendar; } // namespace ecf // Use default copy constructor, assignment operator, destructor class DayAttr { public: enum Day_t { SUNDAY = 0, MONDAY = 1, TUESDAY = 2, WEDNESDAY = 3, THURSDAY = 4, FRIDAY = 5, SATURDAY = 6 }; DayAttr() = default; explicit DayAttr(Day_t day) : day_(day) {} explicit DayAttr(const std::string& str) : day_(DayAttr::getDay(str)) {} explicit DayAttr(const boost::gregorian::date& date) : day_(static_cast(date.day_of_week().as_number())), date_(date) {} bool operator==(const DayAttr& rhs) const; bool operator<(const DayAttr& rhs) const { return day_ < rhs.day_; } bool structureEquals(const DayAttr& rhs) const; /// called when definition is restored from a file/checkpoint. handle reset of date_ void handle_migration(const ecf::Calendar&); // called at begin time. void reset(); void reset(const ecf::Calendar& c); // called when we need a requeue based on a time attribute. Should *NOT* clear expired flag. void requeue_time(); // called when re-queueing because of: // - automatic re-queue due to repeat increment // - manual re-queue // Clears expired flag, and sets day attribute to a next matching *FUTURE* day or current day void requeue_manual(const ecf::Calendar& c); // can match today if today is match day void requeue_repeat_increment(const ecf::Calendar& c); // match *FUTURE* day // Called after a node has completed, if calendar day corresponds to *THIS* day. *Expire* it // This should be called just before: checkForRequeue. // This day attribute should be treated as being deleted. returns false from // - isFree() stops re-queue on expired day // - calendarChanged() stops clearing of free. // Expired flag is RESET only by: // - void requeue_manual(const ecf::Calendar& c); // - void requeue_repeat_increment(const ecf::Calendar& c); void check_for_expiration(const ecf::Calendar&); // We must use a real date, using enum is not sufficient as in ecflow4 // 0,1,2,3,4,5,6 // ^ ^ // sunday saturday // old/buggy: check_for_requeue() { return (day_ > calendar.day_of_week() );} // The old check for re-queue would determine if this day is in the future, with reference to calendar day. // Hence day_(Saturday) is would always re-queue, day_(Sunday) is would never re-queue // When multiple days were involved, it would get even buggier. // By using a real date, we fix the issue, the real date is updated during manual re-queue, or automatically vi // repeat increment. bool checkForRequeue(const ecf::Calendar&) const; void setFree(); // ensures that isFree() always returns true bool isSetFree() const { return free_; } void calendarChanged(const ecf::Calendar& c, bool clear_at_midnight = true); // can set attribute free bool isFree(const ecf::Calendar&) const; bool validForHybrid(const ecf::Calendar&) const; bool why(const ecf::Calendar&, std::string& theReasonWhy) const; // The state_change_no is never reset. Must be incremented if it can affect equality unsigned int state_change_no() const { return state_change_no_; } std::string name() const; // for display/gui only std::string toString() const; std::string dump() const; std::string as_simple_string() const; // return the days, if input is not valid will throw a runtime_error static DayAttr create(const std::string& dayStr); static DayAttr create(const std::vector& lineTokens, bool read_state); static DayAttr::Day_t getDay(const std::string&); static std::vector allDays(); // access DayAttr::Day_t day() const { return day_; } const boost::gregorian::date& date() const { return date_; } boost::gregorian::date next_matching_date(const ecf::Calendar& c) const; void set_expired(); // ********* TREAT this Day Attribute as deleted ********** bool expired() const { return expired_; } void read_state(const std::vector& lineTokens); private: void clearFree(); // resets the free flag void clear_expired(); bool is_free(const ecf::Calendar&) const; // ignores free_ boost::gregorian::date matching_date(const ecf::Calendar& c) const; public: void write(std::string&) const; private: DayAttr::Day_t day_{DayAttr::SUNDAY}; unsigned int state_change_no_{0}; // *not* persisted, only used on server side bool free_{false}; // persisted for use by why() on client side bool expired_{false}; // added for ecflow 5.4.0 boost::gregorian::date date_; // corresponding to day_ friend class cereal::access; template void serialize(Archive& ar); }; #endif /* ecflow_attribute_DayAttr_HPP */ ecflow-5.19.0/libs/attribute/src/ecflow/attribute/LateAttr.cpp0000664000175000017500000002117115250005516024503 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #include "ecflow/attribute/LateAttr.hpp" #include #include "ecflow/core/Calendar.hpp" #include "ecflow/core/Ecf.hpp" #include "ecflow/core/NState.hpp" #include "ecflow/core/Serialization.hpp" #include "ecflow/core/Str.hpp" #include "ecflow/core/TimeSeries.hpp" namespace ecf { LateAttr::LateAttr() = default; std::string LateAttr::toString() const { std::string ret; write(ret); return ret; } void LateAttr::write(std::string& ret) const { ret += "late"; if (!s_.isNULL()) { ret += " -s +"; s_.write(ret); } if (!a_.isNULL()) { ret += " -a "; a_.write(ret); } if (!c_.isNULL()) { ret += " -c "; if (c_is_rel_) { ret += "+"; } c_.write(ret); } } bool LateAttr::operator==(const LateAttr& rhs) const { if (c_is_rel_ != rhs.c_is_rel_) { return false; } if (s_ != rhs.s_) { return false; } if (a_ != rhs.a_) { return false; } if (c_ != rhs.c_) { return false; } if (isLate_ != rhs.isLate_) { return false; } return true; } bool LateAttr::isNull() const { return (s_.isNULL() && a_.isNULL() && c_.isNULL()); } void LateAttr::checkForLateness(const std::pair& state, const ecf::Calendar& calendar) { if (isLate_ || isNull()) { return; } if (check_for_lateness(state, calendar)) { setLate(true); } } bool LateAttr::check_for_lateness(const std::pair& state, const ecf::Calendar& calendar) const { if (isNull()) { return false; } if (state.first == NState::SUBMITTED || state.first == NState::QUEUED) { // Submitted is always relative, ASSUME this means relative to suite start if (state.first == NState::SUBMITTED && !s_.isNULL()) { // ECFLOW-322 // The late attr check for being late in submitted state, is always *RELATIVE* // Previously we had: // // if ( calendar.duration() >= s_.duration() ) { // setLate(true); // return; // } // This is incorrect since calendar.duration() is essentially duration until // the last call to Calendar::init() i.e suite duration time. Hence, late was // set straight away. // // To check for submitted, we need the duration *after* state went into submitted state. // `state.second` is when state went SUBMITTED, relative to suite start auto time_in_submitted_state = calendar.duration() - state.second; if (time_in_submitted_state >= s_.duration()) { return true; } } // In Submitted or queued state, check for active, in REAL time if (!a_.isNULL() && calendar.suiteTime().time_of_day() >= a_.duration()) { return true; } } else if (state.first == NState::ACTIVE && !c_.isNULL()) { if (c_is_rel_) { // To check for complete, we need the duration when state went into active state. // `state.second` is when state went ACTIVE, relative to suite start auto runtime = calendar.duration() - state.second; if (runtime >= c_.duration()) { return true; } } else { // Real time if (calendar.suiteTime().time_of_day() >= c_.duration()) { return true; } } } return false; } void LateAttr::setLate(bool f) { if (f != isLate_) { // minimise changes to state_change_no_ isLate_ = f; state_change_no_ = Ecf::incr_state_change_no(); } #ifdef DEBUG_STATE_CHANGE_NO std::cout << "LateAttr::setLate changed=" << (f != isLate_) << "\n"; #endif } void LateAttr::override_with(LateAttr* in_late) { if (in_late) { if (!in_late->submitted().isNULL()) { s_ = in_late->submitted(); } if (!in_late->active().isNULL()) { a_ = in_late->active(); } if (!in_late->complete().isNULL()) { c_ = in_late->complete(); } c_is_rel_ = in_late->complete_is_relative(); // DO NOT override isLate_, because if the parent is late, we do not want *ALL* children to be set late // isLate_ = in_late->isLate(); } } void LateAttr::parse(LateAttr& lateAttr, const std::string& line, const std::vector& lineTokens, size_t index) { // late -s +00:15 -a 20:00 -c +02:00 #The option can be in any order // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 // late -s +00:15 -c +02:00 # not all options are needed // 0 1 2 3 4 5 assert(lateAttr.isNull()); size_t line_token_size = lineTokens.size(); for (size_t i = index; i < line_token_size; i += 1) { if (lineTokens[i][0] == '#') { break; } if (lineTokens[i] == "-s") { if (!lateAttr.submitted().isNULL()) { throw std::runtime_error("LateParser::doParse: Invalid late, submitted specified twice :" + line); } if (i + 1 < line_token_size) { int hour = -1; int min = -1; TimeSeries::getTime(lineTokens[i + 1], hour, min); lateAttr.addSubmitted(TimeSlot(hour, min)); i++; } else { throw std::runtime_error("LateParser::doParse: Invalid late, submitted time not specified :" + line); } } else if (lineTokens[i] == "-a") { if (!lateAttr.active().isNULL()) { throw std::runtime_error("LateParser::doParse: Invalid late, active specified twice :" + line); } if (i + 1 < line_token_size) { int hour = -1; int min = -1; TimeSeries::getTime(lineTokens[i + 1], hour, min); lateAttr.addActive(TimeSlot(hour, min)); i++; } else { throw std::runtime_error("LateParser::doParse: Invalid late, active time not specified :" + line); } } else if (lineTokens[i] == "-c") { if (!lateAttr.complete().isNULL()) { throw std::runtime_error("LateParser::doParse: Invalid late, complete specified twice :" + line); } if (i + 1 < line_token_size) { int hour = -1; int min = -1; bool relative = TimeSeries::getTime(lineTokens[i + 1], hour, min); lateAttr.addComplete(TimeSlot(hour, min), relative); i++; } else { throw std::runtime_error("LateParser::doParse: Invalid late, active time not specified :" + line); } } else { throw std::runtime_error("LateParser::doParse:5: Invalid late :" + line); } } if (lateAttr.isNull()) { throw std::runtime_error("LateParser::doParse:6: Invalid late :" + line); } } LateAttr LateAttr::create(const std::string& lateString) { std::vector lineTokens; ecf::algorithm::split_at(lineTokens, lateString); if (lineTokens.empty()) { throw std::runtime_error("LateParser::create: empty string no late specified ?" + lateString); } // adjust the index size_t index = 0; if (lineTokens[0] == "late") { index = 1; } LateAttr lateAttr; parse(lateAttr, lateString, lineTokens, index); return lateAttr; } template void LateAttr::serialize(Archive& ar) { CEREAL_OPTIONAL_NVP(ar, s_, [this]() { return !s_.isNULL(); }); CEREAL_OPTIONAL_NVP(ar, a_, [this]() { return !a_.isNULL(); }); CEREAL_OPTIONAL_NVP(ar, c_, [this]() { return !c_.isNULL(); }); CEREAL_OPTIONAL_NVP(ar, c_is_rel_, [this]() { return c_is_rel_; }); CEREAL_OPTIONAL_NVP(ar, isLate_, [this]() { return isLate_; }); } CEREAL_TEMPLATE_SPECIALIZE(LateAttr); } // namespace ecf ecflow-5.19.0/libs/attribute/src/ecflow/attribute/QueueAttr.hpp0000664000175000017500000000575315250005516024717 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #ifndef ecflow_attribute_QueueAttr_HPP #define ecflow_attribute_QueueAttr_HPP #include #include "ecflow/core/NState.hpp" ///////////////////////////////////////////////////////////////////////// class QueueAttr { public: QueueAttr(const std::string& name, const std::vector& theQueue); QueueAttr() = default; ~QueueAttr(); bool operator==(const QueueAttr& rhs) const; bool operator<(const QueueAttr& rhs) const { return name_ < rhs.name(); } /// Accessor const std::string& name() const { return name_; } std::string value() const; int index_or_value() const; int index() const { return currentIndex_; } bool empty() const { return name_.empty(); } const std::vector& list() const { return theQueue_; } const std::vector& state_vec() const { return state_vec_; } NState::State state(const std::string& step) const; std::string toString() const; std::string dump() const; // Mutators void requeue(); std::string active(); // return current index and value, make active, increment index void complete(const std::string& step); // step is more index:value void aborted(const std::string& step); std::string no_of_aborted() const; void reset_index_to_first_queued_or_aborted(); void set_used_in_trigger(bool f) { used_in_trigger_ = f; } // used by simulator only bool used_in_trigger() const { return used_in_trigger_; } static void parse(QueueAttr&, const std::string& line, std::vector& lineTokens, bool parse_state); void set_name(const std::string& name); void set_queue(const std::vector& theQueue, int index, const std::vector& state_vec); void set_state_vec(const std::vector& state_vec); void set_index(int index) { currentIndex_ = index; }; // Added to support return by reference static const QueueAttr& EMPTY(); static QueueAttr& EMPTY1(); unsigned int state_change_no() const { return state_change_no_; } private: void incr_state_change_no(); public: void write(std::string&) const; private: std::vector theQueue_; std::vector state_vec_; std::string name_; int currentIndex_{0}; unsigned int state_change_no_{0}; // *not* persisted, only used on server side bool used_in_trigger_{false}; // *not* persisted, used by simulator only private: friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const version); }; #endif /* ecflow_attribute_QueueAttr_HPP */ ecflow-5.19.0/libs/attribute/src/ecflow/attribute/ZombieAttr.cpp0000664000175000017500000002627315250005516025053 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #include "ecflow/attribute/ZombieAttr.hpp" #include #include #include #include "ecflow/core/Converter.hpp" #include "ecflow/core/Serialization.hpp" #include "ecflow/core/Str.hpp" #include "ecflow/core/ZombieCtrlAction.hpp" using namespace ecf; const ZombieAttr& ZombieAttr::EMPTY() { static const ZombieAttr ZOMBIEATTR = ZombieAttr(); return ZOMBIEATTR; } // Constructor ============================================================================== ZombieAttr::ZombieAttr(ecf::Child::ZombieType t, const std::vector& c, ZombieCtrlAction a, int zombie_lifetime) : child_cmds_(c), zombie_type_(t), action_(a), zombie_lifetime_(zombie_lifetime) { /// Server typically checks every 60 seconds, hence this is lowest valid value for if (zombie_lifetime_ <= 0) { // default constructor. Set defaults switch (zombie_type_) { case Child::USER: zombie_lifetime_ = default_user_zombie_life_time(); break; case Child::PATH: zombie_lifetime_ = default_path_zombie_life_time(); break; case Child::ECF: zombie_lifetime_ = default_ecf_zombie_life_time(); break; case Child::ECF_PID: zombie_lifetime_ = default_ecf_zombie_life_time(); break; case Child::ECF_PID_PASSWD: zombie_lifetime_ = default_ecf_zombie_life_time(); break; case Child::ECF_PASSWD: zombie_lifetime_ = default_ecf_zombie_life_time(); break; case Child::NOT_SET: assert(false); break; } } else if (zombie_lifetime_ < minimum_zombie_life_time()) { zombie_lifetime_ = minimum_zombie_life_time(); } } bool ZombieAttr::operator==(const ZombieAttr& rhs) const { if (child_cmds_ != rhs.child_cmds_) { return false; } if (zombie_type_ != rhs.zombie_type_) { return false; } if (action_ != rhs.action_) { return false; } if (zombie_lifetime_ != rhs.zombie_lifetime_) { return false; } return true; } std::string ZombieAttr::toString() const { /// format is zombie_type : child_cmds(optional) : action : zombie_lifetime_(optional) std::string ret; write(ret); return ret; } void ZombieAttr::write(std::string& ret) const { /// format is zombie_type : child_cmds(optional) : action : zombie_lifetime_(optional) ret += "zombie "; ret += Child::to_string(zombie_type_); ret += ecf::string_constants::colon; ret += ecf::to_string(action_); ret += ecf::string_constants::colon; ret += Child::to_string(child_cmds_); ret += ecf::string_constants::colon; ret += ecf::convert_to(zombie_lifetime_); } bool ZombieAttr::fob(ecf::Child::CmdType child_cmd) const { if (action_ != ZombieCtrlAction::FOB) { return false; } if (child_cmds_.empty()) { return true; } // If we have child commands specified, then the action is only applicable for that child cmd // for all other child cmds we block for (auto i : child_cmds_) { if (i == child_cmd) { return true; } } return false; } bool ZombieAttr::fail(ecf::Child::CmdType child_cmd) const { if (action_ != ZombieCtrlAction::FAIL) { return false; } if (child_cmds_.empty()) { return true; } // If we have child commands specified, then the action is only applicable for that child cmd // for all other child cmds we block for (auto i : child_cmds_) { if (i == child_cmd) { return true; } } return false; } bool ZombieAttr::adopt(ecf::Child::CmdType child_cmd) const { if (action_ != ZombieCtrlAction::ADOPT) { return false; } if (child_cmds_.empty()) { return true; } // If we have child commands specified, then the action is only applicable for that child cmd // for all other child cmds we block for (auto i : child_cmds_) { if (i == child_cmd) { return true; } } return false; } bool ZombieAttr::remove(ecf::Child::CmdType child_cmd) const { if (action_ != ZombieCtrlAction::REMOVE) { return false; } if (child_cmds_.empty()) { return true; } // If we have child commands specified, then the action is only applicable for that child cmd // for all other child cmds we block for (auto i : child_cmds_) { if (i == child_cmd) { return true; } } return false; } bool ZombieAttr::block(ecf::Child::CmdType child_cmd) const { if (action_ != ZombieCtrlAction::BLOCK) { return false; } if (child_cmds_.empty()) { return true; } // If we have child commands specified, then the action is only applicable for that child cmd // for all other child cmds we block for (auto i : child_cmds_) { if (i == child_cmd) { return true; } } return false; } bool ZombieAttr::kill(ecf::Child::CmdType child_cmd) const { if (action_ != ZombieCtrlAction::KILL) { return false; } if (child_cmds_.empty()) { return true; } // If we have child commands specified, then the action is only applicable for that child cmd // for all other child cmds we block for (auto i : child_cmds_) { if (i == child_cmd) { return true; } } return false; } ZombieAttr ZombieAttr::create(const std::string& string_to_parse) { /// Use boost tokenizer instead of Str::split, as it allows preservation of empty tokens boost::char_separator sep(":", "", boost::keep_empty_tokens); using tokenizer = boost::tokenizer>; tokenizer tokenise(string_to_parse, sep); std::vector tokens; std::copy(tokenise.begin(), tokenise.end(), back_inserter(tokens)); if (tokens.size() < 2) { throw std::runtime_error("ZombieAttr::create failed: Invalid zombie type " + string_to_parse); } /// expects ::child_cmds:zombie_lifetime std::string str_zombie_type; std::string action_str; std::string child_cmds; std::string lifetime; size_t tokens_size = tokens.size(); for (size_t i = 0; i < tokens_size; i++) { // cout << " token " << i << ": '" << tokens[i] << "'\n"; if (i == 0) { str_zombie_type = tokens[i]; continue; } if (i == 1) { action_str = tokens[i]; continue; } if (i == 2) { child_cmds = tokens[i]; continue; } if (i == 3) { lifetime = tokens[i]; continue; } throw std::runtime_error("ZombieAttr::create failed: Invalid zombie tokens " + string_to_parse); } // std::cout << " zombie_type = " << str_zombie_type << " user_action = " << action_str << " child_cmds = " // << child_cmds<< " zombie_lifetime = " << lifetime << "\n"; if (!Child::valid_zombie_type(str_zombie_type)) { throw std::runtime_error("ZombieAttr::create failed: Invalid zombie type, expected one of [ user | ecf | " "ecf_pid | ecf_pid_passed | ecf_passwd | path ] but found " + str_zombie_type + std::string(":") + string_to_parse); } if (!action_str.empty() && !ecf::Enumerate::is_valid(action_str)) { throw std::runtime_error("ZombieAttr::create failed: Invalid user action, expected one of [ fob | fail | " "remove | block | adopt | kill ] but found " + action_str + std::string(":") + string_to_parse); } if (!child_cmds.empty() && !Child::valid_child_cmds(child_cmds)) { throw std::runtime_error("ZombieAttr::create failed: Invalid child type, expected one or more of [ " "init,event,meter,label,wait,queue,abort,complete] but found " + tokens[2] + std::string(":") + string_to_parse); } int zombie_lifetime = -1; if (!lifetime.empty()) { try { zombie_lifetime = ecf::convert_to(lifetime); } catch (const ecf::bad_conversion&) { throw std::runtime_error("ZombieAttr::create failed: Zombie life time must be convertible to an integer " + lifetime + std::string(":") + string_to_parse); } } if (action_str.empty() && zombie_lifetime == -1) { throw std::runtime_error( "ZombieAttr::create failed: User Action(fob,fail,remove,adopt,block) or lifetime must be specified: " + string_to_parse); } auto zombie_type = Child::zombie_type(str_zombie_type); auto action = Enumerate::to_enum(action_str).value_or(ZombieCtrlAction::BLOCK); auto childVec = Child::child_cmds(child_cmds); /// If zombie_lifetime is still -1 constructor will reset to standard defaults return ZombieAttr(zombie_type, childVec, action, zombie_lifetime); } ZombieAttr ZombieAttr::get_default_attr(ecf::Child::ZombieType zt) { switch (zt) { case Child::USER: return ZombieAttr( zt, std::vector(), ZombieCtrlAction::BLOCK, default_user_zombie_life_time()); case Child::PATH: return ZombieAttr( zt, std::vector(), ZombieCtrlAction::BLOCK, default_path_zombie_life_time()); case Child::ECF: return ZombieAttr( zt, std::vector(), ZombieCtrlAction::BLOCK, default_ecf_zombie_life_time()); case Child::ECF_PID: return ZombieAttr( zt, std::vector(), ZombieCtrlAction::BLOCK, default_ecf_zombie_life_time()); case Child::ECF_PID_PASSWD: return ZombieAttr( zt, std::vector(), ZombieCtrlAction::BLOCK, default_ecf_zombie_life_time()); case Child::ECF_PASSWD: return ZombieAttr( zt, std::vector(), ZombieCtrlAction::BLOCK, default_ecf_zombie_life_time()); case Child::NOT_SET: break; } return ZombieAttr( Child::ECF, std::vector(), ZombieCtrlAction::BLOCK, default_ecf_zombie_life_time()); } template void ZombieAttr::serialize(Archive& ar, std::uint32_t const version) { ar(CEREAL_NVP(child_cmds_), CEREAL_NVP(zombie_type_), CEREAL_NVP(action_), CEREAL_NVP(zombie_lifetime_)); } CEREAL_TEMPLATE_SPECIALIZE_V(ZombieAttr); ecflow-5.19.0/libs/attribute/src/ecflow/attribute/DateAttr.cpp0000664000175000017500000002604615250005516024501 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #include "ecflow/attribute/DateAttr.hpp" #include #include #include "ecflow/core/Calendar.hpp" #include "ecflow/core/Chrono.hpp" #include "ecflow/core/Converter.hpp" #include "ecflow/core/Ecf.hpp" #include "ecflow/core/Extract.hpp" #include "ecflow/core/Message.hpp" #include "ecflow/core/Serialization.hpp" using namespace ecf; //========================================================================================== DateAttr::DateAttr(int day, int month, int year) : day_(day), month_(month), year_(year) { checkDate(day_, month_, year_, true /* allow wild cards */); } DateAttr::DateAttr(const std::string& str) { DateAttr::getDate(str, day_, month_, year_); checkDate(day_, month_, year_, true /* allow wild cards */); } bool DateAttr::operator<(const DateAttr& rhs) const { if (year_ < rhs.year_) { return true; } if (year_ == rhs.year_) { if (month_ < rhs.month_) { return true; } if (month_ == rhs.month_) { return day_ < rhs.day_; } } return false; } void DateAttr::checkDate(int day, int month, int year, bool allow_wild_cards) { if (allow_wild_cards) { if (day != 0 && (day < 1 || day > 31)) { throw std::out_of_range( "Invalid Date(day,month,year) : the day >= 0 and day < 31, where 0 means wild card "); } if (month != 0 && (month < 1 || month > 12)) { throw std::out_of_range( "Invalid Date(day,month,year): the month >=0 and month <= 12, where 0 means wild card"); } if (year < 0) { throw std::out_of_range("Invalid Date(day,month,year): the year >=0, where 0 means wild card"); } } else { if (day < 1 || day > 31) { throw std::out_of_range("Invalid date attribute : the day >= 1 and day < 31"); } if (month < 1 || month > 12) { throw std::out_of_range("Invalid date attribute: the month >=1 and month <= 12"); } if (year <= 0) { throw std::out_of_range("Invalid date attribute: the year >0"); } } if (day != 0 && month != 0 && year != 0) { // The following validates the given date // -- i.e. an bad_year/_month/_day_of_month exception is thrown in case the date is invalid boost::gregorian::date(year, month, day); } } void DateAttr::calendarChanged(const ecf::Calendar& c, bool clear_at_midnight) { // See ECFLOW-337 versus ECFLOW-1550 if (c.dayChanged()) { if (clear_at_midnight) { clearFree(); } } if (free_) { return; } if (is_free(c)) { setFree(); } } void DateAttr::reset() { free_ = false; state_change_no_ = Ecf::incr_state_change_no(); } void DateAttr::requeue() { free_ = false; state_change_no_ = Ecf::incr_state_change_no(); } void DateAttr::setFree() { free_ = true; state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO std::cout << "DateAttr::setFree()\n"; #endif } void DateAttr::clearFree() { free_ = false; state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO std::cout << "DateAttr::clearFree()\n"; #endif } bool DateAttr::isFree(const ecf::Calendar& calendar) const { // The FreeDepCmd can be used to free the dates, if (free_) { return true; } return is_free(calendar); } bool DateAttr::is_free(const ecf::Calendar& calendar) const { bool dayMatches = true; bool monthMatches = true; bool yearMatches = true; if (day_ != 0) { dayMatches = calendar.day_of_month() == day_; } if (month_ != 0) { monthMatches = calendar.month() == month_; } if (year_ != 0) { yearMatches = calendar.year() == year_; } return (dayMatches && monthMatches && yearMatches); } bool DateAttr::checkForRequeue(const ecf::Calendar& calendar) const { // if calendar is hybrid, we cannot re-queue if (calendar.hybrid()) { return false; } // checkForRequeue is called when we are deciding whether to re-queue the node. // If this date is in the future, we should re-queue if (day_ != 0 && month_ != 0 && year_ != 0) { return boost::gregorian::date(year_, month_, day_) > calendar.date(); } bool futureDayMatches = true; bool futureMonthMatches = true; bool futureYearMatches = true; if (day_ != 0) { futureDayMatches = day_ > calendar.day_of_month(); } if (month_ != 0) { futureMonthMatches = month_ > calendar.month(); } if (year_ != 0) { futureYearMatches = year_ > calendar.year(); } return (futureDayMatches || futureMonthMatches || futureYearMatches); } bool DateAttr::validForHybrid(const ecf::Calendar& calendar) const { if (day_ == 0) { return false; // relies on day change i.e. date *.10.2009 } if (month_ == 0) { return false; // relies on day change i.e. date 12.*.2009 } if (year_ == 0) { return false; // relies on day change i.e. date 12.10.* } // if the date matches exactly for today return (day_ == calendar.day_of_month() && month_ == calendar.month() && year_ == calendar.year()); } bool DateAttr::why(const ecf::Calendar& c, std::string& theReasonWhy) const { if (isFree(c)) { return false; } theReasonWhy += MESSAGE(" is date dependent ( next run on " << boost::gregorian::to_simple_string(next_matching_date(c)) << " the current date is " << c.day_of_month() << "/" << c.month() << "/" << c.year() << " )"); return true; } std::string DateAttr::name() const { std::string os; write(os); if (free_) { os += " # free"; } return os; } std::string DateAttr::toString() const { std::string ret; write(ret); return ret; } void DateAttr::write(std::string& ret) const { ret += "date "; if (day_ == 0) { ret += "*."; } else { ret += ecf::convert_to(day_); ret += "."; } if (month_ == 0) { ret += "*."; } else { ret += ecf::convert_to(month_); ret += "."; } if (year_ == 0) { ret += "*"; } else { ret += ecf::convert_to(year_); } } std::string DateAttr::dump() const { return MESSAGE(toString() << (free_ ? " (free)" : " (holding)")); } bool DateAttr::operator==(const DateAttr& rhs) const { if (free_ != rhs.free_) { return false; } return structureEquals(rhs); } bool DateAttr::structureEquals(const DateAttr& rhs) const { if (day_ != rhs.day_) { return false; } if (month_ != rhs.month_) { return false; } if (year_ != rhs.year_) { return false; } return true; } DateAttr DateAttr::create(const std::string& dateString) { int day = -1, month = -1, year = -1; getDate(dateString, day, month, year); return {day, month, year}; } DateAttr DateAttr::create(const std::vector& lineTokens, bool read_state) { // date 15.11.2009 # free // with PersistStyle::STATE & MIGRATE // date 15.*.* # // date *.1.* // for(size_t i =0; i < lineTokens.size() ; i++) { // cout << "lineTokens[" << i << "] = '" << lineTokens[i] << "'\n"; // } DateAttr date = DateAttr::create(lineTokens[1]); if (read_state) { for (size_t i = 3; i < lineTokens.size(); i++) { if (lineTokens[i] == "free") { date.setFree(); } } } return date; } void DateAttr::getDate(const std::string& date, int& day, int& month, int& year) { size_t firstDotPos = date.find_first_of('.'); size_t lastDotPos = date.find_first_of('.', firstDotPos + 1); if (firstDotPos == std::string::npos) { throw std::runtime_error("DateAttr::getDate Invalid date missing first dot :" + date); } if (lastDotPos == std::string::npos) { throw std::runtime_error("DateAttr::getDate: Invalid date missing second dot :" + date); } if (firstDotPos == lastDotPos) { throw std::runtime_error("DateAttr::getDate: Invalid date :" + date); } std::string theDay = date.substr(0, firstDotPos); std::string theMonth = date.substr(firstDotPos + 1, (lastDotPos - firstDotPos) - 1); std::string theYear = date.substr(lastDotPos + 1); if (theDay == "*") { day = 0; } else { day = Extract::value(theDay, "DateAttr::getDate: Invalid day :" + date); if (day < 1 || day > 31) { throw std::runtime_error("DateAttr::getDate: Invalid clock date: " + date); } } if (theMonth == "*") { month = 0; } else { month = Extract::value(theMonth, "DateAttr::getDate: Invalid month :" + date); if (month < 1 || month > 12) { throw std::runtime_error("DateAttr::getDate Invalid clock date: " + date); } } if (theYear == "*") { year = 0; } else { year = Extract::value(theYear, "DateAttr::getDate: Invalid year :" + date); } if (day == -1 || month == -1 || year == -1) { throw std::runtime_error("DateAttr::getDate: Invalid clock date:" + date); } if (day != 0 && month != 0 && year != 0) { // The following validates the given date // -- i.e. an bad_year/_month/_day_of_month exception is thrown in case the date is invalid boost::gregorian::date(year, month, day); } } boost::gregorian::date DateAttr::next_matching_date(const ecf::Calendar& c) const { auto next_matching_date = c.date(); // today's date auto one_day = boost::gregorian::date_duration(1); bool day_matches = (day_ == 0) ? true : false; bool month_matches = (month_ == 0) ? true : false; bool year_matches = (year_ == 0) ? true : false; for (int i = 0; i < 365; i++) { next_matching_date += one_day; if (day_ != 0 && next_matching_date.day() == day_) { day_matches = true; } if (month_ != 0 && next_matching_date.month() == month_) { month_matches = true; } if (year_ != 0 && next_matching_date.year() == year_) { year_matches = true; } if (day_matches && month_matches && year_matches) { return next_matching_date; } } return c.date(); } template void DateAttr::serialize(Archive& ar) { ar(CEREAL_NVP(day_), CEREAL_NVP(month_), CEREAL_NVP(year_)); CEREAL_OPTIONAL_NVP(ar, free_, [this]() { return free_; }); // conditionally save } CEREAL_TEMPLATE_SPECIALIZE(DateAttr); ecflow-5.19.0/libs/attribute/src/ecflow/attribute/AutoCancelAttr.hpp0000664000175000017500000000342415250005516025642 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #ifndef ecflow_attribute_AutoCancelAttr_HPP #define ecflow_attribute_AutoCancelAttr_HPP #include #include "ecflow/core/TimeSlot.hpp" namespace ecf { class Calendar; } // namespace ecf namespace ecf { // Use compiler , destructor, assignment, copy constructor class AutoCancelAttr { public: AutoCancelAttr() = default; AutoCancelAttr(int hour, int minute, bool relative) : time_(hour, minute), relative_(relative) {} AutoCancelAttr(const TimeSlot& ts, bool relative) : time_(ts), relative_(relative) {} explicit AutoCancelAttr(int days) : time_(TimeSlot(days * 24, 0)), days_(true) {} bool operator==(const AutoCancelAttr& rhs) const; bool operator<(const AutoCancelAttr& rhs) const { return time_ < rhs.time(); } bool isFree(const ecf::Calendar&, const boost::posix_time::time_duration& suiteDurationAtComplete) const; std::string toString() const; const TimeSlot& time() const { return time_; } bool relative() const { return relative_; } bool days() const { return days_; } public: void write(std::string& ret) const; private: TimeSlot time_; bool relative_{true}; bool days_{false}; friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const /*version*/); }; } // namespace ecf #endif /* ecflow_attribute_AutoCancelAttr_HPP */ ecflow-5.19.0/libs/attribute/src/ecflow/attribute/LateAttr.hpp0000664000175000017500000001102615250005516024506 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #ifndef ecflow_attribute_LateAttr_HPP #define ecflow_attribute_LateAttr_HPP #include #include // for pair #include #include "ecflow/core/Chrono.hpp" #include "ecflow/core/TimeSlot.hpp" class NState; namespace ecf { class Calendar; } // namespace ecf namespace ecf { /// ======================================================================== /// Use compiler, destructor, assignment, copy constructor, /// /// ************************************************************************ /// late attribute: Only applies to a task, it can be set on suite/family /// but this is treated as an inherited attribute. /// late attribute lower down the hierarchy overrides it. /// ************************************************************************ /// The late _late_ attribute will not work correctly when the suites clock /// start and stops with the server. Since the late relies on real time /// for some of its functionality. /// -s submitted: The time node can stay submitted (format [+]hh:mm). submitted is always /// relative, so + is simply ignored, if present. If the node stays submitted /// longer than the time specified, the late flag is set /// -a Active : The time of day the node must have become active (format hh:mm). If the node /// is still queued or submitted, the late flag is set /// -c Complete : The time node must become complete (format {+}hh:mm). If relative, time is /// taken from the time the node became active, otherwise node must be complete by /// the time given. /// ======================================================================== class LateAttr { public: LateAttr(); void print(std::string&) const; bool operator==(const LateAttr& rhs) const; void addSubmitted(const TimeSlot& s) { s_ = s; } void add_submitted(int hour, int minute) { s_ = TimeSlot(hour, minute); } void addActive(const TimeSlot& s) { a_ = s; } void add_active(int hour, int minute) { a_ = TimeSlot(hour, minute); } void addComplete(const TimeSlot& s, bool relative) { c_ = s; c_is_rel_ = relative; } void add_complete(int hour, int minute, bool relative) { c_ = TimeSlot(hour, minute); c_is_rel_ = relative; } const TimeSlot& submitted() const { return s_; } const TimeSlot& active() const { return a_; } const TimeSlot& complete() const { return c_; } bool complete_is_relative() const { return c_is_rel_; } /// i.e no time structs specified bool isNull() const; /// Given the state and time of state change, and calendar work out if we are late /// if we are sets the late flag void checkForLateness(const std::pair& state, const ecf::Calendar& c); bool check_for_lateness(const std::pair& state, const ecf::Calendar& c) const; /// To be used by GUI to inform used that a node is late bool isLate() const { return isLate_; } /// To be called at begin and re-queue time void reset() { setLate(false); } // Override this late attributes with the settings form the input. void override_with(LateAttr*); // The state_change_no is never reset. Must be incremented if it can affect equality unsigned int state_change_no() const { return state_change_no_; } /// set flag to be late void setLate(bool f); std::string toString() const; std::string name() const { return toString(); } static void parse(LateAttr&, const std::string& line, const std::vector& lineTokens, size_t index); static LateAttr create(const std::string& lateString); public: void write(std::string&) const; private: TimeSlot s_; // relative by default TimeSlot a_; TimeSlot c_; unsigned int state_change_no_{0}; // *not* persisted, only used on server side bool c_is_rel_{false}; bool isLate_{false}; private: friend class cereal::access; template void serialize(Archive& ar); }; } // namespace ecf #endif /* ecflow_attribute_LateAttr_HPP */ ecflow-5.19.0/libs/attribute/src/ecflow/attribute/RepeatAttr.hpp0000664000175000017500000012076515250005516025054 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #ifndef ecflow_attribute_RepeatAttr_HPP #define ecflow_attribute_RepeatAttr_HPP /// /// \brief Repeat Attribute. Please note that for repeat string, enumeration /// the positional index is used for evaluation. /// /// Simulation: Simulation must not affect the real job submission in the server. /// o Infinite repeats cause problems with simulation, hence we have /// a mechanism to stop this, when reset is called, via server this is disabled /// #include #include #include #include #include #include #include #include #include #include "ecflow/attribute/Variable.hpp" #include "ecflow/core/Chrono.hpp" #include "ecflow/core/TypeTraits.hpp" ///////////////////////////////////////////////////////////////////////// // Node can only have one repeat. // class RepeatBase { public: explicit RepeatBase(const std::string& name) : name_(name) {} RepeatBase() = default; virtual ~RepeatBase(); /// make non-virtual so that it can be in-lined. Called millions of times const std::string& name() const { return name_; } virtual int start() const = 0; virtual int end() const = 0; virtual int step() const = 0; /// /// @brief Retrieve the zero-based index of the current repeat position. /// /// @return the zero-based index of the current repeat position /// virtual long current_index() const = 0; /// /// @brief Retrieve the current repeat value /// /// @return the current repeat value as a human-readable string. /// virtual std::string current_value() const = 0; // Handle generated variables virtual void gen_variables(std::vector& vec) const { vec.push_back(var_); } virtual const Variable& find_gen_variable(const std::string& name) const { return name == name_ ? var_ : Variable::EMPTY(); } virtual void update_repeat_genvar() const; // After Repeat expiration the last call to increment() can cause // value to be beyond the last valid value // Depending on the kind of repeat the returned can be value or the current index // RepeatDate -> value // RepeatDateTime -> value // RepeatDateList -> value // RepeatString -> index into array of strings // RepeatInteger -> value // RepeatEnumerated -> index | value return value at index if cast-able to integer, otherwise return index ****** // RepeatDay -> value virtual long value() const = 0; // Depending on the kind of repeat the returned can be value or *current* index // RepeatDate -> value // RepeatDateTime -> value // RepeatDateList -> value // RepeatString -> index ( will always return an index) // RepeatInteger -> value // RepeatEnumerated -> index ( will always return an index) // RepeatDay -> value virtual long index_or_value() const = 0; virtual void increment() = 0; // returns a value with in the range start/end // Hence at Repeat expiration will return value associated with end() virtual long last_valid_value() const = 0; virtual long last_valid_value_minus(int val) const { return last_valid_value() - val; } virtual long last_valid_value_plus(int val) const { return last_valid_value() + val; } virtual RepeatBase* clone() const = 0; virtual bool compare(RepeatBase*) const = 0; virtual bool valid() const = 0; virtual void setToLastValue() = 0; virtual std::string valueAsString() const = 0; // uses last_valid_value virtual std::string value_as_string(int index) const = 0; // used in test only virtual std::string next_value_as_string() const = 0; virtual std::string prev_value_as_string() const = 0; virtual void reset() = 0; virtual void change(const std::string& newValue) = 0; // can throw std::runtime_error virtual void changeValue(long newValue) = 0; // can throw std::runtime_error virtual void set_value(long new_value_or_index) = 0; // will NOT throw, allows any value std::string toString() const; virtual std::string dump() const = 0; unsigned int state_change_no() const { return state_change_no_; } /// Simulator functions: virtual bool isInfinite() const = 0; virtual bool is_repeat_day() const { return false; } virtual bool isDate() const { return false; } virtual bool isDateTime() const { return false; } virtual bool isDateList() const { return false; } virtual bool isDateTimeList() const { return false; } virtual bool isInteger() const { return false; } virtual bool isEnumerated() const { return false; } virtual bool isString() const { return false; } virtual bool isDay() const { return false; } protected: void incr_state_change_no(); mutable Variable var_; // *not* persisted std::string name_; unsigned int state_change_no_{0}; // *not* persisted, only used on server side private: friend class cereal::access; template void serialize(Archive& ar); }; /// /// The date has no meaning in a physical sense, only used as a for loop over dates class RepeatDate final : public RepeatBase { public: RepeatDate(const std::string& variable, int start, int end, int delta = 1 /* always in days*/); RepeatDate() = default; void gen_variables(std::vector& vec) const override; const Variable& find_gen_variable(const std::string& name) const override; void update_repeat_genvar() const override; int start() const override { return start_; } int end() const override { return end_; } int step() const override { return delta_; } long value() const override { return value_; } long index_or_value() const override { return value_; } long last_valid_value() const override; long last_valid_value_minus(int value) const override; long last_valid_value_plus(int value) const override; /// /// @brief Retrieve the current zero-based index /// /// @return the zero-based index into the repeat sequence /// long current_index() const override; /// /// @brief Retrieve the current value /// /// @return the current date formatted as a yyyymmdd string. /// std::string current_value() const override { return std::to_string(value_); } void delta(int d) { delta_ = d; } int delta() const { return delta_; } bool operator==(const RepeatDate& rhs) const; bool operator<(const RepeatDate& rhs) const { return name() < rhs.name(); } RepeatDate* clone() const override { return new RepeatDate(name_, start_, end_, delta_, value_); } bool compare(RepeatBase*) const override; bool valid() const override { return (delta_ > 0) ? (value_ <= end_) : (value_ >= end_); } std::string valueAsString() const override; std::string value_as_string(int index) const override; std::string next_value_as_string() const override; std::string prev_value_as_string() const override; void setToLastValue() override; void reset() override; void increment() override; void change(const std::string& newValue) override; // can throw std::runtime_error void changeValue(long newValue) override; // can throw std::runtime_error void set_value(long newValue) override; // will NOT throw, allows any value std::string dump() const override; bool isDate() const override { return true; } /// Simulator functions: bool isInfinite() const override { return false; } private: long valid_value(long value) const; RepeatDate(const std::string& name, int start, int end, int delta, long value) : RepeatBase(name), start_(start), end_(end), delta_(delta), value_(value) {} void update_repeat_genvar_value() const; private: int start_{0}; int end_{0}; int delta_{0}; long value_{0}; mutable Variable yyyy_; // *not* persisted mutable Variable mm_; // *not* persisted mutable Variable dom_; // *not* persisted mutable Variable dow_; // *not* persisted mutable Variable julian_; // *not* persisted friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const version); }; class RepeatDateTime final : public RepeatBase { public: RepeatDateTime(const std::string& variable, const std::string& start, const std::string& end, const std::string& delta = "24:00:00"); RepeatDateTime(const std::string& variable, ecf::Instant start, ecf::Instant end, ecf::Duration delta); RepeatDateTime() = default; void gen_variables(std::vector& vec) const override; const Variable& find_gen_variable(const std::string& name) const override; void update_repeat_genvar() const override; const ecf::Instant& start_instant() const { return start_; } const ecf::Instant& end_instant() const { return end_; } const ecf::Duration& step_duration() const { return delta_; } const ecf::Instant& value_instant() const { return value_; } int start() const override { return coerce_from_instant_into_seconds(start_); } int end() const override { return coerce_from_instant_into_seconds(end_); } int step() const override { return delta_.as_seconds().count(); } long value() const override { return coerce_from_instant_into_seconds(value_); } long index_or_value() const override { return coerce_from_instant_into_seconds(value_); } long last_valid_value() const override; long last_valid_value_minus(int value) const override; long last_valid_value_plus(int value) const override; /// /// @brief Retrieve the current zero-based index /// /// @return the zero-based index into the repeat sequence /// long current_index() const override { return (value() - start()) / step(); } /// /// @brief Retrieve the current value /// /// @return the current value formatted as a yyyymmddTHHMMSS string. /// std::string current_value() const override { return ecf::Instant::format(value_); } void delta(const ecf::Duration& d) { delta_ = d; } bool operator==(const RepeatDateTime& rhs) const; bool operator<(const RepeatDateTime& rhs) const { return name() < rhs.name(); } RepeatDateTime* clone() const override { return new RepeatDateTime(name_, start_, end_, delta_, value_); } bool compare(RepeatBase*) const override; bool valid() const override { return (delta_ > ecf::Duration{std::chrono::seconds{0}}) ? (value_ <= end_) : (value_ >= end_); } std::string valueAsString() const override; std::string value_as_string(int index) const override; std::string next_value_as_string() const override; std::string prev_value_as_string() const override; void setToLastValue() override; void reset() override; void increment() override; void change(const std::string& newValue) override; // can throw std::runtime_error void changeValue(long newValue) override; // can throw std::runtime_error void set_value(long newValue) override; // will NOT throw, allows any value std::string dump() const override; bool isDateTime() const override { return true; } /// Simulator functions: bool isInfinite() const override { return false; } private: ecf::Instant valid_value(const ecf::Instant& value) const; RepeatDateTime(const std::string& name, ecf::Instant start, ecf::Instant end, ecf::Duration delta, ecf::Instant value) : RepeatBase(name), start_(start), end_(end), delta_(delta), value_(value) {} void update_repeat_genvar_value() const; private: ecf::Instant start_; ecf::Instant end_; ecf::Duration delta_; ecf::Instant value_; // *not* persisted mutable VariableMap generated_{ // clang-format off // Date Variable::new_variable(name_ + "_DATE", ""), // Date Components Variable::new_variable(name_ + "_YYYY", ""), Variable::new_variable(name_ + "_MM", ""), Variable::new_variable(name_ + "_DD", ""), Variable::new_variable(name_ + "_JULIAN", ""), // Time Variable::new_variable(name_ + "_TIME", ""), // Time Components4 Variable::new_variable(name_ + "_HOURS", ""), Variable::new_variable(name_ + "_MINUTES", ""), Variable::new_variable(name_ + "_SECONDS", "") // clang-format on }; friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const version); }; class RepeatDateList final : public RepeatBase { public: RepeatDateList(const std::string& variable, const std::vector&); // will throw for empty list RepeatDateList() = default; void gen_variables(std::vector& vec) const override; const Variable& find_gen_variable(const std::string& name) const override; void update_repeat_genvar() const override; bool operator==(const RepeatDateList& rhs) const; bool operator<(const RepeatDateList& rhs) const { return name() < rhs.name(); } int start() const override; int end() const override; int step() const override { return 1; } long value() const override; // return value at index otherwise return 0 long value_at(size_t i) const { assert(i < list_.size()); return list_[i]; } long index_or_value() const override { return currentIndex_; } long last_valid_value() const override; long last_valid_value_minus(int value) const override; long last_valid_value_plus(int value) const override; /// /// @brief Retrieve the current zero-based index /// /// @return the zero-based index into the repeat sequence /// long current_index() const override { return currentIndex_; } /// /// @brief Retrieve the current value /// /// @return the current value formatted as a yyyymmdd string, or "" if out of bounds. /// std::string current_value() const override { return currentIndex_ >= 0 && currentIndex_ < static_cast(list_.size()) ? std::to_string(list_[currentIndex_]) : ""; } RepeatBase* clone() const override { return new RepeatDateList(name_, list_, currentIndex_); } bool compare(RepeatBase*) const override; bool valid() const override { return (currentIndex_ >= 0 && currentIndex_ < static_cast(list_.size())); } std::string valueAsString() const override; std::string value_as_string(int index) const override; std::string next_value_as_string() const override; std::string prev_value_as_string() const override; const std::vector& values() const { return list_; } void setToLastValue() override; void reset() override; void increment() override; void change(const std::string& newValue) override; // can throw std::runtime_error void changeValue(long newValue) override; // can throw std::runtime_error void set_value(long newValue) override; // will NOT throw, allows any value std::string dump() const override; bool isDateList() const override { return true; } int indexNum() const { return static_cast(list_.size()); } /// Simulator functions: bool isInfinite() const override { return false; } private: RepeatDateList(const std::string& variable, const std::vector& l, int index) : RepeatBase(variable), currentIndex_(index), list_(l) {} void update_repeat_genvar_value() const; private: int currentIndex_{0}; std::vector list_; mutable Variable yyyy_; // *not* persisted mutable Variable mm_; // *not* persisted mutable Variable dom_; // *not* persisted mutable Variable dow_; // *not* persisted mutable Variable julian_; // *not* persisted friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const version); }; class RepeatDateTimeList final : public RepeatBase { public: RepeatDateTimeList(const std::string& variable, const std::vector&); // will throw for empty list RepeatDateTimeList() = default; void gen_variables(std::vector& vec) const override; const Variable& find_gen_variable(const std::string& name) const override; void update_repeat_genvar() const override; bool operator==(const RepeatDateTimeList& rhs) const; bool operator<(const RepeatDateTimeList& rhs) const { return name() < rhs.name(); } int start() const override; int end() const override; int step() const override { return 1; } long value() const override; long index_or_value() const override { return currentIndex_; } long last_valid_value() const override; long last_valid_value_minus(int value) const override; long last_valid_value_plus(int value) const override; /// /// @brief Retrieve the current zero-based index /// /// @return the zero-based index into the repeat sequence /// long current_index() const override { return currentIndex_; } /// /// @brief Retrieve the current value /// /// @return the current value formatted as a yyyymmddTHHMMSS string, or "" if out of bounds. /// std::string current_value() const override { return currentIndex_ >= 0 && currentIndex_ < static_cast(list_.size()) ? ecf::Instant::format(list_[currentIndex_]) : ""; } RepeatBase* clone() const override { return new RepeatDateTimeList(name_, list_, currentIndex_); } bool compare(RepeatBase*) const override; bool valid() const override { return (currentIndex_ >= 0 && currentIndex_ < static_cast(list_.size())); } std::string valueAsString() const override; std::string value_as_string(int index) const override; std::string next_value_as_string() const override; std::string prev_value_as_string() const override; const std::vector& values() const { return list_; } void setToLastValue() override; void reset() override; void increment() override; void change(const std::string& newValue) override; // can throw std::runtime_error void changeValue(long newValue) override; // can throw std::runtime_error void set_value(long newValue) override; // will NOT throw, allows any value std::string dump() const override; bool isDateTimeList() const override { return true; } int indexNum() const { return static_cast(list_.size()); } /// Simulator functions: bool isInfinite() const override { return false; } private: RepeatDateTimeList(const std::string& variable, const std::vector& l, int index) : RepeatBase(variable), currentIndex_(index), list_(l) {} void update_repeat_genvar_value() const; private: int currentIndex_{0}; std::vector list_; // *not* persisted mutable VariableMap generated_{ // clang-format off Variable::new_variable(name_ + "_DATE", ""), Variable::new_variable(name_ + "_YYYY", ""), Variable::new_variable(name_ + "_MM", ""), Variable::new_variable(name_ + "_DD", ""), Variable::new_variable(name_ + "_JULIAN", ""), Variable::new_variable(name_ + "_TIME", ""), Variable::new_variable(name_ + "_HOURS", ""), Variable::new_variable(name_ + "_MINUTES", ""), Variable::new_variable(name_ + "_SECONDS", "") // clang-format on }; friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const version); }; class RepeatInteger final : public RepeatBase { public: RepeatInteger(const std::string& variable, int start, int end, int delta = 1); RepeatInteger(); bool operator==(const RepeatInteger& rhs) const; bool operator<(const RepeatInteger& rhs) const { return name() < rhs.name(); } int start() const override { return start_; } int end() const override { return end_; } int step() const override { return delta_; } long value() const override { return value_; } long index_or_value() const override { return value_; } long last_valid_value() const override; int delta() const { return delta_; } /// /// @brief Retrieve the current zero-based index /// /// @return the zero-based index into the repeat sequence /// long current_index() const override { return (value_ - start_) / delta_; } /// /// @brief Retrieve the current value /// /// @return the current value as a decimal string. /// std::string current_value() const override { return std::to_string(value_); } RepeatInteger* clone() const override { return new RepeatInteger(name_, start_, end_, delta_, value_); } bool compare(RepeatBase*) const override; bool valid() const override { return (delta_ > 0) ? (value_ <= end_) : (value_ >= end_); } std::string valueAsString() const override; std::string value_as_string(int index) const override; std::string next_value_as_string() const override; std::string prev_value_as_string() const override; void setToLastValue() override; void reset() override; void increment() override; void change(const std::string& newValue) override; // can throw std::runtime_error void changeValue(long newValue) override; // can throw std::runtime_error void set_value(long newValue) override; // will NOT throw, allows any value std::string dump() const override; bool isInteger() const override { return true; } /// Simulator functions: bool isInfinite() const override { return false; } private: long valid_value(long value) const; RepeatInteger(const std::string& name, int start, int end, int delta, long value) : RepeatBase(name), start_(start), end_(end), delta_(delta), value_(value) {} private: int start_{0}; int end_{0}; int delta_{0}; long value_{0}; friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const version); }; // Note:: Difference between RepeatEnumerated and RepeatString, is that // RepeatEnumerated::value() will return the value at the index if cast-able to integer, // whereas RepeatString::value() will always return the index. class RepeatEnumerated final : public RepeatBase { public: RepeatEnumerated(const std::string& variable, const std::vector& theEnums); RepeatEnumerated() = default; bool operator==(const RepeatEnumerated& rhs) const; bool operator<(const RepeatEnumerated& rhs) const { return name() < rhs.name(); } int start() const override { return 0; } int end() const override; int step() const override { return 1; } long value() const override; // return value at index if cast-able to integer, otherwise return index long index_or_value() const override { return currentIndex_; } long last_valid_value() const override; /// /// @brief Retrieve the current zero-based index /// /// @return the zero-based index into the repeat sequence /// long current_index() const override { return currentIndex_; } /// /// @brief Retrieve the current value /// /// @return the current string value, or "" if out of bounds. /// std::string current_value() const override { return currentIndex_ >= 0 && currentIndex_ < static_cast(theEnums_.size()) ? theEnums_[currentIndex_] : ""; } const std::vector& values() const { return theEnums_; } RepeatBase* clone() const override { return new RepeatEnumerated(name_, theEnums_, currentIndex_); } bool compare(RepeatBase*) const override; bool valid() const override { return (currentIndex_ >= 0 && currentIndex_ < static_cast(theEnums_.size())); } std::string valueAsString() const override; std::string value_as_string(int index) const override; std::string next_value_as_string() const override; std::string prev_value_as_string() const override; void setToLastValue() override; void reset() override; void increment() override; void change(const std::string& newValue) override; // can throw std::runtime_error void changeValue(long newValue) override; // can throw std::runtime_error void set_value(long newValue) override; // will NOT throw, allows any value std::string dump() const override; bool isEnumerated() const override { return true; } int indexNum() const { return static_cast(theEnums_.size()); } /// Simulator functions: bool isInfinite() const override { return false; } private: RepeatEnumerated(const std::string& variable, const std::vector& theEnums, int index) : RepeatBase(variable), currentIndex_(index), theEnums_(theEnums) {} private: int currentIndex_{0}; std::vector theEnums_; friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const version); }; class RepeatString final : public RepeatBase { public: RepeatString(const std::string& variable, const std::vector& theEnums); RepeatString() = default; bool operator==(const RepeatString& rhs) const; bool operator<(const RepeatString& rhs) const { return name() < rhs.name(); } int start() const override { return 0; } int end() const override; int step() const override { return 1; } long value() const override { return currentIndex_; } long index_or_value() const override { return currentIndex_; } long last_valid_value() const override; // returns the index /// /// @brief Retrieve the current zero-based index /// /// @return the zero-based index into the repeat sequence /// long current_index() const override { return currentIndex_; } /// /// @brief Retrieve the current value /// /// @return the current string value, or "" if out of bounds. /// std::string current_value() const override { return currentIndex_ >= 0 && currentIndex_ < static_cast(theStrings_.size()) ? theStrings_[currentIndex_] : ""; } RepeatBase* clone() const override { return new RepeatString(name_, theStrings_, currentIndex_); } bool compare(RepeatBase*) const override; bool valid() const override { return (currentIndex_ >= 0 && currentIndex_ < static_cast(theStrings_.size())); } std::string valueAsString() const override; std::string value_as_string(int index) const override; std::string next_value_as_string() const override; std::string prev_value_as_string() const override; const std::vector& values() const { return theStrings_; } void setToLastValue() override; void reset() override; void increment() override; void change(const std::string& newValue) override; // can throw std::runtime_error void changeValue(long newValue) override; // can throw std::runtime_error void set_value(long newValue) override; // will NOT throw, allows any value std::string dump() const override; bool isString() const override { return true; } int indexNum() const { return static_cast(theStrings_.size()); } /// Simulator functions: bool isInfinite() const override { return false; } private: RepeatString(const std::string& variable, const std::vector& theEnums, int index) : RepeatBase(variable), currentIndex_(index), theStrings_(theEnums) {} private: int currentIndex_{0}; std::vector theStrings_; friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const version); }; /// The current repeat day is not that well-defined or deterministic. /// **** Currently I have not come across any suites that use an end-date **** /// o If the suite has defined a real clock /// then number of repeats is deterministic /// However if the end-date is less than suite clock this should be reported as error /// o Currently under the hybrid clock the date is not updated, this raises /// a whole-lot of issues. (We do not want a separate calendar, just for this). /// o If there is _no_ suite clock, then we must use the current day /// now the number of repeats varies, and if end-date is less than the current /// day we need to report an error /// It is not clear what behaviour is required here, hence I will not implement /// the end-date functionality. Until there is clear requirement is this area. /// end-date will be treated as a parser error. /// The minimum deterministic functionality here is to implement the infinite /// repeat that has no end date /// /// RepeatDay do not really have a name: However we need maintain invariant that all NON-empty repeats /// have a name. Hence, the name will be as day /// Note: this applies to the clone as well class RepeatDay final : public RepeatBase { public: RepeatDay() : RepeatBase("day") {} // Enable implicit conversion from integer RepeatDay(int step) : RepeatBase("day"), step_(step) {} bool operator==(const RepeatDay& rhs) const; bool operator<(const RepeatDay& rhs) const { return step_ < rhs.step(); } int start() const override { return 0; } int end() const override { return 0; } int step() const override { return step_; } void increment() override { /* do nothing */ } long value() const override { return step_; } long index_or_value() const override { return step_; } long last_valid_value() const override { return step_; } /// /// @brief Retrieve the current index /// /// For RepeatDay the index is not really meaningful, but we return the step value for consistency with other Repeat /// types. /// /// @return the current index /// long current_index() const override { return step_; } /// /// @brief Retrieve the current value /// /// For RepeatDay the value is not really meaningful, but we return the step value as a string for consistency with /// other Repeat types. /// /// @return the current value as a decimal string. /// std::string current_value() const override { return std::to_string(step_); } RepeatBase* clone() const override { return new RepeatDay(step_, valid_); } bool compare(RepeatBase*) const override; bool valid() const override { return valid_; } std::string valueAsString() const override { return std::string{}; } std::string value_as_string(int) const override { return std::string{}; } std::string next_value_as_string() const override { return std::string{}; } std::string prev_value_as_string() const override { return std::string{}; } void setToLastValue() override { /* do nothing ?? */ } void reset() override { valid_ = true; } void change(const std::string& /*newValue*/) override { /* do nothing */ } void changeValue(long /*newValue*/) override { /* do nothing */ } void set_value(long /*newValue*/) override { /* do nothing */ } std::string dump() const override; bool isDay() const override { return true; } /// Simulator functions: bool isInfinite() const override { return true; } bool is_repeat_day() const override { return true; } private: RepeatDay(int step, bool valid) : RepeatBase("day"), step_(step), valid_(valid) {} private: int step_{1}; bool valid_{true}; // not persisted since only used in simulator friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const version); }; class Repeat { public: Repeat(); // for serialisation // Enable implicit conversion to Repeat Repeat(const RepeatDate&); Repeat(const RepeatDateTime&); Repeat(const RepeatDateList&); Repeat(const RepeatDateTimeList&); Repeat(const RepeatInteger&); Repeat(const RepeatEnumerated&); Repeat(const RepeatString&); Repeat(const RepeatDay&); // Enable copy & move semantics Repeat(const Repeat&); Repeat(Repeat&& rhs); Repeat& operator=(Repeat rhs); ~Repeat(); bool operator==(const Repeat& rhs) const; bool operator<(const Repeat& rhs) const { return name() < rhs.name(); } bool empty() const { return (type_) ? false : true; } void clear() { type_.reset(nullptr); } const std::string& name() const; void gen_variables(std::vector& vec) const { if (type_) { type_->gen_variables(vec); } } const Variable& find_gen_variable(const std::string& name) const { return (type_) ? type_->find_gen_variable(name) : Variable::EMPTY(); } void update_repeat_genvar() const { if (type_) { type_->update_repeat_genvar(); } } int start() const { return (type_) ? type_->start() : 0; } int end() const { return (type_) ? type_->end() : 0; } int step() const { return (type_) ? type_->step() : 0; } long value() const { return (type_) ? type_->value() : 0; } long index_or_value() const { return (type_) ? type_->index_or_value() : 0; } long last_valid_value() const { return (type_) ? type_->last_valid_value() : 0; } long last_valid_value_minus(int val) const { return (type_) ? type_->last_valid_value_minus(val) : -val; } long last_valid_value_plus(int val) const { return (type_) ? type_->last_valid_value_plus(val) : val; } /// /// @brief Retrieve the current zero-based index /// /// @return the current index, or 0 if not holding a specific Repeat type. /// long current_index() const { return (type_) ? type_->current_index() : 0; } /// /// @brief Retrieve the current value /// /// @return the current string value, or "" if not holding a specific Repeat type. /// std::string current_value() const { return (type_) ? type_->current_value() : std::string{}; } bool valid() const { return (type_) ? type_->valid() : false; } void setToLastValue() { if (type_) { type_->setToLastValue(); } } std::string valueAsString() const { return (type_) ? type_->valueAsString() : std::string{}; } std::string value_as_string(int index) const { return (type_) ? type_->value_as_string(index) : std::string{}; } std::string next_value_as_string() const { return (type_) ? type_->next_value_as_string() : std::string{}; } std::string prev_value_as_string() const { return (type_) ? type_->prev_value_as_string() : std::string{}; } void reset() { if (type_) { type_->reset(); } } void increment() { if (type_) { type_->increment(); } } void change(const std::string& newValue) { if (type_) { type_->change(newValue); } } void changeValue(long newValue) { if (type_) { type_->changeValue(newValue); } } void set_value(long newValue) { if (type_) { type_->set_value(newValue); } } std::string toString() const { return (type_) ? type_->toString() : std::string{}; } std::string dump() const { return (type_) ? type_->dump() : std::string{}; } // additional state unsigned int state_change_no() const { return (type_) ? type_->state_change_no() : 0; } /// simulator functions: bool isInfinite() const { return (type_) ? type_->isInfinite() : false; } // Allows Repeat's to be returned by reference static const Repeat& EMPTY(); bool is_repeat_day() const { return (type_) ? type_->is_repeat_day() : false; } /// Expose base for the GUI only, use with caution RepeatBase* repeatBase() { return type_.get(); } const RepeatBase* repeatBase() const { return type_.get(); } template const T& as() const { return dynamic_cast(*repeatBase()); } private: std::unique_ptr type_; friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const version); }; namespace ecf { namespace repeat { template inline std::optional current_value_of(const RepeatType& repeat) { // // This primary template is intentionally reached for combinations where the // Result type does not match the native type of RepeatType — for example, // current_value_of(const RepeatDate&) has no specialisation and // should return nullopt silently (RepeatDate yields int, not string). // // If RepeatType is a completely unknown type (e.g. a new RepeatBase subtype with no // specialisations at all), that is a programming error caught here at compile time. // static_assert( ecf::is_one_of_v, "current_value_of: RepeatType is an unknown Repeat subtype. " "Add explicit specialisation of current_value_of(const RepeatType&) in RepeatAttr.hpp."); return std::nullopt; } template <> inline std::optional current_value_of(const RepeatDate& repeat) { return repeat.value(); } template <> inline std::optional current_value_of(const RepeatDateList& repeat) { return repeat.value(); } template <> inline std::optional current_value_of(const RepeatDateTime& repeat) { return repeat.current_value(); } template <> inline std::optional current_value_of(const RepeatDateTimeList& repeat) { return repeat.current_value(); } template <> inline std::optional current_value_of(const RepeatInteger& repeat) { return repeat.value(); } template <> inline std::optional current_value_of(const RepeatEnumerated& repeat) { return repeat.current_value(); } template <> inline std::optional current_value_of(const RepeatString& repeat) { return repeat.current_value(); } template <> inline std::optional current_value_of(const RepeatDay& repeat) { return repeat.value(); } template inline std::optional current_value_as(const RepeatBase& base) { if (base.isDate()) { auto& repeat = static_cast(base); return current_value_of(repeat); } else if (base.isDateTime()) { auto& repeat = static_cast(base); return current_value_of(repeat); } else if (base.isDateList()) { auto& repeat = static_cast(base); return current_value_of(repeat); } else if (base.isDateTimeList()) { auto& repeat = static_cast(base); return current_value_of(repeat); } else if (base.isInteger()) { auto& repeat = static_cast(base); return current_value_of(repeat); } else if (base.isEnumerated()) { auto& repeat = static_cast(base); return current_value_of(repeat); } else if (base.isString()) { auto& repeat = static_cast(base); return current_value_of(repeat); } else if (base.isDay()) { auto& repeat = static_cast(base); return current_value_of(repeat); } else { return std::nullopt; } } template std::optional current_value_as(const Repeat& repeat) { auto base = repeat.repeatBase(); return (base) ? current_value_as(*base) : std::nullopt; } } // namespace repeat } // namespace ecf #endif /* ecflow_attribute_RepeatAttr_HPP */ ecflow-5.19.0/libs/attribute/src/ecflow/attribute/VerifyAttr.hpp0000664000175000017500000000346415250005516025074 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #ifndef ecflow_attribute_VerifyAttr_HPP #define ecflow_attribute_VerifyAttr_HPP #include "ecflow/core/NState.hpp" // Class VerifyAttr: // This class is only used for testing/verification purposes. It allows us to // embed expected number of states, within the definition file and so // reduce the need for golden log files. // Use compiler , generated destructor, assignment, copy constructor class VerifyAttr { public: VerifyAttr(NState::State state, int expected, int actual = 0) : state_(state), expected_(expected), actual_(actual), state_change_no_(0) {} VerifyAttr() = default; bool operator==(const VerifyAttr& rhs) const; void print(std::string&) const; NState::State state() const { return state_; } int expected() const { return expected_; } int actual() const { return actual_; } void incrementActual(); void reset(); // The state_change_no is never reset. Must be incremented if it can affect equality unsigned int state_change_no() const { return state_change_no_; } std::string toString() const; std::string dump() const; private: NState::State state_{NState::UNKNOWN}; int expected_{0}; int actual_{0}; unsigned int state_change_no_{0}; // *not* persisted, only used on server side friend class cereal::access; template void serialize(Archive& ar); }; #endif /* ecflow_attribute_VerifyAttr_HPP */ ecflow-5.19.0/libs/attribute/src/ecflow/attribute/CronAttr.cpp0000664000175000017500000007466215250005516024534 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #include "ecflow/attribute/CronAttr.hpp" #include #include #include #include "ecflow/core/Calendar.hpp" #include "ecflow/core/Converter.hpp" #include "ecflow/core/Ecf.hpp" #include "ecflow/core/Message.hpp" #include "ecflow/core/Serialization.hpp" #include "ecflow/core/Str.hpp" using namespace ecf; // #define DEBUG_CRON_ATTR 1 // #define DEBUG_CRON_PARSING 1 // #define DEBUG_CRON_SIM 1 namespace ecf { CronAttr::CronAttr() = default; CronAttr::CronAttr(const std::string& str) { if (str.empty()) { throw std::runtime_error("CronAttr::CronAttr : empty string passed"); } std::vector tokens; ecf::algorithm::split_at(tokens, str); if (tokens.empty()) { throw std::runtime_error("CronAttr::CronAttr : incorrect time string ?"); } size_t index = 0; timeSeries_ = TimeSeries::create(index, tokens, false /*parse_state*/); } void CronAttr::addWeekDays(const std::vector& w) { weekDays_ = w; for (int day : weekDays_) { if (day < 0 || day > 6) { throw std::out_of_range( MESSAGE("Invalid range for day(" << day << ") of the week expected range is 0==Sun to 6==Sat")); } auto result = std::find(std::begin(last_week_days_of_month_), std::end(last_week_days_of_month_), day); if (result != std::end(last_week_days_of_month_)) { throw std::runtime_error( MESSAGE("Duplicate day(" << day << ") of the week also found in last week day of the month")); } } } void CronAttr::add_last_week_days_of_month(const std::vector& w) { last_week_days_of_month_ = w; for (int day : last_week_days_of_month_) { if (day < 0 || day > 6) { throw std::out_of_range( MESSAGE("Invalid range for day(" << day << ") of the week expected range is 0==Sun to 6==Sat")); } auto result = std::find(std::begin(weekDays_), std::end(weekDays_), day); if (result != std::end(weekDays_)) { throw std::runtime_error( MESSAGE("Duplicate last week day (" << day << ") of the month also found in week day")); } } } void CronAttr::addDaysOfMonth(const std::vector& d) { daysOfMonth_ = d; for (int day_of_month : daysOfMonth_) { if (day_of_month < 1 || day_of_month > 31) { throw std::out_of_range( MESSAGE("Invalid range for day of month(" << day_of_month << ") expected range is 1-31")); } } } void CronAttr::addMonths(const std::vector& m) { months_ = m; for (int month : months_) { if (month < 1 || month > 12) { throw std::out_of_range( MESSAGE("Invalid range for month(" << month << ") expected range is 1==Jan to 12==Dec")); } } } std::string CronAttr::name() const { std::string ret; write(ret); timeSeries_.write_state_for_gui(ret, free_); return ret; } std::string CronAttr::toString() const { std::string ret; write(ret); return ret; } void CronAttr::write(std::string& ret) const { ret += "cron "; if (!weekDays_.empty()) { ret += "-w "; for (size_t i = 0; i < weekDays_.size(); ++i) { ret += ecf::convert_to(weekDays_[i]); if (i != weekDays_.size() - 1) { ret += ","; } } if (last_week_days_of_month_.empty()) { ret += " "; } else { ret += ","; } } if (!last_week_days_of_month_.empty()) { if (weekDays_.empty()) { ret += "-w "; } for (size_t i = 0; i < last_week_days_of_month_.size(); ++i) { ret += ecf::convert_to(last_week_days_of_month_[i]); ret += 'L'; if (i != last_week_days_of_month_.size() - 1) { ret += ","; } } ret += " "; } if (!daysOfMonth_.empty()) { ret += "-d "; for (size_t i = 0; i < daysOfMonth_.size(); ++i) { ret += ecf::convert_to(daysOfMonth_[i]); if (i != daysOfMonth_.size() - 1) { ret += ","; } } if (!last_day_of_month_) { ret += " "; } } if (last_day_of_month_) { if (daysOfMonth_.empty()) { ret += "-d L "; } else { ret += ",L "; } } if (!months_.empty()) { ret += "-m "; for (size_t i = 0; i < months_.size(); ++i) { ret += ecf::convert_to(months_[i]); if (i != months_.size() - 1) { ret += ","; } } ret += " "; } timeSeries_.write(ret); // no new line added, up to caller } std::string CronAttr::dump() const { return MESSAGE(toString() << (free_ ? " (free)" : " (holding)")); } bool CronAttr::operator==(const CronAttr& rhs) const { if (free_ != rhs.free_) { return false; } if (last_day_of_month_ != rhs.last_day_of_month_) { return false; } if (weekDays_ != rhs.weekDays_) { return false; } if (last_week_days_of_month_ != rhs.last_week_days_of_month_) { return false; } if (daysOfMonth_ != rhs.daysOfMonth_) { return false; } if (months_ != rhs.months_) { return false; } return timeSeries_.operator==(rhs.timeSeries_); } bool CronAttr::structureEquals(const CronAttr& rhs) const { if (last_day_of_month_ != rhs.last_day_of_month_) { return false; } if (weekDays_ != rhs.weekDays_) { return false; } if (daysOfMonth_ != rhs.daysOfMonth_) { return false; } if (last_week_days_of_month_ != rhs.last_week_days_of_month_) { return false; } if (months_ != rhs.months_) { return false; } return timeSeries_.structureEquals(rhs.timeSeries_); } void CronAttr::calendarChanged(const ecf::Calendar& c) { // ensure this called first , since we need always update for relative duration ECFLOW-1648 // This assumes that calendarChanged will set TimeSeries::isValid = true, at day change if (timeSeries_.calendarChanged(c)) { state_change_no_ = Ecf::incr_state_change_no(); } if (free_) { return; } // Once a cron is free, it stays free until re-queue if (isFree(c)) { setFree(); } // A cron is always re-queueable, hence we use isFree to control when it can actually run. } void CronAttr::resetRelativeDuration() { if (timeSeries_.resetRelativeDuration()) { state_change_no_ = Ecf::incr_state_change_no(); } } void CronAttr::setFree() { free_ = true; state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO std::cout << "CronAttr::setFree()\n"; #endif } void CronAttr::clearFree() { free_ = false; state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO std::cout << "CronAttr::clearFree()\n"; #endif } void CronAttr::miss_next_time_slot() { // A cron attribute with a single time slot is repeated indefinitely hence always re-queues timeSeries_.miss_next_time_slot(); state_change_no_ = Ecf::incr_state_change_no(); } // ************************************************************************************** // FOR DEBUG THIS IS THE MAIN FUNCTION, AS THIS DECIDES WHETHER WE CONTINUE OR STOP // ************************************************************************************** bool CronAttr::checkForRequeue(const ecf::Calendar& calendar) const { // checkForRequeue is called when a task/family has reach the complete state // This simple checks if node should be put in re-queued state // A cron is always re-queueable // Hence, in order to use this it should be used in conjunction // with a parent node that has complete expression, (& maybe a dummy task) // This will allow its use with a parent repeat somewhere in the hierarchy return true; } bool CronAttr::validForHybrid(const ecf::Calendar& calendar) const { if (timeSeries_.hasIncrement()) { if (last_day_of_month_) { return false; // relies on day change } if (!months_.empty()) { return false; // relies on day change } if (!daysOfMonth_.empty()) { return false; // relies on day change } if (!weekDays_.empty()) { if (weekDays_.size() != 1) { return false; // relies on day change } return (weekDays_[0] == calendar.day_of_week()); } // cron 10:00 20:00 01:00 // valid for hybrid ? return true; } // A time series that does not have an increment runs indefinitely and hence relies on day change // cron 23:00 return false; } bool CronAttr::why(const ecf::Calendar& c, std::string& theReasonWhy) const { // This will logically AND all the times if (isFree(c)) { return false; } // We are here because: // 1/ Not on a valid time slot in the time series // *OR* // 2/ Logical *AND* of day of week, day of month, or month returned false theReasonWhy += "is cron dependent"; // Let us start by assuming that the time series was NOT free. // First check if week day, day of month, month, matches if (is_day_of_week_day_of_month_and_month_free(c)) { if (timeSeries_.is_valid()) { // This can apply to single and series boost::posix_time::time_duration calendar_time = timeSeries_.duration(c); if (calendar_time < timeSeries_.start().duration()) { timeSeries_.why(c, theReasonWhy); return true; } // calendar_time >= timeSeries_.start().duration() if (timeSeries_.hasIncrement()) { if (calendar_time < timeSeries_.finish().duration()) { timeSeries_.why(c, theReasonWhy); return true; } } } // calendar_time >= timeSeries_.start().duration() && calendar_time >= timeSeries_.finish().duration() // past the end of time slot, find next valid date } // take into account, user can use run/force complete to miss time slots bool do_a_requeue = timeSeries_.requeueable(c); if (do_a_requeue && weekDays_.empty() && daysOfMonth_.empty() && months_.empty()) { TimeSlot the_next_time_slot = timeSeries_.compute_next_time_slot(c); if (the_next_time_slot.isNULL()) { theReasonWhy += " ( *re-queue* to run at this time "; } else { theReasonWhy += " ( *re-queue* to run at "; theReasonWhy += the_next_time_slot.toString(); } theReasonWhy += ", otherwise next run is at "; } else { theReasonWhy += " ( next run is at "; } // Find the *NEXT* date that matches, and use the first time slot auto the_next_date = next_date(c); theReasonWhy += timeSeries_.start().toString(); theReasonWhy += " "; theReasonWhy += to_simple_string(the_next_date); std::ostringstream ss; TimeSlot currentTime = TimeSlot(timeSeries_.duration(c)); ss << ", current time "; if (timeSeries_.relative()) { ss << "+"; } ss << currentTime.toString() << " " << to_simple_string(c.date()) << " )"; theReasonWhy += ss.str(); return true; } void CronAttr::reset_only() { clearFree(); timeSeries_.reset_only(); } void CronAttr::reset(const ecf::Calendar& c) { clearFree(); timeSeries_.reset(c); } void CronAttr::requeue(const ecf::Calendar& c, bool reset_next_time_slot) { clearFree(); timeSeries_.requeue(c, reset_next_time_slot); } bool CronAttr::isFree(const ecf::Calendar& c) const { // The FreeDepCmd can be used to free the crons, if (free_) { return true; } if (!timeSeries_.isFree(c)) { return false; } // Ok time series is Free // ******************************************************************** // IMPORTANT: When there are multiple week days, days of month and months, // the attribute is *ONLY* free, if *ALL* are free, i.e. we need Boolean AND behaviour // ******************************************************************** return is_day_of_week_day_of_month_and_month_free(c); } bool CronAttr::is_day_of_week_day_of_month_and_month_free(const ecf::Calendar& c) const { #ifdef DEBUG_CRON_SIM cout << toString() << " cal : " << to_simple_string(c.date()) << " c.day_of_week:" << c.day_of_week() << " c.day_of_month:" << c.day_of_month(); cout.flush(); #endif bool the_week_day_matches = weekDays_.empty() && last_week_days_of_month_.empty(); // week day matches if no week days bool the_day_of_month_matches = daysOfMonth_.empty(); // day of month if no days of month bool the_month_matches = months_.empty(); // month matches if no months if (!weekDays_.empty()) { the_week_day_matches = week_day_matches(c.day_of_week()); } if (!the_week_day_matches && !last_week_days_of_month_.empty()) { the_week_day_matches = last_week_day_of_month_matches(c); } if (!daysOfMonth_.empty() || last_day_of_month_) { the_day_of_month_matches = day_of_month_matches(c.day_of_month(), c); } if (!months_.empty()) { the_month_matches = month_matches(c.month()); } // Remember we *AND* across -w, -d, -m or *OR* for each element in -w, -d,-m bool matches = false; if (daysOfMonth_.empty() && !last_day_of_month_ && months_.empty()) { // cron -w 0L 10:00 # run on the last sunday of each month matches = the_week_day_matches; // only week day, } else { matches = the_week_day_matches && the_day_of_month_matches && the_month_matches; } #ifdef DEBUG_CRON_SIM if (matches) { cout << " *MATCHES*"; } cout << "\n"; #endif return matches; } bool CronAttr::week_day_matches(int theDayOfWeek) const { for (int theWeekDay : weekDays_) { if (theDayOfWeek == theWeekDay) { return true; } } return false; } bool CronAttr::last_week_day_of_month_matches(const ecf::Calendar& c) const { int cal_day_of_week = c.day_of_week(); auto last_day_of_month = c.date().end_of_month(); auto diff_current_date_and_last_day_of_month = last_day_of_month - c.date(); for (int cron_last_week_day_of_month : last_week_days_of_month_) { if (cal_day_of_week == cron_last_week_day_of_month) { if (diff_current_date_and_last_day_of_month.days() < 7) { return true; } } } return false; } bool CronAttr::day_of_month_matches(int theDayOfMonth, const ecf::Calendar& c) const { for (int dayOfMonth : daysOfMonth_) { if (theDayOfMonth == dayOfMonth) { return true; } } if (last_day_of_month_) { return c.date() == c.date().end_of_month(); } return false; } bool CronAttr::month_matches(int theMonth) const { for (int month : months_) { if (theMonth == month) { return true; } } return false; } //------------------------------------------------------------------ bool CronAttr::checkInvariants(std::string& errormsg) const { return timeSeries_.checkInvariants(errormsg); } //-------------------------------------------------------------- boost::gregorian::date CronAttr::next_date(const ecf::Calendar& calendar) const { // Find the next date that matches, day of week, day of year, and month // that is greater than today's date. // This *ASSUMES* day of week, day of month, and month are evaluated with _AND_ together auto one_day = boost::gregorian::date_duration(1); auto future_date = calendar.date(); // today's date #ifdef DEBUG_CRON_SIM cout << "cron : " << toString() << "\n"; cout << "future_date start : " << to_simple_string(future_date) << "\n"; #endif future_date += one_day; // add one day, so its in the future while (true) { bool week_day_matches = weekDays_.empty(); // week day matches if no week days bool the_last_week_day_of_month_matches = last_week_days_of_month_.empty(); // matches if EMPTY bool day_of_month_matches = daysOfMonth_.empty(); // day of month if no days of month bool month_matches = months_.empty(); // month matches if no months if (daysOfMonth_.empty() && last_day_of_month_) { day_of_month_matches = false; } // deal with case where we have: cron -w 0,1 for (int weekDay : weekDays_) { if (future_date.day_of_week().as_number() == weekDay) { week_day_matches = true; break; } } // *IMPORTANT* the days in weekDays_ and last_week_days_of_month_ can *NOT* overlap. for (int weekDay : last_week_days_of_month_) { if (future_date.day_of_week().as_number() == weekDay) { boost::gregorian::date_duration diff = future_date - future_date.end_of_month(); if (diff.days() < 7) { the_last_week_day_of_month_matches = true; } break; } } // deal with case where we have: cron -d 14,15,16,L # L means last day of month if (!daysOfMonth_.empty() || last_day_of_month_) { for (int d : daysOfMonth_) { if (future_date.day() == d) { day_of_month_matches = true; break; } } if (last_day_of_month_ && future_date == future_date.end_of_month()) { day_of_month_matches = true; } } // deal with case where we have: cron -w 0,1 -d 14,15,16 -m 8, 9 for (int month : months_) { if (future_date.month() == month) { month_matches = true; break; } } // if it all matches, then return the future day // Remember we *AND* across -w, -d, -m or *OR* for each element in -w, -d,-m if ((week_day_matches || the_last_week_day_of_month_matches) && day_of_month_matches && month_matches) { break; // return future_date, replaced with break to keep HPUX compiler happy // otherwise it complains that return at the end of the function is // unreachable } future_date += one_day; #ifdef DEBUG_CRON_SIM cout << "future_date " << to_simple_string(future_date) << "\n"; #endif } return future_date; // should never happen, i.e. we can find future date that matches } //========================================================================================================= // code for parsing a cron: static bool isComment(const std::string& token) { if (token.find("#") == std::string::npos) { return false; } return true; } static bool isTimeSpec(const std::string& token) { if (token.find(ecf::string_constants::colon) == std::string::npos) { return false; } return true; } static bool isOption(const std::string& token) { if (token.find("-w") != std::string::npos) { return true; } if (token.find("-d") != std::string::npos) { return true; } if (token.find("-m") != std::string::npos) { return true; } return false; } static std::string nextToken(size_t& index, const std::vector& lineTokens) { assert(index < lineTokens.size()); index++; if (index < lineTokens.size()) { #ifdef DEBUG_CRON_PARSING cerr << "nextToken lineTokens[" << index << "] = " << lineTokens[index] << "\n"; #endif return lineTokens[index]; } #ifdef DEBUG_CRON_PARSING cerr << "nextToken empty \n"; #endif return std::string(); } std::string extract_list(size_t& index, const std::vector& lineTokens) { // cron -w 0,1L,2L,3 -d 1,12,14,L -m 5,6,7,8 10:00 20:00 01:00 assert(index < lineTokens.size()); // Collate the list of integers, these may have been separated by spaces // since we stop on option or time spec, the top level code should decrement index std::string theIntList; while (index < lineTokens.size() && (!isOption(lineTokens[index]) || !isTimeSpec(lineTokens[index]))) { std::string theNextToken = nextToken(index, lineTokens); if (theNextToken.empty()) { break; } if (isOption(theNextToken)) { break; } if (isTimeSpec(theNextToken)) { break; } theIntList += theNextToken; } #ifdef DEBUG_CRON_PARSING cerr << "theIntList = " << theIntList << "\n"; #endif return theIntList; } std::vector extract_month(size_t& index, const std::vector& lineTokens, const std::string& option) { // cron -w 0,1L,2L,3 -d 1,12,14,L -m 5,6,7,8 10:00 20:00 01:00 assert(index < lineTokens.size()); // Collate the list of integers, these may have been separated by spaces // since we stop on option or time spec, the top level code should decrement index std::string theIntList = extract_list(index, lineTokens); // should have 0,1,2,3 std::vector theIntVec; boost::char_separator sep(",", nullptr, boost::drop_empty_tokens); using tokenizer = boost::tokenizer>; tokenizer theTokenizer(theIntList, sep); for (tokenizer::iterator beg = theTokenizer.begin(); beg != theTokenizer.end(); ++beg) { std::string theIntToken = *beg; ecf::algorithm::trim(theIntToken); if (theIntToken.empty()) { continue; } try { auto theInt = ecf::convert_to(theIntToken); theIntVec.push_back(theInt); } catch (const ecf::bad_conversion&) { throw std::runtime_error(MESSAGE("Invalid cron option: " << option)); } } return theIntVec; } void extract_days_of_week(size_t& index, const std::vector& lineTokens, const std::string& option, std::vector& days_of_week, std::vector& last_week_days_of_month) { // cron -w 0,1L,2L,3 10:00 20:00 01:00 assert(index < lineTokens.size()); // Collate the list of integers, these may have been separated by spaces // since we stop on option or time spec, the top level code should decrement index std::string theIntList = extract_list(index, lineTokens); // should have 0,1,2,3,4L boost::char_separator sep(",", nullptr, boost::drop_empty_tokens); using tokenizer = boost::tokenizer>; tokenizer theTokenizer(theIntList, sep); for (tokenizer::iterator beg = theTokenizer.begin(); beg != theTokenizer.end(); ++beg) { std::string theIntToken = *beg; ecf::algorithm::trim(theIntToken); if (theIntToken.empty()) { continue; } try { if (theIntToken.size() == 2) { if (theIntToken[1] != 'L') { throw std::runtime_error(MESSAGE("Invalid cron option: " << option << " " << theIntToken)); } auto theInt = ecf::convert_to(theIntToken[0]); last_week_days_of_month.push_back(theInt); } else { auto theInt = ecf::convert_to(theIntToken); days_of_week.push_back(theInt); } } catch (const ecf::bad_conversion&) { throw std::runtime_error(MESSAGE("Invalid cron option: " << option)); } } } void extract_days_of_month(size_t& index, const std::vector& lineTokens, const std::string& option, std::vector& days_of_month, bool& last_day_of_month) { // cron -d 1,12,14,L 10:00 20:00 01:00 assert(index < lineTokens.size()); // Collate the list of integers, these may have been separated by spaces // since we stop on option or time spec, the top level code should decrement index std::string theIntList = extract_list(index, lineTokens); // should have 0,1,2,3,4L boost::char_separator sep(",", nullptr, boost::drop_empty_tokens); using tokenizer = boost::tokenizer>; tokenizer theTokenizer(theIntList, sep); for (tokenizer::iterator beg = theTokenizer.begin(); beg != theTokenizer.end(); ++beg) { std::string theIntToken = *beg; ecf::algorithm::trim(theIntToken); if (theIntToken.empty()) { continue; } try { if (theIntToken == "L") { last_day_of_month = true; } else { auto theInt = ecf::convert_to(theIntToken); days_of_month.push_back(theInt); } } catch (const ecf::bad_conversion&) { throw std::runtime_error(MESSAGE("Invalid cron option: " << option)); } } } void extractOption(CronAttr& cronAttr, size_t& index, const std::vector& lineTokens) { assert(index < lineTokens.size()); if (lineTokens[index] == "-w") { std::vector days_of_week; std::vector last_week_days_of_month; extract_days_of_week(index, lineTokens, "week days", days_of_week, last_week_days_of_month); cronAttr.addWeekDays(days_of_week); cronAttr.add_last_week_days_of_month(last_week_days_of_month); } else if (lineTokens[index] == "-d") { std::vector days_of_month; bool last_day_of_month = false; extract_days_of_month(index, lineTokens, "Days of the month", days_of_month, last_day_of_month); cronAttr.addDaysOfMonth(days_of_month); if (last_day_of_month) { cronAttr.add_last_day_of_month(); } } else if (lineTokens[index] == "-m") { cronAttr.addMonths(extract_month(index, lineTokens, "Months")); } else { throw std::runtime_error("extractOption: Invalid cron option :" + lineTokens[index]); } } void CronAttr::parse(CronAttr& cronAttr, const std::vector& lineTokens, size_t index, bool parse_state) { // cron 23:00 # run every day at 23:00 // cron 10:00 20:00 01:00 # run every hour between 10am and 8pm // cron -w 0,1 10:00 # run every sunday and monday at 10am // cron -d 10,11,12 12:00 # run 10th, 11th and 12th of each month at noon // cron -m 1,2,3 12:00 # run on Jan,Feb and March every day at noon. // cron -w 0 -m 5,6,7,8 10:00 20:00 01:00 # run every sunday, between May-Aug, every hour between 10am and 8pm // cron -w 0,1,2L -d 5,6,L 23:00 # run every sunday,monday, and last tuesday of the month, 5,6 on month, and *last* // day of month @11 pm // make *sure* a time spec is specified bool time_spec_specified = false; size_t line_tokens_size = lineTokens.size(); while (index < line_tokens_size) { const std::string& token = lineTokens[index]; #ifdef DEBUG_CRON_PARSING cerr << "CronAttr::doParse " << token << "\n"; #endif if (isOption(token)) { #ifdef DEBUG_CRON_PARSING cerr << "CronAttr::doParse isOption \n"; #endif extractOption(cronAttr, index, lineTokens); index--; // since we did a look ahead } else if (!time_spec_specified && isTimeSpec(token)) { #ifdef DEBUG_CRON_PARSING cerr << "CronAttr::doParse isTimeSpec \n"; #endif // index is passed by *reference*, and used skip over time series cronAttr.addTimeSeries(TimeSeries::create(index, lineTokens, parse_state)); time_spec_specified = true; if (parse_state) { // if index is on the comment, back track, so that we can add cron state( free) if (index < line_tokens_size && lineTokens[index] == "#") { index--; } } else { break; // need to read state after comment } } else if (isComment(token)) { // cron -m 1,2,3 12:00 # free if (parse_state && index + 1 < line_tokens_size) { if (lineTokens[index + 1] == "free") { cronAttr.setFree(); } } break; } index++; } if (!time_spec_specified) { throw std::runtime_error("Invalid cron, no time specified"); } #ifdef DEBUG_CRON_PARSING cronAttr.print(cerr); cerr << "\n"; #endif } CronAttr CronAttr::create(const std::string& cronString) { std::vector lineTokens; ecf::algorithm::split_at(lineTokens, cronString); CronAttr theCronAttr; if (lineTokens.empty()) { return theCronAttr; } // adjust the index size_t index = 0; if (lineTokens[0] == "cron") { index = 1; } parse(theCronAttr, lineTokens, index); return theCronAttr; } template void CronAttr::serialize(Archive& ar, std::uint32_t const version) { ar(CEREAL_NVP(timeSeries_)); CEREAL_OPTIONAL_NVP(ar, weekDays_, [this]() { return !weekDays_.empty(); }); // conditionally save CEREAL_OPTIONAL_NVP( ar, last_week_days_of_month_, [this]() { return !last_week_days_of_month_.empty(); }); // conditionally save CEREAL_OPTIONAL_NVP(ar, daysOfMonth_, [this]() { return !daysOfMonth_.empty(); }); // conditionally save CEREAL_OPTIONAL_NVP(ar, months_, [this]() { return !months_.empty(); }); // conditionally save CEREAL_OPTIONAL_NVP(ar, free_, [this]() { return free_; }); // conditionally save CEREAL_OPTIONAL_NVP(ar, last_day_of_month_, [this]() { return last_day_of_month_; }); // conditionally save CEREAL_OPTIONAL_NVP(ar, w_, [this]() { return w_ != 0; }); // conditionally save } CEREAL_TEMPLATE_SPECIALIZE_V(CronAttr); } // namespace ecf ecflow-5.19.0/libs/attribute/src/ecflow/attribute/GenericAttr.hpp0000664000175000017500000000337315250005516025203 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #ifndef ecflow_attribute_GenericAttr_HPP #define ecflow_attribute_GenericAttr_HPP #include #include namespace cereal { class access; } // Class GenericAttr: // Use compiler , generated destructor, assignment, copy constructor // GenericAttr does *not* have any changeable state class GenericAttr { public: GenericAttr(const std::string& name, const std::vector& values); explicit GenericAttr(const std::string& name); GenericAttr() = default; bool operator==(const GenericAttr& rhs) const; bool operator<(const GenericAttr& rhs) const { return name_ < rhs.name(); } bool empty() const { return name_.empty(); } const std::string& name() const { return name_; } const std::vector& values() const { return values_; } std::vector::const_iterator values_begin() const { return values_.begin(); } // for python std::vector::const_iterator values_end() const { return values_.end(); } // for python std::string to_string() const; // Added to support return by reference static const GenericAttr& EMPTY(); public: void write(std::string& ret) const; private: std::string name_; std::vector values_; friend class cereal::access; template void serialize(Archive& ar); }; #endif /* ecflow_attribute_GenericAttr_HPP */ ecflow-5.19.0/libs/attribute/src/ecflow/attribute/QueueAttr.cpp0000664000175000017500000002132215250005516024700 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #include "ecflow/attribute/QueueAttr.hpp" #include #include "ecflow/core/Ecf.hpp" #include "ecflow/core/Extract.hpp" #include "ecflow/core/Message.hpp" #include "ecflow/core/Serialization.hpp" #include "ecflow/core/Str.hpp" using namespace ecf; ///////////////////////////////////////////////////////////////////////////////////////////// const QueueAttr& QueueAttr::EMPTY() { static const QueueAttr queueAttr = QueueAttr(); return queueAttr; } QueueAttr& QueueAttr::EMPTY1() { static QueueAttr queueAttr = QueueAttr(); return queueAttr; } QueueAttr::QueueAttr(const std::string& name, const std::vector& theQueue) : theQueue_(theQueue), name_(name) { std::string msg; if (!ecf::algorithm::is_valid_name(name, msg)) { throw std::runtime_error("QueueAttr::QueueAttr: Invalid queue name : " + msg); } if (theQueue.empty()) { throw std::runtime_error("QueueAttr::QueueAttr: No queue items specified"); } for (size_t i = 0; i < theQueue.size(); i++) { state_vec_.push_back(NState::QUEUED); } } QueueAttr::~QueueAttr() = default; bool QueueAttr::operator==(const QueueAttr& rhs) const { if (name_ != rhs.name_) { return false; } if (theQueue_ != rhs.theQueue_) { return false; } if (state_vec_ != rhs.state_vec_) { return false; } if (currentIndex_ != rhs.currentIndex_) { return false; } return true; } std::string QueueAttr::value() const { if (currentIndex_ >= 0 && currentIndex_ < static_cast(theQueue_.size())) { return theQueue_[currentIndex_]; } return ""; } int QueueAttr::index_or_value() const { if (currentIndex_ >= 0 && currentIndex_ < static_cast(theQueue_.size())) { try { return ecf::convert_to(theQueue_[currentIndex_]); } catch (ecf::bad_conversion&) { // Ignore and return currentIndex_ } } return currentIndex_; } NState::State QueueAttr::state(const std::string& step) const { for (size_t i = 0; i < theQueue_.size(); i++) { if (step == theQueue_[i]) { if (i >= state_vec_.size()) { throw std::runtime_error("QueueAttr::state: index out of range"); } return state_vec_[i]; } } throw std::runtime_error("QueueAttr::state: could not find step " + step); } void QueueAttr::requeue() { currentIndex_ = 0; for (auto& i : state_vec_) { i = NState::QUEUED; } incr_state_change_no(); } std::string QueueAttr::active() { if (currentIndex_ >= 0 && currentIndex_ < static_cast(theQueue_.size())) { state_vec_[currentIndex_] = NState::ACTIVE; std::string ret = theQueue_[currentIndex_]; currentIndex_++; incr_state_change_no(); return ret; } return ""; } void QueueAttr::complete(const std::string& step) { for (size_t i = 0; i < theQueue_.size(); i++) { if (step == theQueue_[i]) { state_vec_[i] = NState::COMPLETE; incr_state_change_no(); return; } } throw std::runtime_error(MESSAGE("QueueAttr::complete: Could not find " << step << " in queue " << name_)); } void QueueAttr::aborted(const std::string& step) { for (size_t i = 0; i < theQueue_.size(); i++) { if (step == theQueue_[i]) { state_vec_[i] = NState::ABORTED; incr_state_change_no(); return; } } throw std::runtime_error(MESSAGE("QueueAttr::aborted: Could not find " << step << " in queue " << name_)); } std::string QueueAttr::no_of_aborted() const { int count = 0; for (auto i : state_vec_) { if (i == NState::ABORTED) { count++; } } if (count != 0) { return ecf::convert_to(count); } return std::string{}; } void QueueAttr::reset_index_to_first_queued_or_aborted() { for (size_t i = 0; i < state_vec_.size(); i++) { if (state_vec_[i] == NState::QUEUED || state_vec_[i] == NState::ABORTED) { currentIndex_ = i; incr_state_change_no(); break; } } } std::string QueueAttr::toString() const { std::string ret; write(ret); return ret; } void QueueAttr::write(std::string& ret) const { ret += "queue "; ret += name_; for (const auto& i : theQueue_) { ret += " "; ret += i; } } std::string QueueAttr::dump() const { std::ostringstream ss; ss << toString() << " # " << currentIndex_; for (auto i : state_vec_) { ss << " " << i; } return ss.str(); } void QueueAttr::incr_state_change_no() { state_change_no_ = Ecf::incr_state_change_no(); } void QueueAttr::parse(QueueAttr& queAttr, const std::string& line, std::vector& lineTokens, bool parse_state) { size_t line_tokens_size = lineTokens.size(); if (line_tokens_size < 3) { throw std::runtime_error(MESSAGE("QueueAttr::parse: expected at least 3 tokens, found " << line_tokens_size << " on line:" << line << "\n")); } // queue name "first" "second" "last" # current_index state state state // 0 1 2 3 4 5 6 queAttr.set_name(lineTokens[1]); std::vector theEnums; theEnums.reserve(line_tokens_size); for (size_t i = 2; i < line_tokens_size; i++) { std::string theEnum = lineTokens[i]; if (theEnum[0] == '#') { break; } // remove quotes, as they get added back when we persist ecf::algorithm::remove_single_quotes(theEnum); ecf::algorithm::remove_double_quotes(theEnum); theEnums.push_back(theEnum); } if (theEnums.empty()) { throw std::runtime_error("queue: has no values " + line); } int index = 0; std::vector state_vec; if (parse_state) { // queue VARIABLE a b c d # index active complete aborted queued for (size_t i = 3; i < line_tokens_size; i++) { if (lineTokens[i] == "#" && i + 1 < line_tokens_size) { i++; index = Extract::value(lineTokens[i], "QueueAttr::parse, could not extract index"); i++; for (; i < line_tokens_size; i++) { NState::State state = NState::toState(lineTokens[i]); state_vec.push_back(state); } break; } } } queAttr.set_queue(theEnums, index, state_vec); } void QueueAttr::set_queue(const std::vector& theQueue, int index, const std::vector& state_vec) { if (theQueue.empty()) { throw std::runtime_error("QueueAttr::set_queue: No queue items specified"); } if (!state_vec.empty()) { if (state_vec.size() != theQueue.size()) { throw std::runtime_error(MESSAGE("QueueAttr::set_state: for queue " << name_ << " size " << theQueue.size() << " does not match state size " << state_vec.size())); } state_vec_ = state_vec; } else { for (size_t i = 0; i < theQueue.size(); i++) { state_vec_.push_back(NState::QUEUED); } } currentIndex_ = index; theQueue_ = theQueue; } void QueueAttr::set_state_vec(const std::vector& state_vec) { state_vec_ = state_vec; if (theQueue_.size() != state_vec_.size()) { std::cout << "QueueAttr::set_state_vec: for queue " << name_ << " queue size " << theQueue_.size() << " not equal to state_vec size " << state_vec_.size() << "\n"; } } void QueueAttr::set_name(const std::string& name) { std::string msg; if (!ecf::algorithm::is_valid_name(name, msg)) { throw std::runtime_error("QueueAttr::set_name: Invalid queue name : " + msg); } name_ = name; } template void QueueAttr::serialize(Archive& ar, std::uint32_t const version) { ar(CEREAL_NVP(theQueue_), CEREAL_NVP(state_vec_), CEREAL_NVP(name_), CEREAL_NVP(currentIndex_)); } CEREAL_TEMPLATE_SPECIALIZE_V(QueueAttr); ecflow-5.19.0/libs/attribute/src/ecflow/attribute/DayAttr.cpp0000664000175000017500000003523515250005516024341 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #include "ecflow/attribute/DayAttr.hpp" #include #include #include #include "ecflow/core/Calendar.hpp" #include "ecflow/core/Ecf.hpp" #include "ecflow/core/Extract.hpp" #include "ecflow/core/Message.hpp" #include "ecflow/core/Serialization.hpp" #include "ecflow/core/cereal_boost_time.hpp" using namespace ecf; // #define DEBUG_DAYS 1 //=============================================================================== static const char* theDay(DayAttr::Day_t day) { switch (day) { case DayAttr::SUNDAY: return "sunday"; case DayAttr::MONDAY: return "monday"; case DayAttr::TUESDAY: return "tuesday"; case DayAttr::WEDNESDAY: return "wednesday"; case DayAttr::THURSDAY: return "thursday"; case DayAttr::FRIDAY: return "friday"; case DayAttr::SATURDAY: return "saturday"; default: assert(false); } return nullptr; } //=============================================================================== void DayAttr::calendarChanged(const ecf::Calendar& c, bool clear_at_midnight) { // See ECFLOW-337 // repeat .... // family start // family 0 // time 10:00 // day monday # if there was no c.dayChanged(), then after re-queue, & before midnight Monday is still // free. task dummy # hence we will end up also running on Tuesday at 10:00 // complete 1==1 // trigger 0==1 // // ECFLOW-1550 # If children of a family with day/date are still active/submitted/queued, then do not // clear at midnight. repeat .... # This is only applicable for NodeContainers, for task with day/date // always CLEAR at midnight // family f1 // day monday // time 23:00 // task t1 # Task t1 took longer than 1 hour // task t2 # allow task t2 to continue to the next day, i.e. clear_at_midnight = False // trigger t1 == complete #ifdef DEBUG_DAYS cout << " DayAttr::calendarChanged " << dump() << " clear_at_midnight " << clear_at_midnight << " calendar:" << c.suite_time_str() << "\n"; #endif if (expired_) { // ********* TREAT this Day Attribute as deleted ECFLOW-128 ********** return; } if (c.dayChanged()) { if (clear_at_midnight) { clearFree(); #ifdef DEBUG_DAYS cout << " DayAttr::calendarChanged MIDNIGHT " << dump() << " calendar:" << c.suite_time_str() << "\n"; #endif } } if (free_) { return; } if (is_free(c)) { setFree(); #ifdef DEBUG_DAYS cout << " DayAttr::calendarChanged SET FREE " << dump() << " calendar:" << c.suite_time_str() << "\n"; #endif } } void DayAttr::reset() { expired_ = false; free_ = false; date_ = boost::gregorian::date(); state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_DAYS cout << " DayAttr::reset :" << dump() << "\n"; #endif } void DayAttr::handle_migration(const ecf::Calendar& c) { // temp once in Bologna, and when ecflow4 no longer used. if (date_.is_special()) { if (!c.is_special()) { date_ = matching_date(c); } } } void DayAttr::reset(const ecf::Calendar& c) { reset(); date_ = matching_date(c); #ifdef DEBUG_DAYS cout << " DayAttr::reset(calendar) :" << dump() << " calendar:" << c.suite_time_str() << "\n"; #endif } void DayAttr::requeue_time() { #ifdef DEBUG_DAYS cout << " DayAttr::requeue " << dump() << "\n"; #endif if (expired_) { // ********* TREAT this Day Attribute as deleted ********** #ifdef DEBUG_DAYS cout << " DayAttr::requeue_time " << dump() << " EXPIRED(do nothing) returning\n"; #endif return; } free_ = false; state_change_no_ = Ecf::incr_state_change_no(); } void DayAttr::requeue_manual(const ecf::Calendar& c) { reset(); date_ = matching_date(c); #ifdef DEBUG_DAYS cout << " DayAttr::requeue_manual(calendar) " << dump() << " calendar:" << c.suite_time_str() << "\n"; #endif } void DayAttr::requeue_repeat_increment(const ecf::Calendar& c) { reset(); date_ = next_matching_date(c); #ifdef DEBUG_DAYS cout << " DayAttr::requeue_repeat_increment(calendar) " << dump() << " calendar:" << c.suite_time_str() << "\n"; #endif } bool DayAttr::isFree(const ecf::Calendar& c) const { if (expired_) { // ********* TREAT this Day Attribute as deleted ********** #ifdef DEBUG_DAYS cout << " DayAttr::isFree " << dump() << " calendar:" << c.suite_time_str() << " HOLDING due to EXPIRED flag\n"; #endif return false; } // The FreeDepCmd can be used to free the dates, if (free_) { #ifdef DEBUG_DAYS cout << " DayAttr::isFree " << dump() << " calendar:" << c.suite_time_str() << " FREE free_ = TRUE\n"; #endif return true; } bool res = is_free(c); #ifdef DEBUG_DAYS if (res) { cout << " DayAttr::isFree " << dump() << " calendar:" << c.suite_time_str() << " date is FREE\n"; } else { cout << " DayAttr::isFree " << dump() << " calendar:" << c.suite_time_str() << " date is HOLDING\n"; } #endif return res; } bool DayAttr::is_free(const ecf::Calendar& c) const { return (c.date() == date_); } void DayAttr::setFree() { free_ = true; state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_DAYS cout << " DayAttr::setFree() " << dump() << "\n"; #endif } void DayAttr::clearFree() { free_ = false; state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_DAYS cout << " DayAttr::clearFree() " << dump() << "\n"; #endif } void DayAttr::set_expired() { expired_ = true; state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_DAYS cout << " DayAttr::set_expired() " << dump() << "\n"; #endif } void DayAttr::clear_expired() { expired_ = false; state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_DAYS cout << " DayAttr::clear_expired() " << dump() << "\n"; #endif } void DayAttr::check_for_expiration(const ecf::Calendar& c) { #ifdef DEBUG_DAYS cout << "DayAttr::check_for_expiration " << dump() << " calendar:" << c.suite_time_str() << "\n"; #endif // This function is called when a Node has COMPLETED. Avoid running again on same day date <= calendar, expire the // date Note: time attribute, they get handled before. i.e. allowing multiple re-queues on the same date This // function *MUST* be called before checkForRequeue. if (date_.is_special()) { // migration 4->5, or 5->5 from checkpoint date_ = matching_date(c); } if (day_ == c.day_of_week()) { set_expired(); return; } if (date_ <= c.date()) { set_expired(); } } bool DayAttr::checkForRequeue(const ecf::Calendar& c) const { if (expired_) { #ifdef DEBUG_DAYS cout << " DayAttr::check_for_requeue ALREADY EXPIRED " << dump() << " calendar:" << c.suite_time_str() << " HOLDING <<<<<<<<<<<<\n"; #endif return false; } // if calendar is hybrid, we cannot requeue if (c.hybrid()) { return false; } // checkForRequeue is called when we are deciding whether to re-queue the node // Hence we *MUST* have completed. Also, all cron, time, and today attributes have returned false. // *IF* this date is in the future, we should re-queue assert(!date_.is_special()); bool future_date = (date_ > c.date()); #ifdef DEBUG_DAYS cout << " DayAttr::check_for_requeue " << dump() << " calendar:" << c.suite_time_str() << " returning " << future_day << " ************\n"; #endif return future_date; } bool DayAttr::validForHybrid(const ecf::Calendar& calendar) const { return isFree(calendar); } bool DayAttr::why(const ecf::Calendar& c, std::string& theReasonWhy) const { if (isFree(c)) { return false; } theReasonWhy += " is day dependent ( next run on "; theReasonWhy += theDay(day_); theReasonWhy += " "; if (date_.is_special()) { theReasonWhy += to_simple_string(next_matching_date(c)); } else { theReasonWhy += to_simple_string(date_); } theReasonWhy += " the current day is "; theReasonWhy += theDay(static_cast(c.day_of_week())); theReasonWhy += " )"; return true; } std::string DayAttr::name() const { // for display/GUI only std::string os; write(os); bool added_hash = false; if (expired_) { os += " # expired"; added_hash = true; } else { if (free_) { os += " # free"; added_hash = true; } } if (added_hash) { os += " "; os += to_simple_string(date_); } else { os += " # "; os += to_simple_string(date_); } return os; } std::string DayAttr::toString() const { std::string ret; write(ret); return ret; } void DayAttr::write(std::string& ret) const { ret += "day "; ret += theDay(day_); } std::string DayAttr::dump() const { return MESSAGE(toString() << (free_ ? " (free)" : "") << (expired_ ? " (expired)" : "") << " " << as_simple_string()); } std::string DayAttr::as_simple_string() const { return boost::gregorian::to_simple_string(date_); } bool DayAttr::operator==(const DayAttr& rhs) const { if (free_ != rhs.free_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "DayAttr::operator== free_ != rhs.free_ (free_:" << free_ << " rhs.free_:" << rhs.free_ << ") " << dump() << "\n"; } #endif return false; } if (expired_ != rhs.expired_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "DayAttr::operator== expired_ != rhs.expired_ (expired_:" << expired_ << " rhs.expired_:" << rhs.expired_ << ") " << dump() << "\n"; } #endif return false; } if (date_ != rhs.date_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "DayAttr::operator== date_ != rhs.date_ (date_:" << date_ << " rhs.date_:" << rhs.date_ << ") " << dump() << "\n"; } #endif return false; } return structureEquals(rhs); } bool DayAttr::structureEquals(const DayAttr& rhs) const { return (day_ == rhs.day_); } DayAttr DayAttr::create(const std::string& dayStr) { return DayAttr(getDay(dayStr)); } DayAttr DayAttr::create(const std::vector& lineTokens, bool read_state) { if (lineTokens.size() < 2) { throw std::runtime_error("DayAttr::create date tokens to short :"); } // for(size_t i =0; i < lineTokens.size() ; i++) { // cout << "lineTokens[" << i << "] = '" << lineTokens[i] << "'\n"; // } // day monday # free expired date:**** // day monday # expired DayAttr day = DayAttr::create(lineTokens[1]); // state if (read_state) { day.read_state(lineTokens); } return day; } void DayAttr::read_state(const std::vector& lineTokens) { std::string date; for (size_t i = 3; i < lineTokens.size(); i++) { if (lineTokens[i] == "free") { free_ = true; } else if (lineTokens[i] == "expired") { expired_ = true; } else if (lineTokens[i].find("date:") != std::string::npos) { if (!Extract::split_get_second(lineTokens[i], date)) { throw std::runtime_error("DayAttr::read_state failed: (date:)"); } // when a date_ is special date = not-a-date-time\n" if (date.find("not") == std::string::npos) { date_ = boost::gregorian::from_simple_string(date); } } } } DayAttr::Day_t DayAttr::getDay(const std::string& day) { if (day == "monday") { return DayAttr::MONDAY; } if (day == "tuesday") { return DayAttr::TUESDAY; } if (day == "wednesday") { return DayAttr::WEDNESDAY; } if (day == "thursday") { return DayAttr::THURSDAY; } if (day == "friday") { return DayAttr::FRIDAY; } if (day == "saturday") { return DayAttr::SATURDAY; } if (day == "sunday") { return DayAttr::SUNDAY; } throw std::runtime_error(MESSAGE( "Invalid day(" << day << ") specification expected one of [monday,tuesday,wednesday,thursday,friday,saturday,sunday]: ")); } std::vector DayAttr::allDays() { std::vector vec; vec.reserve(7); vec.emplace_back("monday"); vec.emplace_back("tuesday"); vec.emplace_back("wednesday"); vec.emplace_back("thursday"); vec.emplace_back("friday"); vec.emplace_back("saturday"); vec.emplace_back("sunday"); return vec; } boost::gregorian::date DayAttr::matching_date(const ecf::Calendar& c) const { auto one_day = boost::gregorian::date_duration(1); auto matching_date = c.date(); // today's date for (int i = 0; i < 7; i++) { if (matching_date.day_of_week().as_number() == day_) { return matching_date; } matching_date += one_day; } assert(false); // no matching day ?s return c.date(); } boost::gregorian::date DayAttr::next_matching_date(const ecf::Calendar& c) const { auto one_day = boost::gregorian::date_duration(1); auto the_next_matching_date = c.date(); // today's date for (int i = 0; i < 7; i++) { the_next_matching_date += one_day; if (the_next_matching_date.day_of_week().as_number() == day_) { return the_next_matching_date; } } assert(false); return c.date(); } template void DayAttr::serialize(Archive& ar) { ar(CEREAL_NVP(day_)); CEREAL_OPTIONAL_NVP(ar, free_, [this]() { return free_; }); // conditionally save CEREAL_OPTIONAL_NVP(ar, expired_, [this]() { return expired_; }); // conditionally save, new to ecflow 5.4.0, should be ignored by old clients. see tests CEREAL_OPTIONAL_NVP(ar, date_, [this]() { return !date_.is_special(); }); // conditionally save, new to ecflow 5.5.0, should be ignored by old clients. see tests } CEREAL_TEMPLATE_SPECIALIZE(DayAttr); ecflow-5.19.0/libs/attribute/src/ecflow/attribute/ClockAttr.hpp0000664000175000017500000000630615250005516024661 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #ifndef ecflow_attribute_ClockAttr_HPP #define ecflow_attribute_ClockAttr_HPP #include #include "ecflow/core/Chrono.hpp" namespace cereal { class access; } namespace ecf { class Calendar; } // namespace ecf /// The clock attribute is defined on the suite ONLY /// Use default copy constructor and assignment operator, destructor /// The clock attribute is used to initialise the calendar object /// ********************************************************************** /// In the OLD sms the date is actually used as a gain factor(well at least /// according to the user/reference manual), in the ec-flow /// a date, is a date. i.e. it allows us to start a suite in the past. /// /// The Constructor will update the State change number, since it can be added /// by the AlterCmd. in the Client Context, state change number is not incremented /// ************************************************************************ /// class ClockAttr { public: /// The following constructor is used for test only. It allows us to /// create a clock attribute initialised with given date and time explicit ClockAttr(const boost::posix_time::ptime&, bool hybrid = false, bool positiveGain = true); ClockAttr(int day, int month, int year, bool hybrid = false); explicit ClockAttr(bool hybrid = false); bool operator==(const ClockAttr& rhs) const; void date(int day, int month, int year); void set_gain(int hour, int min, bool positiveGain = true); void set_gain_in_seconds(long theGain, bool positiveGain = true); void hybrid(bool f); void set_end_clock() { end_clock_ = true; } // clear local attributes so that, when the suite is requeued, the computer clock is synced void sync(); void init_calendar(ecf::Calendar&); void begin_calendar(ecf::Calendar&) const; // The state_change_no is never reset. Must be incremented if it can affect equality unsigned int state_change_no() const { return state_change_no_; } // access int day() const { return day_; } int month() const { return month_; } int year() const { return year_; } long gain() const { return gain_; } bool positive_gain() const { return positiveGain_; } bool hybrid() const { return hybrid_; } std::string toString() const; boost::posix_time::ptime ptime() const; public: void write(std::string& os) const; private: long gain_{0}; // in seconds int day_{0}; int month_{0}; int year_{0}; unsigned int state_change_no_{0}; // *not* persisted, only used on server side bool hybrid_{false}; bool positiveGain_{false}; bool end_clock_{false}; // *NOT* persisted, used for end clock, simulator only friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const version); }; #endif /* ecflow_attribute_ClockAttr_HPP */ ecflow-5.19.0/libs/attribute/src/ecflow/attribute/AutoArchiveAttr.cpp0000664000175000017500000000737515250005516026042 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #include "ecflow/attribute/AutoArchiveAttr.hpp" #include "ecflow/core/Calendar.hpp" #include "ecflow/core/Chrono.hpp" #include "ecflow/core/Converter.hpp" #include "ecflow/core/Log.hpp" #include "ecflow/core/Serialization.hpp" namespace ecf { std::string AutoArchiveAttr::toString() const { std::string ret; write(ret); return ret; } void AutoArchiveAttr::write(std::string& ret) const { ret += "autoarchive "; if (days_) { ret += ecf::convert_to(time_.hour() / 24); if (idle_) { ret += " -i"; } return; } if (relative_) { ret += "+"; } time_.print(ret); if (idle_) { ret += " -i"; } } bool AutoArchiveAttr::operator==(const AutoArchiveAttr& rhs) const { if (relative_ != rhs.relative_) { return false; } if (days_ != rhs.days_) { return false; } if (idle_ != rhs.idle_) { return false; } return time_.operator==(rhs.time_); } bool AutoArchiveAttr::isFree( const ecf::Calendar& calendar, const std::pair& last_state_and_change_duration) const { // suiteTime() // suiteDurationAtComplete autoarchive time calendar duration // | | | // V V V // ----------------------------------------------------------------------------------> time // ^ ^ // |--------elapsed time---------------------------------------| // // bool is_valid_state = false; if (last_state_and_change_duration.first == NState::COMPLETE) { is_valid_state = true; } if (idle_) { if (last_state_and_change_duration.first == NState::QUEUED) { is_valid_state = true; } if (last_state_and_change_duration.first == NState::ABORTED) { is_valid_state = true; } } if (!is_valid_state) { return false; } if (relative_) { boost::posix_time::time_duration time_elapsed = calendar.duration() - last_state_and_change_duration.second; LOG_ASSERT(!time_elapsed.is_negative(), "should always be positive or some things gone wrong"); if (time_elapsed >= time_.duration()) { return true; } } else { // real time // #ifdef DEBUG // cout << "real time time_(" << to_simple_string(time_.duration()) // << ") calendar.suiteTime().time_of_day(" << to_simple_string(calendar.suiteTime().time_of_day()) << // ")\n"; // #endif if (calendar.suiteTime().time_of_day() >= time_.duration()) { return true; } } return false; } template void AutoArchiveAttr::serialize(Archive& ar, std::uint32_t const version) { ar(CEREAL_NVP(time_)); CEREAL_OPTIONAL_NVP(ar, relative_, [this]() { return !relative_; }); // conditionally save CEREAL_OPTIONAL_NVP(ar, days_, [this]() { return days_; }); // conditionally save CEREAL_OPTIONAL_NVP(ar, idle_, [this]() { return idle_; }); // conditionally save } CEREAL_TEMPLATE_SPECIALIZE_V(AutoArchiveAttr); } // namespace ecf ecflow-5.19.0/libs/attribute/src/ecflow/attribute/AutoCancelAttr.cpp0000664000175000017500000000605715250005516025642 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #include "ecflow/attribute/AutoCancelAttr.hpp" #include "ecflow/core/Calendar.hpp" #include "ecflow/core/Chrono.hpp" #include "ecflow/core/Converter.hpp" #include "ecflow/core/Log.hpp" #include "ecflow/core/Serialization.hpp" namespace ecf { std::string AutoCancelAttr::toString() const { std::string ret; write(ret); return ret; } void AutoCancelAttr::write(std::string& ret) const { ret += "autocancel "; if (days_) { ret += ecf::convert_to(time_.hour() / 24); return; } if (relative_) { ret += "+"; } time_.print(ret); } bool AutoCancelAttr::operator==(const AutoCancelAttr& rhs) const { if (relative_ != rhs.relative_) { return false; } if (days_ != rhs.days_) { return false; } return time_.operator==(rhs.time_); } bool AutoCancelAttr::isFree(const ecf::Calendar& calendar, const boost::posix_time::time_duration& suiteDurationAtComplete) const { // suiteTime() // suiteDurationAtComplete autocancel time calendar duration // | | | // V V V // ----------------------------------------------------------------------------------> time // ^ ^ // |--------elapsed time---------------------------------------| // // if (relative_) { boost::posix_time::time_duration timeElapsedAfterComplete = calendar.duration() - suiteDurationAtComplete; LOG_ASSERT(!timeElapsedAfterComplete.is_negative(), "should always be positive or some things gone wrong"); if (timeElapsedAfterComplete >= time_.duration()) { return true; } } else { // real time // #ifdef DEBUG // cout << "real time time_(" << to_simple_string(time_.duration()) // << ") calendar.suiteTime().time_of_day(" << // to_simple_string(calendar.suiteTime().time_of_day()) << ")\n"; #endif if (calendar.suiteTime().time_of_day() >= time_.duration()) { return true; } } return false; } template void AutoCancelAttr::serialize(Archive& ar, std::uint32_t const /*version*/) { ar(CEREAL_NVP(time_)); CEREAL_OPTIONAL_NVP(ar, relative_, [this]() { return !relative_; }); // conditionally save CEREAL_OPTIONAL_NVP(ar, days_, [this]() { return days_; }); // conditionally save } CEREAL_TEMPLATE_SPECIALIZE_V(AutoCancelAttr); } // namespace ecf ecflow-5.19.0/libs/attribute/src/ecflow/attribute/TodayAttr.hpp0000664000175000017500000002006515250005516024704 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #ifndef ecflow_attribute_TodayAttr_HPP #define ecflow_attribute_TodayAttr_HPP /// /// \brief The Today attribute is heavily tied to the `begin` command /// /// Real Clock: /// 1/ Suite Begin time > Today time /// If the suite 'begin' time is past the time given for today /// the node is free to run. /// 2/ Suite Begin time < Today Time /// The node will 'hold' until current time > today time /// 3/ Suite time, has passed midnight(next day) /// then today command will permanently hold the node /// 4/ Under Real/hybrid clocks today will hold node after /// current is past last today time. /// /// Take the following example when we have a single time slot: /// today 10:00 /// isFree:-----free----- /// begin: /// V /// checkForRequeue:rrrrrrrrrrrrrrrrhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh /// isFree:hhhhhhhhhhhhhhhh|fffffffffffffffffffffffffffffffffffffff *once* free we stay free /// (single slot *only*) begin: /// V /// Today ======================0=====================0================= /// 10:00 Midnight /// /// Difference between time and today. If begin is started after the time slot /// then the node is free to re-run /// /// When we have a today time series: /// today 10:00 20:00 01:00 /// /// *** If the beginning time is past 10:00 as in the case above then the /// *** node should is free to run once. However, for a range its different /// *** if suite begin time is past 20:00 then the node is held. /// /// At 10am the Node is free, when node completes, it is re-queued /// At 11am the Node is free, when node completes, it is re-queued /// .... /// At 20pm the Node is free, when node completes, it is *NOT* re-queued. /// /// isFree is called when a node is queued. if it returns true, Task can be submitted /// checkForRequeue: is called when a node has completed, and need to determine if it should run again. /// These are different/orthogonal concerns. /// There is a *separate* issue of whether nodes should be queued when a node is *manually* /// a/ Set complete /// b/ Runs and then completes /// /// For a *single* time slot we cannot requeue. /// ****However we could have a set of time slots ***** /// /// isFree:ffffffffffffffff /// Begin: /// V /// checkForRequeue:hhhhhhhhhhhhhhhhhrrrrrrrrhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh /// isFree:hhhhhhhhhhhhhhhh|fffffffffffffffffffffffffffffffffffffff /// begin : /// V /// Today ======================0========0===========0==================== /// 10:00 11:00 Midnight /// isFree:hhhhhhhhhhhhhhhhhh /// V /// CheckForRequeue:hhhhhhhrrrrrrrrrrrrrrrrrrrrrrrrrhhhhhhhhhhhhhhhhhhhhhhhhhhhhh /// isFree:hhhhhhhFhhhhFhhhhFhhhhFhhhhFhhhhFFFFFFFFFFFFFFFFFFFFFFFFFFFFF /// V | | | | | | | /// Today ================o====|====|====|====|====0========0==================== /// 10:00 1 2 3 4 15:00 Midnight /// /// If the job starts at 10:00 but takes more than 1 hour, then it will miss the 11:00 slot /// and will have to start at 12:00 /// #include "ecflow/core/TimeSeries.hpp" class DateAttr; // Used in Why class DayAttr; // Used in Why namespace ecf { class Calendar; } // namespace ecf namespace ecf { // Use compiler , destructor, assignment, copy constructor class TodayAttr { public: explicit TodayAttr(const std::string&); TodayAttr() = default; TodayAttr(int hour, int minute, bool relative = false) : ts_(hour, minute, relative) {} explicit TodayAttr(const TimeSlot& t, bool relative = false) : ts_(t, relative) {} explicit TodayAttr(const TimeSeries& ts) : ts_(ts) {} TodayAttr(const TimeSlot& start, const TimeSlot& finish, const TimeSlot& incr, bool relative = false) : ts_(start, finish, incr, relative) {} bool operator==(const TodayAttr& rhs) const; bool operator<(const TodayAttr& rhs) const { return ts_ < rhs.ts_; } bool structureEquals(const TodayAttr& rhs) const; /// This can set attribute as free, once free its stays free, until re-queue/reset void calendarChanged(const ecf::Calendar& c); void resetRelativeDuration(); void reset_only() { clearFree(); ts_.reset_only(); } void reset(const ecf::Calendar& c) { clearFree(); ts_.reset(c); } // updates state_change_no_ void requeue(const ecf::Calendar& c, bool reset_next_time_slot = true) { clearFree(); ts_.requeue(c, reset_next_time_slot); } // updates state_change_no_ void miss_next_time_slot(); // updates state_change_no_ void setFree(); // ensures that isFree() always returns true, updates state_change_no_ bool isSetFree() const { return free_; } // This is used when we have a *single* today attribute // single-slot is free, if calendar time >= today_time // (range) is free, if calendar time == (one of the time ranges) bool isFree(const ecf::Calendar&) const; // This is used when we have a *multiple* today attribute // (single | range) is free, if calendar time == (one of the time ranges) // if timer *expired* returns false // task t1 // today 09:00 // today 10:00 // If current times is 11:00, then we will return false. // since both 09:00 and 10:00 have expired // Multiple single today, should behave like a today with a range. bool isFreeMultipleContext(const ecf::Calendar& c) const { return ts_.isFree(c); } bool checkForRequeue(const ecf::Calendar& c, const TimeSlot& the_min, const TimeSlot& the_max, bool cmd_context = false) const { return ts_.checkForRequeue(c, the_min, the_max, cmd_context); } void min_max_time_slots(TimeSlot& the_min, TimeSlot& the_max) const { ts_.min_max_time_slots(the_min, the_max); } bool why(const ecf::Calendar&, const std::vector&, const std::vector&, std::string& theReasonWhy) const; bool checkInvariants(std::string& errormsg) const { return ts_.checkInvariants(errormsg); } // The state_change_no is never reset. Must be incremented if it can affect equality // Note: changes in state of ts_, i.e. affect the equality operator (used in test) // must be captured. i.e. things like relative duration & next_time_slot are // reported by the Why command, & hence need to be synced. unsigned int state_change_no() const { return state_change_no_; } std::string name() const; // for display/GUI std::string toString() const; void write(std::string&) const; // access const TimeSeries& time_series() const { return ts_; } private: void clearFree(); // resets the free flag, updates state_change_no_ bool is_free(const ecf::Calendar&) const; // ignores free_ private: TimeSeries ts_; unsigned int state_change_no_{0}; // *not* persisted, only used on server side bool free_{false}; // persisted for use by why() on client side && for state changes friend class cereal::access; template void serialize(Archive& ar); }; } // namespace ecf #endif /* ecflow_attribute_TodayAttr_HPP */ ecflow-5.19.0/libs/attribute/src/ecflow/attribute/DateAttr.hpp0000664000175000017500000000676715250005516024516 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #ifndef ecflow_attribute_DateAttr_HPP #define ecflow_attribute_DateAttr_HPP /// /// \note calendarChanged() once a Date is free, it stays free. It relies on parent cron/repeat to re-queue /// #include #include #include "ecflow/core/Chrono.hpp" namespace cereal { class access; } namespace ecf { class Calendar; } // namespace ecf // Use default copy constructor, assignment operator, destructor // Value of 0 for day,month,year means *, i.e. means any value class DateAttr { public: DateAttr(int day, int month, int year); // will throw std::out_of_range for if invalid date explicit DateAttr(const std::string&); // will throw std::runtime_error for if invalid date DateAttr() = default; // for serialisation explicit DateAttr(const boost::gregorian::date& date) : day_(date.day()), month_(date.month()), year_(date.year()) {} // for test bool operator==(const DateAttr& rhs) const; bool operator<(const DateAttr& rhs) const; bool structureEquals(const DateAttr& rhs) const; void reset(); void requeue(); void setFree(); // ensures that isFree() always returns true void clearFree(); // resets the free flag bool isSetFree() const { return free_; } void calendarChanged(const ecf::Calendar& c, bool clear_at_midnight = true); // can set attribute free bool isFree(const ecf::Calendar&) const; bool checkForRequeue(const ecf::Calendar&) const; bool validForHybrid(const ecf::Calendar&) const; bool why(const ecf::Calendar&, std::string& theReasonWhy) const; // The state_change_no is never reset. Must be incremented if it can affect equality unsigned int state_change_no() const { return state_change_no_; } std::string name() const; // for display/gui only std::string toString() const; std::string dump() const; /// Check the date, will throw std::out_of_range or derivative if invalid static void checkDate(int day, int month, int year, bool allow_wild_cards); /// Extract the date, if return integer is zero, date was of the *, i.e. any day,month,year /// will throw std::runtime_error for parse errors /// expect: /// 15.11.2009 /// 15.*.* /// *.1.* static void getDate(const std::string& date, int& day, int& month, int& year); static DateAttr create(const std::string& dateString); static DateAttr create(const std::vector& lineTokens, bool read_state); boost::gregorian::date next_matching_date(const ecf::Calendar&) const; // access int day() const { return day_; } int month() const { return month_; } int year() const { return year_; } bool is_free(const ecf::Calendar&) const; // ignores free_ public: void write(std::string&) const; private: int day_{0}; int month_{0}; int year_{0}; unsigned int state_change_no_{0}; // *not* persisted, only used on server side bool free_{false}; // persisted for use by why() on client side friend class cereal::access; template void serialize(Archive& ar); }; #endif /* ecflow_attribute_DateAttr_HPP */ ecflow-5.19.0/libs/attribute/src/ecflow/attribute/RepeatAttr.cpp0000664000175000017500000017776415250005516025062 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #include "ecflow/attribute/RepeatAttr.hpp" #include #include #include #include #include "ecflow/core/Calendar.hpp" #include "ecflow/core/Converter.hpp" #include "ecflow/core/Ecf.hpp" #include "ecflow/core/Log.hpp" #include "ecflow/core/Serialization.hpp" #include "ecflow/core/Str.hpp" #include "ecflow/node/formatter/DefsWriter.hpp" namespace { template std::string as_n_digits(T value) { static_assert(N > 0, "N must be greater than zero"); static_assert(std::is_integral::value, "T must be an integral type"); std::ostringstream ss; ss << std::setfill('0') << std::setw(N) << value; return ss.str(); } template std::string as_2_digits(T value) { return as_n_digits<2>(value); } template std::string as_4_digits(T value) { return as_n_digits<4>(value); } } // namespace using namespace ecf; const Repeat& Repeat::EMPTY() { static const Repeat REPEAT = Repeat(); return REPEAT; } //========================================================================= Repeat::Repeat() = default; Repeat::Repeat(const RepeatDate& r) : type_(std::make_unique(r)) { } Repeat::Repeat(const RepeatDateTime& r) : type_(std::make_unique(r)) { } Repeat::Repeat(const RepeatDateList& r) : type_(std::make_unique(r)) { } Repeat::Repeat(const RepeatDateTimeList& r) : type_(std::make_unique(r)) { } Repeat::Repeat(const RepeatInteger& r) : type_(std::make_unique(r)) { } Repeat::Repeat(const RepeatEnumerated& r) : type_(std::make_unique(r)) { } Repeat::Repeat(const RepeatString& r) : type_(std::make_unique(r)) { } Repeat::Repeat(const RepeatDay& r) : type_(std::make_unique(r)) { } Repeat::~Repeat() = default; Repeat::Repeat(const Repeat& rhs) { if (rhs.type_) { type_.reset(rhs.type_->clone()); } } Repeat::Repeat(Repeat&& rhs) : type_(std::move(rhs.type_)) { } Repeat& Repeat::operator=(Repeat rhs) { using std::swap; swap(this->type_, rhs.type_); return *this; } bool Repeat::operator==(const Repeat& rhs) const { if (!type_ && rhs.type_) { return false; } if (type_ && !rhs.type_) { return false; } if (!type_ && !rhs.type_) { return true; } return type_->compare(rhs.type_.get()); } const std::string& Repeat::name() const { return (type_.get()) ? type_->name() : ecf::string_constants::empty; } // ========================================================================= RepeatBase::~RepeatBase() = default; void RepeatBase::incr_state_change_no() { state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO std::cout << "RepeatBase::incr_state_change_no()\n"; #endif } void RepeatBase::update_repeat_genvar() const { // **** reset name since generated variables are not persisted var_.set_name(name_); // valueAsString() use the last_valid_value() which should always be in range. // Note repeat::value() can be on e past the last valid value, at expiration of Repeat loop // However Repeat::last_valid_value() will just return the last valid value. var_.set_value(valueAsString()); } std::string RepeatBase::toString() const { return ecf::as_string(*this, PrintStyle::DEFS); } // ============================================================= RepeatDate::RepeatDate(const std::string& variable, int start, int end, int delta /* always in days*/ ) : RepeatBase(variable), start_(start), end_(end), delta_(delta), value_(start) { if (!ecf::algorithm::is_valid_name(variable)) { throw std::runtime_error("RepeatDate::RepeatDate: Invalid name: " + variable); } if (delta == 0) { throw std::runtime_error(MESSAGE("Invalid Repeat date: the delta cannot be zero" << "repeat " << variable << " " << start << " " << end << " " << delta)); } std::string theStart = ecf::convert_to(start); if (theStart.size() != 8) { throw std::runtime_error( MESSAGE("Invalid Repeat date: The start is not a valid date. Please use yyyymmdd format." << "repeat " << variable << " " << start << " " << end << " " << delta)); } std::string theEnd = ecf::convert_to(end); if (theEnd.size() != 8) { throw std::runtime_error(MESSAGE("Invalid Repeat date: The end is not a valid date. Please use yyyymmdd format." << "repeat " << variable << " " << start << " " << end << " " << delta)); } if (delta_ > 0) { // assert end => start if (!(end >= start)) { throw std::runtime_error( MESSAGE("Invalid Repeat date: The end must be greater than the start date, when delta is positive " << "repeat " << variable << " " << start << " " << end << " " << delta)); } } else { // assert start >= end if (!(start >= end)) { throw std::runtime_error( MESSAGE("Invalid Repeat date: The start must be greater than the end date, when delta is negative " << "repeat " << variable << " " << start << " " << end << " " << delta)); } } // Use date lib to check YMD try { (void)boost::gregorian::date(boost::gregorian::from_undelimited_string(theStart)); (void)boost::gregorian::date(boost::gregorian::from_undelimited_string(theEnd)); } catch (std::exception& e) { throw std::runtime_error(MESSAGE("Invalid Repeat date: The start/end is not a valid date." << "repeat " << variable << " " << start << " " << end << " " << delta)); } } void RepeatDate::gen_variables(std::vector& vec) const { vec.push_back(yyyy_); vec.push_back(mm_); vec.push_back(dom_); vec.push_back(dow_); vec.push_back(julian_); RepeatBase::gen_variables(vec); } const Variable& RepeatDate::find_gen_variable(const std::string& name) const { if (name == name_) { return var_; } if (name == julian_.name()) { return julian_; } if (name == dow_.name()) { return dow_; } if (name == dom_.name()) { return dom_; } if (name == mm_.name()) { return mm_; } if (name == yyyy_.name()) { return yyyy_; } return Variable::EMPTY(); } void RepeatDate::update_repeat_genvar() const { RepeatBase::update_repeat_genvar(); yyyy_.set_name(name_ + "_YYYY"); yyyy_.set_value(""); mm_.set_name(name_ + "_MM"); mm_.set_value(""); dom_.set_name(name_ + "_DD"); dom_.set_value(""); dow_.set_name(name_ + "_DOW"); dom_.set_value(""); julian_.set_name(name_ + "_JULIAN"); julian_.set_value(""); yyyy_.set_name(name_ + "_YYYY"); mm_.set_name(name_ + "_MM"); dom_.set_name(name_ + "_DD"); dow_.set_name(name_ + "_DOW"); julian_.set_name(name_ + "_JULIAN"); update_repeat_genvar_value(); } void RepeatDate::update_repeat_genvar_value() const { std::string date_as_string = valueAsString(); if (valid()) { try { auto the_date = boost::gregorian::from_undelimited_string(date_as_string); if (the_date.is_special()) { log(Log::ERR, MESSAGE("RepeatDate::update_repeat_genvar(): invalid current date: " << date_as_string << " is_special")); return; } auto year = static_cast(the_date.year()); yyyy_.set_value(as_4_digits(year)); auto month = the_date.month().as_number(); mm_.set_value(as_2_digits(month)); auto day_of_month = the_date.day().as_number(); dom_.set_value(as_2_digits(day_of_month)); auto day_of_week = the_date.day_of_week().as_number(); dow_.set_value(std::to_string(day_of_week)); auto last_value = last_valid_value(); julian_.set_value(std::to_string(ecf::CalendarDate(last_value).as_julian_day().value())); } catch (std::exception& e) { log(Log::ERR, MESSAGE("RepeatDate::update_repeat_genvar_value : " << toString() << "\n The current date(" << date_as_string << ") is not valid, due to: " << e.what())); return; } } } bool RepeatDate::compare(RepeatBase* rb) const { auto* rhs = dynamic_cast(rb); if (!rhs) { return false; } return operator==(*rhs); } void RepeatDate::setToLastValue() { set_value(end_); } long RepeatDate::current_index() const { // Use Julian-day arithmetic so the index is correct even when the // date sequence crosses a month or year boundary. auto start_julian = ecf::CalendarDate(start_).as_julian_day().value(); auto value_julian = ecf::CalendarDate(value_).as_julian_day().value(); return (value_julian - start_julian) / delta_; } long RepeatDate::last_valid_value() const { return valid_value(value_); } long RepeatDate::valid_value(long value) const { if (delta_ > 0) { if (value < start_) { return start_; } if (value > end_) { return end_; } return value; } if (value > start_) { return start_; } if (value < end_) { return end_; } return value; } long RepeatDate::last_valid_value_minus(int val) const { auto result = ecf::CalendarDate(last_valid_value()) - val; return result.value(); } long RepeatDate::last_valid_value_plus(int val) const { auto result = ecf::CalendarDate(last_valid_value()) + val; return result.value(); } void RepeatDate::reset() { set_value(start_); } std::string RepeatDate::dump() const { return MESSAGE(toString() << " value(" << value_ << ")"); } bool RepeatDate::operator==(const RepeatDate& rhs) const { if (name_ != rhs.name_) { return false; } if (start_ != rhs.start_) { return false; } if (end_ != rhs.end_) { return false; } if (delta_ != rhs.delta_) { return false; } if (value_ != rhs.value_) { return false; } return true; } std::string RepeatDate::valueAsString() const { /// will throw an ecf::bad_conversion if value is not convertible to a string try { return ecf::convert_to(last_valid_value()); } catch (const ecf::bad_conversion&) { LOG_ASSERT(false, "RepeatDate::valueAsString(): could not convert value " << value_ << " to a string"); } return std::string{}; } std::string RepeatDate::value_as_string(int index) const { /// will throw an ecf::bad_conversion if value is not convertible to a string try { return ecf::convert_to(index); } catch (const ecf::bad_conversion&) { } return std::string{}; } std::string RepeatDate::next_value_as_string() const { auto result = ecf::CalendarDate(last_valid_value()) + delta_; long val = result.value(); try { return ecf::convert_to(valid_value(val)); } catch (const ecf::bad_conversion&) { } return std::string{}; } std::string RepeatDate::prev_value_as_string() const { auto result = ecf::CalendarDate(last_valid_value()) - delta_; long val = result.value(); try { return ecf::convert_to(valid_value(val)); } catch (const ecf::bad_conversion&) { } return std::string{}; } void RepeatDate::increment() { auto result = ecf::CalendarDate(last_valid_value()) + delta_; set_value(result.value()); } void RepeatDate::change(const std::string& newdate) { if (newdate.size() != 8) { throw std::runtime_error(MESSAGE( "RepeatDate::change: " << toString() << " The new date is not valid, expected 8 characters in yyyymmdd format but found " << newdate)); } long the_new_date = 0; try { the_new_date = ecf::convert_to(newdate); } catch (const ecf::bad_conversion&) { throw std::runtime_error(MESSAGE("RepeatDate::change: " << toString() << " The new date(" << newdate << ") is not convertible to an long")); } // Use date lib to check YMD try { (void)boost::gregorian::date(boost::gregorian::from_undelimited_string(newdate)); } catch (std::exception& e) { throw std::runtime_error( MESSAGE("RepeatDate::change: " << toString() << " The new date(" << newdate << ") is not valid")); } changeValue(the_new_date); } void RepeatDate::changeValue(long the_new_date) { if (delta_ > 0) { if (the_new_date < start_ || the_new_date > end_) { throw std::runtime_error( MESSAGE("RepeatDate::changeValue: " << toString() << "\nThe new date should be in the range[" << start_ << " : " << end_ << "] but found " << the_new_date)); } } else { if (the_new_date > start_ || the_new_date < end_) { throw std::runtime_error( MESSAGE("RepeatDate::changeValue: " << toString() << "\nThe new date should be in the range[" << start_ << " : " << end_ << "] but found " << the_new_date)); } } // Check new value is in step. ECFLOW-325 repeat date 7 long julian_new_date = ecf::CalendarDate(the_new_date).as_julian_day().value(); long julian_start = ecf::CalendarDate(start_).as_julian_day().value(); long diff = julian_new_date - julian_start; if (diff % delta_ != 0) { throw std::runtime_error(MESSAGE("RepeatDate::changeValue: " << toString() << "\nThe new date " << the_new_date << " is not in line with the delta/step")); } set_value(the_new_date); } void RepeatDate::set_value(long the_new_date) { // Note: the node is incremented one past, the last value // In Node we increment() then check for validity // hence the_new_value may be outside the valid range. // This can occur when an incremental sync happens, // hence, allow memento to copy the value as is. value_ = the_new_date; update_repeat_genvar_value(); incr_state_change_no(); } // ============================================================= RepeatDateTime::RepeatDateTime(const std::string& variable, const std::string& start, const std::string& end, const std::string& delta) : RepeatDateTime(variable, Instant::parse(start), Instant::parse(end), Duration::parse(delta)) { } RepeatDateTime::RepeatDateTime(const std::string& variable, Instant start, Instant end, Duration delta) : RepeatBase(variable), start_(start), end_(end), delta_(delta), value_(start) { if (!ecf::algorithm::is_valid_name(variable)) { throw std::runtime_error("RepeatDateTime::RepeatDateTime: Invalid name: " + variable); } if (delta == Duration{std::chrono::seconds{0}}) { throw std::runtime_error(MESSAGE("Invalid Repeat datetime: the delta cannot be zero" << "repeat " << variable << " " << start << " " << end << " " << delta)); } auto theStart = ecf::convert_to(start); if (theStart.size() != 15) { throw std::runtime_error( MESSAGE("Invalid Repeat datetime: The start is not a valid date+time. Please use yyyymmddTMMHHSS format." << "repeat " << variable << " " << start << " " << end << " " << delta)); } auto theEnd = ecf::convert_to(end); if (theEnd.size() != 15) { throw std::runtime_error( MESSAGE("Invalid Repeat datetime: The end is not a valid date+time. Please use yyyymmddTHHMMSS format." << "repeat " << variable << " " << start << " " << end << " " << delta)); } if (delta_ > Duration{std::chrono::seconds{0}}) { // assert end => start if (!(end >= start)) { throw std::runtime_error(MESSAGE( "Invalid Repeat datetime: The end must be greater than the start date+time, when delta is positive " << "repeat " << variable << " " << start << " " << end << " " << delta)); } } else { // assert start >= end if (!(start >= end)) { throw std::runtime_error(MESSAGE( "Invalid Repeat datetime: The start must be greater than the end date+time, when delta is negative " << "repeat " << variable << " " << start << " " << end << " " << delta)); } } } void RepeatDateTime::gen_variables(std::vector& vec) const { for (const auto& entry : generated_) { vec.push_back(entry); } RepeatBase::gen_variables(vec); } const Variable& RepeatDateTime::find_gen_variable(const std::string& name) const { if (name == name_) { return var_; } for (const auto& entry : generated_) { if (entry.name() == name) { return entry; } } return Variable::EMPTY(); } void RepeatDateTime::update_repeat_genvar() const { RepeatBase::update_repeat_genvar(); // Reset variables values generated_.set_value(""); update_repeat_genvar_value(); } void RepeatDateTime::update_repeat_genvar_value() const { std::string date_as_string = valueAsString(); if (valid()) { try { auto dt = boost::posix_time::from_iso_string(date_as_string); // Using boost posix_time/gregorian, since C++17 still does not include calendar types. auto d = dt.date(); // Date generated_[name_ + "_DATE"].set_value(boost::gregorian::to_iso_string(d)); // Date Components auto year = static_cast(d.year()); generated_[name_ + "_YYYY"].set_value(as_4_digits(year)); auto month = d.month().as_number(); generated_[name_ + "_MM"].set_value(as_2_digits(month)); auto day = d.day().as_number(); generated_[name_ + "_DD"].set_value(as_2_digits(day)); auto julian = d.julian_day(); generated_[name_ + "_JULIAN"].set_value(std::to_string(julian)); auto t = dt.time_of_day(); // Time generated_[name_ + "_TIME"].set_value(boost::posix_time::to_iso_string(t)); // Time Components int hours = t.hours(); generated_[name_ + "_HOURS"].set_value(as_2_digits(hours)); int minutes = t.minutes(); generated_[name_ + "_MINUTES"].set_value(as_2_digits(minutes)); int seconds = t.seconds(); generated_[name_ + "_SECONDS"].set_value(as_2_digits(seconds)); } catch (std::exception& e) { log(Log::ERR, MESSAGE("RepeatDateTime::update_repeat_genvar_value : " << toString() << "\n The current date(" << date_as_string << ") is not valid, due to: " << e.what())); return; } } } bool RepeatDateTime::compare(RepeatBase* rb) const { auto* rhs = dynamic_cast(rb); if (!rhs) { return false; } return operator==(*rhs); } void RepeatDateTime::setToLastValue() { set_value(coerce_from_instant_into_seconds(end_)); } long RepeatDateTime::last_valid_value() const { Instant valid = valid_value(value_); return coerce_from_instant_into_seconds(valid); } Instant RepeatDateTime::valid_value(const Instant& value) const { if (delta_ > Duration{std::chrono::seconds{0}}) { if (value < start_) { return start_; } if (value > end_) { return end_; } return value; } if (value > start_) { return start_; } if (value < end_) { return end_; } return value; } long RepeatDateTime::last_valid_value_minus(int val) const { Instant last_value = coerce_from_seconds_into_instant(last_valid_value()); Instant updated_value = last_value - Duration{std::chrono::seconds{val}}; return coerce_from_instant_into_seconds(updated_value); } long RepeatDateTime::last_valid_value_plus(int val) const { Instant last_value = coerce_from_seconds_into_instant(last_valid_value()); Instant updated_value = last_value + Duration{std::chrono::seconds{val}}; return coerce_from_instant_into_seconds(updated_value); } void RepeatDateTime::reset() { set_value(coerce_from_instant_into_seconds(start_)); } std::string RepeatDateTime::dump() const { return MESSAGE(toString() << " value(" << value_ << ")"); } bool RepeatDateTime::operator==(const RepeatDateTime& rhs) const { if (name_ != rhs.name_) { return false; } if (start_ != rhs.start_) { return false; } if (end_ != rhs.end_) { return false; } if (delta_ != rhs.delta_) { return false; } if (value_ != rhs.value_) { return false; } return true; } std::string RepeatDateTime::valueAsString() const { try { long value = last_valid_value(); Instant instant = coerce_from_seconds_into_instant(value); return ecf::convert_to(instant); } catch (ecf::bad_conversion&) { LOG_ASSERT(false, "RepeatDateTime::valueAsString(): could not convert value " << value_ << " to a string"); } return {}; } std::string RepeatDateTime::value_as_string(int index) const { try { return ecf::convert_to(index); } catch (ecf::bad_conversion&) { } return {}; } std::string RepeatDateTime::next_value_as_string() const { Instant next = coerce_from_seconds_into_instant(last_valid_value()) + delta_; try { return ecf::convert_to(valid_value(next)); } catch (ecf::bad_conversion&) { } return {}; } std::string RepeatDateTime::prev_value_as_string() const { Instant previous = coerce_from_seconds_into_instant(last_valid_value()) - delta_; try { return ecf::convert_to(valid_value(previous)); } catch (ecf::bad_conversion&) { } return {}; } void RepeatDateTime::increment() { auto new_value = value_ + delta_; set_value(ecf::coerce_from_instant_into_seconds(new_value)); } void RepeatDateTime::change(const std::string& newdate) { long the_new_date; try { auto instant = Instant::parse(newdate); the_new_date = ecf::coerce_from_instant_into_seconds(instant); } catch (std::exception& e) { throw std::runtime_error( MESSAGE("RepeatDateTime::change: " << toString() << " The new date(" << newdate << ") is not valid")); } changeValue(the_new_date); } void RepeatDateTime::changeValue(long the_new_date) { auto new_date = ecf::coerce_from_seconds_into_instant(the_new_date); if (delta_ > Duration{std::chrono::seconds{0}}) { if (new_date < start_ || new_date > end_) { throw std::runtime_error( MESSAGE("RepeatDateTime::changeValue: " << toString() << "\nThe new date should be in the range[" << start_ << " : " << end_ << "] but found " << new_date)); } } else { if (new_date > start_ || new_date < end_) { throw std::runtime_error( MESSAGE("RepeatDateTime::changeValue: " << toString() << "\nThe new date should be in the range[" << start_ << " : " << end_ << "] but found " << the_new_date)); } } // Ensure that new value is in step auto diff = new_date - start_; if (diff.as_seconds().count() % delta_.as_seconds().count() != 0) { throw std::runtime_error(MESSAGE("RepeatDateTime::changeValue: " << toString() << "\nThe new date " << the_new_date << " is not in line with the delta/step")); } set_value(the_new_date); } void RepeatDateTime::set_value(long the_new_date) { // Note: the node is incremented one past, the last value // In Node we increment() then check for validity // hence the_new_value may be outside the valid range. // This can occur when an incremental sync happens, // hence, allow memento to copy the value as is. value_ = coerce_from_seconds_into_instant(the_new_date); update_repeat_genvar_value(); incr_state_change_no(); } //====================================================================================== RepeatDateList::RepeatDateList(const std::string& variable, const std::vector& l) : RepeatBase(variable), list_(l) { if (!ecf::algorithm::is_valid_name(variable)) { throw std::runtime_error("RepeatDateList: Invalid name: " + variable); } if (list_.empty()) { throw std::runtime_error("RepeatDateList: " + variable + " is empty"); } for (int i : list_) { std::string date_i = ecf::convert_to(i); if (date_i.size() != 8) { throw std::runtime_error( MESSAGE("Invalid Repeat datelist : " << variable << " the date " << i << " is not valid. Please use yyyymmdd format.")); } try { (void)boost::gregorian::date(boost::gregorian::from_undelimited_string(date_i)); } catch (std::exception& e) { throw std::runtime_error( MESSAGE("Invalid Repeat datelist : " << variable << " the date " << i << " is not valid. Please use yyyymmdd format.")); } } } void RepeatDateList::gen_variables(std::vector& vec) const { vec.push_back(yyyy_); vec.push_back(mm_); vec.push_back(dom_); vec.push_back(dow_); vec.push_back(julian_); RepeatBase::gen_variables(vec); } const Variable& RepeatDateList::find_gen_variable(const std::string& name) const { if (name == name_) { return var_; } if (name == yyyy_.name()) { return yyyy_; } if (name == mm_.name()) { return mm_; } if (name == dom_.name()) { return dom_; } if (name == dow_.name()) { return dow_; } if (name == julian_.name()) { return julian_; } return Variable::EMPTY(); } void RepeatDateList::update_repeat_genvar() const { RepeatBase::update_repeat_genvar(); yyyy_.set_name(name_ + "_YYYY"); yyyy_.set_value(""); mm_.set_name(name_ + "_MM"); mm_.set_value(""); dom_.set_name(name_ + "_DD"); dom_.set_value(""); dow_.set_name(name_ + "_DOW"); dom_.set_value(""); julian_.set_name(name_ + "_JULIAN"); julian_.set_value(""); update_repeat_genvar_value(); } void RepeatDateList::update_repeat_genvar_value() const { if (valid()) { std::string date_as_string = valueAsString(); try { auto the_date = boost::gregorian::from_undelimited_string(date_as_string); if (the_date.is_special()) { log(Log::ERR, MESSAGE("RepeatDateList::update_repeat_genvar_value(): " << toString() << "\n invalid current date: " << date_as_string << " is special ")); return; } auto year = static_cast(the_date.year()); yyyy_.set_value(as_4_digits(year)); auto month = the_date.month().as_number(); mm_.set_value(as_2_digits(month)); auto day_of_month = the_date.day().as_number(); dom_.set_value(as_2_digits(day_of_month)); auto day_of_week = the_date.day_of_week().as_number(); dow_.set_value(std::to_string(day_of_week)); auto julian_day = CalendarDate(last_valid_value()).as_julian_day().value(); julian_.set_value(std::to_string(julian_day)); } catch (std::exception& e) { log(Log::ERR, MESSAGE("RepeatDateList::update_repeat_genvar_value(): " << toString() << "\n invalid current date: " << date_as_string << ", due to: " << e.what())); } } } int RepeatDateList::start() const { if (list_.empty()) { return 0; } return list_[0]; } int RepeatDateList::end() const { if (list_.empty()) { return 0; } return list_[list_.size() - 1]; } bool RepeatDateList::compare(RepeatBase* rb) const { auto* rhs = dynamic_cast(rb); if (!rhs) { return false; } return operator==(*rhs); } std::string RepeatDateList::dump() const { return MESSAGE(toString() << " ordinal-value(" << value() << ") value-as-string(" << valueAsString() << ")"); } void RepeatDateList::reset() { set_value(0); } void RepeatDateList::increment() { set_value(currentIndex_ + 1); } long RepeatDateList::value() const { if (list_.empty()) { return 0; } if (currentIndex_ >= 0 && currentIndex_ < static_cast(list_.size())) { return list_[currentIndex_]; } return 0; } long RepeatDateList::last_valid_value() const { if (list_.empty()) { return 0; } if (currentIndex_ >= 0 && currentIndex_ < static_cast(list_.size())) { return list_[currentIndex_]; } if (currentIndex_ < 0) { return list_[0]; } if (currentIndex_ >= static_cast(list_.size())) { return list_[list_.size() - 1]; } return 0; } long RepeatDateList::last_valid_value_minus(int val) const { long last_value = last_valid_value(); if (last_value == 0) { return 0; } return (CalendarDate(last_value) - val).value(); } long RepeatDateList::last_valid_value_plus(int val) const { long last_value = last_valid_value(); if (last_value == 0) { return 0; } return (CalendarDate(last_value) + val).value(); } void RepeatDateList::setToLastValue() { if (list_.empty()) { return; } set_value(static_cast(list_.size() - 1)); } std::string RepeatDateList::valueAsString() const { return ecf::convert_to(last_valid_value()); } std::string RepeatDateList::value_as_string(int index) const { if (list_.empty()) { return std::string("0"); } if (index >= 0 && index < static_cast(list_.size())) { return ecf::convert_to(list_[index]); } if (index < 0) { return ecf::convert_to(list_[0]); } if (index >= static_cast(list_.size())) { return ecf::convert_to(list_[list_.size() - 1]); } return std::string{}; } std::string RepeatDateList::next_value_as_string() const { if (list_.empty()) { return std::string("0"); } int index = currentIndex_; index++; return value_as_string(index); } std::string RepeatDateList::prev_value_as_string() const { if (list_.empty()) { return std::string("0"); } int index = currentIndex_; index--; return value_as_string(index); } void RepeatDateList::change(const std::string& newValue) { // See if it matches one of the dates int new_val = 0; try { new_val = ecf::convert_to(newValue); } catch (...) { throw std::runtime_error(MESSAGE( "RepeatDateList::change: " << toString() << "\nThe new value " << newValue << " is must be convertible to integer, and correspond to an existing value\n")); } for (size_t i = 0; i < list_.size(); i++) { if (list_[i] == new_val) { set_value(i); return; } } throw std::runtime_error(MESSAGE("RepeatDateList::change: " << toString() << "\nThe new value " << newValue << " is not a valid member of the date list\n")); } void RepeatDateList::changeValue(long the_new_index) { if (list_.empty()) { return; } if (the_new_index < 0 || the_new_index >= static_cast(list_.size())) { throw std::runtime_error(MESSAGE("RepeatDateList::changeValue:" << toString() << "\nThe new value '" << the_new_index << "' is not a valid index " << "expected range[0-" << list_.size() - 1 << "] but found '" << the_new_index << "'")); } set_value(the_new_index); } void RepeatDateList::set_value(long the_new_index) { if (list_.empty()) { return; } // Note: the node is incremented one past, the last value // In Node we increment() then check for validity // hence the_new_value may be outside the valid range. // This can occur when an incremental sync happens, // hence, allow memento to copy the value as is. currentIndex_ = the_new_index; update_repeat_genvar_value(); incr_state_change_no(); } bool RepeatDateList::operator==(const RepeatDateList& rhs) const { if (name_ != rhs.name_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "RepeatDateList::operator==( name_(" << name_ << ") != rhs.name_(" << rhs.name_ << "))\n"; } #endif return false; } if (list_ != rhs.list_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "RepeatDateList::operator==( list_ != rhs.list_ )\n"; } #endif return false; } if (currentIndex_ != rhs.currentIndex_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "RepeatDateList::operator==( currentIndex_(" << currentIndex_ << ") != rhs.currentIndex_(" << rhs.currentIndex_ << "))\n"; } #endif return false; } return true; } //====================================================================================== RepeatDateTimeList::RepeatDateTimeList(const std::string& variable, const std::vector& l) : RepeatBase(variable), list_(l) { if (!ecf::algorithm::is_valid_name(variable)) { throw std::runtime_error("RepeatDateTimeList: Invalid name: " + variable); } if (list_.empty()) { throw std::runtime_error("RepeatDateTimeList: " + variable + " is empty"); } for (const Instant& i : list_) { auto ts = ecf::convert_to(i); if (ts.size() != 15) { throw std::runtime_error( MESSAGE("Invalid Repeat datetimelist : " << variable << " the datetime " << ts << " is not valid. Please use yyyymmddTHHMMSS format.")); } } } void RepeatDateTimeList::gen_variables(std::vector& vec) const { for (const auto& entry : generated_) { vec.push_back(entry); } RepeatBase::gen_variables(vec); } const Variable& RepeatDateTimeList::find_gen_variable(const std::string& name) const { if (name == name_) { return var_; } for (const auto& entry : generated_) { if (entry.name() == name) { return entry; } } return Variable::EMPTY(); } void RepeatDateTimeList::update_repeat_genvar() const { RepeatBase::update_repeat_genvar(); // Reset variables values generated_.set_value(""); update_repeat_genvar_value(); } void RepeatDateTimeList::update_repeat_genvar_value() const { if (valid()) { std::string date_as_string = valueAsString(); try { auto dt = boost::posix_time::from_iso_string(date_as_string); auto d = dt.date(); generated_[name_ + "_DATE"].set_value(boost::gregorian::to_iso_string(d)); auto year = static_cast(d.year()); generated_[name_ + "_YYYY"].set_value(as_4_digits(year)); auto month = d.month().as_number(); generated_[name_ + "_MM"].set_value(as_2_digits(month)); auto day = d.day().as_number(); generated_[name_ + "_DD"].set_value(as_2_digits(day)); auto julian = d.julian_day(); generated_[name_ + "_JULIAN"].set_value(std::to_string(julian)); auto t = dt.time_of_day(); generated_[name_ + "_TIME"].set_value(boost::posix_time::to_iso_string(t)); int hours = t.hours(); generated_[name_ + "_HOURS"].set_value(as_2_digits(hours)); int minutes = t.minutes(); generated_[name_ + "_MINUTES"].set_value(as_2_digits(minutes)); int seconds = t.seconds(); generated_[name_ + "_SECONDS"].set_value(as_2_digits(seconds)); } catch (std::exception& e) { log(Log::ERR, MESSAGE("RepeatDateTimeList::update_repeat_genvar_value : " << toString() << "\n The current datetime(" << date_as_string << ") is not valid, due to: " << e.what())); return; } } } int RepeatDateTimeList::start() const { if (list_.empty()) { return 0; } return coerce_from_instant_into_seconds(list_[0]); } int RepeatDateTimeList::end() const { if (list_.empty()) { return 0; } return coerce_from_instant_into_seconds(list_[list_.size() - 1]); } long RepeatDateTimeList::value() const { if (list_.empty()) { return 0; } if (currentIndex_ >= 0 && currentIndex_ < static_cast(list_.size())) { return coerce_from_instant_into_seconds(list_[currentIndex_]); } return 0; } long RepeatDateTimeList::last_valid_value() const { if (list_.empty()) { return 0; } if (currentIndex_ >= 0 && currentIndex_ < static_cast(list_.size())) { return coerce_from_instant_into_seconds(list_[currentIndex_]); } if (currentIndex_ < 0) { return coerce_from_instant_into_seconds(list_[0]); } if (currentIndex_ >= static_cast(list_.size())) { return coerce_from_instant_into_seconds(list_[list_.size() - 1]); } return 0; } long RepeatDateTimeList::last_valid_value_minus(int val) const { long lv = last_valid_value(); if (lv == 0) { return 0; } Instant last = coerce_from_seconds_into_instant(lv); Instant updated = last - Duration{std::chrono::seconds{val}}; return coerce_from_instant_into_seconds(updated); } long RepeatDateTimeList::last_valid_value_plus(int val) const { long lv = last_valid_value(); if (lv == 0) { return 0; } Instant last = coerce_from_seconds_into_instant(lv); Instant updated = last + Duration{std::chrono::seconds{val}}; return coerce_from_instant_into_seconds(updated); } bool RepeatDateTimeList::compare(RepeatBase* rb) const { auto* rhs = dynamic_cast(rb); if (!rhs) { return false; } return operator==(*rhs); } std::string RepeatDateTimeList::dump() const { return MESSAGE(toString() << " ordinal-value(" << value() << ") value-as-string(" << valueAsString() << ")"); } void RepeatDateTimeList::reset() { set_value(0); } void RepeatDateTimeList::increment() { set_value(currentIndex_ + 1); } void RepeatDateTimeList::setToLastValue() { if (list_.empty()) { return; } set_value(static_cast(list_.size() - 1)); } std::string RepeatDateTimeList::valueAsString() const { if (list_.empty()) { return {}; } int index = currentIndex_; if (index < 0) { index = 0; } if (index >= static_cast(list_.size())) { index = static_cast(list_.size() - 1); } try { return ecf::convert_to(list_[index]); } catch (ecf::bad_conversion&) { } return {}; } std::string RepeatDateTimeList::value_as_string(int index) const { if (list_.empty()) { return {}; } if (index < 0) { index = 0; } if (index >= static_cast(list_.size())) { index = static_cast(list_.size() - 1); } try { return ecf::convert_to(list_[index]); } catch (ecf::bad_conversion&) { } return {}; } std::string RepeatDateTimeList::next_value_as_string() const { if (list_.empty()) { return {}; } return value_as_string(currentIndex_ + 1); } std::string RepeatDateTimeList::prev_value_as_string() const { if (list_.empty()) { return {}; } return value_as_string(currentIndex_ - 1); } void RepeatDateTimeList::change(const std::string& newValue) { Instant target; try { target = Instant::parse(newValue); } catch (std::exception&) { throw std::runtime_error( MESSAGE("RepeatDateTimeList::change: " << toString() << "\nThe new value " << newValue << " is not a valid datetime (yyyymmddTHHMMSS)")); } for (size_t i = 0; i < list_.size(); i++) { if (list_[i] == target) { set_value(static_cast(i)); return; } } throw std::runtime_error(MESSAGE("RepeatDateTimeList::change: " << toString() << "\nThe new value " << newValue << " is not a valid member of the datetimelist\n")); } void RepeatDateTimeList::changeValue(long the_new_index) { if (list_.empty()) { return; } if (the_new_index < 0 || the_new_index >= static_cast(list_.size())) { throw std::runtime_error( MESSAGE("RepeatDateTimeList::changeValue:" << toString() << "\nThe new value '" << the_new_index << "' is not a valid index " << "expected range[0-" << list_.size() - 1 << "] but found '" << the_new_index << "'")); } set_value(the_new_index); } void RepeatDateTimeList::set_value(long the_new_index) { if (list_.empty()) { return; } currentIndex_ = the_new_index; update_repeat_genvar_value(); incr_state_change_no(); } bool RepeatDateTimeList::operator==(const RepeatDateTimeList& rhs) const { if (name_ != rhs.name_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "RepeatDateTimeList::operator==( name_(" << name_ << ") != rhs.name_(" << rhs.name_ << "))\n"; } #endif return false; } if (list_ != rhs.list_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "RepeatDateTimeList::operator==( list_ != rhs.list_ )\n"; } #endif return false; } if (currentIndex_ != rhs.currentIndex_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "RepeatDateTimeList::operator==( currentIndex_(" << currentIndex_ << ") != rhs.currentIndex_(" << rhs.currentIndex_ << "))\n"; } #endif return false; } return true; } //====================================================================================== RepeatInteger::RepeatInteger(const std::string& variable, int start, int end, int delta) : RepeatBase(variable), start_(start), end_(end), delta_(delta), value_(start) { // cout << toString() << "\n"; if (!ecf::algorithm::is_valid_name(variable)) { throw std::runtime_error("RepeatInteger: Invalid name: " + variable); } } RepeatInteger::RepeatInteger() = default; bool RepeatInteger::compare(RepeatBase* rb) const { auto* rhs = dynamic_cast(rb); if (!rhs) { return false; } return operator==(*rhs); } void RepeatInteger::reset() { value_ = start_; incr_state_change_no(); } long RepeatInteger::last_valid_value() const { return valid_value(value_); } long RepeatInteger::valid_value(long value) const { if (delta_ > 0) { if (value < start_) { return start_; } if (value > end_) { return end_; } return value; } if (value > start_) { return start_; } if (value < end_) { return end_; } return value; } void RepeatInteger::increment() { value_ += delta_; incr_state_change_no(); } void RepeatInteger::change(const std::string& newValue) { long the_new_value = 0; try { the_new_value = ecf::convert_to(newValue); } catch (const ecf::bad_conversion&) { throw std::runtime_error(MESSAGE("RepeatInteger::change:" << toString() << " The new value(" << newValue << ") is not convertible to an long")); } changeValue(the_new_value); } void RepeatInteger::changeValue(long the_new_value) { if (delta_ > 0) { if (the_new_value < start_ || the_new_value > end_) { throw std::runtime_error( MESSAGE("RepeatInteger::changeValue:" << toString() << ". The new value should be in the range[" << start_ << "-" << end_ << "] but found " << the_new_value)); } } else { if (the_new_value > start_ || the_new_value < end_) { throw std::runtime_error( MESSAGE("RepeatInteger::changeValue:" << toString() << ". The new value should be in the range[" << start_ << "-" << end_ << "] but found " << the_new_value)); } } set_value(the_new_value); } void RepeatInteger::set_value(long the_new_value) { // To be used by Memento only. as it does no checking // Note: the node is incremented one past, the last value // In Node we increment() then check for validity // hence the_new_value may be outside the valid range. // This can occur when an incremental sync happens, // hence, allow memento to copy the value as is. value_ = the_new_value; incr_state_change_no(); } void RepeatInteger::setToLastValue() { value_ = end_; incr_state_change_no(); } std::string RepeatInteger::dump() const { return MESSAGE(toString() << " value(" << value_ << ")"); } bool RepeatInteger::operator==(const RepeatInteger& rhs) const { if (name_ != rhs.name_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "RepeatInteger::operator==( name_(" << name_ << ") != rhs.name_(" << rhs.name_ << "))" << "\n"; } #endif return false; } if (start_ != rhs.start_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "RepeatInteger::operator==( start_(" << start_ << ") != rhs.start_(" << rhs.start_ << "))" << "\n"; } #endif return false; } if (end_ != rhs.end_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "RepeatInteger::operator==( end_(" << end_ << ") != rhs.end_(" << rhs.end_ << "))" << "\n"; } #endif return false; } if (delta_ != rhs.delta_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "RepeatInteger::operator==( delta_(" << delta_ << ") != rhs.delta_(" << rhs.delta_ << "))" << "\n"; } #endif return false; } if (value_ != rhs.value_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "RepeatInteger::operator==( value_(" << value_ << ") != rhs.value_(" << rhs.value_ << "))" << "\n"; } #endif return false; } return true; } std::string RepeatInteger::valueAsString() const { /// will throw an ecf::bad_conversion if value is not convertible to a string try { return ecf::convert_to(last_valid_value()); } catch (const ecf::bad_conversion&) { LOG_ASSERT(false, ""); } return std::string{}; } std::string RepeatInteger::value_as_string(int index) const { /// will throw an ecf::bad_conversion if value is not convertible to a string try { return ecf::convert_to(index); } catch (const ecf::bad_conversion&) { } return std::string{}; } std::string RepeatInteger::next_value_as_string() const { long val = last_valid_value(); val += delta_; try { return ecf::convert_to(valid_value(val)); } catch (const ecf::bad_conversion&) { } return std::string{}; } std::string RepeatInteger::prev_value_as_string() const { long val = last_valid_value(); val -= delta_; try { return ecf::convert_to(valid_value(val)); } catch (const ecf::bad_conversion&) { } return std::string{}; } //====================================================================================== RepeatEnumerated::RepeatEnumerated(const std::string& variable, const std::vector& theEnums) : RepeatBase(variable), theEnums_(theEnums) { if (!ecf::algorithm::is_valid_name(variable)) { throw std::runtime_error("RepeatEnumerated: Invalid name: " + variable); } if (theEnums.empty()) { throw std::runtime_error("RepeatEnumerated: " + variable + " is empty"); } } int RepeatEnumerated::end() const { if (theEnums_.empty()) { return 0; } return static_cast(theEnums_.size() - 1); } bool RepeatEnumerated::compare(RepeatBase* rb) const { auto* rhs = dynamic_cast(rb); if (!rhs) { return false; } return operator==(*rhs); } std::string RepeatEnumerated::dump() const { return MESSAGE(toString() << " ordinal-value(" << value() << ") value-as-string(" << valueAsString() << ")"); } void RepeatEnumerated::reset() { currentIndex_ = 0; incr_state_change_no(); } void RepeatEnumerated::increment() { currentIndex_++; incr_state_change_no(); } long RepeatEnumerated::value() const { if (currentIndex_ >= 0 && currentIndex_ < static_cast(theEnums_.size())) { try { return ecf::convert_to(theEnums_[currentIndex_]); } catch (const ecf::bad_conversion&) { // Ignore and return currentIndex_ } } return currentIndex_; } long RepeatEnumerated::last_valid_value() const { if (!theEnums_.empty()) { if (currentIndex_ < 0) { try { return ecf::convert_to(theEnums_[0]); } catch (const ecf::bad_conversion&) { /* Ignore and return first index */ } return 0; } if (currentIndex_ >= static_cast(theEnums_.size())) { try { return ecf::convert_to(theEnums_[theEnums_.size() - 1]); } catch (const ecf::bad_conversion&) { /* Ignore and return last index */ } return static_cast(theEnums_.size() - 1); } // return current value as integer or as index return value(); } return 0; } void RepeatEnumerated::setToLastValue() { currentIndex_ = static_cast(theEnums_.size() - 1); if (currentIndex_ < 0) { currentIndex_ = 0; } incr_state_change_no(); } std::string RepeatEnumerated::valueAsString() const { // This must always return a valid value if (!theEnums_.empty()) { // Returns the last valid value if (currentIndex_ < 0) { return theEnums_[0]; // return first } if (currentIndex_ >= static_cast(theEnums_.size())) { return theEnums_[theEnums_.size() - 1]; // return last } return theEnums_[currentIndex_]; } return std::string{}; } std::string RepeatEnumerated::value_as_string(int index) const { if (index >= 0 && index < static_cast(theEnums_.size())) { return theEnums_[index]; } return std::string{}; } std::string RepeatEnumerated::next_value_as_string() const { if (theEnums_.empty()) { return std::string{}; } int index = currentIndex_; index++; if (index < 0) { return theEnums_[0]; // return first } if (index >= static_cast(theEnums_.size())) { return theEnums_[theEnums_.size() - 1]; // return last } return theEnums_[index]; } std::string RepeatEnumerated::prev_value_as_string() const { if (theEnums_.empty()) { return std::string{}; } int index = currentIndex_; index--; if (index < 0) { return theEnums_[0]; // return first } if (index >= static_cast(theEnums_.size())) { return theEnums_[theEnums_.size() - 1]; // return last } return theEnums_[index]; } void RepeatEnumerated::change(const std::string& newValue) { // See if it matches one of the enums for (size_t i = 0; i < theEnums_.size(); i++) { if (theEnums_[i] == newValue) { currentIndex_ = i; incr_state_change_no(); return; } } // If the value is convertible to an integer, treat as an index try { auto the_new_value = ecf::convert_to(newValue); changeValue(the_new_value); // can throw if out of range return; } catch (const ecf::bad_conversion&) { } throw std::runtime_error( MESSAGE("RepeatEnumerated::change:" << toString() << "\nThe new value " << newValue << " is not a valid index or a member of the enumerated list\n")); } void RepeatEnumerated::changeValue(long the_new_value) { if (the_new_value < 0 || the_new_value >= static_cast(theEnums_.size())) { throw std::runtime_error(MESSAGE("RepeatEnumerated::changeValue:" << toString() << "\nThe new value '" << the_new_value << "' is not a valid index " << "expected range[0-" << theEnums_.size() - 1 << "] but found '" << the_new_value << "'")); } set_value(the_new_value); } void RepeatEnumerated::set_value(long the_new_value) { // Note: the node is incremented one past, the last value // In Node we increment() then check for validity // hence the_new_value may be outside the valid range. // This can occur when an incremental sync happens, // hence, allow memento to copy the value as is. currentIndex_ = the_new_value; incr_state_change_no(); } bool RepeatEnumerated::operator==(const RepeatEnumerated& rhs) const { if (name_ != rhs.name_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "RepeatEnumerated::operator==( name_(" << name_ << ") != rhs.name_(" << rhs.name_ << "))\n"; } #endif return false; } if (theEnums_ != rhs.theEnums_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "RepeatEnumerated::operator==( theEnums_ != rhs.theEnums_ )\n"; } #endif return false; } if (currentIndex_ != rhs.currentIndex_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "RepeatEnumerated::operator==( currentIndex_(" << currentIndex_ << ") != rhs.currentIndex_(" << rhs.currentIndex_ << "))\n"; } #endif return false; } return true; } //====================================================================================== RepeatString::RepeatString(const std::string& variable, const std::vector& theEnums) : RepeatBase(variable), theStrings_(theEnums) { if (!ecf::algorithm::is_valid_name(variable)) { throw std::runtime_error("RepeatString:: Invalid name: " + variable); } if (theEnums.empty()) { throw std::runtime_error("RepeatString : " + variable + " is empty"); } } int RepeatString::end() const { if (theStrings_.empty()) { return 0; } return static_cast(theStrings_.size() - 1); } bool RepeatString::compare(RepeatBase* rb) const { auto* rhs = dynamic_cast(rb); if (!rhs) { return false; } return operator==(*rhs); } std::string RepeatString::dump() const { return MESSAGE(toString() << " ordinal-value(" << value() << ") value-as-string(" << valueAsString() << ")"); } void RepeatString::reset() { currentIndex_ = 0; incr_state_change_no(); } long RepeatString::last_valid_value() const { if (!theStrings_.empty()) { if (currentIndex_ < 0) { return 0; } if (currentIndex_ >= static_cast(theStrings_.size())) { return static_cast(theStrings_.size() - 1); } return currentIndex_; } return 0; } std::string RepeatString::next_value_as_string() const { if (!theStrings_.empty()) { int index = currentIndex_; index++; if (index < 0) { return theStrings_[0]; // return first } if (index >= static_cast(theStrings_.size())) { return theStrings_[theStrings_.size() - 1]; // return last } return theStrings_[index]; } return std::string{}; } std::string RepeatString::prev_value_as_string() const { if (!theStrings_.empty()) { int index = currentIndex_; index--; if (index < 0) { return theStrings_[0]; // return first } if (index >= static_cast(theStrings_.size())) { return theStrings_[theStrings_.size() - 1]; // return last } return theStrings_[index]; } return std::string{}; } void RepeatString::increment() { currentIndex_++; incr_state_change_no(); } void RepeatString::setToLastValue() { currentIndex_ = static_cast(theStrings_.size() - 1); if (currentIndex_ < 0) { currentIndex_ = 0; } incr_state_change_no(); } std::string RepeatString::valueAsString() const { if (!theStrings_.empty()) { return theStrings_[last_valid_value()]; } return std::string{}; } std::string RepeatString::value_as_string(int index) const { if (index >= 0 && index < static_cast(theStrings_.size())) { return theStrings_[index]; } return std::string{}; } void RepeatString::change(const std::string& newValue) { // See if it matches one of the strings for (size_t i = 0; i < theStrings_.size(); i++) { if (theStrings_[i] == newValue) { currentIndex_ = i; incr_state_change_no(); return; } } // If the value is convertible to an integer, treat as an index try { long the_new_value = ecf::convert_to(newValue); changeValue(the_new_value); return; } catch (const ecf::bad_conversion&) { } throw std::runtime_error(MESSAGE("RepeatString::change: " << toString() << "\nThe new value " << newValue << " is not a valid index or member of the string list")); } void RepeatString::changeValue(long the_new_value) { if (the_new_value < 0 || the_new_value >= static_cast(theStrings_.size())) { throw std::runtime_error(MESSAGE("RepeatString::change: " << toString() << " The new the integer " << the_new_value << " is not a valid index " << "expected range[0-" << theStrings_.size() - 1 << "]'")); } set_value(the_new_value); } void RepeatString::set_value(long the_new_value) { // Note: the node is incremented one past, the last value // In Node we increment() then check for validity // hence the_new_value may be outside the valid range. // This can occur when an incremental sync happens, // hence, allow memento to copy the value as is. currentIndex_ = the_new_value; incr_state_change_no(); } bool RepeatString::operator==(const RepeatString& rhs) const { if (name_ != rhs.name_) { return false; } if (theStrings_ != rhs.theStrings_) { return false; } if (currentIndex_ != rhs.currentIndex_) { return false; } return true; } //======================================================================================= bool RepeatDay::compare(RepeatBase* rb) const { auto* rhs = dynamic_cast(rb); if (!rhs) { return false; } return operator==(*rhs); } std::string RepeatDay::dump() const { return toString(); } bool RepeatDay::operator==(const RepeatDay& rhs) const { if (step_ != rhs.step_) { return false; } return true; } // ========================================================================================= template void RepeatBase::serialize(Archive& ar) { ar(CEREAL_NVP(name_)); } template void RepeatDate::serialize(Archive& ar, std::uint32_t const version) { ar(cereal::base_class(this), CEREAL_NVP(start_), CEREAL_NVP(end_), CEREAL_NVP(delta_), CEREAL_NVP(value_)); } template void RepeatDateTime::serialize(Archive& ar, std::uint32_t const version) { ar(cereal::base_class(this), CEREAL_NVP(start_), CEREAL_NVP(end_), CEREAL_NVP(delta_), CEREAL_NVP(value_)); } template void RepeatInteger::serialize(Archive& ar, std::uint32_t const version) { ar(cereal::base_class(this), CEREAL_NVP(start_), CEREAL_NVP(end_), CEREAL_NVP(delta_), CEREAL_NVP(value_)); } template void RepeatEnumerated::serialize(Archive& ar, std::uint32_t const version) { ar(cereal::base_class(this), CEREAL_NVP(theEnums_), CEREAL_NVP(currentIndex_)); } template void RepeatDateList::serialize(Archive& ar, std::uint32_t const version) { ar(cereal::base_class(this), CEREAL_NVP(list_), CEREAL_NVP(currentIndex_)); } template void RepeatDateTimeList::serialize(Archive& ar, std::uint32_t const version) { ar(cereal::base_class(this), CEREAL_NVP(list_), CEREAL_NVP(currentIndex_)); } template void RepeatString::serialize(Archive& ar, std::uint32_t const version) { ar(cereal::base_class(this), CEREAL_NVP(theStrings_), CEREAL_NVP(currentIndex_)); } template void RepeatDay::serialize(Archive& ar, std::uint32_t const version) { ar(cereal::base_class(this), CEREAL_NVP(step_)); } template void Repeat::serialize(Archive& ar, std::uint32_t const version) { ar(CEREAL_NVP(type_)); } CEREAL_TEMPLATE_SPECIALIZE(RepeatBase); CEREAL_TEMPLATE_SPECIALIZE_V(RepeatDate); CEREAL_TEMPLATE_SPECIALIZE_V(RepeatDateTime); CEREAL_TEMPLATE_SPECIALIZE_V(RepeatDateList); CEREAL_TEMPLATE_SPECIALIZE_V(RepeatDateTimeList); CEREAL_TEMPLATE_SPECIALIZE_V(RepeatInteger); CEREAL_TEMPLATE_SPECIALIZE_V(RepeatEnumerated); CEREAL_TEMPLATE_SPECIALIZE_V(RepeatString); CEREAL_TEMPLATE_SPECIALIZE_V(RepeatDay); CEREAL_TEMPLATE_SPECIALIZE_V(Repeat); CEREAL_REGISTER_TYPE(RepeatDate) CEREAL_REGISTER_TYPE(RepeatDateTime) CEREAL_REGISTER_TYPE(RepeatDateList) CEREAL_REGISTER_TYPE(RepeatDateTimeList) CEREAL_REGISTER_TYPE(RepeatInteger) CEREAL_REGISTER_TYPE(RepeatEnumerated) CEREAL_REGISTER_TYPE(RepeatString) CEREAL_REGISTER_TYPE(RepeatDay) ecflow-5.19.0/libs/attribute/src/ecflow/attribute/RepeatRange.hpp0000664000175000017500000002261015250005516025164 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #ifndef ecflow_attribute_RepeatRange_HPP #define ecflow_attribute_RepeatRange_HPP #include "ecflow/attribute/RepeatAttr.hpp" #include "ecflow/core/Calendar.hpp" namespace ecf { template struct Range { explicit Range(const R& r) : r_(r) {} const R& r_; }; template <> struct Range { using size_type = std::size_t; using iterator = std::size_t; using value_type = int; explicit Range(const RepeatDay& r) : r_(r) {} iterator begin() const { return 0; } iterator end() const { return 0; } iterator current_index() const { return r_.index_or_value(); } value_type current_value() const { return r_.value(); } size_type size() const { return end() - begin(); } private: const RepeatDay& r_; }; template <> struct Range { using size_type = std::size_t; using iterator = std::size_t; using value_type = int; explicit Range(const RepeatDate& r) : r_(r) {} iterator begin() const { return 0; } iterator end() const { int i = ecf::CalendarDate(r_.start()).as_julian_day().value(); int j = ecf::CalendarDate(r_.end()).as_julian_day().value(); int s = r_.step(); int d = (j - i) + 1; return (d / s) + ((d % s == 0) ? 0 : 1); } iterator current_index() const { auto i = ecf::CalendarDate(r_.start()).as_julian_day().value(); auto s = r_.step(); auto v = ecf::CalendarDate(r_.value()).as_julian_day().value(); auto idx = (v - i) / s; return idx; } value_type current_value() const { return r_.value(); } value_type at(iterator i) const { int j = ecf::CalendarDate(r_.start()).as_julian_day().value(); return ecf::JulianDay(j + i * r_.step()).as_calendar_date().value(); } size_type size() const { return end() - begin(); } private: const RepeatDate& r_; }; template <> struct Range { using date_t = int; using size_type = std::size_t; using iterator = std::size_t; using value_type = date_t; explicit Range(const RepeatDateList& r) : r_(r) {} iterator begin() const { return 0; } iterator end() const { return r_.indexNum(); } iterator current_index() const { return r_.index_or_value(); } value_type current_value() const { return r_.value(); } value_type at(iterator i) const { return r_.value_at(i); } size_type size() const { return end() - begin(); } private: const RepeatDateList& r_; }; template <> struct Range { using size_type = std::size_t; using iterator = std::size_t; using value_type = Instant; explicit Range(const RepeatDateTimeList& r) : r_(r) {} iterator begin() const { return 0; } iterator end() const { return r_.indexNum(); } iterator current_index() const { return r_.index_or_value(); } value_type current_value() const { return r_.values().at(r_.index_or_value()); } value_type at(iterator i) const { return r_.values().at(i); } size_type size() const { return end() - begin(); } private: const RepeatDateTimeList& r_; }; template <> struct Range { using size_type = std::size_t; using iterator = std::size_t; using value_type = Instant; explicit Range(const RepeatDateTime& r) : r_(r) {} iterator begin() const { return 0; } iterator end() const { auto i = r_.start_instant(); auto j = r_.end_instant(); auto s = r_.step_duration(); auto d = (j - i); auto idx = d.as_seconds().count() / s.as_seconds().count(); auto last = i + Duration{std::chrono::seconds{s.as_seconds().count() * idx}}; if (last <= j) { idx++; } return idx; } iterator current_index() const { auto i = r_.start_instant(); auto s = r_.step_duration(); auto v = r_.value_instant(); auto idx = (v - i).as_seconds().count() / s.as_seconds().count(); return idx; } value_type current_value() const { return r_.value_instant(); } value_type at(iterator i) const { auto j = r_.start_instant(); auto s = r_.step_duration(); return j + Duration{std::chrono::seconds{s.as_seconds().count() * i}}; } size_type size() const { return end() - begin(); } private: const RepeatDateTime& r_; }; template <> struct Range { using size_type = std::size_t; using iterator = std::size_t; using value_type = int; explicit Range(const RepeatInteger& r) : r_(r) {} iterator begin() const { return 0; } iterator end() const { int i = r_.start(); int j = r_.end(); int s = r_.step(); int d = (j - i) + 1; auto idx = (d / s) + ((d % s == 0) ? 0 : 1); return idx; } iterator current_index() const { auto i = r_.start(); auto s = r_.step(); auto v = r_.value(); auto idx = (v - i) / s; return idx; } value_type current_value() const { return r_.value(); } value_type at(iterator i) const { return r_.start() + i * r_.step(); } size_type size() const { return end() - begin(); } private: const RepeatInteger& r_; }; template <> struct Range { using size_type = std::size_t; using iterator = std::size_t; using value_type = std::string; explicit Range(const RepeatEnumerated& r) : r_(r) {} iterator begin() const { return 0; } iterator end() const { return r_.end() + 1; } iterator current_index() const { return r_.index_or_value(); } value_type current_value() const { return r_.valueAsString(); } value_type at(iterator i) const { return r_.value_as_string(i); } size_type size() const { return end() - begin(); } private: const RepeatEnumerated& r_; }; template <> struct Range { using size_type = std::size_t; using iterator = std::size_t; using value_type = std::string; explicit Range(const RepeatString& r) : r_(r) {} iterator begin() const { return 0; } iterator end() const { return r_.end() + 1; } iterator current_index() const { return r_.index_or_value(); } value_type current_value() const { return r_.valueAsString(); } value_type at(iterator i) const { return r_.value_as_string(i); } size_type size() const { return end() - begin(); } private: const RepeatString& r_; }; template auto front(const Range& rng) { assert(rng.size() > 0); return rng.at(rng.begin()); } template auto back(const Range& rng) { assert(rng.size() > 0); return rng.at(rng.end() - 1); } template auto size(const Range& rng) { return rng.size(); } template bool empty(const Range& rng) { return size(rng) == 0; } struct Limits { /* * The limits of a Repeat is described by a 0-based range. * * - begin: the first index in the range (inclusive, meaning it is a valid index) * - end: the last index in the range (exclusive, meaning it is one past the last valid index) * * - current is expected to be in the interval [begin, end[ */ size_t begin; size_t end; size_t current; bool is_first() const { return current == begin; } bool is_last() const { return current + 1 >= end; } }; Limits limits_of(const RepeatBase* repeat) { if (auto r1 = dynamic_cast(repeat)) { Range rng(*r1); return {rng.begin(), rng.end(), rng.current_index()}; } else if (auto r2 = dynamic_cast(repeat)) { Range rng(*r2); return {rng.begin(), rng.end(), rng.current_index()}; } else if (auto r3 = dynamic_cast(repeat)) { Range rng(*r3); return {rng.begin(), rng.end(), rng.current_index()}; } else if (auto r3b = dynamic_cast(repeat)) { Range rng(*r3b); return {rng.begin(), rng.end(), rng.current_index()}; } else if (auto r4 = dynamic_cast(repeat)) { Range rng(*r4); return {rng.begin(), rng.end(), rng.current_index()}; } else if (auto r5 = dynamic_cast(repeat)) { Range rng(*r5); return {rng.begin(), rng.end(), rng.current_index()}; } else if (auto r6 = dynamic_cast(repeat)) { Range rng(*r6); return {rng.begin(), rng.end(), rng.current_index()}; } else if (auto r7 = dynamic_cast(repeat)) { Range rng(*r7); return {rng.begin(), rng.end(), rng.current_index()}; } assert(false); // Should never be reached! return {0, 1, 0}; } template auto make_range(const Repeat& repeat) { return Range{repeat.as()}; } } // namespace ecf #endif /* ecflow_attribute_RepeatRange_HPP */ ecflow-5.19.0/libs/attribute/src/ecflow/attribute/TimeAttr.cpp0000664000175000017500000001617615250005516024525 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #include "ecflow/attribute/TimeAttr.hpp" #include #include #include "ecflow/attribute/DateAttr.hpp" // Used in Why #include "ecflow/attribute/DayAttr.hpp" // Used in Why #include "ecflow/core/Calendar.hpp" #include "ecflow/core/Ecf.hpp" #include "ecflow/core/Log.hpp" #include "ecflow/core/Serialization.hpp" #include "ecflow/core/Str.hpp" namespace ecf { TimeAttr::TimeAttr(const std::string& str) { if (str.empty()) { throw std::runtime_error("Time::Time: empty string passed"); } std::vector tokens; ecf::algorithm::split_at(tokens, str); if (tokens.empty()) { throw std::runtime_error("Time::Time: incorrect time string ?"); } size_t index = 0; ts_ = TimeSeries::create(index, tokens, false /*parse_state*/); } void TimeAttr::calendarChanged(const ecf::Calendar& c) { // ensure this called first , since we need always update for relative duration. ECFLOW-1648 if (ts_.calendarChanged(c)) { state_change_no_ = Ecf::incr_state_change_no(); } // log(Log::DBG,"TimeAttr::calendarChanged(1) " + dump()); // ECFLOW-1648 if (free_) { return; } // For a time series, we rely on the re queue to reset makeFree if (isFree(c)) { setFree(); } // log(Log::DBG,"TimeAttr::calendarChanged(2) " + dump()); // ECFLOW-1648 } void TimeAttr::resetRelativeDuration() { if (ts_.resetRelativeDuration()) { state_change_no_ = Ecf::incr_state_change_no(); } } std::string TimeAttr::name() const { std::string ret; write(ret); ts_.write_state_for_gui(ret, free_); return ret; } std::string TimeAttr::toString() const { std::string ret; write(ret); return ret; } void TimeAttr::write(std::string& ret) const { ret += "time "; ts_.write(ret); } std::string TimeAttr::dump() const { return MESSAGE("time " << (free_ ? "(free) " : "(holding) ") << ts_.dump()); } bool TimeAttr::operator==(const TimeAttr& rhs) const { if (free_ != rhs.free_) { return false; } return ts_.operator==(rhs.ts_); } bool TimeAttr::structureEquals(const TimeAttr& rhs) const { return ts_.structureEquals(rhs.ts_); } bool TimeAttr::isFree(const ecf::Calendar& calendar) const { // The FreeDepCmd can be used to free the time, if (free_) { return true; } return is_free(calendar); } bool TimeAttr::is_free(const ecf::Calendar& calendar) const { return ts_.isFree(calendar); } void TimeAttr::setFree() { free_ = true; state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO std::cout << "TimeAttr::setFree()\n"; #endif } void TimeAttr::clearFree() { free_ = false; state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO std::cout << "TimeAttr::clearFree()\n"; #endif } void TimeAttr::miss_next_time_slot() { ts_.miss_next_time_slot(); state_change_no_ = Ecf::incr_state_change_no(); } bool TimeAttr::why(const ecf::Calendar& c, const std::vector& days, const std::vector& dates, std::string& theReasonWhy) const { if (isFree(c)) { return false; } theReasonWhy += "is time "; if (!days.empty()) { theReasonWhy += "and day "; } if (!dates.empty()) { theReasonWhy += "and date "; } theReasonWhy += "dependent"; // Check to see if time has expired; if not, then report why if (ts_.is_valid()) { // This can apply to single and series boost::posix_time::time_duration calendar_time = ts_.duration(c); if (calendar_time < ts_.start().duration()) { ts_.why(c, theReasonWhy); return true; } // calendar_time >= ts_.start().duration() if (ts_.hasIncrement()) { if (calendar_time < ts_.finish().duration()) { ts_.why(c, theReasonWhy); return true; } } // calendar_time >= ts_.start().duration() && calendar_time >= ts_.finish().duration() // past the end of time slot, hence this should not hold job generation, } // the time has expired theReasonWhy += " ( '"; theReasonWhy += toString(); theReasonWhy += "' has expired,"; // take into account, user can use run/force complete to miss time slots bool do_a_requeue = ts_.requeueable(c); if (do_a_requeue) { TimeSlot the_next_time_slot = ts_.compute_next_time_slot(c); if (the_next_time_slot.isNULL() || !ts_.hasIncrement()) { theReasonWhy += " *re-queue* to run at this time"; } else { theReasonWhy += " *re-queue* to run at "; theReasonWhy += the_next_time_slot.toString(); } } else { if (ts_.relative()) { theReasonWhy += " please *re-queue*, to reset the relative duration"; } else { boost::gregorian::date the_min_next_date; if (!days.empty() || !dates.empty()) { for (const auto& day : days) { auto the_next_matching_date = day.next_matching_date(c); if (the_min_next_date.is_special()) { the_min_next_date = the_next_matching_date; } if (the_next_matching_date < the_min_next_date) { the_min_next_date = the_next_matching_date; } } for (const auto& date : dates) { auto the_next_matching_date = date.next_matching_date(c); if (the_min_next_date.is_special()) { the_min_next_date = the_next_matching_date; } if (the_next_matching_date < the_min_next_date) { the_min_next_date = the_next_matching_date; } } theReasonWhy += " next run at "; } else { auto one_day = boost::gregorian::date_duration(1); the_min_next_date = c.date(); // today's date the_min_next_date += one_day; // add one day, so its in the future theReasonWhy += " next run tomorrow at "; } theReasonWhy += ts_.start().toString(); theReasonWhy += " "; theReasonWhy += to_simple_string(the_min_next_date); } } theReasonWhy += " )"; return true; } template void TimeAttr::serialize(Archive& ar) { ar(CEREAL_NVP(ts_)); // Only persisted for testing, see usage of isSetFree() CEREAL_OPTIONAL_NVP(ar, free_, [this]() { return free_; }); // conditionally save } CEREAL_TEMPLATE_SPECIALIZE(TimeAttr); } // namespace ecf ecflow-5.19.0/libs/attribute/src/ecflow/attribute/AutoArchiveAttr.hpp0000664000175000017500000000403715250005516026037 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #ifndef ecflow_attribute_AutoArchiveAttr_HPP #define ecflow_attribute_AutoArchiveAttr_HPP #include #include "ecflow/core/NState.hpp" #include "ecflow/core/TimeSlot.hpp" namespace ecf { class Calendar; } // namespace ecf namespace ecf { // Use compiler , destructor, assignment, copy constructor class AutoArchiveAttr { public: AutoArchiveAttr() = default; AutoArchiveAttr(int hour, int minute, bool relative, bool idle = false) : time_(hour, minute), relative_(relative), idle_(idle) {} AutoArchiveAttr(const TimeSlot& ts, bool relative, bool idle = false) : time_(ts), relative_(relative), idle_(idle) {} explicit AutoArchiveAttr(int days, bool idle = false) : time_(TimeSlot(days * 24, 0)), days_(true), idle_(idle) {} bool operator==(const AutoArchiveAttr& rhs) const; bool operator<(const AutoArchiveAttr& rhs) const { return time_ < rhs.time(); } bool isFree(const ecf::Calendar&, const std::pair& last_state_and_change_duration) const; std::string toString() const; const TimeSlot& time() const { return time_; } bool relative() const { return relative_; } bool days() const { return days_; } bool idle() const { return idle_; } public: void write(std::string&) const; private: TimeSlot time_; bool relative_{true}; bool days_{false}; bool idle_{false}; friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const version); }; } // namespace ecf #endif /* ecflow_attribute_AutoArchiveAttr_HPP */ ecflow-5.19.0/libs/attribute/src/ecflow/attribute/NodeAttr.cpp0000664000175000017500000003436615250005516024515 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #include "ecflow/attribute/NodeAttr.hpp" #include #include #include "ecflow/core/Converter.hpp" #include "ecflow/core/Ecf.hpp" #include "ecflow/core/Message.hpp" #include "ecflow/core/Serialization.hpp" #include "ecflow/core/Str.hpp" using namespace ecf; const std::string& Event::SET() { static const std::string SET = "set"; return SET; } const std::string& Event::CLEAR() { static const std::string CLEAR = "clear"; return CLEAR; } const Event& Event::EMPTY() { static const Event EVENT = Event(); return EVENT; } const Meter& Meter::EMPTY() { static const Meter METER = Meter(); return METER; } const Label& Label::EMPTY() { static const Label LABEL = Label(); return LABEL; } //////////////////////////////////////////////////////////////////////////////////////////// Event::Event(int number, const std::string& eventName, bool iv, bool check_name) : n_(eventName), number_(number), v_(iv), iv_(iv) { if (!eventName.empty() && check_name) { std::string msg; if (!ecf::algorithm::is_valid_name(eventName, msg)) { throw std::runtime_error("Event::Event: Invalid event name : " + msg); } } } Event::Event(const std::string& eventName, bool iv) : n_(eventName), v_(iv), iv_(iv) { if (eventName.empty()) { throw std::runtime_error("Event::Event: Invalid event name : name must be specified if no number supplied"); } // If the eventName is an integer, then treat it as such, by setting number_ and clearing n_ // This was added after migration failed, since *python* api allowed: // ta.add_event(1); // ta.add_event("1"); // and when we called ecflow_client --migrate/--get it generated // event 1 // event 1 // which then did *not* load. // // Test for numeric, and then casting, is ****faster***** than relying on exception alone if (eventName.find_first_of(ecf::string_constants::numeric_chars) == 0) { try { number_ = ecf::convert_to(eventName); n_.clear(); return; } catch (const ecf::bad_conversion&) { // cast failed, a real string, carry on } } std::string msg; if (!ecf::algorithm::is_valid_name(eventName, msg)) { throw std::runtime_error("Event::Event: Invalid event name : " + msg); } } Event Event::make_from_value(const std::string& name, const std::string& value) { // value is expected to be either "set" or "clear" if (!isValidState(value)) { throw std::runtime_error("Event::Event: Invalid state : " + value); } return Event(name, value == Event::SET()); } void Event::set_value(bool b) { v_ = b; state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO std::cout << "Event::set_value\n"; #endif } std::string Event::name_or_number() const { if (n_.empty()) { return MESSAGE(number_); } return n_; } bool Event::operator<(const Event& rhs) const { if (!n_.empty() && !rhs.name().empty()) { return n_ < rhs.name(); } if (n_.empty() && rhs.name().empty()) { return number_ < rhs.number(); } return name_or_number() < rhs.name_or_number(); } bool Event::operator==(const Event& rhs) const { if (v_ != rhs.v_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "v_ != rhs.v_ (v_:" << v_ << " rhs.v_:" << rhs.v_ << ") " << toString() << "\n"; } #endif return false; } if (number_ != rhs.number_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "number_ != rhs.number_ (number_:" << number_ << " rhs.number_:" << rhs.number_ << ") " << toString() << "\n"; } #endif return false; } if (n_ != rhs.n_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "n_ != rhs.n_ (n_:" << n_ << " rhs.n_:" << rhs.n_ << ") " << toString() << "\n"; } #endif return false; } if (iv_ != rhs.iv_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "iv_ != rhs.iv_ (iv_:" << iv_ << " rhs.iv_:" << rhs.iv_ << ") " << toString() << "\n"; } #endif return false; } return true; } bool Event::compare(const Event& rhs) const { if (number_ != rhs.number_) { return false; } if (n_ != rhs.n_) { return false; } return true; } std::string Event::toString() const { std::string ret; write(ret); return ret; } void Event::write(std::string& ret) const { ret += "event "; if (number_ == std::numeric_limits::max()) { ret += n_; } else { ret += ecf::convert_to(number_); ret += " "; ret += n_; } if (iv_) { ret += " set"; // initial value } } std::string Event::dump() const { return MESSAGE(toString() << " value(" << v_ << ") used(" << used_ << ")"); } bool Event::isValidState(const std::string& state) { return state == Event::SET() || state == Event::CLEAR(); } //////////////////////////////////////////////////////////////////////////////////////////// Meter::Meter(const std::string& name, int min, int max, int colorChange, int value, bool check) : min_(min), max_(max), v_(value), cc_(colorChange), n_(name) { if (check) { if (!ecf::algorithm::is_valid_name(name)) { throw std::runtime_error("Meter::Meter: Invalid Meter name: " + name); } } if (min > max) { throw std::out_of_range("Meter::Meter: Invalid Meter(name,min,max,color_change) : min must be less than max"); } if (colorChange == std::numeric_limits::max()) { cc_ = max_; } if (value == std::numeric_limits::max()) { v_ = min_; } if (cc_ < min || cc_ > max) { throw std::out_of_range(MESSAGE("Meter::Meter: Invalid Meter(name,min,max,color_change) color_change(" << cc_ << ") must be between min(" << min_ << ") and max(" << max_ << ")")); } } Meter Meter::make_from_value(const std::string& name, const std::string& value) { // value is expected to be of the form "min,max,value", where min, max and value are integers std::vector tokens; ecf::algorithm::split_at(tokens, value, ","); if (tokens.size() != 3) { throw std::runtime_error( MESSAGE("Meter::make_from_value: Expect three comma-separated values, but found: '" << value << "'")); } try { auto min = ecf::convert_to(tokens[0]); auto max = ecf::convert_to(tokens[1]); auto value = ecf::convert_to(tokens[2]); return Meter(name, min, max, max, value, false); } catch (const ecf::bad_conversion&) { throw std::runtime_error(MESSAGE("Meter::make_from_value: Expect three comma-separated values, but found: (" << tokens[0] << ", " << tokens[1] << ", " << tokens[2] << ")")); } } void Meter::set_value(int v) { if (!isValidValue(v)) { throw std::runtime_error(MESSAGE("Meter::set_value(int): The meter(" << n_ << ") value must be in the range[" << min() << "->" << max() << "] but found '" << v << "'")); } v_ = v; state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO std::cout << "Meter::set_value\n"; #endif } bool Meter::operator==(const Meter& rhs) const { if (v_ != rhs.v_) { return false; } if (min_ != rhs.min_) { return false; } if (max_ != rhs.max_) { return false; } if (cc_ != rhs.cc_) { return false; } if (n_ != rhs.n_) { return false; } return true; } std::string Meter::toString() const { std::string ret; write(ret); return ret; } void Meter::write(std::string& ret) const { ret += "meter "; ret += n_; ret += " "; ret += ecf::convert_to(min_); ret += " "; ret += ecf::convert_to(max_); ret += " "; ret += ecf::convert_to(cc_); } std::string Meter::dump() const { return MESSAGE("meter " << n_ << " min(" << min_ << ") max (" << max_ << ") colorChange(" << cc_ << ") value(" << v_ << ") used(" << used_ << ")"); } ///////////////////////////////////////////////////////////////////////////////////////////// Label::Label(const std::string& name, const std::string& value, const std::string& new_value, bool check_name) : n_(name), v_(value), new_v_(new_value) { if (check_name && !ecf::algorithm::is_valid_name(n_)) { throw std::runtime_error(MESSAGE("Label::Label: Invalid Label name :" << n_)); } } std::string Label::toString() const { // parsing always STRIPS the quotes, hence add them back std::string ret; ret.reserve(n_.size() + v_.size() + 10); write(ret); return ret; } void Label::write(std::string& ret) const { // parsing always STRIPS the quotes, hence add them back ret += "label "; ret += n_; ret += " \""; if (v_.find("\n") == std::string::npos) { ret += v_; } else { // replace \n, otherwise re-parse will fail std::string value = v_; ecf::algorithm::replace_all(value, "\n", "\\n"); ret += value; } ret += "\""; } std::string Label::dump() const { return MESSAGE(toString() << " : \"" << new_v_ << "\""); } void Label::set_new_value(const std::string& l) { new_v_ = l; state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO std::cout << "Label::set_new_value\n"; #endif } void Label::reset() { new_v_.clear(); state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO std::cout << "Label::reset()\n"; #endif } void Label::parse(const std::string& line, std::vector& lineTokens, bool parse_state) { parse(line, lineTokens, parse_state, n_, v_, new_v_); } void Label::parse(const std::string& line, std::vector& lineTokens, bool parse_state, std::string& the_name, std::string& the_value, std::string& the_new_value) { size_t line_token_size = lineTokens.size(); if (line_token_size < 3) { throw std::runtime_error("Label::parse: Invalid label :" + line); } the_name = lineTokens[1]; // parsing will always STRIP single or double quotes, print will add double quotes // label simple_label 'ecgems' if (line_token_size == 3) { ecf::algorithm::remove_double_quotes(lineTokens[2]); ecf::algorithm::remove_single_quotes(lineTokens[2]); the_value = lineTokens[2]; if (the_value.find("\\n") != std::string::npos) { ecf::algorithm::replace_all(the_value, "\\n", "\n"); } } else { // label complex_label "smsfetch -F %ECF_FILES% -I %ECF_INCLUDE%" # fred // label simple_label "fred" # "smsfetch -F %ECF_FILES% -I %ECF_INCLUDE%" std::string value; value.reserve(line.size()); for (size_t i = 2; i < line_token_size; ++i) { if (lineTokens[i].at(0) == '#') { break; } if (i != 2) { value += " "; } value += lineTokens[i]; } ecf::algorithm::remove_double_quotes(value); ecf::algorithm::remove_single_quotes(value); the_value = value; if (the_value.find("\\n") != std::string::npos) { ecf::algorithm::replace_all(the_value, "\\n", "\n"); } // state if (parse_state) { // label name "value" # "new value" bool comment_fnd = false; size_t first_quote_after_comment = 0; size_t last_quote_after_comment = 0; for (size_t i = line.size() - 1; i > 0; i--) { if (line[i] == '#') { comment_fnd = true; break; } if (line[i] == '"') { if (last_quote_after_comment == 0) { last_quote_after_comment = i; } first_quote_after_comment = i; } } if (comment_fnd && first_quote_after_comment != last_quote_after_comment) { std::string new_value = line.substr(first_quote_after_comment + 1, last_quote_after_comment - first_quote_after_comment - 1); // std::cout << "new label = '" << new_value << "'\n"; the_new_value = new_value; if (the_new_value.find("\\n") != std::string::npos) { ecf::algorithm::replace_all(the_new_value, "\\n", "\n"); } } } } } template void Label::serialize(Archive& ar) { ar(CEREAL_NVP(n_)); CEREAL_OPTIONAL_NVP(ar, v_, [this]() { return !v_.empty(); }); CEREAL_OPTIONAL_NVP(ar, new_v_, [this]() { return !new_v_.empty(); }); // conditionally save } template void Event::serialize(Archive& ar) { CEREAL_OPTIONAL_NVP(ar, n_, [this]() { return !n_.empty(); }); CEREAL_OPTIONAL_NVP(ar, number_, [this]() { return number_ != std::numeric_limits::max(); }); CEREAL_OPTIONAL_NVP(ar, v_, [this]() { return v_; }); CEREAL_OPTIONAL_NVP(ar, iv_, [this]() { return iv_; }); } template void Meter::serialize(Archive& ar) { ar(CEREAL_NVP(min_), CEREAL_NVP(max_), CEREAL_NVP(v_), CEREAL_NVP(n_), CEREAL_NVP(cc_)); } CEREAL_TEMPLATE_SPECIALIZE(Label); CEREAL_TEMPLATE_SPECIALIZE(Event); CEREAL_TEMPLATE_SPECIALIZE(Meter); ecflow-5.19.0/libs/attribute/src/ecflow/attribute/ZombieAttr.hpp0000664000175000017500000000614215250005516025051 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #ifndef ecflow_attribute_ZombieAttr_HPP #define ecflow_attribute_ZombieAttr_HPP #include #include "ecflow/core/Child.hpp" #include "ecflow/core/ZombieCtrlAction.hpp" namespace cereal { class access; } // Class ZombieAttr: // Use compiler , generated destructor, assignment, copy constructor // ZombieAttr does *not* have any changeable state class ZombieAttr { public: ZombieAttr(ecf::Child::ZombieType t, const std::vector& c, ecf::ZombieCtrlAction a, int zombie_lifetime = 0); ZombieAttr() = default; bool operator==(const ZombieAttr& rhs) const; bool empty() const { return zombie_type_ == ecf::Child::NOT_SET; } ecf::Child::ZombieType zombie_type() const { return zombie_type_; } ecf::ZombieCtrlAction action() const { return action_; } int zombie_lifetime() const { return zombie_lifetime_; } const std::vector& child_cmds() const { return child_cmds_; } std::vector::const_iterator child_begin() const { return child_cmds_.begin(); } // for python std::vector::const_iterator child_end() const { return child_cmds_.end(); } // for python std::string toString() const; bool fob(ecf::Child::CmdType) const; bool fail(ecf::Child::CmdType) const; bool adopt(ecf::Child::CmdType) const; bool block(ecf::Child::CmdType) const; bool remove(ecf::Child::CmdType) const; bool kill(ecf::Child::CmdType) const; /// Create from a string. Will throw std::runtime_error of parse errors /// expects ::child_cmds:zombie_lifetime static ZombieAttr create(const std::string& str); // Added to support return by reference static const ZombieAttr& EMPTY(); // Provide the default behaviour static ZombieAttr get_default_attr(ecf::Child::ZombieType); static int default_ecf_zombie_life_time() { return 3600; } static int default_user_zombie_life_time() { return 300; } static int default_path_zombie_life_time() { return 900; } static int minimum_zombie_life_time() { return 60; } public: void write(std::string&) const; private: std::vector child_cmds_; // init, event, meter,label, complete ecf::Child::ZombieType zombie_type_{ecf::Child::NOT_SET}; // User,path or ecf ecf::ZombieCtrlAction action_{ecf::ZombieCtrlAction::BLOCK}; // fob, fail,remove, adopt, block, kill int zombie_lifetime_{0}; // How long zombie lives in server friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const version); }; #endif /* ecflow_attribute_ZombieAttr_HPP */ ecflow-5.19.0/libs/attribute/src/ecflow/attribute/NodeAttr.hpp0000664000175000017500000001531615250005516024514 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #ifndef ecflow_attribute_NodeAttr_HPP #define ecflow_attribute_NodeAttr_HPP #include // for std::numeric_limits::max() #include #include namespace cereal { class access; } //////////////////////////////////////////////////////////////////////////////////////// // Class Label: // Use compiler , generated destructor, assignment, copy constructor class Label { public: Label() = default; Label(const std::string& name, const std::string& value, const std::string& new_value = "", bool check_name = true); const std::string& name() const { return n_; } const std::string& value() const { return v_; } const std::string& new_value() const { return new_v_; } void set_new_value(const std::string& new_label); void reset(); bool empty() const { return n_.empty(); } // The state_change_no is never reset. Must be incremented if it can affect equality unsigned int state_change_no() const { return state_change_no_; } friend bool operator==(const Label& lhs, const Label& rhs) { return lhs.n_ == rhs.n_ && lhs.v_ == rhs.v_ && lhs.new_v_ == rhs.new_v_; } friend bool operator!=(const Label& lhs, const Label& rhs) { return !(lhs == rhs); } bool operator<(const Label& rhs) const { return n_ < rhs.name(); } std::string toString() const; std::string dump() const; void parse(const std::string& line, std::vector& lineTokens, bool parse_state); static void parse(const std::string& line, std::vector& lineTokens, bool parse_state, std::string&, std::string&, std::string&); static const Label& EMPTY(); // Added to support return by reference public: void write(std::string&) const; private: std::string n_; std::string v_; std::string new_v_; unsigned int state_change_no_{0}; // *not* persisted, only used on server side friend class cereal::access; template void serialize(Archive& ar); }; // Class Event: // events with the number 007 are the same as 7. // Use compiler , generated destructor, assignment, copy constructor // // Do not use -1, to represent that no number was specified, as on // AIX portable binary archive cannot cope with this // use std::numeric_limits::max() class Event { public: explicit Event(int number, const std::string& eventName = "", bool initial_val = false, bool check_name = true); explicit Event(const std::string& eventName, bool initial_val = false); Event() = default; static Event make_from_value(const std::string& name, const std::string& value); std::string name_or_number() const; // if name present return, else return number const std::string& name() const { return n_; } bool value() const { return v_; } void reset() { set_value(iv_); } bool empty() const { return (n_.empty() && number_ == std::numeric_limits::max()); } void set_initial_value(bool iv) { iv_ = iv; } bool initial_value() const { return iv_; } int number() const { return number_; } bool operator==(const Event& rhs) const; bool operator<(const Event& rhs) const; bool compare(const Event& rhs) const; // two events the same if name/number the same, ignores state void set_value(bool b); // updates state_change_no_ bool usedInTrigger() const { return used_; } void usedInTrigger(bool b) { used_ = b; } unsigned int state_change_no() const { return state_change_no_; } std::string toString() const; std::string dump() const; static bool isValidState(const std::string&); // return true for "set" | "clear" static const std::string& SET(); static const std::string& CLEAR(); static const Event& EMPTY(); // Added to support return by reference public: void write(std::string&) const; private: std::string n_; int number_{std::numeric_limits::max()}; unsigned int state_change_no_{0}; // *not* persisted, only used on server side bool v_{false}; bool iv_{false}; // initial value ECFLOW-1526 bool used_{false}; // used by the simulator not persisted friend class cereal::access; template void serialize(Archive& ar); }; // Class Meter: // Use compiler , generated destructor, assignment, copy constructor // For this class we do not check the value member for the equality functionality // Can have negative min/max however max >= min, and color change should be in the // range min-max class Meter { public: Meter(const std::string& name, int min, int max, int colorChange = std::numeric_limits::max(), int value = std::numeric_limits::max(), bool check = true); Meter() = default; static Meter make_from_value(const std::string& name, const std::string& value); void reset() { set_value(min_); } void set_value(int v); // can throw std::runtime_error if out of range bool empty() const { return n_.empty(); } const std::string& name() const { return n_; } int value() const { return v_; } int min() const { return min_; } int max() const { return max_; } int colorChange() const { return cc_; } // The state_change_no is never reset. Must be incremented if it can affect equality unsigned int state_change_no() const { return state_change_no_; } bool operator==(const Meter& rhs) const; bool operator<(const Meter& rhs) const { return n_ < rhs.name(); } bool usedInTrigger() const { return used_; } void usedInTrigger(bool b) { used_ = b; } std::string toString() const; std::string dump() const; static const Meter& EMPTY(); // Added to support return by reference public: void write(std::string&) const; private: bool isValidValue(int v) const { return (v >= min_ && v <= max_); } int min_{0}; int max_{0}; int v_{0}; // value int cc_{0}; // Colour change, used by gui ? std::string n_; // name unsigned int state_change_no_{0}; // *not* persisted, only used on server side bool used_{false}; // used by the simulator not persisted friend class cereal::access; template void serialize(Archive& ar); }; #endif /* ecflow_attribute_NodeAttr_HPP */ ecflow-5.19.0/libs/attribute/src/ecflow/attribute/TimeAttr.hpp0000664000175000017500000001366615250005516024533 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #ifndef ecflow_attribute_TimeAttr_HPP #define ecflow_attribute_TimeAttr_HPP /// /// \brief isFree is called when a node is queued. If it returns true, Task can be submitted /// checkForRequeue: is called when a node has completed, and need to determine if it should run again. /// These are different/orthogonal concerns. /// There is a *separate* issue of whether nodes should be queued when a node is *manually* /// a/ Set complete /// b/ Runs and then completes /// /// For a *single* time slot we cannot requeue. /// Hence, we checkForRequeue that takes as parameters max/min time slots, so we **treat** /// Multiple single slot as a series. /// /// isFree:hhhhhhhhhhhhhhhhh /// Begin: /// V /// checkForRequeue:rrrrrrrrrrrrrrrrhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh 10:00 /// checkForRequeue:rrrrrrrrrrrrrrrrrrrrrrrrrrrrrhhhhhhhhhhhhhhhhhhhhhhh 11:00 /// checkForRequeue:rrrrrrrrrrrrrrrrrrrrrrrrrrrrrhhhhhhhhhhhhhhhhhhhhhhh for both 10:00 and 11:000 together /// isFree:hhhhhhhhhhhhhhhhffffffffffffffffffffffffffffffffffff *once* free we stay free (single slot *only*) /// begin : | | /// V | | /// Time ======================0============0==============0============= /// 10:00 11:00 Midnight /// /// isFree:hhhhhhhhhhhhhhhhhh /// V /// CheckForRequeue:rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrhhhhhhhhhhhhhhhhhhhhhhhhhhhhh /// isFree:hhhhhhhFhhhhFhhhhFhhhhFhhhhFhhhhFhhhhhhhhhhhhhhhhhhhhhhhhhhhh /// V | | | | | | | /// Time ================o====|====|====|====|====0========0==================== /// 10:00 1 2 3 4 15:00 Midnight /// /// If the job starts at 10:00 but takes more than 1 hour, then it will miss the 11:00 slot /// and will have to start at 12:00 /// #include "ecflow/core/TimeSeries.hpp" class DateAttr; // Used in Why class DayAttr; // Used in Why namespace ecf { class Calendar; } // namespace ecf namespace ecf { // Use compiler , destructor, assignment, copy constructor, class TimeAttr { public: explicit TimeAttr(const std::string&); TimeAttr() = default; TimeAttr(int hour, int minute, bool relative = false) : ts_(hour, minute, relative) {} explicit TimeAttr(const TimeSlot& t, bool relative = false) : ts_(t, relative) {} explicit TimeAttr(const TimeSeries& ts) : ts_(ts) {} TimeAttr(const TimeSlot& start, const TimeSlot& finish, const TimeSlot& incr, bool relative = false) : ts_(start, finish, incr, relative) {} bool operator==(const TimeAttr& rhs) const; bool operator<(const TimeAttr& rhs) const { return ts_ < rhs.ts_; } bool structureEquals(const TimeAttr& rhs) const; /// This can set attribute as free, once free its stays free, until re-queue/reset void calendarChanged(const ecf::Calendar& c); // can set attribute free void resetRelativeDuration(); void reset_only() { clearFree(); ts_.reset_only(); } void reset(const ecf::Calendar& c) { clearFree(); ts_.reset(c); } // updates state_change_no_ void requeue(const ecf::Calendar& c, bool reset_next_time_slot = true) { clearFree(); ts_.requeue(c, reset_next_time_slot); } // updates state_change_no_ void miss_next_time_slot(); // updates state_change_no_ void setFree(); // ensures that isFree() always returns true, updates state_change_no_ bool isSetFree() const { return free_; } bool isFree(const ecf::Calendar&) const; bool checkForRequeue(const ecf::Calendar& c, const TimeSlot& the_min, const TimeSlot& the_max, bool cmd_context = false) const { return ts_.checkForRequeue(c, the_min, the_max, cmd_context); } void min_max_time_slots(TimeSlot& the_min, TimeSlot& the_max) const { ts_.min_max_time_slots(the_min, the_max); } bool why(const ecf::Calendar&, const std::vector&, const std::vector&, std::string& theReasonWhy) const; bool checkInvariants(std::string& errormsg) const { return ts_.checkInvariants(errormsg); } // The state_change_no is never reset. Must be incremented if it can affect equality // Note: changes in state of ts_, i.e. affect the equality operator (used in test) // must be captured. i.e. things like relative duration & next_time_slot are // reported by the Why command, & hence need to be synced. unsigned int state_change_no() const { return state_change_no_; } std::string name() const; // for display/gui std::string toString() const; void write(std::string&) const; std::string dump() const; // access const TimeSeries& time_series() const { return ts_; } private: void clearFree(); // resets the free flag, updates state_change_no_ bool is_free(const ecf::Calendar&) const; // ignores free_ private: TimeSeries ts_; unsigned int state_change_no_{0}; // *not* persisted, only used on server side bool free_{false}; friend class cereal::access; template void serialize(Archive& ar); }; } // namespace ecf #endif /* ecflow_attribute_TimeAttr_HPP */ ecflow-5.19.0/libs/attribute/src/ecflow/attribute/Zombie.cpp0000664000175000017500000003244215250005516024213 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #include "ecflow/attribute/Zombie.hpp" #include "ecflow/core/Calendar.hpp" #include "ecflow/core/Converter.hpp" #include "ecflow/core/Serialization.hpp" using namespace ecf; // support return by reference const Zombie& Zombie::EMPTY() { static const Zombie ZOMBIE = Zombie(); return ZOMBIE; } Zombie& Zombie::EMPTY_() { static Zombie ZOMBIE = Zombie(); return ZOMBIE; } Zombie::Zombie(ecf::Child::ZombieType zombie_type, ecf::Child::CmdType cmd, const ZombieAttr& attr, const std::string& pathToTask, const std::string& jobsPassword, const std::string& process_or_remote_id, int try_no, const std::string& host, const std::string& user_cmd) : try_no_(try_no), zombie_type_(zombie_type), last_child_cmd_(cmd), path_to_task_(pathToTask), jobs_password_(jobsPassword), process_or_remote_id_(process_or_remote_id), user_cmd_(user_cmd), host_(host), attr_(attr), creation_time_(Calendar::second_clock_time()) { } Zombie::Zombie() = default; bool Zombie::operator==(const Zombie& rhs) const { // for python interface only if (zombie_type_ != rhs.zombie_type_) { return false; } if (user_action_ != rhs.user_action_) { return false; } if (try_no_ != rhs.try_no_) { return false; } if (duration_ != rhs.duration_) { return false; } if (calls_ != rhs.calls_) { return false; } if (last_child_cmd_ != rhs.last_child_cmd_) { return false; } if (path_to_task_ != rhs.path_to_task_) { return false; } if (jobs_password_ != rhs.jobs_password_) { return false; } if (process_or_remote_id_ != rhs.process_or_remote_id_) { return false; } if (user_cmd_ != rhs.user_cmd_) { return false; } if (host_ != rhs.host_) { return false; } if (user_action_set_ != rhs.user_action_set_) { return false; } if (!(attr_ == rhs.attr_)) { return false; } return true; } std::string Zombie::to_string() const { // for python interface only std::vector vec; vec.push_back(*this); return Zombie::pretty_print(vec, 1); } std::string Zombie::type_str() const { return Child::to_string(zombie_type_); } ZombieCtrlAction Zombie::user_action() const { // User action needs to take into account, last child command and setting on attr_ if (fob()) { return ZombieCtrlAction::FOB; } else if (block()) { return ZombieCtrlAction::BLOCK; } else if (fail()) { return ZombieCtrlAction::FAIL; } else if (remove()) { return ZombieCtrlAction::REMOVE; } else if (kill()) { return ZombieCtrlAction::KILL; } else if (adopt()) { return ZombieCtrlAction::ADOPT; } return ZombieCtrlAction::BLOCK; // the default action } std::string Zombie::user_action_str() const { std::string ret; if (manual_user_action()) { ret = "manual-"; } else { ret = "auto-"; } ret += ecf::to_string(user_action()); return ret; } /// accessors bool Zombie::fob() const { if (user_action_set_) { return user_action_ == ZombieCtrlAction::FOB; } return attr_.fob(last_child_cmd_); } bool Zombie::fail() const { if (user_action_set_) { return user_action_ == ZombieCtrlAction::FAIL; } return attr_.fail(last_child_cmd_); } bool Zombie::adopt() const { if (user_action_set_) { return user_action_ == ZombieCtrlAction::ADOPT; } return attr_.adopt(last_child_cmd_); } bool Zombie::block() const { if (user_action_set_) { return user_action_ == ZombieCtrlAction::BLOCK; } return attr_.block(last_child_cmd_); } bool Zombie::remove() const { if (user_action_set_) { return user_action_ == ZombieCtrlAction::REMOVE; } return attr_.remove(last_child_cmd_); } bool Zombie::kill() const { if (user_action_set_) { return user_action_ == ZombieCtrlAction::KILL; } return attr_.kill(last_child_cmd_); } int Zombie::allowed_age() const { return attr_.zombie_lifetime(); } void Zombie::set_fob() { user_action_ = ZombieCtrlAction::FOB; user_action_set_ = true; } void Zombie::set_fail() { user_action_ = ZombieCtrlAction::FAIL; user_action_set_ = true; } void Zombie::set_adopt() { user_action_ = ZombieCtrlAction::ADOPT; user_action_set_ = true; } void Zombie::set_block() { user_action_ = ZombieCtrlAction::BLOCK; user_action_set_ = true; } void Zombie::set_kill() { user_action_ = ZombieCtrlAction::KILL; user_action_set_ = true; } std::ostream& operator<<(std::ostream& os, const Zombie& z) { os << z.path_to_task() << " "; os << z.type_str() << " "; os << z.duration() << " "; os << z.jobs_password() << " "; os << z.process_or_remote_id() << " "; os << z.try_no() << " "; os << "calls(" << z.calls() << ") "; os << z.user_action_str(); os << " "; os << Child::to_string(z.last_child_cmd()); return os; } std::string Zombie::explanation() const { std::string ecf_pid_expl = "PID miss-match, password matches. Job scheduled twice. Check submitter"; std::string ecf_pid_passwd_expl = "Both PID and password miss-match. Re-queue & submit of active job?"; std::string ecf_passwd_expl = "Password miss-match, PID matches, system has re-cycled PID or hacked job file?"; std::string ecf_expl = "Two init commands or task complete or aborted but receives another child cmd"; std::string ecf_user = "Created by user action("; std::string ecf_path = "Task not found. Nodes replaced whilst jobs were running"; std::string exp; switch (zombie_type_) { case Child::USER: exp = ecf_user; exp += user_cmd_; exp += ")"; break; case Child::PATH: exp = ecf_path; break; case Child::ECF: exp = ecf_expl; break; case Child::ECF_PID: exp = ecf_pid_expl; break; case Child::ECF_PID_PASSWD: exp = ecf_pid_passwd_expl; break; case Child::ECF_PASSWD: exp = ecf_passwd_expl; break; case Child::NOT_SET: break; } return exp; } std::string Zombie::pretty_print(const std::vector& zombies, int indent) { std::vector list; pretty_print(zombies, list, indent); std::ostringstream ss; for (const auto& i : list) { ss << i << "\n"; } return ss.str(); } void Zombie::pretty_print(const std::vector& zombies, std::vector& list, int indent) { std::string path("task-path"); std::string type("type"); std::string password("password"); std::string rid("pid"); std::string duration("age(s)"); std::string calls("calls"); std::string try_no("try_no"); std::string user_action("action"); std::string child_type("child"); std::string host("host"); std::string explanation("explanation"); size_t path_width = path.size(); size_t type_width = type.size(); size_t duration_width = duration.size(); size_t password_width = password.size(); size_t tryno_width = try_no.size(); size_t rid_width = rid.size(); size_t user_action_width = user_action.size(); // max of FOB,FAIL,ADOPT,BLOCK,REMOVE + (manual- | auto- ) size_t child_type_width = child_type.size(); size_t calls_width = calls.size(); size_t host_width = host.size(); size_t explanation_width = explanation.size(); std::string ecf_pid_expl = "PID miss-match, password matches. Job scheduled twice. Check submitter"; std::string ecf_pid_passwd_expl = "Both PID and password miss-match. Re-queue & submit of active job?"; std::string ecf_passwd_expl = "Password miss-match, PID matches, system has re-cycled PID or hacked job file?"; std::string ecf_expl = "Two init commands or task complete or aborted but receives another child cmd"; std::string ecf_user = "Created by user action("; std::string ecf_path = "Task not found. Nodes replaced whilst jobs were running"; std::string exp; for (const Zombie& z : zombies) { path_width = std::max(path_width, z.path_to_task().size()); type_width = std::max(type_width, z.type_str().size()); password_width = std::max(password_width, z.jobs_password().size()); rid_width = std::max(rid_width, z.process_or_remote_id().size()); std::string no_of_calls = ecf::convert_to(z.calls()); calls_width = std::max(calls_width, no_of_calls.size()); host_width = std::max(host_width, z.host().size()); std::string try_no_int = ecf::convert_to(z.try_no()); tryno_width = std::max(tryno_width, try_no_int.size()); child_type_width = std::max(child_type_width, Child::to_string(z.last_child_cmd()).size()); user_action_width = std::max(user_action_width, z.user_action_str().size()); switch (z.type()) { case Child::USER: exp = ecf_user; exp += z.user_cmd(); exp += ")"; break; case Child::PATH: exp = ecf_path; break; case Child::ECF: exp = ecf_expl; break; case Child::ECF_PID: exp = ecf_pid_expl; break; case Child::ECF_PID_PASSWD: exp = ecf_pid_passwd_expl; break; case Child::ECF_PASSWD: exp = ecf_passwd_expl; break; case Child::NOT_SET: break; } explanation_width = std::max(explanation_width, exp.size()); } { std::ostringstream ss; if (indent != 0) { for (int i = 0; i < indent; i++) { ss << " "; } } ss << std::left << std::setw(path_width) << path << " " << std::setw(type_width) << type << " " << std::setw(duration_width) << duration << " " << std::setw(password_width) << password << " " << std::setw(rid_width) << rid << " " << std::setw(tryno_width) << try_no << " " << std::setw(user_action_width) << user_action << " " << std::setw(child_type_width) << child_type << " " << std::setw(calls_width) << calls << " " << std::setw(host_width) << host << " " << std::setw(explanation_width) << explanation; list.push_back(ss.str()); } for (const Zombie& z : zombies) { std::ostringstream ss; if (indent != 0) { for (int i = 0; i < indent; i++) { ss << " "; } } switch (z.type()) { case Child::USER: exp = ecf_user; exp += z.user_cmd(); exp += ")"; break; case Child::PATH: exp = ecf_path; break; case Child::ECF: exp = ecf_expl; break; case Child::ECF_PID: exp = ecf_pid_expl; break; case Child::ECF_PID_PASSWD: exp = ecf_pid_passwd_expl; break; case Child::ECF_PASSWD: exp = ecf_passwd_expl; break; case Child::NOT_SET: break; } ss << std::left << std::setw(path_width) << z.path_to_task() << " " << std::setw(type_width) << z.type_str() << " " << std::setw(duration_width) << z.duration() << " " << std::setw(password_width) << z.jobs_password() << " " << std::setw(rid_width) << z.process_or_remote_id() << " " << std::setw(tryno_width) << z.try_no() << " " << std::setw(user_action_width) << z.user_action_str() << " " << std::setw(child_type_width) << Child::to_string(z.last_child_cmd()) << " " << std::setw(calls_width) << z.calls() << " " << std::setw(host_width) << z.host() << " " << std::setw(explanation_width) << exp; list.push_back(ss.str()); } } template void Zombie::serialize(Archive& ar, std::uint32_t const version) { ar(CEREAL_NVP(user_action_), CEREAL_NVP(try_no_), CEREAL_NVP(duration_), CEREAL_NVP(calls_), CEREAL_NVP(zombie_type_), CEREAL_NVP(last_child_cmd_), CEREAL_NVP(path_to_task_), CEREAL_NVP(jobs_password_), CEREAL_NVP(process_or_remote_id_), CEREAL_NVP(user_cmd_), CEREAL_NVP(host_), CEREAL_NVP(attr_), CEREAL_NVP(user_action_set_)); } CEREAL_TEMPLATE_SPECIALIZE_V(Zombie); ecflow-5.19.0/libs/attribute/src/ecflow/attribute/VerifyAttr.cpp0000664000175000017500000000342315250005516025062 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #include "ecflow/attribute/VerifyAttr.hpp" #include #include "ecflow/core/Converter.hpp" #include "ecflow/core/Ecf.hpp" #include "ecflow/core/Message.hpp" #include "ecflow/core/NState.hpp" #include "ecflow/core/Serialization.hpp" #include "ecflow/core/Str.hpp" using namespace ecf; bool VerifyAttr::operator==(const VerifyAttr& rhs) const { if (state_ != rhs.state_) { return false; } if (expected_ != rhs.expected_) { return false; } return true; } void VerifyAttr::incrementActual() { actual_++; state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO std::cout << "VerifyAttr::incrementActual()\n"; #endif } void VerifyAttr::reset() { actual_ = 0; state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO std::cout << "VerifyAttr::reset()\n"; #endif } std::string VerifyAttr::toString() const { return MESSAGE("verify " << NState::toString(state_) << ecf::string_constants::colon << expected_); } std::string VerifyAttr::dump() const { return MESSAGE("verify " << NState::toString(state_) << ecf::string_constants::colon << expected_ << " actual(" << actual_ << ")"); } template void VerifyAttr::serialize(Archive& ar) { ar(CEREAL_NVP(state_), CEREAL_NVP(expected_), CEREAL_NVP(actual_)); } CEREAL_TEMPLATE_SPECIALIZE(VerifyAttr); ecflow-5.19.0/libs/attribute/src/ecflow/attribute/Variable.cpp0000664000175000017500000000374015250005516024512 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #include "ecflow/attribute/Variable.hpp" #include #include #include "ecflow/core/Message.hpp" #include "ecflow/core/Serialization.hpp" using namespace ecf; bool Variable::operator==(const Variable& rhs) const { if (v_ != rhs.v_) { return false; } if (n_ != rhs.n_) { return false; } return true; } std::string Variable::toString() const { std::string ret; ret.reserve(n_.size() + v_.size() + 8); write(ret); return ret; } std::string Variable::dump() const { return MESSAGE(toString() << " value(" << value() << ")"); } void Variable::write(std::string& ret) const { ret += "edit "; ret += n_; ret += " '"; if (v_.find("\n") == std::string::npos) { ret += v_; } else { // replace \n, otherwise re-parse will fail std::string value = v_; ecf::algorithm::replace_all(value, "\n", "\\n"); ret += value; } ret += "'"; } const Variable& Variable::EMPTY() { static const Variable VARIABLE = Variable(); return VARIABLE; } template void Variable::serialize(Archive& ar) { ar(CEREAL_NVP(n_), CEREAL_NVP(v_)); } CEREAL_TEMPLATE_SPECIALIZE(Variable); void VariableMap::set_value(const std::string& value) { for (auto& variable : variables_) { variable.set_value(value); } } Variable& VariableMap::operator[](const std::string& name) { if (auto found = index_.find(name); found != std::end(index_)) { return variables_[found->second]; } throw std::runtime_error(MESSAGE("Variable '" << name << "' not found")); } ecflow-5.19.0/libs/attribute/src/ecflow/attribute/Zombie.hpp0000664000175000017500000001665615250005516024231 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #ifndef ecflow_attribute_Zombie_HPP #define ecflow_attribute_Zombie_HPP /// /// \brief Holds the zombie structure /// /// WE have 3 structures: /// Jobs(path,password,process_id) /// Complications ONLY init command provides process_id. /// Task(path,password,process_id) /// Zombie(path,password,process_id) /// Issues: /// At the extreme we can have *2* Jobs running at one go. Typically, user commands /// that have a force parameter will create 'user' zombies. /// ** the command can be invoked at any time, i.e. we could invoke /// ** the user command, whilst the task is in SUBMITTED state /// ** This will go through and create user zombie. However, at this stage we have *NO* process_id /// ** since the job has not started. Zombie(path,password) /// When the Task init command is called, we get given a process_id. /// ** HOWEVER ** need to determine if this is from the real child cmd or from the zombie /// ** We search zombie list, and compare zombies, by path and password /// ** if a match is found, we update the zombie id. /// /// IMPORTANT:: Automated test: /// If a child command provides just path and password, this is *NOT* /// enough information to disambiguate a zombie from a real job. /// To work around this the job file has been updated to add ECF_RID=$$ /// /// IMPORTANT:: For command line interface we just have the task path. /// i,e we do not want to expose password, and user will not easily /// know the process or remote id. Hence, we will make do with the /// task path. We just find the first zombie, and act up on it /// #include "ecflow/attribute/ZombieAttr.hpp" #include "ecflow/core/Child.hpp" #include "ecflow/core/Chrono.hpp" /// Use default copy constructor ,assignment operator and destructor class Zombie { public: Zombie(ecf::Child::ZombieType zombie_type, // The kind of zombie ecf::Child::CmdType child_cmd, // The Child command, that lead to this zombie const ZombieAttr& attr, // will hold Action if set on node tree const std::string& pathToTask, // from child ipc const std::string& jobsPassword, // from child ipc const std::string& process_or_remote_id, // from child ipc int try_no, const std::string& host, // The host where the client was invoked const std::string& user_cmd = "" // user cmd that created this zombie ); Zombie(); bool operator==(const Zombie& rhs) const; // for python only std::string to_string() const; // for python only /// accessors // distinguish between manual and automatic user action. manual take precedence bool manual_user_action() const { return user_action_set_; } bool fob() const; bool fail() const; bool adopt() const; bool block() const; bool remove() const; bool kill() const; ecf::Child::ZombieType type() const { return zombie_type_; } ecf::Child::CmdType last_child_cmd() const { return last_child_cmd_; } const ZombieAttr& attr() const { return attr_; } int calls() const { return calls_; } std::string type_str() const; const std::string& jobs_password() const { return jobs_password_; } const std::string& path_to_task() const { return path_to_task_; } const std::string& process_or_remote_id() const { return process_or_remote_id_; } const std::string& user_cmd() const { return user_cmd_; } const std::string& host() const { return host_; } int try_no() const { return try_no_; } int duration() const { return duration_; } ecf::ZombieCtrlAction user_action() const; std::string user_action_str() const; std::string explanation() const; const boost::posix_time::ptime& creation_time() const { return creation_time_; } bool empty() const { return path_to_task_.empty(); } /// returns in seconds the age the zombie is allowed to live /// Server typically checks every 60 seconds, hence this is lowest valid value that has effect int allowed_age() const; /// mutators void set_attr(const ZombieAttr& attr) { attr_ = attr; } void set_process_or_remote_id(const std::string& s) { process_or_remote_id_ = s; } void set_last_child_cmd(ecf::Child::CmdType c) { last_child_cmd_ = c; } // user cmd that created this zombie void set_host(const std::string& h) { host_ = h; } // The host where client/zombie was created void set_type(ecf::Child::ZombieType zt) { zombie_type_ = zt; } /// User action must take precedence over Zombie attribute settings on node tree /// /// Note: user_action to remove zombie is immediate hence no need to for set_remove() /// Whereas for the other we want to store the action so that when next child /// command communicate with the server, the action is applied. void set_duration(int d) { duration_ = d; } void set_fob(); void set_fail(); void set_adopt(); void set_block(); void set_kill(); void increment_calls() { calls_++; } // write to standard out a title and list of zombies static std::string pretty_print(const std::vector& zombies, int indent = 0); static void pretty_print(const std::vector& zombies, std::vector& list, int indent = 0); // MISC: // Added to support return by reference static const Zombie& EMPTY(); static Zombie& EMPTY_(); private: ecf::ZombieCtrlAction user_action_{ecf::ZombieCtrlAction::BLOCK}; // [ fob, fail, remove, adopt, block, kill ] int try_no_{0}; // task try number, set on construction int duration_{0}; // How long zombie been alive int calls_{0}; // Number of times we have communicated with server. ecf::Child::ZombieType zombie_type_{ecf::Child::USER}; // [ ecf, ecf_pid, ecf_pid_passwd, ecf_passwd, path, user ] ecf::Child::CmdType last_child_cmd_{ ecf::Child::INIT}; // [ init | event | meter | label | wait | queue | abort | complete ] std::string path_to_task_; // set on construction std::string jobs_password_; // set on construction std::string process_or_remote_id_; // set on construction std::string user_cmd_; // user cmd that created this zombie, empty otherwise std::string host_; // The client host name ZombieAttr attr_; // Default or attribute obtained from node tree. boost::posix_time::ptime creation_time_; // When zombie was created. Needed to control lifetime bool user_action_set_{false}; // Differentiate manual from automated, response, manual take precedence friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const version); }; std::ostream& operator<<(std::ostream& os, const Zombie&); #endif /* ecflow_attribute_Zombie_HPP */ ecflow-5.19.0/libs/attribute/src/ecflow/attribute/GenericAttr.cpp0000664000175000017500000000352615250005516025176 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #include "ecflow/attribute/GenericAttr.hpp" #include #include "ecflow/core/Serialization.hpp" #include "ecflow/core/Str.hpp" using namespace ecf; const GenericAttr& GenericAttr::EMPTY() { static const GenericAttr GENERICATTR = GenericAttr(); return GENERICATTR; } GenericAttr::GenericAttr(const std::string& name, const std::vector& values) : name_(name), values_(values) { std::string msg; if (!ecf::algorithm::is_valid_name(name, msg)) { throw std::runtime_error("GenericAttr::GenericAttr : Invalid generic name : " + msg); } } GenericAttr::GenericAttr(const std::string& name) : name_(name) { std::string msg; if (!ecf::algorithm::is_valid_name(name, msg)) { throw std::runtime_error("GenericAttr::GenericAttr : Invalid generic name : " + msg); } } bool GenericAttr::operator==(const GenericAttr& rhs) const { if (name_ != rhs.name_) { return false; } if (values_ != rhs.values_) { return false; } return true; } std::string GenericAttr::to_string() const { std::string ret; write(ret); return ret; } void GenericAttr::write(std::string& ret) const { ret += "generic "; ret += name_; for (const auto& value : values_) { ret += " "; ret += value; } } template void GenericAttr::serialize(Archive& ar) { ar(CEREAL_NVP(name_), CEREAL_NVP(values_)); } CEREAL_TEMPLATE_SPECIALIZE(GenericAttr); ecflow-5.19.0/libs/attribute/src/ecflow/attribute/ClockAttr.cpp0000664000175000017500000001401615250005516024651 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #include "ecflow/attribute/ClockAttr.hpp" #include "ecflow/attribute/DateAttr.hpp" #include "ecflow/core/Calendar.hpp" #include "ecflow/core/Converter.hpp" #include "ecflow/core/Ecf.hpp" #include "ecflow/core/Serialization.hpp" using namespace ecf; //========================================================================================== ClockAttr::ClockAttr(const boost::posix_time::ptime& time, bool hybrid, bool positiveGain) : state_change_no_(Ecf::incr_state_change_no()), hybrid_(hybrid), positiveGain_(positiveGain) { auto theDate = time.date(); day_ = theDate.day(); month_ = theDate.month(); year_ = theDate.year(); tm t = to_tm(time); gain_ = t.tm_hour * 3600 + t.tm_min * 60 + t.tm_sec; } ClockAttr::ClockAttr(int day, int month, int year, bool hybrid) : day_(day), month_(month), year_(year), state_change_no_(Ecf::incr_state_change_no()), hybrid_(hybrid) { // Will throw std::out_of_range exception DateAttr::checkDate(day, month, year, false /* disable wild cards */); } ClockAttr::ClockAttr(bool hybrid) : state_change_no_(Ecf::incr_state_change_no()), hybrid_(hybrid) { } std::string ClockAttr::toString() const { std::string ret; write(ret); return ret; } void ClockAttr::write(std::string& ss) const { if (!end_clock_) { ss += "clock "; if (hybrid_) { ss += "hybrid "; } else { ss += "real "; } } else { ss += "endclock "; } if (day_ != 0) { ss += ecf::convert_to(day_); ss += "."; ss += ecf::convert_to(month_); ss += "."; ss += ecf::convert_to(year_); ss += " "; } if (gain_ != 0) { if (positiveGain_) { ss += "+"; } ss += ecf::convert_to(gain_); } } bool ClockAttr::operator==(const ClockAttr& rhs) const { if (hybrid_ != rhs.hybrid_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "hybrid_ != rhs.hybrid_\n"; } #endif return false; } if (day_ != rhs.day_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "day(" << day_ << ") != rhs.day(" << rhs.day_ << "\n"; } #endif return false; } if (month_ != rhs.month_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "month_(" << month_ << ") != rhs.month_(" << rhs.month_ << "\n"; } #endif return false; } if (year_ != rhs.year_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "year_(" << year_ << ") != rhs.year_(" << rhs.year_ << "\n"; } #endif return false; } if (gain_ != rhs.gain_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "gain_(" << gain_ << ") != rhs.gain_(" << rhs.gain_ << "\n"; } #endif return false; } if (positiveGain_ != rhs.positiveGain_) { #ifdef DEBUG if (Ecf::debug_equality()) { std::cout << "positiveGain_(" << positiveGain_ << ") != rhs.positiveGain_(" << rhs.positiveGain_ << ")\n"; } #endif return false; } return true; } void ClockAttr::date(int day, int month, int year) { // Will throw std::out_of_range exception DateAttr::checkDate(day, month, year, false /* disable wild cards */); day_ = day; month_ = month; year_ = year; state_change_no_ = Ecf::incr_state_change_no(); } void ClockAttr::set_gain(int hour, int min, bool positiveGain) { positiveGain_ = positiveGain; gain_ = (hour * 3600) + min * 60; state_change_no_ = Ecf::incr_state_change_no(); } void ClockAttr::set_gain_in_seconds(long theGain, bool positiveGain) { positiveGain_ = positiveGain; gain_ = theGain; state_change_no_ = Ecf::incr_state_change_no(); } void ClockAttr::hybrid(bool f) { hybrid_ = f; state_change_no_ = Ecf::incr_state_change_no(); } void ClockAttr::sync() { // When begin_calendar() is called we will sync with computer clock. positiveGain_ = false; gain_ = 0; day_ = 0; month_ = 0; year_ = 0; state_change_no_ = Ecf::incr_state_change_no(); } void ClockAttr::init_calendar(ecf::Calendar& calendar) { Calendar::Clock_t clockType = (hybrid_) ? Calendar::HYBRID : Calendar::REAL; calendar.init(clockType); } void ClockAttr::begin_calendar(ecf::Calendar& calendar) const { calendar.begin(ptime()); } boost::posix_time::ptime ClockAttr::ptime() const { if (day_ != 0) { // Use the date given. i.e., we start from midnight on the given day + gain. auto theDate = boost::gregorian::date(year_, month_, day_); return {theDate, boost::posix_time::seconds(gain_)}; } // Get the local time, second level resolution, based on the time zone settings of the computer. auto the_time = boost::posix_time::ptime(Calendar::second_clock_time()); the_time += boost::posix_time::seconds(gain_); return the_time; } template void ClockAttr::serialize(Archive& ar, std::uint32_t const version) { ar(CEREAL_NVP(hybrid_)); CEREAL_OPTIONAL_NVP(ar, positiveGain_, [this]() { return positiveGain_; }); CEREAL_OPTIONAL_NVP(ar, gain_, [this]() { return gain_ != 0; }); CEREAL_OPTIONAL_NVP(ar, day_, [this]() { return day_ != 0; }); CEREAL_OPTIONAL_NVP(ar, month_, [this]() { return month_ != 0; }); CEREAL_OPTIONAL_NVP(ar, year_, [this]() { return year_ != 0; }); } CEREAL_TEMPLATE_SPECIALIZE_V(ClockAttr); ecflow-5.19.0/libs/attribute/src/ecflow/attribute/TodayAttr.cpp0000664000175000017500000001560215250005516024700 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #include "ecflow/attribute/TodayAttr.hpp" #include #include #include "ecflow/attribute/DateAttr.hpp" // Used in Why #include "ecflow/attribute/DayAttr.hpp" // Used in Why #include "ecflow/core/Calendar.hpp" #include "ecflow/core/Ecf.hpp" #include "ecflow/core/Serialization.hpp" #include "ecflow/core/Str.hpp" namespace ecf { TodayAttr::TodayAttr(const std::string& str) { if (str.empty()) { throw std::runtime_error("Today::Today: empty string passed"); } std::vector tokens; ecf::algorithm::split_at(tokens, str); if (tokens.empty()) { throw std::runtime_error("Today::Today: incorrect time string ?"); } size_t index = 0; ts_ = TimeSeries::create(index, tokens, false /*parse_state*/); } std::string TodayAttr::name() const { std::string ret; write(ret); ts_.write_state_for_gui(ret, free_); return ret; } std::string TodayAttr::toString() const { std::string ret; write(ret); return ret; } void TodayAttr::write(std::string& ret) const { ret += "today "; ts_.write(ret); } bool TodayAttr::operator==(const TodayAttr& rhs) const { if (free_ != rhs.free_) { return false; } return ts_.operator==(rhs.ts_); } bool TodayAttr::structureEquals(const TodayAttr& rhs) const { return ts_.structureEquals(rhs.ts_); } void TodayAttr::miss_next_time_slot() { ts_.miss_next_time_slot(); state_change_no_ = Ecf::incr_state_change_no(); } void TodayAttr::setFree() { free_ = true; state_change_no_ = Ecf::incr_state_change_no(); } void TodayAttr::clearFree() { free_ = false; state_change_no_ = Ecf::incr_state_change_no(); } void TodayAttr::calendarChanged(const ecf::Calendar& c) { // ensure this called first , since we need always update for relative duration ECFLOW-1648 if (ts_.calendarChanged(c)) { state_change_no_ = Ecf::incr_state_change_no(); } if (free_) { return; } // For a time series, we rely on the re queue to reset makeFree if (isFree(c)) { setFree(); } } void TodayAttr::resetRelativeDuration() { if (ts_.resetRelativeDuration()) { state_change_no_ = Ecf::incr_state_change_no(); } } bool TodayAttr::isFree(const ecf::Calendar& calendar) const { // The FreeDepCmd can be used to free the today, if (free_) { return true; } return is_free(calendar); } bool TodayAttr::is_free(const ecf::Calendar& calendar) const { if (!ts_.hasIncrement()) { if (ts_.duration(calendar) > ts_.start().duration()) { return true; } } // For time series(/range), this is already handle by ts_ // If timer expired return false. otherwise must match one time slot in the range/series return ts_.isFree(calendar); } bool TodayAttr::why(const ecf::Calendar& c, const std::vector& days, const std::vector& dates, std::string& theReasonWhy) const { if (isFree(c)) { return false; } theReasonWhy += "is today "; if (!days.empty()) { theReasonWhy += "and day "; } if (!dates.empty()) { theReasonWhy += "and date "; } theReasonWhy += "dependent"; // Check to see if time has expired; if not, then report why if (ts_.is_valid()) { // This can apply to single and series auto calendar_time = ts_.duration(c); if (calendar_time < ts_.start().duration()) { ts_.why(c, theReasonWhy); return true; } // calendar_time >= ts_.start().duration() if (ts_.hasIncrement()) { if (calendar_time < ts_.finish().duration()) { ts_.why(c, theReasonWhy); return true; } } // calendar_time >= ts_.start().duration() && calendar_time >= ts_.finish().duration() // past the end of time slot, hence this should not hold job generation, } // the today has expired, theReasonWhy += " ( '"; theReasonWhy += toString(); theReasonWhy += "' has expired,"; // take into account, user can use run/force complete to miss time slots bool do_a_requeue = ts_.requeueable(c); if (do_a_requeue) { TimeSlot the_next_time_slot = ts_.compute_next_time_slot(c); if (the_next_time_slot.isNULL() || !ts_.hasIncrement()) { theReasonWhy += " *re-queue* to run at this time"; } else { theReasonWhy += " *re-queue* to run at"; theReasonWhy += the_next_time_slot.toString(); } } else { if (ts_.relative()) { theReasonWhy += " please *re-queue*, to reset the relative duration"; } else { boost::gregorian::date the_min_next_date; if (!days.empty() || !dates.empty()) { for (auto day : days) { auto the_next_matching_date = day.next_matching_date(c); if (the_min_next_date.is_special()) { the_min_next_date = the_next_matching_date; } if (the_next_matching_date < the_min_next_date) { the_min_next_date = the_next_matching_date; } } for (const auto& i : dates) { auto the_next_matching_date = i.next_matching_date(c); if (the_min_next_date.is_special()) { the_min_next_date = the_next_matching_date; } if (the_next_matching_date < the_min_next_date) { the_min_next_date = the_next_matching_date; } } theReasonWhy += " next run at "; } else { auto one_day = boost::gregorian::date_duration(1); the_min_next_date = c.date(); // today's date the_min_next_date += one_day; // add one day, so its in the future theReasonWhy += " next run tomorrow at "; } theReasonWhy += ts_.start().toString(); theReasonWhy += " "; theReasonWhy += to_simple_string(the_min_next_date); } } theReasonWhy += " )"; return true; } template void TodayAttr::serialize(Archive& ar) { ar(CEREAL_NVP(ts_)); CEREAL_OPTIONAL_NVP(ar, free_, [this]() { return free_; }); // conditionally save } CEREAL_TEMPLATE_SPECIALIZE(TodayAttr); } // namespace ecf ecflow-5.19.0/libs/attribute/src/ecflow/attribute/CronAttr.hpp0000664000175000017500000001407615250005516024532 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #ifndef ecflow_attribute_CronAttr_HPP #define ecflow_attribute_CronAttr_HPP #include "ecflow/core/TimeSeries.hpp" namespace ecf { class Calendar; } // namespace ecf namespace ecf { // ***************************************************************** // A cron attribute with a single time slot is repeated indefinitely // ****************************************************************** // Use compiler , destructor, assignment, copy constructor, class CronAttr { public: CronAttr(); explicit CronAttr(const std::string& time_series); CronAttr(const TimeSlot& s, const TimeSlot& f, const TimeSlot& i) : timeSeries_(s, f, i) {} explicit CronAttr(const TimeSeries& ts) : timeSeries_(ts) {} CronAttr(int h, int m, bool relative = false) : timeSeries_(h, m, relative) {} bool operator==(const CronAttr& rhs) const; bool operator<(const CronAttr& rhs) const { return timeSeries_ < rhs.timeSeries_; } bool structureEquals(const CronAttr& rhs) const; void addWeekDays(const std::vector& w); void add_last_week_days_of_month(const std::vector& w); void addDaysOfMonth(const std::vector& d); void add_last_day_of_month() { last_day_of_month_ = true; } void addMonths(const std::vector& m); void addTimeSeries(const TimeSlot& s, const TimeSlot& f, const TimeSlot& i) { timeSeries_ = TimeSeries(s, f, i); } void addTimeSeries(const TimeSeries& ts) { timeSeries_ = ts; } void add_time_series(int h, int m, bool relative = false) { timeSeries_ = TimeSeries(h, m, relative); } // Once a cron is free its stays free, until re-queue is called void calendarChanged(const ecf::Calendar& c); // can set attribute free void resetRelativeDuration(); void reset_only(); void reset(const ecf::Calendar& c); void requeue(const ecf::Calendar& c, bool reset_next_time_slot = true); void miss_next_time_slot(); void setFree(); // ensures that isFree() always returns true bool isSetFree() const { return free_; } bool isFree(const ecf::Calendar&) const; bool checkForRequeue(const ecf::Calendar&) const; bool validForHybrid(const ecf::Calendar&) const; bool why(const ecf::Calendar&, std::string& theReasonWhy) const; bool last_day_of_the_month() const { return last_day_of_month_; } // The state_change_no is never reset. Must be incremented if it can affect equality // Note: changes in state of timeSeries_, i.e. affect the equality operator (used in test) // must be captured. This means that things like relative duration & next_time_slot are // reported by the Why command and, hence, need to be synced. unsigned int state_change_no() const { return state_change_no_; } bool checkInvariants(std::string& errormsg) const; // Query: const TimeSeries& time() const { return timeSeries_; } const TimeSeries& time_series() const { return timeSeries_; } bool last_day_of_month() const { return last_day_of_month_; } const std::vector& week_days() const { return weekDays_; } const std::vector& last_week_days_of_month() const { return last_week_days_of_month_; } const std::vector& days_of_month() const { return daysOfMonth_; } const std::vector& months() const { return months_; } std::string name() const; // for display/gui std::string toString() const; std::string dump() const; /// parse the line tokens and create a cron attribute. Can throw std::runtime_error /// The index parameter allows us to parse: /// cron -w 0,1 10:00 // index = 1 /// -w 0,1 10:00 // index = 0 /// Expect to parse: /// cron 23:00 # run every day at 23:00 /// cron 10:00 20:00 01:00 # run every hour between 10am and 8pm /// cron -w 0,1 10:00 # run every sunday and monday at 10am /// cron -w 0,1L 10:00 # run every sunday and last monday of the month at 10am /// cron -d 10,11,12 12:00 # run 10th, 11th and 12th of each month at noon /// cron -d 10,L 12:00 # run 10th, and last day of the month at noon /// cron -m 1,2,3 12:00 # run on Jan,Feb and March every day at noon. /// cron -w 0 -m 5,6,7,8 10:00 20:00 01:00 # run every sunday, between May-Aug, every hour between 10am and 8pm /// cron -d 1,L -m 5,6,7,8 10:00 20:00 01:00 # run on first and last day of month, between May-Aug, every hour /// between 10am and 8pm static void parse(CronAttr&, const std::vector& lineTokens, size_t index, bool parse_state = false); static CronAttr create(const std::string& cronString); public: void write(std::string&) const; private: void clearFree(); // resets the free flag bool is_day_of_week_day_of_month_and_month_free(const ecf::Calendar&) const; bool week_day_matches(int) const; bool last_week_day_of_month_matches(const ecf::Calendar&) const; bool day_of_month_matches(int, const ecf::Calendar& c) const; bool month_matches(int) const; boost::gregorian::date next_date(const ecf::Calendar& calendar) const; TimeSeries timeSeries_; std::vector weekDays_; std::vector last_week_days_of_month_; std::vector daysOfMonth_; std::vector months_; unsigned int w_{0}; // NOT implemented, weekday nearest the given day of the month 15W unsigned int state_change_no_{0}; // *not* persisted, only used on server side bool last_day_of_month_{false}; bool free_{false}; // persisted for use by why() on client side friend class cereal::access; template void serialize(Archive& ar, std::uint32_t const version); }; } // namespace ecf #endif /* ecflow_attribute_CronAttr_HPP */ ecflow-5.19.0/libs/attribute/src/ecflow/attribute/Variable.hpp0000664000175000017500000002251615250005516024521 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #ifndef ecflow_attribute_Variable_HPP #define ecflow_attribute_Variable_HPP #include #include #include #include "ecflow/core/Message.hpp" #include "ecflow/core/Str.hpp" namespace cereal { class access; } /// /// @brief Represents an ecFlow variable as a name/value pair. /// /// Variables are lightweight string attributes used throughout the node tree. /// The name is subject to the same validation rules as ecFlow identifiers. /// class Variable { public: /// /// @brief Constructs an empty variable with an empty name and value. /// Variable() = default; /// /// @brief Constructs a variable with the given name and value, without validation. /// /// @param[in] name The variable name. /// @param[in] value The variable value. /// Variable(const std::string& name, const std::string& value) : n_(name), v_(value) {} /// /// @brief Constructs a variable after validating the given name. /// /// @param[in] name The variable name; must be a valid ecFlow identifier. /// @param[in] value The variable value. /// @return A new Variable with the given name and value. /// @throws std::runtime_error if @p name is not a valid variable name. /// @see is_valid_variable_name /// static Variable new_variable(const std::string& name, const std::string& value) { // Perform name validation std::string msg; if (!is_valid_variable_name(name, msg)) { throw std::runtime_error(MESSAGE("Variable::new_variable: " << msg)); } // Create the variable return Variable(name, value); } /// /// @brief Checks whether the variable name is empty. /// /// @return `true` if the variable name is empty, otherwise `false`. /// inline bool empty() const { return n_.empty(); } /// /// @brief Returns the variable name. /// /// @return A reference to the variable name. /// inline const std::string& name() const { return n_; } /// /// @brief Sets the variable name after validating it. /// /// @param[in] name The new variable name; must be a valid ecFlow identifier. /// @throws std::runtime_error if @p name is not a valid variable name. /// @see is_valid_variable_name /// inline void set_name(const std::string& name) { // Perform name validation std::string msg; if (!ecf::algorithm::is_valid_name(name, msg)) { throw std::runtime_error(MESSAGE("Variable::set_name: " << msg)); } // Set the variable name n_ = name; } /// /// @brief Returns the current 'string' variable value. /// /// @return The variable value. /// inline const std::string& value() const { return v_; } /// /// @brief Returns the current variable value, converted to an R value. /// /// @tparam R The target conversion type. /// @return The variable value, or a default value if the value fails the conversion to R. /// /// @note This is defined to establish a generic contract for specific conversions to R. No implementation provided. /// @note The default value, returned when the value cannot be converted to R, is specific to R itself. /// template R value() const; /// /// @brief Sets the variable value. /// /// @param[in] value The new variable value. /// inline void set_value(const std::string& value) { v_ = value; } /// /// @brief Returns a mutable reference to the variable value. /// /// @return A reference to the variable value. /// inline std::string& value_by_ref() { return v_; } /// /// @brief Compares two variables for equality. /// /// @param[in] rhs The variable to compare with. /// @return `true` if both name and value are equal, otherwise `false`. /// bool operator==(const Variable& rhs) const; /// /// @brief Compares two variables by name. /// /// @param[in] rhs The variable to compare with. /// @return `true` if this variable's name is lexicographically less than @p rhs's name. /// inline bool operator<(const Variable& rhs) const { return n_ < rhs.name(); } /// /// @brief Returns an ecFlow-defs representation of this variable. /// /// @return A string in the form `edit ''`. /// std::string toString() const; /// /// @brief Returns a debug string including the integer value. /// /// @return A string combining toString() and value(). /// std::string dump() const; /// /// @brief Appends an ecFlow-defs representation of this variable to the given buffer. /// /// @param[out] ret The buffer to append to. /// void write(std::string& ret) const; /// /// @brief Checks whether the given string is a valid ecFlow variable name. /// /// @param[in] name The name to validate. /// @param[out] msg Diagnostic message populated when validation fails. /// @return `true` if @p name is a valid variable name, otherwise `false`. /// inline static bool is_valid_variable_name(const std::string& name, std::string& msg) { return ecf::algorithm::is_valid_name(name, msg); } /// /// @brief Returns a shared empty variable, suitable for return-by-reference fallback. /// /// @return A reference to a process-wide empty Variable instance. /// static const Variable& EMPTY(); private: std::string n_; std::string v_; friend class cereal::access; template void serialize(Archive& ar); }; /// /// @brief Parses the variable value as an integer. /// /// @return The integer value, or `0` if the value cannot be converted. /// template <> inline int Variable::value() const { // check if the value is convertible to an integer return ecf::algorithm::to_int(v_, 0 /* value to return if conversion fails*/); } /// /// @brief Stores a collection of Variables and provides indexed lookup by name. /// /// Internally keeps a vector of variables together with a name-to-index hash map. /// Duplicate names are not handled explicitly by the constructor; later duplicates /// overwrite earlier entries in the index. /// class VariableMap { public: /// /// @brief Vector-based storage type for the variables. /// using storage_t = std::vector; /// /// @brief Hash map from variable name to its position in @c variables_. /// using index_t = std::unordered_map; /// /// @brief Constructs a map from the given variables. /// /// @tparam VARIABLES A parameter pack of Variable types. /// @param[in] variables The variables to store in the map. /// template explicit VariableMap(const VARIABLES... variables) : variables_{variables...} { // Fill index for (size_t i = 0; i < variables_.size(); ++i) { index_.insert(std::make_pair(variables_[i].name(), i)); } } /// /// @brief Returns an iterator to the first variable. /// /// @return A mutable iterator to the beginning of the storage. /// [[nodiscard]] inline storage_t::iterator begin() { return variables_.begin(); } /// /// @brief Returns a const iterator to the first variable. /// /// @return A const iterator to the beginning of the storage. /// [[nodiscard]] inline storage_t::const_iterator begin() const { return variables_.begin(); } /// /// @brief Returns an iterator past the last variable. /// /// @return A mutable iterator to the end of the storage. /// [[nodiscard]] inline storage_t::iterator end() { return variables_.end(); } /// /// @brief Returns a const iterator past the last variable. /// /// @return A const iterator to the end of the storage. /// [[nodiscard]] inline storage_t::const_iterator end() const { return variables_.end(); } /// /// @brief Checks whether the map contains no variables. /// /// @return `true` if the map is empty, otherwise `false`. /// [[nodiscard]] inline bool empty() const { return variables_.empty(); } /// /// @brief Returns the number of variables in the map. /// /// @return The number of stored variables. /// [[nodiscard]] inline size_t size() const { return variables_.size(); } /// /// @brief Sets the same value on every variable in the map. /// /// @param[in] value The value to assign to all variables. /// void set_value(const std::string& value); /// /// @brief Looks up a variable by name. /// /// @param[in] name The name of the variable to retrieve. /// @return A reference to the matching Variable. /// @throws std::runtime_error if no variable with @p name exists in the map. /// [[nodiscard]] Variable& operator[](const std::string& name); private: storage_t variables_; index_t index_; }; #endif /* ecflow_attribute_Variable_HPP */ ecflow-5.19.0/libs/attribute/test/0000775000175000017500000000000015250005516017163 5ustar alastairalastairecflow-5.19.0/libs/attribute/test/TestAvisoAttr.cpp0000664000175000017500000000475015250005516022451 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #include #include #include "ecflow/node/AvisoAttr.hpp" BOOST_AUTO_TEST_SUITE(U_Attributes) BOOST_AUTO_TEST_SUITE(T_AvisoAttr) BOOST_AUTO_TEST_CASE(can_create_aviso_attribute_with_all_parameters) { using namespace ecf; auto aviso = AvisoAttr{nullptr, "A", R"({ "event": "mars", "request": { "class": "od"} })", "http://host:port", "/path/to/schema", "60", 0, "/path/to/auth", "'this is a reason'"}; BOOST_CHECK_EQUAL(aviso.name(), "A"); BOOST_CHECK_EQUAL(aviso.listener(), R"('{ "event": "mars", "request": { "class": "od"} }')"); BOOST_CHECK_EQUAL(aviso.url(), "http://host:port"); BOOST_CHECK_EQUAL(aviso.schema(), "/path/to/schema"); BOOST_CHECK_EQUAL(aviso.auth(), "/path/to/auth"); BOOST_CHECK_EQUAL(aviso.polling(), "60"); BOOST_CHECK_EQUAL(aviso.reason(), "'this is a reason'"); } BOOST_AUTO_TEST_CASE(can_create_aviso_attribute_ensuring_single_quotes_when_absent) { using namespace ecf; // Notice the absence of single quotes, on both the listener and reason auto aviso = AvisoAttr{nullptr, "A", R"({ "event": "mars", "request": { "class": "od"} })", "http://host:port", "/path/to/schema", "60", 0, "/path/to/auth", "this is a reason"}; BOOST_CHECK_EQUAL(aviso.name(), "A"); BOOST_CHECK_EQUAL(aviso.listener(), R"('{ "event": "mars", "request": { "class": "od"} }')"); BOOST_CHECK_EQUAL(aviso.url(), "http://host:port"); BOOST_CHECK_EQUAL(aviso.schema(), "/path/to/schema"); BOOST_CHECK_EQUAL(aviso.auth(), "/path/to/auth"); BOOST_CHECK_EQUAL(aviso.polling(), "60"); BOOST_CHECK_EQUAL(aviso.reason(), "'this is a reason'"); } BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() ecflow-5.19.0/libs/attribute/test/TestZombieAttr.cpp0000664000175000017500000001610015250005516022605 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #include #include #include "ecflow/attribute/ZombieAttr.hpp" #include "ecflow/test/scaffold/Naming.hpp" using namespace ecf; BOOST_AUTO_TEST_SUITE(U_Attributes) BOOST_AUTO_TEST_SUITE(T_ZombieAttr) BOOST_AUTO_TEST_CASE(test_zombie_attr) { ECF_NAME_THIS_TEST(); { ZombieAttr ecf(ecf::Child::ECF, std::vector(), ecf::ZombieCtrlAction::FAIL); BOOST_CHECK_MESSAGE(ecf.zombie_lifetime() == ZombieAttr::default_ecf_zombie_life_time(), "expected " << ZombieAttr::default_ecf_zombie_life_time() << " but got " << ecf.zombie_lifetime()); ZombieAttr user(ecf::Child::USER, std::vector(), ecf::ZombieCtrlAction::FAIL); BOOST_CHECK_MESSAGE(user.zombie_lifetime() == ZombieAttr::default_user_zombie_life_time(), "Zombie life time not as expected"); ZombieAttr path(ecf::Child::PATH, std::vector(), ecf::ZombieCtrlAction::FAIL); BOOST_CHECK_MESSAGE(path.zombie_lifetime() == ZombieAttr::default_path_zombie_life_time(), "Zombie life time not as expected"); } { int zombie_life_time = 0; ZombieAttr ecf( ecf::Child::ECF, std::vector(), ecf::ZombieCtrlAction::FAIL, zombie_life_time); BOOST_CHECK_MESSAGE(ecf.zombie_lifetime() == ZombieAttr::default_ecf_zombie_life_time(), "Zombie life time not as expected"); ZombieAttr user( ecf::Child::USER, std::vector(), ecf::ZombieCtrlAction::FAIL, zombie_life_time); BOOST_CHECK_MESSAGE(user.zombie_lifetime() == ZombieAttr::default_user_zombie_life_time(), "Zombie life time not as expected"); ZombieAttr path( ecf::Child::PATH, std::vector(), ecf::ZombieCtrlAction::FAIL, zombie_life_time); BOOST_CHECK_MESSAGE(path.zombie_lifetime() == ZombieAttr::default_path_zombie_life_time(), "Zombie life time not as expected"); } { int zombie_life_time = -1; ZombieAttr ecf( ecf::Child::ECF, std::vector(), ecf::ZombieCtrlAction::FAIL, zombie_life_time); BOOST_CHECK_MESSAGE(ecf.zombie_lifetime() == ZombieAttr::default_ecf_zombie_life_time(), "Zombie life time not as expected"); ZombieAttr user( ecf::Child::USER, std::vector(), ecf::ZombieCtrlAction::FAIL, zombie_life_time); BOOST_CHECK_MESSAGE(user.zombie_lifetime() == ZombieAttr::default_user_zombie_life_time(), "Zombie life time not as expected"); ZombieAttr path( ecf::Child::PATH, std::vector(), ecf::ZombieCtrlAction::FAIL, zombie_life_time); BOOST_CHECK_MESSAGE(path.zombie_lifetime() == ZombieAttr::default_path_zombie_life_time(), "Zombie life time not as expected"); } { int zombie_life_time = 29; ZombieAttr ecf( ecf::Child::ECF, std::vector(), ecf::ZombieCtrlAction::FAIL, zombie_life_time); BOOST_CHECK_MESSAGE(ecf.zombie_lifetime() == ZombieAttr::minimum_zombie_life_time(), "Zombie life time not as expected"); ZombieAttr user( ecf::Child::USER, std::vector(), ecf::ZombieCtrlAction::FAIL, zombie_life_time); BOOST_CHECK_MESSAGE(user.zombie_lifetime() == ZombieAttr::minimum_zombie_life_time(), "Zombie life time not as expected"); ZombieAttr path( ecf::Child::PATH, std::vector(), ecf::ZombieCtrlAction::FAIL, zombie_life_time); BOOST_CHECK_MESSAGE(path.zombie_lifetime() == ZombieAttr::minimum_zombie_life_time(), "Zombie life time not as expected"); } } BOOST_AUTO_TEST_CASE(test_zombie_attr_parsing) { ECF_NAME_THIS_TEST(); { ZombieAttr zombie = ZombieAttr::create("user:fob::"); BOOST_CHECK_MESSAGE(zombie.zombie_type() == ecf::Child::USER, "Type not as expected"); BOOST_CHECK_MESSAGE(zombie.action() == ecf::ZombieCtrlAction::FOB, "action not as expected"); BOOST_CHECK_MESSAGE(zombie.zombie_lifetime() == ZombieAttr::default_user_zombie_life_time(), "Zombie life time not as expected"); BOOST_CHECK_MESSAGE(zombie.child_cmds().empty(), "Expected no children"); } { ZombieAttr zombie = ZombieAttr::create("ecf:fail::"); BOOST_CHECK_MESSAGE(zombie.zombie_type() == ecf::Child::ECF, "Type not as expected"); BOOST_CHECK_MESSAGE(zombie.action() == ecf::ZombieCtrlAction::FAIL, "action not as expected"); BOOST_CHECK_MESSAGE(zombie.zombie_lifetime() == ZombieAttr::default_ecf_zombie_life_time(), "Zombie life time not as expected"); BOOST_CHECK_MESSAGE(zombie.child_cmds().empty(), "Expected no children"); } { ZombieAttr zombie = ZombieAttr::create("path:fail::"); BOOST_CHECK_MESSAGE(zombie.zombie_type() == ecf::Child::PATH, "Type not as expected"); BOOST_CHECK_MESSAGE(zombie.action() == ecf::ZombieCtrlAction::FAIL, "action not as expected"); BOOST_CHECK_MESSAGE(zombie.zombie_lifetime() == ZombieAttr::default_path_zombie_life_time(), "Zombie life time not as expected"); BOOST_CHECK_MESSAGE(zombie.child_cmds().empty(), "Expected no children"); } { ZombieAttr zombie = ZombieAttr::create("user:fob::29"); BOOST_CHECK_MESSAGE(zombie.zombie_type() == ecf::Child::USER, "Type not as expected"); BOOST_CHECK_MESSAGE(zombie.action() == ecf::ZombieCtrlAction::FOB, "action not as expected"); BOOST_CHECK_MESSAGE(zombie.zombie_lifetime() == ZombieAttr::minimum_zombie_life_time(), "Zombie life time < 60, should default to 60"); BOOST_CHECK_MESSAGE(zombie.child_cmds().empty(), "Expected no children"); } { ZombieAttr zombie = ZombieAttr::create("user:fob:init:29"); BOOST_CHECK_MESSAGE(zombie.zombie_type() == ecf::Child::USER, "Type not as expected"); BOOST_CHECK_MESSAGE(zombie.action() == ecf::ZombieCtrlAction::FOB, "action not as expected"); BOOST_CHECK_MESSAGE(zombie.zombie_lifetime() == ZombieAttr::minimum_zombie_life_time(), "Zombie life time < 60, should default to 60"); BOOST_CHECK_MESSAGE(zombie.child_cmds().size() == 1, "Expected one child"); } } BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() ecflow-5.19.0/libs/attribute/test/TestVariableMap.cpp0000664000175000017500000000466315250005516022723 0ustar alastairalastair/* * Copyright 2023- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #include #include "ecflow/attribute/Variable.hpp" #include "ecflow/test/scaffold/Naming.hpp" BOOST_AUTO_TEST_SUITE(U_Attributes) BOOST_AUTO_TEST_SUITE(T_VariableMap) BOOST_AUTO_TEST_CASE(test_variablemap_is_able_to_create_empty_variable_map) { ECF_NAME_THIS_TEST(); VariableMap empty; BOOST_CHECK(empty.empty()); BOOST_CHECK(empty.size() == 0); } BOOST_AUTO_TEST_CASE(test_variablemap_is_able_to_create_variable_map) { ECF_NAME_THIS_TEST(); VariableMap variables{ Variable::new_variable("n1", "v1"), Variable::new_variable("n2", "v2"), Variable::new_variable("n3", "v3")}; BOOST_CHECK(!variables.empty()); BOOST_CHECK(variables.size() == 3); } BOOST_AUTO_TEST_CASE(test_variablemap_is_able_set_value_to_all_variables_in_variable_map) { ECF_NAME_THIS_TEST(); VariableMap variables{ Variable::new_variable("n1", "v1"), Variable::new_variable("n2", "v2"), Variable::new_variable("n3", "v3")}; std::string value = "some large value just for precaution!"; // set value of all variables variables.set_value(value); for (const auto& variable : variables) { BOOST_CHECK(variable.value() == value); } } BOOST_AUTO_TEST_CASE(test_variablemap_is_able_to_access_variable_in_variable_map) { ECF_NAME_THIS_TEST(); VariableMap variables{ Variable::new_variable("n1", "v1"), Variable::new_variable("n2", "v2"), Variable::new_variable("n3", "v3")}; const Variable& variable = variables["n1"]; BOOST_CHECK(variable.name() == "n1"); BOOST_CHECK(variable.value() == "v1"); } BOOST_AUTO_TEST_CASE(test_variablemap_throws_when_accessing_inexistent_variable_in_variable_map) { ECF_NAME_THIS_TEST(); VariableMap variables{ Variable::new_variable("n1", "v1"), Variable::new_variable("n2", "v2"), Variable::new_variable("n3", "v3")}; Variable found; BOOST_CHECK_EXCEPTION( found = variables["nX"], std::runtime_error, [](const std::runtime_error& e) { return true; }); } BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() ecflow-5.19.0/libs/attribute/test/TestLabel.cpp0000664000175000017500000000740415250005516021553 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #include #include #include #include "ecflow/attribute/NodeAttr.hpp" #include "ecflow/core/Str.hpp" #include "ecflow/test/scaffold/Naming.hpp" using namespace ecf; BOOST_AUTO_TEST_SUITE(U_Attributes) BOOST_AUTO_TEST_SUITE(T_Label) BOOST_AUTO_TEST_CASE(test_label_parsing) { ECF_NAME_THIS_TEST(); { std::string line = "label name \"value\""; std::vector linetokens; ecf::algorithm::split_at(linetokens, line); Label label; label.parse(line, linetokens, false); Label expected("name", "value"); BOOST_CHECK_MESSAGE(label == expected, "Expected " << expected.toString() << " but found " << label.toString()); BOOST_CHECK_MESSAGE(label.dump() == expected.dump(), "Expected " << expected.dump() << " but found " << label.dump()); } { std::string line = R"(label name "value\nvalue")"; std::vector linetokens; ecf::algorithm::split_at(linetokens, line); Label label; label.parse(line, linetokens, false); Label expected("name", "value\nvalue"); BOOST_CHECK_MESSAGE(label == expected, "Expected " << expected.toString() << " but found " << label.toString()); BOOST_CHECK_MESSAGE(label.dump() == expected.dump(), "Expected " << expected.dump() << " but found " << label.dump()); } { std::string line = "label name \"value that is multiple token !!!! 23445 !^ & * ( )\""; std::vector linetokens; ecf::algorithm::split_at(linetokens, line); Label label; label.parse(line, linetokens, false); Label expected("name", "value that is multiple token !!!! 23445 !^ & * ( )"); BOOST_CHECK_MESSAGE(label == expected, "Expected " << expected.toString() << " but found " << label.toString()); BOOST_CHECK_MESSAGE(label.dump() == expected.dump(), "Expected " << expected.dump() << " but found " << label.dump()); } { std::string line = R"(label name "value\n that\n is\n multiple\n token\n and\n new\n \nlines")"; std::vector linetokens; ecf::algorithm::split_at(linetokens, line); Label label; label.parse(line, linetokens, false); Label expected("name", "value\n that\n is\n multiple\n token\n and\n new\n \nlines"); BOOST_CHECK_MESSAGE(label == expected, "Expected " << expected.toString() << " but found " << label.toString()); BOOST_CHECK_MESSAGE(label.dump() == expected.dump(), "Expected " << expected.dump() << " but found " << label.dump()); } } BOOST_AUTO_TEST_CASE(test_label_equality) { ECF_NAME_THIS_TEST(); Label a("name", "value", "new_value"); Label same("name", "value", "new_value"); Label different_name("other", "value", "new_value"); Label different_value("name", "other", "new_value"); Label different_new_value("name", "value", "other"); BOOST_CHECK(a == same); BOOST_CHECK(!(a != same)); BOOST_CHECK(a != different_name); BOOST_CHECK(!(a == different_name)); BOOST_CHECK(a != different_value); BOOST_CHECK(!(a == different_value)); BOOST_CHECK(a != different_new_value); BOOST_CHECK(!(a == different_new_value)); } BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() ecflow-5.19.0/libs/attribute/test/TestAttributes_main.cpp0000664000175000017500000000073415250005516023665 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #define BOOST_TEST_MODULE Test_Attributes #include ecflow-5.19.0/libs/attribute/test/data/0000775000175000017500000000000015250005516020074 5ustar alastairalastairecflow-5.19.0/libs/attribute/test/data/migration/0000775000175000017500000000000015250005516022065 5ustar alastairalastairecflow-5.19.0/libs/attribute/test/data/migration/default_constructor_1_2_2/0000775000175000017500000000000015250005516027040 5ustar alastairalastairecflow-5.19.0/libs/attribute/test/data/migration/default_constructor_1_2_2/GenericAttr0000664000175000017500000000005315250005516031170 0ustar alastairalastair{"11GenericAttr":{"name_":"","values_":[]}}ecflow-5.19.0/libs/attribute/test/data/migration/default_constructor_1_2_2/AutoCancelAttr0000664000175000017500000000011715250005516031633 0ustar alastairalastair{"N3ecf14AutoCancelAttrE":{"cereal_class_version":0,"time_":{"h_":-1,"m_":-1}}}ecflow-5.19.0/libs/attribute/test/data/migration/default_constructor_1_2_2/TodayAttr0000664000175000017500000000012215250005516030671 0ustar alastairalastair{"N3ecf9TodayAttrE":{"ts_":{"cereal_class_version":0,"start_":{"h_":-1,"m_":-1}}}}ecflow-5.19.0/libs/attribute/test/data/migration/default_constructor_1_2_2/DateAttr0000664000175000017500000000005515250005516030473 0ustar alastairalastair{"8DateAttr":{"day_":0,"month_":0,"year_":0}}ecflow-5.19.0/libs/attribute/test/data/migration/default_constructor_1_2_2/ClockAttr0000664000175000017500000000007115250005516030647 0ustar alastairalastair{"9ClockAttr":{"cereal_class_version":0,"hybrid_":false}}ecflow-5.19.0/libs/attribute/test/data/migration/default_constructor_1_2_2/DayAttr0000664000175000017500000000002715250005516030332 0ustar alastairalastair{"7DayAttr":{"day_":0}}ecflow-5.19.0/libs/attribute/test/data/migration/default_constructor_1_2_2/RepeatDate0000664000175000017500000000015315250005516031000 0ustar alastairalastair{"10RepeatDate":{"cereal_class_version":0,"value0":{"name_":""},"start_":0,"end_":0,"delta_":0,"value_":0}}ecflow-5.19.0/libs/attribute/test/data/migration/default_constructor_1_2_2/RepeatEnumerated0000664000175000017500000000015015250005516032211 0ustar alastairalastair{"16RepeatEnumerated":{"cereal_class_version":0,"value0":{"name_":""},"theEnums_":[],"currentIndex_":0}}ecflow-5.19.0/libs/attribute/test/data/migration/default_constructor_1_2_2/VerifyAttr0000664000175000017500000000006715250005516031065 0ustar alastairalastair{"10VerifyAttr":{"state_":0,"expected_":0,"actual_":0}}ecflow-5.19.0/libs/attribute/test/data/migration/default_constructor_1_2_2/Label0000664000175000017500000000002415250005516027776 0ustar alastairalastair{"5Label":{"n_":""}}ecflow-5.19.0/libs/attribute/test/data/migration/default_constructor_1_2_2/Meter0000664000175000017500000000006515250005516030040 0ustar alastairalastair{"5Meter":{"min_":0,"max_":0,"v_":0,"n_":"","cc_":0}}ecflow-5.19.0/libs/attribute/test/data/migration/default_constructor_1_2_2/LateAttr0000664000175000017500000000002615250005516030501 0ustar alastairalastair{"N3ecf8LateAttrE":{}}ecflow-5.19.0/libs/attribute/test/data/migration/default_constructor_1_2_2/RepeatInteger0000664000175000017500000000015615250005516031523 0ustar alastairalastair{"13RepeatInteger":{"cereal_class_version":0,"value0":{"name_":""},"start_":0,"end_":0,"delta_":0,"value_":0}}ecflow-5.19.0/libs/attribute/test/data/migration/default_constructor_1_2_2/RepeatString0000664000175000017500000000014615250005516031373 0ustar alastairalastair{"12RepeatString":{"cereal_class_version":0,"value0":{"name_":""},"theStrings_":[],"currentIndex_":0}}ecflow-5.19.0/libs/attribute/test/data/migration/default_constructor_1_2_2/QueueAttr0000664000175000017500000000014515250005516030702 0ustar alastairalastair{"9QueueAttr":{"cereal_class_version":0,"theQueue_":[],"state_vec_":[],"name_":"","currentIndex_":0}}ecflow-5.19.0/libs/attribute/test/data/migration/default_constructor_1_2_2/AutoArchiveAttr0000664000175000017500000000012015250005516032021 0ustar alastairalastair{"N3ecf15AutoArchiveAttrE":{"cereal_class_version":0,"time_":{"h_":-1,"m_":-1}}}ecflow-5.19.0/libs/attribute/test/data/migration/default_constructor_1_2_2/CronAttr0000664000175000017500000000016215250005516030516 0ustar alastairalastair{"N3ecf8CronAttrE":{"cereal_class_version":0,"timeSeries_":{"cereal_class_version":0,"start_":{"h_":-1,"m_":-1}}}}ecflow-5.19.0/libs/attribute/test/data/migration/default_constructor_1_2_2/Event0000664000175000017500000000001515250005516030040 0ustar alastairalastair{"5Event":{}}ecflow-5.19.0/libs/attribute/test/data/migration/default_constructor_1_2_2/TimeAttr0000664000175000017500000000012115250005516030506 0ustar alastairalastair{"N3ecf8TimeAttrE":{"ts_":{"cereal_class_version":0,"start_":{"h_":-1,"m_":-1}}}}ecflow-5.19.0/libs/attribute/test/data/migration/default_constructor_1_2_2/RepeatDateList0000664000175000017500000000014215250005516031632 0ustar alastairalastair{"14RepeatDateList":{"cereal_class_version":0,"value0":{"name_":""},"list_":[],"currentIndex_":0}}ecflow-5.19.0/libs/attribute/test/data/migration/default_constructor_1_2_2/Variable0000664000175000017500000000003715250005516030510 0ustar alastairalastair{"8Variable":{"n_":"","v_":""}}ecflow-5.19.0/libs/attribute/test/data/migration/default_constructor_1_2_2/ZombieAttr0000664000175000017500000000015615250005516031045 0ustar alastairalastair{"10ZombieAttr":{"cereal_class_version":0,"child_cmds_":[],"zombie_type_":6,"action_":4,"zombie_lifetime_":0}}ecflow-5.19.0/libs/attribute/test/data/migration/1_2_2/0000775000175000017500000000000015250005516022667 5ustar alastairalastairecflow-5.19.0/libs/attribute/test/data/migration/1_2_2/GenericAttr0000664000175000017500000000006615250005516025023 0ustar alastairalastair{"11GenericAttr":{"name_":"gen1","values_":["a","b"]}}ecflow-5.19.0/libs/attribute/test/data/migration/1_2_2/AutoCancelAttr0000664000175000017500000000013515250005516025462 0ustar alastairalastair{"N3ecf14AutoCancelAttrE":{"cereal_class_version":0,"time_":{"h_":2400,"m_":0},"days_":true}}ecflow-5.19.0/libs/attribute/test/data/migration/1_2_2/TodayAttr0000664000175000017500000000012215250005516024520 0ustar alastairalastair{"N3ecf9TodayAttrE":{"ts_":{"cereal_class_version":0,"start_":{"h_":10,"m_":12}}}}ecflow-5.19.0/libs/attribute/test/data/migration/1_2_2/DateAttr0000664000175000017500000000006215250005516024320 0ustar alastairalastair{"8DateAttr":{"day_":12,"month_":12,"year_":2012}}ecflow-5.19.0/libs/attribute/test/data/migration/1_2_2/ClockAttr0000664000175000017500000000017415250005516024502 0ustar alastairalastair{"9ClockAttr":{"cereal_class_version":0,"hybrid_":false,"positiveGain_":true,"gain_":3600,"day_":1,"month_":1,"year_":2009}}ecflow-5.19.0/libs/attribute/test/data/migration/1_2_2/DayAttr0000664000175000017500000000002715250005516024161 0ustar alastairalastair{"7DayAttr":{"day_":1}}ecflow-5.19.0/libs/attribute/test/data/migration/1_2_2/RepeatDate0000664000175000017500000000020415250005516024624 0ustar alastairalastair{"10RepeatDate":{"cereal_class_version":0,"value0":{"name_":"date"},"start_":20110112,"end_":20110115,"delta_":1,"value_":20110112}}ecflow-5.19.0/libs/attribute/test/data/migration/1_2_2/RepeatEnumerated0000664000175000017500000000016315250005516026044 0ustar alastairalastair{"16RepeatEnumerated":{"cereal_class_version":0,"value0":{"name_":"enum"},"theEnums_":["a","b"],"currentIndex_":0}}ecflow-5.19.0/libs/attribute/test/data/migration/1_2_2/Event_10000664000175000017500000000003015250005516024104 0ustar alastairalastair{"5Event":{"number_":1}}ecflow-5.19.0/libs/attribute/test/data/migration/1_2_2/VerifyAttr0000664000175000017500000000006715250005516024714 0ustar alastairalastair{"10VerifyAttr":{"state_":1,"expected_":3,"actual_":0}}ecflow-5.19.0/libs/attribute/test/data/migration/1_2_2/AutoArchiveAttr_10000664000175000017500000000012015250005516026070 0ustar alastairalastair{"N3ecf15AutoArchiveAttrE":{"cereal_class_version":0,"time_":{"h_":10,"m_":12}}}ecflow-5.19.0/libs/attribute/test/data/migration/1_2_2/Label0000664000175000017500000000007215250005516023630 0ustar alastairalastair{"5Label":{"n_":"name","v_":"value","new_v_":"new_value"}}ecflow-5.19.0/libs/attribute/test/data/migration/1_2_2/Meter0000664000175000017500000000010015250005516023655 0ustar alastairalastair{"5Meter":{"min_":10,"max_":100,"v_":10,"n_":"meter","cc_":100}}ecflow-5.19.0/libs/attribute/test/data/migration/1_2_2/LateAttr0000664000175000017500000000015315250005516024331 0ustar alastairalastair{"N3ecf8LateAttrE":{"s_":{"h_":10,"m_":12},"a_":{"h_":10,"m_":12},"c_":{"h_":10,"m_":12},"c_is_rel_":true}}ecflow-5.19.0/libs/attribute/test/data/migration/1_2_2/RepeatInteger0000664000175000017500000000016715250005516025354 0ustar alastairalastair{"13RepeatInteger":{"cereal_class_version":0,"value0":{"name_":"integer"},"start_":0,"end_":100,"delta_":2,"value_":0}}ecflow-5.19.0/libs/attribute/test/data/migration/1_2_2/RepeatString0000664000175000017500000000016315250005516025221 0ustar alastairalastair{"12RepeatString":{"cereal_class_version":0,"value0":{"name_":"string"},"theStrings_":["a","b"],"currentIndex_":0}}ecflow-5.19.0/libs/attribute/test/data/migration/1_2_2/QueueAttr0000664000175000017500000000016415250005516024532 0ustar alastairalastair{"9QueueAttr":{"cereal_class_version":0,"theQueue_":["a","b"],"state_vec_":[2,2],"name_":"queue","currentIndex_":0}}ecflow-5.19.0/libs/attribute/test/data/migration/1_2_2/AutoArchiveAttr0000664000175000017500000000013615250005516025657 0ustar alastairalastair{"N3ecf15AutoArchiveAttrE":{"cereal_class_version":0,"time_":{"h_":2400,"m_":0},"days_":true}}ecflow-5.19.0/libs/attribute/test/data/migration/1_2_2/CronAttr0000664000175000017500000000033215250005516024344 0ustar alastairalastair{"N3ecf8CronAttrE":{"cereal_class_version":0,"timeSeries_":{"cereal_class_version":0,"start_":{"h_":0,"m_":0},"finish_":{"h_":20,"m_":0},"incr_":{"h_":0,"m_":1}},"weekDays_":[1,2],"daysOfMonth_":[1,2],"months_":[1,2]}}ecflow-5.19.0/libs/attribute/test/data/migration/1_2_2/AutoCancelAttr_10000664000175000017500000000011715250005516025702 0ustar alastairalastair{"N3ecf14AutoCancelAttrE":{"cereal_class_version":0,"time_":{"h_":10,"m_":12}}}ecflow-5.19.0/libs/attribute/test/data/migration/1_2_2/Event_30000664000175000017500000000007215250005516024114 0ustar alastairalastair{"5Event":{"n_":"event","number_":1,"v_":true,"iv_":true}}ecflow-5.19.0/libs/attribute/test/data/migration/1_2_2/ZombieAttr10000664000175000017500000000017715250005516024760 0ustar alastairalastair{"10ZombieAttr":{"cereal_class_version":0,"child_cmds_":[0,1,2,3,4,5,6,7],"zombie_type_":0,"action_":0,"zombie_lifetime_":500}}ecflow-5.19.0/libs/attribute/test/data/migration/1_2_2/TimeAttr0000664000175000017500000000012115250005516024335 0ustar alastairalastair{"N3ecf8TimeAttrE":{"ts_":{"cereal_class_version":0,"start_":{"h_":10,"m_":12}}}}ecflow-5.19.0/libs/attribute/test/data/migration/1_2_2/RepeatDateList0000664000175000017500000000017315250005516025465 0ustar alastairalastair{"14RepeatDateList":{"cereal_class_version":0,"value0":{"name_":"datelist"},"list_":[19990101,19990102],"currentIndex_":0}}ecflow-5.19.0/libs/attribute/test/data/migration/1_2_2/Variable0000664000175000017500000000006015250005516024333 0ustar alastairalastair{"8Variable":{"n_":"var_name","v_":"var_value"}}ecflow-5.19.0/libs/attribute/test/data/migration/1_2_2/ZombieAttr0000664000175000017500000000017715250005516024677 0ustar alastairalastair{"10ZombieAttr":{"cereal_class_version":0,"child_cmds_":[0,1,2,3,4,5,6,7],"zombie_type_":0,"action_":0,"zombie_lifetime_":300}}ecflow-5.19.0/libs/attribute/test/data/migration/1_2_2/Event_20000664000175000017500000000003115250005516024106 0ustar alastairalastair{"5Event":{"n_":"event"}}ecflow-5.19.0/libs/attribute/test/TestDayAttr.cpp0000664000175000017500000001422415250005516022102 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #include #include #include #include "ecflow/attribute/DayAttr.hpp" #include "ecflow/core/Calendar.hpp" #include "ecflow/core/PrintStyle.hpp" #include "ecflow/core/Str.hpp" #include "ecflow/node/formatter/DefsWriter.hpp" #include "ecflow/test/scaffold/Naming.hpp" using namespace ecf; BOOST_AUTO_TEST_SUITE(U_Attributes) BOOST_AUTO_TEST_SUITE(T_DayAttr) BOOST_AUTO_TEST_CASE(test_day_attr) { ECF_NAME_THIS_TEST(); // See TimeAttr.hpp for rules concerning isFree() and checkForReque() // test time attr isFree(), and checkForRequeue Calendar calendar; calendar.init(boost::posix_time::ptime(boost::gregorian::date(2013, 7, 9), boost::posix_time::minutes(0)), Calendar::REAL); // tuesday // Represent a day within a week (range 0==Sun to 6==Sat) BOOST_CHECK_MESSAGE(calendar.day_of_week() == 2, " Expected tuesday(2) but found " << calendar.day_of_week()); DayAttr day(DayAttr::WEDNESDAY); day.reset(calendar); // int day_changed = 0; // after midnight make sure we keep day_changed // day_changed = 0; tuesday // day_changed = 1; wednesday // day_changed = 3; thursday for (int m = 1; m < 96; m++) { calendar.update(boost::posix_time::time_duration(boost::posix_time::hours(1))); // if (calendar.dayChanged()) // day_changed++; day.calendarChanged(calendar); if (day.date() < calendar.date()) { BOOST_CHECK_MESSAGE(!day.isFree(calendar), day.toString() << " is free should fail at day " << calendar.day_of_week()); BOOST_CHECK_MESSAGE(!day.checkForRequeue(calendar), day.toString() << " checkForRequeue should return false for a single day " << calendar.day_of_week()); } else if (day.date() == calendar.date()) { BOOST_CHECK_MESSAGE(day.isFree(calendar), day.toString() << " is free should pass at day " << calendar.day_of_week()); BOOST_CHECK_MESSAGE(!day.checkForRequeue(calendar), day.toString() << " checkForRequeue should fail at " << calendar.day_of_week()); } else { BOOST_CHECK_MESSAGE(day.date() > calendar.date(), ""); BOOST_CHECK_MESSAGE(!day.isFree(calendar), day.toString() << " is free should pass at day " << calendar.day_of_week()); BOOST_CHECK_MESSAGE(day.checkForRequeue(calendar), day.toString() << " checkForRequeue should fail at " << calendar.day_of_week()); } } } BOOST_AUTO_TEST_CASE(test_day_attr_constructor) { ECF_NAME_THIS_TEST(); { DayAttr day; BOOST_CHECK_MESSAGE(day.day() == DayAttr::SUNDAY, ""); BOOST_CHECK_MESSAGE(day.state_change_no() == 0, ""); BOOST_CHECK_MESSAGE(day.isSetFree() == false, ""); } { DayAttr day(DayAttr::WEDNESDAY); BOOST_CHECK_MESSAGE(day.day() == DayAttr::WEDNESDAY, ""); BOOST_CHECK_MESSAGE(day.state_change_no() == 0, ""); BOOST_CHECK_MESSAGE(day.isSetFree() == false, ""); } { DayAttr day("monday"); BOOST_CHECK_MESSAGE(day.day() == DayAttr::MONDAY, ""); BOOST_CHECK_MESSAGE(day.state_change_no() == 0, ""); BOOST_CHECK_MESSAGE(day.isSetFree() == false, ""); } } static DayAttr print_and_parse_attr(DayAttr& day) { std::string output; ecf::write_t(output, day, PrintStyle::MIGRATE); output.erase(output.begin() + output.size() - 1); // remove trailing newline std::vector tokens; ecf::algorithm::split_at(tokens, output); return DayAttr::create(tokens, true /*read state*/); } BOOST_AUTO_TEST_CASE(test_day_parsing) { ECF_NAME_THIS_TEST(); { DayAttr day(DayAttr::WEDNESDAY); day.setFree(); DayAttr parsed_day = print_and_parse_attr(day); BOOST_CHECK_MESSAGE(day == parsed_day, "Parse failed expected " << day.dump() << " but found " << parsed_day.dump()); } { DayAttr day(DayAttr::WEDNESDAY); day.setFree(); DayAttr parsed_day = print_and_parse_attr(day); BOOST_CHECK_MESSAGE(day == parsed_day, "Parse failed expected " << day.dump() << " but found " << parsed_day.dump()); } { DayAttr day(DayAttr::WEDNESDAY); DayAttr parsed_day = print_and_parse_attr(day); BOOST_CHECK_MESSAGE(day == parsed_day, "Parse failed expected " << day.dump() << " but found " << parsed_day.dump()); } { Calendar calendar; calendar.init(boost::posix_time::ptime(boost::gregorian::date(2020, 6, 26), boost::posix_time::minutes(0)), Calendar::REAL); // friday DayAttr day(calendar.date()); DayAttr parsed_day = print_and_parse_attr(day); BOOST_CHECK_MESSAGE(day == parsed_day, "Parse failed expected " << day.dump() << " but found " << parsed_day.dump()); } { Calendar c; c.init(boost::posix_time::ptime(boost::gregorian::date(2020, 6, 26), boost::posix_time::minutes(0)), Calendar::REAL); // friday auto one_day = boost::gregorian::date_duration(1); auto next_day = c.date(); // todays date for (int i = 0; i < 7; i++) { next_day += one_day; DayAttr day(next_day); DayAttr parsed_day = print_and_parse_attr(day); BOOST_CHECK_MESSAGE(day == parsed_day, "Parse failed expected " << day.dump() << " but found " << parsed_day.dump()); } } } BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() ecflow-5.19.0/libs/attribute/test/TestTimeAttr.cpp0000664000175000017500000005474315250005516022275 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #include #include #include #include "ecflow/attribute/TimeAttr.hpp" #include "ecflow/core/Calendar.hpp" #include "ecflow/core/Chrono.hpp" #include "ecflow/core/TimeSeries.hpp" #include "ecflow/test/scaffold/Naming.hpp" using namespace ecf; BOOST_AUTO_TEST_SUITE(U_Attributes) BOOST_AUTO_TEST_SUITE(T_TimeAttr) BOOST_AUTO_TEST_CASE(test_time_string_constrcutor) { ECF_NAME_THIS_TEST(); { TimeAttr time("+00:30"); BOOST_CHECK_MESSAGE(time.time_series().start().hour() == 0 && time.time_series().start().minute() == 30 && time.time_series().finish().isNULL() && time.time_series().incr().isNULL() && time.time_series().relative(), "Error in time constructor"); } { TimeAttr time("12:30"); BOOST_CHECK_MESSAGE(time.time_series().start().hour() == 12 && time.time_series().start().minute() == 30 && time.time_series().finish().isNULL() && time.time_series().incr().isNULL() && !time.time_series().relative(), "Error in time constructor"); } { TimeAttr time("+00:30 11:30 00:01"); BOOST_CHECK_MESSAGE(time.time_series().start().hour() == 0 && time.time_series().start().minute() == 30 && time.time_series().finish().hour() == 11 && time.time_series().finish().minute() == 30 && time.time_series().incr().hour() == 0 && time.time_series().incr().minute() == 1 && time.time_series().relative(), "Error in time constructor"); } { BOOST_REQUIRE_THROW(TimeAttr(""), std::runtime_error); BOOST_REQUIRE_THROW(TimeAttr(" "), std::runtime_error); BOOST_REQUIRE_THROW(TimeAttr("sdsdsdsd"), std::runtime_error); } } BOOST_AUTO_TEST_CASE(test_time_attr) { ECF_NAME_THIS_TEST(); // See TimeAttr.hpp for rules concerning isFree() and checkForReque() // test time attr isFree(), and checkForRequeue Calendar calendar; calendar.init(boost::posix_time::ptime(boost::gregorian::date(2010, 2, 10), boost::posix_time::minutes(0)), Calendar::REAL); // Create a test when we can match a time series. Need to sync hour with suite time // at hour 1, suite time should also be 01:00, for test to work // // Create the time series: start 10:00 // finish 20:00 // incr 1:00 TimeSeries timeSeriesX(TimeSlot(10, 0), TimeSlot(20, 0), TimeSlot(1, 0), false /* relative */); TimeSeries timeSeries2X(TimeSlot(11, 0), TimeSlot(15, 0), TimeSlot(1, 0), false /* relative */); TimeSeries timeSeries3X(TimeSlot(15, 0), false /* relative */); TimeSeries timeSeries4X(TimeSlot(0, 0), false /* relative */); TimeSlot t1_min, t1_max, t2_min, t2_max, t3_min, t3_max, t4_min, t4_max; timeSeriesX.min_max_time_slots(t1_min, t1_max); timeSeries2X.min_max_time_slots(t2_min, t2_max); timeSeries3X.min_max_time_slots(t3_min, t3_max); timeSeries4X.min_max_time_slots(t4_min, t4_max); BOOST_CHECK_MESSAGE(t1_min == TimeSlot(10, 0) && t1_max == TimeSlot(20, 0), "Not as expected"); BOOST_CHECK_MESSAGE(t2_min == TimeSlot(11, 0) && t2_max == TimeSlot(15, 0), "Not as expected"); BOOST_CHECK_MESSAGE(t3_min == TimeSlot(15, 0) && t3_max == TimeSlot(15, 0), "Not as expected"); BOOST_CHECK_MESSAGE(t4_min == TimeSlot(0, 0) && t4_max == TimeSlot(0, 0), "Not as expected"); TimeAttr timeSeries(timeSeriesX); TimeAttr timeSeries2(timeSeries2X); TimeAttr timeSeries3(timeSeries3X); TimeAttr timeSeries4(timeSeries4X); std::vector timeSeries_free_slots; std::vector timeSeries2_free_slots; timeSeries.time_series().free_slots(timeSeries_free_slots); timeSeries2.time_series().free_slots(timeSeries2_free_slots); BOOST_CHECK_MESSAGE(timeSeries_free_slots.size() == 11, "Expected 11 free slots for " << timeSeries.toString() << " but found " << timeSeries_free_slots.size()); BOOST_CHECK_MESSAGE(timeSeries2_free_slots.size() == 5, "Expected 5 free slots for " << timeSeries2.toString() << " but found " << timeSeries_free_slots.size()); // follow normal process timeSeries.reset(calendar); timeSeries2.reset(calendar); timeSeries3.reset(calendar); timeSeries4.reset(calendar); bool cmd_context = true; bool day_changed = false; // after midnight make sure we keep day_changed for (int m = 1; m < 96; m++) { calendar.update(boost::posix_time::time_duration(boost::posix_time::minutes(30))); if (!day_changed) { day_changed = calendar.dayChanged(); } boost::posix_time::time_duration time = calendar.suiteTime().time_of_day(); timeSeries.calendarChanged(calendar); timeSeries2.calendarChanged(calendar); timeSeries3.calendarChanged(calendar); timeSeries4.calendarChanged(calendar); if (calendar.dayChanged()) { BOOST_CHECK_MESSAGE(!timeSeries.checkForRequeue(calendar, t1_min, t1_max), " expected " << timeSeries.toString() << " checkForRequeue to fail at " << to_simple_string(calendar.suiteTime())); BOOST_CHECK_MESSAGE(timeSeries.checkForRequeue(calendar, t1_min, t1_max, cmd_context), " expected " << timeSeries.toString() << " checkForRequeue to pass at " << to_simple_string(calendar.suiteTime())); } else if (time < timeSeries.time_series().start().duration()) { BOOST_CHECK_MESSAGE(!timeSeries.isFree(calendar), timeSeries.toString() << " should NOT be free at time " << time); BOOST_CHECK_MESSAGE(!timeSeries.checkForRequeue(calendar, t1_min, t1_max), timeSeries.toString() << " checkForRequeue should fail at " << time); BOOST_CHECK_MESSAGE(timeSeries.checkForRequeue(calendar, t1_min, t1_max, cmd_context), timeSeries.toString() << " checkForRequeue should pass at " << time); } else if (time >= timeSeries.time_series().start().duration() && time <= timeSeries.time_series().finish().duration()) { bool matches_free_slot = false; for (const auto& timeSeries_free_slot : timeSeries_free_slots) { if (time == timeSeries_free_slot) { matches_free_slot = true; break; } } if (matches_free_slot) { BOOST_CHECK_MESSAGE(timeSeries.isFree(calendar), timeSeries.toString() << " should be free at time " << time); } else { BOOST_CHECK_MESSAGE(!timeSeries.isFree(calendar), timeSeries.toString() << " should be fail at time " << time); } /// At the last hour checkForRequeue should return false; This ensures that value will /// not get incremented and so, should leave node in the complete state. if (time < timeSeries.time_series().finish().duration()) { BOOST_CHECK_MESSAGE(timeSeries.checkForRequeue(calendar, t1_min, t1_max), timeSeries.toString() << " checkForRequeue should be free at time " << time); } else { BOOST_CHECK_MESSAGE(!timeSeries.checkForRequeue(calendar, t1_min, t1_max), timeSeries.toString() << "checkForRequeue should Not free at time " << time); } } else { BOOST_CHECK_MESSAGE(!timeSeries.isFree(calendar), timeSeries.toString() << " should be holding at time " << time); BOOST_CHECK_MESSAGE(!timeSeries.checkForRequeue(calendar, t1_min, t1_max), timeSeries.toString() << " should fail at " << time); } if (calendar.dayChanged()) { BOOST_CHECK_MESSAGE(!timeSeries2.checkForRequeue(calendar, t2_min, t2_max), " expected " << timeSeries2.toString() << " checkForRequeue to pass at " << to_simple_string(calendar.suiteTime())); } else if (time < timeSeries2.time_series().start().duration()) { BOOST_CHECK_MESSAGE(!timeSeries2.isFree(calendar), timeSeries2.toString() << " should NOT be free at time " << time); BOOST_CHECK_MESSAGE(!timeSeries2.checkForRequeue(calendar, t2_min, t2_max), timeSeries2.toString() << " checkForRequeue should pass at " << time); } else if (time >= timeSeries2.time_series().start().duration() && time <= timeSeries2.time_series().finish().duration()) { bool matches_free_slot = false; for (const auto& timeSeries2_free_slot : timeSeries2_free_slots) { if (time == timeSeries2_free_slot) { matches_free_slot = true; break; } } if (matches_free_slot) { BOOST_CHECK_MESSAGE(timeSeries2.isFree(calendar), timeSeries2.toString() << " should be free at time " << time); } else { BOOST_CHECK_MESSAGE(!timeSeries2.isFree(calendar), timeSeries2.toString() << " should be fail at time " << time); } /// At the last time checkForRequeue should return false; This ensures that value will /// not get incremented and so, should leave node in the complete state. if (time < timeSeries2.time_series().finish().duration()) { BOOST_CHECK_MESSAGE(timeSeries2.checkForRequeue(calendar, t2_min, t2_max), timeSeries2.toString() << " checkForRequeue should be free at time " << time); } else { BOOST_CHECK_MESSAGE(!timeSeries2.checkForRequeue(calendar, t2_min, t2_max), timeSeries2.toString() << "checkForRequeue should Not free at time " << time); } } else { BOOST_CHECK_MESSAGE(!timeSeries2.isFree(calendar), timeSeries2.toString() << " should be holding at time " << time); BOOST_CHECK_MESSAGE(!timeSeries2.checkForRequeue(calendar, t2_min, t2_max), timeSeries2.toString() << " should fail at " << time); } // Single slot, Once a single slot is Free it *stays* free until explicitly requeued, (i.e by parent // repeat/cron) if (!day_changed) { if (time < timeSeries3.time_series().start().duration()) { BOOST_CHECK_MESSAGE(!timeSeries3.isFree(calendar), timeSeries3.toString() << " should be fail at time " << time); } else if (time == timeSeries3.time_series().start().duration()) { BOOST_CHECK_MESSAGE(timeSeries3.isFree(calendar), timeSeries3.toString() << " should be free at time " << time); } else if (time > timeSeries3.time_series().start().duration()) { BOOST_CHECK_MESSAGE(timeSeries3.isFree(calendar), timeSeries3.toString() << " isFree should pass at time " << time); } } else { BOOST_CHECK_MESSAGE(timeSeries3.isFree(calendar), timeSeries3.toString() << " should be free at time after day change" << time); } BOOST_CHECK_MESSAGE(!timeSeries3.checkForRequeue(calendar, t3_min, t3_max), timeSeries3.toString() << " checkForRequeue should fail at " << time); // single slot at midnight, Once a single slot if Free it *stays* free until explicitly requeued, (i.e by parent // repeat/cron) if (!day_changed) { if (time == timeSeries4.time_series().start().duration()) { BOOST_CHECK_MESSAGE(timeSeries4.isFree(calendar), timeSeries4.toString() << " should be free at time " << time); } else { BOOST_CHECK_MESSAGE(!timeSeries4.isFree(calendar), timeSeries4.toString() << " day_changed(" << day_changed << ") isFree should fail at time " << time); } } else { BOOST_CHECK_MESSAGE(timeSeries4.isFree(calendar), timeSeries4.toString() << " day_changed(" << day_changed << ") isFree should pass at time " << time); } BOOST_CHECK_MESSAGE(!timeSeries4.checkForRequeue(calendar, t4_min, t4_max), timeSeries4.toString() << " checkForRequeue should fail at " << time); // Typically when a time is free, it stays free, until it is re-queued // However in order to test isFree for time with time intervals, we need to re-queue timeSeries.requeue(calendar); timeSeries2.requeue(calendar); // Do not requeue time 00, and time 15, so that we can check for free } } BOOST_AUTO_TEST_CASE(test_time_once_free_stays_free) { ECF_NAME_THIS_TEST(); Calendar calendar; calendar.init(boost::posix_time::ptime(boost::gregorian::date(2010, 2, 10), boost::posix_time::minutes(0)), Calendar::REAL); TimeSeries timeSeriesX(TimeSlot(10, 0), TimeSlot(20, 0), TimeSlot(1, 0), false /* relative */); TimeSeries timeSeries2X(TimeSlot(11, 0), TimeSlot(15, 0), TimeSlot(1, 0), false /* relative */); TimeSeries timeSeries3X(TimeSlot(15, 0), false /* relative */); TimeSeries timeSeries4X(TimeSlot(0, 0), false /* relative */); TimeAttr timeSeries(timeSeriesX); TimeAttr timeSeries2(timeSeries2X); TimeAttr timeSeries3(timeSeries3X); TimeAttr timeSeries4(timeSeries4X); bool day_changed = false; // after midnight make sure we keep day_changed for (int m = 1; m < 96; m++) { calendar.update(boost::posix_time::time_duration(boost::posix_time::minutes(30))); if (!day_changed) { day_changed = calendar.dayChanged(); } boost::posix_time::time_duration time = calendar.suiteTime().time_of_day(); timeSeries.calendarChanged(calendar); timeSeries2.calendarChanged(calendar); timeSeries3.calendarChanged(calendar); timeSeries4.calendarChanged(calendar); // ********************************************************************************** // When a time (regardless of whether its single slot or time series) is free, it stays free, // until explicitly re-queued, // *********************************************************************************** if (time < timeSeries.time_series().start().duration()) { if (!day_changed) { BOOST_CHECK_MESSAGE(!timeSeries.isFree(calendar), timeSeries.toString() << " should NOT be free at time " << time); } else { BOOST_CHECK_MESSAGE(timeSeries.isFree(calendar), timeSeries.toString() << " should be free at time " << time); } } else if (time >= timeSeries.time_series().start().duration()) { BOOST_CHECK_MESSAGE(timeSeries.isFree(calendar), timeSeries.toString() << " should be free at time " << time); } if (time < timeSeries2.time_series().start().duration()) { if (!day_changed) { BOOST_CHECK_MESSAGE(!timeSeries2.isFree(calendar), timeSeries2.toString() << " should NOT be free at time " << time); } else { BOOST_CHECK_MESSAGE(timeSeries.isFree(calendar), timeSeries.toString() << " should be free at time " << time); } } else if (time >= timeSeries2.time_series().start().duration()) { BOOST_CHECK_MESSAGE(timeSeries2.isFree(calendar), timeSeries2.toString() << " should be free at time " << time); } if (!day_changed) { if (time == timeSeries3.time_series().start().duration()) { BOOST_CHECK_MESSAGE(timeSeries3.isFree(calendar), timeSeries3.toString() << " should be free at time " << time); } else if (time > timeSeries3.time_series().start().duration()) { BOOST_CHECK_MESSAGE(timeSeries3.isFree(calendar), timeSeries3.toString() << " isFree, once free should stay free at time " << time); } } else { BOOST_CHECK_MESSAGE(timeSeries3.isFree(calendar), timeSeries3.toString() << " should be free at time after day change " << time); } // single slot at midnight, Once a single slot if Free it *stays* free until explicitly requeued, (i.e by parent // repeat/cron) if (!day_changed) { if (time == timeSeries4.time_series().start().duration()) { BOOST_CHECK_MESSAGE(timeSeries4.isFree(calendar), timeSeries4.toString() << " should be free at time " << time); } else { BOOST_CHECK_MESSAGE(!timeSeries4.isFree(calendar), timeSeries4.toString() << " day_changed(" << day_changed << ") isFree should fail at time " << time); } } else { BOOST_CHECK_MESSAGE(timeSeries4.isFree(calendar), timeSeries4.toString() << " day_changed(" << day_changed << ") isFree should pass at time " << time); } } } BOOST_AUTO_TEST_CASE(test_time_attr_multiples) { ECF_NAME_THIS_TEST(); // See TimeAttr.hpp for rules concerning isFree() and checkForReque() // test time attr isFree(), and checkForRequeue Calendar calendar; calendar.init(boost::posix_time::ptime(boost::gregorian::date(2010, 2, 10), boost::posix_time::minutes(0)), Calendar::REAL); TimeSeries timeSeries1530(TimeSlot(15, 30), false /* relative */); TimeSeries timeSeries1630(TimeSlot(16, 30), false /* relative */); TimeSeries timeSeries2030(TimeSlot(20, 30), false /* relative */); TimeSlot t1_min, t1_max; timeSeries1530.min_max_time_slots(t1_min, t1_max); timeSeries1630.min_max_time_slots(t1_min, t1_max); timeSeries2030.min_max_time_slots(t1_min, t1_max); BOOST_CHECK_MESSAGE(t1_min == TimeSlot(15, 30) && t1_max == TimeSlot(20, 30), "Not as expected"); TimeAttr timeSeries(timeSeries1530); TimeAttr timeSeries2(timeSeries1630); TimeAttr timeSeries3(timeSeries2030); bool day_changed = false; // after midnight make sure we keep day_changed for (int m = 1; m < 96; m++) { calendar.update(boost::posix_time::time_duration(boost::posix_time::minutes(30))); if (!day_changed) { day_changed = calendar.dayChanged(); } boost::posix_time::time_duration time = calendar.suiteTime().time_of_day(); timeSeries.calendarChanged(calendar); timeSeries2.calendarChanged(calendar); timeSeries3.calendarChanged(calendar); if (!day_changed) { if (time >= t1_min.duration() && time < t1_max.duration()) { BOOST_CHECK_MESSAGE(timeSeries.checkForRequeue(calendar, t1_min, t1_max), timeSeries.toString() << " checkForRequeue should pass at " << time); BOOST_CHECK_MESSAGE(timeSeries2.checkForRequeue(calendar, t1_min, t1_max), timeSeries2.toString() << " checkForRequeue should pass at " << time); BOOST_CHECK_MESSAGE(timeSeries3.checkForRequeue(calendar, t1_min, t1_max), timeSeries3.toString() << " checkForRequeue should pass at " << time); } else { BOOST_CHECK_MESSAGE(!timeSeries.checkForRequeue(calendar, t1_min, t1_max), timeSeries.toString() << " checkForRequeue should fail at " << time); BOOST_CHECK_MESSAGE(!timeSeries2.checkForRequeue(calendar, t1_min, t1_max), timeSeries2.toString() << " checkForRequeue should fail at " << time); BOOST_CHECK_MESSAGE(!timeSeries3.checkForRequeue(calendar, t1_min, t1_max), timeSeries3.toString() << " checkForRequeue should fail at " << time); } } else { // Once a single slot if Free it *stays* free until explicitly requeued, (i.e by parent repeat/cron) BOOST_CHECK_MESSAGE(timeSeries.isFree(calendar), timeSeries.toString() << " day_changed(" << day_changed << ") isFree should pass at time " << time); BOOST_CHECK_MESSAGE(timeSeries2.isFree(calendar), timeSeries2.toString() << " day_changed(" << day_changed << ") isFree should pass at time " << time); BOOST_CHECK_MESSAGE(timeSeries3.isFree(calendar), timeSeries2.toString() << " day_changed(" << day_changed << ") isFree should pass at time " << time); } } } BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() ecflow-5.19.0/libs/attribute/test/TestVariable.cpp0000664000175000017500000002121215250005516022252 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #include #include #include #include #include "ecflow/attribute/Variable.hpp" #include "ecflow/test/scaffold/Naming.hpp" BOOST_AUTO_TEST_SUITE(U_Attributes) BOOST_AUTO_TEST_SUITE(T_Variable) BOOST_AUTO_TEST_CASE(test_multi_line_variable_values) { ECF_NAME_THIS_TEST(); struct tc { std::string name; std::string value; std::string expected; }; auto values = std::vector{{"name", "value", "edit name 'value'"}, {"name", "", "edit name ''"}, {"name", "value\n", "edit name 'value\\n'"}, {"name", "val1\nxxx\nval2", "edit name 'val1\\nxxx\\nval2'"}}; for (const auto& [name, value, expected] : values) { auto variable = Variable::new_variable(name, value); { auto actual = variable.name(); BOOST_CHECK_MESSAGE(actual == name, "expected name '" << name << "', but found '" << actual << "'"); } { auto actual = variable.value(); BOOST_CHECK_MESSAGE(actual == value, "expected value '" << value << "', but found '" << actual << "'"); } { auto actual = variable.toString(); BOOST_CHECK_MESSAGE(actual == expected, "expected '" << expected << "', but found '" << actual << "'"); } } } BOOST_AUTO_TEST_CASE(test_variable_integer_value) { ECF_NAME_THIS_TEST(); struct tc { std::string value; int expected; }; auto values = std::vector{{"sdsd", 0}, {"0fred0", 0}, {"fted", 0}, {"%value%", 0}, {"a", 0}, {"", 0}, {"0", 0}, {"00", 0}, {"000", 0}, {"0000", 0}, {"0000000000000", 0}, {"0100", 100}, {"0001", 1}, {"2359", 2359}}; for (const auto& [value, expected] : values) { auto variable = Variable::new_variable("name", value); auto actual = variable.value(); BOOST_CHECK_MESSAGE(actual == expected, "expected '0' but found " << actual << " for " << value); } } BOOST_AUTO_TEST_CASE(test_variable_time_value) { ECF_NAME_THIS_TEST(); std::array smstime; for (int h = 0; h < 24; h++) { for (int m = 0; m < 60; m++) { int output_written = snprintf(smstime.data(), smstime.size(), "%02d%02d", h, m); BOOST_CHECK_MESSAGE(output_written == 4, " expected size 4 but found " << output_written); auto variable = Variable::new_variable("name", smstime.data()); int actual = variable.value(); int expected = stoi(std::string(smstime.data(), smstime.size())); BOOST_CHECK_MESSAGE(actual == expected, "expected '" << expected << "' but found '" << actual << "'"); } } } BOOST_AUTO_TEST_CASE(test_variable_default_construction) { ECF_NAME_THIS_TEST(); const Variable variable; BOOST_CHECK(variable.empty()); BOOST_CHECK(variable.name().empty()); BOOST_CHECK(variable.value().empty()); BOOST_CHECK_EQUAL(variable.value(), 0); BOOST_CHECK_EQUAL(variable.toString(), "edit ''"); } BOOST_AUTO_TEST_CASE(test_variable_parameterized_construction) { ECF_NAME_THIS_TEST(); const Variable variable("name", "value"); BOOST_CHECK(!variable.empty()); BOOST_CHECK_EQUAL(variable.name(), "name"); BOOST_CHECK_EQUAL(variable.value(), "value"); } BOOST_AUTO_TEST_CASE(test_variable_new_variable_valid_names) { ECF_NAME_THIS_TEST(); auto valid_names = std::vector{"name", "_name", "name.with.dots", "name123", "Name_1"}; for (const auto& name : valid_names) { auto variable = Variable::new_variable(name, "value"); BOOST_CHECK_EQUAL(variable.name(), name); BOOST_CHECK_EQUAL(variable.value(), "value"); } } BOOST_AUTO_TEST_CASE(test_variable_new_variable_invalid_names) { ECF_NAME_THIS_TEST(); auto invalid_names = std::vector{"", ".name", "name-with-dash", "name with space", "name!"}; for (const auto& name : invalid_names) { BOOST_CHECK_THROW(Variable::new_variable(name, "value"), std::runtime_error); } } BOOST_AUTO_TEST_CASE(test_variable_set_name) { ECF_NAME_THIS_TEST(); auto variable = Variable::new_variable("old_name", "value"); variable.set_name("new_name"); BOOST_CHECK_EQUAL(variable.name(), "new_name"); BOOST_CHECK_EQUAL(variable.value(), "value"); } BOOST_AUTO_TEST_CASE(test_variable_set_name_invalid_is_noop) { ECF_NAME_THIS_TEST(); auto variable = Variable::new_variable("name", "value"); BOOST_CHECK_THROW(variable.set_name(".name"), std::runtime_error); BOOST_CHECK_EQUAL(variable.name(), "name"); BOOST_CHECK_EQUAL(variable.value(), "value"); BOOST_CHECK_THROW(variable.set_name(""), std::runtime_error); } BOOST_AUTO_TEST_CASE(test_variable_set_value) { ECF_NAME_THIS_TEST(); auto variable = Variable::new_variable("name", "old_value"); variable.set_value("new_value"); BOOST_CHECK_EQUAL(variable.value(), "new_value"); BOOST_CHECK_EQUAL(variable.name(), "name"); } BOOST_AUTO_TEST_CASE(test_variable_value_by_ref) { ECF_NAME_THIS_TEST(); auto variable = Variable::new_variable("name", "value"); variable.value_by_ref() = "modified"; BOOST_CHECK_EQUAL(variable.value(), "modified"); } BOOST_AUTO_TEST_CASE(test_variable_equality) { ECF_NAME_THIS_TEST(); Variable a("name", "value"); Variable b("name", "value"); Variable c("other", "value"); Variable d("name", "other"); Variable e; Variable f; BOOST_CHECK(a == b); BOOST_CHECK(e == f); BOOST_CHECK(!(a == c)); BOOST_CHECK(!(a == d)); BOOST_CHECK(!(a == e)); } BOOST_AUTO_TEST_CASE(test_variable_less_than) { ECF_NAME_THIS_TEST(); Variable a("a", "x"); Variable b("b", "y"); Variable c("a", "z"); BOOST_CHECK(a < b); BOOST_CHECK(!(b < a)); BOOST_CHECK(!(a < c)); BOOST_CHECK(!(a < a)); } BOOST_AUTO_TEST_CASE(test_variable_dump) { ECF_NAME_THIS_TEST(); auto variable = Variable::new_variable("name", "123"); BOOST_CHECK_EQUAL(variable.dump(), "edit name '123' value(123)"); } BOOST_AUTO_TEST_CASE(test_variable_write_appends) { ECF_NAME_THIS_TEST(); auto variable = Variable::new_variable("name", "value"); std::string buffer = "prefix "; variable.write(buffer); BOOST_CHECK_EQUAL(buffer, "prefix edit name 'value'"); } BOOST_AUTO_TEST_CASE(test_variable_is_valid_variable_name) { ECF_NAME_THIS_TEST(); std::string msg; BOOST_CHECK(Variable::is_valid_variable_name("valid_name", msg)); BOOST_CHECK(msg.empty()); BOOST_CHECK(!Variable::is_valid_variable_name(".name", msg)); BOOST_CHECK(!msg.empty()); BOOST_CHECK(!Variable::is_valid_variable_name("", msg)); } BOOST_AUTO_TEST_CASE(test_variable_empty_singleton) { ECF_NAME_THIS_TEST(); const Variable& empty1 = Variable::EMPTY(); const Variable& empty2 = Variable::EMPTY(); BOOST_CHECK(empty1.empty()); BOOST_CHECK(empty2.empty()); BOOST_CHECK(&empty1 == &empty2); BOOST_CHECK_EQUAL(empty1.name(), ""); BOOST_CHECK_EQUAL(empty1.value(), ""); } BOOST_AUTO_TEST_CASE(test_variable_map) { ECF_NAME_THIS_TEST(); VariableMap map(Variable("a", "1"), Variable("b", "2")); BOOST_CHECK(!map.empty()); BOOST_CHECK_EQUAL(map.size(), 2); BOOST_CHECK_EQUAL(map["a"].name(), "a"); BOOST_CHECK_EQUAL(map["a"].value(), "1"); BOOST_CHECK_EQUAL(map["b"].name(), "b"); BOOST_CHECK_EQUAL(map["b"].value(), "2"); map.set_value("X"); BOOST_CHECK_EQUAL(map["a"].value(), "X"); BOOST_CHECK_EQUAL(map["b"].value(), "X"); BOOST_CHECK_THROW(static_cast(map["c"]), std::runtime_error); size_t count = 0; for (const auto& var : map) { BOOST_CHECK(!var.empty()); ++count; } BOOST_CHECK_EQUAL(count, 2); } BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() ecflow-5.19.0/libs/attribute/test/TestCron.cpp0000664000175000017500000007071215250005516021437 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #include #include #include #include "ecflow/attribute/CronAttr.hpp" #include "ecflow/core/Calendar.hpp" #include "ecflow/core/Str.hpp" #include "ecflow/core/TimeSeries.hpp" #include "ecflow/test/scaffold/Naming.hpp" using namespace ecf; BOOST_AUTO_TEST_SUITE(U_Attributes) BOOST_AUTO_TEST_SUITE(T_Cron) BOOST_AUTO_TEST_CASE(test_cron_parsing) { ECF_NAME_THIS_TEST(); TimeSlot start(10, 10); TimeSlot finish(23, 10); TimeSlot incr(0, 1); { CronAttr cron; CronAttr parsedCron; BOOST_CHECK_MESSAGE(parsedCron.structureEquals(cron), "Expected " << cron.toString() << " but found " << parsedCron.toString()); } { CronAttr cron; cron.add_time_series(10, 10, false); CronAttr cron1(10, 10, false); CronAttr parsedCron = CronAttr::create("cron 10:10"); BOOST_CHECK_MESSAGE(parsedCron.structureEquals(cron), "Expected " << cron.toString() << " but found " << parsedCron.toString()); BOOST_CHECK_MESSAGE(parsedCron.structureEquals(cron1), "Expected " << cron1.toString() << " but found " << parsedCron.toString()); } { CronAttr cron; cron.add_time_series(10, 10, true); CronAttr cron1(10, 10, true); CronAttr parsedCron = CronAttr::create("cron +10:10"); BOOST_CHECK_MESSAGE(parsedCron.structureEquals(cron), "Expected " << cron.toString() << " but found " << parsedCron.toString()); BOOST_CHECK_MESSAGE(parsedCron.structureEquals(cron1), "Expected " << cron1.toString() << " but found " << parsedCron.toString()); } { CronAttr cron; cron.addTimeSeries(start, finish, incr); CronAttr cron1(start, finish, incr); CronAttr parsedCron = CronAttr::create("cron 10:10 23:10 00:01"); BOOST_CHECK_MESSAGE(parsedCron.structureEquals(cron), "Expected " << cron.toString() << " but found " << parsedCron.toString()); BOOST_CHECK_MESSAGE(parsedCron.structureEquals(cron1), "Expected " << cron1.toString() << " but found " << parsedCron.toString()); } { std::vector week_days{0, 1, 2, 3, 4, 5, 6}; CronAttr cron; cron.addWeekDays(week_days); cron.add_time_series(10, 10, true); CronAttr cron1(10, 10, true); cron1.addWeekDays(week_days); std::string cron_str = "cron -w 0,1,2,3,4,5,6 +10:10"; CronAttr parsedCron = CronAttr::create(cron_str); BOOST_CHECK_MESSAGE(parsedCron.toString() == cron_str, "Expected " << cron_str << " but found " << parsedCron.toString()); BOOST_CHECK_MESSAGE(parsedCron.structureEquals(cron), "Expected " << cron.toString() << " but found " << parsedCron.toString()); BOOST_CHECK_MESSAGE(parsedCron.structureEquals(cron1), "Expected " << cron1.toString() << " but found " << parsedCron.toString()); } { std::vector week_days{0, 1, 2, 3, 4, 5, 6}; CronAttr cron; cron.add_last_week_days_of_month(week_days); cron.add_time_series(10, 10, true); std::string cron_str = "cron -w 0L,1L,2L,3L,4L,5L,6L +10:10"; CronAttr parsedCron = CronAttr::create(cron_str); BOOST_CHECK_MESSAGE(parsedCron.toString() == cron_str, "Expected " << cron_str << " but found " << parsedCron.toString()); BOOST_CHECK_MESSAGE(parsedCron.structureEquals(cron), "Expected " << cron.toString() << " but found " << parsedCron.toString()); } { std::vector week_days{0, 1, 2}; std::vector last_week_days_of_month{3, 4, 5, 6}; CronAttr cron; cron.addWeekDays(week_days); cron.add_last_week_days_of_month(last_week_days_of_month); cron.add_time_series(10, 10, true); std::string cron_str = "cron -w 0,1,2,3L,4L,5L,6L +10:10"; CronAttr parsedCron = CronAttr::create(cron_str); BOOST_CHECK_MESSAGE(parsedCron.toString() == cron_str, "Expected " << cron_str << " but found " << parsedCron.toString()); BOOST_CHECK_MESSAGE(parsedCron.structureEquals(cron), "Expected " << cron.toString() << " but found " << parsedCron.toString()); } { std::vector week_days{0, 1, 2}; std::vector last_week_days_of_month{3, 4}; CronAttr cron; cron.addWeekDays(week_days); cron.add_last_week_days_of_month(last_week_days_of_month); cron.add_time_series(10, 10, true); try { CronAttr parsedCron = CronAttr::create("cron -w 0,1,2,0L,1L,2L +10:10"); BOOST_CHECK_MESSAGE(false, "Expected exception for " << parsedCron.toString() << " cron " << cron.toString()); } catch (...) { } } { std::vector days_of_month{1, 3, 4, 5, 6, 24, 25}; CronAttr cron; cron.addDaysOfMonth(days_of_month); cron.add_last_day_of_month(); cron.add_time_series(10, 10, true); CronAttr cron1(10, 10, true); cron1.addDaysOfMonth(days_of_month); cron1.add_last_day_of_month(); CronAttr parsedCron = CronAttr::create("cron -d L,1,3,4,5,6,24,25 +10:10"); CronAttr parsedCron2 = CronAttr::create("cron -d 1,3,4,5,6,24,25,L +10:10"); BOOST_CHECK_MESSAGE(parsedCron.structureEquals(cron), "Expected " << cron.toString() << " but found " << parsedCron.toString()); BOOST_CHECK_MESSAGE(parsedCron2.structureEquals(cron), "Expected " << cron.toString() << " but found " << parsedCron2.toString()); BOOST_CHECK_MESSAGE(parsedCron.structureEquals(cron1), "Expected " << cron1.toString() << " but found " << parsedCron.toString()); } std::vector months{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; { CronAttr cron; cron.addMonths(months); cron.add_time_series(10, 10, true); CronAttr cron1(10, 10, true); cron1.addMonths(months); CronAttr parsedCron = CronAttr::create("cron -m 1,2,3,4,5,6,7,8,9,10,11,12 +10:10"); BOOST_CHECK_MESSAGE(parsedCron.structureEquals(cron), "Expected " << cron.toString() << " but found " << parsedCron.toString()); BOOST_CHECK_MESSAGE(parsedCron.structureEquals(cron1), "Expected " << cron1.toString() << " but found " << parsedCron.toString()); } { std::vector week_days{0, 1, 2}; std::vector last_week_days_of_month{3, 4, 5, 6}; std::vector days_of_month{1, 3, 4, 5, 6, 24, 25}; CronAttr cron; cron.addWeekDays(week_days); cron.add_last_week_days_of_month(last_week_days_of_month); cron.addDaysOfMonth(days_of_month); cron.addMonths(months); cron.add_last_day_of_month(); cron.addTimeSeries(start, finish, incr); CronAttr cron1(start, finish, incr); cron1.addWeekDays(week_days); cron1.add_last_week_days_of_month(last_week_days_of_month); cron1.addDaysOfMonth(days_of_month); cron1.addMonths(months); cron1.add_last_day_of_month(); CronAttr parsedCron = CronAttr::create( "cron -w 0,1,2,3L,4L,5L,6L -d 1,3,4,5,6,24,L,25 -m 1,2,3,4,5,6,7,8,9,10,11,12 10:10 23:10 00:01"); BOOST_CHECK_MESSAGE(parsedCron.structureEquals(cron), "Expected " << cron.toString() << " but found " << parsedCron.toString()); BOOST_CHECK_MESSAGE(parsedCron.structureEquals(cron1), "Expected " << cron1.toString() << " but found " << parsedCron.toString()); } { std::vector cron_vec{ "cron -w 0,1,2,3L,4L,5L,6L -d 1,3,4,5,6,24,L,25 -m 1,2,3,4,5,6,7,8,9,10,11,12 10:10 23:10 00:01", "cron -w 0L,1L,2L,3L,4L,5L,6L -d 1,L 23:00"}; for (const auto& cron : cron_vec) { try { CronAttr parsedCron = CronAttr::create(cron); } catch (...) { BOOST_CHECK_MESSAGE(false, "Could not parse " << cron); } } } } BOOST_AUTO_TEST_CASE(test_cron_state_parsing) { ECF_NAME_THIS_TEST(); size_t index = 1; // to get over the cron { std::string line = "cron 04:30 # isValid:false"; std::vector lineTokens; ecf::algorithm::split_at(lineTokens, line); bool parse_state = true; CronAttr parsed_cronAttr; CronAttr::parse(parsed_cronAttr, lineTokens, index, parse_state); CronAttr expected; TimeSeries series(4, 30); series.set_isValid(false); // to match isValid:false expected.addTimeSeries(series); BOOST_CHECK_MESSAGE(parsed_cronAttr == expected, "Expected " << expected.dump() << " : " << expected.time().dump() << " but found " << parsed_cronAttr.dump() << " : " << parsed_cronAttr.time().dump()); } { std::string line = "cron 04:30 # free isValid:false"; std::vector lineTokens; ecf::algorithm::split_at(lineTokens, line); bool parse_state = true; CronAttr parsed_cronAttr; CronAttr::parse(parsed_cronAttr, lineTokens, index, parse_state); CronAttr expected; TimeSeries series(4, 30); series.set_isValid(false); // to match isValid:false expected.addTimeSeries(series); expected.setFree(); BOOST_CHECK_MESSAGE(parsed_cronAttr == expected, "Expected " << expected.dump() << " : " << expected.time().dump() << " but found " << parsed_cronAttr.dump() << " : " << parsed_cronAttr.time().dump()); } { std::string line = "cron 00:01 23:59 01:00 # nextTimeSlot/12:01"; std::vector lineTokens; ecf::algorithm::split_at(lineTokens, line); bool parse_state = true; CronAttr parsed_cronAttr; CronAttr::parse(parsed_cronAttr, lineTokens, index, parse_state); CronAttr expected; TimeSlot start(0, 1); TimeSlot finish(23, 59); TimeSlot incr(1, 0); TimeSeries series(start, finish, incr); series.set_next_time_slot(TimeSlot(12, 1)); expected.addTimeSeries(series); BOOST_CHECK_MESSAGE(parsed_cronAttr == expected, "Expected " << expected.dump() << " : " << expected.time().dump() << " but found " << parsed_cronAttr.dump() << " : " << parsed_cronAttr.time().dump()); } { std::string line = "cron 00:01 23:59 01:00 # free nextTimeSlot/12:01"; std::vector lineTokens; ecf::algorithm::split_at(lineTokens, line); bool parse_state = true; CronAttr parsed_cronAttr; CronAttr::parse(parsed_cronAttr, lineTokens, index, parse_state); CronAttr expected; TimeSlot start(0, 1); TimeSlot finish(23, 59); TimeSlot incr(1, 0); TimeSeries series(start, finish, incr); series.set_next_time_slot(TimeSlot(12, 1)); expected.addTimeSeries(series); expected.setFree(); BOOST_CHECK_MESSAGE(parsed_cronAttr == expected, "Expected " << expected.dump() << " : " << expected.time().dump() << " but found " << parsed_cronAttr.dump() << " : " << parsed_cronAttr.time().dump()); } { std::string line = "cron 00:00 18:00 06:00 # isValid:false nextTimeSlot/24:00"; std::vector lineTokens; ecf::algorithm::split_at(lineTokens, line); bool parse_state = true; CronAttr parsed_cronAttr; CronAttr::parse(parsed_cronAttr, lineTokens, index, parse_state); CronAttr expected; TimeSlot start(0, 0); TimeSlot finish(18, 0); TimeSlot incr(6, 0); TimeSeries series(start, finish, incr); series.set_next_time_slot(TimeSlot(24, 0)); series.set_isValid(false); // to match isValid:false expected.addTimeSeries(series); BOOST_CHECK_MESSAGE(parsed_cronAttr == expected, "Expected " << expected.dump() << " : " << expected.time().dump() << " but found " << parsed_cronAttr.dump() << " : " << parsed_cronAttr.time().dump()); } { // ECFLOW-1693 // Could not parse 'cron +00:00 23:59 00:01 # isValid:false nextTimeSlot/523:40' around line number 654 std::string line = "cron +00:00 23:59 00:01 # isValid:false nextTimeSlot/523:40"; std::vector lineTokens; ecf::algorithm::split_at(lineTokens, line); bool parse_state = true; CronAttr parsed_cronAttr; CronAttr::parse(parsed_cronAttr, lineTokens, index, parse_state); CronAttr expected; TimeSlot start(0, 0); TimeSlot finish(23, 59); TimeSlot incr(0, 1); TimeSeries series(start, finish, incr, true); series.set_next_time_slot(TimeSlot(523, 40)); series.set_isValid(false); // to match isValid:false expected.addTimeSeries(series); BOOST_CHECK_MESSAGE(parsed_cronAttr == expected, "Expected " << expected.dump() << " : " << expected.time().dump() << " but found " << parsed_cronAttr.dump() << " : " << parsed_cronAttr.time().dump()); } } BOOST_AUTO_TEST_CASE(test_cron_once_free_stays_free) { ECF_NAME_THIS_TEST(); Calendar calendar; calendar.init(boost::posix_time::ptime(boost::gregorian::date(2010, 2, 10), boost::posix_time::minutes(0)), Calendar::REAL); TimeSeries timeSeriesX(TimeSlot(10, 0), TimeSlot(20, 0), TimeSlot(1, 0), false /* relative */); TimeSeries timeSeries2X(TimeSlot(11, 0), TimeSlot(15, 0), TimeSlot(1, 0), false /* relative */); TimeSeries timeSeries3X(TimeSlot(15, 0), false /* relative */); TimeSeries timeSeries4X(TimeSlot(0, 0), false /* relative */); CronAttr timeSeries; timeSeries.addTimeSeries(timeSeriesX); CronAttr timeSeries2; timeSeries2.addTimeSeries(timeSeries2X); CronAttr timeSeries3; timeSeries3.addTimeSeries(timeSeries3X); CronAttr timeSeries4; timeSeries4.addTimeSeries(timeSeries4X); std::vector timeSeries_free_slots; std::vector timeSeries2_free_slots; timeSeries.time_series().free_slots(timeSeries_free_slots); timeSeries2.time_series().free_slots(timeSeries2_free_slots); BOOST_CHECK_MESSAGE(timeSeries_free_slots.size() == 11, "Expected 11 free slots for " << timeSeries.toString() << " but found " << timeSeries_free_slots.size()); BOOST_CHECK_MESSAGE(timeSeries2_free_slots.size() == 5, "Expected 5 free slots for " << timeSeries2.toString() << " but found " << timeSeries_free_slots.size()); bool day_changed = false; // after midnight make sure we keep day_changed for (int m = 1; m < 96; m++) { calendar.update(boost::posix_time::time_duration(boost::posix_time::minutes(30))); if (!day_changed) { day_changed = calendar.dayChanged(); } boost::posix_time::time_duration time = calendar.suiteTime().time_of_day(); timeSeries.calendarChanged(calendar); timeSeries2.calendarChanged(calendar); timeSeries3.calendarChanged(calendar); timeSeries4.calendarChanged(calendar); // cron should always reque regardless of time series BOOST_CHECK_MESSAGE(timeSeries.checkForRequeue(calendar), timeSeries.toString() << " checkForRequeue should be true at time " << time); BOOST_CHECK_MESSAGE(timeSeries2.checkForRequeue(calendar), timeSeries2.toString() << " checkForRequeue should be true at time " << time); BOOST_CHECK_MESSAGE(timeSeries3.checkForRequeue(calendar), timeSeries3.toString() << " checkForRequeue should be true at time " << time); BOOST_CHECK_MESSAGE(timeSeries4.checkForRequeue(calendar), timeSeries4.toString() << " checkForRequeue should be true at time " << time); // ********************************************************************************** // When a cron (regardless of whether its single slot or time series) is free, it stays free, // until explicitly re-queued, // *********************************************************************************** if (time < timeSeries.time_series().start().duration()) { if (!day_changed) { BOOST_CHECK_MESSAGE(!timeSeries.isFree(calendar), timeSeries.toString() << " should NOT be free at time " << time); } else { BOOST_CHECK_MESSAGE(timeSeries.isFree(calendar), timeSeries.toString() << " should be free at time " << time); } } else if (time >= timeSeries.time_series().start().duration()) { BOOST_CHECK_MESSAGE(timeSeries.isFree(calendar), timeSeries.toString() << " should be free at time " << time); } if (time < timeSeries2.time_series().start().duration()) { if (!day_changed) { BOOST_CHECK_MESSAGE(!timeSeries2.isFree(calendar), timeSeries2.toString() << " should NOT be free at time " << time); } else { BOOST_CHECK_MESSAGE(timeSeries.isFree(calendar), timeSeries.toString() << " should be free at time " << time); } } else if (time >= timeSeries2.time_series().start().duration()) { BOOST_CHECK_MESSAGE(timeSeries2.isFree(calendar), timeSeries2.toString() << " should be free at time " << time); } if (!day_changed) { if (time == timeSeries3.time_series().start().duration()) { BOOST_CHECK_MESSAGE(timeSeries3.isFree(calendar), timeSeries3.toString() << " should be free at time " << time); } else if (time > timeSeries3.time_series().start().duration()) { BOOST_CHECK_MESSAGE(timeSeries3.isFree(calendar), timeSeries3.toString() << " isFree, once free should stay free at time " << time); } } else { BOOST_CHECK_MESSAGE(timeSeries3.isFree(calendar), timeSeries3.toString() << " should be free at time after day change " << time); } // single slot at midnight, Once a single slot if Free it *stays* free until explicitly requeued, (i.e by parent // repeat/cron) if (!day_changed) { if (time == timeSeries4.time_series().start().duration()) { BOOST_CHECK_MESSAGE(timeSeries4.isFree(calendar), timeSeries4.toString() << " should be free at time " << time); } else { BOOST_CHECK_MESSAGE(!timeSeries4.isFree(calendar), timeSeries4.toString() << " day_changed(" << day_changed << ") isFree should fail at time " << time); } } else { BOOST_CHECK_MESSAGE(timeSeries4.isFree(calendar), timeSeries4.toString() << " day_changed(" << day_changed << ") isFree should pass at time " << time); } } } BOOST_AUTO_TEST_CASE(test_cron_time_series) { ECF_NAME_THIS_TEST(); // See TimeAttr.hpp for rules concerning isFree() and checkForReque() // test time attr isFree(), and checkForRequeue Calendar calendar; calendar.init(boost::posix_time::ptime(boost::gregorian::date(2010, 2, 10), boost::posix_time::minutes(0)), Calendar::REAL); TimeSeries timeSeriesX(TimeSlot(10, 0), TimeSlot(20, 0), TimeSlot(1, 0), false /* relative */); TimeSeries timeSeries2X(TimeSlot(11, 0), TimeSlot(15, 0), TimeSlot(1, 0), false /* relative */); TimeSeries timeSeries3X(TimeSlot(15, 0), false /* relative */); TimeSeries timeSeries4X(TimeSlot(0, 0), false /* relative */); CronAttr timeSeries; timeSeries.addTimeSeries(timeSeriesX); CronAttr timeSeries2; timeSeries2.addTimeSeries(timeSeries2X); CronAttr timeSeries3; timeSeries3.addTimeSeries(timeSeries3X); CronAttr timeSeries4; timeSeries4.addTimeSeries(timeSeries4X); std::vector timeSeries_free_slots; std::vector timeSeries2_free_slots; timeSeries.time_series().free_slots(timeSeries_free_slots); timeSeries2.time_series().free_slots(timeSeries2_free_slots); BOOST_CHECK_MESSAGE(timeSeries_free_slots.size() == 11, "Expected 11 free slots for " << timeSeries.toString() << " but found " << timeSeries_free_slots.size()); BOOST_CHECK_MESSAGE(timeSeries2_free_slots.size() == 5, "Expected 5 free slots for " << timeSeries2.toString() << " but found " << timeSeries_free_slots.size()); bool day_changed = false; // after midnight make sure we keep day_changed for (int m = 1; m < 96; m++) { calendar.update(boost::posix_time::time_duration(boost::posix_time::minutes(30))); if (!day_changed) { day_changed = calendar.dayChanged(); } boost::posix_time::time_duration time = calendar.suiteTime().time_of_day(); timeSeries.calendarChanged(calendar); timeSeries2.calendarChanged(calendar); timeSeries3.calendarChanged(calendar); timeSeries4.calendarChanged(calendar); // cron should always reque regardless of time series BOOST_CHECK_MESSAGE(timeSeries.checkForRequeue(calendar), timeSeries.toString() << " checkForRequeue should be true at time " << time); BOOST_CHECK_MESSAGE(timeSeries2.checkForRequeue(calendar), timeSeries2.toString() << " checkForRequeue should be true at time " << time); BOOST_CHECK_MESSAGE(timeSeries3.checkForRequeue(calendar), timeSeries3.toString() << " checkForRequeue should be true at time " << time); BOOST_CHECK_MESSAGE(timeSeries4.checkForRequeue(calendar), timeSeries4.toString() << " checkForRequeue should be true at time " << time); // ********************************************************************************** // When a cron (regardless of whether its single slot or time series) is free, it stays free // However in order to test crons with time series, we will re-queue ate the end of this loop // *********************************************************************************** if (time < timeSeries.time_series().start().duration()) { BOOST_CHECK_MESSAGE(!timeSeries.isFree(calendar), timeSeries.toString() << " should NOT be free at time " << time); } else if (time >= timeSeries.time_series().start().duration() && time <= timeSeries.time_series().finish().duration()) { bool matches_free_slot = false; for (const auto& timeSeries_free_slot : timeSeries_free_slots) { if (time == timeSeries_free_slot) { matches_free_slot = true; break; } } if (matches_free_slot) { BOOST_CHECK_MESSAGE(timeSeries.isFree(calendar), timeSeries.toString() << " should be free at time " << time); } else { BOOST_CHECK_MESSAGE(!timeSeries.isFree(calendar), timeSeries.toString() << " should be fail at time " << time); } } else { BOOST_CHECK_MESSAGE(!timeSeries.isFree(calendar), timeSeries.toString() << " should be holding at time " << time); } if (time < timeSeries2.time_series().start().duration()) { BOOST_CHECK_MESSAGE(!timeSeries2.isFree(calendar), timeSeries2.toString() << " should NOT be free at time " << time); } else if (time >= timeSeries2.time_series().start().duration() && time <= timeSeries2.time_series().finish().duration()) { bool matches_free_slot = false; for (const auto& timeSeries2_free_slot : timeSeries2_free_slots) { if (time == timeSeries2_free_slot) { matches_free_slot = true; break; } } if (matches_free_slot) { BOOST_CHECK_MESSAGE(timeSeries2.isFree(calendar), timeSeries2.toString() << " should be free at time " << time); } else { BOOST_CHECK_MESSAGE(!timeSeries2.isFree(calendar), timeSeries2.toString() << " should be fail at time " << time); } } else { BOOST_CHECK_MESSAGE(!timeSeries2.isFree(calendar), timeSeries2.toString() << " should be holding at time " << time); } // Single slot, Once a single slot if Free it *stays* free until explicitly requeued, (i.e by parent // repeat/cron) if (!day_changed) { if (time == timeSeries3.time_series().start().duration()) { BOOST_CHECK_MESSAGE(timeSeries3.isFree(calendar), timeSeries3.toString() << " should be free at time " << time); } else if (time > timeSeries3.time_series().start().duration()) { BOOST_CHECK_MESSAGE(timeSeries3.isFree(calendar), timeSeries3.toString() << " isFree, once free should stay free at time " << time); } } else { BOOST_CHECK_MESSAGE(timeSeries3.isFree(calendar), timeSeries3.toString() << " should be free at time after day change " << time); } // single slot at midnight, Once a single slot if Free it *stays* free until explicitly requeued, (i.e by parent // repeat/cron) if (!day_changed) { if (time == timeSeries4.time_series().start().duration()) { BOOST_CHECK_MESSAGE(timeSeries4.isFree(calendar), timeSeries4.toString() << " should be free at time " << time); } else { BOOST_CHECK_MESSAGE(!timeSeries4.isFree(calendar), timeSeries4.toString() << " day_changed(" << day_changed << ") isFree should fail at time " << time); } } else { BOOST_CHECK_MESSAGE(timeSeries4.isFree(calendar), timeSeries4.toString() << " day_changed(" << day_changed << ") isFree should pass at time " << time); } // Typically when a cron is free, it stays free, until it is re-queued // However in order to test isFree for cron with time inetrvals, we need to re-queue timeSeries.requeue(calendar); timeSeries2.requeue(calendar); // Do not requeue cron 00, and cron 15, so that we can check for free } } BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() ecflow-5.19.0/libs/attribute/test/TestSizeOf.cpp0000664000175000017500000000420415250005516021726 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #include #include #include #include "ecflow/attribute/DayAttr.hpp" #include "ecflow/core/TimeSeries.hpp" #include "ecflow/test/scaffold/Naming.hpp" #define STRINGIFY(x) #x template inline const char* typeName() { return "unknown"; } #define TYPE_STRING(T) \ template <> \ inline const char* typeName() { \ return STRINGIFY(T); \ } TYPE_STRING(std::ofstream) TYPE_STRING(std::string) TYPE_STRING(std::vector) TYPE_STRING(std::vector) TYPE_STRING(std::weak_ptr) TYPE_STRING(std::nullptr_t) TYPE_STRING(std::unique_ptr) TYPE_STRING(double) TYPE_STRING(long) TYPE_STRING(int) TYPE_STRING(unsigned int) TYPE_STRING(bool) TYPE_STRING(ecf::TimeSeries) TYPE_STRING(DayAttr) template void inspect_size_of(T t = T{}) { #if PRINT_SIZEOF_RESULTS ECF_TEST_DBG(" * sizeof(" << typeName() << ") = " << sizeof(T)); #endif BOOST_REQUIRE_EQUAL(sizeof(T), sizeof(T)); } BOOST_AUTO_TEST_SUITE(U_Attributes) BOOST_AUTO_TEST_SUITE(T_SizeOf) BOOST_AUTO_TEST_CASE(test_size_of) { ECF_NAME_THIS_TEST(); inspect_size_of(); inspect_size_of(); inspect_size_of>(); inspect_size_of>(); inspect_size_of>(); inspect_size_of(); inspect_size_of(nullptr); inspect_size_of>(); inspect_size_of(); inspect_size_of(); inspect_size_of(); inspect_size_of(); inspect_size_of(); inspect_size_of(); inspect_size_of(); } BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() ecflow-5.19.0/libs/attribute/test/TestDateAttr.cpp0000664000175000017500000000755315250005516022251 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #include #include #include #include "ecflow/attribute/DateAttr.hpp" #include "ecflow/core/PrintStyle.hpp" #include "ecflow/core/Str.hpp" #include "ecflow/node/formatter/DefsWriter.hpp" #include "ecflow/test/scaffold/Naming.hpp" using namespace ecf; BOOST_AUTO_TEST_SUITE(U_Attributes) BOOST_AUTO_TEST_SUITE(T_DateAttr) BOOST_AUTO_TEST_CASE(test_date) { ECF_NAME_THIS_TEST(); { DateAttr empty; DateAttr empty2; BOOST_CHECK_MESSAGE(empty == empty2, "Equality failed"); BOOST_CHECK_MESSAGE(empty.day() == 0 && empty.month() == 0 && empty.year() == 0, ""); } { for (int day = 0; day < 28; day++) { for (int month = 0; month < 13; month++) { int year = 2017; if (month == 0) { year = 0; } std::ostringstream ss; if (day == 0) { ss << "*"; } else { ss << day; } ss << "."; if (month == 0) { ss << "*"; } else { ss << month; } ss << "."; if (year == 0) { ss << "*"; } else { ss << year; } DateAttr date1(day, month, year); DateAttr date2(ss.str()); BOOST_CHECK_MESSAGE(date1 == date2, "Equality failed"); BOOST_CHECK_MESSAGE(date1.name() == date2.name(), "name failed"); } } } } static DateAttr print_and_parse_attr(DateAttr& date) { std::string output; ecf::write_t(output, date, PrintStyle::MIGRATE); output.erase(output.begin() + output.size() - 1); // remove trailing newline std::vector tokens; ecf::algorithm::split_at(tokens, output); return DateAttr::create(tokens, true /*read state*/); } BOOST_AUTO_TEST_CASE(test_date_parsing) { ECF_NAME_THIS_TEST(); { DateAttr date("12.12.2019"); date.setFree(); DateAttr parsed_date = print_and_parse_attr(date); BOOST_CHECK_MESSAGE(date == parsed_date, "Parse failed expected " << date.dump() << " but found " << parsed_date.dump()); } { DateAttr date("12.12.2019"); date.setFree(); DateAttr parsed_date = print_and_parse_attr(date); BOOST_CHECK_MESSAGE(date == parsed_date, "Parse failed expected " << date.dump() << " but found " << parsed_date.dump()); } { DateAttr date("12.12.2019"); DateAttr parsed_date = print_and_parse_attr(date); BOOST_CHECK_MESSAGE(date == parsed_date, "Parse failed expected " << date.dump() << " but found " << parsed_date.dump()); } } BOOST_AUTO_TEST_CASE(test_date_errors) { ECF_NAME_THIS_TEST(); { BOOST_REQUIRE_THROW(DateAttr("-1.2.*"), std::runtime_error); BOOST_REQUIRE_THROW(DateAttr("32.2.*"), std::runtime_error); BOOST_REQUIRE_THROW(DateAttr("1.-1.*"), std::runtime_error); BOOST_REQUIRE_THROW(DateAttr("1.13.*"), std::runtime_error); BOOST_REQUIRE_THROW(DateAttr("1.13.-1"), std::runtime_error); BOOST_REQUIRE_THROW(DateAttr("1.13.99999999"), std::runtime_error); } } BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() ecflow-5.19.0/libs/attribute/test/TestMigration.cpp0000664000175000017500000003721015250005516022463 0ustar alastairalastair/* * Copyright 2009- ECMWF. * * This software is licensed under the terms of the Apache Licence version 2.0 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. * In applying this licence, ECMWF does not waive the privileges and immunities * granted to it by virtue of its status as an intergovernmental organisation * nor does it submit to any jurisdiction. */ #include #include "ecflow/attribute/AutoArchiveAttr.hpp" #include "ecflow/attribute/AutoCancelAttr.hpp" #include "ecflow/attribute/ClockAttr.hpp" #include "ecflow/attribute/CronAttr.hpp" #include "ecflow/attribute/DateAttr.hpp" #include "ecflow/attribute/DayAttr.hpp" #include "ecflow/attribute/GenericAttr.hpp" #include "ecflow/attribute/LateAttr.hpp" #include "ecflow/attribute/NodeAttr.hpp" #include "ecflow/attribute/QueueAttr.hpp" #include "ecflow/attribute/RepeatAttr.hpp" #include "ecflow/attribute/TimeAttr.hpp" #include "ecflow/attribute/TodayAttr.hpp" #include "ecflow/attribute/Variable.hpp" #include "ecflow/attribute/VerifyAttr.hpp" #include "ecflow/attribute/ZombieAttr.hpp" #include "ecflow/core/File.hpp" #include "ecflow/core/TimeSlot.hpp" #include "ecflow/core/cereal_boost_time.hpp" #include "ecflow/test/scaffold/Naming.hpp" #include "ecflow/test/scaffold/Serialisation.hpp" using namespace ecf; // #define UPDATE_TESTS 1 BOOST_AUTO_TEST_SUITE(U_Attributes) BOOST_AUTO_TEST_SUITE(T_Migration) // These test are used for future release. They help to ensure that we have // backward compatibility.i.e future release can open file, created by an earlier release // BOOST_AUTO_TEST_CASE(test_migration_restore_def_con) { ECF_NAME_THIS_TEST(); std::string file_name = File::test_data("libs/attribute/test/data/migration/default_constructor_1_2_2/", "libs/attribute"); // BOOST_CHECK_MESSAGE(File::createDirectories(file_name ),"Could not create directory " << file_name); // Create migration data #ifdef UPDATE_TESTS doSave(file_name + "VerifyAttr", VerifyAttr()); doSave(file_name + "TodayAttr", TodayAttr()); doSave(file_name + "TimeAttr", TimeAttr()); doSave(file_name + "RepeatDate", RepeatDate()); doSave(file_name + "RepeatDateList", RepeatDateList()); doSave(file_name + "RepeatInteger", RepeatInteger()); doSave(file_name + "RepeatEnumerated", RepeatEnumerated()); doSave(file_name + "RepeatString", RepeatString()); doSave(file_name + "LateAttr", LateAttr()); doSave(file_name + "DayAttr", DayAttr()); doSave(file_name + "DateAttr", DateAttr()); doSave(file_name + "CronAttr", CronAttr()); doSave(file_name + "ClockAttr", ClockAttr()); doSave(file_name + "AutoCancelAttr", AutoCancelAttr()); doSave(file_name + "AutoArchiveAttr", AutoArchiveAttr()); doSave(file_name + "Label", Label()); doSave(file_name + "Variable", Variable()); doSave(file_name + "Event", Event()); doSave(file_name + "Meter", Meter()); doSave(file_name + "ZombieAttr", ZombieAttr()); doSave(file_name + "QueueAttr", QueueAttr()); doSave(file_name + "GenericAttr", GenericAttr()); #endif do_restore(file_name + "VerifyAttr", VerifyAttr()); do_restore(file_name + "TodayAttr", TodayAttr()); do_restore(file_name + "TimeAttr", TimeAttr()); do_restore(file_name + "RepeatDate", RepeatDate()); do_restore(file_name + "RepeatDateList", RepeatDateList()); do_restore(file_name + "RepeatInteger", RepeatInteger()); do_restore(file_name + "RepeatEnumerated", RepeatEnumerated()); do_restore(file_name + "RepeatString", RepeatString()); do_restore(file_name + "LateAttr", LateAttr()); do_restore(file_name + "DayAttr", DayAttr()); do_restore(file_name + "DateAttr", DateAttr()); do_restore(file_name + "CronAttr", CronAttr()); do_restore(file_name + "AutoCancelAttr", AutoCancelAttr()); do_restore(file_name + "AutoArchiveAttr", AutoArchiveAttr()); do_restore