blob: a5dc4db171e4d629b33387f214f00b9934433c67 [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.
*/
/**
* @file
* This file implements a unit test suite for testing different zone
* polygons for self-intersection.
*
*/
#include "pw_unit_test/framework.h"
#include <app/clusters/commodity-tariff-server/CommodityTariffAttrsDataMgmt.h>
#include <cstdint>
#include <mutex>
#include <thread>
#include <lib/support/CodeUtils.h>
using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters;
using namespace chip::app::CommodityTariffAttrsDataMgmt;
// =================================
// Unit tests
// =================================
// In TestCommodityTariffBaseDataClass.cpp or a test helper header
namespace chip {
namespace Platform {
namespace Internal {
void AssertChipStackLockedByCurrentThread(const char * file, int line)
{
// Empty implementation for tests - or add test-specific logic
// You could use gtest assertions here if you're using Google Test
}
} // namespace Internal
} // namespace Platform
} // namespace chip
namespace chip {
namespace app {
class TestCommodityTariffBaseDataClass : public ::testing::Test
{
public:
static void SetUpTestSuite() {}
static void TearDownTestSuite() {}
protected:
void SetUp() override
{
// Initialize CHIP stack if needed
Platform::MemoryInit();
}
void TearDown() override { Platform::MemoryShutdown(); }
};
TEST_F(TestCommodityTariffBaseDataClass, BasicConstruction)
{
// Test construction with different types
CTC_BaseDataClass<uint32_t> intData(1);
CTC_BaseDataClass<DataModel::Nullable<uint32_t>> nullableData(2);
CTC_BaseDataClass<DataModel::List<uint32_t>> listData(3);
// Verify initial states
EXPECT_EQ(intData.HasValue(), false);
EXPECT_EQ(nullableData.GetValue().IsNull(), true); // Nullable starts as null
EXPECT_EQ(nullableData.HasValue(), false); // Nullable starts as null
EXPECT_EQ(listData.GetValue().size() == 0, true); // List starts as empty
EXPECT_EQ(listData.HasValue(), false);
}
template <>
CHIP_ERROR CTC_BaseDataClass<uint32_t>::ValidateNewValue()
{
return CHIP_NO_ERROR;
}
TEST_F(TestCommodityTariffBaseDataClass, ScalarValueUpdateFlow)
{
CTC_BaseDataClass<uint32_t> data(1);
const uint32_t data_sample_1 = 0xde;
const uint32_t data_sample_2 = 0xad;
// Create new value
EXPECT_EQ(data.CreateNewSingleValue(), CHIP_NO_ERROR);
data.GetNewValue() = data_sample_1;
EXPECT_EQ(data.MarkAsAssigned(), CHIP_NO_ERROR);
// Validate and commit
EXPECT_EQ(data.UpdateBegin(nullptr), CHIP_NO_ERROR);
EXPECT_TRUE(data.UpdateFinish(true));
EXPECT_EQ(data.GetValue(), data_sample_1);
EXPECT_TRUE(data.HasValue());
// Create new value
EXPECT_EQ(data.SetNewValue(data_sample_2), CHIP_NO_ERROR);
// Validate and commit
EXPECT_EQ(data.UpdateBegin(nullptr), CHIP_NO_ERROR);
EXPECT_TRUE(data.UpdateFinish(true));
EXPECT_EQ(data.GetValue(), data_sample_2);
EXPECT_TRUE(data.HasValue());
}
TEST_F(TestCommodityTariffBaseDataClass, ScalarValueNoChangeDetection)
{
CTC_BaseDataClass<uint32_t> data(1);
// Set initial value
data.SetNewValue(42);
data.UpdateBegin(nullptr);
data.UpdateFinish(true);
// Try to set same value again
data.SetNewValue(42);
data.UpdateBegin(nullptr);
EXPECT_FALSE(data.UpdateFinish(true)); // Should return false (no change)
}
TEST_F(TestCommodityTariffBaseDataClass, NullableValueTransitions)
{
CTC_BaseDataClass<DataModel::Nullable<uint32_t>> data(1);
const uint32_t data_sample_1 = 0xde;
const uint32_t data_sample_2 = 0xad;
// Set non-null value
data.CreateNewSingleValue();
data.GetNewValue().SetNonNull(data_sample_1);
data.MarkAsAssigned();
data.UpdateBegin(nullptr);
EXPECT_TRUE(data.UpdateFinish(true));
EXPECT_FALSE(data.GetValue().IsNull());
EXPECT_TRUE(data.HasValue());
EXPECT_EQ(data.GetValue().Value(), data_sample_1);
data.SetNewValue(DataModel::MakeNullable(data_sample_2));
data.UpdateBegin(nullptr);
EXPECT_TRUE(data.UpdateFinish(true));
EXPECT_FALSE(data.GetValue().IsNull());
EXPECT_TRUE(data.HasValue());
EXPECT_EQ(data.GetValue().Value(), data_sample_2);
// Set to null
data.SetNewValue(std::nullopt);
data.UpdateBegin(nullptr);
EXPECT_TRUE(data.UpdateFinish(true));
EXPECT_TRUE(data.GetValue().IsNull());
EXPECT_TRUE(data.HasValue());
}
TEST_F(TestCommodityTariffBaseDataClass, NullableSetNewValue)
{
CTC_BaseDataClass<DataModel::Nullable<uint32_t>> data(1);
DataModel::Nullable<uint32_t> newValue;
const uint32_t data_sample = 200;
// Test setting null value
newValue.SetNull();
EXPECT_EQ(data.SetNewValue(newValue), CHIP_NO_ERROR);
data.UpdateBegin(nullptr);
data.UpdateFinish(true);
EXPECT_TRUE(data.GetValue().IsNull());
// Test setting non-null value
newValue.SetNonNull(data_sample);
EXPECT_EQ(data.SetNewValue(newValue), CHIP_NO_ERROR);
data.UpdateBegin(nullptr);
EXPECT_TRUE(data.UpdateFinish(true));
EXPECT_EQ(data.GetValue().Value(), data_sample);
}
struct MockStruct
{
uint32_t field1;
uint16_t field2;
bool operator!=(const MockStruct & other) const { return field1 != other.field1 || field2 != other.field2; }
};
// Specializations for struct handling
template <>
CHIP_ERROR CTC_BaseDataClass<MockStruct>::CopyData(const StructType & input, StructType & output)
{
output.field1 = input.field1;
output.field2 = input.field2;
return CHIP_NO_ERROR;
}
template <>
CHIP_ERROR CTC_BaseDataClass<MockStruct>::ValidateNewValue()
{
return CHIP_NO_ERROR;
}
template <>
void CTC_BaseDataClass<MockStruct>::CleanupStruct(StructType & value)
{
// No special cleanup needed for this simple struct
}
TEST_F(TestCommodityTariffBaseDataClass, StructValueHandling)
{
CTC_BaseDataClass<MockStruct> data(1);
const uint32_t sample_field_one = 100;
const uint16_t sample_field_two = 200;
MockStruct testStruct = { sample_field_one, sample_field_two };
EXPECT_EQ(data.SetNewValue(testStruct), CHIP_NO_ERROR);
data.UpdateBegin(nullptr);
data.UpdateFinish(true);
EXPECT_EQ(data.GetValue().field1, sample_field_one);
EXPECT_EQ(data.GetValue().field2, sample_field_two);
}
// Specializations for struct handling
template <>
CHIP_ERROR CTC_BaseDataClass<DataModel::Nullable<MockStruct>>::CopyData(const StructType & input, StructType & output)
{
output.field1 = input.field1;
output.field2 = input.field2;
return CHIP_NO_ERROR;
}
template <>
CHIP_ERROR CTC_BaseDataClass<DataModel::Nullable<MockStruct>>::ValidateNewValue()
{
return CHIP_NO_ERROR;
}
template <>
void CTC_BaseDataClass<DataModel::Nullable<MockStruct>>::CleanupStruct(StructType & value)
{
// No special cleanup needed for this simple struct
}
TEST_F(TestCommodityTariffBaseDataClass, NullableStructValueHandling)
{
CTC_BaseDataClass<DataModel::Nullable<MockStruct>> data(1);
const uint32_t sample_field_one = 100;
const uint16_t sample_field_two = 200;
MockStruct testStruct = { sample_field_one, sample_field_two };
DataModel::Nullable<MockStruct> newValue;
newValue.SetNonNull(testStruct);
EXPECT_EQ(data.SetNewValue(newValue), CHIP_NO_ERROR);
data.UpdateBegin(nullptr);
data.UpdateFinish(true);
EXPECT_EQ(data.GetValue().Value().field1, sample_field_one);
EXPECT_EQ(data.GetValue().Value().field2, sample_field_two);
}
template <>
CHIP_ERROR CTC_BaseDataClass<DataModel::List<uint32_t>>::ValidateNewValue()
{
return CHIP_NO_ERROR;
}
TEST_F(TestCommodityTariffBaseDataClass, ListValueCreationAndCleanup)
{
CTC_BaseDataClass<DataModel::List<uint32_t>> data(1);
uint32_t SampleArr[] = { 10, 20, 30 };
const uint32_t SampleListLen = sizeof(SampleArr) / sizeof(uint32_t);
const DataModel::List<uint32_t> ListSample = DataModel::List<uint32_t>(SampleArr, SampleListLen);
EXPECT_EQ(data.SetNewValue(ListSample), CHIP_NO_ERROR);
data.UpdateBegin(nullptr);
EXPECT_TRUE(data.UpdateFinish(true));
EXPECT_EQ(data.GetValue().size(), SampleListLen);
EXPECT_EQ(data.GetValue()[0], 10u);
EXPECT_EQ(data.GetValue()[1], 20u);
EXPECT_EQ(data.GetValue()[2], 30u);
data.Cleanup();
EXPECT_EQ(data.GetValue().data(), nullptr);
}
TEST_F(TestCommodityTariffBaseDataClass, ListValueMemoryManagement)
{
CTC_BaseDataClass<DataModel::List<uint32_t>> data(1);
// Test that memory is properly freed
void * originalPtr = nullptr;
data.CreateNewListValue(100);
originalPtr = data.GetNewValue().data();
data.MarkAsAssigned();
data.UpdateBegin(nullptr);
data.UpdateFinish(true);
// Create new list - should free old memory
data.CreateNewListValue(50);
// Memory should be different (old was freed)
EXPECT_NE(data.GetNewValue().data(), originalPtr);
// Cleanup without commit
data.MarkAsAssigned();
data.UpdateBegin(nullptr);
EXPECT_FALSE(data.UpdateFinish(false)); // Don't commit - should cleanup
EXPECT_EQ(data.GetNewValue().data(), nullptr);
}
TEST_F(TestCommodityTariffBaseDataClass, StateMachineEnforcement)
{
CTC_BaseDataClass<uint32_t> data(1);
// Should fail - not in initialized state
EXPECT_EQ(data.CreateNewSingleValue(), CHIP_NO_ERROR);
data.GetNewValue() = 0xaau;
// Should fail - not in assigned state
EXPECT_EQ(data.UpdateBegin(nullptr), CHIP_ERROR_INCORRECT_STATE);
EXPECT_FALSE(data.UpdateFinish(false)); // just to cleanup
// Proper flow
EXPECT_EQ(data.CreateNewSingleValue(), CHIP_NO_ERROR);
data.GetNewValue() = 0x55u;
EXPECT_EQ(data.MarkAsAssigned(), CHIP_NO_ERROR);
EXPECT_EQ(data.UpdateBegin(nullptr), CHIP_NO_ERROR);
EXPECT_TRUE(data.UpdateFinish(true));
}
TEST_F(TestCommodityTariffBaseDataClass, DoubleUpdatePrevention)
{
CTC_BaseDataClass<uint32_t> data(1);
// Start first update
data.CreateNewSingleValue();
// Should fail - already in update
EXPECT_EQ(data.CreateNewSingleValue(), CHIP_ERROR_INCORRECT_STATE);
}
TEST_F(TestCommodityTariffBaseDataClass, NullableScalarChangeDetection)
{
CTC_BaseDataClass<DataModel::Nullable<uint32_t>> data(5u);
// Set initial value
EXPECT_EQ(data.CreateNewSingleValue(), CHIP_NO_ERROR);
data.GetNewValue().SetNonNull(50u);
EXPECT_EQ(data.MarkAsAssigned(), CHIP_NO_ERROR);
EXPECT_EQ(data.UpdateBegin(nullptr), CHIP_NO_ERROR);
EXPECT_TRUE(data.UpdateFinish(true));
// Set same value - should detect no change
EXPECT_EQ(data.CreateNewSingleValue(), CHIP_NO_ERROR);
data.GetNewValue().SetNonNull(50u);
EXPECT_EQ(data.MarkAsAssigned(), CHIP_NO_ERROR);
EXPECT_EQ(data.UpdateBegin(nullptr), CHIP_NO_ERROR);
EXPECT_FALSE(data.UpdateFinish(true)); // No change detected
// Set different value - should detect change
EXPECT_EQ(data.CreateNewSingleValue(), CHIP_NO_ERROR);
data.GetNewValue().SetNonNull(100u);
EXPECT_EQ(data.MarkAsAssigned(), CHIP_NO_ERROR);
EXPECT_EQ(data.UpdateBegin(nullptr), CHIP_NO_ERROR);
EXPECT_TRUE(data.UpdateFinish(true)); // Change detected
}
TEST_F(TestCommodityTariffBaseDataClass, ErrorConditions)
{
CTC_BaseDataClass<DataModel::Nullable<uint32_t>> data(0u);
// Wrong method call for type
EXPECT_EQ(data.CreateNewListValue(5), CHIP_ERROR_INTERNAL);
// Double initialization
EXPECT_EQ(data.CreateNewSingleValue(), CHIP_NO_ERROR);
EXPECT_EQ(data.CreateNewSingleValue(), CHIP_ERROR_INCORRECT_STATE);
}
TEST_F(TestCommodityTariffBaseDataClass, UpdateAbort)
{
CTC_BaseDataClass<DataModel::Nullable<uint32_t>> data(0u);
// Start update but abort
EXPECT_EQ(data.CreateNewSingleValue(), CHIP_NO_ERROR);
data.GetNewValue().SetNonNull(99u);
EXPECT_EQ(data.MarkAsAssigned(), CHIP_NO_ERROR);
// Abort without validation
EXPECT_FALSE(data.UpdateFinish(false));
EXPECT_FALSE(data.HasNewValue()); // Should not have value after abort
EXPECT_FALSE(data.HasValue()); // Should not have value after abort
}
// Complex struct with resources that need explicit cleanup
struct ResourceStruct
{
uint32_t id;
char * dynamicString; // Requires manual memory management
DataModel::List<uint32_t> * nestedList; // Requires cleanup
ResourceStruct() : id(0), dynamicString(nullptr), nestedList(nullptr) {}
bool operator!=(const ResourceStruct & other) const
{
if (id != other.id)
return true;
if (dynamicString && other.dynamicString)
{
if (strcmp(dynamicString, other.dynamicString) != 0)
return true;
}
else if (dynamicString != other.dynamicString)
{
return true; // One is null, other isn't
}
// Compare nested lists if both exist
if (nestedList && other.nestedList)
{
if (nestedList->size() != other.nestedList->size())
return true;
for (size_t i = 0; i < nestedList->size(); i++)
{
if ((*nestedList)[i] != (*other.nestedList)[i])
return true;
}
}
else if (nestedList != other.nestedList)
{
return true; // One is null, other isn't
}
return false;
}
};
using ComplexType = DataModel::Nullable<DataModel::List<ResourceStruct>>;
// Specializations for ResourceStruct handling
template <>
CHIP_ERROR CTC_BaseDataClass<ComplexType>::CopyData(const StructType & input, StructType & output)
{
output.id = input.id;
// Copy dynamic string
if (input.dynamicString)
{
output.dynamicString = static_cast<char *>(Platform::MemoryCalloc(strlen(input.dynamicString) + 1, 1));
if (!output.dynamicString)
{
return CHIP_ERROR_NO_MEMORY;
}
strcpy(output.dynamicString, input.dynamicString);
}
else
{
output.dynamicString = nullptr;
}
// Copy nested list
if (input.nestedList)
{
output.nestedList = new DataModel::List<uint32_t>();
if (!output.nestedList)
{
Platform::MemoryFree(output.dynamicString);
return CHIP_ERROR_NO_MEMORY;
}
if (input.nestedList->size() > 0)
{
auto * buffer = static_cast<uint32_t *>(Platform::MemoryCalloc(input.nestedList->size(), sizeof(uint32_t)));
if (!buffer)
{
Platform::MemoryFree(output.dynamicString);
delete output.nestedList;
return CHIP_ERROR_NO_MEMORY;
}
for (size_t i = 0; i < input.nestedList->size(); i++)
{
buffer[i] = (*input.nestedList)[i];
}
*output.nestedList = DataModel::List<uint32_t>(buffer, input.nestedList->size());
}
}
else
{
output.nestedList = nullptr;
}
return CHIP_NO_ERROR;
}
template <>
CHIP_ERROR CTC_BaseDataClass<ComplexType>::ValidateNewValue()
{
return CHIP_NO_ERROR;
}
template <>
void CTC_BaseDataClass<ComplexType>::CleanupStruct(StructType & value)
{
// Cleanup dynamic string
if (value.dynamicString)
{
Platform::MemoryFree(value.dynamicString);
value.dynamicString = nullptr;
}
// Cleanup nested list
if (value.nestedList)
{
if (value.nestedList->data())
{
Platform::MemoryFree(value.nestedList->data());
}
delete value.nestedList;
value.nestedList = nullptr;
}
value.id = 0;
}
TEST_F(TestCommodityTariffBaseDataClass, NullableListOfResourceStructs_CreationAndCleanup)
{
CTC_BaseDataClass<ComplexType> data(1);
// Create list with resource-intensive structs
EXPECT_EQ(data.CreateNewListValue(2), CHIP_NO_ERROR);
auto & newList = data.GetNewValue().Value();
// First struct with dynamic resources
newList[0].id = 1;
newList[0].dynamicString = static_cast<char *>(Platform::MemoryCalloc(10, 1));
strcpy(newList[0].dynamicString, "test1");
auto * nestedBuffer1 = static_cast<uint32_t *>(Platform::MemoryCalloc(2, sizeof(uint32_t)));
nestedBuffer1[0] = 100;
nestedBuffer1[1] = 200;
newList[0].nestedList = new DataModel::List<uint32_t>(nestedBuffer1, 2);
// Second struct
newList[1].id = 2;
newList[1].dynamicString = static_cast<char *>(Platform::MemoryCalloc(10, 1));
strcpy(newList[1].dynamicString, "test2");
data.MarkAsAssigned();
data.UpdateBegin(nullptr);
EXPECT_TRUE(data.UpdateFinish(true));
// Verify data was copied correctly
EXPECT_FALSE(data.GetValue().IsNull());
EXPECT_EQ(data.GetValue().Value().size(), 2ul);
EXPECT_EQ(data.GetValue().Value()[0].id, 1u);
EXPECT_STREQ(data.GetValue().Value()[0].dynamicString, "test1");
EXPECT_EQ(data.GetValue().Value()[0].nestedList->size(), 2ul);
EXPECT_EQ((*data.GetValue().Value()[0].nestedList)[0], 100u);
}
TEST_F(TestCommodityTariffBaseDataClass, NullableListOfResourceStructs_SetNewValue)
{
CTC_BaseDataClass<ComplexType> data(1);
// Prepare source data
ComplexType sourceValue;
// Create a struct with resources
ResourceStruct testStruct = {};
testStruct.id = 42;
testStruct.dynamicString = static_cast<char *>(Platform::MemoryCalloc(20, 1));
strcpy(testStruct.dynamicString, "dynamic_content");
auto * nestedBuffer = static_cast<uint32_t *>(Platform::MemoryCalloc(2, sizeof(uint32_t)));
nestedBuffer[0] = 100;
nestedBuffer[1] = 200;
testStruct.nestedList = new DataModel::List<uint32_t>(nestedBuffer, 2);
sourceValue.SetNonNull(DataModel::List<ResourceStruct>(&testStruct, 1ul));
// Use SetNewValue to copy the complex data
EXPECT_EQ(data.SetNewValue(sourceValue), CHIP_NO_ERROR);
data.UpdateBegin(nullptr);
data.UpdateFinish(true);
// Cleanup source (should not affect the copied data)
data.CleanupExtListEntry(testStruct);
EXPECT_EQ(testStruct.dynamicString, nullptr);
EXPECT_EQ(testStruct.nestedList, nullptr);
EXPECT_EQ(data.GetValue().Value().size(), 1ul);
EXPECT_STREQ(data.GetValue().Value()[0].dynamicString, "dynamic_content");
EXPECT_NE(data.GetValue().Value()[0].nestedList, nullptr);
EXPECT_EQ(data.GetValue().Value()[0].nestedList->size(), 2ul);
EXPECT_EQ((*data.GetValue().Value()[0].nestedList)[0], 100u);
EXPECT_EQ((*data.GetValue().Value()[0].nestedList)[1], 200u);
}
TEST_F(TestCommodityTariffBaseDataClass, NullableListOfResourceStructs_NullTransition)
{
CTC_BaseDataClass<ComplexType> data(1);
// First set non-null with resources
data.CreateNewListValue(1);
data.GetNewValue().Value()[0].id = 1;
data.GetNewValue().Value()[0].dynamicString = static_cast<char *>(Platform::MemoryCalloc(10, 1));
strcpy(data.GetNewValue().Value()[0].dynamicString, "test");
data.MarkAsAssigned();
data.UpdateBegin(nullptr);
EXPECT_TRUE(data.UpdateFinish(true));
EXPECT_FALSE(data.GetValue().IsNull());
// Transition to null - should cleanup all resources
data.SetNewValue(std::nullopt);
data.UpdateBegin(nullptr);
EXPECT_TRUE(data.UpdateFinish(true));
EXPECT_TRUE(data.GetValue().IsNull());
// Memory should be properly freed by cleanup
}
TEST_F(TestCommodityTariffBaseDataClass, NullableListOfResourceStructs_UpdateRejectionCleanup)
{
CTC_BaseDataClass<ComplexType> data(1);
// Create resource-intensive update but reject it
data.CreateNewListValue(3);
for (uint32_t i = 0; i < 3; i++)
{
data.GetNewValue().Value()[i].id = i;
data.GetNewValue().Value()[i].dynamicString = static_cast<char *>(Platform::MemoryCalloc(20, 1));
sprintf(data.GetNewValue().Value()[i].dynamicString, "resource_%" PRIu32 "", i);
}
data.MarkAsAssigned();
EXPECT_TRUE(data.HasNewValue());
EXPECT_FALSE(data.HasValue());
data.UpdateBegin(nullptr);
// Reject the update - should cleanup all allocated resources
EXPECT_FALSE(data.UpdateFinish(false));
// Verify cleanup happened (no memory leaks)
EXPECT_FALSE(data.HasNewValue());
EXPECT_FALSE(data.HasValue());
}
TEST_F(TestCommodityTariffBaseDataClass, NullableListOfResourceStructs_ZeroSizeList)
{
CTC_BaseDataClass<ComplexType> data(1);
// Test empty list creation
EXPECT_EQ(data.CreateNewListValue(0), CHIP_ERROR_INVALID_LIST_LENGTH);
EXPECT_TRUE(data.GetNewValue().IsNull()); // Should be null for zero size
data.MarkAsAssigned();
data.UpdateBegin(nullptr);
EXPECT_FALSE(data.UpdateFinish(true));
EXPECT_TRUE(data.GetValue().IsNull());
}
TEST_F(TestCommodityTariffBaseDataClass, ConcurrentReadAccess)
{
CTC_BaseDataClass<DataModel::Nullable<uint32_t>> data(2);
// Initialize with a value
EXPECT_EQ(data.CreateNewSingleValue(), CHIP_NO_ERROR);
data.GetNewValue().SetNonNull(123);
EXPECT_EQ(data.MarkAsAssigned(), CHIP_NO_ERROR);
EXPECT_EQ(data.UpdateBegin(nullptr), CHIP_NO_ERROR);
EXPECT_TRUE(data.UpdateFinish(true));
// Multiple threads should be able to read simultaneously
auto reader = [&data]() {
for (int i = 0; i < 1000; i++)
{
auto & value = data.GetValue();
EXPECT_FALSE(value.IsNull());
EXPECT_EQ(value.Value(), 123u);
}
};
std::thread t1(reader);
std::thread t2(reader);
std::thread t3(reader);
t1.join();
t2.join();
t3.join();
}
TEST_F(TestCommodityTariffBaseDataClass, ConcurrentWriteAccess_ShouldBeSynchronized)
{
CTC_BaseDataClass<DataModel::Nullable<uint32_t>> data(2);
std::atomic<int> successCount(0);
std::atomic<int> failureCount(0);
auto writer = [&data, &successCount, &failureCount](uint32_t value) {
for (uint32_t i = 0; i < 100; i++)
{
CHIP_ERROR err = data.CreateNewSingleValue();
if (err != CHIP_NO_ERROR)
{
failureCount++;
continue;
}
data.GetNewValue().SetNonNull(value + i);
err = data.MarkAsAssigned();
if (err != CHIP_NO_ERROR)
{
failureCount++;
continue;
}
err = data.UpdateBegin(nullptr);
if (err != CHIP_NO_ERROR)
{
failureCount++;
continue;
}
if (data.UpdateFinish(true))
{
successCount++;
}
// Small delay to increase chance of race conditions
std::this_thread::sleep_for(std::chrono::microseconds(10));
}
};
std::thread t1(writer, 1000);
std::thread t2(writer, 2000);
std::thread t3(writer, 3000);
t1.join();
t2.join();
t3.join();
// Without synchronization, we expect many failures due to state conflicts
if (failureCount.load() > 0)
{
EXPECT_GT(failureCount.load(), 0) << "Concurrent writes should cause state errors without synchronization";
}
EXPECT_GT(successCount.load(), 0) << "Some writes should succeed";
}
TEST_F(TestCommodityTariffBaseDataClass, ConcurrentWriteAccess_WithSynchronization)
{
CTC_BaseDataClass<DataModel::Nullable<uint32_t>> data(2);
std::mutex dataMutex;
std::atomic<int> successCount(0);
std::atomic<int> failureCount(0);
std::vector<uint32_t> finalValues;
auto synchronizedWriter = [&data, &dataMutex, &successCount, &failureCount, &finalValues](uint32_t baseValue) {
for (uint32_t i = 0; i < 50; i++)
{
std::lock_guard<std::mutex> lock(dataMutex);
CHIP_ERROR err = data.CreateNewSingleValue();
if (err != CHIP_NO_ERROR)
continue;
data.GetNewValue().SetNonNull(baseValue + i);
err = data.MarkAsAssigned();
if (err != CHIP_NO_ERROR)
{
failureCount++;
continue;
}
err = data.UpdateBegin(nullptr);
if (err != CHIP_NO_ERROR)
{
failureCount++;
continue;
}
if (data.UpdateFinish(true))
{
successCount++;
// Store the value that was successfully written
if (!data.GetValue().IsNull())
{
finalValues.push_back(data.GetValue().Value());
}
}
}
};
std::thread t1(synchronizedWriter, 1000);
std::thread t2(synchronizedWriter, 2000);
std::thread t3(synchronizedWriter, 3000);
t1.join();
t2.join();
t3.join();
// With synchronization, all operations should succeed
EXPECT_EQ(failureCount.load(), 0) << "All synchronized writes should succeed";
EXPECT_EQ(successCount.load(), 150) << "All synchronized writes should succeed";
EXPECT_TRUE(data.HasValue()) << "Data should have a value after writes";
}
TEST_F(TestCommodityTariffBaseDataClass, MixedReadWriteConcurrency)
{
CTC_BaseDataClass<DataModel::Nullable<uint32_t>> data(2);
std::mutex dataMutex;
std::atomic<bool> stopThreads(false);
std::atomic<int> readCount(0);
std::atomic<int> writeCount(0);
std::atomic<int> readErrors(0);
// Initialize with a value
{
std::lock_guard<std::mutex> lock(dataMutex);
EXPECT_EQ(data.CreateNewSingleValue(), CHIP_NO_ERROR);
data.GetNewValue().SetNonNull(999);
EXPECT_EQ(data.MarkAsAssigned(), CHIP_NO_ERROR);
EXPECT_EQ(data.UpdateBegin(nullptr), CHIP_NO_ERROR);
EXPECT_TRUE(data.UpdateFinish(true));
}
auto reader = [&]() {
while (!stopThreads.load())
{
// Readers don't need synchronization for GetValue()
auto & value = data.GetValue();
readCount++;
if (value.IsNull())
{
readErrors++;
}
else if (value.Value() < 100)
{
readErrors++; // Should never see values < 100
}
std::this_thread::sleep_for(std::chrono::microseconds(5));
}
};
auto writer = [&](uint32_t startValue) {
for (uint32_t i = 0; i < 20; i++)
{
std::lock_guard<std::mutex> lock(dataMutex);
CHIP_ERROR err = data.CreateNewSingleValue();
if (err != CHIP_NO_ERROR)
continue;
data.GetNewValue().SetNonNull(startValue + i);
err = data.MarkAsAssigned();
if (err != CHIP_NO_ERROR)
continue;
err = data.UpdateBegin(nullptr);
if (err != CHIP_NO_ERROR)
continue;
if (data.UpdateFinish(true))
{
writeCount++;
}
std::this_thread::sleep_for(std::chrono::microseconds(10));
}
};
// Start readers
std::thread reader1(reader);
std::thread reader2(reader);
// Start writers
std::thread writer1(writer, 100);
std::thread writer2(writer, 200);
writer1.join();
writer2.join();
stopThreads.store(true);
reader1.join();
reader2.join();
EXPECT_GT(readCount.load(), 0) << "Should have performed many reads";
EXPECT_GT(writeCount.load(), 0) << "Should have performed many writes";
EXPECT_EQ(readErrors.load(), 0) << "No read errors should occur during concurrent access";
// Final value should be from one of the writers
EXPECT_FALSE(data.GetValue().IsNull());
EXPECT_GE(data.GetValue().Value(), 100u);
}
TEST_F(TestCommodityTariffBaseDataClass, ConcurrentListOperations)
{
CTC_BaseDataClass<DataModel::List<uint32_t>> data(2);
std::mutex dataMutex;
auto listWriter = [&](uint32_t threadId) {
std::lock_guard<std::mutex> lock(dataMutex);
EXPECT_EQ(data.CreateNewListValue(3), CHIP_NO_ERROR);
auto & list = data.GetNewValue();
for (size_t i = 0; i < list.size(); i++)
{
list[i] = threadId * 100 + static_cast<uint32_t>(i);
}
EXPECT_EQ(data.MarkAsAssigned(), CHIP_NO_ERROR);
EXPECT_EQ(data.UpdateBegin(nullptr), CHIP_NO_ERROR);
EXPECT_TRUE(data.UpdateFinish(true));
};
std::thread t1(listWriter, 1);
std::thread t2(listWriter, 2);
std::thread t3(listWriter, 3);
t1.join();
t2.join();
t3.join();
// Final list should be from one of the threads
EXPECT_TRUE(data.HasValue());
EXPECT_EQ(data.GetValue().size(), 3u);
// Verify the list contains valid values from one thread
auto & finalList = data.GetValue();
uint32_t baseValue = finalList[0] / 100 * 100;
EXPECT_GE(baseValue, 100u);
EXPECT_LE(baseValue, 300u);
for (size_t i = 0; i < finalList.size(); i++)
{
EXPECT_EQ(finalList[i], baseValue + i);
}
}
TEST_F(TestCommodityTariffBaseDataClass, StressTest_ManyThreads)
{
constexpr size_t NUM_THREADS = 10;
constexpr size_t OPERATIONS_PER_THREAD = 50;
CTC_BaseDataClass<DataModel::Nullable<uint32_t>> data(2);
std::mutex dataMutex;
std::atomic<size_t> totalOperations(0);
auto worker = [&](uint32_t threadId) {
for (size_t i = 0; i < OPERATIONS_PER_THREAD; i++)
{
std::lock_guard<std::mutex> lock(dataMutex);
CHIP_ERROR err = data.CreateNewSingleValue();
if (err != CHIP_NO_ERROR)
continue;
data.GetNewValue().SetNonNull(threadId * 1000 + static_cast<uint32_t>(i));
err = data.MarkAsAssigned();
if (err != CHIP_NO_ERROR)
continue;
err = data.UpdateBegin(nullptr);
if (err != CHIP_NO_ERROR)
continue;
if (data.UpdateFinish(true))
{
totalOperations++;
}
}
};
std::vector<std::thread> threads;
for (uint32_t i = 0; i < NUM_THREADS; i++)
{
threads.emplace_back(worker, i);
}
for (auto & t : threads)
{
t.join();
}
EXPECT_EQ(totalOperations, NUM_THREADS * OPERATIONS_PER_THREAD);
EXPECT_TRUE(data.HasValue());
EXPECT_FALSE(data.GetValue().IsNull());
}
} // namespace app
} // namespace chip