diff --git a/CMakeLists.txt b/CMakeLists.txt index 30f049785..8aca0d680 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ set(AGENT_VERSION_MAJOR 2) set(AGENT_VERSION_MINOR 6) set(AGENT_VERSION_PATCH 0) -set(AGENT_VERSION_BUILD 6) +set(AGENT_VERSION_BUILD 7) set(AGENT_VERSION_RC "") # This minimum version is to support Visual Studio 2019 and C++ feature checking and FetchContent diff --git a/agent_lib/CMakeLists.txt b/agent_lib/CMakeLists.txt index 4e979e0c1..6c22ca22a 100644 --- a/agent_lib/CMakeLists.txt +++ b/agent_lib/CMakeLists.txt @@ -393,7 +393,7 @@ target_include_directories( target_link_libraries( agent_lib PUBLIC - boost::boost LibXml2::LibXml2 date::date-tz openssl::openssl + boost::boost LibXml2::LibXml2 date::date openssl::openssl nlohmann_json::nlohmann_json mqtt_cpp::mqtt_cpp rapidjson BZip2::BZip2 diff --git a/cmake/osx_no_app_or_frameworks.cmake b/cmake/osx_no_app_or_frameworks.cmake index 4b39c4054..cc7be2e47 100644 --- a/cmake/osx_no_app_or_frameworks.cmake +++ b/cmake/osx_no_app_or_frameworks.cmake @@ -8,6 +8,6 @@ set(CMAKE_FIND_FRAMEWORK NEVER FORCE) # ms suffix which was added post 11. if (APPLE) # set(COVERAGE_FLAGS "-fcoverage-mapping -fprofile-instr-generate") - set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD "c++17") + set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD "c++20") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-local-typedef -Wno-deprecated-declarations -Wall") endif() diff --git a/conanfile.py b/conanfile.py index 94213e50e..e5662f96f 100644 --- a/conanfile.py +++ b/conanfile.py @@ -68,7 +68,7 @@ class MTConnectAgentConan(ConanFile): "openssl*:shared": False, - "date*:use_system_tz_db": True + "date*:header_only": True } exports_sources = "*", "!build", "!test_package/build", "!*~" @@ -112,14 +112,14 @@ def tool_requires_version(self, package, version): def build_requirements(self): self.tool_requires_version("cmake", [3, 26, 4]) if self.options.with_docs: - self.tool_requires_version("doxygen", [1, 14, 0]) + self.tool_requires_version("doxygen", [1, 15, 0]) def requirements(self): self.requires("boost/1.88.0", headers=True, libs=True, transitive_headers=True, transitive_libs=True) self.requires("libxml2/2.14.5", headers=True, libs=True, visible=True, transitive_headers=True, transitive_libs=True) self.requires("date/3.0.4", headers=True, libs=True, transitive_headers=True, transitive_libs=True) self.requires("nlohmann_json/3.12.0", headers=True, libs=False, transitive_headers=True, transitive_libs=False) - self.requires("openssl/3.5.4", headers=True, libs=True, transitive_headers=True, transitive_libs=True) + self.requires("openssl/3.6.0", headers=True, libs=True, transitive_headers=True, transitive_libs=True) self.requires("rapidjson/cci.20230929", headers=True, libs=False, transitive_headers=True, transitive_libs=False) self.requires("mqtt_cpp/13.2.2", headers=True, libs=False, transitive_headers=True, transitive_libs=False) self.requires("bzip2/1.0.8", headers=True, libs=True, transitive_headers=True, transitive_libs=True) diff --git a/docker/alpine/Dockerfile b/docker/alpine/Dockerfile index ec7599cf2..bffa8a23b 100644 --- a/docker/alpine/Dockerfile +++ b/docker/alpine/Dockerfile @@ -29,7 +29,7 @@ # --------------------------------------------------------------------- # base image - alpine 3.18 -FROM alpine:3.19 AS os +FROM alpine:3.22 AS os # --------------------------------------------------------------------- # build @@ -53,6 +53,7 @@ ENV CONAN_PROFILE="$HOME/agent/cppagent/conan/profiles/docker" RUN apk --update add \ autoconf \ automake \ + bash \ cmake \ g++ \ gcompat \ diff --git a/src/mtconnect/agent.cpp b/src/mtconnect/agent.cpp index 52544662e..7f27ad008 100644 --- a/src/mtconnect/agent.cpp +++ b/src/mtconnect/agent.cpp @@ -1019,7 +1019,7 @@ namespace mtconnect { stringstream msg; msg << "Device " << *device->getUuid() << " already exists. " << " Update not supported yet"; - throw msg; + throw msg.str(); } else { diff --git a/src/mtconnect/configuration/agent_config.cpp b/src/mtconnect/configuration/agent_config.cpp index 36a182d48..ab63fa019 100644 --- a/src/mtconnect/configuration/agent_config.cpp +++ b/src/mtconnect/configuration/agent_config.cpp @@ -941,7 +941,7 @@ namespace mtconnect::configuration { loadAdapters(config, options); m_afterAgentHooks.exec(*this); - + #ifdef WITH_PYTHON configurePython(config, options); #endif diff --git a/src/mtconnect/entity/entity.hpp b/src/mtconnect/entity/entity.hpp index fa34a8b0f..e04bf24b0 100644 --- a/src/mtconnect/entity/entity.hpp +++ b/src/mtconnect/entity/entity.hpp @@ -716,73 +716,75 @@ namespace mtconnect { return changed; } - + /// @brief variant visitor to output Value to stream struct StreamOutputVisitor { /// @brief constructor /// @param os the output stream - StreamOutputVisitor(std::ostream& os) : m_os(os) {} - - void operator()(const std::monostate&) { - m_os << "null"; - } - - void operator()(const EntityPtr& entity) { + StreamOutputVisitor(std::ostream &os) : m_os(os) {} + + void operator()(const std::monostate &) { m_os << "null"; } + + void operator()(const EntityPtr &entity) + { const auto &id = entity->getIdentity(); m_os << "Entity(" << entity->getName() << ":"; StreamOutputVisitor visitor(m_os); std::visit(visitor, id); m_os << ")"; } - - void operator()(const EntityList& list) { + + void operator()(const EntityList &list) + { m_os << "EntityList["; - for (auto e : list) { + for (auto e : list) + { StreamOutputVisitor visitor(m_os); visitor(e); m_os << " "; } m_os << "]"; } - - void operator()(const DataSet& dataSet) { - m_os << "DataSet(" << dataSet.size() << " items)"; - } - - void operator()(const QName& qname) { - m_os << qname.str(); - } - - void operator()(const Vector& vec) { + + void operator()(const DataSet &dataSet) { m_os << "DataSet(" << dataSet.size() << " items)"; } + + void operator()(const QName &qname) { m_os << qname.str(); } + + void operator()(const Vector &vec) + { m_os << "Vector["; - for (const auto &v : vec) { + for (const auto &v : vec) + { m_os << v << " "; } m_os << "]"; } - + template - void operator()(const T& value) { + void operator()(const T &value) + { m_os << value; } - - std::ostream& m_os; + + std::ostream &m_os; }; - + /// @brief output operator for Value /// @param os the output stream /// @param v the Value to output - inline std::ostream& operator<<(std::ostream& os, const Value &v) { + inline std::ostream &operator<<(std::ostream &os, const Value &v) + { StreamOutputVisitor visitor(os); std::visit(visitor, v); return os; } - + /// @brief output operator for Value /// @param os the output stream /// @param v the Value to output - inline std::ostream& operator<<(std::ostream& os, const EntityPtr &v) { + inline std::ostream &operator<<(std::ostream &os, const EntityPtr &v) + { StreamOutputVisitor visitor(os); visitor(v); return os; diff --git a/src/mtconnect/entity/requirement.cpp b/src/mtconnect/entity/requirement.cpp index 51c2848d5..1422144bd 100644 --- a/src/mtconnect/entity/requirement.cpp +++ b/src/mtconnect/entity/requirement.cpp @@ -186,12 +186,13 @@ namespace mtconnect { // If there isa a time portion in the string, parse the time if (arg.find('T') != string::npos) { - in >> std::setw(6) >> date::parse("%FT%T", ts); + in >> std::setw(6); + date::from_stream(in, "%FT%T", ts); } else { // Just parse the date - in >> date::parse("%F", ts); + date::from_stream(in, "%F", ts); } } void operator()(const string &arg, Vector &r) diff --git a/src/mtconnect/pipeline/response_document.cpp b/src/mtconnect/pipeline/response_document.cpp index d538488c6..d49ee36d8 100644 --- a/src/mtconnect/pipeline/response_document.cpp +++ b/src/mtconnect/pipeline/response_document.cpp @@ -29,6 +29,7 @@ #include "mtconnect/entity/xml_parser.hpp" #include "mtconnect/observation/observation.hpp" #include "mtconnect/pipeline/timestamp_extractor.hpp" +#include "mtconnect/utilities.hpp" using namespace std; @@ -280,20 +281,6 @@ namespace mtconnect::pipeline { }); } - inline static Timestamp parseTimestamp(const std::string value) - { - Timestamp ts; - istringstream in(value); - in >> std::setw(6) >> date::parse("%FT%T", ts); - if (!in.good()) - { - LOG(error) << "Cound not parse XML timestamp: " << value; - ts = std::chrono::system_clock::now(); - } - - return ts; - } - inline static DataItemPtr findDataItem(const std::string &name, DevicePtr device, const entity::Properties &properties) { diff --git a/src/mtconnect/pipeline/timestamp_extractor.hpp b/src/mtconnect/pipeline/timestamp_extractor.hpp index 651c77350..4208e057e 100644 --- a/src/mtconnect/pipeline/timestamp_extractor.hpp +++ b/src/mtconnect/pipeline/timestamp_extractor.hpp @@ -84,7 +84,6 @@ namespace mtconnect::pipeline { Now now = DefaultNow) { using namespace std; - using namespace date; using namespace chrono; using namespace chrono_literals; using namespace date; @@ -103,7 +102,8 @@ namespace mtconnect::pipeline { if (has_t) { istringstream in(timestamp.data()); - in >> std::setw(6) >> date::parse("%FT%T", result); + in >> std::setw(6); + date::from_stream(in, "%FT%T", result); if (!in.good()) { result = now(); diff --git a/src/mtconnect/pipeline/validator.hpp b/src/mtconnect/pipeline/validator.hpp index d1f624f49..29e92af9e 100644 --- a/src/mtconnect/pipeline/validator.hpp +++ b/src/mtconnect/pipeline/validator.hpp @@ -37,11 +37,11 @@ namespace mtconnect::pipeline { public: Validator(const Validator &) = default; Validator(PipelineContextPtr context) - : Transform("Validator"), m_contract(context->m_contract.get()) + : Transform("Validator"), m_contract(context->m_contract.get()) { m_guard = TypeGuard(RUN) || TypeGuard(SKIP); } - + /// @brief validate the Event /// @param entity The Event entity /// @returns modified entity with quality and deprecated properties @@ -51,7 +51,7 @@ namespace mtconnect::pipeline { using namespace mtconnect::validation::observations; auto obs = std::dynamic_pointer_cast(entity); auto &value = obs->getValue(); - + bool valid = true; auto di = obs->getDataItem(); if (!obs->isUnavailable() && !di->isDataSet()) @@ -71,7 +71,7 @@ namespace mtconnect::pipeline { // Check if it has not been introduced yet if (lit->second.first > 0 && m_contract->getSchemaVersion() < lit->second.first) valid = false; - + // Check if deprecated if (lit->second.second > 0 && m_contract->getSchemaVersion() >= lit->second.second) { @@ -96,11 +96,11 @@ namespace mtconnect::pipeline { else if (auto spl = std::dynamic_pointer_cast(obs)) { if (!(spl->hasProperty("quality") || std::holds_alternative(value) || - std::holds_alternative(value))) + std::holds_alternative(value))) valid = false; } } - + if (!valid) { obs->setProperty("quality", std::string("INVALID")); @@ -108,8 +108,8 @@ namespace mtconnect::pipeline { auto &id = di->getId(); if (m_logOnce.count(id) < 1) { - LOG(warning) << "DataItem '" << id << "': Invalid value for '" << obs->getName() - << "': '" << value << '\''; + LOG(warning) << "DataItem '" << id << "': Invalid value for '" << obs->getName() << "': '" + << value << '\''; m_logOnce.insert(id); } else @@ -121,7 +121,7 @@ namespace mtconnect::pipeline { { obs->setProperty("quality", std::string("VALID")); } - + return next(std::move(obs)); } diff --git a/src/mtconnect/printer/xml_printer.cpp b/src/mtconnect/printer/xml_printer.cpp index 06c0da048..f4b5bafa2 100644 --- a/src/mtconnect/printer/xml_printer.cpp +++ b/src/mtconnect/printer/xml_printer.cpp @@ -94,7 +94,7 @@ namespace mtconnect::printer { xmlFreeTextWriter(m_writer); m_writer = nullptr; } - return string((char *)m_buf->content, m_buf->use); + return std::string((char *)xmlBufferContent(m_buf), xmlBufferLength(m_buf)); } protected: diff --git a/src/mtconnect/printer/xml_printer_helper.hpp b/src/mtconnect/printer/xml_printer_helper.hpp index fb5d4b223..74a45cf24 100644 --- a/src/mtconnect/printer/xml_printer_helper.hpp +++ b/src/mtconnect/printer/xml_printer_helper.hpp @@ -68,7 +68,7 @@ namespace mtconnect::printer { xmlFreeTextWriter(m_writer); m_writer = nullptr; } - return std::string((char *)m_buf->content, m_buf->use); + return std::string((char *)xmlBufferContent(m_buf), xmlBufferLength(m_buf)); } protected: diff --git a/src/mtconnect/sink/rest_sink/error.hpp b/src/mtconnect/sink/rest_sink/error.hpp index 72592b88e..5f5bc44aa 100644 --- a/src/mtconnect/sink/rest_sink/error.hpp +++ b/src/mtconnect/sink/rest_sink/error.hpp @@ -19,7 +19,7 @@ #include -#include +#include #include "mtconnect/entity/entity.hpp" #include "mtconnect/entity/factory.hpp" diff --git a/src/mtconnect/sink/rest_sink/rest_service.cpp b/src/mtconnect/sink/rest_sink/rest_service.cpp index 4bdfbaad2..1f01a8445 100644 --- a/src/mtconnect/sink/rest_sink/rest_service.cpp +++ b/src/mtconnect/sink/rest_sink/rest_service.cpp @@ -1411,12 +1411,7 @@ namespace mtconnect { Timestamp ts; if (time) { - istringstream in(*time); - in >> std::setw(6) >> date::parse("%FT%T", ts); - if (!in.good()) - { - ts = chrono::system_clock::now(); - } + ts = parseTimestamp(*time); } else { diff --git a/src/mtconnect/source/adapter/shdr/shdr_adapter.hpp b/src/mtconnect/source/adapter/shdr/shdr_adapter.hpp index 15c25eba3..5b84fd249 100644 --- a/src/mtconnect/source/adapter/shdr/shdr_adapter.hpp +++ b/src/mtconnect/source/adapter/shdr/shdr_adapter.hpp @@ -18,7 +18,6 @@ #pragma once #include -#include #include #include #include diff --git a/src/mtconnect/utilities.cpp b/src/mtconnect/utilities.cpp index d6fb420c8..b9cdf7603 100644 --- a/src/mtconnect/utilities.cpp +++ b/src/mtconnect/utilities.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -44,8 +43,6 @@ #define _WINSOCKAPI_ #include #include -#define localtime_r(t, tm) localtime_s(tm, t) -#define gmtime_r(t, tm) gmtime_s(tm, t) #define DELTA_EPOCH_IN_MICROSECS 11644473600000000ull #endif @@ -77,8 +74,6 @@ BOOST_FUSION_ADAPT_STRUCT(mtconnect::url::Url, m_fragment)) namespace mtconnect { - AGENT_LIB_API void mt_localtime(const time_t *time, struct tm *buf) { localtime_r(time, buf); } - inline string::size_type insertPrefix(string &aPath, string::size_type &aPos, const string aPrefix) { diff --git a/src/mtconnect/utilities.hpp b/src/mtconnect/utilities.hpp index 8ccbeb09e..dff98f5d8 100644 --- a/src/mtconnect/utilities.hpp +++ b/src/mtconnect/utilities.hpp @@ -31,8 +31,8 @@ #include #include -#include #include +#include #include #include #include @@ -238,11 +238,17 @@ namespace mtconnect { return true; } - /// @brief Gets the local time - /// @param[in] time the time - /// @param[out] buf struct tm - AGENT_LIB_API void mt_localtime(const time_t *time, struct tm *buf); - + /// @brief Thread safe localtime function that uses localtime_s or localtime_r based on platform + /// @param[in] timer pointer to time_t + /// @param[out] buf pointer to tm struct to fill + inline void safe_localtime(const std::time_t* timer, std::tm* buf) { +#ifdef _WINDOWS + localtime_s(buf, timer); +#else + localtime_r(timer, buf); +#endif + } + /// @brief Formats the timePoint as string given the format /// @param[in] timePoint the time /// @param[in] format the format @@ -253,11 +259,6 @@ namespace mtconnect { using namespace std; using namespace std::chrono; constexpr char ISO_8601_FMT[] = "%Y-%m-%dT%H:%M:%SZ"; -#ifdef _WINDOWS - namespace tzchrono = std::chrono; -#else - namespace tzchrono = date; -#endif switch (format) { @@ -269,9 +270,12 @@ namespace mtconnect { return date::format(ISO_8601_FMT, date::floor(timePoint)); case LOCAL: { - auto zone = tzchrono::current_zone(); - auto zt = date::zoned_time(zone, timePoint); - return date::format("%Y-%m-%dT%H:%M:%S%z", zt); + time_t t = std::chrono::system_clock::to_time_t(timePoint); + struct tm local; + safe_localtime(&t, &local); + char buf[64]; + strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", &local); + return string(buf); } } @@ -776,7 +780,8 @@ namespace mtconnect { { Timestamp ts; std::istringstream in(timestamp); - in >> std::setw(6) >> date::parse("%FT%T", ts); + in >> std::setw(6); + date::from_stream(in, "%FT%T", ts); if (!in.good()) { ts = std::chrono::system_clock::now(); diff --git a/test_package/agent_test.cpp b/test_package/agent_test.cpp index 41a60bf69..5039741e1 100644 --- a/test_package/agent_test.cpp +++ b/test_package/agent_test.cpp @@ -63,7 +63,7 @@ class AgentTest : public testing::Test public: typedef std::map map_type; using queue_type = list; - + protected: void SetUp() override { @@ -71,19 +71,19 @@ class AgentTest : public testing::Test m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "1.3", 25, true); m_agentId = to_string(getCurrentTimeInSec()); } - + void TearDown() override { m_agentTestHelper.reset(); } - + void addAdapter(ConfigOptions options = ConfigOptions {}) { m_agentTestHelper->addAdapter(options, "localhost", 7878, m_agentTestHelper->m_agent->getDefaultDevice()->getName()); } - + public: std::string m_agentId; std::unique_ptr m_agentTestHelper; - + std::chrono::milliseconds m_delay {}; }; @@ -91,18 +91,18 @@ TEST_F(AgentTest, Constructor) { using namespace configuration; ConfigOptions options {{BufferSize, 17}, {MaxAssets, 8}, {SchemaVersion, "1.7"s}}; - + unique_ptr agent = make_unique(m_agentTestHelper->m_ioContext, TEST_RESOURCE_DIR "/samples/badPath.xml", options); auto context = std::make_shared(); context->m_contract = agent->makePipelineContract(); - + ASSERT_THROW(agent->initialize(context), FatalException); agent.reset(); - + agent = make_unique(m_agentTestHelper->m_ioContext, TEST_RESOURCE_DIR "/samples/test_config.xml", options); - + context = std::make_shared(); context->m_contract = agent->makePipelineContract(); ASSERT_NO_THROW(agent->initialize(context)); @@ -114,17 +114,17 @@ TEST_F(AgentTest, Probe) PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Devices/m:Device@name", "LinuxCNC"); } - + { PARSE_XML_RESPONSE("/"); ASSERT_XML_PATH_EQUAL(doc, "//m:Devices/m:Device@name", "LinuxCNC"); } - + { PARSE_XML_RESPONSE("/LinuxCNC"); ASSERT_XML_PATH_EQUAL(doc, "//m:Devices/m:Device@name", "LinuxCNC"); } - + { PARSE_XML_RESPONSE("/LinuxCNC/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Devices/m:Device@name", "LinuxCNC"); @@ -135,12 +135,12 @@ TEST_F(AgentTest, FailWithDuplicateDeviceUUID) { using namespace configuration; ConfigOptions options {{BufferSize, 17}, {MaxAssets, 8}, {SchemaVersion, "1.5"s}}; - + unique_ptr agent = make_unique(m_agentTestHelper->m_ioContext, TEST_RESOURCE_DIR "/samples/dup_uuid.xml", options); auto context = std::make_shared(); context->m_contract = agent->makePipelineContract(); - + ASSERT_THROW(agent->initialize(context), FatalException); } @@ -159,7 +159,7 @@ TEST_F(AgentTest, should_return_2_6_error_for_unknown_device) { auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, {{configuration::Validation, false}}); - + { PARSE_XML_RESPONSE("/LinuxCN/probe"); string message = (string) "Could not find the device 'LinuxCN'"; @@ -180,7 +180,7 @@ TEST_F(AgentTest, should_return_error_when_path_cannot_be_parsed) ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "INVALID_XPATH"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", message.c_str()); } - + { QueryMap query {{"path", "//Axes?//Linear"}}; PARSE_XML_RESPONSE_QUERY("/current", query); @@ -188,7 +188,7 @@ TEST_F(AgentTest, should_return_error_when_path_cannot_be_parsed) ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "INVALID_XPATH"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", message.c_str()); } - + { QueryMap query {{"path", "//Devices/Device[@name=\"I_DON'T_EXIST\""}}; PARSE_XML_RESPONSE_QUERY("/current", query); @@ -203,7 +203,7 @@ TEST_F(AgentTest, should_return_2_6_error_when_path_cannot_be_parsed) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, {{configuration::Validation, false}}); - + { QueryMap query {{"path", "//////Linear"}}; PARSE_XML_RESPONSE_QUERY("/current", query); @@ -212,7 +212,7 @@ TEST_F(AgentTest, should_return_2_6_error_when_path_cannot_be_parsed) ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidXPath/m:ErrorMessage", message.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidXPath/m:URI", "/current?path=//////Linear"); } - + { QueryMap query {{"path", "//Axes?//Linear"}}; PARSE_XML_RESPONSE_QUERY("/current", query); @@ -221,7 +221,7 @@ TEST_F(AgentTest, should_return_2_6_error_when_path_cannot_be_parsed) ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidXPath/m:ErrorMessage", message.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidXPath/m:URI", "/current?path=//Axes?//Linear"); } - + { QueryMap query {{"path", "//Devices/Device[@name=\"I_DON'T_EXIST\""}}; PARSE_XML_RESPONSE_QUERY("/current", query); @@ -243,12 +243,12 @@ TEST_F(AgentTest, should_handle_a_correct_path) "UNAVAILABLE"); ASSERT_XML_PATH_COUNT(doc, "//m:ComponentStream", 1); } - + { QueryMap query { - {"path", "//Rotary[@name='C']//DataItem[@category='SAMPLE' or @category='CONDITION']"}}; + {"path", "//Rotary[@name='C']//DataItem[@category='SAMPLE' or @category='CONDITION']"}}; PARSE_XML_RESPONSE_QUERY("/current", query); - + ASSERT_XML_PATH_EQUAL(doc, "//m:ComponentStream[@component='Rotary']//m:SpindleSpeed", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:ComponentStream[@component='Rotary']//m:Load", "UNAVAILABLE"); @@ -266,7 +266,7 @@ TEST_F(AgentTest, should_report_an_invalid_uri) EXPECT_EQ(status::not_found, m_agentTestHelper->session()->m_code); EXPECT_FALSE(m_agentTestHelper->m_dispatched); } - + { PARSE_XML_RESPONSE("/bad/path/"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "INVALID_URI"); @@ -274,7 +274,7 @@ TEST_F(AgentTest, should_report_an_invalid_uri) EXPECT_EQ(status::not_found, m_agentTestHelper->session()->m_code); EXPECT_FALSE(m_agentTestHelper->m_dispatched); } - + { PARSE_XML_RESPONSE("/LinuxCNC/current/blah"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "INVALID_URI"); @@ -288,10 +288,10 @@ TEST_F(AgentTest, should_report_an_invalid_uri) TEST_F(AgentTest, should_report_a_2_6_invalid_uri) { using namespace rest_sink; - + m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, {{configuration::Validation, false}}); - + { PARSE_XML_RESPONSE("/bad_path"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI@errorCode", "INVALID_URI"); @@ -301,25 +301,25 @@ TEST_F(AgentTest, should_report_a_2_6_invalid_uri) EXPECT_EQ(status::not_found, m_agentTestHelper->session()->m_code); EXPECT_FALSE(m_agentTestHelper->m_dispatched); } - + { PARSE_XML_RESPONSE("/bad/path/"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI@errorCode", "INVALID_URI"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI/m:ErrorMessage", "0.0.0.0: Cannot find handler for: GET /bad/path/"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI/m:URI", "/bad/path/"); - + EXPECT_EQ(status::not_found, m_agentTestHelper->session()->m_code); EXPECT_FALSE(m_agentTestHelper->m_dispatched); } - + { PARSE_XML_RESPONSE("/LinuxCNC/current/blah"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI@errorCode", "INVALID_URI"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI/m:ErrorMessage", "0.0.0.0: Cannot find handler for: GET /LinuxCNC/current/blah"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI/m:URI", "/LinuxCNC/current/blah"); - + EXPECT_EQ(status::not_found, m_agentTestHelper->session()->m_code); EXPECT_FALSE(m_agentTestHelper->m_dispatched); } @@ -329,21 +329,21 @@ TEST_F(AgentTest, should_handle_current_at) { QueryMap query; PARSE_XML_RESPONSE_QUERY("/current", query); - + addAdapter(); - + // Get the current position auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence(); char line[80] = {0}; - + // Add many events for (int i = 1; i <= 100; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d", i); m_agentTestHelper->m_adapter->processData(line); } - + // Check each current at all the positions. for (int i = 0; i < 100; i++) { @@ -354,7 +354,7 @@ TEST_F(AgentTest, should_handle_current_at) // m_agentTestHelper->printsession(); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line", to_string(i + 1).c_str()); } - + // Test buffer wrapping // Add a large many events for (int i = 101; i <= 301; i++) @@ -362,7 +362,7 @@ TEST_F(AgentTest, should_handle_current_at) sprintf(line, "2021-02-01T12:00:00Z|line|%d", i); m_agentTestHelper->m_adapter->processData(line); } - + // Check each current at all the positions. for (int i = 100; i < 301; i++) { @@ -371,7 +371,7 @@ TEST_F(AgentTest, should_handle_current_at) PARSE_XML_RESPONSE_QUERY("/current", query); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line", to_string(i + 1).c_str()); } - + // Check the first couple of items in the list for (int j = 0; j < 10; j++) { @@ -381,7 +381,7 @@ TEST_F(AgentTest, should_handle_current_at) PARSE_XML_RESPONSE_QUERY("/current", query); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line", to_string(i + 1).c_str()); } - + // Test out of range... { auto i = circ.getSequence() - circ.getBufferSize() - seq - 1; @@ -397,25 +397,25 @@ TEST_F(AgentTest, should_handle_current_at) TEST_F(AgentTest, should_handle_64_bit_current_at) { QueryMap query; - + addAdapter(); - + // Get the current position char line[80] = {0}; - + // Initialize the sliding buffer at a very large number. auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); - + uint64_t start = (((uint64_t)1) << 48) + 1317; circ.setSequence(start); - + // Add many events for (int i = 1; i <= 500; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d", i); m_agentTestHelper->m_adapter->processData(line); } - + // Check each current at all the positions. for (uint64_t i = start + 300; i < start + 500; i++) { @@ -429,22 +429,22 @@ TEST_F(AgentTest, should_handle_64_bit_current_at) TEST_F(AgentTest, should_report_out_of_range_for_current_at) { QueryMap query; - + addAdapter(); - + // Get the current position char line[80] = {0}; - + // Add many events for (int i = 1; i <= 200; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d", i); m_agentTestHelper->m_adapter->processData(line); } - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence(); - + { query["at"] = to_string(seq); sprintf(line, "'at' must be less than %d", int32_t(seq)); @@ -452,9 +452,9 @@ TEST_F(AgentTest, should_report_out_of_range_for_current_at) ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "OUT_OF_RANGE"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", line); } - + seq = circ.getFirstSequence() - 1; - + { query["at"] = to_string(seq); sprintf(line, "'at' must be greater than %d", int32_t(seq)); @@ -468,21 +468,21 @@ TEST_F(AgentTest, should_report_2_6_out_of_range_for_current_at) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, {{configuration::Validation, false}}); - + QueryMap query; - + addAdapter(); - + // Get the current position char line[80] = {0}; - + // Add many events for (int i = 1; i <= 200; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d", i); m_agentTestHelper->m_adapter->processData(line); } - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence(); auto max = seq - 1; @@ -500,9 +500,9 @@ TEST_F(AgentTest, should_report_2_6_out_of_range_for_current_at) ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", "1"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Maximum", to_string(max).c_str()); } - + seq = circ.getFirstSequence() - 1; - + { query["at"] = to_string(seq); sprintf(line, "'at' must be greater than 0"); @@ -523,15 +523,15 @@ TEST_F(AgentTest, AddAdapter) { addAdapter(); } TEST_F(AgentTest, should_download_file) { QueryMap query; - + string uri("/schemas/MTConnectDevices_1.1.xsd"); - + // Register a file with the agent. auto rest = m_agentTestHelper->getRestService(); rest->getFileCache()->setMaxCachedFileSize(100 * 1024); rest->getFileCache()->registerFile( - uri, string(PROJECT_ROOT_DIR "/schemas/MTConnectDevices_1.1.xsd"), "1.1"); - + uri, string(PROJECT_ROOT_DIR "/schemas/MTConnectDevices_1.1.xsd"), "1.1"); + // Reqyest the file... PARSE_TEXT_RESPONSE(uri.c_str()); ASSERT_FALSE(m_agentTestHelper->session()->m_body.empty()); @@ -542,13 +542,13 @@ TEST_F(AgentTest, should_download_file) TEST_F(AgentTest, should_report_not_found_when_cannot_find_file) { QueryMap query; - + string uri("/schemas/MTConnectDevices_1.1.xsd"); - + // Register a file with the agent. auto rest = m_agentTestHelper->getRestService(); rest->getFileCache()->registerFile(uri, string("./BadFileName.xsd"), "1.1"); - + { PARSE_XML_RESPONSE(uri.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error@errorCode", "INVALID_URI"); @@ -561,15 +561,15 @@ TEST_F(AgentTest, should_report_2_6_not_found_when_cannot_find_file) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, {{configuration::Validation, false}}); - + QueryMap query; - + string uri("/schemas/MTConnectDevices_1.1.xsd"); - + // Register a file with the agent. auto rest = m_agentTestHelper->getRestService(); rest->getFileCache()->registerFile(uri, string("./BadFileName.xsd"), "1.1"); - + { PARSE_XML_RESPONSE(uri.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI@errorCode", "INVALID_URI"); @@ -583,20 +583,20 @@ TEST_F(AgentTest, should_include_composition_ids_in_observations) { auto agent = m_agentTestHelper->m_agent.get(); addAdapter(); - + DataItemPtr motor = agent->getDataItemForDevice("LinuxCNC", "zt1"); ASSERT_TRUE(motor); - + DataItemPtr amp = agent->getDataItemForDevice("LinuxCNC", "zt2"); ASSERT_TRUE(amp); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|zt1|100|zt2|200"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Temperature[@dataItemId='zt1']", "100"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Temperature[@dataItemId='zt2']", "200"); - + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Temperature[@dataItemId='zt1']@compositionId", "zmotor"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Temperature[@dataItemId='zt2']@compositionId", @@ -616,7 +616,7 @@ TEST_F(AgentTest, should_report_an_error_when_the_count_is_out_of_range) "query parameter 'count': cannot convert " "string 'NON_INTEGER' to integer"); } - + { QueryMap query {{"count", "-500"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); @@ -625,14 +625,14 @@ TEST_F(AgentTest, should_report_an_error_when_the_count_is_out_of_range) value += to_string(-size); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", value.c_str()); } - + { QueryMap query {{"count", "0"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "OUT_OF_RANGE"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", "'count' must not be zero(0)"); } - + { QueryMap query {{"count", "500"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); @@ -641,7 +641,7 @@ TEST_F(AgentTest, should_report_an_error_when_the_count_is_out_of_range) value += to_string(size); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", value.c_str()); } - + { QueryMap query {{"count", "9999999"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); @@ -650,7 +650,7 @@ TEST_F(AgentTest, should_report_an_error_when_the_count_is_out_of_range) value += to_string(size); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", value.c_str()); } - + { QueryMap query {{"count", "-9999999"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); @@ -665,7 +665,7 @@ TEST_F(AgentTest, should_report_a_2_6_error_when_the_count_is_out_of_range) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, {{configuration::Validation, false}}); - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); int size = circ.getBufferSize() + 1; { @@ -681,7 +681,7 @@ TEST_F(AgentTest, should_report_a_2_6_error_when_the_count_is_out_of_range) ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidParameterValue/m:QueryParameter/m:Type", "integer"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidParameterValue/m:QueryParameter/m:Value", "NON_INTEGER"); } - + { QueryMap query {{"count", "-500"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); @@ -698,7 +698,7 @@ TEST_F(AgentTest, should_report_a_2_6_error_when_the_count_is_out_of_range) ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", to_string(-size + 1).c_str()); } - + { QueryMap query {{"count", "0"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); @@ -712,13 +712,13 @@ TEST_F(AgentTest, should_report_a_2_6_error_when_the_count_is_out_of_range) ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", to_string(-size + 1).c_str()); } - + { QueryMap query {{"count", "500"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); string value("'count' must be less than "); value += to_string(size); - + ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange@errorCode", "OUT_OF_RANGE"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:ErrorMessage", value.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:URI", "/sample?count=500"); @@ -729,13 +729,13 @@ TEST_F(AgentTest, should_report_a_2_6_error_when_the_count_is_out_of_range) ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", to_string(-size + 1).c_str()); } - + { QueryMap query {{"count", "9999999"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); string value("'count' must be less than "); value += to_string(size); - + ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange@errorCode", "OUT_OF_RANGE"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:ErrorMessage", value.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:URI", "/sample?count=9999999"); @@ -746,13 +746,13 @@ TEST_F(AgentTest, should_report_a_2_6_error_when_the_count_is_out_of_range) ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", to_string(-size + 1).c_str()); } - + { QueryMap query {{"count", "-9999999"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); string value("'count' must be greater than "); value += to_string(-size); - + ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange@errorCode", "OUT_OF_RANGE"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:ErrorMessage", value.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:URI", "/sample?count=-9999999"); @@ -768,24 +768,24 @@ TEST_F(AgentTest, should_report_a_2_6_error_when_the_count_is_out_of_range) TEST_F(AgentTest, should_process_addapter_data) { addAdapter(); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[2]", "204"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Alarm[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|alarm|code|nativeCode|severity|state|description"); - + "2021-02-01T12:00:00Z|alarm|code|nativeCode|severity|state|description"); + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); @@ -799,17 +799,17 @@ TEST_F(AgentTest, should_get_samples_using_next_sequence) { QueryMap query; addAdapter(); - + // Get the current position char line[80] = {0}; - + // Add many events for (int i = 1; i <= 300; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d", i); m_agentTestHelper->m_adapter->processData(line); } - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence(); { @@ -825,27 +825,27 @@ TEST_F(AgentTest, should_give_correct_number_of_samples_with_count) addAdapter(); auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence(); - + // Get the current position char line[80] = {0}; - + // Add many events for (int i = 0; i < 128; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d|Xact|%d", i, i); m_agentTestHelper->m_adapter->processData(line); } - + { query["path"] = "//DataItem[@name='Xact']"; query["from"] = to_string(seq); query["count"] = "10"; - + PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@nextSequence", to_string(seq + 20).c_str()); - + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Position", 10); - + // Make sure we got 10 lines for (int j = 0; j < 10; j++) { @@ -859,29 +859,29 @@ TEST_F(AgentTest, should_give_correct_number_of_samples_with_negative_count) { QueryMap query; addAdapter(); - + // Get the current position char line[80] = {0}; - + // Add many events for (int i = 0; i < 128; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d|Xact|%d", i, i); m_agentTestHelper->m_adapter->processData(line); } - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence() - 20; - + { query["path"] = "//DataItem[@name='Xact']"; query["count"] = "-10"; - + PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@nextSequence", to_string(seq).c_str()); - + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Position", 10); - + // Make sure we got 10 lines for (int j = 0; j < 10; j++) { @@ -895,30 +895,30 @@ TEST_F(AgentTest, should_give_correct_number_of_samples_with_to_parameter) { QueryMap query; addAdapter(); - + // Get the current position char line[80] = {0}; - + // Add many events for (int i = 0; i < 128; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d|Xact|%d", i, i); m_agentTestHelper->m_adapter->processData(line); } - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence() - 20; - + { query["path"] = "//DataItem[@name='Xact']"; query["count"] = "10"; query["to"] = to_string(seq); - + PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@nextSequence", to_string(seq + 1).c_str()); - + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Position", 10); - + // Make sure we got 10 lines auto start = seq - 20; for (int j = 0; j < 10; j++) @@ -927,18 +927,18 @@ TEST_F(AgentTest, should_give_correct_number_of_samples_with_to_parameter) ASSERT_XML_PATH_EQUAL(doc, line, to_string(start + j * 2 + 1).c_str()); } } - + { query["path"] = "//DataItem[@name='Xact']"; query["count"] = "10"; query["to"] = to_string(seq); query["from"] = to_string(seq - 10); - + PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@nextSequence", to_string(seq + 1).c_str()); - + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Position", 5); - + // Make sure we got 10 lines auto start = seq - 10; for (int j = 0; j < 5; j++) @@ -947,7 +947,7 @@ TEST_F(AgentTest, should_give_correct_number_of_samples_with_to_parameter) ASSERT_XML_PATH_EQUAL(doc, line, to_string(start + j * 2 + 1).c_str()); } } - + // TODO: Test negative conditions // count < 0 // to > nextSequence @@ -963,11 +963,11 @@ TEST_F(AgentTest, should_give_empty_stream_with_no_new_samples) ASSERT_XML_PATH_EQUAL(doc, "//m:ComponentStream[@componentId='path']/m:Condition/m:Unavailable", nullptr); ASSERT_XML_PATH_EQUAL( - doc, "//m:ComponentStream[@componentId='path']/m:Condition/m:Unavailable@qualifier", - nullptr); + doc, "//m:ComponentStream[@componentId='path']/m:Condition/m:Unavailable@qualifier", + nullptr); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:RotaryMode", "SPINDLE"); } - + { auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); QueryMap query {{"from", to_string(circ.getSequence())}}; @@ -980,31 +980,31 @@ TEST_F(AgentTest, should_not_leak_observations_when_added_to_buffer) { auto agent = m_agentTestHelper->m_agent.get(); QueryMap query; - + string device("LinuxCNC"), key("badKey"), value("ON"); SequenceNumber_t seqNum {0}; auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto event1 = circ.getFromBuffer(seqNum); ASSERT_FALSE(event1); - + { query["from"] = to_string(circ.getSequence()); PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:Streams", nullptr); } - + key = "power"; - + auto di2 = agent->getDataItemForDevice(device, key); seqNum = m_agentTestHelper->addToBuffer(di2, {{"VALUE", value}}, chrono::system_clock::now()); auto event2 = circ.getFromBuffer(seqNum); ASSERT_EQ(3, event2.use_count()); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:PowerState", "ON"); } - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:PowerState[1]", "UNAVAILABLE"); @@ -1017,53 +1017,53 @@ TEST_F(AgentTest, should_int_64_sequences_should_not_truncate_at_32_bits) #ifndef WIN32 QueryMap query; addAdapter(); - + // Set the sequence number near MAX_UINT32 auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); circ.setSequence(0xFFFFFFA0); SequenceNumber_t seq = circ.getSequence(); ASSERT_EQ((int64_t)0xFFFFFFA0, seq); - + // Get the current position char line[80] = {0}; - + // Add many events for (int i = 0; i < 128; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d", i); m_agentTestHelper->m_adapter->processData(line); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line@sequence", to_string(seq + i).c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@nextSequence", to_string(seq + i + 1).c_str()); } - + { query["from"] = to_string(seq); query["count"] = "128"; - + PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@nextSequence", to_string(seq + i + 1).c_str()); - + for (int j = 0; j <= i; j++) { sprintf(line, "//m:DeviceStream//m:Line[%d]@sequence", j + 1); ASSERT_XML_PATH_EQUAL(doc, line, to_string(seq + j).c_str()); } } - + for (int j = 0; j <= i; j++) { query["from"] = to_string(seq + j); query["count"] = "1"; - + PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line@sequence", to_string(seq + j).c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@nextSequence", to_string(seq + j + 1).c_str()); } } - + ASSERT_EQ(uint64_t(0xFFFFFFA0) + 128ul, circ.getSequence()); #endif } @@ -1071,22 +1071,22 @@ TEST_F(AgentTest, should_int_64_sequences_should_not_truncate_at_32_bits) TEST_F(AgentTest, should_not_allow_duplicates_values) { addAdapter(); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[2]", "204"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|205"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); @@ -1098,20 +1098,20 @@ TEST_F(AgentTest, should_not_allow_duplicates_values) TEST_F(AgentTest, should_not_duplicate_unavailable_when_disconnected) { addAdapter({{configuration::FilterDuplicates, true}}); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|205"); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[2]", "204"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[3]", "205"); } - + m_agentTestHelper->m_adapter->disconnected(); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); @@ -1119,11 +1119,11 @@ TEST_F(AgentTest, should_not_duplicate_unavailable_when_disconnected) ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[3]", "205"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[4]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->connected(); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|205"); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); @@ -1143,31 +1143,31 @@ TEST_F(AgentTest, should_handle_auto_available_if_adapter_option_is_set) auto d = agent->getDevices().front(); StringList devices; devices.emplace_back(*d->getComponentName()); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Availability[1]", "UNAVAILABLE"); } - + agent->connected(id, devices, true); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Availability[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Availability[2]", "AVAILABLE"); } - + agent->disconnected(id, devices, true); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Availability[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Availability[2]", "AVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Availability[3]", "UNAVAILABLE"); } - + agent->connected(id, devices, true); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Availability[1]", "UNAVAILABLE"); @@ -1183,66 +1183,66 @@ TEST_F(AgentTest, should_handle_multiple_disconnnects) auto agent = m_agentTestHelper->m_agent.get(); auto adapter = m_agentTestHelper->m_adapter; auto id = adapter->getIdentity(); - + auto d = agent->getDevices().front(); StringList devices; devices.emplace_back(*d->getComponentName()); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//*[@dataItemId='p1'][1]", "UNAVAILABLE"); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Unavailable[@dataItemId='cmp']", 1); } - + agent->connected(id, devices, false); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|block|GTH"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|cmp|normal||||"); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//*[@dataItemId='p1'][1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//*[@dataItemId='p1'][2]", "GTH"); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//*[@dataItemId='p1']", 2); - + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Unavailable[@dataItemId='cmp']", 1); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Normal[@dataItemId='cmp']", 1); } - + agent->disconnected(id, devices, false); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Unavailable[@dataItemId='cmp']", 2); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Normal[@dataItemId='cmp']", 1); - + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//*[@dataItemId='p1'][2]", "GTH"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//*[@dataItemId='p1'][3]", "UNAVAILABLE"); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//*[@dataItemId='p1']", 3); } - + agent->disconnected(id, devices, false); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Unavailable[@dataItemId='cmp']", 2); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Normal[@dataItemId='cmp']", 1); - + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//*[@dataItemId='p1'][3]", "UNAVAILABLE"); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//*[@dataItemId='p1']", 3); } - + agent->connected(id, devices, false); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|block|GTH"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|cmp|normal||||"); - + agent->disconnected(id, devices, false); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Unavailable[@dataItemId='cmp']", 3); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Normal[@dataItemId='cmp']", 2); - + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//*[@dataItemId='p1']", 5); } } @@ -1250,18 +1250,18 @@ TEST_F(AgentTest, should_handle_multiple_disconnnects) TEST_F(AgentTest, should_ignore_timestamps_if_configured_to_do_so) { addAdapter(); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[2]@timestamp", "2021-02-01T12:00:00Z"); } - + m_agentTestHelper->m_adapter->setOptions({{configuration::IgnoreTimestamps, true}}); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|205"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); @@ -1273,7 +1273,7 @@ TEST_F(AgentTest, should_ignore_timestamps_if_configured_to_do_so) TEST_F(AgentTest, InitialTimeSeriesValues) { addAdapter(); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:PositionTimeSeries[@dataItemId='x1ts']", @@ -1285,41 +1285,41 @@ TEST_F(AgentTest, should_support_dynamic_calibration_data) { addAdapter({{configuration::ConversionRequired, true}}); auto agent = m_agentTestHelper->getAgent(); - + // Add a 10.111000 seconds m_agentTestHelper->m_adapter->protocolCommand( - "* calibration:Yact|.01|200.0|Zact|0.02|300|Xts|0.01|500"); + "* calibration:Yact|.01|200.0|Zact|0.02|300|Xts|0.01|500"); auto di = agent->getDataItemForDevice("LinuxCNC", "Yact"); ASSERT_TRUE(di); - + // TODO: Fix conversions auto &conv1 = di->getConverter(); ASSERT_TRUE(conv1); ASSERT_EQ(0.01, conv1->factor()); ASSERT_EQ(200.0, conv1->offset()); - + di = agent->getDataItemForDevice("LinuxCNC", "Zact"); ASSERT_TRUE(di); - + auto &conv2 = di->getConverter(); ASSERT_TRUE(conv2); ASSERT_EQ(0.02, conv2->factor()); ASSERT_EQ(300.0, conv2->offset()); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|Yact|200|Zact|600"); m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|Xts|25|| 5118 5118 5118 5118 5118 5118 5118 5118 5118 5118 5118 5118 " - "5119 5119 5118 " - "5118 5117 5117 5119 5119 5118 5118 5118 5118 5118"); - + "2021-02-01T12:00:00Z|Xts|25|| 5118 5118 5118 5118 5118 5118 5118 5118 5118 5118 5118 5118 " + "5119 5119 5118 " + "5118 5117 5117 5119 5119 5118 5118 5118 5118 5118"); + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[@dataItemId='y1']", "4"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[@dataItemId='z1']", "18"); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:PositionTimeSeries[@dataItemId='x1ts']", - "56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.19 56.19 56.18 " - "56.18 56.17 56.17 56.19 56.19 56.18 56.18 56.18 56.18 56.18"); + doc, "//m:DeviceStream//m:PositionTimeSeries[@dataItemId='x1ts']", + "56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.19 56.19 56.18 " + "56.18 56.17 56.17 56.19 56.19 56.18 56.18 56.18 56.18 56.18"); } } @@ -1327,32 +1327,32 @@ TEST_F(AgentTest, should_filter_as_specified_in_1_3_test_1) { m_agentTestHelper->createAgent("/samples/filter_example_1.3.xml", 8, 4, "1.5", 25); addAdapter(); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|load|100"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[2]", "100"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|load|103"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|load|106"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[2]", "100"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[3]", "106"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|load|106|load|108|load|112"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); @@ -1366,15 +1366,15 @@ TEST_F(AgentTest, should_filter_as_specified_in_1_3_test_2) { m_agentTestHelper->createAgent("/samples/filter_example_1.3.xml", 8, 4, "1.5", 25); addAdapter(); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2018-04-27T05:00:26.555666|load|100|pos|20"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); @@ -1382,10 +1382,10 @@ TEST_F(AgentTest, should_filter_as_specified_in_1_3_test_2) ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[2]", "20"); } - + m_agentTestHelper->m_adapter->processData("2018-04-27T05:00:32.000666|load|103|pos|25"); m_agentTestHelper->m_adapter->processData("2018-04-27T05:00:36.888666|load|106|pos|30"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); @@ -1395,10 +1395,10 @@ TEST_F(AgentTest, should_filter_as_specified_in_1_3_test_2) ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[2]", "20"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[3]", "30"); } - + m_agentTestHelper->m_adapter->processData( - "2018-04-27T05:00:40.25|load|106|load|108|load|112|pos|35|pos|40"); - + "2018-04-27T05:00:40.25|load|106|load|108|load|112|pos|35|pos|40"); + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); @@ -1409,9 +1409,9 @@ TEST_F(AgentTest, should_filter_as_specified_in_1_3_test_2) ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[2]", "20"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[3]", "30"); } - + m_agentTestHelper->m_adapter->processData("2018-04-27T05:00:47.50|pos|45|pos|50"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); @@ -1431,24 +1431,24 @@ TEST_F(AgentTest, period_filter_should_work_with_ignore_timestamps) // Test period filter with ignore timestamps m_agentTestHelper->createAgent("/samples/filter_example_1.3.xml", 8, 4, "1.5", 25); addAdapter({{configuration::IgnoreTimestamps, true}}); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2018-04-27T05:00:26.555666|load|100|pos|20"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[2]", "20"); } - + m_agentTestHelper->m_adapter->processData("2018-04-27T05:01:32.000666|load|103|pos|25"); this_thread::sleep_for(11s); m_agentTestHelper->m_adapter->processData("2018-04-27T05:01:40.888666|load|106|pos|30"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); @@ -1462,23 +1462,23 @@ TEST_F(AgentTest, period_filter_should_work_with_relative_time) // Test period filter with relative time m_agentTestHelper->createAgent("/samples/filter_example_1.3.xml", 8, 4, "1.5", 25); addAdapter({{configuration::RelativeTime, true}}); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("0|load|100|pos|20"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[2]", "20"); } - + m_agentTestHelper->m_adapter->processData("5000|load|103|pos|25"); m_agentTestHelper->m_adapter->processData("11000|load|106|pos|30"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); @@ -1490,13 +1490,13 @@ TEST_F(AgentTest, period_filter_should_work_with_relative_time) TEST_F(AgentTest, reset_triggered_should_work) { addAdapter(); - + m_agentTestHelper->m_adapter->processData("TIME1|pcount|0"); m_agentTestHelper->m_adapter->processData("TIME2|pcount|1"); m_agentTestHelper->m_adapter->processData("TIME3|pcount|2"); m_agentTestHelper->m_adapter->processData("TIME4|pcount|0:DAY"); m_agentTestHelper->m_adapter->processData("TIME3|pcount|5"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:PartCount[1]", "UNAVAILABLE"); @@ -1513,58 +1513,58 @@ TEST_F(AgentTest, reset_triggered_should_work) TEST_F(AgentTest, should_honor_references_when_getting_current_or_sample) { using namespace device_model; - + m_agentTestHelper->createAgent("/samples/reference_example.xml"); addAdapter(); auto agent = m_agentTestHelper->getAgent(); - + string id = "mf"; auto item = agent->getDataItemForDevice((string) "LinuxCNC", id); auto comp = item->getComponent(); - + auto references = comp->getList("References"); ASSERT_TRUE(references); ASSERT_EQ(3, references->size()); auto reference = references->begin(); - + EXPECT_EQ("DataItemRef", (*reference)->getName()); - + EXPECT_EQ("chuck", (*reference)->get("name")); EXPECT_EQ("c4", (*reference)->get("idRef")); - + auto ref = dynamic_pointer_cast(*reference); ASSERT_TRUE(ref); - + ASSERT_EQ(Reference::DATA_ITEM, ref->getReferenceType()); ASSERT_TRUE(ref->getDataItem().lock()) << "DataItem was not resolved"; reference++; - + EXPECT_EQ("door", (*reference)->get("name")); EXPECT_EQ("d2", (*reference)->get("idRef")); - + ref = dynamic_pointer_cast(*reference); ASSERT_TRUE(ref); - + ASSERT_EQ(Reference::DATA_ITEM, ref->getReferenceType()); ASSERT_TRUE(ref->getDataItem().lock()) << "DataItem was not resolved"; - + reference++; EXPECT_EQ("electric", (*reference)->get("name")); EXPECT_EQ("ele", (*reference)->get("idRef")); - + ref = dynamic_pointer_cast(*reference); ASSERT_TRUE(ref); - + ASSERT_EQ(Reference::COMPONENT, ref->getReferenceType()); ASSERT_TRUE(ref->getComponent().lock()) << "DataItem was not resolved"; - + // Additional data items should be included { QueryMap query {{"path", "//BarFeederInterface"}}; PARSE_XML_RESPONSE_QUERY("/current", query); - + ASSERT_XML_PATH_EQUAL( - doc, "//m:ComponentStream[@component='BarFeederInterface']//m:MaterialFeed", "UNAVAILABLE"); + doc, "//m:ComponentStream[@component='BarFeederInterface']//m:MaterialFeed", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:ComponentStream[@component='Door']//m:DoorState", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:ComponentStream[@component='Rotary']//m:ChuckState", @@ -1577,34 +1577,34 @@ TEST_F(AgentTest, should_honor_discrete_data_items_and_not_filter_dups) m_agentTestHelper->createAgent("/samples/discrete_example.xml"); addAdapter({{configuration::FilterDuplicates, true}}); auto agent = m_agentTestHelper->getAgent(); - + auto msg = agent->getDataItemForDevice("LinuxCNC", "message"); ASSERT_TRUE(msg); ASSERT_EQ(true, msg->isDiscreteRep()); - + // Validate we are dup checking. { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|205"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[2]", "204"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[3]", "205"); - + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:MessageDiscrete[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|message|Hi|Hello"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|message|Hi|Hello"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|message|Hi|Hello"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:MessageDiscrete[1]", "UNAVAILABLE"); @@ -1619,17 +1619,17 @@ TEST_F(AgentTest, should_honor_discrete_data_items_and_not_filter_dups) TEST_F(AgentTest, should_honor_upcase_values) { addAdapter({{configuration::FilterDuplicates, true}, {configuration::UpcaseDataItemValue, true}}); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|mode|Hello"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:ControllerMode", "HELLO"); } - + m_agentTestHelper->m_adapter->setOptions({{configuration::UpcaseDataItemValue, false}}); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|mode|Hello"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:ControllerMode", "Hello"); @@ -1642,7 +1642,7 @@ TEST_F(AgentTest, should_handle_condition_activation) auto agent = m_agentTestHelper->getAgent(); auto logic = agent->getDataItemForDevice("LinuxCNC", "lp"); ASSERT_TRUE(logic); - + // Validate we are dup checking. { PARSE_XML_RESPONSE("/current"); @@ -1652,29 +1652,29 @@ TEST_F(AgentTest, should_handle_condition_activation) "m:Unavailable[@dataItemId='lp']", 1); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|lp|NORMAL||||XXX"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Normal", - "XXX"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Normal", + "XXX"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); } - + m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|lp|FAULT|2218|ALARM_B|HIGH|2218-1 ALARM_B UNUSABLE G-code A side " - "FFFFFFFF"); - + "2021-02-01T12:00:00Z|lp|FAULT|2218|ALARM_B|HIGH|2218-1 ALARM_B UNUSABLE G-code A side " + "FFFFFFFF"); + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault", - "2218-1 ALARM_B UNUSABLE G-code A side FFFFFFFF"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault", + "2218-1 ALARM_B UNUSABLE G-code A side FFFFFFFF"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//" "m:ComponentStream[@component='Controller']/m:Condition/" @@ -1691,28 +1691,28 @@ TEST_F(AgentTest, should_handle_condition_activation) "m:Fault@qualifier", "HIGH"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|lp|NORMAL||||"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Normal", - 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Normal", + 1); } - + m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|lp|FAULT|4200|ALARM_D||4200 ALARM_D Power on effective parameter set"); - + "2021-02-01T12:00:00Z|lp|FAULT|4200|ALARM_D||4200 ALARM_D Power on effective parameter set"); + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault", - "4200 ALARM_D Power on effective parameter set"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault", + "4200 ALARM_D Power on effective parameter set"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//" "m:ComponentStream[@component='Controller']/m:Condition/" @@ -1724,21 +1724,21 @@ TEST_F(AgentTest, should_handle_condition_activation) "m:Fault@nativeSeverity", "ALARM_D"); } - + m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|lp|FAULT|2218|ALARM_B|HIGH|2218-1 ALARM_B UNUSABLE G-code A side " - "FFFFFFFF"); - + "2021-02-01T12:00:00Z|lp|FAULT|2218|ALARM_B|HIGH|2218-1 ALARM_B UNUSABLE G-code A side " + "FFFFFFFF"); + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 2); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 2); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[1]", - "4200 ALARM_D Power on effective parameter set"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[1]", + "4200 ALARM_D Power on effective parameter set"); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[2]", - "2218-1 ALARM_B UNUSABLE G-code A side FFFFFFFF"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[2]", + "2218-1 ALARM_B UNUSABLE G-code A side FFFFFFFF"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//" "m:ComponentStream[@component='Controller']/m:Condition/" @@ -1755,18 +1755,18 @@ TEST_F(AgentTest, should_handle_condition_activation) "m:Fault[2]@qualifier", "HIGH"); } - + m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|lp|FAULT|4200|ALARM_D|LOW|4200 ALARM_D Power on effective parameter " - "set"); - + "2021-02-01T12:00:00Z|lp|FAULT|4200|ALARM_D|LOW|4200 ALARM_D Power on effective parameter " + "set"); + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 2); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 2); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[1]", - "2218-1 ALARM_B UNUSABLE G-code A side FFFFFFFF"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[1]", + "2218-1 ALARM_B UNUSABLE G-code A side FFFFFFFF"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//" "m:ComponentStream[@component='Controller']/m:Condition/" @@ -1783,35 +1783,35 @@ TEST_F(AgentTest, should_handle_condition_activation) "m:Fault[1]@qualifier", "HIGH"); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[2]", - "4200 ALARM_D Power on effective parameter set"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[2]", + "4200 ALARM_D Power on effective parameter set"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|lp|NORMAL|2218|||"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//" "m:ComponentStream[@component='Controller']/m:Condition/" "m:Fault[1]@nativeCode", "4200"); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[1]", - "4200 ALARM_D Power on effective parameter set"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[1]", + "4200 ALARM_D Power on effective parameter set"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|lp|NORMAL||||"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Normal", - 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Normal", + 1); } } @@ -1819,55 +1819,55 @@ TEST_F(AgentTest, should_handle_empty_entry_as_last_pair_from_adapter) { addAdapter({{configuration::FilterDuplicates, true}}); auto agent = m_agentTestHelper->getAgent(); - + auto program = agent->getDataItemForDevice("LinuxCNC", "program"); ASSERT_TRUE(program); - + auto tool_id = agent->getDataItemForDevice("LinuxCNC", "block"); ASSERT_TRUE(tool_id); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|program|A|block|B"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", "A"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block", "B"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|program||block|B"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", ""); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block", "B"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|program||block|"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", ""); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block", ""); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|program|A|block|B"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|program|A|block|"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", "A"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block", ""); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|program|A|block|B|line|C"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|program|D|block||line|E"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", "D"); @@ -1882,17 +1882,17 @@ TEST_F(AgentTest, should_handle_constant_values) auto agent = m_agentTestHelper->getAgent(); auto di = agent->getDataItemForDevice("LinuxCNC", "block"); ASSERT_TRUE(di); - + di->setConstantValue("UNAVAILABLE"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|block|G01X00|Smode|INDEX|line|204"); - + "2021-02-01T12:00:00Z|block|G01X00|Smode|INDEX|line|204"); + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block[1]", "UNAVAILABLE"); @@ -1906,14 +1906,14 @@ TEST_F(AgentTest, should_handle_constant_values) TEST_F(AgentTest, should_handle_bad_data_item_from_adapter) { addAdapter(); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|bad|ignore|dummy|1244|line|204"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); @@ -1927,18 +1927,18 @@ TEST_F(AgentTest, adapter_should_receive_commands) { addAdapter(); auto agent = m_agentTestHelper->getAgent(); - + auto device = agent->getDeviceByName("LinuxCNC"); ASSERT_TRUE(device); ASSERT_FALSE(device->preserveUuid()); - + m_agentTestHelper->m_adapter->parseBuffer("* uuid: MK-1234\n"); m_agentTestHelper->m_ioContext.run_for(2000ms); - + m_agentTestHelper->m_adapter->parseBuffer("* manufacturer: Big Tool\n"); m_agentTestHelper->m_adapter->parseBuffer("* serialNumber: XXXX-1234\n"); m_agentTestHelper->m_adapter->parseBuffer("* station: YYYY\n"); - + { PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Device@uuid", "MK-1234"); @@ -1946,19 +1946,19 @@ TEST_F(AgentTest, adapter_should_receive_commands) ASSERT_XML_PATH_EQUAL(doc, "//m:Description@serialNumber", "XXXX-1234"); ASSERT_XML_PATH_EQUAL(doc, "//m:Description@station", "YYYY"); } - + device = agent->getDeviceByName("LinuxCNC"); ASSERT_TRUE(device); - + device->setPreserveUuid(true); m_agentTestHelper->m_adapter->parseBuffer("* uuid: XXXXXXX\n"); m_agentTestHelper->m_ioContext.run_for(1000ms); - + { PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Device@uuid", "MK-1234"); } - + auto &options = m_agentTestHelper->m_adapter->getOptions(); ASSERT_EQ("MK-1234", *GetOption(options, configuration::Device)); } @@ -1968,21 +1968,21 @@ TEST_F(AgentTest, adapter_should_not_process_uuid_command_with_preserve_uuid) auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.3", 4, false, true, {{configuration::PreserveUUID, true}}); addAdapter(); - + auto device = agent->getDeviceByName("LinuxCNC"); ASSERT_TRUE(device); ASSERT_TRUE(device->preserveUuid()); - + m_agentTestHelper->m_adapter->parseBuffer("* uuid: MK-1234\n"); - + { PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Device@uuid", "000"); } - + m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|block|G01X00|mode|AUTOMATIC|execution|READY"); - + "2021-02-01T12:00:00Z|block|G01X00|mode|AUTOMATIC|execution|READY"); + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Block", "G01X00"); @@ -1995,37 +1995,37 @@ TEST_F(AgentTest, adapter_should_receive_device_commands) { m_agentTestHelper->createAgent("/samples/two_devices.xml"); auto agent = m_agentTestHelper->getAgent(); - + auto device1 = agent->getDeviceByName("Device1"); ASSERT_TRUE(device1); auto device2 = agent->getDeviceByName("Device2"); ASSERT_TRUE(device2); - + addAdapter(); - + auto device = - GetOption(m_agentTestHelper->m_adapter->getOptions(), configuration::Device); + GetOption(m_agentTestHelper->m_adapter->getOptions(), configuration::Device); ASSERT_EQ(device1->getComponentName(), device); - + m_agentTestHelper->m_adapter->parseBuffer("* device: device-2\n"); device = GetOption(m_agentTestHelper->m_adapter->getOptions(), configuration::Device); ASSERT_EQ(string(*device2->getUuid()), device); - + m_agentTestHelper->m_adapter->parseBuffer("* uuid: new-uuid\n"); - + device2 = agent->getDeviceByName("Device2"); ASSERT_TRUE(device2); - + ASSERT_EQ("new-uuid", string(*device2->getUuid())); - + m_agentTestHelper->m_adapter->parseBuffer("* device: device-1\n"); device = GetOption(m_agentTestHelper->m_adapter->getOptions(), configuration::Device); ASSERT_EQ(string(*device1->getUuid()), device); - + m_agentTestHelper->m_adapter->parseBuffer("* uuid: another-uuid\n"); device1 = agent->getDeviceByName("Device1"); ASSERT_TRUE(device1); - + ASSERT_EQ("another-uuid", string(*device1->getUuid())); } @@ -2033,19 +2033,19 @@ TEST_F(AgentTest, adapter_command_should_set_adapter_and_mtconnect_versions) { m_agentTestHelper->createAgent("/samples/kinematics.xml", 8, 4, "1.7", 25); addAdapter(); - + auto printer = m_agentTestHelper->m_agent->getPrinter("xml"); ASSERT_FALSE(printer->getModelChangeTime().empty()); - + { PARSE_XML_RESPONSE("/Agent/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:AdapterSoftwareVersion", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectVersion", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->parseBuffer("* adapterVersion: 2.10\n"); m_agentTestHelper->m_adapter->parseBuffer("* mtconnectVersion: 1.7\n"); - + { PARSE_XML_RESPONSE("/Agent/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:AdapterSoftwareVersion", "2.10"); @@ -2054,24 +2054,24 @@ TEST_F(AgentTest, adapter_command_should_set_adapter_and_mtconnect_versions) printer->getModelChangeTime().c_str()); ; } - + // Test updating device change time string old = printer->getModelChangeTime(); m_agentTestHelper->m_adapter->parseBuffer("* uuid: another-uuid\n"); ASSERT_GT(printer->getModelChangeTime(), old); - + { PARSE_XML_RESPONSE("/Agent/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@deviceModelChangeTime", printer->getModelChangeTime().c_str()); ; } - + // Test Case insensitivity - + m_agentTestHelper->m_adapter->parseBuffer("* adapterversion: 3.10\n"); m_agentTestHelper->m_adapter->parseBuffer("* mtconnectversion: 1.6\n"); - + { PARSE_XML_RESPONSE("/Agent/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:AdapterSoftwareVersion", "3.10"); @@ -2085,14 +2085,14 @@ TEST_F(AgentTest, should_handle_uuid_change) auto device = agent->getDeviceByName("LinuxCNC"); ASSERT_TRUE(device); ASSERT_FALSE(device->preserveUuid()); - + addAdapter(); - + m_agentTestHelper->m_adapter->parseBuffer("* uuid: MK-1234\n"); m_agentTestHelper->m_adapter->parseBuffer("* manufacturer: Big Tool\n"); m_agentTestHelper->m_adapter->parseBuffer("* serialNumber: XXXX-1234\n"); m_agentTestHelper->m_adapter->parseBuffer("* station: YYYY\n"); - + { PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Device@uuid", "MK-1234"); @@ -2100,11 +2100,11 @@ TEST_F(AgentTest, should_handle_uuid_change) ASSERT_XML_PATH_EQUAL(doc, "//m:Description@serialNumber", "XXXX-1234"); ASSERT_XML_PATH_EQUAL(doc, "//m:Description@station", "YYYY"); } - + auto *pipe = static_cast(m_agentTestHelper->m_adapter->getPipeline()); - + ASSERT_EQ("MK-1234", pipe->getDevice()); - + { // TODO: Fix and make sure dom is updated so this xpath will parse correctly. // PARSE_XML_RESPONSE("/current?path=//Device[@uuid=\"MK-1234\"]"); @@ -2120,7 +2120,7 @@ TEST_F(AgentTest, should_handle_uuid_change) TEST_F(AgentTest, interval_should_be_a_valid_integer_value) { QueryMap query; - + /// - Cannot be test or a non-integer value { query["interval"] = "NON_INTEGER"; @@ -2130,7 +2130,7 @@ TEST_F(AgentTest, interval_should_be_a_valid_integer_value) "query parameter 'interval': cannot " "convert string 'NON_INTEGER' to integer"); } - + /// - Cannot be nagative { query["interval"] = "-123"; @@ -2138,7 +2138,7 @@ TEST_F(AgentTest, interval_should_be_a_valid_integer_value) ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "OUT_OF_RANGE"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", "'interval' must be greater than -1"); } - + /// - Cannot be >= 2147483647 { query["interval"] = "2147483647"; @@ -2146,7 +2146,7 @@ TEST_F(AgentTest, interval_should_be_a_valid_integer_value) ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "OUT_OF_RANGE"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", "'interval' must be less than 2147483647"); } - + /// - Cannot wrap around and create a negative number was set as a int32 { query["interval"] = "999999999999999999"; @@ -2163,7 +2163,7 @@ TEST_F(AgentTest, interval_should_be_a_valid_integer_value_in_2_6) m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, {{configuration::Validation, false}}); QueryMap query; - + /// - Cannot be test or a non-integer value { query["interval"] = "NON_INTEGER"; @@ -2178,7 +2178,7 @@ TEST_F(AgentTest, interval_should_be_a_valid_integer_value_in_2_6) ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidParameterValue/m:QueryParameter/m:Type", "integer"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidParameterValue/m:QueryParameter/m:Value", "NON_INTEGER"); } - + /// - Cannot be nagative { query["interval"] = "-123"; @@ -2192,13 +2192,13 @@ TEST_F(AgentTest, interval_should_be_a_valid_integer_value_in_2_6) ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", "0"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Maximum", "2147483646"); } - + /// - Cannot be >= 2147483647 { query["interval"] = "2147483647"; PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange@errorCode", "OUT_OF_RANGE"); - + ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:URI", "/sample?interval=2147483647"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:ErrorMessage", "'interval' must be less than 2147483647"); @@ -2207,7 +2207,7 @@ TEST_F(AgentTest, interval_should_be_a_valid_integer_value_in_2_6) ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", "0"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Maximum", "2147483646"); } - + /// - Cannot wrap around and create a negative number was set as a int32 { query["interval"] = "999999999999999999"; @@ -2231,18 +2231,18 @@ TEST_F(AgentTest, should_stream_data_with_interval) auto rest = m_agentTestHelper->getRestService(); auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); rest->start(); - + // Start a thread... QueryMap query; query["interval"] = "50"; query["heartbeat"] = to_string(heartbeatFreq.count()); query["from"] = to_string(circ.getSequence()); - + // Heartbeat test. Heartbeat should be sent in 200ms. Give // 25ms range. { auto slop {35ms}; - + auto startTime = system_clock::now(); PARSE_XML_STREAM_QUERY("/LinuxCNC/sample", query); while (m_agentTestHelper->m_session->m_chunkBody.empty() && @@ -2251,34 +2251,34 @@ TEST_F(AgentTest, should_stream_data_with_interval) auto delta = system_clock::now() - startTime; cout << "Delta after heartbeat: " << delta.count() << endl; ASSERT_FALSE(m_agentTestHelper->m_session->m_chunkBody.empty()); - + PARSE_XML_CHUNK(); ASSERT_XML_PATH_EQUAL(doc, "//m:Streams", nullptr); EXPECT_GT((heartbeatFreq + slop), delta) - << "delta " << delta.count() << " < hbf " << (heartbeatFreq + slop).count(); + << "delta " << delta.count() << " < hbf " << (heartbeatFreq + slop).count(); EXPECT_LT(heartbeatFreq, delta) << "delta > hbf: " << delta.count(); - + m_agentTestHelper->m_session->closeStream(); } - + // Set some data and make sure we get data within 40ms. // Again, allow for some slop. { auto delay {40ms}; auto slop {35ms}; - + PARSE_XML_STREAM_QUERY("/LinuxCNC/sample", query); m_agentTestHelper->m_ioContext.run_for(delay); - + auto startTime = system_clock::now(); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); m_agentTestHelper->m_ioContext.run_for(5ms); auto delta = system_clock::now() - startTime; cout << "Delta after data: " << delta.count() << endl; - + ASSERT_FALSE(m_agentTestHelper->m_session->m_chunkBody.empty()); PARSE_XML_CHUNK(); - + auto deltaMS = duration_cast(delta); EXPECT_GT(slop, deltaMS) << "delta " << deltaMS.count() << " < delay " << slop.count(); } @@ -2290,9 +2290,9 @@ TEST_F(AgentTest, should_signal_observer_when_observations_arrive) addAdapter(); auto rest = m_agentTestHelper->getRestService(); rest->start(); - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); - + /// - Set up streaming every 100ms with a 1000ms heartbeat std::map query; query["interval"] = "100"; @@ -2300,7 +2300,7 @@ TEST_F(AgentTest, should_signal_observer_when_observations_arrive) query["count"] = "10"; query["from"] = to_string(circ.getSequence()); query["path"] = "//DataItem[@name='line']"; - + /// - Test to make sure the signal will push the sequence number forward and capture /// the new data. { @@ -2312,7 +2312,7 @@ TEST_F(AgentTest, should_signal_observer_when_observations_arrive) } m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); m_agentTestHelper->m_ioContext.run_for(200ms); - + PARSE_XML_CHUNK(); ASSERT_XML_PATH_EQUAL(doc, "//m:Line@sequence", seq.c_str()); } @@ -2324,9 +2324,9 @@ TEST_F(AgentTest, should_fail_if_from_is_out_of_range) addAdapter(); auto rest = m_agentTestHelper->getRestService(); rest->start(); - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); - + // Start a thread... std::map query; query["interval"] = "100"; @@ -2334,14 +2334,14 @@ TEST_F(AgentTest, should_fail_if_from_is_out_of_range) query["count"] = "10"; query["from"] = to_string(circ.getSequence() + 5); query["path"] = "//DataItem[@name='line']"; - + // Test to make sure the signal will push the sequence number forward and capture // the new data. { PARSE_XML_RESPONSE_QUERY("/LinuxCNC/sample", query); auto seq = to_string(circ.getSequence() + 20ull); m_agentTestHelper->m_ioContext.run_for(200ms); - + ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "OUT_OF_RANGE"); } } @@ -2356,18 +2356,18 @@ TEST_F(AgentTest, should_fail_if_from_is_out_of_range) TEST_F(AgentTest, should_allow_making_observations_via_http_put) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "1.3", 4, true); - + QueryMap queries; string body; - + queries["time"] = "2021-02-01T12:00:00Z"; queries["line"] = "205"; queries["power"] = "ON"; - + { PARSE_XML_RESPONSE_PUT("/LinuxCNC", body, queries); } - + { PARSE_XML_RESPONSE("/LinuxCNC/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Line@timestamp", "2021-02-01T12:00:00Z"); @@ -2380,17 +2380,17 @@ TEST_F(AgentTest, should_allow_making_observations_via_http_put) TEST_F(AgentTest, put_condition_should_parse_condition_data) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "1.3", 4, true); - + QueryMap queries; string body; - + queries["time"] = "2021-02-01T12:00:00Z"; queries["lp"] = "FAULT|2001|1||SCANHISTORYRESET"; - + { PARSE_XML_RESPONSE_PUT("/LinuxCNC", body, queries); } - + { PARSE_XML_RESPONSE("/LinuxCNC/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Fault@timestamp", "2021-02-01T12:00:00Z"); @@ -2403,7 +2403,7 @@ TEST_F(AgentTest, put_condition_should_parse_condition_data) TEST_F(AgentTest, shound_add_asset_count_when_20) { m_agentTestHelper->createAgent("/samples/min_config.xml", 8, 4, "2.0", 25); - + { PARSE_XML_RESPONSE("/LinuxCNC/probe"); ASSERT_XML_PATH_COUNT(doc, "//m:DataItem[@type='ASSET_CHANGED']", 1); @@ -2423,7 +2423,7 @@ TEST_F(AgentTest, pre_start_hook_should_be_called) }; m_agentTestHelper->setAgentCreateHook(helperHook); auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 4, true); - + ASSERT_FALSE(called); agent->start(); ASSERT_TRUE(called); @@ -2439,7 +2439,7 @@ TEST_F(AgentTest, pre_initialize_hooks_should_be_called) }; m_agentTestHelper->setAgentCreateHook(helperHook); m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 4, true); - + ASSERT_TRUE(called); } @@ -2452,7 +2452,7 @@ TEST_F(AgentTest, post_initialize_hooks_should_be_called) }; m_agentTestHelper->setAgentCreateHook(helperHook); m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 4, true); - + ASSERT_TRUE(called); } @@ -2465,7 +2465,7 @@ TEST_F(AgentTest, pre_stop_hook_should_be_called) }; m_agentTestHelper->setAgentCreateHook(helperHook); auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 4, true); - + ASSERT_FALSE(called); agent->start(); ASSERT_FALSE(called); @@ -2476,24 +2476,24 @@ TEST_F(AgentTest, pre_stop_hook_should_be_called) TEST_F(AgentTest, device_should_have_hash_for_2_2) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.2", 4, true); - + auto device = m_agentTestHelper->getAgent()->getDeviceByName("LinuxCNC"); ASSERT_TRUE(device); - + auto hash = device->get("hash"); ASSERT_EQ(28, hash.length()); - + { PARSE_XML_RESPONSE("/LinuxCNC/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Device@hash", hash.c_str()); } - + auto devices = m_agentTestHelper->getAgent()->getDevices(); auto di = devices.begin(); - + { PARSE_XML_RESPONSE("/Agent/sample"); - + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceAdded[2]@hash", (*di++)->get("hash").c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceAdded[3]@hash", (*di)->get("hash").c_str()); } @@ -2502,19 +2502,19 @@ TEST_F(AgentTest, device_should_have_hash_for_2_2) TEST_F(AgentTest, should_not_add_spaces_to_output) { addAdapter(); - + m_agentTestHelper->m_adapter->processData("2024-01-22T20:00:00Z|program|"); m_agentTestHelper->m_adapter->processData("2024-01-22T20:00:00Z|block|"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", ""); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block", ""); } - + m_agentTestHelper->m_adapter->processData( - "2024-01-22T20:00:00Z|program| |block| "); - + "2024-01-22T20:00:00Z|program| |block| "); + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", ""); @@ -2531,7 +2531,7 @@ TEST_F(AgentTest, should_set_sender_from_config_in_XML_header) PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@sender", "MachineXXX"); } - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@sender", "MachineXXX"); @@ -2547,12 +2547,12 @@ TEST_F(AgentTest, should_not_set_validation_flag_in_header_when_validation_is_fa PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); } - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); } - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); @@ -2568,12 +2568,12 @@ TEST_F(AgentTest, should_set_validation_flag_in_header_when_version_2_5_validati PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", "true"); } - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", "true"); } - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", "true"); @@ -2589,12 +2589,12 @@ TEST_F(AgentTest, should_not_set_validation_flag_in_header_when_version_below_2_ PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); } - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); } - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); @@ -2604,19 +2604,19 @@ TEST_F(AgentTest, should_not_set_validation_flag_in_header_when_version_below_2_ TEST_F(AgentTest, should_initialize_observaton_to_initial_value_when_available) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.2", 4, true); - + auto device = m_agentTestHelper->getAgent()->getDeviceByName("LinuxCNC"); ASSERT_TRUE(device); - + addAdapter(); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:PartCount", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2024-01-22T20:00:00Z|avail|AVAILABLE"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:PartCount", "0"); diff --git a/test_package/mqtt_entity_sink_test.cpp b/test_package/mqtt_entity_sink_test.cpp index 3a6a513b2..f5b781ab1 100644 --- a/test_package/mqtt_entity_sink_test.cpp +++ b/test_package/mqtt_entity_sink_test.cpp @@ -60,13 +60,13 @@ class MqttEntitySinkTest : public testing::Test std::shared_ptr m_client; std::unique_ptr m_jsonPrinter; uint16_t m_port {0}; - + void SetUp() override { m_agentTestHelper = std::make_unique(); m_jsonPrinter = std::make_unique(2, true); } - + void TearDown() override { stopAgent(); @@ -76,43 +76,43 @@ class MqttEntitySinkTest : public testing::Test m_agentTestHelper.reset(); m_jsonPrinter.reset(); } - + void createAgent(std::string testFile = {}, ConfigOptions options = {}) { if (testFile == "") testFile = "/samples/test_config.xml"; ConfigOptions opts(options); MergeOptions( - opts, - { - {"MqttEntitySink", true}, - {configuration::MqttPort, m_port}, - {MqttCurrentInterval, 200ms}, - {MqttSampleInterval, 100ms}, - {configuration::MqttHost, "127.0.0.1"s}, - {configuration::ObservationTopicPrefix, "MTConnect/Devices/[device]/Observations"s}, - {configuration::DeviceTopicPrefix, "MTConnect/Probe/[device]"s}, - {configuration::AssetTopicPrefix, "MTConnect/Asset/[device]"s}, - {configuration::MqttLastWillTopic, "MTConnect/Probe/[device]/Availability"s}, - }); + opts, + { + {"MqttEntitySink", true}, + {configuration::MqttPort, m_port}, + {MqttCurrentInterval, 200ms}, + {MqttSampleInterval, 100ms}, + {configuration::MqttHost, "127.0.0.1"s}, + {configuration::ObservationTopicPrefix, "MTConnect/Devices/[device]/Observations"s}, + {configuration::DeviceTopicPrefix, "MTConnect/Probe/[device]"s}, + {configuration::AssetTopicPrefix, "MTConnect/Asset/[device]"s}, + {configuration::MqttLastWillTopic, "MTConnect/Probe/[device]/Availability"s}, + }); m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 25, false, true, opts); addAdapter(); m_agentTestHelper->getAgent()->start(); } - + void createServer(const ConfigOptions& options) { using namespace mtconnect::configuration; ConfigOptions opts(options); MergeOptions(opts, {{ServerIp, "127.0.0.1"s}, - {MqttPort, 0}, - {MqttTls, false}, - {AutoAvailable, false}, - {RealTime, false}}); + {MqttPort, 0}, + {MqttTls, false}, + {AutoAvailable, false}, + {RealTime, false}}); m_server = std::make_shared( - m_agentTestHelper->m_ioContext, opts); + m_agentTestHelper->m_ioContext, opts); } - + template bool waitFor(const chrono::duration& time, function pred) { @@ -125,12 +125,12 @@ class MqttEntitySinkTest : public testing::Test }); while (!timeout && !pred()) { - m_agentTestHelper->m_ioContext.run_for(100ms); + m_agentTestHelper->m_ioContext.run_for(200ms); } timer.cancel(); return pred(); } - + void startServer() { if (m_server) @@ -143,19 +143,19 @@ class MqttEntitySinkTest : public testing::Test } } } - + void createClient(const ConfigOptions& options, unique_ptr&& handler) { ConfigOptions opts(options); MergeOptions(opts, {{MqttHost, "127.0.0.1"s}, - {MqttPort, m_port}, - {MqttTls, false}, - {AutoAvailable, false}, - {RealTime, false}}); + {MqttPort, m_port}, + {MqttTls, false}, + {AutoAvailable, false}, + {RealTime, false}}); m_client = make_shared(m_agentTestHelper->m_ioContext, opts, std::move(handler)); } - + bool startClient() { bool started = m_client && m_client->start(); @@ -165,13 +165,13 @@ class MqttEntitySinkTest : public testing::Test } return started; } - + void addAdapter(ConfigOptions options = ConfigOptions {}) { m_agentTestHelper->addAdapter(options, "localhost", 0, m_agentTestHelper->m_agent->getDefaultDevice()->getName()); } - + void stopAgent() { const auto agent = m_agentTestHelper->getAgent(); @@ -181,7 +181,7 @@ class MqttEntitySinkTest : public testing::Test m_agentTestHelper->m_ioContext.run_for(100ms); } } - + void stopClient() { if (m_client) @@ -191,7 +191,7 @@ class MqttEntitySinkTest : public testing::Test m_client.reset(); } } - + void stopServer() { if (m_server) @@ -207,77 +207,85 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_use_flat_topic_structure) { bool gotMessage = false; std::string receivedTopic; - + createServer({}); startServer(); - + auto client = mqtt::make_async_client(m_agentTestHelper->m_ioContext.get(), "localhost", m_port); - + client->set_client_id("test_client"); client->set_clean_session(true); client->set_keep_alive_sec(30); - + bool subscribed = false; client->set_connack_handler( - [client, &subscribed](bool sp, mqtt::connect_return_code connack_return_code) { - if (connack_return_code == mqtt::connect_return_code::accepted) - { - auto pid = client->acquire_unique_packet_id(); - client->async_subscribe(pid, "MTConnect/#", MQTT_NS::qos::at_least_once, - [](MQTT_NS::error_code ec) { EXPECT_FALSE(ec); }); - } - return true; - }); - + [client](bool sp, mqtt::connect_return_code connack_return_code) { + if (connack_return_code == mqtt::connect_return_code::accepted) + { + auto pid = client->acquire_unique_packet_id(); + client->async_subscribe(pid, "MTConnect/#", MQTT_NS::qos::at_least_once, + [](MQTT_NS::error_code ec) { EXPECT_FALSE(ec); }); + } + return true; + }); + client->set_suback_handler( - [&subscribed](std::uint16_t packet_id, std::vector results) { - subscribed = true; - return true; - }); - - client->set_publish_handler([&gotMessage, &receivedTopic](mqtt::optional packet_id, - mqtt::publish_options pubopts, - mqtt::buffer topic_name, - mqtt::buffer contents) { - receivedTopic = topic_name; - std::cout << "Received topic: " << topic_name << " payload: " << contents << std::endl; - if (topic_name.find("MTConnect/Devices/") == 0 && - topic_name.find("/Observations/") != std::string::npos) - { - gotMessage = true; - } - return true; - }); - + [&subscribed](std::uint16_t packet_id, std::vector results) { + subscribed = true; + return true; + }); + + size_t messageCount = 0; + client->set_publish_handler( + [&messageCount, &gotMessage, &receivedTopic](mqtt::optional packet_id, + mqtt::publish_options pubopts, + mqtt::buffer topic_name, mqtt::buffer contents) { + receivedTopic = topic_name; + std::cout << "Received topic: " << topic_name << " payload: " << contents << std::endl; + if (topic_name.find("MTConnect/Devices/") == 0 && + topic_name.find("/Observations/") != std::string::npos) + { + messageCount += 1; + gotMessage = true; + } + return true; + }); + client->async_connect([](mqtt::error_code ec) { ASSERT_FALSE(ec) << "Cannot connect"; }); - + // Wait for subscription to complete ASSERT_TRUE(waitFor(10s, [&subscribed]() { return subscribed; })) - << "Subscription never completed"; - + << "Subscription never completed"; + // Create the agent and wait for its MQTT sink to connect. - createAgent(); + createAgent("", {{configuration::DisableAgentDevice, true}}); auto sink = m_agentTestHelper->getMqttEntitySink(); ASSERT_TRUE(sink != nullptr); ASSERT_TRUE(waitFor(10s, [&sink]() { return sink->isConnected(); })) - << "MqttEntitySink failed to connect to broker"; - + << "MqttEntitySink failed to connect to broker"; + + auto device = m_agentTestHelper->m_agent->getDefaultDevice(); + size_t diCount = device->getDeviceDataItems().size(); + + // Add deterministic check to make sure we have received all the initial messages + ASSERT_TRUE(waitFor(5s, [&messageCount, &diCount]() { return messageCount >= diCount; })); + gotMessage = false; receivedTopic.clear(); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); ASSERT_TRUE(waitFor(10s, [&gotMessage]() { return gotMessage; })) - << "Timeout waiting for adapter data. Last topic: " << receivedTopic; - + << "Timeout waiting for adapter data. Last topic: " << receivedTopic; + EXPECT_TRUE(receivedTopic.find("MTConnect/Devices/") == 0) - << "Topic doesn't start with MTConnect/Devices/: " << receivedTopic; + << "Topic doesn't start with MTConnect/Devices/: " << receivedTopic; EXPECT_TRUE(receivedTopic.find("/Observations/") != std::string::npos) - << "Topic doesn't contain /Observations/: " << receivedTopic; + << "Topic doesn't contain /Observations/: " << receivedTopic; EXPECT_EQ("MTConnect/Devices/000/Observations/p3", receivedTopic) - << "Topic does not match expected format: " << receivedTopic; - + << "Topic does not match expected format: " << receivedTopic; + client->async_disconnect(); - + stopAgent(); stopClient(); } @@ -285,14 +293,14 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_use_flat_topic_structure) TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_entity_json_format) { ConfigOptions options; - + createServer({}); startServer(); - + auto handler = make_unique(); bool gotMessage = false; json receivedJson; - + handler->m_receive = [&gotMessage, &receivedJson](std::shared_ptr client, const std::string& topic, const std::string& payload) { @@ -310,24 +318,24 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_entity_json_format) LOG(error) << "Failed to parse JSON: " << e.what(); } }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Devices/#"); // Ensure subscription is active before sending data m_agentTestHelper->m_ioContext.run_for(200ms); - + createAgent(); - + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); - + // Wait a bit to ensure sink and client are ready m_agentTestHelper->m_ioContext.run_for(200ms); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); ASSERT_TRUE(waitFor(10s, [&gotMessage]() { return gotMessage; })); - + // Verify Entity JSON format EXPECT_TRUE(receivedJson.contains("dataItemId")); EXPECT_TRUE(receivedJson.contains("timestamp")); @@ -335,23 +343,23 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_entity_json_format) EXPECT_TRUE(receivedJson.contains("sequence")); EXPECT_TRUE(receivedJson.contains("type")); EXPECT_TRUE(receivedJson.contains("category")); - + EXPECT_EQ("204", receivedJson["result"].get()); - + stopClient(); } TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_include_optional_fields) { ConfigOptions options; - + createServer({}); startServer(); - + auto handler = make_unique(); bool gotMessage = false; json receivedJson; - + handler->m_receive = [&gotMessage, &receivedJson](std::shared_ptr client, const std::string& topic, const std::string& payload) { @@ -362,20 +370,20 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_include_optional_fields) } receivedJson = js; }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Devices/#"); - + createAgent(); - + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); ASSERT_TRUE(waitFor(10s, [&gotMessage]() { return gotMessage; })); - + // Verify optional fields if (receivedJson.contains("name")) { @@ -385,21 +393,21 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_include_optional_fields) { EXPECT_TRUE(receivedJson["subType"].is_string()); } - + stopClient(); } TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_samples) { ConfigOptions options; - + createServer({}); startServer(); - + auto handler = make_unique(); bool gotSample = false; json receivedJson; - + handler->m_receive = [&gotSample, &receivedJson](std::shared_ptr client, const std::string& topic, const std::string& payload) { @@ -418,40 +426,40 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_samples) LOG(error) << "Failed to parse JSON: " << e.what(); } }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Devices/#"); m_agentTestHelper->m_ioContext.run_for(200ms); - + createAgent(); - + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = dynamic_pointer_cast(sink); - + ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); - + m_agentTestHelper->m_ioContext.run_for(200ms); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|z2|204"); ASSERT_TRUE(waitFor(10s, [&gotSample]() { return gotSample; })); - + EXPECT_EQ("SAMPLE", receivedJson["category"].get()); EXPECT_EQ("204.000000", receivedJson["result"].get()); - + stopClient(); } TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_events) { ConfigOptions options; - + createServer({}); startServer(); - + auto handler = make_unique(); bool gotEvent = false; json receivedJson; - + handler->m_receive = [&gotEvent, &receivedJson](std::shared_ptr client, const std::string& topic, const std::string& payload) { @@ -470,39 +478,39 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_events) LOG(error) << "Failed to parse JSON: " << e.what(); } }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Devices/#"); m_agentTestHelper->m_ioContext.run_for(200ms); - + createAgent(); - + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); - + m_agentTestHelper->m_ioContext.run_for(200ms); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|p4|READY"); ASSERT_TRUE(waitFor(10s, [&gotEvent]() { return gotEvent; })); - + EXPECT_EQ("EVENT", receivedJson["category"].get()); EXPECT_EQ("READY", receivedJson["result"].get()); - + stopClient(); } TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_conditions) { ConfigOptions options; - + createServer({}); startServer(); - + auto handler = make_unique(); bool gotCondition = false; json receivedJson; - + handler->m_receive = [&gotCondition, &receivedJson](std::shared_ptr client, const std::string& topic, const std::string& payload) { @@ -521,44 +529,44 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_conditions) LOG(error) << "Failed to parse JSON: " << e.what(); } }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Devices/#"); m_agentTestHelper->m_ioContext.run_for(200ms); - + createAgent(); - + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); - + m_agentTestHelper->m_ioContext.run_for(200ms); m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|zlc|FAULT|1234|LOW|Hydraulic pressure low"); + "2021-02-01T12:00:00Z|zlc|FAULT|1234|LOW|Hydraulic pressure low"); ASSERT_TRUE(waitFor(10s, [&gotCondition]() { return gotCondition; })); - + EXPECT_EQ("CONDITION", receivedJson["category"].get()); EXPECT_EQ("FAULT", receivedJson["level"].get()); if (receivedJson.contains("nativeCode")) { EXPECT_EQ("1234", receivedJson["nativeCode"].get()); } - + stopClient(); } TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_availability) { ConfigOptions options; - + createServer({}); startServer(); - + auto handler = make_unique(); bool gotAvailable = false; std::string availabilityValue; - + handler->m_receive = [&gotAvailable, &availabilityValue](std::shared_ptr client, const std::string& topic, const std::string& payload) { @@ -568,33 +576,33 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_availability) gotAvailable = true; } }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Probe/#"); - + createAgent(); - + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); - + ASSERT_TRUE(waitFor(5s, [&gotAvailable]() { return gotAvailable; })); EXPECT_EQ("AVAILABLE", availabilityValue); - + stopClient(); } TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_initial_observations) { ConfigOptions options; - + createServer({}); startServer(); - + auto handler = make_unique(); int messageCount = 0; - + handler->m_receive = [&messageCount](std::shared_ptr client, const std::string& topic, const std::string& payload) { if (topic.find("/Observations/") != std::string::npos) @@ -602,37 +610,36 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_initial_observations) messageCount++; } }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Devices/#"); - + createAgent(); - + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); ASSERT_TRUE(waitFor(10s, [&messageCount]() { return messageCount > 0; })); EXPECT_GT(messageCount, 0); - + stopClient(); } TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_handle_unavailable) { ConfigOptions options; - + createServer({}); startServer(); - + auto handler = make_unique(); bool gotUnavailable = false; json receivedJson; - + handler->m_receive = [&gotUnavailable, &receivedJson](std::shared_ptr client, const std::string& topic, const std::string& payload) { - json j = json::parse(payload); if (j["category"] != "CONDITION" && topic.starts_with("MTConnect/Devices/000/Observations/")) { @@ -643,24 +650,24 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_handle_unavailable) receivedJson = j; } }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Devices/#"); - + createAgent(); - + auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink->isConnected(); })); - + // Initial observations should include UNAVAILABLE values // The issue is the initial values will be received in a random order and the conditions have a // level instead of a result. If in the interviening time a condition is received, the json may // not be correct. ASSERT_TRUE(waitFor(10s, [&gotUnavailable]() { return gotUnavailable; })); EXPECT_EQ("UNAVAILABLE", receivedJson["result"].get()); - + stopClient(); } @@ -670,23 +677,23 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_support_authentication) options["MqttUserName"] = "mtconnect"; options["MqttPassword"] = "password123"; options["MqttClientId"] = "auth-client"; - + createServer({}); startServer(); - + bool connected = false; auto handler = std::make_unique(); handler->m_connected = [&connected](std::shared_ptr) { connected = true; }; - + createClient(options, std::move(handler)); startClient(); - + auto start = std::chrono::steady_clock::now(); while (!connected && std::chrono::steady_clock::now() - start < std::chrono::seconds(5)) { std::this_thread::sleep_for(std::chrono::milliseconds(50)); } - + ASSERT_TRUE(connected) << "MQTT client did not connect with authentication"; } @@ -695,25 +702,27 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_support_qos_levels) ConfigOptions options; options["MqttQOS"] = "exactly_once"; options["MqttClientId"] = "qos-client"; - + createServer({}); startServer(); - + auto handler = std::make_unique(); bool received = false; handler->m_receive = [&received](std::shared_ptr client, const std::string& topic, const std::string& payload) { received = true; }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Devices/#"); - + createAgent(); auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = std::dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink && mqttSink->isConnected(); })); - + ASSERT_TRUE(waitFor(5s, [&received]() { return received; })); + + stopClient(); } TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_support_retained_messages) @@ -721,10 +730,10 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_support_retained_messages) ConfigOptions options; options["MqttRetain"] = "true"; options["MqttClientId"] = "retain-client"; - + createServer({}); startServer(); - + auto handler = std::make_unique(); bool retainedReceived = false; std::string retainedPayload; @@ -734,19 +743,19 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_support_retained_messages) retainedReceived = true; retainedPayload = payload; }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Devices/#"); - + createAgent(); auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = std::dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink && mqttSink->isConnected(); })); - + ASSERT_TRUE(waitFor(5s, [&retainedReceived]() { return retainedReceived; })); ASSERT_FALSE(retainedPayload.empty()); - + stopClient(); } @@ -755,10 +764,10 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_last_will) ConfigOptions options; options["MqttLastWillTopic"] = "MTConnect/Probe/J55-411045-cpp/Availability"; options["MqttClientId"] = "lastwill-client"; - + createServer({}); startServer(); - + auto handler = std::make_unique(); bool lastWillReceived = false; handler->m_receive = [&lastWillReceived](std::shared_ptr client, @@ -768,18 +777,18 @@ TEST_F(MqttEntitySinkTest, mqtt_entity_sink_should_publish_last_will) lastWillReceived = true; } }; - + createClient(options, std::move(handler)); ASSERT_TRUE(startClient()); m_client->subscribe("MTConnect/Probe/#"); - + createAgent(); auto sink = m_agentTestHelper->getAgent()->findSink("MqttEntitySink"); auto mqttSink = std::dynamic_pointer_cast(sink); ASSERT_TRUE(waitFor(10s, [&mqttSink]() { return mqttSink && mqttSink->isConnected(); })); - + m_client->stop(); ASSERT_TRUE(waitFor(5s, [&lastWillReceived]() { return lastWillReceived; })); - + stopClient(); } diff --git a/test_package/observation_validation_test.cpp b/test_package/observation_validation_test.cpp index 9799e9cf9..14b80eeba 100644 --- a/test_package/observation_validation_test.cpp +++ b/test_package/observation_validation_test.cpp @@ -288,36 +288,36 @@ TEST_F(ObservationValidationTest, should_validate_sample) { auto contract = static_cast(m_context->m_contract.get()); contract->m_schemaVersion = SCHEMA_VERSION(2, 5); - + shared_ptr mapper; mapper = make_shared(m_context, "", 2); mapper->bind(m_validator); - + ErrorList errors; m_dataItem = DataItem::make( - {{"id", "pos"s}, {"category", "SAMPLE"s}, {"type", "POSITION"s}, {"units", "MILLIMETER"s}}, - errors); - + {{"id", "pos"s}, {"category", "SAMPLE"s}, {"type", "POSITION"s}, {"units", "MILLIMETER"s}}, + errors); + auto ts = make_shared(); ts->m_tokens = {{"pos"s, "1.234"s}}; ts->m_timestamp = chrono::system_clock::now(); ts->setProperty("timestamp", ts->m_timestamp); - + auto observations = (*mapper)(ts); auto &r = *observations; ASSERT_EQ(typeid(Observations), typeid(r)); - + auto oblist = observations->getValue(); ASSERT_EQ(1, oblist.size()); - + auto oi = oblist.begin(); - + { auto sample = dynamic_pointer_cast(*oi++); ASSERT_TRUE(sample); ASSERT_EQ(m_dataItem, sample->getDataItem()); ASSERT_FALSE(sample->isUnavailable()); - + ASSERT_EQ("VALID", sample->get("quality")); } } @@ -326,17 +326,16 @@ TEST_F(ObservationValidationTest, should_validate_sample_with_int64_value) { ErrorList errors; m_dataItem = DataItem::make( - {{"id", "pos"s}, {"category", "SAMPLE"s}, {"type", "POSITION"s}, {"units", "MILLIMETER"s}}, - errors); - + {{"id", "pos"s}, {"category", "SAMPLE"s}, {"type", "POSITION"s}, {"units", "MILLIMETER"s}}, + errors); + auto obs = Observation::make(m_dataItem, {{"VALUE", int64_t(100)}}, m_time, errors); - + auto evt = (*m_validator)(std::move(obs)); auto quality = evt->get("quality"); ASSERT_EQ("VALID", quality); } - TEST_F(ObservationValidationTest, should_not_validate_if_validation_is_off) { auto contract = static_cast(m_context->m_contract.get()); @@ -428,7 +427,7 @@ TEST_F(ObservationValidationTest, should_validate_sample_double_value) { ErrorList errors; auto event = Observation::make(m_dataItem, {{"VALUE", "READY"s}}, m_time, errors); - + auto evt = (*m_validator)(std::move(event)); auto quality = evt->get("quality"); ASSERT_EQ("VALID", quality);