blob: 2451eda01410cb188c50c4d9604ad858b9ed8c5a [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 <app-common/zap-generated/cluster-objects.h>
#include <app/InteractionModelEngine.h>
#include <app/tests/AppTestContext.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;
} // namespace
namespace chip {
namespace app {
CHIP_ERROR ReadSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, bool aIsFabricFiltered,
const ConcreteReadAttributePath & aPath, AttributeReportIBs::Builder & aAttributeReports,
AttributeValueEncoder::AttributeEncodeState * 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, 0 /* 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;
});
}
else
{
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();
}
}
else
{
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();
}
return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE;
}
bool IsClusterDataVersionEqual(EndpointId aEndpointId, ClusterId aClusterId, DataVersion aRequiredDataVersion)
{
if (aRequiredDataVersion == kDataVersion)
{
return true;
}
else
{
return false;
}
}
} // namespace app
} // namespace chip
namespace {
class TestReadInteraction
{
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_MultipleSubscriptionsWithDataVersionFilter(nlTestSuite * apSuite, void * apContext);
static void TestReadHandlerResourceExhaustion_MultipleSubscriptions(nlTestSuite * apSuite, void * apContext);
static void TestReadHandlerResourceExhaustion_MultipleReads(nlTestSuite * apSuite, void * apContext);
private:
};
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 == 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();
app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
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();
app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
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::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();
app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
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();
app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
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.DrainAndServiceIO();
NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 2);
ctx.ExpireSessionBobToAlice();
ctx.DrainAndServiceIO();
NL_TEST_ASSERT(apSuite, !onSuccessCbInvoked && onFailureCbInvoked);
//
// TODO: Figure out why I cannot enable this line below.
//
// NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 1);
ctx.DrainAndServiceIO();
app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
ctx.DrainAndServiceIO();
ctx.ExpireSessionAliceToBob();
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();
//
// TODO: Figure out why I cannot enable this line below.
//
// 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]() { 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.
//
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);
}
//
// It may take a couple of service calls since we may hit the limit of CHIP_IM_MAX_REPORTS_IN_FLIGHT
// reports.
//
for (int i = 0; i < 10 && (numSubscriptionEstablishedCalls != (CHIP_IM_MAX_NUM_READ_HANDLER + 1)); i++)
{
ctx.DrainAndServiceIO();
app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
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();
//
// TODO: Figure out why I cannot enable this line below.
//
// NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 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 == 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]() { 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);
}
//
// It may take a couple of service calls since we may hit the limit of CHIP_IM_MAX_REPORTS_IN_FLIGHT
// reports.
//
for (int i = 0; i < 10 && (numSubscriptionEstablishedCalls != (CHIP_IM_MAX_NUM_READ_HANDLER + 1)); i++)
{
ctx.DrainAndServiceIO();
app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
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();
//
// TODO: Figure out why I cannot enable this line below.
//
// 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]() { 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);
//
// It may take a couple of service calls since we may hit the limit of CHIP_IM_MAX_REPORTS_IN_FLIGHT
// reports.
//
for (int i = 0; i < 10; i++)
{
ctx.DrainAndServiceIO();
app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
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();
//
// TODO: Figure out why I cannot enable this line below.
//
// 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);
//
// It may take a couple of service calls since we may hit the limit of CHIP_IM_MAX_REPORTS_IN_FLIGHT
// reports.
//
for (int i = 0; i < 10; i++)
{
ctx.DrainAndServiceIO();
app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
ctx.DrainAndServiceIO();
}
app::InteractionModelEngine::GetInstance()->SetHandlerCapacity(-1);
app::InteractionModelEngine::GetInstance()->ShutdownActiveReads();
NL_TEST_ASSERT(apSuite, numSuccessCalls == 0);
NL_TEST_ASSERT(apSuite, numFailureCalls == 1);
//
// TODO: Figure out why I cannot enable this line below.
//
// 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();
app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
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();
app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
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("TestReadAttributeTimeout", TestReadInteraction::TestReadAttributeTimeout),
NL_TEST_DEF("TestReadHandler_MultipleSubscriptions", TestReadInteraction::TestReadHandler_MultipleSubscriptions),
NL_TEST_DEF("TestReadHandler_MultipleSubscriptionsWithDataVersionFilter", TestReadInteraction::TestReadHandler_MultipleSubscriptionsWithDataVersionFilter),
NL_TEST_DEF("TestReadHandlerResourceExhaustion_MultipleSubscriptions", TestReadInteraction::TestReadHandlerResourceExhaustion_MultipleSubscriptions),
NL_TEST_DEF("TestReadHandlerResourceExhaustion_MultipleReads", TestReadInteraction::TestReadHandlerResourceExhaustion_MultipleReads),
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)