blob: da771798c81ab1bb1965d0b99221b0b58c36f70b [file] [log] [blame]
/*
* Copyright (c) 2025 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/server-cluster/ServerClusterInterface.h>
#include <pw_unit_test/framework.h>
#include <app-common/zap-generated/ids/Attributes.h>
#include <app/ConcreteClusterPath.h>
#include <app/server-cluster/DefaultServerCluster.h>
#include <app/server-cluster/ServerClusterContext.h>
#include <app/server-cluster/ServerClusterInterfaceRegistry.h>
#include <app/server-cluster/testing/TestServerClusterContext.h>
#include <lib/core/CHIPError.h>
#include <lib/core/DataModelTypes.h>
#include <lib/core/StringBuilderAdapters.h>
#include <algorithm>
#include <cstdlib>
using namespace chip;
using namespace chip::Test;
using namespace chip::app;
using namespace chip::app::DataModel;
using namespace chip::app::Clusters;
namespace {
constexpr chip::EndpointId kEp1 = 1;
constexpr chip::EndpointId kEp2 = 2;
constexpr chip::ClusterId kCluster1 = 1;
constexpr chip::ClusterId kCluster2 = 2;
constexpr chip::ClusterId kCluster3 = 3;
class FakeServerClusterInterface : public DefaultServerCluster
{
public:
FakeServerClusterInterface(const ConcreteClusterPath & path) : DefaultServerCluster(path) {}
FakeServerClusterInterface(EndpointId endpoint, ClusterId cluster) : DefaultServerCluster({ endpoint, cluster }) {}
DataModel::ActionReturnStatus ReadAttribute(const DataModel::ReadAttributeRequest & request,
AttributeValueEncoder & encoder) override
{
switch (request.path.mAttributeId)
{
case Globals::Attributes::FeatureMap::Id:
return encoder.Encode<uint32_t>(0);
case Globals::Attributes::ClusterRevision::Id:
return encoder.Encode<uint32_t>(123);
}
return CHIP_ERROR_INVALID_ARGUMENT;
}
bool HasContext() { return mContext != nullptr; }
const ConcreteClusterPath & GetPath() const { return mPath; }
};
class MultiPathCluster : public DefaultServerCluster
{
public:
MultiPathCluster(Span<const ConcreteClusterPath> paths) : DefaultServerCluster(paths[0]), mActualPaths(paths) {}
DataModel::ActionReturnStatus ReadAttribute(const DataModel::ReadAttributeRequest & request,
AttributeValueEncoder & encoder) override
{
return CHIP_ERROR_NOT_IMPLEMENTED;
}
Span<const ConcreteClusterPath> GetPaths() const override { return mActualPaths; }
private:
Span<const ConcreteClusterPath> mActualPaths;
};
struct TestServerClusterInterfaceRegistry : public ::testing::Test
{
static void SetUpTestSuite() { ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); }
static void TearDownTestSuite() { chip::Platform::MemoryShutdown(); }
};
} // namespace
TEST_F(TestServerClusterInterfaceRegistry, AcceptDifferentEndpointPaths)
{
{
const std::array<ConcreteClusterPath, 2> kTestPaths{ {
{ 1, 100 },
{ 2, 88 },
} };
MultiPathCluster cluster(kTestPaths);
ServerClusterRegistration registration(cluster);
ServerClusterInterfaceRegistry registry;
ASSERT_EQ(registry.Register(registration), CHIP_NO_ERROR);
ASSERT_EQ(registry.Get({ 1, 100 }), &cluster);
ASSERT_EQ(registry.Get({ 2, 88 }), &cluster);
}
{
const std::array<ConcreteClusterPath, 3> kTestPaths{ {
{ 1, 100 },
{ 1, 200 },
{ 3, 100 },
} };
MultiPathCluster cluster(kTestPaths);
ServerClusterRegistration registration(cluster);
ServerClusterInterfaceRegistry registry;
ASSERT_EQ(registry.Register(registration), CHIP_NO_ERROR);
ASSERT_EQ(registry.Get({ 1, 100 }), &cluster);
ASSERT_EQ(registry.Get({ 1, 200 }), &cluster);
ASSERT_EQ(registry.Get({ 3, 100 }), &cluster);
}
}
TEST_F(TestServerClusterInterfaceRegistry, LazyRegistrationTest)
{
LazyRegisteredServerCluster<FakeServerClusterInterface> obj;
EXPECT_FALSE(obj.IsConstructed());
obj.Create(kEp1, kCluster1);
EXPECT_TRUE(obj.IsConstructed());
EXPECT_EQ(obj.Cluster().GetPath(), ConcreteClusterPath(kEp1, kCluster1));
EXPECT_EQ(obj.Registration().serverClusterInterface, &obj.Cluster());
obj.Destroy();
EXPECT_FALSE(obj.IsConstructed());
obj.Create(kEp2, kCluster3);
EXPECT_TRUE(obj.IsConstructed());
EXPECT_EQ(obj.Cluster().GetPath(), ConcreteClusterPath(kEp2, kCluster3));
EXPECT_EQ(obj.Registration().serverClusterInterface, &obj.Cluster());
obj.Destroy();
EXPECT_FALSE(obj.IsConstructed());
}
TEST_F(TestServerClusterInterfaceRegistry, AllClustersIteration)
{
FakeServerClusterInterface cluster1(kEp1, kCluster1);
FakeServerClusterInterface cluster2(kEp2, kCluster2);
FakeServerClusterInterface cluster3(kEp2, kCluster3);
ServerClusterRegistration registration1(cluster1);
ServerClusterRegistration registration2(cluster2);
ServerClusterRegistration registration3(cluster3);
ServerClusterInterfaceRegistry registry;
EXPECT_EQ(registry.Register(registration1), CHIP_NO_ERROR);
EXPECT_EQ(registry.Register(registration2), CHIP_NO_ERROR);
EXPECT_EQ(registry.Register(registration3), CHIP_NO_ERROR);
std::vector<ServerClusterInterface *> found_clusters;
for (auto * cluster : registry.AllServerClusterInstances())
{
found_clusters.push_back(cluster);
}
EXPECT_EQ(found_clusters.size(), 3u);
EXPECT_NE(std::find(found_clusters.begin(), found_clusters.end(), &cluster1), found_clusters.end());
EXPECT_NE(std::find(found_clusters.begin(), found_clusters.end(), &cluster2), found_clusters.end());
EXPECT_NE(std::find(found_clusters.begin(), found_clusters.end(), &cluster3), found_clusters.end());
registry.Unregister(&cluster2);
found_clusters.clear();
for (auto * cluster : registry.AllServerClusterInstances())
{
found_clusters.push_back(cluster);
}
EXPECT_EQ(found_clusters.size(), 2u);
EXPECT_NE(std::find(found_clusters.begin(), found_clusters.end(), &cluster1), found_clusters.end());
EXPECT_EQ(std::find(found_clusters.begin(), found_clusters.end(), &cluster2), found_clusters.end());
EXPECT_NE(std::find(found_clusters.begin(), found_clusters.end(), &cluster3), found_clusters.end());
}
TEST_F(TestServerClusterInterfaceRegistry, Context)
{
FakeServerClusterInterface cluster1(kEp1, kCluster1);
FakeServerClusterInterface cluster2(kEp1, kCluster2);
FakeServerClusterInterface cluster3(kEp2, kCluster3);
ServerClusterRegistration registration1(cluster1);
ServerClusterRegistration registration2(cluster2);
ServerClusterRegistration registration3(cluster3);
{
ServerClusterInterfaceRegistry registry;
EXPECT_FALSE(cluster1.HasContext());
EXPECT_FALSE(cluster2.HasContext());
EXPECT_FALSE(cluster3.HasContext());
// registry is NOT initialized
EXPECT_EQ(registry.Register(registration1), CHIP_NO_ERROR);
EXPECT_FALSE(cluster1.HasContext());
// set up the registry
TestServerClusterContext context;
EXPECT_EQ(registry.SetContext(context.Create()), CHIP_NO_ERROR);
EXPECT_TRUE(cluster1.HasContext());
EXPECT_FALSE(cluster2.HasContext());
EXPECT_FALSE(cluster3.HasContext());
// adding clusters automatically adds the context
EXPECT_EQ(registry.Register(registration2), CHIP_NO_ERROR);
EXPECT_TRUE(cluster2.HasContext());
// clearing the context clears all clusters
registry.ClearContext();
EXPECT_FALSE(cluster1.HasContext());
EXPECT_FALSE(cluster2.HasContext());
EXPECT_FALSE(cluster3.HasContext());
EXPECT_EQ(registry.SetContext(context.Create()), CHIP_NO_ERROR);
EXPECT_TRUE(cluster1.HasContext());
EXPECT_TRUE(cluster2.HasContext());
EXPECT_FALSE(cluster3.HasContext());
EXPECT_EQ(registry.Register(registration3), CHIP_NO_ERROR);
EXPECT_TRUE(cluster3.HasContext());
// removing clears the context/shuts clusters down
EXPECT_EQ(registry.Unregister(&cluster2), CHIP_NO_ERROR);
EXPECT_TRUE(cluster1.HasContext());
EXPECT_FALSE(cluster2.HasContext());
EXPECT_TRUE(cluster3.HasContext());
// re-setting context works
EXPECT_EQ(registry.SetContext(context.Create()), CHIP_NO_ERROR);
EXPECT_TRUE(cluster1.HasContext());
EXPECT_FALSE(cluster2.HasContext());
EXPECT_TRUE(cluster3.HasContext());
// also not valid, but different
TestServerClusterContext otherContext;
EXPECT_EQ(registry.SetContext(otherContext.Create()), CHIP_NO_ERROR);
EXPECT_TRUE(cluster1.HasContext());
EXPECT_FALSE(cluster2.HasContext());
EXPECT_TRUE(cluster3.HasContext());
}
// destructor clears the context
EXPECT_FALSE(cluster1.HasContext());
EXPECT_FALSE(cluster2.HasContext());
EXPECT_FALSE(cluster3.HasContext());
}
TEST_F(TestServerClusterInterfaceRegistry, GetWithCache)
{
FakeServerClusterInterface cluster1(kEp1, kCluster1);
FakeServerClusterInterface cluster2(kEp1, kCluster2);
ServerClusterRegistration registration1(cluster1);
ServerClusterRegistration registration2(cluster2);
ServerClusterInterfaceRegistry registry;
EXPECT_EQ(registry.Register(registration1), CHIP_NO_ERROR);
EXPECT_EQ(registry.Register(registration2), CHIP_NO_ERROR);
// First Get should find and cache cluster2
EXPECT_EQ(registry.Get({ kEp1, kCluster2 }), &cluster2);
// Second Get should hit the cache
EXPECT_EQ(registry.Get({ kEp1, kCluster2 }), &cluster2);
// Get a different cluster, which will not be cached
EXPECT_EQ(registry.Get({ kEp1, kCluster1 }), &cluster1);
// Now cluster1 should be cached
EXPECT_EQ(registry.Get({ kEp1, kCluster1 }), &cluster1);
}
TEST_F(TestServerClusterInterfaceRegistry, RegisterErrors)
{
FakeServerClusterInterface cluster1(kEp1, kCluster1);
ServerClusterRegistration registration1(cluster1);
FakeServerClusterInterface invalidPathInterface(kInvalidEndpointId, kCluster1);
ServerClusterRegistration invalidPathRegistration(invalidPathInterface);
FakeServerClusterInterface anotherCluster1(kEp1, kCluster1);
ServerClusterRegistration anotherRegistration1(anotherCluster1);
ServerClusterInterfaceRegistry registry;
// Can't register an interface with an invalid path
EXPECT_EQ(registry.Register(invalidPathRegistration), CHIP_ERROR_INVALID_ARGUMENT);
// Can't register a duplicate cluster
EXPECT_EQ(registry.Register(registration1), CHIP_NO_ERROR);
EXPECT_EQ(registry.Register(registration1), CHIP_ERROR_DUPLICATE_KEY_ID);
EXPECT_EQ(registry.Register(anotherRegistration1), CHIP_ERROR_DUPLICATE_KEY_ID);
}
TEST_F(TestServerClusterInterfaceRegistry, UnregisterErrors)
{
FakeServerClusterInterface cluster1(kEp1, kCluster1);
FakeServerClusterInterface cluster2(kEp1, kCluster2);
ServerClusterRegistration registration1(cluster1);
ServerClusterInterfaceRegistry registry;
EXPECT_EQ(registry.Register(registration1), CHIP_NO_ERROR);
// Can't unregister a cluster that was not registered.
EXPECT_EQ(registry.Unregister(&cluster2), CHIP_ERROR_NOT_FOUND);
// Can't unregister a null cluster.
EXPECT_EQ(registry.Unregister(nullptr), CHIP_ERROR_NOT_FOUND);
}