From 907c5285122a2aa14dfffc792e5e6c3f5863cf2c Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Sun, 8 Jun 2025 22:58:41 -0400 Subject: [PATCH 1/2] Fixed data set difference and added test cases --- src/mtconnect/entity/data_set.hpp | 21 ++-- test_package/table_test.cpp | 169 +++++++++++++++++++++++++++++- test_package/test_utilities.hpp | 62 ++++++++--- 3 files changed, 220 insertions(+), 32 deletions(-) diff --git a/src/mtconnect/entity/data_set.hpp b/src/mtconnect/entity/data_set.hpp index 923ea9e10..db343a68d 100644 --- a/src/mtconnect/entity/data_set.hpp +++ b/src/mtconnect/entity/data_set.hpp @@ -99,16 +99,7 @@ namespace mtconnect::entity { /// @brief the other value to compare const TableCellValue &m_other; }; - - /// @brief Compares to cells - /// @param other the other cell - bool same(const TableCell &other) const - { - const auto &ov = other.m_value; - return m_key == other.m_key && m_removed == other.m_removed && - std::visit(SameValue(ov), m_value); - } - + /// @brief compares two cell values /// @param other the other cell value to compare bool sameValue(const TableCell &other) const @@ -117,6 +108,14 @@ namespace mtconnect::entity { return std::visit(SameValue(ov), m_value); } + /// @brief Compares to cells + /// @param other the other cell + bool same(const TableCell &other) const + { + return m_key == other.m_key && m_removed == other.m_removed && + sameValue(other); + } + std::string m_key; TableCellValue m_value; bool m_removed {false}; @@ -280,7 +279,7 @@ namespace mtconnect::entity { for (const auto &e1 : v) { const auto &e2 = oset.find(e1); - if (e2 == oset.end() || e2->sameValue(e1)) + if (e2 == oset.end() || !e2->sameValue(e1)) return false; } diff --git a/test_package/table_test.cpp b/test_package/table_test.cpp index abccd63b1..f424a12e7 100644 --- a/test_package/table_test.cpp +++ b/test_package/table_test.cpp @@ -56,7 +56,7 @@ class TableTest : public testing::Test void SetUp() override { // Create an agent with only 16 slots and 8 data items. m_agentTestHelper = make_unique(); - m_agentTestHelper->createAgent("/samples/data_set.xml", 8, 4, "1.6", 25); + m_agentTestHelper->createAgent("/samples/data_set.xml", 8, 4, "2.0", 25); m_agentId = to_string(getCurrentTimeInSec()); m_checkpoint = nullptr; @@ -323,7 +323,7 @@ TEST_F(TableTest, JsonCurrent) "U=10.0}"); { - PARSE_JSON_RESPONSE("/current"); + PARSE_JSON_RESPONSE("/LinuxCNC/current"); auto streams = doc.at("/MTConnectStreams/Streams/0/DeviceStream/ComponentStreams"_json_pointer); ASSERT_EQ(4_S, streams.size()); @@ -379,7 +379,7 @@ TEST_F(TableTest, JsonCurrentText) "G53.3={X=7.0 Y=8.0 Z=9 U=10.0}"); { - PARSE_JSON_RESPONSE("/current"); + PARSE_JSON_RESPONSE("/LinuxCNC/current"); auto streams = doc.at("/MTConnectStreams/Streams/0/DeviceStream/ComponentStreams"_json_pointer); ASSERT_EQ(4_S, streams.size()); @@ -501,7 +501,7 @@ TEST_F(TableTest, JsonDefinitionTest) m_agentTestHelper->addAdapter(); { - PARSE_JSON_RESPONSE("/probe"); + PARSE_JSON_RESPONSE("/LinuxCNC/probe"); auto devices = doc.at("/MTConnectDevices/Devices"_json_pointer); auto device = devices.at(0).at("/Device"_json_pointer); @@ -778,3 +778,164 @@ TEST_F(TableTest, shoud_parse_table_with_no_space) nullptr); } } + +TEST_F(TableTest, shoud_handle_complex_sequences) +{ + m_agentTestHelper->addAdapter(); + + { + PARSE_XML_RESPONSE("/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:WorkOffsetTable[@dataItemId='wp1']", + "UNAVAILABLE"); + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:WorkOffsetTable[@dataItemId='wp1']@count", "0"); + } + + QueryMap query {{"path", "//DataItem[@id='wp1']"}}; + + m_agentTestHelper->m_adapter->processData( + "2021-02-01T12:00:00Z|wp1|A={X=1.0 Y=2.0 Z=3.0} B={X=4.0 Y=5.0 Z=6.0} C={X=7.0 Y=8.0 Z=9} " + "D={title=\"Testing\"}"); + + m_agentTestHelper->m_adapter->processData( + "2021-02-01T12:00:00Z|wp1|A= B C={X=107.0 Y=108.0 Z=109.0}"); + + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|wp1|A={X=101.0 Y=102.0 Z=103.0}"); + + ValueResponse s1, s2, s3; + + { + PARSE_XML_RESPONSE_QUERY("/sample", query); + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:WorkOffsetTable[1]", "UNAVAILABLE"); + + s1 = XML_PATH_VALUE(doc, "//m:DeviceStream//m:WorkOffsetTable[2]@sequence"); + + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[2]/m:Entry[@key='A']/m:Cell[@key='X']", "1"); + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[2]/m:Entry[@key='A']/m:Cell[@key='Y']", "2"); + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[2]/m:Entry[@key='A']/m:Cell[@key='Z']", "3"); + + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[2]/m:Entry[@key='B']/m:Cell[@key='X']", "4"); + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[2]/m:Entry[@key='B']/m:Cell[@key='Y']", "5"); + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[2]/m:Entry[@key='B']/m:Cell[@key='Z']", "6"); + + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[2]/m:Entry[@key='C']/m:Cell[@key='X']", "7"); + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[2]/m:Entry[@key='C']/m:Cell[@key='Y']", "8"); + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[2]/m:Entry[@key='C']/m:Cell[@key='Z']", "9"); + + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[2]/m:Entry[@key='D']/m:Cell[@key='title']", + "Testing"); + + s2 = XML_PATH_VALUE(doc, "//m:DeviceStream//m:WorkOffsetTable[3]@sequence"); + + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:WorkOffsetTable[3]/m:Entry[@key='A']@removed", + "true"); + + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:WorkOffsetTable[3]/m:Entry[@key='B']@removed", + "true"); + + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[3]/m:Entry[@key='C']/m:Cell[@key='X']", "107"); + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[3]/m:Entry[@key='C']/m:Cell[@key='Y']", "108"); + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[3]/m:Entry[@key='C']/m:Cell[@key='Z']", "109"); + + s3 = XML_PATH_VALUE(doc, "//m:DeviceStream//m:WorkOffsetTable[4]@sequence"); + + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[4]/m:Entry[@key='A']/m:Cell[@key='X']", "101"); + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[4]/m:Entry[@key='A']/m:Cell[@key='Y']", "102"); + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[4]/m:Entry[@key='A']/m:Cell[@key='Z']", "103"); + } + + ASSERT_TRUE(s1.first); + ASSERT_TRUE(s2.first); + ASSERT_TRUE(s3.first); + + query.clear(); + query["at"] = *s1.first; + + { + PARSE_XML_RESPONSE_QUERY("/current", query); + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[1]/m:Entry[@key='A']/m:Cell[@key='X']", "1"); + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[1]/m:Entry[@key='A']/m:Cell[@key='Y']", "2"); + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[1]/m:Entry[@key='A']/m:Cell[@key='Z']", "3"); + + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[1]/m:Entry[@key='B']/m:Cell[@key='X']", "4"); + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[1]/m:Entry[@key='B']/m:Cell[@key='Y']", "5"); + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[1]/m:Entry[@key='B']/m:Cell[@key='Z']", "6"); + + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[1]/m:Entry[@key='C']/m:Cell[@key='X']", "7"); + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[1]/m:Entry[@key='C']/m:Cell[@key='Y']", "8"); + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[1]/m:Entry[@key='C']/m:Cell[@key='Z']", "9"); + + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[1]/m:Entry[@key='D']/m:Cell[@key='title']", + "Testing"); + } + + query["at"] = *s2.first; + + { + PARSE_XML_RESPONSE_QUERY("/current", query); + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:WorkOffsetTable[1]/m:Entry[@key='A']", nullptr); + + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:WorkOffsetTable[1]/m:Entry[@key='B']", nullptr); + + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[1]/m:Entry[@key='C']/m:Cell[@key='X']", "107"); + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[1]/m:Entry[@key='C']/m:Cell[@key='Y']", "108"); + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[1]/m:Entry[@key='C']/m:Cell[@key='Z']", "109"); + + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[1]/m:Entry[@key='D']/m:Cell[@key='title']", + "Testing"); + } + + query["at"] = *s3.first; + + { + PARSE_XML_RESPONSE_QUERY("/current", query); + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[1]/m:Entry[@key='A']/m:Cell[@key='X']", "101"); + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[1]/m:Entry[@key='A']/m:Cell[@key='Y']", "102"); + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[1]/m:Entry[@key='A']/m:Cell[@key='Z']", "103"); + + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:WorkOffsetTable[1]/m:Entry[@key='B']", nullptr); + + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[1]/m:Entry[@key='C']/m:Cell[@key='X']", "107"); + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[1]/m:Entry[@key='C']/m:Cell[@key='Y']", "108"); + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[1]/m:Entry[@key='C']/m:Cell[@key='Z']", "109"); + + ASSERT_XML_PATH_EQUAL( + doc, "//m:DeviceStream//m:WorkOffsetTable[1]/m:Entry[@key='D']/m:Cell[@key='title']", + "Testing"); + } +} diff --git a/test_package/test_utilities.hpp b/test_package/test_utilities.hpp index df1b0699e..1c4a58d9c 100644 --- a/test_package/test_utilities.hpp +++ b/test_package/test_utilities.hpp @@ -100,6 +100,8 @@ inline void fillAttribute(std::string &xmlString, const std::string &attribute, #define ASSERT_XML_PATH_EQUAL(doc, path, expected) \ xpathTest(doc, path, expected, __FILE__, __LINE__) +#define XML_PATH_VALUE(doc, path) xpathValue(doc, path, __FILE__, __LINE__) + #define PARSE_XML(expr) \ string result = expr; \ auto doc = xmlParseMemory(result.c_str(), static_cast(result.length())); \ @@ -127,8 +129,9 @@ inline void assertIf(bool condition, const std::string &message, const std::stri ASSERT_TRUE(condition) << file << "(" << line << "): Failed " << message; } -inline void xpathTest(xmlDocPtr doc, const char *xpath, const char *expected, - const std::string &file, int line) +using ValueResponse = std::pair, std::optional>; + +inline ValueResponse xpathValue(xmlDocPtr doc, const char *xpath, const std::string &file, int line, bool noValue = false) { using namespace std; @@ -178,19 +181,20 @@ inline void xpathTest(xmlDocPtr doc, const char *xpath, const char *expected, << ((const char *)memory); xmlFree(memory); - FAIL() << message.str(); - if (obj) xmlXPathFreeObject(obj); - xmlXPathFreeContext(xpathCtx); - return; + + if (noValue) + return ValueResponse(std::nullopt, std::nullopt); + else + return ValueResponse(std::nullopt, message.str()); } // Special case when no children are expected xmlNodePtr first = obj->nodesetval->nodeTab[0]; - if (expected == nullptr) + if (noValue) { bool has_content = false; stringstream message; @@ -228,8 +232,10 @@ inline void xpathTest(xmlDocPtr doc, const char *xpath, const char *expected, xmlXPathFreeObject(obj); xmlXPathFreeContext(xpathCtx); - failIf(has_content, message.str(), file, line); - return; + if (has_content) + return ValueResponse(std::nullopt, message.str()); + else + return ValueResponse(std::nullopt, std::nullopt); } string actual; @@ -275,16 +281,38 @@ inline void xpathTest(xmlDocPtr doc, const char *xpath, const char *expected, xmlXPathFreeContext(xpathCtx); actual = mtconnect::trim(actual); - string message = (string) "Incorrect value for path " + xpath; - if (expected[0] != '!') - { - failNotEqualIf(actual != expected, expected, actual, message, file, line); - } - else + return ValueResponse(actual, std::nullopt); +} + +inline void xpathTest(xmlDocPtr doc, const char *xpath, const char *expected, + const std::string &file, int line) +{ + using namespace std; + + auto res = xpathValue(doc, xpath, file, line, expected == nullptr); + if (res.second) + ADD_FAILURE_AT(file.c_str(), line) << *res.second; + else if (expected != nullptr) { - expected += 1; - failNotEqualIf(actual == expected, expected, actual, message, file, line); + if (res.first) + { + string message = (string) "Incorrect value for path " + xpath; + auto actual = *res.first; + if (expected[0] != '!') + { + failNotEqualIf(actual != expected, expected, actual, message, file, line); + } + else + { + expected += 1; + failNotEqualIf(actual == expected, expected, actual, message, file, line); + } + } + else + { + ADD_FAILURE_AT(file.c_str(), line) << "No value for " << xpath; + } } } From 3f0abf6da0766a19ba4ce618fce85fc43d9f727e Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Sun, 8 Jun 2025 23:00:14 -0400 Subject: [PATCH 2/2] Formatted --- src/mtconnect/entity/data_set.hpp | 7 +++---- test_package/test_utilities.hpp | 3 ++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/mtconnect/entity/data_set.hpp b/src/mtconnect/entity/data_set.hpp index db343a68d..abeb5f397 100644 --- a/src/mtconnect/entity/data_set.hpp +++ b/src/mtconnect/entity/data_set.hpp @@ -99,7 +99,7 @@ namespace mtconnect::entity { /// @brief the other value to compare const TableCellValue &m_other; }; - + /// @brief compares two cell values /// @param other the other cell value to compare bool sameValue(const TableCell &other) const @@ -112,8 +112,7 @@ namespace mtconnect::entity { /// @param other the other cell bool same(const TableCell &other) const { - return m_key == other.m_key && m_removed == other.m_removed && - sameValue(other); + return m_key == other.m_key && m_removed == other.m_removed && sameValue(other); } std::string m_key; @@ -296,7 +295,7 @@ namespace mtconnect::entity { return std::holds_alternative(m_other) && std::get(m_other) == v; } - const DataSetValue &m_other; //! the other data set value + const DataSetValue &m_other; //! the other data set value }; inline bool DataSetEntry::same(const DataSetEntry &other) const diff --git a/test_package/test_utilities.hpp b/test_package/test_utilities.hpp index 1c4a58d9c..96491b907 100644 --- a/test_package/test_utilities.hpp +++ b/test_package/test_utilities.hpp @@ -131,7 +131,8 @@ inline void assertIf(bool condition, const std::string &message, const std::stri using ValueResponse = std::pair, std::optional>; -inline ValueResponse xpathValue(xmlDocPtr doc, const char *xpath, const std::string &file, int line, bool noValue = false) +inline ValueResponse xpathValue(xmlDocPtr doc, const char *xpath, const std::string &file, int line, + bool noValue = false) { using namespace std;