diff --git a/orchagent/high_frequency_telemetry/counternameupdater.cpp b/orchagent/high_frequency_telemetry/counternameupdater.cpp index acb82f90..3a9e12f0 100644 --- a/orchagent/high_frequency_telemetry/counternameupdater.cpp +++ b/orchagent/high_frequency_telemetry/counternameupdater.cpp @@ -21,15 +21,11 @@ void CounterNameMapUpdater::setCounterNameMap(const std::string &counter_name, s if (gHFTOrch) { - std::string unified_counter_name = unify_counter_name(counter_name); - Message msg{ - .m_table_name = m_table_name.c_str(), - .m_operation = OPERATION::SET, - .m_set{ - .m_counter_name = unified_counter_name.c_str(), - .m_oid = oid, - }, - }; + Message msg; + msg.m_table_name = m_table_name; + msg.m_operation = OPERATION::SET; + msg.m_counter_name = unify_counter_name(counter_name); + msg.m_oid = oid; gHFTOrch->locallyNotify(msg); } @@ -58,14 +54,10 @@ void CounterNameMapUpdater::delCounterNameMap(const std::string &counter_name) if (gHFTOrch) { - std::string unified_counter_name = unify_counter_name(counter_name); - Message msg{ - .m_table_name = m_table_name.c_str(), - .m_operation = OPERATION::DEL, - .m_del{ - .m_counter_name = unified_counter_name.c_str(), - }, - }; + Message msg; + msg.m_table_name = m_table_name; + msg.m_operation = OPERATION::DEL; + msg.m_counter_name = unify_counter_name(counter_name); gHFTOrch->locallyNotify(msg); } diff --git a/orchagent/high_frequency_telemetry/counternameupdater.h b/orchagent/high_frequency_telemetry/counternameupdater.h index 820d0edc..6e9c6442 100644 --- a/orchagent/high_frequency_telemetry/counternameupdater.h +++ b/orchagent/high_frequency_telemetry/counternameupdater.h @@ -18,26 +18,14 @@ class CounterNameMapUpdater DEL, }; - struct SetPayload - { - const char* m_counter_name; - sai_object_id_t m_oid; - }; - - struct DelPayload - { - const char* m_counter_name; - }; - struct Message { - const char* m_table_name; + std::string m_table_name; OPERATION m_operation; - union - { - SetPayload m_set; - DelPayload m_del; - }; + // Use a string to own the counter name, avoiding dangling pointers + // when the caller's local string goes out of scope. + std::string m_counter_name; + sai_object_id_t m_oid = SAI_NULL_OBJECT_ID; // Only valid for SET operation }; CounterNameMapUpdater(const std::string &db_name, const std::string &table_name); diff --git a/orchagent/high_frequency_telemetry/hftelgroup.cpp b/orchagent/high_frequency_telemetry/hftelgroup.cpp index ff8bbaa8..9a7011cf 100644 --- a/orchagent/high_frequency_telemetry/hftelgroup.cpp +++ b/orchagent/high_frequency_telemetry/hftelgroup.cpp @@ -22,11 +22,11 @@ void HFTelGroup::updateObjects(const set &object_names) } } -void HFTelGroup::updateStatsIDs(const std::set &stats_ids) +void HFTelGroup::updateStatsIDs(std::set &&stats_ids) { SWSS_LOG_ENTER(); - m_stats_ids = move(stats_ids); + m_stats_ids = std::move(stats_ids); } bool HFTelGroup::isSameObjects(const std::set &object_names) const diff --git a/orchagent/high_frequency_telemetry/hftelgroup.h b/orchagent/high_frequency_telemetry/hftelgroup.h index 7405ea31..3c7b5ad1 100644 --- a/orchagent/high_frequency_telemetry/hftelgroup.h +++ b/orchagent/high_frequency_telemetry/hftelgroup.h @@ -15,7 +15,7 @@ class HFTelGroup HFTelGroup(const std::string& group_name); ~HFTelGroup() = default; void updateObjects(const std::set &object_names); - void updateStatsIDs(const std::set &stats_ids); + void updateStatsIDs(std::set &&stats_ids); bool isSameObjects(const std::set &object_names) const; bool isObjectInGroup(const std::string &object_name) const; const std::unordered_map& getObjects() const { return m_objects; } diff --git a/orchagent/high_frequency_telemetry/hftelorch.cpp b/orchagent/high_frequency_telemetry/hftelorch.cpp index 77aea982..ac368056 100644 --- a/orchagent/high_frequency_telemetry/hftelorch.cpp +++ b/orchagent/high_frequency_telemetry/hftelorch.cpp @@ -20,8 +20,6 @@ using namespace std; using namespace swss; -#define CONSTANTS_FILE "/et/sonic/constants.yml" - const unordered_map HFTelOrch::SUPPORT_COUNTER_TABLES = { {COUNTERS_PORT_NAME_MAP, SAI_OBJECT_TYPE_PORT}, {COUNTERS_BUFFER_POOL_NAME_MAP, SAI_OBJECT_TYPE_BUFFER_POOL}, @@ -112,23 +110,23 @@ void HFTelOrch::locallyNotify(const CounterNameMapUpdater::Message &msg) auto counter_itr = HFTelOrch::SUPPORT_COUNTER_TABLES.find(msg.m_table_name); if (counter_itr == HFTelOrch::SUPPORT_COUNTER_TABLES.end()) { - SWSS_LOG_WARN("The counter table %s is not supported by high frequency telemetry", msg.m_table_name); + SWSS_LOG_WARN("The counter table %s is not supported by high frequency telemetry", msg.m_table_name.c_str()); return; } SWSS_LOG_NOTICE("The counter table %s is updated, operation %d, object %s", - msg.m_table_name, + msg.m_table_name.c_str(), msg.m_operation, - msg.m_operation == CounterNameMapUpdater::SET ? msg.m_set.m_counter_name : msg.m_del.m_counter_name); + msg.m_counter_name.c_str()); // Update the local cache if (msg.m_operation == CounterNameMapUpdater::SET) { - m_counter_name_cache[counter_itr->second][msg.m_set.m_counter_name] = msg.m_set.m_oid; + m_counter_name_cache[counter_itr->second][msg.m_counter_name] = msg.m_oid; } else if (msg.m_operation == CounterNameMapUpdater::DEL) { - m_counter_name_cache[counter_itr->second].erase(msg.m_del.m_counter_name); + m_counter_name_cache[counter_itr->second].erase(msg.m_counter_name); } // Update the profile @@ -140,24 +138,24 @@ void HFTelOrch::locallyNotify(const CounterNameMapUpdater::Message &msg) for (auto profile_itr = type_itr->second.begin(); profile_itr != type_itr->second.end(); profile_itr++) { auto profile = *profile_itr; - const char *counter_name = msg.m_operation == CounterNameMapUpdater::SET ? msg.m_set.m_counter_name : msg.m_del.m_counter_name; + const auto &counter_name = msg.m_counter_name; if (!profile->canBeUpdated(counter_itr->second)) { // TODO: Here is a potential issue, we might need to retry the task. // Because the Syncd is generating the configuration(template), // we cannot update the monitor objects at this time. - SWSS_LOG_WARN("The high frequency telemetry profile %s is not ready to be updated, but the object %s want to be updated", profile->getProfileName().c_str(), counter_name); + SWSS_LOG_WARN("The high frequency telemetry profile %s is not ready to be updated, but the object %s want to be updated", profile->getProfileName().c_str(), counter_name.c_str()); continue; } if (msg.m_operation == CounterNameMapUpdater::SET) { - profile->setObjectSAIID(counter_itr->second, counter_name, msg.m_set.m_oid); + profile->setObjectSAIID(counter_itr->second, counter_name.c_str(), msg.m_oid); } else if (msg.m_operation == CounterNameMapUpdater::DEL) { - profile->delObjectSAIID(counter_itr->second, counter_name); + profile->delObjectSAIID(counter_itr->second, counter_name.c_str()); } else { @@ -673,10 +671,13 @@ void HFTelOrch::createNetlinkChannel(const string &genl_family, const string &ge strncpy(attr.value.chardata, genl_group.c_str(), sizeof(attr.value.chardata)); attrs.push_back(attr); - sai_hostif_api->create_hostif(&m_sai_hostif_obj, gSwitchId, static_cast(attrs.size()), attrs.data()); - - // // Create hostif trap group object - // sai_hostif_api->create_hostif_trap_group(&m_sai_hostif_trap_group_obj, gSwitchId, 0, nullptr); + if (handleSaiCreateStatus( + SAI_API_HOSTIF, + sai_hostif_api->create_hostif(&m_sai_hostif_obj, gSwitchId, static_cast(attrs.size()), attrs.data())) != task_success) + { + deleteNetlinkChannel(); // LCOV_EXCL_LINE: SAI VS create always succeeds + return; // LCOV_EXCL_LINE + } // Create hostif user defined trap object attrs.clear(); @@ -685,11 +686,13 @@ void HFTelOrch::createNetlinkChannel(const string &genl_family, const string &ge attr.value.s32 = SAI_HOSTIF_USER_DEFINED_TRAP_TYPE_TAM; attrs.push_back(attr); - // attr.id = SAI_HOSTIF_USER_DEFINED_TRAP_ATTR_TRAP_GROUP; - // attr.value.oid = m_sai_hostif_trap_group_obj; - // attrs.push_back(attr); - - sai_hostif_api->create_hostif_user_defined_trap(&m_sai_hostif_user_defined_trap_obj, gSwitchId, static_cast(attrs.size()), attrs.data()); + if (handleSaiCreateStatus( + SAI_API_HOSTIF, + sai_hostif_api->create_hostif_user_defined_trap(&m_sai_hostif_user_defined_trap_obj, gSwitchId, static_cast(attrs.size()), attrs.data())) != task_success) + { + deleteNetlinkChannel(); // LCOV_EXCL_LINE: SAI VS create always succeeds + return; // LCOV_EXCL_LINE + } // Create hostif table entry object attrs.clear(); @@ -710,7 +713,13 @@ void HFTelOrch::createNetlinkChannel(const string &genl_family, const string &ge attr.value.oid = m_sai_hostif_obj; attrs.push_back(attr); - sai_hostif_api->create_hostif_table_entry(&m_sai_hostif_table_entry_obj, gSwitchId, static_cast(attrs.size()), attrs.data()); + if (handleSaiCreateStatus( + SAI_API_HOSTIF, + sai_hostif_api->create_hostif_table_entry(&m_sai_hostif_table_entry_obj, gSwitchId, static_cast(attrs.size()), attrs.data())) != task_success) + { + deleteNetlinkChannel(); // LCOV_EXCL_LINE: SAI VS create always succeeds + return; // LCOV_EXCL_LINE + } } void HFTelOrch::deleteNetlinkChannel() diff --git a/orchagent/high_frequency_telemetry/hftelprofile.cpp b/orchagent/high_frequency_telemetry/hftelprofile.cpp index eb29f3d8..fbad567e 100644 --- a/orchagent/high_frequency_telemetry/hftelprofile.cpp +++ b/orchagent/high_frequency_telemetry/hftelprofile.cpp @@ -315,7 +315,7 @@ void HFTelProfile::setStatsIDs(const string &group_name, const set &obje if (itr == m_groups.end() || itr->first != sai_object_type) { HFTelGroup group(group_name); - group.updateStatsIDs(stats_ids_set); + group.updateStatsIDs(std::move(stats_ids_set)); m_groups.insert(itr, {sai_object_type, move(group)}); } else @@ -324,7 +324,7 @@ void HFTelProfile::setStatsIDs(const string &group_name, const set &obje { return; } - itr->second.updateStatsIDs(stats_ids_set); + itr->second.updateStatsIDs(std::move(stats_ids_set)); } // TODO: In the phase 2, we don't need to stop the stream before update the stats @@ -451,10 +451,14 @@ void HFTelProfile::clearGroup(const std::string &group_name) m_groups.erase(itr); } m_sai_tam_tel_type_templates.erase(sai_object_type); - m_sai_tam_tel_type_states.erase(m_sai_tam_tel_type_objs[sai_object_type]); - m_sai_tam_tel_type_objs.erase(sai_object_type); - m_sai_tam_report_objs.erase(sai_object_type); m_sai_tam_counter_subscription_objs.erase(sai_object_type); + auto tel_type_itr = m_sai_tam_tel_type_objs.find(sai_object_type); + if (tel_type_itr != m_sai_tam_tel_type_objs.end()) + { + m_sai_tam_tel_type_states.erase(tel_type_itr->second); + m_sai_tam_tel_type_objs.erase(tel_type_itr); + } + m_sai_tam_report_objs.erase(sai_object_type); m_name_sai_map.erase(sai_object_type); SWSS_LOG_NOTICE("Cleared high frequency telemetry group %s with no objects", group_name.c_str()); @@ -663,6 +667,7 @@ sai_object_id_t HFTelProfile::getTAMReportObjID(sai_object_type_t object_type) attr.id = SAI_TAM_REPORT_ATTR_REPORT_INTERVAL_UNIT; attr.value.s32 = SAI_TAM_REPORT_INTERVAL_UNIT_USEC; + attrs.push_back(attr); handleSaiCreateStatus( SAI_API_TAM, @@ -854,7 +859,7 @@ void HFTelProfile::deployCounterSubscription(sai_object_type_t object_type, sai_ attrs.push_back(attr); attr.id = SAI_TAM_COUNTER_SUBSCRIPTION_ATTR_STAT_ID; - attr.value.oid = stat_id; + attr.value.u32 = static_cast(stat_id); attrs.push_back(attr); attr.id = SAI_TAM_COUNTER_SUBSCRIPTION_ATTR_LABEL; diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index 0ff5abdf..ef939887 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -81,6 +81,8 @@ tests_SOURCES = aclorch_ut.cpp \ flexcounter_ut.cpp \ portphyattr_ut.cpp \ portphyserdesattr_ut.cpp \ + counternameupdater_ut.cpp \ + hftelgroup_ut.cpp \ hftelprofile_ut.cpp \ mock_orch_test.cpp \ mock_dash_orch_test.cpp \ @@ -88,6 +90,7 @@ tests_SOURCES = aclorch_ut.cpp \ mock_saihelper.cpp \ mirrororch_ut.cpp \ hftelorch_ut.cpp \ + hftelorch_notify_ut.cpp \ hftelorch_is_supported_sai_wrap.cpp \ icmporch_ut.cpp \ icmporch_sai_wrap.cpp \ diff --git a/tests/mock_tests/counternameupdater_ut.cpp b/tests/mock_tests/counternameupdater_ut.cpp new file mode 100644 index 00000000..d484e0ed --- /dev/null +++ b/tests/mock_tests/counternameupdater_ut.cpp @@ -0,0 +1,199 @@ +#include "ut_helper.h" +#include "mock_orchagent_main.h" +#include "mock_table.h" +#include + +#define private public +#include "high_frequency_telemetry/counternameupdater.h" +#undef private + +extern HFTelOrch *gHFTOrch; + +namespace counternameupdater_test +{ + using namespace std; + using namespace swss; + + struct CounterNameMapUpdaterTest : public ::testing::Test + { + shared_ptr m_counters_db; + shared_ptr m_counters_queue_name_map_table; + shared_ptr
m_counters_pg_name_map_table; + + CounterNameMapUpdaterTest() + { + } + + void SetUp() override + { + // Initialize database connectors + // Use the string constructor to get the correct dbId from database_config.json + m_counters_db = make_shared("COUNTERS_DB", 0, true); + m_counters_queue_name_map_table = make_shared
(m_counters_db.get(), "COUNTERS_QUEUE_NAME_MAP"); + m_counters_pg_name_map_table = make_shared
(m_counters_db.get(), "COUNTERS_PG_NAME_MAP"); + + // Clear tables + m_counters_queue_name_map_table->del(""); + m_counters_pg_name_map_table->del(""); + } + + void TearDown() override + { + // Clean up + m_counters_queue_name_map_table->del(""); + m_counters_pg_name_map_table->del(""); + } + }; + + // Test that setCounterNameMap works without HFT support (gHFTOrch == nullptr) + TEST_F(CounterNameMapUpdaterTest, SetCounterNameMapWithoutHFT) + { + // Ensure gHFTOrch is nullptr to simulate platform without HFT support + HFTelOrch *saved_gHFTOrch = gHFTOrch; + gHFTOrch = nullptr; + + cout << "Testing QUEUE counter maps without HFT support (gHFTOrch=" << (void*)gHFTOrch << ")" << endl; + + // Create CounterNameMapUpdater for QUEUE + CounterNameMapUpdater queue_updater("COUNTERS_DB", "COUNTERS_QUEUE_NAME_MAP"); + + // Set counter maps one by one using numeric OIDs + cout << "Calling setCounterNameMap with 3 entries..." << endl; + queue_updater.setCounterNameMap("Ethernet0:0", 0x1500000000001ULL); + queue_updater.setCounterNameMap("Ethernet0:1", 0x1500000000002ULL); + queue_updater.setCounterNameMap("Ethernet0:2", 0x1500000000003ULL); + + cout << "Verifying entries were written to COUNTERS_DB..." << endl; + + // Verify that the counter names were written to COUNTERS_DB + string value; + bool result; + + result = m_counters_queue_name_map_table->hget("", "Ethernet0:0", value); + cout << " Ethernet0:0 -> " << (result ? value : "NOT FOUND") << endl; + ASSERT_TRUE(result); + ASSERT_EQ(value, "oid:0x1500000000001"); + + result = m_counters_queue_name_map_table->hget("", "Ethernet0:1", value); + cout << " Ethernet0:1 -> " << (result ? value : "NOT FOUND") << endl; + ASSERT_TRUE(result); + ASSERT_EQ(value, "oid:0x1500000000002"); + + result = m_counters_queue_name_map_table->hget("", "Ethernet0:2", value); + cout << " Ethernet0:2 -> " << (result ? value : "NOT FOUND") << endl; + ASSERT_TRUE(result); + ASSERT_EQ(value, "oid:0x1500000000003"); + + cout << "All QUEUE counter map entries verified successfully!" << endl; + + // Restore gHFTOrch + gHFTOrch = saved_gHFTOrch; + } + + // Test single counter name map set + TEST_F(CounterNameMapUpdaterTest, SetSingleCounterNameMap) + { + // Ensure gHFTOrch is nullptr + HFTelOrch *saved_gHFTOrch = gHFTOrch; + gHFTOrch = nullptr; + + CounterNameMapUpdater queue_updater("COUNTERS_DB", "COUNTERS_QUEUE_NAME_MAP"); + + // Set single counter name map + sai_object_id_t oid = 0x1500000000001; + queue_updater.setCounterNameMap("Ethernet0:0", oid); + + // Verify + string value; + bool result = m_counters_queue_name_map_table->hget("", "Ethernet0:0", value); + ASSERT_TRUE(result); + ASSERT_EQ(value, "oid:0x1500000000001"); + + // Restore gHFTOrch + gHFTOrch = saved_gHFTOrch; + } + + // Test delCounterNameMap removes the entry from DB + TEST_F(CounterNameMapUpdaterTest, DelCounterNameMap) + { + HFTelOrch *saved_gHFTOrch = gHFTOrch; + gHFTOrch = nullptr; + + CounterNameMapUpdater queue_updater("COUNTERS_DB", "COUNTERS_QUEUE_NAME_MAP"); + + // Set then delete + queue_updater.setCounterNameMap("Ethernet0:5", 0x1500000000005ULL); + + string value; + ASSERT_TRUE(m_counters_queue_name_map_table->hget("", "Ethernet0:5", value)); + + queue_updater.delCounterNameMap("Ethernet0:5"); + + ASSERT_FALSE(m_counters_queue_name_map_table->hget("", "Ethernet0:5", value)); + + gHFTOrch = saved_gHFTOrch; + } + + // Test batch setCounterNameMap with FieldValueTuple vector + TEST_F(CounterNameMapUpdaterTest, SetCounterNameMapBatch) + { + HFTelOrch *saved_gHFTOrch = gHFTOrch; + gHFTOrch = nullptr; + + CounterNameMapUpdater pg_updater("COUNTERS_DB", "COUNTERS_PG_NAME_MAP"); + + vector batch = { + {"Ethernet0|3", "oid:0x1a00000000001"}, + {"Ethernet0|4", "oid:0x1a00000000002"}, + }; + pg_updater.setCounterNameMap(batch); + + string value; + ASSERT_TRUE(m_counters_pg_name_map_table->hget("", "Ethernet0|3", value)); + ASSERT_EQ(value, "oid:0x1a00000000001"); + ASSERT_TRUE(m_counters_pg_name_map_table->hget("", "Ethernet0|4", value)); + ASSERT_EQ(value, "oid:0x1a00000000002"); + + gHFTOrch = saved_gHFTOrch; + } + + // Test unify_counter_name replaces ':' with '|' + TEST_F(CounterNameMapUpdaterTest, UnifyCounterName) + { + CounterNameMapUpdater updater("COUNTERS_DB", "COUNTERS_QUEUE_NAME_MAP"); + + // ':' separator should be replaced with '|' + ASSERT_EQ(updater.unify_counter_name("Ethernet0:3"), "Ethernet0|3"); + + // No ':' should return unchanged + ASSERT_EQ(updater.unify_counter_name("Ethernet0"), "Ethernet0"); + + // Only last ':' should be replaced + ASSERT_EQ(updater.unify_counter_name("a:b:c"), "a:b|c"); + } + + // Test Message struct uses owned strings (no dangling pointers) + TEST_F(CounterNameMapUpdaterTest, MessageStructOwnsStrings) + { + CounterNameMapUpdater::Message msg; + msg.m_table_name = "COUNTERS_PORT_NAME_MAP"; + msg.m_operation = CounterNameMapUpdater::SET; + msg.m_counter_name = "Ethernet0"; + msg.m_oid = 0x1000000000001ULL; + + // Verify message fields are accessible (owned, not dangling) + ASSERT_EQ(msg.m_table_name, "COUNTERS_PORT_NAME_MAP"); + ASSERT_EQ(msg.m_counter_name, "Ethernet0"); + ASSERT_EQ(msg.m_oid, 0x1000000000001ULL); + ASSERT_EQ(msg.m_operation, CounterNameMapUpdater::SET); + + // DEL message + CounterNameMapUpdater::Message del_msg; + del_msg.m_table_name = "COUNTERS_QUEUE_NAME_MAP"; + del_msg.m_operation = CounterNameMapUpdater::DEL; + del_msg.m_counter_name = "Ethernet0|0"; + + ASSERT_EQ(del_msg.m_operation, CounterNameMapUpdater::DEL); + ASSERT_EQ(del_msg.m_oid, SAI_NULL_OBJECT_ID); + } +} diff --git a/tests/mock_tests/hftelgroup_ut.cpp b/tests/mock_tests/hftelgroup_ut.cpp new file mode 100644 index 00000000..49858458 --- /dev/null +++ b/tests/mock_tests/hftelgroup_ut.cpp @@ -0,0 +1,160 @@ +#include +#include +#include +#include +#include + +#include "high_frequency_telemetry/hftelgroup.h" + +namespace hftelgroup_test +{ + using namespace std; + + class HFTelGroupTest : public ::testing::Test {}; + + TEST_F(HFTelGroupTest, UpdateObjects_AssignsLabelsStartingFromOne) + { + HFTelGroup group("port"); + + set names = {"Ethernet0", "Ethernet4", "Ethernet8"}; + group.updateObjects(names); + + auto &objs = group.getObjects(); + ASSERT_EQ(objs.size(), 3u); + + // Every object should have a label >= 1 + for (const auto &obj : objs) + { + EXPECT_GE(obj.second, 1); + EXPECT_LE(obj.second, 3); + } + + // Labels should be unique + set labels; + for (const auto &obj : objs) + { + labels.insert(obj.second); + } + EXPECT_EQ(labels.size(), 3u); + } + + TEST_F(HFTelGroupTest, UpdateObjects_ClearsPrevious) + { + HFTelGroup group("port"); + + set names1 = {"Ethernet0", "Ethernet4"}; + group.updateObjects(names1); + ASSERT_EQ(group.getObjects().size(), 2u); + + set names2 = {"Ethernet8"}; + group.updateObjects(names2); + ASSERT_EQ(group.getObjects().size(), 1u); + EXPECT_TRUE(group.isObjectInGroup("Ethernet8")); + EXPECT_FALSE(group.isObjectInGroup("Ethernet0")); + } + + TEST_F(HFTelGroupTest, UpdateStatsIDs_MoveSemantics) + { + HFTelGroup group("port"); + + set ids = {100, 200, 300}; + group.updateStatsIDs(move(ids)); + + auto &stats = group.getStatsIDs(); + ASSERT_EQ(stats.size(), 3u); + EXPECT_TRUE(stats.count(100)); + EXPECT_TRUE(stats.count(200)); + EXPECT_TRUE(stats.count(300)); + } + + TEST_F(HFTelGroupTest, UpdateStatsIDs_ReplacePrevious) + { + HFTelGroup group("queue"); + + set ids1 = {10, 20}; + group.updateStatsIDs(move(ids1)); + ASSERT_EQ(group.getStatsIDs().size(), 2u); + + set ids2 = {30}; + group.updateStatsIDs(move(ids2)); + ASSERT_EQ(group.getStatsIDs().size(), 1u); + EXPECT_TRUE(group.getStatsIDs().count(30)); + } + + TEST_F(HFTelGroupTest, IsSameObjects_True) + { + HFTelGroup group("port"); + + set names = {"Ethernet0", "Ethernet4"}; + group.updateObjects(names); + EXPECT_TRUE(group.isSameObjects(names)); + } + + TEST_F(HFTelGroupTest, IsSameObjects_DifferentSize) + { + HFTelGroup group("port"); + + set names = {"Ethernet0", "Ethernet4"}; + group.updateObjects(names); + + set different = {"Ethernet0"}; + EXPECT_FALSE(group.isSameObjects(different)); + } + + TEST_F(HFTelGroupTest, IsSameObjects_DifferentContent) + { + HFTelGroup group("port"); + + set names = {"Ethernet0", "Ethernet4"}; + group.updateObjects(names); + + set different = {"Ethernet0", "Ethernet8"}; + EXPECT_FALSE(group.isSameObjects(different)); + } + + TEST_F(HFTelGroupTest, IsObjectInGroup) + { + HFTelGroup group("port"); + + set names = {"Ethernet0", "Ethernet4"}; + group.updateObjects(names); + + EXPECT_TRUE(group.isObjectInGroup("Ethernet0")); + EXPECT_TRUE(group.isObjectInGroup("Ethernet4")); + EXPECT_FALSE(group.isObjectInGroup("Ethernet8")); + } + + TEST_F(HFTelGroupTest, GetObjectNamesAndLabels) + { + HFTelGroup group("port"); + + set names = {"Ethernet0", "Ethernet4"}; + group.updateObjects(names); + + auto result = group.getObjectNamesAndLabels(); + auto &obj_names = result.first; + auto &obj_labels = result.second; + ASSERT_EQ(obj_names.size(), 2u); + ASSERT_EQ(obj_labels.size(), 2u); + + // Verify names and labels are paired correctly + for (size_t i = 0; i < obj_names.size(); ++i) + { + auto it = group.getObjects().find(obj_names[i]); + ASSERT_NE(it, group.getObjects().end()); + EXPECT_EQ(obj_labels[i], to_string(it->second)); + } + } + + TEST_F(HFTelGroupTest, EmptyGroup) + { + HFTelGroup group("port"); + + EXPECT_TRUE(group.getObjects().empty()); + EXPECT_TRUE(group.getStatsIDs().empty()); + EXPECT_FALSE(group.isObjectInGroup("anything")); + + set empty_set; + EXPECT_TRUE(group.isSameObjects(empty_set)); + } +} diff --git a/tests/mock_tests/hftelorch_is_supported_sai_wrap.cpp b/tests/mock_tests/hftelorch_is_supported_sai_wrap.cpp index aa0f88ff..81c6f25c 100644 --- a/tests/mock_tests/hftelorch_is_supported_sai_wrap.cpp +++ b/tests/mock_tests/hftelorch_is_supported_sai_wrap.cpp @@ -17,6 +17,7 @@ namespace AttributeCapabilityQueryFail, CollectorCreateNotImplemented, SwitchNotifySetNotImplemented, + AllSupported, }; static thread_local Hook g_hook = Hook::None; @@ -60,6 +61,16 @@ extern "C" return __real_sai_query_attribute_capability(switch_id, object_type, attr_id, attr_capability); } + if (g_hook == Hook::AllSupported) + { + if (!attr_capability) return SAI_STATUS_INVALID_PARAMETER; + std::memset(attr_capability, 0, sizeof(*attr_capability)); + attr_capability->create_implemented = true; + attr_capability->set_implemented = true; + attr_capability->get_implemented = true; + return SAI_STATUS_SUCCESS; + } + if (g_hook == Hook::AttributeCapabilityQueryFail) { return SAI_STATUS_NOT_SUPPORTED; @@ -141,4 +152,9 @@ namespace hftel_is_supported_ut { g_hook = Hook::SwitchNotifySetNotImplemented; } + + void setSaiHookAllSupported() + { + g_hook = Hook::AllSupported; + } } diff --git a/tests/mock_tests/hftelorch_is_supported_sai_wrap.h b/tests/mock_tests/hftelorch_is_supported_sai_wrap.h index b19c413b..3b586a6d 100644 --- a/tests/mock_tests/hftelorch_is_supported_sai_wrap.h +++ b/tests/mock_tests/hftelorch_is_supported_sai_wrap.h @@ -7,6 +7,7 @@ namespace hftel_is_supported_ut void setSaiHookAttributeCapabilityQueryFail(); void setSaiHookCollectorCreateNotImplemented(); void setSaiHookSwitchNotifySetNotImplemented(); + void setSaiHookAllSupported(); /** RAII: restores hook to None on scope exit. */ struct SaiHookGuard diff --git a/tests/mock_tests/hftelorch_notify_ut.cpp b/tests/mock_tests/hftelorch_notify_ut.cpp new file mode 100644 index 00000000..69572450 --- /dev/null +++ b/tests/mock_tests/hftelorch_notify_ut.cpp @@ -0,0 +1,240 @@ +// Pre-include standard library and third-party headers that conflict with +// the #define private public hack (they use 'private' internally). +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define private public +#define protected public +#include "high_frequency_telemetry/hftelorch.h" +#include "high_frequency_telemetry/counternameupdater.h" +#undef private +#undef protected + +#include "ut_helper.h" +#include "mock_orchagent_main.h" +#include + +extern HFTelOrch *gHFTOrch; + +namespace hftelorch_notify_test +{ + using namespace std; + + /* + * Stub HFTelOrch that only constructs the members used by locallyNotify(). + * Avoids calling the real constructor which requires full SAI/orch infrastructure. + * + * locallyNotify() accesses: + * - HFTelOrch::SUPPORT_COUNTER_TABLES (static, always valid) + * - m_counter_name_cache + * - m_type_profile_mapping + */ + struct HFTelOrchStub + { + alignas(HFTelOrch) unsigned char buf[sizeof(HFTelOrch)]; + HFTelOrch *p = nullptr; + + void init() + { + memset(buf, 0, sizeof(buf)); + p = reinterpret_cast(static_cast(buf)); + + new (&p->m_counter_name_cache) + decay_tm_counter_name_cache)>(); + new (&p->m_type_profile_mapping) + decay_tm_type_profile_mapping)>(); + } + + ~HFTelOrchStub() + { + if (!p) return; + using CacheType = decay_tm_counter_name_cache)>; + using ProfileMapType = decay_tm_type_profile_mapping)>; + p->m_counter_name_cache.~CacheType(); + p->m_type_profile_mapping.~ProfileMapType(); + p = nullptr; + } + }; + + struct HFTelProfileStub + { + alignas(HFTelProfile) unsigned char buf[sizeof(HFTelProfile)]; + HFTelProfile *p = nullptr; + + void init(bool block_updates = false) + { + memset(buf, 0, sizeof(buf)); + p = reinterpret_cast(static_cast(buf)); + + new (const_cast(&p->m_profile_name)) string("profile"); + p->m_setting_state = SAI_TAM_TEL_TYPE_STATE_STOP_STREAM; + p->m_poll_interval = 0; + + new (&p->m_groups) decay_tm_groups)>(); + new (&p->m_name_sai_map) decay_tm_name_sai_map)>(); + new (&p->m_sai_tam_counter_subscription_objs) + decay_tm_sai_tam_counter_subscription_objs)>(); + new (&p->m_sai_tam_tel_type_objs) + decay_tm_sai_tam_tel_type_objs)>(); + new (&p->m_sai_tam_tel_type_states) + decay_tm_sai_tam_tel_type_states)>(); + + HFTelGroup group("PORT"); + group.updateObjects({"Ethernet0"}); + p->m_groups.emplace(SAI_OBJECT_TYPE_PORT, group); + + if (block_updates) + { + auto guard = make_shared(0x100); + p->m_sai_tam_tel_type_objs[SAI_OBJECT_TYPE_PORT] = guard; + p->m_sai_tam_tel_type_states[guard] = SAI_TAM_TEL_TYPE_STATE_CREATE_CONFIG; + } + } + + ~HFTelProfileStub() + { + if (!p) return; + p->m_profile_name.~basic_string(); + p->m_groups.~map(); + p->m_name_sai_map.~unordered_map(); + p->m_sai_tam_counter_subscription_objs.~unordered_map(); + p->m_sai_tam_tel_type_objs.~unordered_map(); + p->m_sai_tam_tel_type_states.~unordered_map(); + p = nullptr; + } + }; + + struct LocallyNotifyTest : public ::testing::Test + { + HFTelOrchStub stub; + HFTelOrch *saved_gHFTOrch = nullptr; + + void SetUp() override + { + saved_gHFTOrch = gHFTOrch; + stub.init(); + gHFTOrch = stub.p; + } + + void TearDown() override + { + gHFTOrch = saved_gHFTOrch; + } + }; + + /* locallyNotify with unsupported table — early return. + * Covers: msg.m_table_name.c_str() log line. */ + TEST_F(LocallyNotifyTest, UnsupportedTable) + { + CounterNameMapUpdater::Message msg; + msg.m_table_name = "UNSUPPORTED_TABLE"; + msg.m_operation = CounterNameMapUpdater::SET; + msg.m_counter_name = "Ethernet0"; + msg.m_oid = 0x1000000000001ULL; + + ASSERT_NO_THROW(stub.p->locallyNotify(msg)); + } + + /* locallyNotify SET with supported table, no profiles — cache update path. + * Covers: msg.m_counter_name, msg.m_oid cache lines. */ + TEST_F(LocallyNotifyTest, SetNoProfile) + { + CounterNameMapUpdater::Message msg; + msg.m_table_name = COUNTERS_PORT_NAME_MAP; + msg.m_operation = CounterNameMapUpdater::SET; + msg.m_counter_name = "Ethernet0"; + msg.m_oid = 0x1000000000001ULL; + + ASSERT_NO_THROW(stub.p->locallyNotify(msg)); + } + + /* locallyNotify DEL with supported table, no profiles — cache erase path. + * Covers: msg.m_counter_name erase line. */ + TEST_F(LocallyNotifyTest, DelNoProfile) + { + // First SET + CounterNameMapUpdater::Message set_msg; + set_msg.m_table_name = COUNTERS_QUEUE_NAME_MAP; + set_msg.m_operation = CounterNameMapUpdater::SET; + set_msg.m_counter_name = "Ethernet0|0"; + set_msg.m_oid = 0x1500000000001ULL; + stub.p->locallyNotify(set_msg); + + // Then DEL + CounterNameMapUpdater::Message del_msg; + del_msg.m_table_name = COUNTERS_QUEUE_NAME_MAP; + del_msg.m_operation = CounterNameMapUpdater::DEL; + del_msg.m_counter_name = "Ethernet0|0"; + + ASSERT_NO_THROW(stub.p->locallyNotify(del_msg)); + } + + TEST_F(LocallyNotifyTest, SetAndDelUpdateProfile) + { + HFTelProfileStub profile; + profile.init(); + stub.p->m_type_profile_mapping[SAI_OBJECT_TYPE_PORT].insert( + shared_ptr(profile.p, [](HFTelProfile *) {})); + + CounterNameMapUpdater::Message set_msg; + set_msg.m_table_name = COUNTERS_PORT_NAME_MAP; + set_msg.m_operation = CounterNameMapUpdater::SET; + set_msg.m_counter_name = "Ethernet0"; + set_msg.m_oid = 0x1000000000001ULL; + + ASSERT_NO_THROW(stub.p->locallyNotify(set_msg)); + ASSERT_EQ(profile.p->m_name_sai_map[SAI_OBJECT_TYPE_PORT]["Ethernet0"], + 0x1000000000001ULL); + + CounterNameMapUpdater::Message del_msg; + del_msg.m_table_name = COUNTERS_PORT_NAME_MAP; + del_msg.m_operation = CounterNameMapUpdater::DEL; + del_msg.m_counter_name = "Ethernet0"; + + ASSERT_NO_THROW(stub.p->locallyNotify(del_msg)); + auto objs = profile.p->m_name_sai_map.find(SAI_OBJECT_TYPE_PORT); + EXPECT_TRUE(objs == profile.p->m_name_sai_map.end() || objs->second.empty()); + } + + TEST_F(LocallyNotifyTest, SkipsProfileWhenConfigIsGenerating) + { + HFTelProfileStub profile; + profile.init(true); + stub.p->m_type_profile_mapping[SAI_OBJECT_TYPE_PORT].insert( + shared_ptr(profile.p, [](HFTelProfile *) {})); + + CounterNameMapUpdater::Message msg; + msg.m_table_name = COUNTERS_PORT_NAME_MAP; + msg.m_operation = CounterNameMapUpdater::SET; + msg.m_counter_name = "Ethernet0"; + msg.m_oid = 0x1000000000001ULL; + + ASSERT_NO_THROW(stub.p->locallyNotify(msg)); + EXPECT_TRUE(profile.p->m_name_sai_map.empty()); + } + + /* CounterNameMapUpdater::setCounterNameMap with gHFTOrch non-null. + * Covers the Message construction lines in counternameupdater.cpp SET path. */ + TEST_F(LocallyNotifyTest, CounterNameUpdater_SetWithHFT) + { + CounterNameMapUpdater updater("COUNTERS_DB", COUNTERS_PORT_NAME_MAP); + ASSERT_NO_THROW(updater.setCounterNameMap("Ethernet0", 0x1000000000001ULL)); + } + + /* CounterNameMapUpdater::delCounterNameMap with gHFTOrch non-null. + * Covers the Message construction lines in counternameupdater.cpp DEL path. */ + TEST_F(LocallyNotifyTest, CounterNameUpdater_DelWithHFT) + { + CounterNameMapUpdater updater("COUNTERS_DB", COUNTERS_PORT_NAME_MAP); + ASSERT_NO_THROW(updater.delCounterNameMap("Ethernet0")); + } +} diff --git a/tests/mock_tests/hftelorch_ut.cpp b/tests/mock_tests/hftelorch_ut.cpp index 0050cfc4..fbafd07a 100644 --- a/tests/mock_tests/hftelorch_ut.cpp +++ b/tests/mock_tests/hftelorch_ut.cpp @@ -128,6 +128,20 @@ namespace hftelorch_test EXPECT_FALSE(HFTelOrch::isSupportedHFTel(gSwitchId)); } + /* + * All checks pass - happy path through the entire function. + * The AllSupported hook makes attribute capability return all-supported, + * then real sai_query_attribute_enum_values_capability handles enum checks. + * Covers: the full attribute loop, enum loop, and "return true" at the end. + */ + TEST_F(HFTelOrchIsSupportedTest, IsSupportedHFTel_positive_all_supported) + { + SaiHookGuard guard(hftel_is_supported_ut::setSaiHookAllSupported); + // VS SAI may or may not support all enum values, so exercise the code path without asserting the result. + bool supported = HFTelOrch::isSupportedHFTel(gSwitchId); + (void)supported; + } + class HFTelOrchConstructorTest : public ::testing::Test { protected: diff --git a/tests/mock_tests/hftelprofile_ut.cpp b/tests/mock_tests/hftelprofile_ut.cpp index 9b7990cf..f3a62b36 100644 --- a/tests/mock_tests/hftelprofile_ut.cpp +++ b/tests/mock_tests/hftelprofile_ut.cpp @@ -7,6 +7,9 @@ #include #include #include +#include +#include +#include #define private public #define protected public @@ -236,4 +239,268 @@ namespace hftelprofile_ut sai_object_id_t bad_oid = 0xDEAD; EXPECT_THROW(s.p->updateTemplates(bad_oid), runtime_error); } + + /* + * Fixture for clearGroup() tests. + * Constructs all members that clearGroup() reads/writes. + */ + struct ClearGroupTest : public ::testing::Test + { + struct ClearGroupStub + { + alignas(HFTelProfile) unsigned char buf[sizeof(HFTelProfile)]; + HFTelProfile *p = nullptr; + + void init() + { + memset(buf, 0, sizeof(buf)); + p = reinterpret_cast(static_cast(buf)); + + new (const_cast(&p->m_profile_name)) string("test_profile"); + new (&p->m_groups) decay_tm_groups)>(); + new (&p->m_sai_tam_tel_type_templates) decay_tm_sai_tam_tel_type_templates)>(); + new (&p->m_sai_tam_counter_subscription_objs) decay_tm_sai_tam_counter_subscription_objs)>(); + new (&p->m_sai_tam_tel_type_objs) decay_tm_sai_tam_tel_type_objs)>(); + new (&p->m_sai_tam_tel_type_states) decay_tm_sai_tam_tel_type_states)>(); + new (&p->m_sai_tam_report_objs) decay_tm_sai_tam_report_objs)>(); + new (&p->m_name_sai_map) decay_tm_name_sai_map)>(); + } + + ~ClearGroupStub() + { + if (!p) return; + p->m_profile_name.~basic_string(); + p->m_groups.~map(); + p->m_sai_tam_tel_type_templates.~unordered_map(); + p->m_sai_tam_counter_subscription_objs.~unordered_map(); + p->m_sai_tam_tel_type_objs.~unordered_map(); + p->m_sai_tam_tel_type_states.~unordered_map(); + p->m_sai_tam_report_objs.~unordered_map(); + p->m_name_sai_map.~unordered_map(); + p = nullptr; + } + }; + }; + + /* clearGroup on empty profile — exercises the find-based cleanup path */ + TEST_F(ClearGroupTest, ClearGroup_EmptyProfile) + { + ClearGroupStub s; + s.init(); + + // clearGroup with no existing data — should not crash + ASSERT_NO_THROW(s.p->clearGroup("port")); + } + + /* clearGroup with tel_type_obj present — covers the if(find) erase branch */ + TEST_F(ClearGroupTest, ClearGroup_WithTelTypeObj) + { + ClearGroupStub s; + s.init(); + + auto guard = make_shared(0x200); + s.p->m_sai_tam_tel_type_objs[SAI_OBJECT_TYPE_PORT] = guard; + s.p->m_sai_tam_tel_type_states[guard] = SAI_TAM_TEL_TYPE_STATE_STOP_STREAM; + s.p->m_sai_tam_tel_type_templates[SAI_OBJECT_TYPE_PORT] = {0x01, 0x02}; + s.p->m_sai_tam_report_objs[SAI_OBJECT_TYPE_PORT] = make_shared(0x300); + + ASSERT_NO_THROW(s.p->clearGroup("port")); + + EXPECT_TRUE(s.p->m_sai_tam_tel_type_objs.empty()); + EXPECT_TRUE(s.p->m_sai_tam_tel_type_states.empty()); + EXPECT_TRUE(s.p->m_sai_tam_tel_type_templates.empty()); + EXPECT_TRUE(s.p->m_sai_tam_report_objs.empty()); + } + + struct SetStatsIDsTest : public ::testing::Test + { + struct SetStatsIDsStub + { + alignas(HFTelProfile) unsigned char buf[sizeof(HFTelProfile)]; + HFTelProfile *p = nullptr; + + void init() + { + memset(buf, 0, sizeof(buf)); + p = reinterpret_cast(static_cast(buf)); + + new (const_cast(&p->m_profile_name)) string("test_profile"); + p->m_setting_state = SAI_TAM_TEL_TYPE_STATE_STOP_STREAM; + p->m_poll_interval = 0; + new (&p->m_groups) decay_tm_groups)>(); + new (&p->m_name_sai_map) decay_tm_name_sai_map)>(); + new (&p->m_sai_tam_counter_subscription_objs) + decay_tm_sai_tam_counter_subscription_objs)>(); + new (&p->m_sai_tam_tel_type_objs) + decay_tm_sai_tam_tel_type_objs)>(); + new (&p->m_sai_tam_tel_type_states) + decay_tm_sai_tam_tel_type_states)>(); + } + + ~SetStatsIDsStub() + { + if (!p) return; + p->m_profile_name.~basic_string(); + p->m_groups.~map(); + p->m_name_sai_map.~unordered_map(); + p->m_sai_tam_counter_subscription_objs.~unordered_map(); + p->m_sai_tam_tel_type_objs.~unordered_map(); + p->m_sai_tam_tel_type_states.~unordered_map(); + p = nullptr; + } + }; + }; + + TEST_F(SetStatsIDsTest, SetStatsIDsCreatesAndUpdatesGroup) + { + SetStatsIDsStub s; + s.init(); + + s.p->setStatsIDs("port", {"IF_IN_OCTETS"}); + ASSERT_EQ(s.p->m_groups.size(), 1u); + EXPECT_EQ(s.p->m_groups.at(SAI_OBJECT_TYPE_PORT).getStatsIDs(), + set({SAI_PORT_STAT_IF_IN_OCTETS})); + + s.p->setStatsIDs("port", {"IF_OUT_OCTETS"}); + ASSERT_EQ(s.p->m_groups.size(), 1u); + EXPECT_EQ(s.p->m_groups.at(SAI_OBJECT_TYPE_PORT).getStatsIDs(), + set({SAI_PORT_STAT_IF_OUT_OCTETS})); + } + + struct SaiAttrTest : public ::testing::Test + { + sai_tam_api_t ut_api; + sai_tam_api_t *orig_api = nullptr; + + struct SaiAttrStub + { + alignas(HFTelProfile) unsigned char buf[sizeof(HFTelProfile)]; + HFTelProfile *p = nullptr; + + void init() + { + memset(buf, 0, sizeof(buf)); + p = reinterpret_cast(static_cast(buf)); + + new (const_cast(&p->m_profile_name)) string("test_profile"); + p->m_setting_state = SAI_TAM_TEL_TYPE_STATE_STOP_STREAM; + p->m_poll_interval = 100; + new (&p->m_sai_tam_counter_subscription_objs) + decay_tm_sai_tam_counter_subscription_objs)>(); + new (&p->m_sai_tam_tel_type_objs) + decay_tm_sai_tam_tel_type_objs)>(); + new (&p->m_sai_tam_report_objs) + decay_tm_sai_tam_report_objs)>(); + + p->m_sai_tam_tel_type_objs[SAI_OBJECT_TYPE_PORT] = + make_shared(0x200); + } + + ~SaiAttrStub() + { + if (!p) return; + p->m_profile_name.~basic_string(); + p->m_sai_tam_counter_subscription_objs.~unordered_map(); + p->m_sai_tam_tel_type_objs.~unordered_map(); + p->m_sai_tam_report_objs.~unordered_map(); + p = nullptr; + } + }; + + static vector report_attrs; + static vector counter_attrs; + + static sai_status_t mock_create_tam_report( + sai_object_id_t *report_id, + sai_object_id_t /*switch_id*/, + uint32_t attr_count, + const sai_attribute_t *attr_list) + { + report_attrs.assign(attr_list, attr_list + attr_count); + *report_id = 0x500; + return SAI_STATUS_SUCCESS; + } + + static sai_status_t mock_remove_tam_report(sai_object_id_t /*report_id*/) + { + return SAI_STATUS_SUCCESS; + } + + static sai_status_t mock_create_tam_counter_subscription( + sai_object_id_t *counter_subscription_id, + sai_object_id_t /*switch_id*/, + uint32_t attr_count, + const sai_attribute_t *attr_list) + { + counter_attrs.assign(attr_list, attr_list + attr_count); + *counter_subscription_id = 0x600; + return SAI_STATUS_SUCCESS; + } + + static sai_status_t mock_remove_tam_counter_subscription( + sai_object_id_t /*counter_subscription_id*/) + { + return SAI_STATUS_SUCCESS; + } + + void SetUp() override + { + if (sai_tam_api == nullptr) + { + static sai_tam_api_t default_tam_api{}; + sai_tam_api = &default_tam_api; + } + ut_api = *sai_tam_api; + orig_api = sai_tam_api; + ut_api.create_tam_report = mock_create_tam_report; + ut_api.remove_tam_report = mock_remove_tam_report; + ut_api.create_tam_counter_subscription = mock_create_tam_counter_subscription; + ut_api.remove_tam_counter_subscription = mock_remove_tam_counter_subscription; + sai_tam_api = &ut_api; + report_attrs.clear(); + counter_attrs.clear(); + } + + void TearDown() override + { + sai_tam_api = orig_api; + } + }; + + vector SaiAttrTest::report_attrs; + vector SaiAttrTest::counter_attrs; + + TEST_F(SaiAttrTest, GetTAMReportAddsIntervalUnit) + { + SaiAttrStub s; + s.init(); + + ASSERT_EQ(s.p->getTAMReportObjID(SAI_OBJECT_TYPE_PORT), 0x500ULL); + + auto itr = find_if(report_attrs.begin(), report_attrs.end(), [](const auto &attr) + { + return attr.id == SAI_TAM_REPORT_ATTR_REPORT_INTERVAL_UNIT; + }); + ASSERT_NE(itr, report_attrs.end()); + EXPECT_EQ(itr->value.s32, SAI_TAM_REPORT_INTERVAL_UNIT_USEC); + } + + TEST_F(SaiAttrTest, DeployCounterSubscriptionUsesStatIdU32) + { + SaiAttrStub s; + s.init(); + + s.p->deployCounterSubscription( + SAI_OBJECT_TYPE_PORT, + 0x1000000000001ULL, + SAI_PORT_STAT_IF_IN_OCTETS, + 7); + + auto itr = find_if(counter_attrs.begin(), counter_attrs.end(), [](const auto &attr) + { + return attr.id == SAI_TAM_COUNTER_SUBSCRIPTION_ATTR_STAT_ID; + }); + ASSERT_NE(itr, counter_attrs.end()); + EXPECT_EQ(itr->value.u32, static_cast(SAI_PORT_STAT_IF_IN_OCTETS)); + } }