| /** |
| * @file test_MctpUtil.cpp |
| * @brief Unit tests for MctpUtil.cpp |
| * |
| * These tests verify the deferred state handling in MctpUtil when processing |
| * MCTP endpoint discovery events. Specifically, it tests that: |
| * 1. A single Add event populates the map. |
| * 2. A Remove event arriving while a query is in progress correctly cancels |
| * or rolls back the operation, leaving the map empty. |
| * 3. An Add -> Remove -> Add sequence in rapid succession correctly schedules |
| * a new query and leaves the map populated at the end. |
| * |
| * Synchronization Design: |
| * To avoid impacting production code, the tests use custom D-Bus signals |
| * emitted by the mock server (mock_mctpd.py) to know exactly when a query |
| * starts and completes. This allows precise orchestration of events without |
| * sleeps or production code hooks. |
| * |
| * The tests run in a single thread using `io.poll()` to match the production |
| * execution model. |
| */ |
| #include "MctpReactorDevice.hpp" |
| #include "MctpUtil.hpp" |
| #include "Utils.hpp" |
| |
| #include <sys/prctl.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| |
| #include <boost/asio/io_context.hpp> |
| #include <sdbusplus/asio/connection.hpp> |
| #include <sdbusplus/asio/object_server.hpp> |
| #include <sdbusplus/bus/match.hpp> |
| |
| #include <array> |
| #include <csignal> |
| #include <cstdlib> |
| #include <iostream> |
| #include <memory> |
| #include <string> |
| #include <thread> |
| #include <vector> |
| |
| #include <gtest/gtest.h> |
| |
| #define xstr(s) str(s) // NOLINT |
| #define str(s) #s // NOLINT |
| |
| static void runCmd(const std::string& cmd) |
| { |
| std::string finalCmd = cmd; |
| const char* dbusAddr = std::getenv("DBUS_SESSION_BUS_ADDRESS"); |
| if (dbusAddr != nullptr) |
| { |
| size_t pos = finalCmd.find("busctl "); |
| if (pos != std::string::npos) |
| { |
| finalCmd.replace(pos, 7, |
| "busctl --address=" + std::string(dbusAddr) + " "); |
| } |
| } |
| int rc = system(finalCmd.c_str()); // NOLINT(cert-env33-c) |
| ASSERT_EQ(rc, 0); |
| } |
| |
| class MctpUtilTest : public ::testing::Test |
| { |
| protected: |
| boost::asio::io_context io; |
| std::shared_ptr<sdbusplus::asio::connection> conn; |
| pid_t mockPid = -1; |
| |
| void SetUp() override |
| { |
| io.restart(); |
| conn = std::make_shared<sdbusplus::asio::connection>(io); |
| |
| // Clear global map before each test |
| mctpEndpointConfigMap.clear(); |
| std::cout << "--- Starting Python Mock ---\n"; |
| mockPid = fork(); |
| if (mockPid == 0) |
| { |
| // Child process |
| // Ask the kernel to automatically send SIGTERM to this process |
| // if the parent (the test suite) dies for any reason, including a |
| // segfault. |
| // clang-format off |
| prctl(PR_SET_PDEATHSIG, SIGTERM); // NOLINT(cppcoreguidelines-pro-type-vararg) |
| std::string scriptPath = TEST_SRC_DIR "/mock_mctpd.py"; |
| execlp("python3", "python3", scriptPath.c_str(), nullptr); // NOLINT(cppcoreguidelines-pro-type-vararg) |
| // clang-format on |
| exit(1); |
| } |
| // Wait for mock to claim names |
| bool ready = false; |
| for (int i = 0; i < 50; i++) |
| { |
| std::string cmd = |
| "busctl call org.freedesktop.DBus /org/freedesktop/DBus " |
| "org.freedesktop.DBus GetNameOwner s " |
| "au.com.codeconstruct.MCTP1 > /dev/null 2>&1"; |
| const char* dbusAddr = std::getenv("DBUS_SESSION_BUS_ADDRESS"); |
| if (dbusAddr != nullptr) |
| { |
| size_t pos = cmd.find("busctl "); |
| if (pos != std::string::npos) |
| { |
| cmd.replace(pos, 7, |
| "busctl --address=" + std::string(dbusAddr) + |
| " "); |
| } |
| } |
| int rc = system(cmd.c_str()); // NOLINT(cert-env33-c) |
| if (rc != -1 && WIFEXITED(rc) && WEXITSTATUS(rc) == 0) |
| { |
| ready = true; |
| break; |
| } |
| usleep(100000); // 100ms |
| } |
| |
| ASSERT_TRUE(ready) |
| << "Mock mctpd service au.com.codeconstruct.MCTP1 not ready!"; |
| } |
| |
| void TearDown() override |
| { |
| cleanupMctpEndpointListener(); |
| if (mockPid > 0) |
| { |
| kill(mockPid, SIGTERM); |
| waitpid(mockPid, nullptr, 0); |
| } |
| |
| // First, process any pending D-Bus replies (like GetSubTree) while conn |
| // is still externally referenced. This prevents conn from destructing |
| // inside sd_bus_process if a callback drops the last reference. |
| while (io.poll() > 0) |
| {} |
| |
| // Now safely destroy the connection |
| conn.reset(); |
| |
| // Drain any cancellation handlers (e.g. async_read) queued by conn's |
| // destructor |
| while (io.poll() > 0) |
| {} |
| } |
| |
| static bool checkPathInMapper(const std::string& path) |
| { |
| std::string cmd = "busctl tree xyz.openbmc_project.ObjectMapper"; |
| const char* dbusAddr = std::getenv("DBUS_SESSION_BUS_ADDRESS"); |
| if (dbusAddr != nullptr) |
| { |
| cmd = "busctl --address=" + std::string(dbusAddr) + |
| " tree xyz.openbmc_project.ObjectMapper"; |
| } |
| FILE* pipe = popen(cmd.c_str(), "r"); // NOLINT(cert-env33-c) |
| if (pipe == nullptr) |
| { |
| return false; |
| } |
| std::array<char, 128> buffer{}; |
| std::string result; |
| while (fgets(buffer.data(), buffer.size(), pipe) != nullptr) |
| { |
| result += buffer.data(); |
| } |
| pclose(pipe); |
| return result.find(path) != std::string::npos; |
| } |
| }; |
| |
| // 1. A single Add event will trigger only one O-M query. And add the correct |
| // result in mctpEndpointConfigMap |
| TEST_F(MctpUtilTest, SingleAdd_PopulatesMap) |
| { |
| std::string endpointPath = |
| "/au/com/codeconstruct/mctp1/networks/1/endpoints/test1"; |
| std::string emConfigPath = |
| "/xyz/openbmc_project/inventory/system/board/MockBoard/mctp_device"; |
| |
| // 2.a setupMctpEndpointListener() first in the test. |
| setupMctpEndpointListener(conn, MctpMessageType::NVME_MI); |
| |
| // 2.b setup the busctl event listener to all O-M event. |
| // We use a C++ match listener for all InterfacesAdded signals! |
| int queryCompletedCount = 0; |
| |
| const std::string queryCompletedSpec = |
| "type='signal',interface='com.example.Control',member='QueryCompleted'"; |
| auto queryCompletedMatch = std::make_unique<sdbusplus::bus::match_t>( |
| static_cast<sdbusplus::bus_t&>(*conn), queryCompletedSpec, |
| [&](sdbusplus::message_t&) { queryCompletedCount++; }); |
| |
| // Wait for priming loop to complete (finding nothing) |
| io.poll(); |
| usleep(100000); // 100ms |
| io.poll(); |
| |
| // Trigger Add event via D-Bus! |
| std::string addCmd = |
| "busctl call au.com.codeconstruct.MCTP1 /au/com/codeconstruct/mctp1 com.example.Control TriggerAdd ss \"" + |
| endpointPath + "\" \"" + emConfigPath + "\""; |
| runCmd(addCmd); |
| |
| // Wait for query to complete with a timeout (up to 5 seconds) |
| for (int i = 0; i < 50; i++) |
| { |
| io.poll(); |
| usleep(100000); // NOLINT(cert-api50-c) |
| if (queryCompletedCount >= 1) |
| { |
| break; |
| } |
| } |
| |
| // Wait a bit more to see if any extra signals arrive |
| usleep(500000); // 500ms |
| io.poll(); |
| |
| EXPECT_EQ(queryCompletedCount, 1); |
| |
| // Wait a bit more for MctpUtil to process the reply! |
| usleep(100000); // 100ms |
| |
| // Verify map populated |
| auto it = mctpEndpointConfigMap.find(endpointPath); |
| ASSERT_NE(it, mctpEndpointConfigMap.end()); |
| EXPECT_FALSE(it->second.empty()); |
| } |
| |
| // 2. Add -> Remove during query |
| TEST_F(MctpUtilTest, AddRemove_ClearsMap) |
| { |
| std::string endpointPath = |
| "/au/com/codeconstruct/mctp1/networks/1/endpoints/test2"; |
| std::string emConfigPath = |
| "/xyz/openbmc_project/inventory/system/board/MockBoard/mctp_device"; |
| |
| setupMctpEndpointListener(conn, MctpMessageType::NVME_MI); |
| |
| bool queryStarted = false; |
| int queryCompletedCount = 0; |
| |
| const std::string queryStartedSpec = |
| "type='signal',interface='com.example.Control',member='QueryStarted'"; |
| auto queryStartedMatch = std::make_unique<sdbusplus::bus::match_t>( |
| static_cast<sdbusplus::bus_t&>(*conn), queryStartedSpec, |
| [&](sdbusplus::message_t&) { queryStarted = true; }); |
| |
| const std::string queryCompletedSpec = |
| "type='signal',interface='com.example.Control',member='QueryCompleted'"; |
| auto queryCompletedMatch = std::make_unique<sdbusplus::bus::match_t>( |
| static_cast<sdbusplus::bus_t&>(*conn), queryCompletedSpec, |
| [&](sdbusplus::message_t&) { queryCompletedCount++; }); |
| |
| // Wait for priming loop to complete (finding nothing) |
| io.poll(); |
| usleep(100000); // 100ms |
| io.poll(); |
| |
| // Trigger Add event |
| std::string addCmd = |
| "busctl call au.com.codeconstruct.MCTP1 /au/com/codeconstruct/mctp1 com.example.Control TriggerAdd ss \"" + |
| endpointPath + "\" \"" + emConfigPath + "\""; |
| runCmd(addCmd); |
| |
| // Wait for query to start |
| while (!queryStarted) |
| { |
| io.poll(); |
| usleep(10000); // 10ms |
| } |
| |
| // IMMEDIATELY trigger Remove event (while query is in progress!) |
| std::string removeCmd = |
| "busctl call au.com.codeconstruct.MCTP1 /au/com/codeconstruct/mctp1 com.example.Control TriggerRemove s \"" + |
| endpointPath + "\""; |
| runCmd(removeCmd); |
| |
| // Wait for query to complete with a timeout (up to 5 seconds) |
| for (int i = 0; i < 50; i++) |
| { |
| io.poll(); |
| usleep(100000); // NOLINT(cert-api50-c) |
| if (queryCompletedCount >= 1) |
| { |
| break; |
| } |
| } |
| |
| // Wait a bit more to see if any extra signals arrive |
| usleep(500000); // 500ms |
| io.poll(); |
| |
| EXPECT_EQ(queryCompletedCount, 1); |
| |
| // Verify map is empty |
| auto it = mctpEndpointConfigMap.find(endpointPath); |
| EXPECT_TRUE(it == mctpEndpointConfigMap.end()); |
| } |
| |
| // 3. Add -> Remove -> Add during query |
| TEST_F(MctpUtilTest, AddRemoveAdd_PopulatesMap) |
| { |
| std::string endpointPath = |
| "/au/com/codeconstruct/mctp1/networks/1/endpoints/test3"; |
| std::string emConfigPath = |
| "/xyz/openbmc_project/inventory/system/board/MockBoard/mctp_device"; |
| |
| setupMctpEndpointListener(conn, MctpMessageType::NVME_MI); |
| |
| bool queryStarted = false; |
| int queryCompletedCount = 0; |
| |
| const std::string queryStartedSpec = |
| "type='signal',interface='com.example.Control',member='QueryStarted'"; |
| auto queryStartedMatch = std::make_unique<sdbusplus::bus::match_t>( |
| static_cast<sdbusplus::bus_t&>(*conn), queryStartedSpec, |
| [&](sdbusplus::message_t&) { queryStarted = true; }); |
| |
| const std::string queryCompletedSpec = |
| "type='signal',interface='com.example.Control',member='QueryCompleted'"; |
| auto queryCompletedMatch = std::make_unique<sdbusplus::bus::match_t>( |
| static_cast<sdbusplus::bus_t&>(*conn), queryCompletedSpec, |
| [&](sdbusplus::message_t&) { queryCompletedCount++; }); |
| |
| // Wait for priming loop to complete (finding nothing) |
| io.poll(); |
| usleep(100000); // 100ms |
| io.poll(); |
| |
| // Trigger Add event |
| std::string addCmd = |
| "busctl call au.com.codeconstruct.MCTP1 /au/com/codeconstruct/mctp1 com.example.Control TriggerAdd ss \"" + |
| endpointPath + "\" \"" + emConfigPath + "\""; |
| runCmd(addCmd); |
| |
| // Wait for query to start |
| while (!queryStarted) |
| { |
| io.poll(); |
| usleep(10000); // 10ms |
| } |
| |
| // IMMEDIATELY trigger Remove event |
| std::string removeCmd = |
| "busctl call au.com.codeconstruct.MCTP1 /au/com/codeconstruct/mctp1 com.example.Control TriggerRemove s \"" + |
| endpointPath + "\""; |
| runCmd(removeCmd); |
| |
| // IMMEDIATELY trigger Add event again! |
| runCmd(addCmd); |
| |
| // Wait for queries to complete with a timeout (up to 5 seconds) |
| for (int i = 0; i < 70; i++) |
| { |
| io.poll(); |
| usleep(100000); // NOLINT(cert-api50-c) |
| if (queryCompletedCount >= 2) |
| { |
| break; |
| } |
| } |
| |
| // Wait a bit more to see if any extra signals arrive |
| usleep(500000); // 500ms |
| io.poll(); |
| |
| EXPECT_EQ(queryCompletedCount, 2); |
| |
| // Verify map is populated at the end |
| auto it = mctpEndpointConfigMap.find(endpointPath); |
| ASSERT_NE(it, mctpEndpointConfigMap.end()); |
| EXPECT_FALSE(it->second.empty()); |
| } |
| |
| // 4. Race between priming query and new event |
| TEST_F(MctpUtilTest, PrimeQueryRace_TriggersDeferredQuery) |
| { |
| std::string endpointPath = |
| "/au/com/codeconstruct/mctp1/networks/1/endpoints/test4"; |
| std::string emConfigPath = |
| "/xyz/openbmc_project/inventory/system/board/MockBoard/mctp_device"; |
| |
| // Pre-populate association by triggering it BEFORE listener starts! |
| auto m = conn->new_method_call("au.com.codeconstruct.MCTP1", |
| "/au/com/codeconstruct/mctp1", |
| "com.example.Control", "TriggerAdd"); |
| m.append(endpointPath, emConfigPath); |
| conn->call(m); |
| |
| // Wait for mapperx to see it |
| bool found = false; |
| for (int i = 0; i < 300; i++) |
| { |
| if (checkPathInMapper(endpointPath + "/configured_by")) |
| { |
| found = true; |
| break; |
| } |
| usleep(100000); // 100ms |
| } |
| ASSERT_TRUE(found); |
| |
| // Now setup listener (this starts priming!) |
| setupMctpEndpointListener(conn, MctpMessageType::NVME_MI); |
| |
| bool queryStarted = false; |
| int queryCompletedCount = 0; |
| |
| const std::string queryStartedSpec = |
| "type='signal',interface='com.example.Control',member='QueryStarted'"; |
| auto queryStartedMatch = std::make_unique<sdbusplus::bus::match_t>( |
| static_cast<sdbusplus::bus_t&>(*conn), queryStartedSpec, |
| [&](sdbusplus::message_t&) { |
| std::cout << "[TEST] QueryStarted received\n"; |
| queryStarted = true; |
| }); |
| |
| const std::string queryCompletedSpec = |
| "type='signal',interface='com.example.Control',member='QueryCompleted'"; |
| auto queryCompletedMatch = std::make_unique<sdbusplus::bus::match_t>( |
| static_cast<sdbusplus::bus_t&>(*conn), queryCompletedSpec, |
| [&](sdbusplus::message_t&) { |
| std::cout << "[TEST] QueryCompleted received\n"; |
| queryCompletedCount++; |
| }); |
| |
| // Wait for query to start (triggered by priming!) |
| while (!queryStarted) |
| { |
| io.poll(); |
| usleep(10000); // 10ms |
| } |
| |
| std::string endpointPath5 = |
| "/au/com/codeconstruct/mctp1/networks/1/endpoints/test5"; |
| std::cout << "[DEBUG] Triggering Add for test5 (racing!)\n"; |
| // Trigger Add event for a DIFFERENT endpoint while priming query is in |
| // progress! |
| conn->async_method_call( |
| [](const boost::system::error_code& ec, const std::string&) { |
| if (ec) |
| { |
| std::cout << "[TEST] TriggerAdd failed: " << ec.message() << "\n"; |
| } |
| }, "au.com.codeconstruct.MCTP1", "/au/com/codeconstruct/mctp1", |
| "com.example.Control", "TriggerAdd", endpointPath5, emConfigPath); |
| |
| // Wait for queries to complete (we expect 2!) |
| for (int i = 0; i < 100; i++) |
| { |
| while (io.poll() > 0) |
| {} // Drain all ready handlers! |
| usleep(100000); // 100ms |
| if (queryCompletedCount >= 2) |
| { |
| break; |
| } |
| } |
| |
| // Wait a bit more to see if any extra signals arrive |
| usleep(500000); // 500ms |
| io.poll(); |
| |
| EXPECT_EQ(queryCompletedCount, 2); |
| |
| // Verify map populated for both endpoints! |
| EXPECT_EQ(mctpEndpointConfigMap.size(), 2); |
| |
| auto it4 = mctpEndpointConfigMap.find(endpointPath); |
| ASSERT_NE(it4, mctpEndpointConfigMap.end()); |
| EXPECT_FALSE(it4->second.empty()); |
| |
| auto it5 = mctpEndpointConfigMap.find(endpointPath5); |
| ASSERT_NE(it5, mctpEndpointConfigMap.end()); |
| EXPECT_FALSE(it5->second.empty()); |
| } |
| |
| // 5. During Priming, Remove on the single endpoint |
| TEST_F(MctpUtilTest, PrimeQueryRace_RemovePostponed) |
| { |
| std::string endpointPath = |
| "/au/com/codeconstruct/mctp1/networks/1/endpoints/test5"; |
| std::string emConfigPath = |
| "/xyz/openbmc_project/inventory/system/board/MockBoard/mctp_device"; |
| |
| // Pre-populate association by triggering it BEFORE listener starts! |
| auto m = conn->new_method_call("au.com.codeconstruct.MCTP1", |
| "/au/com/codeconstruct/mctp1", |
| "com.example.Control", "TriggerAdd"); |
| m.append(endpointPath, emConfigPath); |
| conn->call(m); |
| |
| // Wait for mapperx to see it |
| bool found = false; |
| for (int i = 0; i < 300; i++) |
| { |
| if (checkPathInMapper(endpointPath + "/configured_by")) |
| { |
| found = true; |
| break; |
| } |
| usleep(100000); // 100ms |
| } |
| ASSERT_TRUE(found); |
| |
| // Now setup listener (this starts priming!) |
| setupMctpEndpointListener(conn, MctpMessageType::NVME_MI); |
| |
| bool queryStarted = false; |
| int queryCompletedCount = 0; |
| |
| const std::string queryStartedSpec = |
| "type='signal',interface='com.example.Control',member='QueryStarted'"; |
| auto queryStartedMatch = std::make_unique<sdbusplus::bus::match_t>( |
| static_cast<sdbusplus::bus_t&>(*conn), queryStartedSpec, |
| [&](sdbusplus::message_t&) { |
| std::cout << "[TEST] QueryStarted received\n"; |
| queryStarted = true; |
| }); |
| |
| const std::string queryCompletedSpec = |
| "type='signal',interface='com.example.Control',member='QueryCompleted'"; |
| auto queryCompletedMatch = std::make_unique<sdbusplus::bus::match_t>( |
| static_cast<sdbusplus::bus_t&>(*conn), queryCompletedSpec, |
| [&](sdbusplus::message_t&) { |
| std::cout << "[TEST] QueryCompleted received\n"; |
| queryCompletedCount++; |
| }); |
| |
| // Wait for query to start (triggered by priming!) |
| while (!queryStarted) |
| { |
| io.poll(); |
| usleep(10000); // 10ms |
| } |
| |
| std::cout << "[DEBUG] Triggering Remove (racing!)\n"; |
| // Trigger Remove event while priming query is in progress! |
| conn->async_method_call( |
| [](const boost::system::error_code& ec, const std::string&) { |
| if (ec) |
| { |
| std::cout << "[TEST] TriggerRemove failed: " << ec.message() |
| << "\n"; |
| } |
| }, "au.com.codeconstruct.MCTP1", "/au/com/codeconstruct/mctp1", |
| "com.example.Control", "TriggerRemove", endpointPath); |
| |
| // Wait for queries to complete (we expect 1!) |
| for (int i = 0; i < 100; i++) |
| { |
| while (io.poll() > 0) |
| {} // Drain all ready handlers! |
| usleep(100000); // 100ms |
| if (queryCompletedCount >= 1) |
| { |
| break; |
| } |
| } |
| |
| EXPECT_EQ(queryCompletedCount, 1); |
| |
| // Verify map is EMPTY (because it was removed!) |
| EXPECT_EQ(mctpEndpointConfigMap.size(), 0); |
| |
| auto it = mctpEndpointConfigMap.find(endpointPath); |
| EXPECT_EQ(it, mctpEndpointConfigMap.end()); |
| } |
| |
| // 6. During Priming, Remove -> Add on the single endpoint |
| TEST_F(MctpUtilTest, PrimeQueryRace_RemoveAddPostponed) |
| { |
| std::string endpointPath = |
| "/au/com/codeconstruct/mctp1/networks/1/endpoints/test6"; |
| std::string emConfigPath = |
| "/xyz/openbmc_project/inventory/system/board/MockBoard/mctp_device"; |
| |
| // Pre-populate association by triggering it BEFORE listener starts! |
| auto m = conn->new_method_call("au.com.codeconstruct.MCTP1", |
| "/au/com/codeconstruct/mctp1", |
| "com.example.Control", "TriggerAdd"); |
| m.append(endpointPath, emConfigPath); |
| conn->call(m); |
| |
| // Wait for mapperx to see it |
| bool found = false; |
| for (int i = 0; i < 300; i++) |
| { |
| if (checkPathInMapper(endpointPath + "/configured_by")) |
| { |
| found = true; |
| break; |
| } |
| usleep(100000); // 100ms |
| } |
| ASSERT_TRUE(found); |
| |
| // Now setup listener (this starts priming!) |
| setupMctpEndpointListener(conn, MctpMessageType::NVME_MI); |
| |
| bool queryStarted = false; |
| int queryCompletedCount = 0; |
| |
| const std::string queryStartedSpec = |
| "type='signal',interface='com.example.Control',member='QueryStarted'"; |
| auto queryStartedMatch = std::make_unique<sdbusplus::bus::match_t>( |
| static_cast<sdbusplus::bus_t&>(*conn), queryStartedSpec, |
| [&](sdbusplus::message_t&) { |
| std::cout << "[TEST] QueryStarted received\n"; |
| queryStarted = true; |
| }); |
| |
| const std::string queryCompletedSpec = |
| "type='signal',interface='com.example.Control',member='QueryCompleted'"; |
| auto queryCompletedMatch = std::make_unique<sdbusplus::bus::match_t>( |
| static_cast<sdbusplus::bus_t&>(*conn), queryCompletedSpec, |
| [&](sdbusplus::message_t&) { |
| std::cout << "[TEST] QueryCompleted received\n"; |
| queryCompletedCount++; |
| }); |
| |
| // Wait for query to start (triggered by priming!) |
| while (!queryStarted) |
| { |
| io.poll(); |
| usleep(10000); // 10ms |
| } |
| |
| std::cout << "[DEBUG] Triggering Remove (racing!)\n"; |
| // Trigger Remove event! |
| conn->async_method_call( |
| [](const boost::system::error_code& ec, const std::string&) { |
| if (ec) |
| { |
| std::cout << "[TEST] TriggerRemove failed: " << ec.message() |
| << "\n"; |
| } |
| }, "au.com.codeconstruct.MCTP1", "/au/com/codeconstruct/mctp1", |
| "com.example.Control", "TriggerRemove", endpointPath); |
| |
| std::cout << "[DEBUG] Triggering Add again (racing!)\n"; |
| // Trigger Add event AGAIN! |
| conn->async_method_call( |
| [](const boost::system::error_code& ec, const std::string&) { |
| if (ec) |
| { |
| std::cout << "[TEST] TriggerAdd failed: " << ec.message() << "\n"; |
| } |
| }, "au.com.codeconstruct.MCTP1", "/au/com/codeconstruct/mctp1", |
| "com.example.Control", "TriggerAdd", endpointPath, emConfigPath); |
| |
| // Wait for queries to complete (we expect 2!) |
| for (int i = 0; i < 100; i++) |
| { |
| while (io.poll() > 0) |
| {} // Drain all ready handlers! |
| usleep(100000); // 100ms |
| if (queryCompletedCount >= 2) |
| { |
| break; |
| } |
| } |
| |
| EXPECT_EQ(queryCompletedCount, 2); |
| |
| // Verify map populated |
| EXPECT_EQ(mctpEndpointConfigMap.size(), 1); |
| |
| auto it = mctpEndpointConfigMap.find(endpointPath); |
| ASSERT_NE(it, mctpEndpointConfigMap.end()); |
| EXPECT_FALSE(it->second.empty()); |
| } |
| |
| // 7. Add delay for a single ep Add to Add event. i.e. Add -> Remove -> Add |
| // (this add should be cooled down). |
| TEST_F(MctpUtilTest, Cooldown_AddRemoveAdd_IsCooledDown) |
| { |
| std::string endpointPath = |
| "/au/com/codeconstruct/mctp1/networks/1/endpoints/test7"; |
| std::string emConfigPath = |
| "/xyz/openbmc_project/inventory/system/board/MockBoard/mctp_device"; |
| |
| setupMctpEndpointListener(conn, MctpMessageType::NVME_MI); |
| |
| bool queryStarted = false; |
| int queryCompletedCount = 0; |
| |
| const std::string queryStartedSpec = |
| "type='signal',interface='com.example.Control',member='QueryStarted'"; |
| auto queryStartedMatch = std::make_unique<sdbusplus::bus::match_t>( |
| static_cast<sdbusplus::bus_t&>(*conn), queryStartedSpec, |
| [&](sdbusplus::message_t&) { |
| std::cout << "[TEST] QueryStarted received" << '\n'; |
| queryStarted = true; |
| }); |
| |
| const std::string queryCompletedSpec = |
| "type='signal',interface='com.example.Control',member='QueryCompleted'"; |
| auto queryCompletedMatch = std::make_unique<sdbusplus::bus::match_t>( |
| static_cast<sdbusplus::bus_t&>(*conn), queryCompletedSpec, |
| [&](sdbusplus::message_t&) { |
| std::cout << "[TEST] QueryCompleted received" << '\n'; |
| queryCompletedCount++; |
| }); |
| |
| // Trigger FIRST Add! |
| conn->async_method_call( |
| [](const boost::system::error_code& ec, const std::string&) { |
| if (ec) |
| { |
| std::cout << "[TEST] TriggerAdd failed: " << ec.message() << '\n'; |
| } |
| }, "au.com.codeconstruct.MCTP1", "/au/com/codeconstruct/mctp1", |
| "com.example.Control", "TriggerAdd", endpointPath, emConfigPath); |
| |
| // Wait for FIRST query to complete! |
| for (int i = 0; i < 50; i++) |
| { |
| while (io.poll() > 0) |
| {} // Drain all ready handlers! |
| usleep(100000); // 100ms |
| if (queryCompletedCount >= 1) |
| { |
| break; |
| } |
| } |
| ASSERT_EQ(queryCompletedCount, 1); |
| |
| std::cout << "[DEBUG] Triggering Remove and Add (racing!)" << '\n'; |
| // Trigger Remove! |
| conn->async_method_call( |
| [](const boost::system::error_code& ec, const std::string&) { |
| if (ec) |
| { |
| std::cout << "[TEST] TriggerRemove failed: " << ec.message() |
| << '\n'; |
| } |
| }, "au.com.codeconstruct.MCTP1", "/au/com/codeconstruct/mctp1", |
| "com.example.Control", "TriggerRemove", endpointPath); |
| |
| // Trigger SECOND Add! (Immediately after!). |
| conn->async_method_call( |
| [](const boost::system::error_code& ec, const std::string&) { |
| if (ec) |
| { |
| std::cout << "[TEST] TriggerAdd failed: " << ec.message() << '\n'; |
| } |
| }, "au.com.codeconstruct.MCTP1", "/au/com/codeconstruct/mctp1", |
| "com.example.Control", "TriggerAdd", endpointPath, emConfigPath); |
| |
| // Wait for SECOND query to complete! |
| // We expect it to be DELAYED by 5 seconds! |
| auto startTime = std::chrono::steady_clock::now(); |
| for (int i = 0; i < 100; i++) |
| { |
| while (io.poll() > 0) |
| {} // Drain all ready handlers! |
| usleep(100000); // 100ms |
| if (queryCompletedCount >= 2) |
| { |
| break; |
| } |
| } |
| auto endTime = std::chrono::steady_clock::now(); |
| auto duration = |
| std::chrono::duration_cast<std::chrono::seconds>(endTime - startTime) |
| .count(); |
| |
| EXPECT_EQ(queryCompletedCount, 2); |
| EXPECT_GE(duration, 4); // Expect at least 4-5 seconds delay! |
| |
| // Verify map populated |
| auto it = mctpEndpointConfigMap.find(endpointPath); |
| ASSERT_NE(it, mctpEndpointConfigMap.end()); |
| } |
| |
| // 8. Priming scan does block its immediate following Add. i.e. Priming -> |
| // Remove -> Add (not cooldown) |
| TEST_F(MctpUtilTest, Cooldown_PrimingRemoveAdd_IsNormalized) |
| { |
| std::string endpointPath = |
| "/au/com/codeconstruct/mctp1/networks/1/endpoints/test8"; |
| std::string emConfigPath = |
| "/xyz/openbmc_project/inventory/system/board/MockBoard/mctp_device"; |
| |
| // Pre-populate association by triggering it BEFORE listener starts! |
| auto m = conn->new_method_call("au.com.codeconstruct.MCTP1", |
| "/au/com/codeconstruct/mctp1", |
| "com.example.Control", "TriggerAdd"); |
| m.append(endpointPath, emConfigPath); |
| conn->call(m); |
| |
| // Wait for mapperx to see it |
| bool found = false; |
| for (int i = 0; i < 300; i++) |
| { |
| if (checkPathInMapper(endpointPath + "/configured_by")) |
| { |
| found = true; |
| break; |
| } |
| usleep(100000); // 100ms |
| } |
| ASSERT_TRUE(found); |
| |
| // Now setup listener (this starts priming!) |
| setupMctpEndpointListener(conn, MctpMessageType::NVME_MI); |
| |
| bool queryStarted = false; |
| int queryCompletedCount = 0; |
| |
| const std::string queryStartedSpec = |
| "type='signal',interface='com.example.Control',member='QueryStarted'"; |
| auto queryStartedMatch = std::make_unique<sdbusplus::bus::match_t>( |
| static_cast<sdbusplus::bus_t&>(*conn), queryStartedSpec, |
| [&](sdbusplus::message_t&) { |
| std::cout << "[TEST] QueryStarted received" << '\n'; |
| queryStarted = true; |
| }); |
| |
| const std::string queryCompletedSpec = |
| "type='signal',interface='com.example.Control',member='QueryCompleted'"; |
| auto queryCompletedMatch = std::make_unique<sdbusplus::bus::match_t>( |
| static_cast<sdbusplus::bus_t&>(*conn), queryCompletedSpec, |
| [&](sdbusplus::message_t&) { |
| std::cout << "[TEST] QueryCompleted received" << '\n'; |
| queryCompletedCount++; |
| }); |
| |
| // Wait for query to start (triggered by priming!) |
| while (!queryStarted) |
| { |
| io.poll(); |
| usleep(10000); // 10ms |
| } |
| |
| std::cout << "[DEBUG] Triggering Remove and Add (racing with priming!)" |
| << '\n'; |
| // Trigger Remove event! |
| conn->async_method_call( |
| [](const boost::system::error_code& ec, const std::string&) { |
| if (ec) |
| { |
| std::cout << "[TEST] TriggerRemove failed: " << ec.message() |
| << '\n'; |
| } |
| }, "au.com.codeconstruct.MCTP1", "/au/com/codeconstruct/mctp1", |
| "com.example.Control", "TriggerRemove", endpointPath); |
| |
| // Trigger Add event! |
| conn->async_method_call( |
| [](const boost::system::error_code& ec, const std::string&) { |
| if (ec) |
| { |
| std::cout << "[TEST] TriggerAdd failed: " << ec.message() << '\n'; |
| } |
| }, "au.com.codeconstruct.MCTP1", "/au/com/codeconstruct/mctp1", |
| "com.example.Control", "TriggerAdd", endpointPath, emConfigPath); |
| |
| // Wait for queries to complete! |
| auto startTime = std::chrono::steady_clock::now(); |
| for (int i = 0; i < 100; i++) |
| { |
| while (io.poll() > 0) |
| {} // Drain all ready handlers! |
| usleep(100000); // 100ms |
| if (queryCompletedCount >= 2) |
| { |
| break; |
| } |
| } |
| auto endTime = std::chrono::steady_clock::now(); |
| auto duration = |
| std::chrono::duration_cast<std::chrono::seconds>(endTime - startTime) |
| .count(); |
| |
| EXPECT_EQ(queryCompletedCount, 2); |
| EXPECT_LT(duration, |
| 8); // Expect it to complete without additional 5s cooldown! |
| |
| // Verify map populated |
| auto it = mctpEndpointConfigMap.find(endpointPath); |
| ASSERT_NE(it, mctpEndpointConfigMap.end()); |
| } |
| |
| // 9. Remove has no cooldown (i.e. Remove -> Add -> Remove (no cooldown). |
| TEST_F(MctpUtilTest, Cooldown_RemoveAddRemove_NoCooldown) |
| { |
| std::string endpointPath = |
| "/au/com/codeconstruct/mctp1/networks/1/endpoints/test9"; |
| std::string emConfigPath = |
| "/xyz/openbmc_project/inventory/system/board/MockBoard/mctp_device"; |
| |
| setupMctpEndpointListener(conn, MctpMessageType::NVME_MI); |
| |
| bool queryStarted = false; |
| int queryCompletedCount = 0; |
| |
| const std::string queryStartedSpec = |
| "type='signal',interface='com.example.Control',member='QueryStarted'"; |
| auto queryStartedMatch = std::make_unique<sdbusplus::bus::match_t>( |
| static_cast<sdbusplus::bus_t&>(*conn), queryStartedSpec, |
| [&](sdbusplus::message_t&) { |
| std::cout << "[TEST] QueryStarted received" << '\n'; |
| queryStarted = true; |
| }); |
| |
| const std::string queryCompletedSpec = |
| "type='signal',interface='com.example.Control',member='QueryCompleted'"; |
| auto queryCompletedMatch = std::make_unique<sdbusplus::bus::match_t>( |
| static_cast<sdbusplus::bus_t&>(*conn), queryCompletedSpec, |
| [&](sdbusplus::message_t&) { |
| std::cout << "[TEST] QueryCompleted received" << '\n'; |
| queryCompletedCount++; |
| }); |
| |
| // Trigger FIRST Add! |
| conn->async_method_call( |
| [](const boost::system::error_code& ec, const std::string&) { |
| if (ec) |
| { |
| std::cout << "[TEST] TriggerAdd failed: " << ec.message() << '\n'; |
| } |
| }, "au.com.codeconstruct.MCTP1", "/au/com/codeconstruct/mctp1", |
| "com.example.Control", "TriggerAdd", endpointPath, emConfigPath); |
| |
| // Wait for FIRST query to complete! |
| for (int i = 0; i < 50; i++) |
| { |
| while (io.poll() > 0) |
| {} // Drain all ready handlers! |
| usleep(100000); // 100ms |
| if (queryCompletedCount >= 1) |
| { |
| break; |
| } |
| } |
| ASSERT_EQ(queryCompletedCount, 1); |
| |
| std::cout << "[DEBUG] Triggering Remove -> Add -> Remove" << '\n'; |
| // Trigger Remove! |
| conn->async_method_call( |
| [](const boost::system::error_code& ec, const std::string&) { |
| if (ec) |
| { |
| std::cout << "[TEST] TriggerRemove failed: " << ec.message() |
| << '\n'; |
| } |
| }, "au.com.codeconstruct.MCTP1", "/au/com/codeconstruct/mctp1", |
| "com.example.Control", "TriggerRemove", endpointPath); |
| |
| // Trigger Add! |
| conn->async_method_call( |
| [](const boost::system::error_code& ec, const std::string&) { |
| if (ec) |
| { |
| std::cout << "[TEST] TriggerAdd failed: " << ec.message() << '\n'; |
| } |
| }, "au.com.codeconstruct.MCTP1", "/au/com/codeconstruct/mctp1", |
| "com.example.Control", "TriggerAdd", endpointPath, emConfigPath); |
| |
| // Trigger Remove! |
| conn->async_method_call( |
| [](const boost::system::error_code& ec, const std::string&) { |
| if (ec) |
| { |
| std::cout << "[TEST] TriggerRemove failed: " << ec.message() |
| << '\n'; |
| } |
| }, "au.com.codeconstruct.MCTP1", "/au/com/codeconstruct/mctp1", |
| "com.example.Control", "TriggerRemove", endpointPath); |
| |
| // Wait for queries to complete! |
| for (int i = 0; i < 100; i++) |
| { |
| while (io.poll() > 0) |
| {} // Drain all ready handlers! |
| usleep(100000); // 100ms |
| } |
| |
| EXPECT_EQ(queryCompletedCount, 1); |
| |
| // Verify map is EMPTY! |
| auto it = mctpEndpointConfigMap.find(endpointPath); |
| EXPECT_EQ(it, mctpEndpointConfigMap.end()); |
| } |
| |
| // 10. Filter unsupported message types |
| TEST_F(MctpUtilTest, Filter_UnsupportedMessageType_IsIgnored) |
| { |
| std::string endpointPathSupported = |
| "/au/com/codeconstruct/mctp1/networks/1/endpoints/test10_supported"; |
| std::string endpointPathUnsupported = |
| "/au/com/codeconstruct/mctp1/networks/1/endpoints/" |
| "test10_unsupported"; |
| std::string emConfigPath = |
| "/xyz/openbmc_project/inventory/system/board/MockBoard/mctp_device"; |
| |
| setupMctpEndpointListener(conn, MctpMessageType::NVME_MI); |
| |
| int queryCompletedCount = 0; |
| const std::string queryCompletedSpec = |
| "type='signal',interface='com.example.Control',member='QueryCompleted'"; |
| auto queryCompletedMatch = std::make_unique<sdbusplus::bus::match_t>( |
| static_cast<sdbusplus::bus_t&>(*conn), queryCompletedSpec, |
| [&](sdbusplus::message_t&) { queryCompletedCount++; }); |
| |
| // 1. Trigger Add for SUPPORTED endpoint (type 4 included) |
| std::vector<uint8_t> msgTypesSupported = {2, 0, 4}; |
| auto m1 = conn->new_method_call("au.com.codeconstruct.MCTP1", |
| "/au/com/codeconstruct/mctp1", |
| "com.example.Control", "TriggerAddCustom"); |
| m1.append(endpointPathSupported, emConfigPath, msgTypesSupported); |
| conn->call(m1); |
| |
| // 2. Trigger Add for UNSUPPORTED endpoint (no type 4) |
| std::vector<uint8_t> msgTypesUnsupported = {2}; |
| auto m2 = conn->new_method_call("au.com.codeconstruct.MCTP1", |
| "/au/com/codeconstruct/mctp1", |
| "com.example.Control", "TriggerAddCustom"); |
| m2.append(endpointPathUnsupported, emConfigPath, msgTypesUnsupported); |
| conn->call(m2); |
| |
| // Wait for queries to complete (we expect exactly 1 to complete!) |
| for (int i = 0; i < 50; i++) |
| { |
| while (io.poll() > 0) |
| {} // Drain all ready handlers! |
| usleep(100000); // 100ms |
| if (queryCompletedCount >= 1) |
| { |
| break; |
| } |
| } |
| |
| EXPECT_EQ(queryCompletedCount, 1); |
| |
| // Verify map size is 1 |
| EXPECT_EQ(mctpEndpointConfigMap.size(), 1); |
| |
| // Verify supported endpoint is present |
| auto itSupported = mctpEndpointConfigMap.find(endpointPathSupported); |
| EXPECT_NE(itSupported, mctpEndpointConfigMap.end()); |
| |
| // Verify unsupported endpoint is NOT present |
| auto itUnsupported = mctpEndpointConfigMap.find(endpointPathUnsupported); |
| EXPECT_EQ(itUnsupported, mctpEndpointConfigMap.end()); |
| } |
| |
| // 11. MctpReactorDevice creation BEFORE mctpEndpointConfigMap population |
| // (Deferred Setup) |
| TEST_F(MctpUtilTest, MctpReactorDevice_DeferredSetup_USB) |
| { |
| std::string endpointPath = |
| "/au/com/codeconstruct/mctp1/networks/1/endpoints/11"; |
| std::string emConfigPath = |
| "/xyz/openbmc_project/inventory/system/board/MockBoard/mctp_device"; |
| |
| setupMctpEndpointListener(conn, MctpMessageType::NVME_MI); |
| |
| // Create BusInfo for target device (matching what mock_mctpd.py returns for |
| // USB) |
| BusInfo targetBusInfo; |
| targetBusInfo["BusType"] = "USB"; |
| targetBusInfo["Port"] = "1.5.1"; |
| targetBusInfo["Configuration"] = "1"; |
| targetBusInfo["InterfaceNum"] = "4"; |
| targetBusInfo["RootHubPath"] = "/sys/devices/platform/ahb/f0806000.usb"; |
| |
| bool setupCompleted = false; |
| |
| // Create MctpReactorDevice |
| auto mctpDev = std::make_shared<MctpReactorDevice>( |
| conn, emConfigPath, std::vector<BusInfo>{targetBusInfo}, std::nullopt, |
| std::nullopt); |
| mctpDev->setup([&](const std::error_code& ec, |
| const std::shared_ptr<MctpEndpoint>& ep) { |
| setupCompleted = true; |
| EXPECT_FALSE(ec); |
| EXPECT_NE(ep, nullptr); |
| }); |
| |
| // Verify it didn't complete immediately |
| io.poll(); |
| EXPECT_FALSE(setupCompleted); |
| |
| // Trigger Add event via D-Bus! |
| std::string addCmd = |
| "busctl call au.com.codeconstruct.MCTP1 /au/com/codeconstruct/mctp1 com.example.Control TriggerAdd ss \"" + |
| endpointPath + "\" \"" + emConfigPath + "\""; |
| runCmd(addCmd); |
| |
| // Wait for setup to complete (up to 5 seconds) |
| for (int i = 0; i < 50; i++) |
| { |
| io.poll(); |
| usleep(100000); // 100ms |
| if (setupCompleted) |
| { |
| break; |
| } |
| } |
| |
| EXPECT_TRUE(setupCompleted); |
| } |
| |
| // 12. MctpReactorDevice creation AFTER mctpEndpointConfigMap population |
| // (Immediate Setup) |
| TEST_F(MctpUtilTest, MctpReactorDevice_ImmediateSetup_USB) |
| { |
| std::string endpointPath = |
| "/au/com/codeconstruct/mctp1/networks/1/endpoints/12"; |
| std::string emConfigPath = |
| "/xyz/openbmc_project/inventory/system/board/MockBoard/mctp_device"; |
| |
| setupMctpEndpointListener(conn, MctpMessageType::NVME_MI); |
| |
| // Trigger Add event FIRST! |
| std::string addCmd = |
| "busctl call au.com.codeconstruct.MCTP1 /au/com/codeconstruct/mctp1 com.example.Control TriggerAdd ss \"" + |
| endpointPath + "\" \"" + emConfigPath + "\""; |
| runCmd(addCmd); |
| |
| // Wait for map to be populated (up to 5 seconds) |
| bool populated = false; |
| for (int i = 0; i < 50; i++) |
| { |
| io.poll(); |
| usleep(100000); // 100ms |
| if (mctpEndpointConfigMap.find(endpointPath) != |
| mctpEndpointConfigMap.end()) |
| { |
| populated = true; |
| break; |
| } |
| } |
| ASSERT_TRUE(populated); |
| |
| // Create BusInfo for target device |
| BusInfo targetBusInfo; |
| targetBusInfo["BusType"] = "USB"; |
| targetBusInfo["Port"] = "1.5.1"; |
| targetBusInfo["Configuration"] = "1"; |
| targetBusInfo["InterfaceNum"] = "4"; |
| targetBusInfo["RootHubPath"] = "/sys/devices/platform/ahb/f0806000.usb"; |
| |
| bool setupCompleted = false; |
| |
| // Create MctpReactorDevice |
| auto mctpDev = std::make_shared<MctpReactorDevice>( |
| conn, emConfigPath, std::vector<BusInfo>{targetBusInfo}, std::nullopt, |
| std::nullopt); |
| mctpDev->setup([&](const std::error_code& ec, |
| const std::shared_ptr<MctpEndpoint>& ep) { |
| setupCompleted = true; |
| EXPECT_FALSE(ec); |
| EXPECT_NE(ep, nullptr); |
| }); |
| |
| // Verify it completes IMMEDIATELY |
| io.poll(); |
| EXPECT_TRUE(setupCompleted); |
| } |
| |
| // 13. MctpReactorDevice removal notification (Case C) |
| TEST_F(MctpUtilTest, MctpReactorDevice_RemovalNotification_USB) |
| { |
| std::string endpointPath = |
| "/au/com/codeconstruct/mctp1/networks/1/endpoints/13"; |
| std::string emConfigPath = |
| "/xyz/openbmc_project/inventory/system/board/MockBoard/mctp_device"; |
| |
| setupMctpEndpointListener(conn, MctpMessageType::NVME_MI); |
| usleep(100000); // 100ms for match rules to register |
| |
| // Trigger Add event FIRST! |
| std::string addCmd = |
| "busctl call au.com.codeconstruct.MCTP1 /au/com/codeconstruct/mctp1 com.example.Control TriggerAdd ss \"" + |
| endpointPath + "\" \"" + emConfigPath + "\""; |
| runCmd(addCmd); |
| |
| // Wait for map to be populated |
| bool populated = false; |
| for (int i = 0; i < 50; i++) |
| { |
| io.poll(); |
| usleep(100000); // 100ms |
| if (mctpEndpointConfigMap.find(endpointPath) != |
| mctpEndpointConfigMap.end()) |
| { |
| populated = true; |
| break; |
| } |
| } |
| ASSERT_TRUE(populated); |
| |
| bool sensorNotified = false; |
| |
| // Create BusInfo for target device |
| BusInfo targetBusInfo; |
| targetBusInfo["BusType"] = "USB"; |
| targetBusInfo["Port"] = "1.5.1"; |
| targetBusInfo["Configuration"] = "1"; |
| targetBusInfo["InterfaceNum"] = "4"; |
| targetBusInfo["RootHubPath"] = "/sys/devices/platform/ahb/f0806000.usb"; |
| |
| bool setupCompleted = false; |
| std::shared_ptr<MctpEndpoint> endpoint; |
| |
| // Create MctpReactorDevice |
| auto mctpDev = std::make_shared<MctpReactorDevice>( |
| conn, emConfigPath, std::vector<BusInfo>{targetBusInfo}, std::nullopt, |
| std::nullopt); |
| mctpDev->setup([&](const std::error_code& ec, |
| const std::shared_ptr<MctpEndpoint>& ep) { |
| setupCompleted = true; |
| endpoint = ep; |
| EXPECT_FALSE(ec); |
| EXPECT_NE(ep, nullptr); |
| }); |
| |
| io.poll(); |
| ASSERT_TRUE(setupCompleted); |
| ASSERT_NE(endpoint, nullptr); |
| |
| endpoint->subscribe( |
| nullptr, nullptr, |
| [&](const std::shared_ptr<MctpEndpoint>&) { sensorNotified = true; }); |
| |
| // Trigger Remove event! |
| std::string removeCmd = |
| "busctl call au.com.codeconstruct.MCTP1 /au/com/codeconstruct/mctp1 com.example.Control TriggerRemove s \"" + |
| endpointPath + "\""; |
| runCmd(removeCmd); |
| |
| // Wait for removal notification (up to 5 seconds) |
| for (int i = 0; i < 50; i++) |
| { |
| io.poll(); |
| usleep(100000); // 100ms |
| if (sensorNotified) |
| { |
| break; |
| } |
| } |
| |
| EXPECT_TRUE(sensorNotified); |
| } |
| |
| // 14. Cancellation on Device Removal (Case D) |
| TEST_F(MctpUtilTest, MctpReactorDevice_Cancellation_USB) |
| { |
| std::string emConfigPath = |
| "/xyz/openbmc_project/inventory/system/board/MockBoard/mctp_device"; |
| |
| setupMctpEndpointListener(conn, MctpMessageType::NVME_MI); |
| |
| // Create BusInfo for target device |
| BusInfo targetBusInfo; |
| targetBusInfo["BusType"] = "USB"; |
| targetBusInfo["Port"] = "1.5.1"; |
| targetBusInfo["Configuration"] = "1"; |
| targetBusInfo["InterfaceNum"] = "4"; |
| targetBusInfo["RootHubPath"] = "/sys/devices/platform/ahb/f0806000.usb"; |
| |
| bool setupCompleted = false; |
| std::error_code errorResult; |
| |
| // Create MctpReactorDevice |
| auto mctpDev = std::make_shared<MctpReactorDevice>( |
| conn, emConfigPath, std::vector<BusInfo>{targetBusInfo}, std::nullopt, |
| std::nullopt); |
| mctpDev->setup([&](const std::error_code& ec, |
| const std::shared_ptr<MctpEndpoint>& ep) { |
| setupCompleted = true; |
| errorResult = ec; |
| EXPECT_EQ(ep, nullptr); |
| }); |
| |
| // Verify it didn't complete immediately |
| io.poll(); |
| EXPECT_FALSE(setupCompleted); |
| |
| // Call remove() to trigger cancellation! |
| mctpDev->remove(); |
| |
| // Verify it completed with operation_canceled! |
| EXPECT_TRUE(setupCompleted); |
| EXPECT_EQ(errorResult, std::make_error_code(std::errc::operation_canceled)); |
| } |
| |
| // 15. Immediate Notification on Device Destruction (Case E) |
| TEST_F(MctpUtilTest, MctpReactorDevice_DestructionNotification_USB) |
| { |
| std::string endpointPath = |
| "/au/com/codeconstruct/mctp1/networks/1/endpoints/14"; |
| std::string emConfigPath = |
| "/xyz/openbmc_project/inventory/system/board/MockBoard/mctp_device"; |
| |
| setupMctpEndpointListener(conn, MctpMessageType::NVME_MI); |
| usleep(100000); // 100ms for match rules to register |
| |
| // Trigger Add event FIRST! |
| std::string addCmd = |
| "busctl call au.com.codeconstruct.MCTP1 /au/com/codeconstruct/mctp1 com.example.Control TriggerAdd ss \"" + |
| endpointPath + "\" \"" + emConfigPath + "\""; |
| runCmd(addCmd); |
| |
| // Wait for map to be populated |
| bool populated = false; |
| for (int i = 0; i < 50; i++) |
| { |
| io.poll(); |
| usleep(100000); // 100ms |
| if (mctpEndpointConfigMap.find(endpointPath) != |
| mctpEndpointConfigMap.end()) |
| { |
| populated = true; |
| break; |
| } |
| } |
| ASSERT_TRUE(populated); |
| |
| // Create BusInfo for target device |
| BusInfo targetBusInfo; |
| targetBusInfo["BusType"] = "USB"; |
| targetBusInfo["Port"] = "1.5.1"; |
| targetBusInfo["Configuration"] = "1"; |
| targetBusInfo["InterfaceNum"] = "4"; |
| targetBusInfo["RootHubPath"] = "/sys/devices/platform/ahb/f0806000.usb"; |
| |
| bool sensorNotified = false; |
| auto cb = [&](const std::shared_ptr<MctpEndpoint>&) { |
| sensorNotified = true; |
| }; |
| { |
| bool setupCompleted = false; |
| std::shared_ptr<MctpEndpoint> endpoint; |
| |
| // Create MctpReactorDevice |
| auto mctpDev = std::make_shared<MctpReactorDevice>( |
| conn, emConfigPath, std::vector<BusInfo>{targetBusInfo}, |
| std::nullopt, std::nullopt); |
| |
| mctpDev->setup([&](const std::error_code&, |
| const std::shared_ptr<MctpEndpoint>& ep) { |
| setupCompleted = true; |
| endpoint = ep; |
| }); |
| |
| io.poll(); |
| ASSERT_TRUE(setupCompleted); |
| ASSERT_NE(endpoint, nullptr); |
| |
| endpoint->subscribe(nullptr, nullptr, cb); |
| } |
| |
| // mctpDev is destroyed here. Wait for the removal notification to be |
| // processed. |
| int i = 0; |
| for (; i < 50; i++) |
| { |
| io.poll(); |
| usleep(100000); // 100ms |
| if (sensorNotified) |
| { |
| break; |
| } |
| } |
| |
| ASSERT_LT(i, 50) << "Timeout: Destruction notification was never fired!"; |
| EXPECT_TRUE(sensorNotified); |
| } |
| |
| // 16. Bridged Endpoint Resolution |
| TEST_F(MctpUtilTest, BridgedEndpoint_ResolvesParentBridge) |
| { |
| std::string bridgePath = |
| "/au/com/codeconstruct/mctp1/networks/1/endpoints/mctp_device"; |
| std::string bridgeEmPath = |
| "/xyz/openbmc_project/inventory/system/board/MockBoard/mctp_device"; |
| std::string endpointPath = |
| "/au/com/codeconstruct/mctp1/networks/1/endpoints/test_bridged"; |
| std::string emConfigPath = |
| "/xyz/openbmc_project/inventory/system/board/MockBoard/bridged_endpoint_config"; |
| |
| setupMctpEndpointListener(conn, MctpMessageType::NVME_MI); |
| |
| int queryCompletedCount = 0; |
| const std::string queryCompletedSpec = |
| "type='signal',interface='com.example.Control',member='QueryCompleted'"; |
| auto queryCompletedMatch = std::make_unique<sdbusplus::bus::match_t>( |
| static_cast<sdbusplus::bus_t&>(*conn), queryCompletedSpec, |
| [&](sdbusplus::message_t&) { queryCompletedCount++; }); |
| |
| // Wait for priming loop to complete (finding nothing) |
| io.poll(); |
| usleep(100000); // 100ms |
| io.poll(); |
| |
| std::string addBridgeCmd = |
| "busctl call au.com.codeconstruct.MCTP1 /au/com/codeconstruct/mctp1 com.example.Control TriggerAdd ss \"" + |
| bridgePath + "\" \"" + bridgeEmPath + "\""; |
| runCmd(addBridgeCmd); |
| |
| // Wait for bridge query to complete |
| for (int i = 0; i < 50; i++) |
| { |
| io.poll(); |
| usleep(100000); // 100ms |
| if (queryCompletedCount >= 1) |
| { |
| break; |
| } |
| } |
| ASSERT_EQ(queryCompletedCount, 1); |
| |
| // Wait for mapperx to create association for bridge |
| bool found = false; |
| for (int i = 0; i < 50; i++) |
| { |
| if (checkPathInMapper(bridgePath + "/configured_by")) |
| { |
| found = true; |
| break; |
| } |
| io.poll(); |
| usleep(100000); // 100ms |
| } |
| ASSERT_TRUE(found) << "Timed out waiting for bridge association in mapper"; |
| |
| // 2. Trigger Add for Endpoint! |
| std::string addEpCmd = |
| "busctl call au.com.codeconstruct.MCTP1 /au/com/codeconstruct/mctp1 com.example.Control TriggerAdd ss \"" + |
| endpointPath + "\" \"" + emConfigPath + "\""; |
| runCmd(addEpCmd); |
| |
| // Wait for endpoint query to complete |
| for (int i = 0; i < 50; i++) |
| { |
| io.poll(); |
| usleep(100000); // 100ms |
| if (queryCompletedCount >= 2) |
| { |
| break; |
| } |
| } |
| EXPECT_EQ(queryCompletedCount, 2); |
| |
| auto it = mctpEndpointConfigMap.find(endpointPath); |
| ASSERT_NE(it, mctpEndpointConfigMap.end()); |
| EXPECT_EQ(it->second.size(), 2); |
| |
| MctpReactorBusInfo busInfo(it->second); |
| EXPECT_TRUE(busInfo.isValid()); |
| EXPECT_EQ(busInfo.getType().front(), "USB"); |
| EXPECT_EQ(busInfo.getProperties().front().at("Port"), "1.5.1"); |
| } |
| |
| // 17. Broken Bridge Chain (Non-existent bridge) |
| TEST_F(MctpUtilTest, BridgedEndpoint_BrokenChain_Nonexistent_Fails) |
| { |
| std::string endpointPath = |
| "/au/com/codeconstruct/mctp1/networks/1/endpoints/test_broken_nonexistent"; |
| std::string emConfigPath = |
| "/xyz/openbmc_project/inventory/system/board/MockBoard/broken_endpoint_nonexistent"; |
| |
| setupMctpEndpointListener(conn, MctpMessageType::NVME_MI); |
| |
| int queryCompletedCount = 0; |
| const std::string queryCompletedSpec = |
| "type='signal',interface='com.example.Control',member='QueryCompleted'"; |
| auto queryCompletedMatch = std::make_unique<sdbusplus::bus::match_t>( |
| static_cast<sdbusplus::bus_t&>(*conn), queryCompletedSpec, |
| [&](sdbusplus::message_t&) { queryCompletedCount++; }); |
| |
| // Wait for priming loop to complete (finding nothing) |
| io.poll(); |
| usleep(100000); // 100ms |
| io.poll(); |
| |
| std::string addCmd = |
| "busctl call au.com.codeconstruct.MCTP1 /au/com/codeconstruct/mctp1 com.example.Control TriggerAdd ss \"" + |
| endpointPath + "\" \"" + emConfigPath + "\""; |
| runCmd(addCmd); |
| |
| for (int i = 0; i < 50; i++) |
| { |
| io.poll(); |
| usleep(100000); // 100ms |
| if (queryCompletedCount >= 1) |
| { |
| break; |
| } |
| } |
| EXPECT_EQ(queryCompletedCount, 1); |
| |
| auto it = mctpEndpointConfigMap.find(endpointPath); |
| EXPECT_EQ(it, mctpEndpointConfigMap.end()); |
| } |
| |
| // 18. Multi-Level Bridging |
| TEST_F(MctpUtilTest, BridgedEndpoint_MultiLevel_ResolvesRootBridge) |
| { |
| std::string rootBridgePath = |
| "/au/com/codeconstruct/mctp1/networks/1/endpoints/mctp_device"; |
| std::string rootBridgeEmPath = |
| "/xyz/openbmc_project/inventory/system/board/MockBoard/mctp_device"; |
| std::string interBridgePath = |
| "/au/com/codeconstruct/mctp1/networks/1/endpoints/intermediate_bridge"; |
| std::string interBridgeEmPath = |
| "/xyz/openbmc_project/inventory/system/board/MockBoard/intermediate_bridge"; |
| std::string endpointPath = |
| "/au/com/codeconstruct/mctp1/networks/1/endpoints/test_multilevel"; |
| std::string emConfigPath = |
| "/xyz/openbmc_project/inventory/system/board/MockBoard/multilevel_endpoint"; |
| |
| setupMctpEndpointListener(conn, MctpMessageType::NVME_MI); |
| |
| int queryCompletedCount = 0; |
| const std::string queryCompletedSpec = |
| "type='signal',interface='com.example.Control',member='QueryCompleted'"; |
| auto queryCompletedMatch = std::make_unique<sdbusplus::bus::match_t>( |
| static_cast<sdbusplus::bus_t&>(*conn), queryCompletedSpec, |
| [&](sdbusplus::message_t&) { queryCompletedCount++; }); |
| |
| // Wait for priming loop to complete (finding nothing) |
| io.poll(); |
| usleep(100000); // 100ms |
| io.poll(); |
| |
| // 1. Trigger Add for Root Bridge! |
| std::string addRootCmd = |
| "busctl call au.com.codeconstruct.MCTP1 /au/com/codeconstruct/mctp1 com.example.Control TriggerAdd ss \"" + |
| rootBridgePath + "\" \"" + rootBridgeEmPath + "\""; |
| runCmd(addRootCmd); |
| |
| // Wait for root bridge query to complete |
| for (int i = 0; i < 50; i++) |
| { |
| io.poll(); |
| usleep(100000); // 100ms |
| if (queryCompletedCount >= 1) |
| { |
| break; |
| } |
| } |
| ASSERT_EQ(queryCompletedCount, 1); |
| |
| // 2. Trigger Add for Intermediate Bridge! |
| std::string addInterCmd = |
| "busctl call au.com.codeconstruct.MCTP1 /au/com/codeconstruct/mctp1 com.example.Control TriggerAdd ss \"" + |
| interBridgePath + "\" \"" + interBridgeEmPath + "\""; |
| runCmd(addInterCmd); |
| |
| // Wait for intermediate bridge query to complete |
| for (int i = 0; i < 50; i++) |
| { |
| io.poll(); |
| usleep(100000); // 100ms |
| if (queryCompletedCount >= 2) |
| { |
| break; |
| } |
| } |
| ASSERT_EQ(queryCompletedCount, 2); |
| |
| // 3. Trigger Add for Endpoint! |
| std::string addEpCmd = |
| "busctl call au.com.codeconstruct.MCTP1 /au/com/codeconstruct/mctp1 com.example.Control TriggerAdd ss \"" + |
| endpointPath + "\" \"" + emConfigPath + "\""; |
| runCmd(addEpCmd); |
| |
| // Wait for endpoint query to complete |
| for (int i = 0; i < 50; i++) |
| { |
| io.poll(); |
| usleep(100000); // 100ms |
| if (queryCompletedCount >= 3) |
| { |
| break; |
| } |
| } |
| EXPECT_EQ(queryCompletedCount, 3); |
| |
| auto it = mctpEndpointConfigMap.find(endpointPath); |
| ASSERT_NE(it, mctpEndpointConfigMap.end()); |
| EXPECT_EQ(it->second.size(), 3); |
| |
| MctpReactorBusInfo busInfo(it->second); |
| EXPECT_TRUE(busInfo.isValid()); |
| EXPECT_EQ(busInfo.getType().front(), "USB"); |
| EXPECT_EQ(busInfo.getProperties().front().at("Port"), "1.5.1"); |
| } |
| |
| // 19. Out-of-Order Triggers (Endpoint before Bridge) - Expect Success after |
| // Retry |
| TEST_F(MctpUtilTest, BridgedEndpoint_OutOfOrder_SucceedsAfterRetry) |
| { |
| std::string bridgePath = |
| "/au/com/codeconstruct/mctp1/networks/1/endpoints/mctp_device"; |
| std::string bridgeEmPath = |
| "/xyz/openbmc_project/inventory/system/board/MockBoard/mctp_device"; |
| std::string endpointPath = |
| "/au/com/codeconstruct/mctp1/networks/1/endpoints/test_out_of_order"; |
| std::string emConfigPath = |
| "/xyz/openbmc_project/inventory/system/board/MockBoard/bridged_endpoint_config"; |
| |
| setupMctpEndpointListener(conn, MctpMessageType::NVME_MI); |
| |
| int queryCompletedCount = 0; |
| const std::string queryCompletedSpec = |
| "type='signal',interface='com.example.Control',member='QueryCompleted'"; |
| auto queryCompletedMatch = std::make_unique<sdbusplus::bus::match_t>( |
| static_cast<sdbusplus::bus_t&>(*conn), queryCompletedSpec, |
| [&](sdbusplus::message_t&) { queryCompletedCount++; }); |
| |
| // Wait for priming loop to complete |
| io.poll(); |
| usleep(100000); // 100ms |
| io.poll(); |
| |
| // Wait 5 seconds to ensure cooldown expires! |
| for (int i = 0; i < 50; i++) |
| { |
| io.poll(); |
| usleep(100000); // 100ms |
| } |
| |
| // 1. Trigger Add for Endpoint FIRST! |
| std::string addEpCmd = |
| "busctl call au.com.codeconstruct.MCTP1 /au/com/codeconstruct/mctp1 com.example.Control TriggerAdd ss \"" + |
| endpointPath + "\" \"" + emConfigPath + "\""; |
| runCmd(addEpCmd); |
| |
| // Trigger Add for Bridge SECOND (after 2s)! |
| for (int i = 0; i < 20; i++) |
| { |
| io.poll(); |
| usleep(100000); // 100ms |
| } |
| std::string addBridgeCmd = |
| "busctl call au.com.codeconstruct.MCTP1 /au/com/codeconstruct/mctp1 com.example.Control TriggerAdd ss \"" + |
| bridgePath + "\" \"" + bridgeEmPath + "\""; |
| runCmd(addBridgeCmd); |
| |
| // Wait for all to complete! |
| // B retries after 5 seconds, so it should complete around 6-7 seconds! |
| for (int i = 0; i < 100; i++) |
| { // 10 seconds max |
| io.poll(); |
| usleep(100000); // 100ms |
| } |
| |
| // Expect 3 queries: B (fail) -> Bridge A (success) -> B (success retry) |
| EXPECT_EQ(queryCompletedCount, 3); |
| |
| auto it = mctpEndpointConfigMap.find(endpointPath); |
| EXPECT_NE(it, mctpEndpointConfigMap.end()); |
| } |
| |
| // 20. performEmConfigQuery Failure Triggers Retry - Expect Failure with Current |
| // Code |
| TEST_F(MctpUtilTest, performEmConfigQuery_Failure_TriggersRetry) |
| { |
| std::string endpointPath = |
| "/au/com/codeconstruct/mctp1/networks/1/endpoints/test_retry"; |
| std::string emConfigPath = |
| "/xyz/openbmc_project/inventory/system/board/MockBoard/mctp_device"; |
| |
| setupMctpEndpointListener(conn, MctpMessageType::NVME_MI); |
| |
| int queryCompletedCount = 0; |
| const std::string queryCompletedSpec = |
| "type='signal',interface='com.example.Control',member='QueryCompleted'"; |
| auto queryCompletedMatch = std::make_unique<sdbusplus::bus::match_t>( |
| static_cast<sdbusplus::bus_t&>(*conn), queryCompletedSpec, |
| [&](sdbusplus::message_t&) { queryCompletedCount++; }); |
| |
| // Wait for priming loop to complete |
| for (int i = 0; i < 50; i++) |
| { |
| io.poll(); |
| usleep(100000); // 100ms |
| } |
| |
| // Enable failure mode in mock! |
| runCmd( |
| "busctl call xyz.openbmc_project.EntityManager /xyz/openbmc_project/inventory com.example.Control SetFailMode b true"); |
| |
| // Trigger Add for Endpoint! |
| std::string addCmd = |
| "busctl call au.com.codeconstruct.MCTP1 /au/com/codeconstruct/mctp1 com.example.Control TriggerAdd ss \"" + |
| endpointPath + "\" \"" + emConfigPath + "\""; |
| runCmd(addCmd); |
| |
| // Wait for first query to complete (it should fail!) |
| for (int i = 0; i < 20; i++) |
| { |
| io.poll(); |
| usleep(100000); // 100ms |
| if (queryCompletedCount >= 1) |
| { |
| break; |
| } |
| } |
| EXPECT_EQ(queryCompletedCount, 1); |
| |
| // Disable failure mode in mock! |
| runCmd( |
| "busctl call xyz.openbmc_project.EntityManager /xyz/openbmc_project/inventory com.example.Control SetFailMode b false"); |
| |
| // Wait for retry! |
| // With current code, it won't retry on failure! So count will stay 1! |
| // With fix, it should retry after 5 seconds and succeed! |
| // So count should reach 2! |
| for (int i = 0; i < 80; i++) |
| { // 8 seconds max |
| io.poll(); |
| usleep(100000); // 100ms |
| if (queryCompletedCount >= 2) |
| { |
| break; |
| } |
| } |
| |
| // Expect success (count=2) after fix, but FAIL with current code! |
| EXPECT_EQ(queryCompletedCount, 2); |
| |
| auto it = mctpEndpointConfigMap.find(endpointPath); |
| EXPECT_NE(it, mctpEndpointConfigMap.end()); |
| } |
| |
| // 21. Priming with failing endpoint does not block others |
| TEST_F(MctpUtilTest, Priming_FailingEndpoint_DoesNotBlockOthers) |
| { |
| std::string endpointPathFail = |
| "/au/com/codeconstruct/mctp1/networks/1/endpoints/test_prime_fail"; |
| std::string emConfigPathFail = |
| "/xyz/openbmc_project/inventory/system/board/MockBoard/broken_endpoint_nonexistent"; |
| |
| std::string endpointPathSuccess = |
| "/au/com/codeconstruct/mctp1/networks/1/endpoints/test_prime_success"; |
| std::string emConfigPathSuccess = |
| "/xyz/openbmc_project/inventory/system/board/MockBoard/mctp_device"; |
| |
| // Pre-populate association for failing endpoint BEFORE listener starts! |
| auto m = conn->new_method_call("au.com.codeconstruct.MCTP1", |
| "/au/com/codeconstruct/mctp1", |
| "com.example.Control", "TriggerAdd"); |
| m.append(endpointPathFail, emConfigPathFail); |
| conn->call(m); |
| |
| // Wait for mapperx to see it |
| bool found = false; |
| for (int i = 0; i < 300; i++) |
| { |
| if (checkPathInMapper(endpointPathFail + "/configured_by")) |
| { |
| found = true; |
| break; |
| } |
| usleep(100000); // 100ms |
| } |
| ASSERT_TRUE(found); |
| |
| // Now setup listener (this starts priming!) |
| setupMctpEndpointListener(conn, MctpMessageType::NVME_MI); |
| |
| int queryCompletedCount = 0; |
| const std::string queryCompletedSpec = |
| "type='signal',interface='com.example.Control',member='QueryCompleted'"; |
| auto queryCompletedMatch = std::make_unique<sdbusplus::bus::match_t>( |
| static_cast<sdbusplus::bus_t&>(*conn), queryCompletedSpec, |
| [&](sdbusplus::message_t&) { queryCompletedCount++; }); |
| |
| // Wait for priming query to start for failing endpoint |
| usleep(500000); // 500ms |
| io.poll(); |
| |
| // Trigger Add for a VALID endpoint while priming is in progress! |
| std::string addCmd = |
| "busctl call au.com.codeconstruct.MCTP1 /au/com/codeconstruct/mctp1 com.example.Control TriggerAdd ss \"" + |
| endpointPathSuccess + "\" \"" + emConfigPathSuccess + "\""; |
| runCmd(addCmd); |
| |
| // Wait for queries to complete. |
| // With fix: failing one fails and defers, valid one succeeds! Total = 2 |
| // queries. Without fix: failing one hangs, valid one never starts! Total = |
| // 1 query. |
| |
| for (int i = 0; i < 150; i++) |
| { // 15 seconds max |
| io.poll(); |
| usleep(100000); // 100ms |
| if (mctpEndpointConfigMap.find(endpointPathSuccess) != |
| mctpEndpointConfigMap.end()) |
| { |
| break; |
| } |
| } |
| |
| auto it = mctpEndpointConfigMap.find(endpointPathSuccess); |
| EXPECT_NE(it, mctpEndpointConfigMap.end()); |
| } |
| |
| // 22. MctpReactorBusInfo chain parsing and operator== matching |
| TEST_F(MctpUtilTest, MctpReactorBusInfo_ChainParsingAndMatching) |
| { |
| std::vector<SensorData> chain; |
| |
| // Leaf (endpoint) |
| SensorBaseConfigMap epProps; |
| epProps["EIDOffset"] = uint64_t(2); |
| SensorData epData; |
| epData["xyz.openbmc_project.Configuration.MCTPBridgeDownstreamDevice"] = |
| epProps; |
| chain.push_back(epData); |
| |
| // Bridge |
| SensorBaseConfigMap bridgeProps; |
| bridgeProps["BusType"] = std::string("USB"); |
| bridgeProps["Port"] = std::string("1.5.1"); |
| bridgeProps["Configuration"] = |
| std::string("1"); // Extra property not in target |
| SensorData bridgeData; |
| bridgeData["xyz.openbmc_project.Configuration.MCTPUSBDevice"] = bridgeProps; |
| chain.push_back(bridgeData); |
| |
| MctpReactorBusInfo busInfo(chain); |
| |
| EXPECT_TRUE(busInfo.isValid()); |
| auto propsList = busInfo.getProperties(); |
| ASSERT_EQ(propsList.size(), 2); |
| |
| // Verify order is root-to-leaf (from Test 22) |
| EXPECT_EQ(propsList[0].at("BusType"), "USB"); |
| EXPECT_EQ(propsList[0].at("Port"), "1.5.1"); |
| |
| EXPECT_EQ(propsList[1].at("BusType"), "MCTP Bridge"); |
| EXPECT_EQ(propsList[1].at("EIDOffset"), "2"); |
| |
| // Verify operator== subset matching (from Test 23) |
| std::vector<BusInfo> targetConfig; |
| |
| // Root level |
| BusInfo targetBridge; |
| targetBridge["BusType"] = "USB"; |
| targetBridge["Port"] = "1.5.1"; |
| targetConfig.push_back(targetBridge); |
| |
| // Leaf level |
| BusInfo targetEp; |
| targetEp["EIDOffset"] = "2"; |
| targetConfig.push_back(targetEp); |
| |
| // Should match because target is a subset of extracted properties |
| EXPECT_TRUE(busInfo == targetConfig); |
| |
| // Test mismatch in value |
| targetConfig[1]["EIDOffset"] = "3"; |
| EXPECT_FALSE(busInfo == targetConfig); |
| |
| // Test missing property in extracted props |
| targetConfig[1]["EIDOffset"] = "2"; |
| targetConfig[1]["ExtraProp"] = "value"; |
| EXPECT_FALSE(busInfo == targetConfig); |
| |
| // Test size mismatch |
| targetConfig.pop_back(); |
| EXPECT_FALSE(busInfo == targetConfig); |
| } |
| |
| int main(int argc, char** argv) |
| { |
| ::testing::InitGoogleTest(&argc, argv); |
| return RUN_ALL_TESTS(); |
| } |