blob: 39d7bfd40b6d284dc73c7cbdda6bca5f51a0c95d [file] [log] [blame]
/*
* Copyright (c) 2025 Project CHIP Authors
*
* 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 <pw_unit_test/framework.h>
#include <app/clusters/software-diagnostics-server/SoftwareDiagnosticsCluster.h>
#include <app/data-model-provider/MetadataTypes.h>
#include <app/server-cluster/DefaultServerCluster.h>
#include <app/server-cluster/testing/ClusterTester.h>
#include <app/server-cluster/testing/ValidateGlobalAttributes.h>
#include <clusters/SoftwareDiagnostics/Enums.h>
#include <clusters/SoftwareDiagnostics/Metadata.h>
#include <lib/core/CHIPError.h>
#include <lib/core/StringBuilderAdapters.h>
#include <lib/support/ReadOnlyBuffer.h>
#include <platform/DiagnosticDataProvider.h>
#include <cmath>
namespace {
using namespace chip;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::SoftwareDiagnostics;
using chip::app::ClusterShutdownType;
template <class T>
class ScopedDiagnosticsProvider
{
public:
ScopedDiagnosticsProvider()
{
mOldProvider = &DeviceLayer::GetDiagnosticDataProvider();
DeviceLayer::SetDiagnosticDataProvider(&mProvider);
}
~ScopedDiagnosticsProvider() { DeviceLayer::SetDiagnosticDataProvider(mOldProvider); }
ScopedDiagnosticsProvider(const ScopedDiagnosticsProvider &) = delete;
ScopedDiagnosticsProvider & operator=(const ScopedDiagnosticsProvider &) = delete;
ScopedDiagnosticsProvider(ScopedDiagnosticsProvider &&) = delete;
ScopedDiagnosticsProvider & operator=(ScopedDiagnosticsProvider &&) = delete;
T & GetProvider() { return mProvider; }
private:
DeviceLayer::DiagnosticDataProvider * mOldProvider;
T mProvider;
};
struct TestSoftwareDiagnosticsCluster : public ::testing::Test
{
static void SetUpTestSuite() { ASSERT_EQ(Platform::MemoryInit(), CHIP_NO_ERROR); }
static void TearDownTestSuite() { Platform::MemoryShutdown(); }
};
TEST_F(TestSoftwareDiagnosticsCluster, CompileTest)
{
// The cluster should compile for any logic
SoftwareDiagnosticsServerCluster cluster({});
// Essentially say "code executes"
ASSERT_EQ(cluster.GetClusterFlags({ kRootEndpointId, SoftwareDiagnostics::Id }),
BitFlags<app::DataModel::ClusterQualityFlags>());
}
TEST_F(TestSoftwareDiagnosticsCluster, AttributesAndCommandTest)
{
{
// everything returns empty here ..
class NullProvider : public DeviceLayer::DiagnosticDataProvider
{
};
ScopedDiagnosticsProvider<NullProvider> nullProvider;
SoftwareDiagnosticsServerCluster cluster({});
chip::Testing::ClusterTester tester(cluster);
// without watermarks, no commands are accepted
EXPECT_TRUE(Testing::IsAcceptedCommandsListEqualTo(cluster, {}));
// feature map attribute
Attributes::FeatureMap::TypeInfo::DecodableType featureMap{};
ASSERT_TRUE(tester.ReadAttribute(Attributes::FeatureMap::Id, featureMap).IsSuccess());
EXPECT_EQ(featureMap, BitFlags<SoftwareDiagnostics::Feature>{}.Raw());
// Everything is unimplemented, so attributes are just the global ones.
// This is really not a useful cluster, but possible...
EXPECT_TRUE(Testing::IsAttributesListEqualTo(cluster, {}));
}
{
class WatermarksProvider : public DeviceLayer::DiagnosticDataProvider
{
public:
bool SupportsWatermarks() override { return true; }
CHIP_ERROR ResetWatermarks() override
{
resetCalled = true;
return CHIP_NO_ERROR;
}
bool resetCalled = false;
};
ScopedDiagnosticsProvider<WatermarksProvider> watermarksProvider;
SoftwareDiagnosticsServerCluster cluster(
SoftwareDiagnosticsLogic::OptionalAttributeSet().Set<Attributes::CurrentHeapHighWatermark::Id>());
chip::Testing::ClusterTester tester(cluster);
ASSERT_TRUE(Testing::IsAcceptedCommandsListEqualTo(cluster, { Commands::ResetWatermarks::kMetadataEntry }));
// feature map attribute
Attributes::FeatureMap::TypeInfo::DecodableType featureMap{};
ASSERT_TRUE(tester.ReadAttribute(Attributes::FeatureMap::Id, featureMap).IsSuccess());
EXPECT_EQ(featureMap, BitFlags<SoftwareDiagnostics::Feature>{ SoftwareDiagnostics::Feature::kWatermarks }.Raw());
// attribute list (only heap high watermark supported)
EXPECT_TRUE(Testing::IsAttributesListEqualTo(
cluster, { SoftwareDiagnostics::Attributes::CurrentHeapHighWatermark::kMetadataEntry }));
// Call the command, and verify it calls through to the provider
EXPECT_FALSE(watermarksProvider.GetProvider().resetCalled);
EXPECT_TRUE(tester.Invoke(Commands::ResetWatermarks::Id, Commands::ResetWatermarks::Type{}).IsSuccess());
EXPECT_TRUE(watermarksProvider.GetProvider().resetCalled);
}
{
class AllProvider : public DeviceLayer::DiagnosticDataProvider
{
public:
bool releaseCalled = false;
bool SupportsWatermarks() override { return true; }
CHIP_ERROR GetCurrentHeapFree(uint64_t & v) override
{
v = 123;
return CHIP_NO_ERROR;
}
CHIP_ERROR GetCurrentHeapUsed(uint64_t & v) override
{
v = 234;
return CHIP_NO_ERROR;
}
CHIP_ERROR GetCurrentHeapHighWatermark(uint64_t & v) override
{
v = 456;
return CHIP_NO_ERROR;
}
CHIP_ERROR GetThreadMetrics(DeviceLayer::ThreadMetrics ** threadMetricsList) override
{
static DeviceLayer::ThreadMetrics metrics[2];
metrics[0].Next = &metrics[1];
metrics[0].id = 1;
metrics[1].Next = nullptr;
metrics[1].id = 2;
metrics[1].stackFreeMinimum.SetValue(567u);
*threadMetricsList = metrics;
return CHIP_NO_ERROR;
}
void ReleaseThreadMetrics(DeviceLayer::ThreadMetrics * threadMetricsList) override { releaseCalled = true; }
};
ScopedDiagnosticsProvider<AllProvider> allProvider;
SoftwareDiagnosticsServerCluster cluster(SoftwareDiagnosticsLogic::OptionalAttributeSet()
.Set<Attributes::ThreadMetrics::Id>()
.Set<Attributes::CurrentHeapFree::Id>()
.Set<Attributes::CurrentHeapUsed::Id>()
.Set<Attributes::CurrentHeapHighWatermark::Id>());
chip::Testing::ClusterTester tester(cluster);
// accepted commands list
ASSERT_TRUE(Testing::IsAcceptedCommandsListEqualTo(cluster, { Commands::ResetWatermarks::kMetadataEntry }));
// generated commands list
EXPECT_TRUE(Testing::IsGeneratedCommandsListEqualTo(cluster, {}));
// attribute list
ASSERT_TRUE(Testing::IsAttributesListEqualTo(cluster,
{
SoftwareDiagnostics::Attributes::CurrentHeapHighWatermark::kMetadataEntry,
SoftwareDiagnostics::Attributes::CurrentHeapFree::kMetadataEntry,
SoftwareDiagnostics::Attributes::CurrentHeapUsed::kMetadataEntry,
SoftwareDiagnostics::Attributes::ThreadMetrics::kMetadataEntry,
}));
// Test all attributes
// cluster revision
Attributes::ClusterRevision::TypeInfo::DecodableType clusterRevision;
ASSERT_TRUE(tester.ReadAttribute(Attributes::ClusterRevision::Id, clusterRevision).IsSuccess());
EXPECT_EQ(clusterRevision, SoftwareDiagnostics::kRevision);
// feature map
Attributes::FeatureMap::TypeInfo::DecodableType featureMap;
ASSERT_TRUE(tester.ReadAttribute(Attributes::FeatureMap::Id, featureMap).IsSuccess());
EXPECT_EQ(featureMap, BitFlags<SoftwareDiagnostics::Feature>{ SoftwareDiagnostics::Feature::kWatermarks }.Raw());
// heapfree
Attributes::CurrentHeapFree::TypeInfo::DecodableType heapFree;
ASSERT_TRUE(tester.ReadAttribute(Attributes::CurrentHeapFree::Id, heapFree).IsSuccess());
EXPECT_EQ(heapFree, 123u);
// heapused
Attributes::CurrentHeapUsed::TypeInfo::DecodableType heapUsed;
ASSERT_TRUE(tester.ReadAttribute(Attributes::CurrentHeapUsed::Id, heapUsed).IsSuccess());
EXPECT_EQ(heapUsed, 234u);
// highwatermark
Attributes::CurrentHeapHighWatermark::TypeInfo::DecodableType highWatermark;
ASSERT_TRUE(tester.ReadAttribute(Attributes::CurrentHeapHighWatermark::Id, highWatermark).IsSuccess());
EXPECT_EQ(highWatermark, 456u);
// threadmetrics
Attributes::ThreadMetrics::TypeInfo::DecodableType threadMetrics;
ASSERT_TRUE(tester.ReadAttribute(Attributes::ThreadMetrics::Id, threadMetrics).IsSuccess());
EXPECT_TRUE(allProvider.GetProvider().releaseCalled);
{
auto it = threadMetrics.begin();
ASSERT_TRUE(it.Next());
const auto & tm1 = it.GetValue();
EXPECT_EQ(tm1.id, 1u);
EXPECT_FALSE(tm1.name.HasValue());
EXPECT_FALSE(tm1.stackFreeCurrent.HasValue());
EXPECT_FALSE(tm1.stackFreeMinimum.HasValue());
EXPECT_FALSE(tm1.stackSize.HasValue());
ASSERT_TRUE(it.Next());
const auto & tm2 = it.GetValue();
EXPECT_EQ(tm2.id, 2u);
EXPECT_FALSE(tm2.name.HasValue());
EXPECT_FALSE(tm2.stackFreeCurrent.HasValue());
ASSERT_TRUE(tm2.stackFreeMinimum.HasValue());
EXPECT_EQ(tm2.stackFreeMinimum.Value(), 567u);
EXPECT_FALSE(tm2.stackSize.HasValue());
EXPECT_FALSE(it.Next());
EXPECT_EQ(it.GetStatus(), CHIP_NO_ERROR);
}
}
}
TEST_F(TestSoftwareDiagnosticsCluster, TestEventGeneration)
{
SoftwareDiagnosticsServerCluster cluster({});
chip::Testing::ClusterTester tester(cluster);
ASSERT_EQ(cluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
// Notify a fault, and verify it is received
const char faultData[] = "faultdata";
Events::SoftwareFault::Type fault{
.id = 1234,
.name = Optional{ "test"_span },
.faultRecording = Optional{ ByteSpan(Uint8::from_const_char(faultData), strlen(faultData)) },
};
SoftwareDiagnostics::SoftwareFaultListener::GlobalNotifySoftwareFaultDetect(fault);
Events::SoftwareFault::DecodableType decodedFault;
auto event = tester.GetNextGeneratedEvent();
ASSERT_TRUE(event.has_value());
ASSERT_EQ(event->GetEventData(decodedFault), CHIP_NO_ERROR); // NOLINT(bugprone-unchecked-optional-access)
EXPECT_EQ(decodedFault.id, fault.id);
ASSERT_TRUE(decodedFault.name.HasValue());
EXPECT_TRUE(decodedFault.name.Value().data_equal(fault.name.Value()));
ASSERT_TRUE(decodedFault.faultRecording.HasValue());
EXPECT_TRUE(decodedFault.faultRecording.Value().data_equal(fault.faultRecording.Value()));
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
} // namespace