Skip to content

Commit

Permalink
[ICD]Add unit tests for the ICD Manager operational states (#28729)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
2 people authored and pull[bot] committed Jul 22, 2024
1 parent bcd2427 commit 4152291
Show file tree
Hide file tree
Showing 3 changed files with 199 additions and 8 deletions.
7 changes: 5 additions & 2 deletions src/app/icd/ICDManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,12 @@ void ICDManager::Shutdown()

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;
}

Expand Down
6 changes: 6 additions & 0 deletions src/app/icd/ICDManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -67,6 +71,8 @@ class ICDManager
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);

Expand Down
194 changes: 188 additions & 6 deletions src/app/tests/TestICDManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
public:
void OnEnterActiveMode() {}
};

TestICDStateObserver mICDStateObserver;
static Clock::Internal::MockClock gMockClock;
static Clock::ClockBase * gRealClock;

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
{
static nlTest sTests[] = { NL_TEST_SENTINEL() };
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();
}

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

nlTestRunner(&cmSuite, nullptr);
return (nlTestRunnerStats(&cmSuite));
// 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)

0 comments on commit 4152291

Please sign in to comment.