Skip to content

Commit 4222995

Browse files
jmartinez-silabsrestyled-commits
authored andcommitted
[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 <[email protected]>
1 parent c02cc98 commit 4222995

File tree

3 files changed

+199
-8
lines changed

3 files changed

+199
-8
lines changed

src/app/icd/ICDManager.cpp

+5-2
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,12 @@ void ICDManager::Shutdown()
7575

7676
bool ICDManager::SupportsCheckInProtocol()
7777
{
78-
bool success;
79-
uint32_t featureMap;
78+
bool success = false;
79+
uint32_t featureMap = 0;
80+
// Can't use attribute accessors/Attributes::FeatureMap::Get in unit tests
81+
#ifndef CONFIG_BUILD_FOR_HOST_UNIT_TEST
8082
success = (Attributes::FeatureMap::Get(kRootEndpointId, &featureMap) == EMBER_ZCL_STATUS_SUCCESS);
83+
#endif
8184
return success ? ((featureMap & to_underlying(Feature::kCheckInProtocolSupport)) != 0) : false;
8285
}
8386

src/app/icd/ICDManager.h

+6
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
namespace chip {
2727
namespace app {
2828

29+
// Forward declaration of TestICDManager to allow it to be friend with ICDManager
30+
// Used in unit tests
31+
class TestICDManager;
32+
2933
/**
3034
* @brief ICD Manager is responsible of processing the events and triggering the correct action for an ICD
3135
*/
@@ -67,6 +71,8 @@ class ICDManager
6771
static System::Clock::Milliseconds32 GetFastPollingInterval() { return kFastPollingInterval; }
6872

6973
protected:
74+
friend class TestICDManager;
75+
7076
static void OnIdleModeDone(System::Layer * aLayer, void * appState);
7177
static void OnActiveModeDone(System::Layer * aLayer, void * appState);
7278

src/app/tests/TestICDManager.cpp

+188-6
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,199 @@
1515
* See the License for the specific language governing permissions and
1616
* limitations under the License.
1717
*/
18+
#include <app/EventManagement.h>
19+
#include <app/tests/AppTestContext.h>
20+
#include <lib/support/TestPersistentStorageDelegate.h>
21+
#include <lib/support/UnitTestContext.h>
1822
#include <lib/support/UnitTestRegistration.h>
1923
#include <nlunit-test.h>
24+
#include <system/SystemLayerImpl.h>
2025

21-
int TestICDManager()
26+
#include <app/icd/ICDManager.h>
27+
#include <app/icd/ICDStateObserver.h>
28+
#include <app/icd/IcdManagementServer.h>
29+
30+
using namespace chip;
31+
using namespace chip::app;
32+
using namespace chip::System;
33+
34+
namespace {
35+
36+
class TestICDStateObserver : public app::ICDStateObserver
37+
{
38+
public:
39+
void OnEnterActiveMode() {}
40+
};
41+
42+
TestICDStateObserver mICDStateObserver;
43+
static Clock::Internal::MockClock gMockClock;
44+
static Clock::ClockBase * gRealClock;
45+
46+
class TestContext : public Test::AppContext
47+
{
48+
public:
49+
static int Initialize(void * context)
50+
{
51+
if (AppContext::Initialize(context) != SUCCESS)
52+
return FAILURE;
53+
54+
auto * ctx = static_cast<TestContext *>(context);
55+
DeviceLayer::SetSystemLayerForTesting(&ctx->GetSystemLayer());
56+
57+
gRealClock = &SystemClock();
58+
Clock::Internal::SetSystemClockForTesting(&gMockClock);
59+
60+
if (ctx->mEventCounter.Init(0) != CHIP_NO_ERROR)
61+
{
62+
return FAILURE;
63+
}
64+
65+
ctx->mICDManager.Init(&ctx->testStorage, &ctx->GetFabricTable(), &mICDStateObserver);
66+
return SUCCESS;
67+
}
68+
69+
static int Finalize(void * context)
70+
{
71+
auto * ctx = static_cast<TestContext *>(context);
72+
ctx->mICDManager.Shutdown();
73+
app::EventManagement::DestroyEventManagement();
74+
System::Clock::Internal::SetSystemClockForTesting(gRealClock);
75+
DeviceLayer::SetSystemLayerForTesting(nullptr);
76+
77+
if (AppContext::Finalize(context) != SUCCESS)
78+
return FAILURE;
79+
80+
return SUCCESS;
81+
}
82+
83+
app::ICDManager mICDManager;
84+
85+
private:
86+
TestPersistentStorageDelegate testStorage;
87+
MonotonicallyIncreasingCounter<EventNumber> mEventCounter;
88+
};
89+
90+
} // namespace
91+
92+
namespace chip {
93+
namespace app {
94+
class TestICDManager
2295
{
23-
static nlTest sTests[] = { NL_TEST_SENTINEL() };
96+
public:
97+
/*
98+
* Advance the test Mock clock time by the amout passed in argument
99+
* and then force the SystemLayer Timer event loop. It will check for any expired timer,
100+
* and invoke their callbacks if there are any.
101+
*
102+
* @param time_ms: Value in milliseconds.
103+
*/
104+
static void AdvanceClockAndRunEventLoop(TestContext * ctx, uint32_t time_ms)
105+
{
106+
gMockClock.AdvanceMonotonic(System::Clock::Timeout(time_ms));
107+
ctx->GetIOContext().DriveIO();
108+
}
24109

25-
nlTestSuite cmSuite = { "TestICDManager", &sTests[0], nullptr, nullptr };
110+
static void TestICDModeIntervals(nlTestSuite * aSuite, void * aContext)
111+
{
112+
TestContext * ctx = static_cast<TestContext *>(aContext);
26113

27-
nlTestRunner(&cmSuite, nullptr);
28-
return (nlTestRunnerStats(&cmSuite));
114+
// After the init we should be in active mode
115+
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
116+
AdvanceClockAndRunEventLoop(ctx, IcdManagementServer::GetInstance().GetActiveModeInterval() + 1);
117+
// Active mode interval expired, ICDManager transitioned to the IdleMode.
118+
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
119+
AdvanceClockAndRunEventLoop(ctx, IcdManagementServer::GetInstance().GetIdleModeInterval() + 1);
120+
// Idle mode interval expired, ICDManager transitioned to the ActiveMode.
121+
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
122+
123+
// Events updating the Operation to Active mode can extend the current active mode time by 1 Active mode threshold.
124+
// Kick an active Threshold just before the end of the Active interval and validate that the active mode is extended.
125+
AdvanceClockAndRunEventLoop(ctx, IcdManagementServer::GetInstance().GetActiveModeInterval() - 1);
126+
ctx->mICDManager.UpdateOperationState(ICDManager::OperationalState::ActiveMode);
127+
AdvanceClockAndRunEventLoop(ctx, IcdManagementServer::GetInstance().GetActiveModeThreshold() / 2);
128+
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
129+
AdvanceClockAndRunEventLoop(ctx, IcdManagementServer::GetInstance().GetActiveModeThreshold());
130+
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
131+
}
132+
133+
static void TestKeepActivemodeRequests(nlTestSuite * aSuite, void * aContext)
134+
{
135+
TestContext * ctx = static_cast<TestContext *>(aContext);
136+
137+
// Setting a requirement will transition the ICD to active mode.
138+
ctx->mICDManager.SetKeepActiveModeRequirements(ICDManager::KeepActiveFlags::kCommissioningWindowOpen, true);
139+
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
140+
// Advance time so active mode interval expires.
141+
AdvanceClockAndRunEventLoop(ctx, IcdManagementServer::GetInstance().GetActiveModeInterval() + 1);
142+
// Requirement flag still set. We stay in active mode
143+
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
144+
145+
// Remove requirement. we should directly transition to idle mode.
146+
ctx->mICDManager.SetKeepActiveModeRequirements(ICDManager::KeepActiveFlags::kCommissioningWindowOpen, false);
147+
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
148+
149+
ctx->mICDManager.SetKeepActiveModeRequirements(ICDManager::KeepActiveFlags::kFailSafeArmed, true);
150+
// Requirement will transition us to active mode.
151+
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
152+
153+
// Advance time, but by less than the active mode interval and remove the requirement.
154+
// We should stay in active mode.
155+
AdvanceClockAndRunEventLoop(ctx, IcdManagementServer::GetInstance().GetActiveModeInterval() / 2);
156+
ctx->mICDManager.SetKeepActiveModeRequirements(ICDManager::KeepActiveFlags::kFailSafeArmed, false);
157+
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
158+
159+
// Advance time again, The activemode interval is completed.
160+
AdvanceClockAndRunEventLoop(ctx, IcdManagementServer::GetInstance().GetActiveModeInterval() + 1);
161+
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
162+
163+
// Set two requirements
164+
ctx->mICDManager.SetKeepActiveModeRequirements(ICDManager::KeepActiveFlags::kExpectingMsgResponse, true);
165+
ctx->mICDManager.SetKeepActiveModeRequirements(ICDManager::KeepActiveFlags::kAwaitingMsgAck, true);
166+
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
167+
// advance time so the active mode interval expires.
168+
AdvanceClockAndRunEventLoop(ctx, IcdManagementServer::GetInstance().GetActiveModeInterval() + 1);
169+
// A requirement flag is still set. We stay in active mode.
170+
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
171+
172+
// remove 1 requirement. Active mode is maintained
173+
ctx->mICDManager.SetKeepActiveModeRequirements(ICDManager::KeepActiveFlags::kExpectingMsgResponse, false);
174+
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
175+
// remove the last requirement
176+
ctx->mICDManager.SetKeepActiveModeRequirements(ICDManager::KeepActiveFlags::kAwaitingMsgAck, false);
177+
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
178+
}
179+
};
180+
181+
} // namespace app
182+
} // namespace chip
183+
184+
namespace {
185+
/**
186+
* Test Suite. It lists all the test functions.
187+
*/
188+
// clang-format off
189+
static const nlTest sTests[] =
190+
{
191+
NL_TEST_DEF("TestICDModeIntervals", TestICDManager::TestICDModeIntervals),
192+
NL_TEST_DEF("TestKeepActivemodeRequests", TestICDManager::TestKeepActivemodeRequests),
193+
NL_TEST_SENTINEL()
194+
};
195+
// clang-format on
196+
197+
// clang-format off
198+
nlTestSuite cmSuite =
199+
{
200+
"TestICDManager",
201+
&sTests[0],
202+
TestContext::Initialize,
203+
TestContext::Finalize
204+
};
205+
// clang-format on
206+
} // namespace
207+
208+
int TestSuiteICDManager()
209+
{
210+
return ExecuteTestsWithContext<TestContext>(&cmSuite);
29211
}
30212

31-
CHIP_REGISTER_TEST_SUITE(TestICDManager)
213+
CHIP_REGISTER_TEST_SUITE(TestSuiteICDManager)

0 commit comments

Comments
 (0)