diff --git a/CMakeLists.txt b/CMakeLists.txt index d08dcd14cc..26cd020d8d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -488,15 +488,16 @@ target_include_directories( INTERFACE $ $ $/${CMAKE_INSTALL_INCLUDE_DIR}> - $ - $ - $ - $ ) # the utilities/gmlc is to account for the transfer of the header to a known location on install target_include_directories( - helics_base SYSTEM INTERFACE $ + helics_base SYSTEM + INTERFACE $ + $ + $ + $ + $ ) target_link_libraries(helics_base INTERFACE toml11::toml11) diff --git a/config/cmake/addBoost.cmake b/config/cmake/addBoost.cmake index 6a9abc3f6e..88a812187e 100644 --- a/config/cmake/addBoost.cmake +++ b/config/cmake/addBoost.cmake @@ -163,7 +163,8 @@ if(NOT Boost_FOUND) if(Boost_VERSION_MINOR GREATER_EQUAL ${BOOST_MINIMUM_VERSION}) add_library(Boost::headers INTERFACE IMPORTED) set_target_properties( - Boost::headers PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${Boost_INCLUDE_DIR}" + Boost::headers PROPERTIES INTERFACE_SYSTEM_INCLUDE_DIRECTORIES + "${Boost_INCLUDE_DIR}" ) add_library(Boost::boost INTERFACE IMPORTED) set_target_properties(Boost::boost PROPERTIES INTERFACE_LINK_LIBRARIES Boost::headers) diff --git a/src/helics/apps/CMakeLists.txt b/src/helics/apps/CMakeLists.txt index a9c122efa3..ef51b51075 100644 --- a/src/helics/apps/CMakeLists.txt +++ b/src/helics/apps/CMakeLists.txt @@ -190,11 +190,14 @@ if(HELICS_BUILD_APP_LIBRARY) target_include_directories( helicscpp-apps INTERFACE $ - $ $/${CMAKE_INSTALL_INCLUDEDIR}> PRIVATE $ ) + target_include_directories( + helicscpp-apps SYSTEM INTERFACE $ + ) + set_target_properties( helicscpp-apps PROPERTIES VERSION ${HELICS_VERSION} SOVERSION ${HELICS_VERSION_MAJOR} ) diff --git a/src/helics/core/BrokerBase.cpp b/src/helics/core/BrokerBase.cpp index baa4ba4c73..76e61c2add 100644 --- a/src/helics/core/BrokerBase.cpp +++ b/src/helics/core/BrokerBase.cpp @@ -7,12 +7,14 @@ SPDX-License-Identifier: BSD-3-Clause #include "BrokerBase.hpp" +#include "../common/configFileHelpers.hpp" #include "../common/logging.hpp" #include "AsyncTimeCoordinator.hpp" #include "ForwardingTimeCoordinator.hpp" #include "GlobalTimeCoordinator.hpp" #include "LogManager.hpp" #include "ProfilerBuffer.hpp" +#include "core-exceptions.hpp" #include "flagOperations.hpp" #include "gmlc/libguarded/guarded.hpp" #include "gmlc/utilities/stringOps.h" @@ -81,7 +83,7 @@ BrokerBase::~BrokerBase() joinAllThreads(); } catch (...) { - // no exceptions in the destructor + ; // no exceptions in the destructor } } } @@ -324,6 +326,61 @@ int BrokerBase::parseArgs(std::vector args) int BrokerBase::parseArgs(std::string_view initializationString) { + auto type = fileops::getConfigType(initializationString); + + switch (type) { + case fileops::ConfigType::JSON_FILE: + fileInUse = true; + loadInfoFromJson(std::string(initializationString)); + configString = initializationString; + return 0; + case fileops::ConfigType::JSON_STRING: + try { + loadInfoFromJson(std::string(initializationString)); + configString = initializationString; + return 0; + } + catch (const helics::InvalidParameter&) { + if (fileops::looksLikeConfigToml(initializationString)) { + try { + loadInfoFromToml(std::string(initializationString)); + configString = initializationString; + return 0; + } + catch (const helics::InvalidParameter&) { + if (fileops::looksLikeCommandLine(initializationString)) { + break; + } + throw; + } + } + throw; + } + break; + case fileops::ConfigType::TOML_FILE: + fileInUse = true; + loadInfoFromToml(std::string(initializationString)); + configString = initializationString; + return 0; + case fileops::ConfigType::TOML_STRING: + try { + loadInfoFromToml(std::string(initializationString)); + configString = initializationString; + return 0; + } + catch (const helics::InvalidParameter&) { + if (fileops::looksLikeCommandLine(configString)) { + break; + } + throw; + } + break; + case fileops::ConfigType::CMD_LINE: + case fileops::ConfigType::NONE: + // with NONE there are default command line and environment possibilities + break; + } + auto app = generateBaseCLI(); auto sApp = generateCLI(); app->add_subcommand(sApp); @@ -331,6 +388,122 @@ int BrokerBase::parseArgs(std::string_view initializationString) return static_cast(res); } +void BrokerBase::loadInfoFromJson(const std::string& jsonString, bool runArgParser) +{ + nlohmann::json doc; + try { + doc = fileops::loadJson(jsonString); + } + catch (const std::invalid_argument& iarg) { + throw(helics::InvalidParameter(iarg.what())); + } + const bool hasHelicsSection = doc.contains("helics"); + bool hasHelicsSubSection{false}; + bool hasHelicsBrokerSubSection{false}; + if (hasHelicsSection) { + hasHelicsSubSection = doc["helics"].contains("helics"); + hasHelicsBrokerSubSection = doc["helics"].contains("broker"); + } + const bool hasBrokerSection = doc.contains("broker"); + + if (runArgParser) { + auto app = generateBaseCLI(); + auto sApp = generateCLI(); + app->add_subcommand(sApp); + app->allow_extras(); + try { + if (jsonString.find('{') != std::string::npos) { + std::istringstream jstring(jsonString); + app->parse_from_stream(jstring); + if (hasHelicsSection) { + app->get_config_formatter_base()->section("helics"); + std::istringstream jstringHelics(jsonString); + app->parse_from_stream(jstringHelics); + if (hasHelicsSubSection) { + app->get_config_formatter_base()->section("helics.helics"); + std::istringstream jstringHelicsSub(jsonString); + app->parse_from_stream(jstringHelicsSub); + } + if (hasHelicsBrokerSubSection) { + app->get_config_formatter_base()->section("helics.broker"); + std::istringstream jstringHelicsSub(jsonString); + app->parse_from_stream(jstringHelicsSub); + } + } + if (hasBrokerSection) { + app->get_config_formatter_base()->section("broker"); + std::istringstream jstringBroker(jsonString); + app->parse_from_stream(jstringBroker); + } + } else { + std::ifstream file(jsonString); + app->parse_from_stream(file); + if (hasHelicsSection) { + file.clear(); + file.seekg(0); + app->get_config_formatter_base()->section("helics"); + app->parse_from_stream(file); + if (hasHelicsSubSection) { + file.clear(); + file.seekg(0); + app->get_config_formatter_base()->section("helics.helics"); + app->parse_from_stream(file); + } + if (hasHelicsBrokerSubSection) { + file.clear(); + file.seekg(0); + app->get_config_formatter_base()->section("helics.broker"); + app->parse_from_stream(file); + } + } + if (hasBrokerSection) { + file.clear(); + file.seekg(0); + app->get_config_formatter_base()->section("broker"); + app->parse_from_stream(file); + } + } + } + catch (const CLI::Error& clierror) { + throw(InvalidIdentifier(clierror.what())); + } + } +} + +void BrokerBase::loadInfoFromToml(const std::string& tomlString, bool runArgParser) +{ + toml::value doc; + try { + doc = fileops::loadToml(tomlString); + } + catch (const std::invalid_argument& iarg) { + throw(helics::InvalidParameter(iarg.what())); + } + + if (runArgParser) { + auto app = generateBaseCLI(); + auto sApp = generateCLI(); + app->add_subcommand(sApp); + app->allow_extras(); + auto dptr = std::static_pointer_cast(app->get_config_formatter_base()); + if (dptr) { + dptr->skipJson(true); + } + try { + if (tomlString.find('=') != std::string::npos) { + std::istringstream tstring(tomlString); + app->parse_from_stream(tstring); + } else { + std::ifstream file(tomlString); + app->parse_from_stream(file); + } + } + catch (const CLI::Error& e) { + throw(InvalidIdentifier(e.what())); + } + } +} + void BrokerBase::configureBase() { if (debugging) { @@ -428,6 +601,10 @@ void BrokerBase::writeProfilingData() prBuff->writeFile(); } catch (const std::ios_base::failure&) { + sendToLogger(parent_broker_id, + LogLevels::ERROR_LEVEL, + identifier, + "Unable to write profiling buffer data"); } } } @@ -669,7 +846,7 @@ static void bbase->addActionMessage(CMD_TICK); } catch (std::exception& e) { - std::cerr << "exception caught from addActionMessage" << e.what() << std::endl; + std::cerr << "exception caught from addActionMessage" << e.what() << '\n'; } } else { ActionMessage tick(CMD_TICK); diff --git a/src/helics/core/BrokerBase.hpp b/src/helics/core/BrokerBase.hpp index 65a5f9cfbf..39a0a0f4bb 100644 --- a/src/helics/core/BrokerBase.hpp +++ b/src/helics/core/BrokerBase.hpp @@ -154,6 +154,9 @@ class BrokerBase { decltype(std::chrono::steady_clock::now()) disconnectTime; std::atomic lastErrorCode{0}; //!< storage for last error code std::string lastErrorString; //!< storage for last error string + std::string configString; //!< storage for a config file location + bool fileInUse{false}; + private: /// buffer for profiling messages std::shared_ptr prBuff; @@ -170,6 +173,17 @@ class BrokerBase { explicit BrokerBase(std::string_view broker_name, bool DisableQueue = false); virtual ~BrokerBase(); + + /** load broker information object from a toml string either a file or toml string + @param toml a string containing the name of the toml file or toml contents + */ + void loadInfoFromToml(const std::string& toml, bool runArgParser = true); + + /** load broker information from a JSON string either a file or JSON string + @param json a string containing the name of the JSON file or JSON contents + */ + void loadInfoFromJson(const std::string& json, bool runArgParser = true); + /** parse configuration information from command line arguments @return 0 for OK, positive numbers for expected information calls and negative number for error */ diff --git a/src/helics/core/BrokerFactory.cpp b/src/helics/core/BrokerFactory.cpp index e233d7ca02..14cb4677ea 100644 --- a/src/helics/core/BrokerFactory.cpp +++ b/src/helics/core/BrokerFactory.cpp @@ -12,6 +12,7 @@ SPDX-License-Identifier: BSD-3-Clause #include "CoreBroker.hpp" #include "CoreTypes.hpp" #include "core-exceptions.hpp" +#include "coreTypeOperations.hpp" #include "gmlc/concurrency/DelayedDestructor.hpp" #include "gmlc/concurrency/SearchableObjectHolder.hpp" #include "gmlc/concurrency/TripWire.hpp" @@ -109,6 +110,17 @@ std::shared_ptr create(CoreType type, std::string_view configureString) std::shared_ptr create(CoreType type, std::string_view brokerName, std::string_view configureString) { + std::string newName; + CoreType newType; + if (type == CoreType::EXTRACT || brokerName.empty()) { + std::tie(newType, newName) = core::extractCoreType(std::string{configureString}); + if (brokerName.empty() && !newName.empty()) { + brokerName = newName; + } + if (type == CoreType::EXTRACT) { + type = newType; + } + } auto broker = makeBroker(type, brokerName); if (!broker) { throw(helics::RegistrationFailure("unable to create broker")); @@ -129,6 +141,17 @@ std::shared_ptr create(CoreType type, int argc, char* argv[]) std::shared_ptr create(CoreType type, std::string_view brokerName, int argc, char* argv[]) { + std::string newName; + CoreType newType; + if (type == CoreType::EXTRACT || brokerName.empty()) { + std::tie(newType, newName) = core::extractCoreType(argc, argv); + if (brokerName.empty() && !newName.empty()) { + brokerName = newName; + } + if (type == CoreType::EXTRACT) { + type = newType; + } + } auto broker = makeBroker(type, brokerName); broker->configureFromArgs(argc, argv); if (!registerBroker(broker, type)) { @@ -147,6 +170,17 @@ std::shared_ptr create(CoreType type, std::vector args) std::shared_ptr create(CoreType type, std::string_view brokerName, std::vector args) { + std::string newName; + CoreType newType; + if (type == CoreType::EXTRACT || brokerName.empty()) { + std::tie(newType, newName) = core::extractCoreType(args); + if (brokerName.empty() && !newName.empty()) { + brokerName = newName; + } + if (type == CoreType::EXTRACT) { + type = newType; + } + } auto broker = makeBroker(type, brokerName); broker->configureFromVector(std::move(args)); if (!registerBroker(broker, type)) { diff --git a/src/helics/core/BrokerFactory.hpp b/src/helics/core/BrokerFactory.hpp index f0c287c2a0..e79bf2e944 100644 --- a/src/helics/core/BrokerFactory.hpp +++ b/src/helics/core/BrokerFactory.hpp @@ -32,7 +32,7 @@ namespace BrokerFactory { class BrokerTypeBuilder final: public BrokerBuilder { public: static_assert(std::is_base_of::value, - "Type does not inherit from helics::Core"); + "Type does not inherit from helics::Broker"); using broker_build_type = BrokerTYPE; virtual std::shared_ptr build(std::string_view name) override @@ -55,6 +55,7 @@ namespace BrokerFactory { defineBrokerBuilder(bbld, brokerTypeName, code); return bbld; } + /** * Creates a Broker object of the specified type. * diff --git a/src/helics/core/CommonCore.cpp b/src/helics/core/CommonCore.cpp index ae5eda64b2..effe9bed72 100644 --- a/src/helics/core/CommonCore.cpp +++ b/src/helics/core/CommonCore.cpp @@ -163,6 +163,9 @@ bool CommonCore::connect() transmit(parent_route_id, reg); setBrokerState(BrokerState::CONNECTED); disconnection.activate(); + if (!configString.empty()) { + makeConnections(configString); + } } else { setBrokerState(BrokerState::CONFIGURED); } @@ -2017,10 +2020,20 @@ InterfaceHandle CommonCore::getTranslator(std::string_view name) const void CommonCore::makeConnections(const std::string& file) { - if (fileops::hasTomlExtension(file)) { - fileops::makeConnectionsToml(this, file); - } else { - fileops::makeConnectionsJson(this, file); + auto type = fileops::getConfigType(file); + + switch (type) { + case fileops::ConfigType::JSON_FILE: + case fileops::ConfigType::JSON_STRING: + fileops::makeConnectionsJson(this, file); + break; + case fileops::ConfigType::TOML_FILE: + case fileops::ConfigType::TOML_STRING: + fileops::makeConnectionsToml(this, file); + case fileops::ConfigType::CMD_LINE: + case fileops::ConfigType::NONE: + // with NONE there are default command line and environment possibilities + break; } } diff --git a/src/helics/core/CoreBroker.cpp b/src/helics/core/CoreBroker.cpp index 3c9222badd..addc9b5612 100644 --- a/src/helics/core/CoreBroker.cpp +++ b/src/helics/core/CoreBroker.cpp @@ -169,10 +169,20 @@ bool CoreBroker::verifyBrokerKey(std::string_view key) const void CoreBroker::makeConnections(const std::string& file) { - if (fileops::hasTomlExtension(file)) { - fileops::makeConnectionsToml(this, file); - } else { - fileops::makeConnectionsJson(this, file); + auto type = fileops::getConfigType(file); + + switch (type) { + case fileops::ConfigType::JSON_FILE: + case fileops::ConfigType::JSON_STRING: + fileops::makeConnectionsJson(this, file); + break; + case fileops::ConfigType::TOML_FILE: + case fileops::ConfigType::TOML_STRING: + fileops::makeConnectionsToml(this, file); + case fileops::ConfigType::CMD_LINE: + case fileops::ConfigType::NONE: + // with NONE there are default command line and environment possibilities + break; } } @@ -2409,6 +2419,9 @@ bool CoreBroker::connect() fmt::format("Broker {} connected on {}", getIdentifier(), getAddress())); + if (!configString.empty()) { + makeConnections(configString); + } } else { setBrokerState(BrokerState::CONFIGURED); } diff --git a/src/helics/core/CoreFactory.cpp b/src/helics/core/CoreFactory.cpp index 4dc7c3110e..4ea28bb2a6 100644 --- a/src/helics/core/CoreFactory.cpp +++ b/src/helics/core/CoreFactory.cpp @@ -13,11 +13,11 @@ SPDX-License-Identifier: BSD-3-Clause #include "CoreTypes.hpp" #include "EmptyCore.hpp" #include "core-exceptions.hpp" +#include "coreTypeOperations.hpp" #include "gmlc/concurrency/DelayedDestructor.hpp" #include "gmlc/concurrency/SearchableObjectHolder.hpp" #include "gmlc/libguarded/shared_guarded.hpp" #include "helics/helics-config.h" -#include "helicsCLI11.hpp" #include #include @@ -134,12 +134,7 @@ Core* getEmptyCorePtr() std::shared_ptr create(std::string_view initializationString) { - helicsCLI11App tparser; - tparser.remove_helics_specifics(); - tparser.addTypeOption(); - tparser.allow_extras(); - tparser.parse(std::string(initializationString)); - return create(tparser.getCoreType(), gHelicsEmptyString, tparser.remaining_for_passthrough()); + return create(CoreType::EXTRACT, gHelicsEmptyString, initializationString); } std::shared_ptr create(CoreType type, std::string_view configureString) @@ -150,6 +145,17 @@ std::shared_ptr create(CoreType type, std::string_view configureString) std::shared_ptr create(CoreType type, std::string_view coreName, std::string_view configureString) { + std::string newName; + CoreType newType; + if (type == CoreType::EXTRACT || coreName.empty()) { + std::tie(newType, newName) = core::extractCoreType(std::string{configureString}); + if (coreName.empty() && !newName.empty()) { + coreName = newName; + } + if (type == CoreType::EXTRACT) { + type = newType; + } + } auto core = makeCore(type, coreName); if (!core) { throw(helics::RegistrationFailure("unable to create core")); @@ -165,13 +171,7 @@ std::shared_ptr std::shared_ptr create(std::vector args) { - helicsCLI11App tparser; - tparser.remove_helics_specifics(); - tparser.addTypeOption(); - - tparser.allow_extras(); - tparser.parse(std::move(args)); - return create(tparser.getCoreType(), gHelicsEmptyString, tparser.remaining_for_passthrough()); + return create(CoreType::EXTRACT, gHelicsEmptyString, std::move(args)); } std::shared_ptr create(CoreType type, std::vector args) @@ -182,6 +182,18 @@ std::shared_ptr create(CoreType type, std::vector args) std::shared_ptr create(CoreType type, std::string_view coreName, std::vector args) { + std::string newName; + CoreType newType; + if (type == CoreType::EXTRACT || coreName.empty()) { + std::tie(newType, newName) = core::extractCoreType(args); + if (coreName.empty() && !newName.empty()) { + coreName = newName; + } + if (type == CoreType::EXTRACT) { + type = newType; + } + } + auto core = makeCore(type, coreName); core->configureFromVector(std::move(args)); if (!registerCore(core, type)) { @@ -194,13 +206,7 @@ std::shared_ptr std::shared_ptr create(int argc, char* argv[]) { - helicsCLI11App tparser; - tparser.remove_helics_specifics(); - tparser.addTypeOption(); - - tparser.allow_extras(); - tparser.parse(argc, argv); - return create(tparser.getCoreType(), tparser.remaining_for_passthrough()); + return create(CoreType::EXTRACT, gHelicsEmptyString, argc, argv); } std::shared_ptr create(CoreType type, int argc, char* argv[]) @@ -210,6 +216,17 @@ std::shared_ptr create(CoreType type, int argc, char* argv[]) std::shared_ptr create(CoreType type, std::string_view coreName, int argc, char* argv[]) { + std::string newName; + CoreType newType; + if (type == CoreType::EXTRACT || coreName.empty()) { + std::tie(newType, newName) = core::extractCoreType(argc, argv); + if (coreName.empty() && !newName.empty()) { + coreName = newName; + } + if (type == CoreType::EXTRACT) { + type = newType; + } + } auto core = makeCore(type, coreName); core->configureFromArgs(argc, argv); if (!registerCore(core, type)) { diff --git a/src/helics/core/CoreTypes.hpp b/src/helics/core/CoreTypes.hpp index 05fb024e13..490b28555d 100644 --- a/src/helics/core/CoreTypes.hpp +++ b/src/helics/core/CoreTypes.hpp @@ -67,7 +67,8 @@ enum class CoreType : int { NULLCORE = HELICS_CORE_TYPE_NULL, //!< explicit core type that doesn't exist EMPTY = HELICS_CORE_TYPE_EMPTY, //!< core type that does nothing and can't communicate UNRECOGNIZED = 22, //!< unknown - MULTI = 45 //!< use the multi-broker + MULTI = 45, //!< use the multi-broker + EXTRACT = HELICS_CORE_TYPE_EXTRACT //!< extract core type from later arguments/files }; /** enumeration of the possible message processing results*/ diff --git a/src/helics/core/coreTypeOperations.cpp b/src/helics/core/coreTypeOperations.cpp index 415b54517a..11536cb2b8 100644 --- a/src/helics/core/coreTypeOperations.cpp +++ b/src/helics/core/coreTypeOperations.cpp @@ -6,19 +6,25 @@ SPDX-License-Identifier: BSD-3-Clause */ #include "coreTypeOperations.hpp" +#include "../common/configFileHelpers.hpp" #include "CoreTypes.hpp" #include "core-exceptions.hpp" #include "helics/helics-config.h" +#include "helicsCLI11JsonConfig.hpp" #include #include #include #include #include +#include #include +#include #include #include #include +#include +#include namespace frozen { template<> @@ -65,7 +71,7 @@ std::string to_string(CoreType type) case CoreType::EMPTY: return "empty_"; default: - return std::string(); + return {}; } } static constexpr frozen::unordered_map coreTypes{ @@ -309,6 +315,109 @@ bool matchingTypes(std::string_view type1, std::string_view type2) return (res != global_match_strings.end()); } +namespace { + std::unique_ptr makeCLITypeApp(std::string& corestring, std::string& namestring) + { + auto app = std::make_unique("type extraction app"); + app->remove_helics_specifics(); + app->option_defaults()->ignore_case()->ignore_underscore(); + app->allow_config_extras(CLI::config_extras_mode::ignore_all); + app->allow_extras(); + app->set_config("--config-file,--config", ""); + app->add_option("--name,--identifier", namestring); + auto* fmtr = addJsonConfig(app.get()); + fmtr->maxLayers(0); + fmtr->promoteSection("helics"); + auto* networking = app->add_option_group("network type")->immediate_callback(); + networking->add_option("--core", corestring); + networking->add_option("--coretype,-t", corestring)->envname("HELICS_CORE_TYPE"); + app->add_subcommand("broker")->fallthrough(); + app->add_subcommand("core")->fallthrough(); + return app; + } +} // namespace + +std::pair extractCoreType(const std::string& configureString) +{ + std::string corestring; + std::string namestring; + auto app = makeCLITypeApp(corestring, namestring); + auto type = fileops::getConfigType(configureString); + try { + switch (type) { + case fileops::ConfigType::JSON_FILE: { + std::ifstream file{configureString}; + app->parse_from_stream(file); + break; + } + case fileops::ConfigType::JSON_STRING: { + std::istringstream jstring{configureString}; + app->parse_from_stream(jstring); + break; + } + case fileops::ConfigType::TOML_FILE: { + app->allow_extras(); + auto dptr = + std::static_pointer_cast(app->get_config_formatter_base()); + if (dptr) { + dptr->skipJson(true); + } + std::ifstream file{configureString}; + app->parse_from_stream(file); + break; + } + case fileops::ConfigType::TOML_STRING: { + app->allow_extras(); + auto dptr = + std::static_pointer_cast(app->get_config_formatter_base()); + if (dptr) { + dptr->skipJson(true); + } + std::istringstream tstring{configureString}; + app->parse_from_stream(tstring); + break; + } + case fileops::ConfigType::CMD_LINE: + app->helics_parse(configureString); + break; + case fileops::ConfigType::NONE: + break; + } + } + catch (const std::exception&) { + corestring.clear(); + } + if (corestring.empty()) { + return {CoreType::DEFAULT, namestring}; + } + return {coreTypeFromString(corestring), namestring}; +} + +std::pair extractCoreType(const std::vector& args) +{ + std::string corestring; + std::string namestring; + auto app = makeCLITypeApp(corestring, namestring); + auto vec = args; + app->helics_parse(vec); + if (corestring.empty()) { + return {CoreType::DEFAULT, namestring}; + } + return {coreTypeFromString(corestring), namestring}; +} + +std::pair extractCoreType(int argc, char* argv[]) +{ + std::string corestring; + std::string namestring; + auto app = makeCLITypeApp(corestring, namestring); + app->helics_parse(argc, argv); + if (corestring.empty()) { + return {CoreType::DEFAULT, namestring}; + } + return {coreTypeFromString(corestring), namestring}; +} + } // namespace helics::core namespace helics { diff --git a/src/helics/core/coreTypeOperations.hpp b/src/helics/core/coreTypeOperations.hpp index dbf74ee1f7..0502436d45 100644 --- a/src/helics/core/coreTypeOperations.hpp +++ b/src/helics/core/coreTypeOperations.hpp @@ -9,6 +9,8 @@ SPDX-License-Identifier: BSD-3-Clause #include #include +#include +#include /** @file @details function definitions for operations on core types @@ -40,4 +42,9 @@ bool matchingTypes(std::string_view type1, std::string_view type2); /** generate an extended version and system info string in json format*/ std::string systemInfo(); +/** methods to extract the core type and name from various forms of arguments*/ +std::pair extractCoreType(const std::string& configureString); +std::pair extractCoreType(const std::vector& args); +std::pair extractCoreType(int argc, char* argv[]); + } // namespace helics::core diff --git a/src/helics/helics_enums.h b/src/helics/helics_enums.h index e8f29e1e79..7a2913dbe6 100644 --- a/src/helics/helics_enums.h +++ b/src/helics/helics_enums.h @@ -58,7 +58,9 @@ typedef enum { /* NOLINT */ HELICS_CORE_TYPE_NULL = 66, /** an explicit core type exists but does nothing but return empty values or sink calls*/ - HELICS_CORE_TYPE_EMPTY = 77 + HELICS_CORE_TYPE_EMPTY = 77, + /** core type specification to allow extraction from later arguments or files*/ + HELICS_CORE_TYPE_EXTRACT = 101 } HelicsCoreTypes; /** enumeration of allowable data types for publications and inputs*/ diff --git a/src/helics/shared_api_library/helicsCore.h b/src/helics/shared_api_library/helicsCore.h index 506885bed6..f565bbdf6b 100644 --- a/src/helics/shared_api_library/helicsCore.h +++ b/src/helics/shared_api_library/helicsCore.h @@ -110,7 +110,8 @@ HELICS_EXPORT HelicsBool helicsIsCoreTypeAvailable(const char* type); * @param type The type of the core to create. * @param name The name of the core. It can be a nullptr or empty string to have a name automatically assigned. * @param initString An initialization string to send to the core. The format is similar to command line arguments. - * Typical options include a broker name, the broker address, the number of federates, etc. + * Typical options include a broker name, the broker address, the number of federates, etc. Can also be a + * file (toml, ini, json) or json object containing the core configuration. * * @param[in,out] err An error object that will contain an error code and string if any error occurred during the execution of the function. @@ -166,6 +167,7 @@ HELICS_EXPORT HelicsBool helicsCoreIsValid(HelicsCore core); * @param name The name of the broker. It can be a nullptr or empty string to have a name automatically assigned. * @param initString An initialization string to send to the core-the format is similar to command line arguments. * Typical options include a broker address such as --broker="XSSAF" if this is a subbroker, or the number of federates, + * or it can also be a json or toml file with broker configuration. * or the address. * * @param[in,out] err An error object that will contain an error code and string if any error occurred during the execution of the function. diff --git a/tests/helics/CMakeLists.txt b/tests/helics/CMakeLists.txt index ba39cfed93..188348c44f 100644 --- a/tests/helics/CMakeLists.txt +++ b/tests/helics/CMakeLists.txt @@ -15,7 +15,7 @@ include(AddGoogletest) add_library(helics_test_base INTERFACE) target_link_libraries(helics_test_base INTERFACE gtest gtest_main gmock) target_link_libraries(helics_test_base INTERFACE compile_flags_target spdlog::spdlog) -target_include_directories(helics_test_base INTERFACE ${PROJECT_SOURCE_DIR}/ThirdParty) +target_include_directories(helics_test_base SYSTEM INTERFACE ${PROJECT_SOURCE_DIR}/ThirdParty) add_subdirectory(core) add_subdirectory(network) diff --git a/tests/helics/application_api/FederateTests.cpp b/tests/helics/application_api/FederateTests.cpp index ffdf31c502..217d2ba89f 100644 --- a/tests/helics/application_api/FederateTests.cpp +++ b/tests/helics/application_api/FederateTests.cpp @@ -1440,6 +1440,39 @@ TEST_P(FederateGlobalFiles, global_file_ci_skip) brk->waitForDisconnect(); } +TEST(federate, broker_global_file_ci_skip) +{ + auto testFile = std::string(TEST_DIR) + "example_global_broker.json"; + auto brk = helics::BrokerFactory::create(helics::CoreType::EXTRACT, "", testFile); + brk->connect(); + + helics::FederateInfo fedInfo(CORE_TYPE_TO_TEST); + fedInfo.coreName = "core_globalc"; + fedInfo.coreInitString = "-f 2 --broker=brk_globalb"; + + auto Fed1 = std::make_shared("fed1", fedInfo); + + auto Fed2 = std::make_shared("fed2", fedInfo); + + Fed1->enterInitializingModeAsync(); + Fed2->enterInitializingMode(); + + Fed1->enterInitializingModeComplete(); + + auto str1 = Fed1->query("global_value", "global1"); + EXPECT_EQ(str1, "this is a global1 value"); + str1 = Fed2->query("global_value", "global1"); + EXPECT_EQ(str1, "this is a global1 value"); + + str1 = Fed1->query("global_value", "global2"); + EXPECT_EQ(str1, "this is another global value"); + str1 = Fed2->query("global_value", "global2"); + EXPECT_EQ(str1, "this is another global value"); + Fed1->finalize(); + Fed2->finalize(); + brk->waitForDisconnect(); +} + TEST_P(FederateGlobalFiles, core_global_file_ci_skip) { auto brk = helics::BrokerFactory::create(helics::CoreType::TEST, "b1", "-f2"); diff --git a/tests/helics/core/BrokerClassTests.cpp b/tests/helics/core/BrokerClassTests.cpp index 3d9fbbf047..fa3beb767e 100644 --- a/tests/helics/core/BrokerClassTests.cpp +++ b/tests/helics/core/BrokerClassTests.cpp @@ -12,6 +12,7 @@ SPDX-License-Identifier: BSD-3-Clause #include "gtest/gtest.h" #include +#include /** test the assignment and retrieval of global value from a broker object*/ TEST(brokers, global_value) @@ -91,6 +92,70 @@ TEST(brokers, subbroker_min) EXPECT_TRUE(brk3->waitForDisconnect()); } +/** test the assignment and retrieval of global value from a broker object*/ +TEST(brokers, subbroker_min_files) +{ + auto brk = helics::BrokerFactory::create(helics::CoreType::EXTRACT, + "", + std::string(TEST_DIR) + "broker_test_subbroker.json"); + + EXPECT_EQ(brk->getIdentifier(), "gbroker_f1"); + + auto brk2 = + helics::BrokerFactory::create(helics::CoreType::EXTRACT, + "", + std::string(TEST_DIR) + "broker_test_subbroker2.toml"); + + auto cr1 = helics::CoreFactory::create(helics::CoreType::TEST, "--name=cf1 --broker=gbf2"); + + helics::CoreFederateInfo cf1; + auto fid1 = cr1->registerFederate("fed1", cf1); + auto fid2 = cr1->registerFederate("fed2", cf1); + + auto fut1 = + std::async(std::launch::async, [fid1, &cr1]() { cr1->enterInitializingMode(fid1); }); + + auto fut2 = + std::async(std::launch::async, [fid2, &cr1]() { cr1->enterInitializingMode(fid2); }); + + auto res = fut1.wait_for(std::chrono::milliseconds(100)); + // this should not allow initializingMode entry since only 1 subbroker + EXPECT_EQ(res, std::future_status::timeout); + + auto cr2 = helics::CoreFactory::create(helics::CoreType::EXTRACT, + std::string(TEST_DIR) + "broker_test_core.json"); + auto fid3 = cr2->registerFederate("fed3", cf1); + + auto fut3 = + std::async(std::launch::async, [fid3, &cr2]() { cr2->enterInitializingMode(fid3); }); + + res = fut1.wait_for(std::chrono::milliseconds(100)); + // this should still not allow initializingMode entry since still only 1 subbroker + EXPECT_EQ(res, std::future_status::timeout); + + auto brk3 = + helics::BrokerFactory::create(helics::CoreType::TEST, "gbf3", R"({"broker":"gbroker_f1"})"); + + auto cr3 = helics::CoreFactory::create(helics::CoreType::TEST, "cf3", "broker=\"gbf3\""); + auto fid4 = cr3->registerFederate("fed4", cf1); + + auto fut4 = + std::async(std::launch::async, [fid4, &cr3]() { cr3->enterInitializingMode(fid4); }); + + // now it should grant + fut1.get(); + fut2.get(); + fut3.get(); + fut4.get(); + + EXPECT_FALSE(brk->isOpenToNewFederates()); + EXPECT_FALSE(cr3->isOpenToNewFederates()); + + brk->disconnect(); + + EXPECT_TRUE(brk3->waitForDisconnect()); +} + /** This test should be removed once log levels with numbers is re-enabled ~helics 3.3 */ TEST(brokers, broker_log_command_failures) { diff --git a/tests/helics/core/CoreFactory-tests.cpp b/tests/helics/core/CoreFactory-tests.cpp index b96bb2d66d..cc95bf44d9 100644 --- a/tests/helics/core/CoreFactory-tests.cpp +++ b/tests/helics/core/CoreFactory-tests.cpp @@ -139,7 +139,7 @@ TEST(CoreFactory_tests, udpCore_test) } #endif -/** This test should be removed once log levels with numbers is re-enabled ~helics 3.3 */ +/** This test should be removed once log levels with numbers is re-enabled if ever */ TEST(core, core_log_command_failures) { EXPECT_THROW(helics::CoreFactory::create(helics::CoreType::TEST, diff --git a/tests/helics/system_tests/ErrorTests.cpp b/tests/helics/system_tests/ErrorTests.cpp index 03dd2ef5e5..88c0887403 100644 --- a/tests/helics/system_tests/ErrorTests.cpp +++ b/tests/helics/system_tests/ErrorTests.cpp @@ -147,8 +147,8 @@ TEST_F(error_tests, single_thread_fed) EXPECT_THROW(fed1->requestTimeAsync(3.2), helics::InvalidFunctionCall); EXPECT_THROW(fed1->requestTimeComplete(), helics::InvalidFunctionCall); - auto t1 = fed1->requestTime(2.0); - EXPECT_EQ(t1, 2.0); + auto time = fed1->requestTime(2.0); + EXPECT_EQ(time, 2.0); EXPECT_THROW(fed1->requestTimeIterativeAsync(3.2, helics::IterationRequest::FORCE_ITERATION), helics::InvalidFunctionCall); @@ -483,8 +483,8 @@ TEST_F(error_tests, missing_required_pub) auto fed2 = GetFederateAs(1); fed1->registerGlobalPublication("t1", ""); - auto& i2 = fed2->registerSubscription("abcd", ""); - i2.setOption(helics::defs::Options::CONNECTION_REQUIRED); + auto& sub1 = fed2->registerSubscription("abcd", ""); + sub1.setOption(helics::defs::Options::CONNECTION_REQUIRED); fed1->enterInitializingModeAsync(); EXPECT_THROW(fed2->enterInitializingMode(), helics::ConnectionFailure); @@ -584,8 +584,8 @@ TEST_F(error_tests, missing_required_ept) auto fed2 = GetFederateAs(1); fed1->registerGlobalTargetedEndpoint("t1", ""); - auto& e2 = fed2->registerGlobalTargetedEndpoint("abcd", ""); - e2.setOption(helics::defs::Options::CONNECTION_REQUIRED); + auto& ept1 = fed2->registerGlobalTargetedEndpoint("abcd", ""); + ept1.setOption(helics::defs::Options::CONNECTION_REQUIRED); fed1->enterInitializingModeAsync(); EXPECT_THROW(fed2->enterInitializingMode(), helics::ConnectionFailure); @@ -597,11 +597,13 @@ TEST_F(error_tests, missing_required_ept) class error_tests_type: public ::testing::TestWithParam, public FederateTestFixture {}; /** test simple creation and destruction*/ -TEST_P(error_tests_type, test_duplicate_broker_name) +TEST_P(error_tests_type, duplicate_broker_name) { - auto broker = AddBroker(GetParam(), "--name=brk1"); + std::string bname = std::string("brk_dup_") + GetParam(); + auto broker = AddBroker(GetParam(), std::string("--name=") + bname); EXPECT_TRUE(broker->isConnected()); - EXPECT_THROW(AddBroker(GetParam(), "--name=brk1 --timeout=500"), helics::RegistrationFailure); + EXPECT_THROW(AddBroker(GetParam(), std::string("--name=") + bname + " --timeout=500"), + helics::RegistrationFailure); broker->disconnect(); helics::cleanupHelicsLibrary(); } diff --git a/tests/helics/system_tests/networkTests.cpp b/tests/helics/system_tests/networkTests.cpp index 6f800298a6..510da2893f 100644 --- a/tests/helics/system_tests/networkTests.cpp +++ b/tests/helics/system_tests/networkTests.cpp @@ -158,7 +158,7 @@ TEST_F(network_tests, test_otherport2) } } -TEST_F(network_tests, test_otherport_fail) +TEST_F(network_tests, otherport_fail) { const std::string brokerArgs = "--local_interface=tcp://127.0.0.1:33100"; auto broker = helics::BrokerFactory::create(helics::CoreType::ZMQ, brokerArgs); @@ -172,17 +172,18 @@ TEST_F(network_tests, test_otherport_fail) } } -TEST_F(network_tests, test_otherport_env) +TEST_F(network_tests, otherport_env) { setEnvironmentVariable("HELICS_CONNECTION_PORT", "33102"); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); const std::string brokerArgs = "-f 2"; auto broker = helics::BrokerFactory::create(helics::CoreType::ZMQ, brokerArgs); EXPECT_TRUE(broker->isConnected()); - helics::FederateInfo fedInfo("--core_type=ZMQ --corename=c1"); + helics::FederateInfo fedInfo("--core_type=ZMQ --corename=cop1"); helics::ValueFederate fed1("fed1", fedInfo); - helics::FederateInfo fi2("--core_type=ZMQ --broker=tcp://127.0.0.1:33102 --corename=c2"); + helics::FederateInfo fi2("--core_type=ZMQ --broker=tcp://127.0.0.1:33102 --corename=cop2"); helics::ValueFederate fed2("fed2", fi2); fed2.enterExecutingModeAsync(); diff --git a/tests/helics/test_files/broker_test_core.json b/tests/helics/test_files/broker_test_core.json new file mode 100644 index 0000000000..b19900a635 --- /dev/null +++ b/tests/helics/test_files/broker_test_core.json @@ -0,0 +1,5 @@ +{ + "name": "cf2", + "coretype": "test", + "broker": "gbf2" +} diff --git a/tests/helics/test_files/broker_test_subbroker.json b/tests/helics/test_files/broker_test_subbroker.json new file mode 100644 index 0000000000..5902af95c1 --- /dev/null +++ b/tests/helics/test_files/broker_test_subbroker.json @@ -0,0 +1,7 @@ +{ + "name": "gbroker_f1", + "coretype": "test", + "sub_brokers": 2, + "federates": 2, + "root": true +} diff --git a/tests/helics/test_files/broker_test_subbroker2.toml b/tests/helics/test_files/broker_test_subbroker2.toml new file mode 100644 index 0000000000..e43346aa83 --- /dev/null +++ b/tests/helics/test_files/broker_test_subbroker2.toml @@ -0,0 +1,4 @@ +name="gbf2" +coretype="test" +sub_brokers=2 +broker="gbroker_f1" diff --git a/tests/helics/test_files/example_global_broker.json b/tests/helics/test_files/example_global_broker.json new file mode 100644 index 0000000000..f4b60b7a40 --- /dev/null +++ b/tests/helics/test_files/example_global_broker.json @@ -0,0 +1,11 @@ +//this should be a valid json file (except comments are not recognized in standard JSON) +{ + "coretype": "test", + "federates": 2, + "name": "brk_globalb", + //example json file with globals [, ] + "globals": [ + ["global1", "this is a global1 value"], + ["global2", "this is another global value"] + ] +}