From 94b71b1f97549f6a6622e1775a43100633789a60 Mon Sep 17 00:00:00 2001 From: lpbeliveau-silabs <112982107+lpbeliveau-silabs@users.noreply.github.com> Date: Thu, 7 Sep 2023 17:11:55 -0400 Subject: [PATCH] Added unit test for Early firing timers in the report scheduler (#29113) --- src/app/reporting/ReportScheduler.h | 1 + src/app/tests/TestReadInteraction.cpp | 143 ++++++++++++++++++++++++++ 2 files changed, 144 insertions(+) diff --git a/src/app/reporting/ReportScheduler.h b/src/app/reporting/ReportScheduler.h index 54b0bb5062286f..43441be73bddf5 100644 --- a/src/app/reporting/ReportScheduler.h +++ b/src/app/reporting/ReportScheduler.h @@ -171,6 +171,7 @@ class ReportScheduler : public ReadHandler::Observer, public ICDStateObserver ReadHandlerNode * node = FindReadHandlerNode(aReadHandler); return node->GetMaxTimestamp(); } + ReadHandlerNode * GetReadHandlerNode(const ReadHandler * aReadHandler) { return FindReadHandlerNode(aReadHandler); } #endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST protected: diff --git a/src/app/tests/TestReadInteraction.cpp b/src/app/tests/TestReadInteraction.cpp index ea464c3d9471b0..7a6c88d2f04341 100644 --- a/src/app/tests/TestReadInteraction.cpp +++ b/src/app/tests/TestReadInteraction.cpp @@ -321,6 +321,9 @@ bool IsDeviceTypeOnEndpoint(DeviceTypeId deviceType, EndpointId endpoint) class TestReadInteraction { + using Seconds16 = System::Clock::Seconds16; + using Milliseconds32 = System::Clock::Milliseconds32; + public: static void TestReadClient(nlTestSuite * apSuite, void * apContext); static void TestReadUnexpectedSubscriptionId(nlTestSuite * apSuite, void * apContext); @@ -349,6 +352,7 @@ class TestReadInteraction static void TestReadChunking(nlTestSuite * apSuite, void * apContext); static void TestSetDirtyBetweenChunks(nlTestSuite * apSuite, void * apContext); static void TestSubscribeRoundtrip(nlTestSuite * apSuite, void * apContext); + static void TestSubscribeEarlyReport(nlTestSuite * apSuite, void * apContext); static void TestSubscribeUrgentWildcardEvent(nlTestSuite * apSuite, void * apContext); static void TestSubscribeWildcard(nlTestSuite * apSuite, void * apContext); static void TestSubscribePartialOverlap(nlTestSuite * apSuite, void * apContext); @@ -2099,6 +2103,144 @@ void TestReadInteraction::TestSubscribeRoundtrip(nlTestSuite * apSuite, void * a NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); } +void TestReadInteraction::TestSubscribeEarlyReport(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + CHIP_ERROR err = CHIP_NO_ERROR; + + Messaging::ReliableMessageMgr * rm = ctx.GetExchangeManager().GetReliableMessageMgr(); + // Shouldn't have anything in the retransmit table when starting the test. + NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); + + MockInteractionModelApp delegate; + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + ReportSchedulerImpl * reportScheduler = app::reporting::GetDefaultReportScheduler(); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), reportScheduler); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); + + ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); + chip::app::EventPathParams eventPathParams[1]; + readPrepareParams.mpEventPathParamsList = eventPathParams; + readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEventEndpointId; + readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestEventClusterId; + + readPrepareParams.mEventPathParamsListSize = 1; + + readPrepareParams.mpAttributePathParamsList = nullptr; + readPrepareParams.mAttributePathParamsListSize = 0; + + readPrepareParams.mMinIntervalFloorSeconds = 1; + readPrepareParams.mMaxIntervalCeilingSeconds = 5; + + readPrepareParams.mKeepSubscriptions = true; + + { + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), delegate, + chip::app::ReadClient::InteractionType::Subscribe); + readPrepareParams.mpEventPathParamsList[0].mIsUrgentEvent = true; + delegate.mGotEventResponse = false; + err = readClient.SendRequest(readPrepareParams); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + ctx.DrainAndServiceIO(); + System::Clock::Timestamp startTime = gMockClock.GetMonotonicTimestamp(); + + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadHandlers() == 1); + NL_TEST_ASSERT(apSuite, engine->ActiveHandlerAt(0) != nullptr); + delegate.mpReadHandler = engine->ActiveHandlerAt(0); + + NL_TEST_ASSERT(apSuite, delegate.mGotEventResponse); + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadHandlers(ReadHandler::InteractionType::Subscribe) == 1); + + NL_TEST_ASSERT(apSuite, + reportScheduler->GetMinTimestampForHandler(delegate.mpReadHandler) == + gMockClock.GetMonotonicTimestamp() + Seconds16(readPrepareParams.mMinIntervalFloorSeconds)); + NL_TEST_ASSERT(apSuite, + reportScheduler->GetMaxTimestampForHandler(delegate.mpReadHandler) == + gMockClock.GetMonotonicTimestamp() + Seconds16(readPrepareParams.mMaxIntervalCeilingSeconds)); + + // Confirm that the node is scheduled to run + NL_TEST_ASSERT(apSuite, reportScheduler->IsReportScheduled(delegate.mpReadHandler)); + ReportScheduler::ReadHandlerNode * node = reportScheduler->GetReadHandlerNode(delegate.mpReadHandler); + NL_TEST_ASSERT(apSuite, node != nullptr); + + GenerateEvents(apSuite, apContext); + + // modify the node's min timestamp to be 50ms later than the timer expiration time + node->SetIntervalTimeStamps(delegate.mpReadHandler, startTime + Milliseconds32(50)); + NL_TEST_ASSERT(apSuite, + reportScheduler->GetMinTimestampForHandler(delegate.mpReadHandler) == + gMockClock.GetMonotonicTimestamp() + Seconds16(readPrepareParams.mMinIntervalFloorSeconds) + + Milliseconds32(50)); + + NL_TEST_ASSERT(apSuite, reportScheduler->GetMinTimestampForHandler(delegate.mpReadHandler) > startTime); + NL_TEST_ASSERT(apSuite, delegate.mpReadHandler->IsDirty()); + + // Advance monotonic timestamp for min interval to elapse + gMockClock.AdvanceMonotonic(Seconds16(readPrepareParams.mMinIntervalFloorSeconds)); + NL_TEST_ASSERT(apSuite, !InteractionModelEngine::GetInstance()->GetReportingEngine().IsRunScheduled()); + // Service Timer expired event + ctx.GetIOContext().DriveIO(); + + // Verify the ReadHandler is considered as reportable even if its node's min timestamp has not expired + NL_TEST_ASSERT(apSuite, + reportScheduler->GetMinTimestampForHandler(delegate.mpReadHandler) > gMockClock.GetMonotonicTimestamp()); + NL_TEST_ASSERT(apSuite, reportScheduler->IsReportableNow(delegate.mpReadHandler)); + NL_TEST_ASSERT(apSuite, InteractionModelEngine::GetInstance()->GetReportingEngine().IsRunScheduled()); + + // Service Engine Run + ctx.GetIOContext().DriveIO(); + // Service EventManagement event + ctx.GetIOContext().DriveIO(); + ctx.GetIOContext().DriveIO(); + NL_TEST_ASSERT(apSuite, delegate.mGotEventResponse); + + // Check the logic works for timer expiring at maximum as well + NL_TEST_ASSERT(apSuite, !delegate.mpReadHandler->IsDirty()); + delegate.mGotEventResponse = false; + NL_TEST_ASSERT(apSuite, + reportScheduler->GetMinTimestampForHandler(delegate.mpReadHandler) == + gMockClock.GetMonotonicTimestamp() + Seconds16(readPrepareParams.mMinIntervalFloorSeconds)); + NL_TEST_ASSERT(apSuite, + reportScheduler->GetMaxTimestampForHandler(delegate.mpReadHandler) == + gMockClock.GetMonotonicTimestamp() + Seconds16(readPrepareParams.mMaxIntervalCeilingSeconds)); + + // Confirm that the node is scheduled to run + NL_TEST_ASSERT(apSuite, reportScheduler->IsReportScheduled(delegate.mpReadHandler)); + NL_TEST_ASSERT(apSuite, node != nullptr); + + // modify the node's max timestamp to be 50ms later than the timer expiration time + node->SetIntervalTimeStamps(delegate.mpReadHandler, gMockClock.GetMonotonicTimestamp() + Milliseconds32(50)); + NL_TEST_ASSERT(apSuite, + reportScheduler->GetMaxTimestampForHandler(delegate.mpReadHandler) == + gMockClock.GetMonotonicTimestamp() + Seconds16(readPrepareParams.mMaxIntervalCeilingSeconds) + + Milliseconds32(50)); + + // Advance monotonic timestamp for min interval to elapse + gMockClock.AdvanceMonotonic(Seconds16(readPrepareParams.mMaxIntervalCeilingSeconds)); + + NL_TEST_ASSERT(apSuite, !InteractionModelEngine::GetInstance()->GetReportingEngine().IsRunScheduled()); + // Service Timer expired event + ctx.GetIOContext().DriveIO(); + + // Verify the ReadHandler is considered as reportable even if its node's min timestamp has not expired + NL_TEST_ASSERT(apSuite, + reportScheduler->GetMaxTimestampForHandler(delegate.mpReadHandler) > gMockClock.GetMonotonicTimestamp()); + NL_TEST_ASSERT(apSuite, reportScheduler->IsReportableNow(delegate.mpReadHandler)); + NL_TEST_ASSERT(apSuite, !reportScheduler->IsReportScheduled(delegate.mpReadHandler)); + NL_TEST_ASSERT(apSuite, !delegate.mpReadHandler->IsDirty()); + NL_TEST_ASSERT(apSuite, InteractionModelEngine::GetInstance()->GetReportingEngine().IsRunScheduled()); + // Service Engine Run + ctx.GetIOContext().DriveIO(); + // Service EventManagement event + ctx.GetIOContext().DriveIO(); + ctx.GetIOContext().DriveIO(); + NL_TEST_ASSERT(apSuite, reportScheduler->IsReportScheduled(delegate.mpReadHandler)); + NL_TEST_ASSERT(apSuite, !InteractionModelEngine::GetInstance()->GetReportingEngine().IsRunScheduled()); + } +} + void TestReadInteraction::TestSubscribeUrgentWildcardEvent(nlTestSuite * apSuite, void * apContext) { TestContext & ctx = *static_cast(apContext); @@ -4796,6 +4938,7 @@ const nlTest sTests[] = NL_TEST_DEF("TestICDProcessSubscribeRequestInvalidIdleModeInterval", chip::app::TestReadInteraction::TestICDProcessSubscribeRequestInvalidIdleModeInterval), #endif // #if CHIP_CONFIG_ENABLE_ICD_SERVER NL_TEST_DEF("TestSubscribeRoundtrip", chip::app::TestReadInteraction::TestSubscribeRoundtrip), + NL_TEST_DEF("TestSubscribeEarlyReport", chip::app::TestReadInteraction::TestSubscribeEarlyReport), NL_TEST_DEF("TestPostSubscribeRoundtripChunkReport", chip::app::TestReadInteraction::TestPostSubscribeRoundtripChunkReport), NL_TEST_DEF("TestReadClientReceiveInvalidMessage", chip::app::TestReadInteraction::TestReadClientReceiveInvalidMessage), NL_TEST_DEF("TestSubscribeClientReceiveInvalidStatusResponse", chip::app::TestReadInteraction::TestSubscribeClientReceiveInvalidStatusResponse),