[ICD]Add unit tests for the ICD Manager operational states (#28729)
* Add unit tests for the ICD Manager
* Address comment, try to fix test for ESP and IOT SDK
* Use GetIOContext().DriveIO() to run event loop. Set the systemLayer for test to be the IOContext one
* Clean up
* Restyled by whitespace
---------
Co-authored-by: Restyled.io <commits@restyled.io>
diff --git a/src/app/icd/ICDManager.cpp b/src/app/icd/ICDManager.cpp
index e3787f2..287d0dd 100644
--- a/src/app/icd/ICDManager.cpp
+++ b/src/app/icd/ICDManager.cpp
@@ -75,9 +75,12 @@
bool ICDManager::SupportsCheckInProtocol()
{
- bool success;
- uint32_t featureMap;
+ bool success = false;
+ uint32_t featureMap = 0;
+ // Can't use attribute accessors/Attributes::FeatureMap::Get in unit tests
+#ifndef CONFIG_BUILD_FOR_HOST_UNIT_TEST
success = (Attributes::FeatureMap::Get(kRootEndpointId, &featureMap) == EMBER_ZCL_STATUS_SUCCESS);
+#endif
return success ? ((featureMap & to_underlying(Feature::kCheckInProtocolSupport)) != 0) : false;
}
diff --git a/src/app/icd/ICDManager.h b/src/app/icd/ICDManager.h
index e5406f1..6e98902 100644
--- a/src/app/icd/ICDManager.h
+++ b/src/app/icd/ICDManager.h
@@ -26,6 +26,10 @@
namespace chip {
namespace app {
+// Forward declaration of TestICDManager to allow it to be friend with ICDManager
+// Used in unit tests
+class TestICDManager;
+
/**
* @brief ICD Manager is responsible of processing the events and triggering the correct action for an ICD
*/
@@ -67,6 +71,8 @@
static System::Clock::Milliseconds32 GetFastPollingInterval() { return kFastPollingInterval; }
protected:
+ friend class TestICDManager;
+
static void OnIdleModeDone(System::Layer * aLayer, void * appState);
static void OnActiveModeDone(System::Layer * aLayer, void * appState);
diff --git a/src/app/tests/TestICDManager.cpp b/src/app/tests/TestICDManager.cpp
index 6fdaeed..81739ae 100644
--- a/src/app/tests/TestICDManager.cpp
+++ b/src/app/tests/TestICDManager.cpp
@@ -15,17 +15,199 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#include <app/EventManagement.h>
+#include <app/tests/AppTestContext.h>
+#include <lib/support/TestPersistentStorageDelegate.h>
+#include <lib/support/UnitTestContext.h>
#include <lib/support/UnitTestRegistration.h>
#include <nlunit-test.h>
+#include <system/SystemLayerImpl.h>
-int TestICDManager()
+#include <app/icd/ICDManager.h>
+#include <app/icd/ICDStateObserver.h>
+#include <app/icd/IcdManagementServer.h>
+
+using namespace chip;
+using namespace chip::app;
+using namespace chip::System;
+
+namespace {
+
+class TestICDStateObserver : public app::ICDStateObserver
{
- static nlTest sTests[] = { NL_TEST_SENTINEL() };
+public:
+ void OnEnterActiveMode() {}
+};
- nlTestSuite cmSuite = { "TestICDManager", &sTests[0], nullptr, nullptr };
+TestICDStateObserver mICDStateObserver;
+static Clock::Internal::MockClock gMockClock;
+static Clock::ClockBase * gRealClock;
- nlTestRunner(&cmSuite, nullptr);
- return (nlTestRunnerStats(&cmSuite));
+class TestContext : public Test::AppContext
+{
+public:
+ static int Initialize(void * context)
+ {
+ if (AppContext::Initialize(context) != SUCCESS)
+ return FAILURE;
+
+ auto * ctx = static_cast<TestContext *>(context);
+ DeviceLayer::SetSystemLayerForTesting(&ctx->GetSystemLayer());
+
+ gRealClock = &SystemClock();
+ Clock::Internal::SetSystemClockForTesting(&gMockClock);
+
+ if (ctx->mEventCounter.Init(0) != CHIP_NO_ERROR)
+ {
+ return FAILURE;
+ }
+
+ ctx->mICDManager.Init(&ctx->testStorage, &ctx->GetFabricTable(), &mICDStateObserver);
+ return SUCCESS;
+ }
+
+ static int Finalize(void * context)
+ {
+ auto * ctx = static_cast<TestContext *>(context);
+ ctx->mICDManager.Shutdown();
+ app::EventManagement::DestroyEventManagement();
+ System::Clock::Internal::SetSystemClockForTesting(gRealClock);
+ DeviceLayer::SetSystemLayerForTesting(nullptr);
+
+ if (AppContext::Finalize(context) != SUCCESS)
+ return FAILURE;
+
+ return SUCCESS;
+ }
+
+ app::ICDManager mICDManager;
+
+private:
+ TestPersistentStorageDelegate testStorage;
+ MonotonicallyIncreasingCounter<EventNumber> mEventCounter;
+};
+
+} // namespace
+
+namespace chip {
+namespace app {
+class TestICDManager
+{
+public:
+ /*
+ * Advance the test Mock clock time by the amout passed in argument
+ * and then force the SystemLayer Timer event loop. It will check for any expired timer,
+ * and invoke their callbacks if there are any.
+ *
+ * @param time_ms: Value in milliseconds.
+ */
+ static void AdvanceClockAndRunEventLoop(TestContext * ctx, uint32_t time_ms)
+ {
+ gMockClock.AdvanceMonotonic(System::Clock::Timeout(time_ms));
+ ctx->GetIOContext().DriveIO();
+ }
+
+ static void TestICDModeIntervals(nlTestSuite * aSuite, void * aContext)
+ {
+ TestContext * ctx = static_cast<TestContext *>(aContext);
+
+ // After the init we should be in active mode
+ NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
+ AdvanceClockAndRunEventLoop(ctx, IcdManagementServer::GetInstance().GetActiveModeInterval() + 1);
+ // Active mode interval expired, ICDManager transitioned to the IdleMode.
+ NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
+ AdvanceClockAndRunEventLoop(ctx, IcdManagementServer::GetInstance().GetIdleModeInterval() + 1);
+ // Idle mode interval expired, ICDManager transitioned to the ActiveMode.
+ NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
+
+ // Events updating the Operation to Active mode can extend the current active mode time by 1 Active mode threshold.
+ // Kick an active Threshold just before the end of the Active interval and validate that the active mode is extended.
+ AdvanceClockAndRunEventLoop(ctx, IcdManagementServer::GetInstance().GetActiveModeInterval() - 1);
+ ctx->mICDManager.UpdateOperationState(ICDManager::OperationalState::ActiveMode);
+ AdvanceClockAndRunEventLoop(ctx, IcdManagementServer::GetInstance().GetActiveModeThreshold() / 2);
+ NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
+ AdvanceClockAndRunEventLoop(ctx, IcdManagementServer::GetInstance().GetActiveModeThreshold());
+ NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
+ }
+
+ static void TestKeepActivemodeRequests(nlTestSuite * aSuite, void * aContext)
+ {
+ TestContext * ctx = static_cast<TestContext *>(aContext);
+
+ // Setting a requirement will transition the ICD to active mode.
+ ctx->mICDManager.SetKeepActiveModeRequirements(ICDManager::KeepActiveFlags::kCommissioningWindowOpen, true);
+ NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
+ // Advance time so active mode interval expires.
+ AdvanceClockAndRunEventLoop(ctx, IcdManagementServer::GetInstance().GetActiveModeInterval() + 1);
+ // Requirement flag still set. We stay in active mode
+ NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
+
+ // Remove requirement. we should directly transition to idle mode.
+ ctx->mICDManager.SetKeepActiveModeRequirements(ICDManager::KeepActiveFlags::kCommissioningWindowOpen, false);
+ NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
+
+ ctx->mICDManager.SetKeepActiveModeRequirements(ICDManager::KeepActiveFlags::kFailSafeArmed, true);
+ // Requirement will transition us to active mode.
+ NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
+
+ // Advance time, but by less than the active mode interval and remove the requirement.
+ // We should stay in active mode.
+ AdvanceClockAndRunEventLoop(ctx, IcdManagementServer::GetInstance().GetActiveModeInterval() / 2);
+ ctx->mICDManager.SetKeepActiveModeRequirements(ICDManager::KeepActiveFlags::kFailSafeArmed, false);
+ NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
+
+ // Advance time again, The activemode interval is completed.
+ AdvanceClockAndRunEventLoop(ctx, IcdManagementServer::GetInstance().GetActiveModeInterval() + 1);
+ NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
+
+ // Set two requirements
+ ctx->mICDManager.SetKeepActiveModeRequirements(ICDManager::KeepActiveFlags::kExpectingMsgResponse, true);
+ ctx->mICDManager.SetKeepActiveModeRequirements(ICDManager::KeepActiveFlags::kAwaitingMsgAck, true);
+ NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
+ // advance time so the active mode interval expires.
+ AdvanceClockAndRunEventLoop(ctx, IcdManagementServer::GetInstance().GetActiveModeInterval() + 1);
+ // A requirement flag is still set. We stay in active mode.
+ NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
+
+ // remove 1 requirement. Active mode is maintained
+ ctx->mICDManager.SetKeepActiveModeRequirements(ICDManager::KeepActiveFlags::kExpectingMsgResponse, false);
+ NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
+ // remove the last requirement
+ ctx->mICDManager.SetKeepActiveModeRequirements(ICDManager::KeepActiveFlags::kAwaitingMsgAck, false);
+ NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
+ }
+};
+
+} // namespace app
+} // namespace chip
+
+namespace {
+/**
+ * Test Suite. It lists all the test functions.
+ */
+// clang-format off
+static const nlTest sTests[] =
+{
+ NL_TEST_DEF("TestICDModeIntervals", TestICDManager::TestICDModeIntervals),
+ NL_TEST_DEF("TestKeepActivemodeRequests", TestICDManager::TestKeepActivemodeRequests),
+ NL_TEST_SENTINEL()
+};
+// clang-format on
+
+// clang-format off
+nlTestSuite cmSuite =
+{
+ "TestICDManager",
+ &sTests[0],
+ TestContext::Initialize,
+ TestContext::Finalize
+};
+// clang-format on
+} // namespace
+
+int TestSuiteICDManager()
+{
+ return ExecuteTestsWithContext<TestContext>(&cmSuite);
}
-CHIP_REGISTER_TEST_SUITE(TestICDManager)
+CHIP_REGISTER_TEST_SUITE(TestSuiteICDManager)