blob: 295ad9355f5f3d2e66bbc9f3923d4c27f5fddb51 [file] [log] [blame]
/*
*
* Copyright (c) 2021 Project CHIP Authors
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "transport/SecureSession.h"
#include <app-common/zap-generated/cluster-objects.h>
#include <app/ClusterStateCache.h>
#include <app/InteractionModelEngine.h>
#include <app/tests/AppTestContext.h>
#include <app/util/mock/Constants.h>
#include <app/util/mock/Functions.h>
#include <controller/ReadInteraction.h>
#include <lib/support/ErrorStr.h>
#include <lib/support/UnitTestRegistration.h>
#include <lib/support/logging/CHIPLogging.h>
#include <messaging/tests/MessagingContext.h>
#include <nlunit-test.h>
#include <protocols/interaction_model/Constants.h>
using TestContext = chip::Test::AppContext;
using namespace chip;
using namespace chip::app::Clusters;
using namespace chip::Protocols;
namespace {
constexpr EndpointId kTestEndpointId = 1;
constexpr DataVersion kDataVersion = 5;
enum ResponseDirective
{
kSendDataResponse,
kSendDataError
};
ResponseDirective responseDirective;
// Number of reads of TestCluster::Attributes::Int16u that we have observed.
// Every read will increment this count by 1 and return the new value.
uint16_t totalReadCount = 0;
} // namespace
namespace chip {
namespace app {
CHIP_ERROR ReadSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, bool aIsFabricFiltered,
const ConcreteReadAttributePath & aPath, AttributeReportIBs::Builder & aAttributeReports,
AttributeValueEncoder::AttributeEncodeState * apEncoderState)
{
if (aPath.mClusterId >= Test::kMockEndpointMin)
{
return Test::ReadSingleMockClusterData(aSubjectDescriptor.fabricIndex, aPath, aAttributeReports, apEncoderState);
}
if (responseDirective == kSendDataResponse)
{
if (aPath.mClusterId == app::Clusters::TestCluster::Id &&
aPath.mAttributeId == app::Clusters::TestCluster::Attributes::ListFabricScoped::Id)
{
AttributeValueEncoder::AttributeEncodeState state =
(apEncoderState == nullptr ? AttributeValueEncoder::AttributeEncodeState() : *apEncoderState);
AttributeValueEncoder valueEncoder(aAttributeReports, aSubjectDescriptor.fabricIndex, aPath,
kDataVersion /* data version */, aIsFabricFiltered, state);
return valueEncoder.EncodeList([aSubjectDescriptor](const auto & encoder) -> CHIP_ERROR {
app::Clusters::TestCluster::Structs::TestFabricScoped::Type val;
val.fabricIndex = aSubjectDescriptor.fabricIndex;
ReturnErrorOnFailure(encoder.Encode(val));
val.fabricIndex = (val.fabricIndex == 1) ? 2 : 1;
ReturnErrorOnFailure(encoder.Encode(val));
return CHIP_NO_ERROR;
});
}
if (aPath.mClusterId == app::Clusters::TestCluster::Id &&
aPath.mAttributeId == app::Clusters::TestCluster::Attributes::Int16u::Id)
{
AttributeValueEncoder::AttributeEncodeState state =
(apEncoderState == nullptr ? AttributeValueEncoder::AttributeEncodeState() : *apEncoderState);
AttributeValueEncoder valueEncoder(aAttributeReports, aSubjectDescriptor.fabricIndex, aPath,
kDataVersion /* data version */, aIsFabricFiltered, state);
return valueEncoder.Encode(++totalReadCount);
}
AttributeReportIB::Builder & attributeReport = aAttributeReports.CreateAttributeReport();
ReturnErrorOnFailure(aAttributeReports.GetError());
AttributeDataIB::Builder & attributeData = attributeReport.CreateAttributeData();
ReturnErrorOnFailure(attributeReport.GetError());
TestCluster::Attributes::ListStructOctetString::TypeInfo::Type value;
TestCluster::Structs::TestListStructOctet::Type valueBuf[4];
value = valueBuf;
uint8_t i = 0;
for (auto & item : valueBuf)
{
item.fabricIndex = i;
i++;
}
attributeData.DataVersion(kDataVersion);
ReturnErrorOnFailure(attributeData.GetError());
AttributePathIB::Builder & attributePath = attributeData.CreatePath();
attributePath.Endpoint(aPath.mEndpointId).Cluster(aPath.mClusterId).Attribute(aPath.mAttributeId).EndOfAttributePathIB();
ReturnErrorOnFailure(attributePath.GetError());
ReturnErrorOnFailure(
DataModel::Encode(*(attributeData.GetWriter()), TLV::ContextTag(to_underlying(AttributeDataIB::Tag::kData)), value));
ReturnErrorOnFailure(attributeData.EndOfAttributeDataIB().GetError());
return attributeReport.EndOfAttributeReportIB().GetError();
}
AttributeReportIB::Builder & attributeReport = aAttributeReports.CreateAttributeReport();
ReturnErrorOnFailure(aAttributeReports.GetError());
AttributeStatusIB::Builder & attributeStatus = attributeReport.CreateAttributeStatus();
AttributePathIB::Builder & attributePath = attributeStatus.CreatePath();
attributePath.Endpoint(aPath.mEndpointId).Cluster(aPath.mClusterId).Attribute(aPath.mAttributeId).EndOfAttributePathIB();
ReturnErrorOnFailure(attributePath.GetError());
StatusIB::Builder & errorStatus = attributeStatus.CreateErrorStatus();
ReturnErrorOnFailure(attributeStatus.GetError());
errorStatus.EncodeStatusIB(StatusIB(Protocols::InteractionModel::Status::Busy));
attributeStatus.EndOfAttributeStatusIB();
ReturnErrorOnFailure(attributeStatus.GetError());
return attributeReport.EndOfAttributeReportIB().GetError();
}
bool IsClusterDataVersionEqual(const ConcreteClusterPath & aConcreteClusterPath, DataVersion aRequiredVersion)
{
if (aRequiredVersion == kDataVersion)
{
return true;
}
if (Test::GetVersion() == aRequiredVersion)
{
return true;
}
return false;
}
bool IsDeviceTypeOnEndpoint(DeviceTypeId deviceType, EndpointId endpoint)
{
return false;
}
} // namespace app
} // namespace chip
namespace {
class TestReadInteraction : public app::ReadHandler::ApplicationCallback
{
public:
TestReadInteraction() {}
static void TestReadAttributeResponse(nlTestSuite * apSuite, void * apContext);
static void TestReadDataVersionFilter(nlTestSuite * apSuite, void * apContext);
static void TestReadAttributeError(nlTestSuite * apSuite, void * apContext);
static void TestReadAttributeTimeout(nlTestSuite * apSuite, void * apContext);
static void TestReadEventResponse(nlTestSuite * apSuite, void * apContext);
static void TestReadFabricScopedWithoutFabricFilter(nlTestSuite * apSuite, void * apContext);
static void TestReadFabricScopedWithFabricFilter(nlTestSuite * apSuite, void * apContext);
static void TestReadHandler_MultipleSubscriptions(nlTestSuite * apSuite, void * apContext);
static void TestReadHandler_SubscriptionAppRejection(nlTestSuite * apSuite, void * apContext);
static void TestReadHandler_MultipleReads(nlTestSuite * apSuite, void * apContext);
static void TestReadHandler_OneSubscribeMultipleReads(nlTestSuite * apSuite, void * apContext);
static void TestReadHandler_TwoSubscribesMultipleReads(nlTestSuite * apSuite, void * apContext);
static void TestReadHandler_MultipleSubscriptionsWithDataVersionFilter(nlTestSuite * apSuite, void * apContext);
static void TestReadHandler_SubscriptionAlteredReportingIntervals(nlTestSuite * apSuite, void * apContext);
static void TestReadHandlerResourceExhaustion_MultipleSubscriptions(nlTestSuite * apSuite, void * apContext);
static void TestReadHandlerResourceExhaustion_MultipleReads(nlTestSuite * apSuite, void * apContext);
static void TestReadSubscribeAttributeResponseWithCache(nlTestSuite * apSuite, void * apContext);
private:
static constexpr uint16_t kTestMinInterval = 33;
static constexpr uint16_t kTestMaxInterval = 66;
CHIP_ERROR OnSubscriptionRequested(app::ReadHandler & aReadHandler, Transport::SecureSession & aSecureSession)
{
VerifyOrReturnError(!mEmitSubscriptionError, CHIP_ERROR_INVALID_ARGUMENT);
if (mAlterSubscriptionIntervals)
{
ReturnErrorOnFailure(aReadHandler.SetReportingIntervals(kTestMinInterval, kTestMaxInterval));
}
return CHIP_NO_ERROR;
}
void OnSubscriptionEstablished(app::ReadHandler & aReadHandler) { mNumActiveSubscriptions++; }
void OnSubscriptionTerminated(app::ReadHandler & aReadHandler) { mNumActiveSubscriptions--; }
// Issue the given number of reads in parallel and wait for them all to
// succeed.
static void MultipleReadHelper(nlTestSuite * apSuite, TestContext & aCtx, size_t aReadCount);
// Establish the given number of subscriptions, then issue the given number
// of reads in parallel and wait for them all to succeed.
static void SubscribeThenReadHelper(nlTestSuite * apSuite, TestContext & aCtx, size_t aSubscribeCount, size_t aReadCount);
bool mEmitSubscriptionError = false;
int32_t mNumActiveSubscriptions = 0;
bool mAlterSubscriptionIntervals = false;
};
TestReadInteraction gTestReadInteraction;
class MockInteractionModelApp : public chip::app::ClusterStateCache::Callback
{
public:
void OnEventData(const chip::app::EventHeader & aEventHeader, chip::TLV::TLVReader * apData,
const chip::app::StatusIB * apStatus) override
{}
void OnAttributeData(const chip::app::ConcreteDataAttributePath & aPath, chip::TLV::TLVReader * apData,
const chip::app::StatusIB & status) override
{
if (status.mStatus == chip::Protocols::InteractionModel::Status::Success)
{
mNumAttributeResponse++;
}
}
void OnError(CHIP_ERROR aError) override
{
mError = aError;
mReadError = true;
}
void OnDone() override {}
void OnDeallocatePaths(chip::app::ReadPrepareParams && aReadPrepareParams) override
{
if (aReadPrepareParams.mpAttributePathParamsList != nullptr)
{
delete[] aReadPrepareParams.mpAttributePathParamsList;
}
if (aReadPrepareParams.mpEventPathParamsList != nullptr)
{
delete[] aReadPrepareParams.mpEventPathParamsList;
}
if (aReadPrepareParams.mpDataVersionFilterList != nullptr)
{
delete[] aReadPrepareParams.mpDataVersionFilterList;
}
}
int mNumAttributeResponse = 0;
bool mReadError = false;
CHIP_ERROR mError = CHIP_NO_ERROR;
};
void TestReadInteraction::TestReadAttributeResponse(nlTestSuite * apSuite, void * apContext)
{
TestContext & ctx = *static_cast<TestContext *>(apContext);
auto sessionHandle = ctx.GetSessionBobToAlice();
bool onSuccessCbInvoked = false, onFailureCbInvoked = false;
responseDirective = kSendDataResponse;
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onSuccessCb = [apSuite, &onSuccessCbInvoked](const app::ConcreteDataAttributePath & attributePath,
const auto & dataResponse) {
uint8_t i = 0;
NL_TEST_ASSERT(apSuite, attributePath.mDataVersion.HasValue() && attributePath.mDataVersion.Value() == kDataVersion);
auto iter = dataResponse.begin();
while (iter.Next())
{
auto & item = iter.GetValue();
NL_TEST_ASSERT(apSuite, item.fabricIndex == i);
i++;
}
NL_TEST_ASSERT(apSuite, i == 4);
NL_TEST_ASSERT(apSuite, iter.GetStatus() == CHIP_NO_ERROR);
onSuccessCbInvoked = true;
};
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onFailureCb = [&onFailureCbInvoked](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) {
onFailureCbInvoked = true;
};
Controller::ReadAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>(&ctx.GetExchangeManager(), sessionHandle,
kTestEndpointId, onSuccessCb, onFailureCb);
ctx.DrainAndServiceIO();
NL_TEST_ASSERT(apSuite, onSuccessCbInvoked && !onFailureCbInvoked);
NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0);
NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0);
NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
}
void TestReadInteraction::TestReadDataVersionFilter(nlTestSuite * apSuite, void * apContext)
{
TestContext & ctx = *static_cast<TestContext *>(apContext);
auto sessionHandle = ctx.GetSessionBobToAlice();
bool onSuccessCbInvoked = false, onFailureCbInvoked = false;
responseDirective = kSendDataResponse;
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onSuccessCb = [apSuite, &onSuccessCbInvoked](const app::ConcreteDataAttributePath & attributePath,
const auto & dataResponse) {
uint8_t i = 0;
auto iter = dataResponse.begin();
while (iter.Next())
{
auto & item = iter.GetValue();
NL_TEST_ASSERT(apSuite, item.fabricIndex == i);
i++;
}
NL_TEST_ASSERT(apSuite, i == 4);
NL_TEST_ASSERT(apSuite, iter.GetStatus() == CHIP_NO_ERROR);
onSuccessCbInvoked = true;
};
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onFailureCb = [&onFailureCbInvoked](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) {
onFailureCbInvoked = true;
};
Optional<DataVersion> dataVersion(kDataVersion);
Controller::ReadAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>(
&ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, true, dataVersion);
ctx.DrainAndServiceIO();
NL_TEST_ASSERT(apSuite, !onSuccessCbInvoked && !onFailureCbInvoked);
NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0);
NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0);
NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
}
void TestReadInteraction::TestReadSubscribeAttributeResponseWithCache(nlTestSuite * apSuite, void * apContext)
{
TestContext & ctx = *static_cast<TestContext *>(apContext);
CHIP_ERROR err = CHIP_NO_ERROR;
responseDirective = kSendDataResponse;
MockInteractionModelApp delegate;
chip::app::ClusterStateCache cache(delegate);
auto * engine = chip::app::InteractionModelEngine::GetInstance();
err = engine->Init(&ctx.GetExchangeManager());
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
chip::app::EventPathParams eventPathParams[100];
for (uint32_t index = 0; index < 100; index++)
{
eventPathParams[index].mEndpointId = Test::kMockEndpoint3;
eventPathParams[index].mClusterId = Test::MockClusterId(2);
eventPathParams[index].mEventId = 0;
}
chip::app::ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice());
readPrepareParams.mMinIntervalFloorSeconds = 0;
readPrepareParams.mMaxIntervalCeilingSeconds = 4;
//
// Test the application callback as well to ensure we get the right number of SubscriptionEstablishment/Termination
// callbacks.
//
app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction);
int testId = 0;
// Initial Read of E2C3A1, E2C3A2 and E3C2A2.
// Expect no versions would be cached.
{
testId++;
ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId);
app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(),
cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read);
chip::app::AttributePathParams attributePathParams1[3];
attributePathParams1[0].mEndpointId = Test::kMockEndpoint2;
attributePathParams1[0].mClusterId = Test::MockClusterId(3);
attributePathParams1[0].mAttributeId = Test::MockAttributeId(1);
attributePathParams1[1].mEndpointId = Test::kMockEndpoint2;
attributePathParams1[1].mClusterId = Test::MockClusterId(3);
attributePathParams1[1].mAttributeId = Test::MockAttributeId(2);
attributePathParams1[2].mEndpointId = Test::kMockEndpoint3;
attributePathParams1[2].mClusterId = Test::MockClusterId(2);
attributePathParams1[2].mAttributeId = Test::MockAttributeId(2);
readPrepareParams.mpAttributePathParamsList = attributePathParams1;
readPrepareParams.mAttributePathParamsListSize = 3;
err = readClient.SendRequest(readPrepareParams);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
ctx.DrainAndServiceIO();
NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 3);
NL_TEST_ASSERT(apSuite, !delegate.mReadError);
Optional<DataVersion> version1;
NL_TEST_ASSERT(apSuite, cache.GetVersion(Test::kMockEndpoint2, Test::MockClusterId(3), version1) == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite, !version1.HasValue());
Optional<DataVersion> version2;
NL_TEST_ASSERT(apSuite, cache.GetVersion(Test::kMockEndpoint3, Test::MockClusterId(2), version2) == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite, !version2.HasValue());
delegate.mNumAttributeResponse = 0;
}
// Second read of E2C2A* and E3C2A2. We cannot use the stored data versions in the cache since there is no cached version from
// previous test. Expect cache E2C2 version
{
testId++;
ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId);
app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(),
cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read);
chip::app::AttributePathParams attributePathParams2[2];
attributePathParams2[0].mEndpointId = Test::kMockEndpoint2;
attributePathParams2[0].mClusterId = Test::MockClusterId(3);
attributePathParams2[0].mAttributeId = kInvalidAttributeId;
attributePathParams2[1].mEndpointId = Test::kMockEndpoint3;
attributePathParams2[1].mClusterId = Test::MockClusterId(2);
attributePathParams2[1].mAttributeId = Test::MockAttributeId(2);
readPrepareParams.mpAttributePathParamsList = attributePathParams2;
readPrepareParams.mAttributePathParamsListSize = 2;
err = readClient.SendRequest(readPrepareParams);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
ctx.DrainAndServiceIO();
// There are supported 2 global and 3 non-global attributes in E2C2A* and 1 E3C2A2
NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 6);
NL_TEST_ASSERT(apSuite, !delegate.mReadError);
Optional<DataVersion> version1;
NL_TEST_ASSERT(apSuite, cache.GetVersion(Test::kMockEndpoint2, Test::MockClusterId(3), version1) == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite, version1.HasValue() && (version1.Value() == 0));
Optional<DataVersion> version2;
NL_TEST_ASSERT(apSuite, cache.GetVersion(Test::kMockEndpoint3, Test::MockClusterId(2), version2) == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite, !version2.HasValue());
delegate.mNumAttributeResponse = 0;
}
// Third read of E2C3A1, E2C3A2, and E3C2A2. It would use the stored data versions in the cache since our subsequent read's C1A1
// path intersects with previous cached data version Expect no E2C3 attributes in report, only E3C2A1 attribute in report
{
testId++;
ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId);
app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(),
cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read);
chip::app::AttributePathParams attributePathParams1[3];
attributePathParams1[0].mEndpointId = Test::kMockEndpoint2;
attributePathParams1[0].mClusterId = Test::MockClusterId(3);
attributePathParams1[0].mAttributeId = Test::MockAttributeId(1);
attributePathParams1[1].mEndpointId = Test::kMockEndpoint2;
attributePathParams1[1].mClusterId = Test::MockClusterId(3);
attributePathParams1[1].mAttributeId = Test::MockAttributeId(2);
attributePathParams1[2].mEndpointId = Test::kMockEndpoint3;
attributePathParams1[2].mClusterId = Test::MockClusterId(2);
attributePathParams1[2].mAttributeId = Test::MockAttributeId(2);
readPrepareParams.mpAttributePathParamsList = attributePathParams1;
readPrepareParams.mAttributePathParamsListSize = 3;
err = readClient.SendRequest(readPrepareParams);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
ctx.DrainAndServiceIO();
NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 1);
NL_TEST_ASSERT(apSuite, !delegate.mReadError);
Optional<DataVersion> version1;
NL_TEST_ASSERT(apSuite, cache.GetVersion(Test::kMockEndpoint2, Test::MockClusterId(3), version1) == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite, version1.HasValue() && (version1.Value() == 0));
Optional<DataVersion> version2;
NL_TEST_ASSERT(apSuite, cache.GetVersion(Test::kMockEndpoint3, Test::MockClusterId(2), version2) == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite, !version2.HasValue());
delegate.mNumAttributeResponse = 0;
}
// Fourth read of E2C3A* and E3C2A2. It would use the stored data versions in the cache since our subsequent read's C1A* path
// intersects with previous cached data version Expect no C1 attributes in report, only E3C2A2 attribute in report
{
testId++;
ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId);
app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(),
cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read);
chip::app::AttributePathParams attributePathParams2[2];
attributePathParams2[0].mEndpointId = Test::kMockEndpoint2;
attributePathParams2[0].mClusterId = Test::MockClusterId(3);
attributePathParams2[0].mAttributeId = kInvalidAttributeId;
attributePathParams2[1].mEndpointId = Test::kMockEndpoint3;
attributePathParams2[1].mClusterId = Test::MockClusterId(2);
attributePathParams2[1].mAttributeId = Test::MockAttributeId(2);
readPrepareParams.mpAttributePathParamsList = attributePathParams2;
readPrepareParams.mAttributePathParamsListSize = 2;
err = readClient.SendRequest(readPrepareParams);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
ctx.DrainAndServiceIO();
NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 1);
NL_TEST_ASSERT(apSuite, !delegate.mReadError);
Optional<DataVersion> version1;
NL_TEST_ASSERT(apSuite, cache.GetVersion(Test::kMockEndpoint2, Test::MockClusterId(3), version1) == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite, version1.HasValue() && (version1.Value() == 0));
Optional<DataVersion> version2;
NL_TEST_ASSERT(apSuite, cache.GetVersion(Test::kMockEndpoint3, Test::MockClusterId(2), version2) == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite, !version2.HasValue());
delegate.mNumAttributeResponse = 0;
}
Test::BumpVersion();
// Fifth read of E2C3A1, E2C3A2 and E3C2A2. It would use the stored data versions in the cache since our subsequent read's C1A*
// path intersects with previous cached data version, server's version is changed. Expect E2C3A1, E2C3A2 and E3C2A2 attribute in
// report, and invalidate the cached pending and committed data version since no wildcard attributes exists in mRequestPathSet.
{
testId++;
ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId);
app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(),
cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read);
chip::app::AttributePathParams attributePathParams1[3];
attributePathParams1[0].mEndpointId = Test::kMockEndpoint2;
attributePathParams1[0].mClusterId = Test::MockClusterId(3);
attributePathParams1[0].mAttributeId = Test::MockAttributeId(1);
attributePathParams1[1].mEndpointId = Test::kMockEndpoint2;
attributePathParams1[1].mClusterId = Test::MockClusterId(3);
attributePathParams1[1].mAttributeId = Test::MockAttributeId(2);
attributePathParams1[2].mEndpointId = Test::kMockEndpoint3;
attributePathParams1[2].mClusterId = Test::MockClusterId(2);
attributePathParams1[2].mAttributeId = Test::MockAttributeId(2);
readPrepareParams.mpAttributePathParamsList = attributePathParams1;
readPrepareParams.mAttributePathParamsListSize = 3;
err = readClient.SendRequest(readPrepareParams);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
ctx.DrainAndServiceIO();
NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 3);
NL_TEST_ASSERT(apSuite, !delegate.mReadError);
Optional<DataVersion> version1;
NL_TEST_ASSERT(apSuite, cache.GetVersion(Test::kMockEndpoint2, Test::MockClusterId(3), version1) == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite, !version1.HasValue());
Optional<DataVersion> version2;
NL_TEST_ASSERT(apSuite, cache.GetVersion(Test::kMockEndpoint3, Test::MockClusterId(2), version2) == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite, !version2.HasValue());
delegate.mNumAttributeResponse = 0;
}
// Sixth read of E2C3A1, E2C3A2 and E3C2A2. It would use none stored data versions in the cache since previous read does not
// cache any committed data version. Expect E2C3A1, E2C3A2 and E3C2A2 attribute in report
{
testId++;
ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId);
app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(),
cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read);
chip::app::AttributePathParams attributePathParams1[3];
attributePathParams1[0].mEndpointId = Test::kMockEndpoint2;
attributePathParams1[0].mClusterId = Test::MockClusterId(3);
attributePathParams1[0].mAttributeId = Test::MockAttributeId(1);
attributePathParams1[1].mEndpointId = Test::kMockEndpoint2;
attributePathParams1[1].mClusterId = Test::MockClusterId(3);
attributePathParams1[1].mAttributeId = Test::MockAttributeId(2);
attributePathParams1[2].mEndpointId = Test::kMockEndpoint3;
attributePathParams1[2].mClusterId = Test::MockClusterId(2);
attributePathParams1[2].mAttributeId = Test::MockAttributeId(2);
readPrepareParams.mpAttributePathParamsList = attributePathParams1;
readPrepareParams.mAttributePathParamsListSize = 3;
err = readClient.SendRequest(readPrepareParams);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
ctx.DrainAndServiceIO();
NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 3);
NL_TEST_ASSERT(apSuite, !delegate.mReadError);
Optional<DataVersion> version1;
NL_TEST_ASSERT(apSuite, cache.GetVersion(Test::kMockEndpoint2, Test::MockClusterId(3), version1) == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite, !version1.HasValue());
Optional<DataVersion> version2;
NL_TEST_ASSERT(apSuite, cache.GetVersion(Test::kMockEndpoint3, Test::MockClusterId(2), version2) == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite, !version2.HasValue());
delegate.mNumAttributeResponse = 0;
}
// Seventh read of E2C3A* and E3C2A2, here there is no cached data version filter
// Expect E2C3A* attributes in report, and E3C2A2 attribute in report and cache latest data version
{
testId++;
ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId);
app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(),
cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read);
chip::app::AttributePathParams attributePathParams2[2];
attributePathParams2[0].mEndpointId = Test::kMockEndpoint2;
attributePathParams2[0].mClusterId = Test::MockClusterId(3);
attributePathParams2[0].mAttributeId = kInvalidAttributeId;
attributePathParams2[1].mEndpointId = Test::kMockEndpoint3;
attributePathParams2[1].mClusterId = Test::MockClusterId(2);
attributePathParams2[1].mAttributeId = Test::MockAttributeId(2);
readPrepareParams.mpAttributePathParamsList = attributePathParams2;
readPrepareParams.mAttributePathParamsListSize = 2;
err = readClient.SendRequest(readPrepareParams);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
ctx.DrainAndServiceIO();
NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 6);
NL_TEST_ASSERT(apSuite, !delegate.mReadError);
Optional<DataVersion> version1;
NL_TEST_ASSERT(apSuite, cache.GetVersion(Test::kMockEndpoint2, Test::MockClusterId(3), version1) == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite, version1.HasValue() && (version1.Value() == 1));
Optional<DataVersion> version2;
NL_TEST_ASSERT(apSuite, cache.GetVersion(Test::kMockEndpoint3, Test::MockClusterId(2), version2) == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite, !version2.HasValue());
delegate.mNumAttributeResponse = 0;
}
// Eighth read of E2C3A* and E3C2A2, and inject a large amount of event path list, then it would try to apply previous cache
// latest data version and construct data version list but no enough memory, finally fully rollback data version filter. Expect
// E2C3A* attributes in report, and E3C2A2 attribute in report
{
testId++;
ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId);
app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(),
cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read);
chip::app::AttributePathParams attributePathParams2[2];
attributePathParams2[0].mEndpointId = Test::kMockEndpoint2;
attributePathParams2[0].mClusterId = Test::MockClusterId(3);
attributePathParams2[0].mAttributeId = kInvalidAttributeId;
attributePathParams2[1].mEndpointId = Test::kMockEndpoint3;
attributePathParams2[1].mClusterId = Test::MockClusterId(2);
attributePathParams2[1].mAttributeId = Test::MockAttributeId(2);
readPrepareParams.mpAttributePathParamsList = attributePathParams2;
readPrepareParams.mAttributePathParamsListSize = 2;
readPrepareParams.mpEventPathParamsList = eventPathParams;
readPrepareParams.mEventPathParamsListSize = 64;
err = readClient.SendRequest(readPrepareParams);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
ctx.DrainAndServiceIO();
NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 6);
NL_TEST_ASSERT(apSuite, !delegate.mReadError);
Optional<DataVersion> version1;
NL_TEST_ASSERT(apSuite, cache.GetVersion(Test::kMockEndpoint2, Test::MockClusterId(3), version1) == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite, version1.HasValue() && (version1.Value() == 1));
Optional<DataVersion> version2;
NL_TEST_ASSERT(apSuite, cache.GetVersion(Test::kMockEndpoint3, Test::MockClusterId(2), version2) == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite, !version2.HasValue());
delegate.mNumAttributeResponse = 0;
readPrepareParams.mpEventPathParamsList = nullptr;
readPrepareParams.mEventPathParamsListSize = 0;
}
Test::BumpVersion();
// Ninth read of E1C2A* and E2C3A* and E2C2A*, it would use C1 cached version to construct DataVersionFilter, but version has
// changed in server. Expect E1C2A* and C2C3A* and E2C2A* attributes in report, and cache their versions
{
testId++;
ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId);
app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(),
cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read);
chip::app::AttributePathParams attributePathParams3[3];
attributePathParams3[0].mEndpointId = Test::kMockEndpoint1;
attributePathParams3[0].mClusterId = Test::MockClusterId(2);
attributePathParams3[0].mAttributeId = kInvalidAttributeId;
attributePathParams3[1].mEndpointId = Test::kMockEndpoint2;
attributePathParams3[1].mClusterId = Test::MockClusterId(3);
attributePathParams3[1].mAttributeId = kInvalidAttributeId;
attributePathParams3[2].mEndpointId = Test::kMockEndpoint2;
attributePathParams3[2].mClusterId = Test::MockClusterId(2);
attributePathParams3[2].mAttributeId = kInvalidAttributeId;
readPrepareParams.mpAttributePathParamsList = attributePathParams3;
readPrepareParams.mAttributePathParamsListSize = 3;
err = readClient.SendRequest(readPrepareParams);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
ctx.DrainAndServiceIO();
// E1C2A* has 3 attributes and E2C3A* has 5 attributes and E2C2A* has 4 attributes
NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 12);
NL_TEST_ASSERT(apSuite, !delegate.mReadError);
Optional<DataVersion> version1;
NL_TEST_ASSERT(apSuite, cache.GetVersion(Test::kMockEndpoint2, Test::MockClusterId(3), version1) == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite, version1.HasValue() && (version1.Value() == 2));
Optional<DataVersion> version2;
NL_TEST_ASSERT(apSuite, cache.GetVersion(Test::kMockEndpoint2, Test::MockClusterId(2), version2) == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite, version2.HasValue() && (version2.Value() == 2));
Optional<DataVersion> version3;
NL_TEST_ASSERT(apSuite, cache.GetVersion(Test::kMockEndpoint1, Test::MockClusterId(2), version3) == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite, version3.HasValue() && (version3.Value() == 2));
delegate.mNumAttributeResponse = 0;
}
// Tenth read of E1C2A*(3 attributes) and E2C3A*(5 attributes) and E2C2A*(4 attributes), and inject a large amount of event path
// list, then it would try to apply previous cache latest data version and construct data version list with the ordering from
// largest cluster size to smallest cluster size(C2, C3, C1) but no enough memory, finally partially rollback data version
// filter with only C2. Expect E1C2A*, E2C2A* attributes(7 attributes) in report,
{
testId++;
ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId);
app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(),
cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read);
chip::app::AttributePathParams attributePathParams3[3];
attributePathParams3[0].mEndpointId = Test::kMockEndpoint1;
attributePathParams3[0].mClusterId = Test::MockClusterId(2);
attributePathParams3[0].mAttributeId = kInvalidAttributeId;
attributePathParams3[1].mEndpointId = Test::kMockEndpoint2;
attributePathParams3[1].mClusterId = Test::MockClusterId(3);
attributePathParams3[1].mAttributeId = kInvalidAttributeId;
attributePathParams3[2].mEndpointId = Test::kMockEndpoint2;
attributePathParams3[2].mClusterId = Test::MockClusterId(2);
attributePathParams3[2].mAttributeId = kInvalidAttributeId;
readPrepareParams.mpAttributePathParamsList = attributePathParams3;
readPrepareParams.mAttributePathParamsListSize = 3;
readPrepareParams.mpEventPathParamsList = eventPathParams;
readPrepareParams.mEventPathParamsListSize = 62;
err = readClient.SendRequest(readPrepareParams);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
ctx.DrainAndServiceIO();
NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 7);
NL_TEST_ASSERT(apSuite, !delegate.mReadError);
Optional<DataVersion> version1;
NL_TEST_ASSERT(apSuite, cache.GetVersion(Test::kMockEndpoint2, Test::MockClusterId(3), version1) == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite, version1.HasValue() && (version1.Value() == 2));
Optional<DataVersion> version2;
NL_TEST_ASSERT(apSuite, cache.GetVersion(Test::kMockEndpoint2, Test::MockClusterId(2), version2) == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite, version2.HasValue() && (version2.Value() == 2));
Optional<DataVersion> version3;
NL_TEST_ASSERT(apSuite, cache.GetVersion(Test::kMockEndpoint1, Test::MockClusterId(2), version3) == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite, version3.HasValue() && (version3.Value() == 2));
delegate.mNumAttributeResponse = 0;
readPrepareParams.mpEventPathParamsList = nullptr;
readPrepareParams.mEventPathParamsListSize = 0;
}
NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadClients() == 0);
engine->Shutdown();
NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
}
void TestReadInteraction::TestReadEventResponse(nlTestSuite * apSuite, void * apContext)
{
TestContext & ctx = *static_cast<TestContext *>(apContext);
auto sessionHandle = ctx.GetSessionBobToAlice();
bool onSuccessCbInvoked = false, onFailureCbInvoked = false;
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onSuccessCb = [apSuite, &onSuccessCbInvoked](const app::EventHeader & eventHeader, const auto & EventResponse) {
// TODO: Need to add check when IM event server integration completes
IgnoreUnusedVariable(apSuite);
onSuccessCbInvoked = true;
};
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onFailureCb = [&onFailureCbInvoked](const app::EventHeader * eventHeader, CHIP_ERROR aError) {
onFailureCbInvoked = true;
};
Controller::ReadEvent<TestCluster::Events::TestEvent::DecodableType>(&ctx.GetExchangeManager(), sessionHandle, kTestEndpointId,
onSuccessCb, onFailureCb);
ctx.DrainAndServiceIO();
NL_TEST_ASSERT(apSuite, !onFailureCbInvoked);
NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0);
NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0);
NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
}
void TestReadInteraction::TestReadAttributeError(nlTestSuite * apSuite, void * apContext)
{
TestContext & ctx = *static_cast<TestContext *>(apContext);
auto sessionHandle = ctx.GetSessionBobToAlice();
bool onSuccessCbInvoked = false, onFailureCbInvoked = false;
responseDirective = kSendDataError;
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onSuccessCb = [&onSuccessCbInvoked](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) {
onSuccessCbInvoked = true;
};
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onFailureCb = [&onFailureCbInvoked, apSuite](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) {
NL_TEST_ASSERT(apSuite, aError.IsIMStatus() && app::StatusIB(aError).mStatus == Protocols::InteractionModel::Status::Busy);
onFailureCbInvoked = true;
};
Controller::ReadAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>(&ctx.GetExchangeManager(), sessionHandle,
kTestEndpointId, onSuccessCb, onFailureCb);
ctx.DrainAndServiceIO();
NL_TEST_ASSERT(apSuite, !onSuccessCbInvoked && onFailureCbInvoked);
NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0);
NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0);
NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
}
void TestReadInteraction::TestReadAttributeTimeout(nlTestSuite * apSuite, void * apContext)
{
TestContext & ctx = *static_cast<TestContext *>(apContext);
auto sessionHandle = ctx.GetSessionBobToAlice();
bool onSuccessCbInvoked = false, onFailureCbInvoked = false;
responseDirective = kSendDataError;
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onSuccessCb = [&onSuccessCbInvoked](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) {
onSuccessCbInvoked = true;
};
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onFailureCb = [&onFailureCbInvoked, apSuite](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) {
NL_TEST_ASSERT(apSuite, aError == CHIP_ERROR_TIMEOUT);
onFailureCbInvoked = true;
};
Controller::ReadAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>(&ctx.GetExchangeManager(), sessionHandle,
kTestEndpointId, onSuccessCb, onFailureCb);
ctx.ExpireSessionAliceToBob();
ctx.DrainAndServiceIO();
NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 1);
ctx.ExpireSessionBobToAlice();
ctx.DrainAndServiceIO();
NL_TEST_ASSERT(apSuite, !onSuccessCbInvoked && onFailureCbInvoked);
NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0);
//
// Let's put back the sessions so that the next tests (which assume a valid initialized set of sessions)
// can function correctly.
//
ctx.CreateSessionAliceToBob();
ctx.CreateSessionBobToAlice();
NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
}
void TestReadInteraction::TestReadHandler_MultipleSubscriptions(nlTestSuite * apSuite, void * apContext)
{
TestContext & ctx = *static_cast<TestContext *>(apContext);
auto sessionHandle = ctx.GetSessionBobToAlice();
uint32_t numSuccessCalls = 0;
uint32_t numSubscriptionEstablishedCalls = 0;
responseDirective = kSendDataResponse;
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onSuccessCb = [&numSuccessCalls](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) {
numSuccessCalls++;
};
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onFailureCb = [&apSuite](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) {
//
// We shouldn't be encountering any failures in this test.
//
NL_TEST_ASSERT(apSuite, false);
};
auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls](const app::ReadClient & readClient) {
numSubscriptionEstablishedCalls++;
};
//
// Test the application callback as well to ensure we get the right number of SubscriptionEstablishment/Termination
// callbacks.
//
app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction);
//
// Try to issue parallel subscriptions that will exceed the value for CHIP_IM_MAX_NUM_READ_HANDLER.
// If heap allocation is correctly setup, this should result in it successfully servicing more than the number
// present in that define.
//
for (int i = 0; i < (CHIP_IM_MAX_NUM_READ_HANDLER + 1); i++)
{
NL_TEST_ASSERT(apSuite,
Controller::SubscribeAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>(
&ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10,
onSubscriptionEstablishedCb, false, true) == CHIP_NO_ERROR);
}
ctx.DrainAndServiceIO();
NL_TEST_ASSERT(apSuite, numSuccessCalls == (CHIP_IM_MAX_NUM_READ_HANDLER + 1));
NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == (CHIP_IM_MAX_NUM_READ_HANDLER + 1));
NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == (CHIP_IM_MAX_NUM_READ_HANDLER + 1));
app::InteractionModelEngine::GetInstance()->ShutdownActiveReads();
NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 0);
NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
app::InteractionModelEngine::GetInstance()->UnregisterReadHandlerAppCallback();
}
void TestReadInteraction::TestReadHandler_SubscriptionAppRejection(nlTestSuite * apSuite, void * apContext)
{
TestContext & ctx = *static_cast<TestContext *>(apContext);
auto sessionHandle = ctx.GetSessionBobToAlice();
uint32_t numSuccessCalls = 0;
uint32_t numFailureCalls = 0;
uint32_t numSubscriptionEstablishedCalls = 0;
responseDirective = kSendDataResponse;
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onSuccessCb = [&numSuccessCalls](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) {
numSuccessCalls++;
};
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onFailureCb = [&numFailureCalls](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) {
numFailureCalls++;
};
auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls](const app::ReadClient & readClient) {
numSubscriptionEstablishedCalls++;
};
//
// Test the application callback as well to ensure we get the right number of SubscriptionEstablishment/Termination
// callbacks.
//
app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction);
//
// Test the application rejecting subscriptions.
//
gTestReadInteraction.mEmitSubscriptionError = true;
NL_TEST_ASSERT(apSuite,
Controller::SubscribeAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>(
&ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10,
onSubscriptionEstablishedCb, false, true) == CHIP_NO_ERROR);
ctx.DrainAndServiceIO();
NL_TEST_ASSERT(apSuite, numSuccessCalls == 0);
//
// Failures won't get routed to us here since re-subscriptions are enabled by default in the Controller::SubscribeAttribute
// implementation.
//
NL_TEST_ASSERT(apSuite, numFailureCalls == 0);
NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == 0);
NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 0);
app::InteractionModelEngine::GetInstance()->ShutdownActiveReads();
NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 0);
NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
app::InteractionModelEngine::GetInstance()->UnregisterReadHandlerAppCallback();
gTestReadInteraction.mEmitSubscriptionError = false;
}
void TestReadInteraction::TestReadHandler_SubscriptionAlteredReportingIntervals(nlTestSuite * apSuite, void * apContext)
{
TestContext & ctx = *static_cast<TestContext *>(apContext);
auto sessionHandle = ctx.GetSessionBobToAlice();
uint32_t numSuccessCalls = 0;
uint32_t numFailureCalls = 0;
uint32_t numSubscriptionEstablishedCalls = 0;
responseDirective = kSendDataResponse;
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onSuccessCb = [&numSuccessCalls](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) {
numSuccessCalls++;
};
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onFailureCb = [&numFailureCalls](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) {
numFailureCalls++;
};
auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls, &apSuite](const app::ReadClient & readClient) {
uint16_t minInterval = 0, maxInterval = 0;
CHIP_ERROR err = readClient.GetReportingIntervals(minInterval, maxInterval);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite, minInterval == kTestMinInterval);
NL_TEST_ASSERT(apSuite, maxInterval == kTestMaxInterval);
numSubscriptionEstablishedCalls++;
};
//
// Test the application callback as well to ensure we get the right number of SubscriptionEstablishment/Termination
// callbacks.
//
app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction);
//
// Test the server-side application altering the subscription intervals.
//
gTestReadInteraction.mAlterSubscriptionIntervals = true;
NL_TEST_ASSERT(apSuite,
Controller::SubscribeAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>(
&ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10,
onSubscriptionEstablishedCb, false, true) == CHIP_NO_ERROR);
ctx.DrainAndServiceIO();
//
// Failures won't get routed to us here since re-subscriptions are enabled by default in the Controller::SubscribeAttribute
// implementation.
//
NL_TEST_ASSERT(apSuite, numSuccessCalls != 0);
NL_TEST_ASSERT(apSuite, numFailureCalls == 0);
NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == 1);
NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 1);
app::InteractionModelEngine::GetInstance()->ShutdownActiveReads();
NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 0);
NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
app::InteractionModelEngine::GetInstance()->UnregisterReadHandlerAppCallback();
gTestReadInteraction.mAlterSubscriptionIntervals = false;
}
void TestReadInteraction::TestReadHandler_MultipleReads(nlTestSuite * apSuite, void * apContext)
{
TestContext & ctx = *static_cast<TestContext *>(apContext);
static_assert(CHIP_IM_MAX_REPORTS_IN_FLIGHT <= CHIP_IM_MAX_NUM_READ_HANDLER,
"How can we have more reports in flight than read handlers?");
MultipleReadHelper(apSuite, ctx, CHIP_IM_MAX_REPORTS_IN_FLIGHT);
NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
app::InteractionModelEngine::GetInstance()->ShutdownActiveReads();
}
void TestReadInteraction::TestReadHandler_OneSubscribeMultipleReads(nlTestSuite * apSuite, void * apContext)
{
TestContext & ctx = *static_cast<TestContext *>(apContext);
static_assert(CHIP_IM_MAX_REPORTS_IN_FLIGHT <= CHIP_IM_MAX_NUM_READ_HANDLER,
"How can we have more reports in flight than read handlers?");
static_assert(CHIP_IM_MAX_REPORTS_IN_FLIGHT > 1, "We won't do any reads");
SubscribeThenReadHelper(apSuite, ctx, 1, CHIP_IM_MAX_REPORTS_IN_FLIGHT - 1);
NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
app::InteractionModelEngine::GetInstance()->ShutdownActiveReads();
}
void TestReadInteraction::TestReadHandler_TwoSubscribesMultipleReads(nlTestSuite * apSuite, void * apContext)
{
TestContext & ctx = *static_cast<TestContext *>(apContext);
static_assert(CHIP_IM_MAX_REPORTS_IN_FLIGHT <= CHIP_IM_MAX_NUM_READ_HANDLER,
"How can we have more reports in flight than read handlers?");
static_assert(CHIP_IM_MAX_REPORTS_IN_FLIGHT > 2, "We won't do any reads");
SubscribeThenReadHelper(apSuite, ctx, 2, CHIP_IM_MAX_REPORTS_IN_FLIGHT - 2);
NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
app::InteractionModelEngine::GetInstance()->ShutdownActiveReads();
}
void TestReadInteraction::SubscribeThenReadHelper(nlTestSuite * apSuite, TestContext & aCtx, size_t aSubscribeCount,
size_t aReadCount)
{
auto sessionHandle = aCtx.GetSessionBobToAlice();
uint32_t numSuccessCalls = 0;
uint32_t numSubscriptionEstablishedCalls = 0;
responseDirective = kSendDataResponse;
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onSuccessCb = [&numSuccessCalls](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) {
numSuccessCalls++;
};
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onFailureCb = [&apSuite](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) {
//
// We shouldn't be encountering any failures in this test.
//
NL_TEST_ASSERT(apSuite, false);
};
auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls, &apSuite, &aCtx, aSubscribeCount,
aReadCount](const app::ReadClient & readClient) {
numSubscriptionEstablishedCalls++;
if (numSubscriptionEstablishedCalls == aSubscribeCount)
{
MultipleReadHelper(apSuite, aCtx, aReadCount);
}
};
for (size_t i = 0; i < aSubscribeCount; ++i)
{
NL_TEST_ASSERT(apSuite,
Controller::SubscribeAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>(
&aCtx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10,
onSubscriptionEstablishedCb, false, true) == CHIP_NO_ERROR);
}
aCtx.DrainAndServiceIO();
NL_TEST_ASSERT(apSuite, numSuccessCalls == aSubscribeCount);
NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == aSubscribeCount);
}
void TestReadInteraction::MultipleReadHelper(nlTestSuite * apSuite, TestContext & aCtx, size_t aReadCount)
{
auto sessionHandle = aCtx.GetSessionBobToAlice();
uint32_t numSuccessCalls = 0;
uint32_t numFailureCalls = 0;
responseDirective = kSendDataResponse;
uint16_t firstExpectedResponse = totalReadCount + 1;
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onFailureCb = [&apSuite, &numFailureCalls](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) {
numFailureCalls++;
NL_TEST_ASSERT(apSuite, attributePath == nullptr);
};
for (size_t i = 0; i < aReadCount; ++i)
{
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise,
// it's not safe to do so.
auto onSuccessCb = [&numSuccessCalls, &apSuite, firstExpectedResponse,
i](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) {
NL_TEST_ASSERT(apSuite, dataResponse == firstExpectedResponse + i);
numSuccessCalls++;
};
NL_TEST_ASSERT(apSuite,
Controller::ReadAttribute<TestCluster::Attributes::Int16u::TypeInfo>(
&aCtx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb) == CHIP_NO_ERROR);
}
aCtx.DrainAndServiceIO();
NL_TEST_ASSERT(apSuite, numSuccessCalls == aReadCount);
NL_TEST_ASSERT(apSuite, numFailureCalls == 0);
}
void TestReadInteraction::TestReadHandler_MultipleSubscriptionsWithDataVersionFilter(nlTestSuite * apSuite, void * apContext)
{
TestContext & ctx = *static_cast<TestContext *>(apContext);
auto sessionHandle = ctx.GetSessionBobToAlice();
uint32_t numSuccessCalls = 0;
uint32_t numSubscriptionEstablishedCalls = 0;
responseDirective = kSendDataResponse;
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onSuccessCb = [apSuite, &numSuccessCalls](const app::ConcreteDataAttributePath & attributePath,
const auto & dataResponse) {
NL_TEST_ASSERT(apSuite, attributePath.mDataVersion.HasValue() && attributePath.mDataVersion.Value() == kDataVersion);
numSuccessCalls++;
};
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onFailureCb = [&apSuite](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) {
//
// We shouldn't be encountering any failures in this test.
//
NL_TEST_ASSERT(apSuite, false);
};
auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls](const app::ReadClient & readClient) {
numSubscriptionEstablishedCalls++;
};
//
// Try to issue parallel subscriptions that will exceed the value for CHIP_IM_MAX_NUM_READ_HANDLER.
// If heap allocation is correctly setup, this should result in it successfully servicing more than the number
// present in that define.
//
chip::Optional<chip::DataVersion> dataVersion(1);
for (int i = 0; i < (CHIP_IM_MAX_NUM_READ_HANDLER + 1); i++)
{
NL_TEST_ASSERT(apSuite,
Controller::SubscribeAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>(
&ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10,
onSubscriptionEstablishedCb, false, true, dataVersion) == CHIP_NO_ERROR);
}
ctx.DrainAndServiceIO();
NL_TEST_ASSERT(apSuite, numSuccessCalls == (CHIP_IM_MAX_NUM_READ_HANDLER + 1));
NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == (CHIP_IM_MAX_NUM_READ_HANDLER + 1));
app::InteractionModelEngine::GetInstance()->ShutdownActiveReads();
NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
}
void TestReadInteraction::TestReadHandlerResourceExhaustion_MultipleSubscriptions(nlTestSuite * apSuite, void * apContext)
{
TestContext & ctx = *static_cast<TestContext *>(apContext);
auto sessionHandle = ctx.GetSessionBobToAlice();
uint32_t numSuccessCalls = 0;
uint32_t numFailureCalls = 0;
uint32_t numSubscriptionEstablishedCalls = 0;
responseDirective = kSendDataResponse;
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onSuccessCb = [&numSuccessCalls](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) {
numSuccessCalls++;
};
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onFailureCb = [&apSuite, &numFailureCalls](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) {
numFailureCalls++;
NL_TEST_ASSERT(apSuite, aError == CHIP_IM_GLOBAL_STATUS(ResourceExhausted));
NL_TEST_ASSERT(apSuite, attributePath == nullptr);
};
auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls](const app::ReadClient & readClient) {
numSubscriptionEstablishedCalls++;
};
//
// Artifically limit the capacity to 2 ReadHandlers. This will also validate reservation of handlers for Reads,
// since the second subscription below should fail correctly.
//
app::InteractionModelEngine::GetInstance()->SetHandlerCapacity(2);
NL_TEST_ASSERT(apSuite,
Controller::SubscribeAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>(
&ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10,
onSubscriptionEstablishedCb, false, true) == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite,
Controller::SubscribeAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>(
&ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10,
onSubscriptionEstablishedCb, false, true) == CHIP_NO_ERROR);
ctx.DrainAndServiceIO();
NL_TEST_ASSERT(apSuite, numSuccessCalls == 1);
NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == 1);
// Resubscription is happening for second subscribe call
NL_TEST_ASSERT(apSuite, numFailureCalls == 0);
app::InteractionModelEngine::GetInstance()->SetHandlerCapacity(-1);
app::InteractionModelEngine::GetInstance()->ShutdownActiveReads();
NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
}
void TestReadInteraction::TestReadHandlerResourceExhaustion_MultipleReads(nlTestSuite * apSuite, void * apContext)
{
TestContext & ctx = *static_cast<TestContext *>(apContext);
auto sessionHandle = ctx.GetSessionBobToAlice();
uint32_t numSuccessCalls = 0;
uint32_t numFailureCalls = 0;
responseDirective = kSendDataResponse;
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onSuccessCb = [&numSuccessCalls](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) {
numSuccessCalls++;
};
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onFailureCb = [&apSuite, &numFailureCalls](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) {
numFailureCalls++;
NL_TEST_ASSERT(apSuite, aError == CHIP_IM_GLOBAL_STATUS(ResourceExhausted));
NL_TEST_ASSERT(apSuite, attributePath == nullptr);
};
app::InteractionModelEngine::GetInstance()->SetHandlerCapacity(0);
NL_TEST_ASSERT(apSuite,
Controller::ReadAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>(
&ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb) == CHIP_NO_ERROR);
ctx.DrainAndServiceIO();
app::InteractionModelEngine::GetInstance()->SetHandlerCapacity(-1);
app::InteractionModelEngine::GetInstance()->ShutdownActiveReads();
NL_TEST_ASSERT(apSuite, numSuccessCalls == 0);
NL_TEST_ASSERT(apSuite, numFailureCalls == 1);
NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
}
void TestReadInteraction::TestReadFabricScopedWithoutFabricFilter(nlTestSuite * apSuite, void * apContext)
{
/**
* TODO: we cannot implement the e2e read tests w/ fabric filter since the test session has only one session, and the
* ReadSingleClusterData is not the one in real applications. We should be able to move some logic out of the ember library and
* make it possible to have more fabrics in test setup so we can have a better test coverage.
*
* NOTE: Based on the TODO above, the test is testing two separate logics:
* - When a fabric filtered read request is received, the server is able to pass the required fabric index to the response
* encoder.
* - When a fabric filtered read request is received, the response encoder is able to encode the attribute correctly.
*/
TestContext & ctx = *static_cast<TestContext *>(apContext);
auto sessionHandle = ctx.GetSessionBobToAlice();
bool onSuccessCbInvoked = false, onFailureCbInvoked = false;
responseDirective = kSendDataResponse;
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onSuccessCb = [apSuite, &onSuccessCbInvoked](const app::ConcreteDataAttributePath & attributePath,
const auto & dataResponse) {
size_t len = 0;
NL_TEST_ASSERT(apSuite, dataResponse.ComputeSize(&len) == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite, len > 1);
onSuccessCbInvoked = true;
};
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onFailureCb = [&onFailureCbInvoked](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) {
onFailureCbInvoked = true;
};
Controller::ReadAttribute<TestCluster::Attributes::ListFabricScoped::TypeInfo>(
&ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, false /* fabric filtered */);
ctx.DrainAndServiceIO();
NL_TEST_ASSERT(apSuite, onSuccessCbInvoked && !onFailureCbInvoked);
NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0);
NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0);
NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
}
void TestReadInteraction::TestReadFabricScopedWithFabricFilter(nlTestSuite * apSuite, void * apContext)
{
/**
* TODO: we cannot implement the e2e read tests w/ fabric filter since the test session has only one session, and the
* ReadSingleClusterData is not the one in real applications. We should be able to move some logic out of the ember library and
* make it possible to have more fabrics in test setup so we can have a better test coverage.
*
* NOTE: Based on the TODO above, the test is testing two separate logics:
* - When a fabric filtered read request is received, the server is able to pass the required fabric index to the response
* encoder.
* - When a fabric filtered read request is received, the response encoder is able to encode the attribute correctly.
*/
TestContext & ctx = *static_cast<TestContext *>(apContext);
auto sessionHandle = ctx.GetSessionBobToAlice();
bool onSuccessCbInvoked = false, onFailureCbInvoked = false;
responseDirective = kSendDataResponse;
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onSuccessCb = [apSuite, &onSuccessCbInvoked](const app::ConcreteDataAttributePath & attributePath,
const auto & dataResponse) {
size_t len = 0;
NL_TEST_ASSERT(apSuite, dataResponse.ComputeSize(&len) == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite, len == 1);
// TODO: Uncomment the following code after we have fabric support in unit tests.
/*
auto iter = dataResponse.begin();
if (iter.Next())
{
auto & item = iter.GetValue();
NL_TEST_ASSERT(apSuite, item.fabricIndex == 1);
}
*/
onSuccessCbInvoked = true;
};
// Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
// not safe to do so.
auto onFailureCb = [&onFailureCbInvoked](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) {
onFailureCbInvoked = true;
};
Controller::ReadAttribute<TestCluster::Attributes::ListFabricScoped::TypeInfo>(
&ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, true /* fabric filtered */);
ctx.DrainAndServiceIO();
NL_TEST_ASSERT(apSuite, onSuccessCbInvoked && !onFailureCbInvoked);
NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0);
NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0);
NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
}
// clang-format off
const nlTest sTests[] =
{
NL_TEST_DEF("TestReadAttributeResponse", TestReadInteraction::TestReadAttributeResponse),
NL_TEST_DEF("TestReadDataVersionFilter", TestReadInteraction::TestReadDataVersionFilter),
NL_TEST_DEF("TestReadEventResponse", TestReadInteraction::TestReadEventResponse),
NL_TEST_DEF("TestReadAttributeError", TestReadInteraction::TestReadAttributeError),
NL_TEST_DEF("TestReadFabricScopedWithoutFabricFilter", TestReadInteraction::TestReadFabricScopedWithoutFabricFilter),
NL_TEST_DEF("TestReadFabricScopedWithFabricFilter", TestReadInteraction::TestReadFabricScopedWithFabricFilter),
NL_TEST_DEF("TestReadHandler_MultipleSubscriptions", TestReadInteraction::TestReadHandler_MultipleSubscriptions),
NL_TEST_DEF("TestReadHandler_SubscriptionAppRejection", TestReadInteraction::TestReadHandler_SubscriptionAppRejection),
NL_TEST_DEF("TestReadHandler_MultipleSubscriptionsWithDataVersionFilter", TestReadInteraction::TestReadHandler_MultipleSubscriptionsWithDataVersionFilter),
NL_TEST_DEF("TestReadHandler_MultipleReads", TestReadInteraction::TestReadHandler_MultipleReads),
NL_TEST_DEF("TestReadHandler_OneSubscribeMultipleReads", TestReadInteraction::TestReadHandler_OneSubscribeMultipleReads),
NL_TEST_DEF("TestReadHandler_TwoSubscribesMultipleReads", TestReadInteraction::TestReadHandler_TwoSubscribesMultipleReads),
NL_TEST_DEF("TestReadHandlerResourceExhaustion_MultipleSubscriptions", TestReadInteraction::TestReadHandlerResourceExhaustion_MultipleSubscriptions),
NL_TEST_DEF("TestReadHandlerResourceExhaustion_MultipleReads", TestReadInteraction::TestReadHandlerResourceExhaustion_MultipleReads),
NL_TEST_DEF("TestReadAttributeTimeout", TestReadInteraction::TestReadAttributeTimeout),
NL_TEST_DEF("TestReadHandler_SubscriptionAlteredReportingIntervals", TestReadInteraction::TestReadHandler_SubscriptionAlteredReportingIntervals),
NL_TEST_DEF("TestReadSubscribeAttributeResponseWithCache", TestReadInteraction::TestReadSubscribeAttributeResponseWithCache),
NL_TEST_SENTINEL()
};
// clang-format on
// clang-format off
nlTestSuite sSuite =
{
"TestRead",
&sTests[0],
TestContext::InitializeAsync,
TestContext::Finalize
};
// clang-format on
} // namespace
int TestReadInteractionTest()
{
TestContext gContext;
nlTestRunner(&sSuite, &gContext);
return (nlTestRunnerStats(&sSuite));
}
CHIP_REGISTER_TEST_SUITE(TestReadInteractionTest)