Skip to content
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
608777f
Bluetooth Persistence Migration: Initial put
pahearn73 Apr 26, 2026
adba413
Bluetooth Persistence Migration: Added test plan
pahearn73 May 6, 2026
da47f15
Merge branch 'develop' into feature/RDKEMW-17454
pahearn73 May 7, 2026
908c196
Bluetooth Persistence Migration: Fixed BLUETOOTH_ENABLE_PERSISTENCE_M…
pahearn73 May 12, 2026
fb7c8e3
Bluetooth Persistence Migration: Added debug
pahearn73 May 13, 2026
6d7a380
Bluetooth Persistence Migration: Fixed lastConnectTimeUtc reset in pe…
pahearn73 May 15, 2026
292aa4f
Bluetooth Persistence Migration: Fixed PersistentStore return code check
pahearn73 May 18, 2026
8ccc1b6
Bluetooth Persistence Migration: Added debug
pahearn73 May 18, 2026
83fd636
Bluetooth Persistence Migration: Updated test plan
pahearn73 May 18, 2026
d0108e2
Bluetooth Persistence Migration: Formatting
pahearn73 May 18, 2026
ca54d37
Bluetooth Persistence Migration: Build 2
pahearn73 May 18, 2026
74a390e
Bluetooth Persistence Migration: Parameterized some tests to reduce c…
pahearn73 May 18, 2026
ae9cbcd
Bluetooth Persistence Migration: Build 20260520a
pahearn73 May 20, 2026
9e3a8cd
Bluetooth Persistence Migration: Fixed unit test
pahearn73 May 22, 2026
d139e98
Bluetooth Persistence Migration: Added L2 tests
pahearn73 May 22, 2026
b69d483
Bluetooth Persistence Migration: Added L2 tests
pahearn73 May 22, 2026
f13322e
Bluetooth Persistence Migration: Added L2 tests
pahearn73 May 22, 2026
0e64907
Bluetooth Persistence Migration: Added L2 tests
pahearn73 May 22, 2026
dac8187
Bluetooth Persistence Migration: Added L2 tests
pahearn73 May 22, 2026
0708d5f
Bluetooth Persistence Migration: Added L2 tests
pahearn73 May 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
576 changes: 576 additions & 0 deletions .github/workflows/L2-tests.yml

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions .github/workflows/tests-trigger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,10 @@ jobs:
caller_source: local
secrets:
RDKCM_RDKE: ${{ secrets.RDKCM_RDKE }}

trigger-L2:
uses: ./.github/workflows/L2-tests.yml
with:
caller_source: local
secrets:
RDKCM_RDKE: ${{ secrets.RDKCM_RDKE }}
3 changes: 3 additions & 0 deletions Bluetooth/Bluetooth.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1827,6 +1827,9 @@ namespace WPEFramework
{
LOGINFO("Making a call with deviceID=%llu ", deviceID);
successFlag = setDeviceVolumeMuteProperties(deviceID, deviceTypeStr, ui8volume, mute);
if (successFlag) {
m_bluetoothDeviceManager.setLastVolumeSetting(deviceIDStr, static_cast<long long>(ui8volume));
Comment thread
pahearn73 marked this conversation as resolved.
}
} else {
LOGERR("Please specify parameters. Example: \"params\": {\"deviceID\": \"271731989589742\", \"deviceType\": \"HEADPHONES\", \"volume\": \"0-255\", \"mute\": \"0-1\"}");
successFlag = false;
Expand Down
57 changes: 54 additions & 3 deletions Bluetooth/BluetoothDeviceManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

#include "BluetoothDeviceManager.h"
#include "btmgr.h"

#ifdef BLUETOOTH_ENABLE_PERSISTENCE_MIGRATION
#include "BluetoothPersistenceAdapter.h"
#endif
Expand All @@ -33,7 +34,13 @@
namespace WPEFramework {
namespace Plugin {

inline bool missingFromPersistentStore(Core::hresult result)
{
return (Core::ERROR_NOT_EXIST == result) || (Core::ERROR_UNKNOWN_KEY == result);
}

#ifdef BLUETOOTH_ENABLE_PERSISTENCE_MIGRATION

Core::hresult BluetoothDeviceManager::writeCacheFromFilesystemPersistence()
{
BluetoothPersistenceAdapter adapter;
Expand Down Expand Up @@ -202,7 +209,7 @@ namespace WPEFramework {

_adminLock.Unlock();

} else if (Core::ERROR_NOT_EXIST != result) {
} else if (!missingFromPersistentStore(result)) {
LOGERR("Failed to load device info from PersistentStore, hresult=%d\n", result);
}

Expand Down Expand Up @@ -341,6 +348,22 @@ namespace WPEFramework {

Core::hresult BluetoothDeviceManager::init(PluginHost::IShell* service)
{
LOGINFO("BLUETOOTH_ENABLE_PERSISTENCE_MIGRATION is %s",
#ifdef BLUETOOTH_ENABLE_PERSISTENCE_MIGRATION
"enabled"
#else
"disabled"
#endif
);

LOGINFO("BLUETOOTH_PERSISTENT_FILE_PATH is '%s'",
#ifdef BLUETOOTH_PERSISTENT_FILE_PATH
BLUETOOTH_PERSISTENT_FILE_PATH
#else
"unavailable"
#endif
);

if (service == nullptr) {
return Core::ERROR_GENERAL;
}
Expand All @@ -349,13 +372,13 @@ namespace WPEFramework {
_service->AddRef();

const Core::hresult storageResult = updateCacheFromStorage();
if ((Core::ERROR_NONE != storageResult) && (Core::ERROR_NOT_EXIST != storageResult)) {
if ((Core::ERROR_NONE != storageResult) && !missingFromPersistentStore(storageResult)) {
LOGERR("PersistentStore read failed (hresult=%d); aborting init to avoid data loss", storageResult);
return storageResult;
}
Comment on lines 371 to 378

#ifdef BLUETOOTH_ENABLE_PERSISTENCE_MIGRATION
if (Core::ERROR_NOT_EXIST == storageResult) {
if (missingFromPersistentStore(storageResult)) {
LOGINFO("Migration attempted: PersistentStore device info is missing; trying filesystem persistence import.");

const Core::hresult migrationResult = writeCacheFromFilesystemPersistence();
Expand Down Expand Up @@ -488,6 +511,34 @@ namespace WPEFramework {
}
}

Core::hresult BluetoothDeviceManager::setLastVolumeSetting(const std::string& deviceID, long long volumeSetting)
{
LOGINFO("deviceID=%s, volumeSetting=%lld\n", deviceID.c_str(), volumeSetting);

BluetoothDeviceInfo deviceInfo;

_adminLock.Lock();

Core::hresult result = getPairedDeviceInfo(deviceID, deviceInfo);

if (Core::ERROR_NONE != result) {
LOGERR("Device info is not found in cache for deviceID: %s", deviceID.c_str());
_adminLock.Unlock();
return Core::ERROR_NOT_EXIST;
}

deviceInfo.lastVolumeSetting = volumeSetting;
_pairedDeviceCache[deviceID] = std::move(deviceInfo);
_adminLock.Unlock();

result = writeStorageFromCache();
if (Core::ERROR_NONE != result) {
LOGERR("Failed to update storage from cache after setting lastVolumeSetting for deviceID=%s", deviceID.c_str());
}

return result;
}

Core::hresult BluetoothDeviceManager::getLastConnectTimeUtc(const std::string& deviceID, std::string& lastConnectTimeUtc)
{
LOGINFO("deviceID=%s\n", deviceID.c_str());
Expand Down
1 change: 1 addition & 0 deletions Bluetooth/BluetoothDeviceManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ namespace WPEFramework {
Core::hresult getAutoConnect(const std::string& deviceID, AutoConnectStatus& status);
void setLastConnectTimeUtc(const std::string& deviceID);
Core::hresult getLastConnectTimeUtc(const std::string& deviceID, std::string& lastConnectTimeUtc);
Core::hresult setLastVolumeSetting(const std::string& deviceID, long long volumeSetting);
Core::hresult addDevice(const std::string& deviceID);
Core::hresult removeDevice(const std::string& deviceID);
std::unordered_map<std::string /* deviceID */, BluetoothDeviceInfo /* deviceInfo */> getPairedDeviceInfos();
Expand Down
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ if(RDK_SERVICES_L1_TEST)
add_subdirectory(Tests/L1Tests)
endif()

if(RDK_SERVICE_L2_TEST)
add_subdirectory(Tests/L2Tests)
endif()

if(PLUGIN_BLUETOOTH)
add_subdirectory(Bluetooth)
endif()
Expand Down
37 changes: 25 additions & 12 deletions Tests/L1Tests/tests/test_Bluetooth.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1280,11 +1280,24 @@ TEST_F(BluetoothLegacyPersistenceMigrationParseTest, legacyPersistenceMigrationS
EXPECT_TRUE(response.find("\"lastConnectTimeUtc\":\"1700000000\"") != string::npos);
}

TEST_F(BluetoothLegacyPersistenceMigrationParseTest, legacyPersistenceMigrationMissingStore_ValidFilesystemPersistenceImportPersistsToStore)
class BluetoothLegacyPersistenceMigrationParseParamTest
: public BluetoothLegacyPersistenceMigrationParseTest
, public ::testing::WithParamInterface<uint32_t> {
};

INSTANTIATE_TEST_SUITE_P(
Comment thread
pahearn73 marked this conversation as resolved.
Dismissed
StoreKeyNotFound,
BluetoothLegacyPersistenceMigrationParseParamTest,
::testing::Values(Core::ERROR_NOT_EXIST, Core::ERROR_UNKNOWN_KEY),
[](const ::testing::TestParamInfo<uint32_t>& paramInfo) {
return paramInfo.param == Core::ERROR_NOT_EXIST ? "ERROR_NOT_EXIST" : "ERROR_UNKNOWN_KEY";
});

TEST_P(BluetoothLegacyPersistenceMigrationParseParamTest, legacyPersistenceMigrationMissingStore_ValidFilesystemPersistenceImportPersistsToStore)
{
std::string persistedJson;
EXPECT_CALL(*p_storeMock, GetValue(::testing::_, ::testing::_, ::testing::_))
.WillOnce(::testing::Return(Core::ERROR_NOT_EXIST));
.WillOnce(::testing::Return(GetParam()));
EXPECT_CALL(*p_storeMock, SetValue(::testing::_, ::testing::_, ::testing::_))
.Times(::testing::AtLeast(1))
.WillRepeatedly(::testing::DoAll(
Expand All @@ -1306,7 +1319,7 @@ TEST_F(BluetoothLegacyPersistenceMigrationParseTest, legacyPersistenceMigrationM
EXPECT_TRUE(persistedJson.find("\"lastConnectTimeUtc\":\"1712345680\"") != string::npos);
}

TEST_F(BluetoothLegacyPersistenceMigrationParseTest, legacyPersistenceMigrationMissingStore_MissingFilesystemPersistenceSourceGracefulFallback)
TEST_P(BluetoothLegacyPersistenceMigrationParseParamTest, legacyPersistenceMigrationMissingStore_MissingFilesystemPersistenceSourceGracefulFallback)
{
const std::string seedPayload =
"{\"pairedDevices\":[{\"deviceAddr\":\"123\",\"deviceType\":\"HEADPHONES\","
Expand All @@ -1316,7 +1329,7 @@ TEST_F(BluetoothLegacyPersistenceMigrationParseTest, legacyPersistenceMigrationM
}

EXPECT_CALL(*p_storeMock, GetValue(::testing::_, ::testing::_, ::testing::_))
.WillOnce(::testing::Return(Core::ERROR_NOT_EXIST));
.WillOnce(::testing::Return(GetParam()));

EXPECT_TRUE(plugin->Initialize(&service).empty());

Expand All @@ -1329,10 +1342,10 @@ TEST_F(BluetoothLegacyPersistenceMigrationParseTest, legacyPersistenceMigrationM
EXPECT_TRUE(syncedFilesystemPersistencePayload.find("\"autoConnectStatus\":true") != string::npos);
}

TEST_F(BluetoothLegacyPersistenceMigrationParseTest, legacyPersistenceMigrationMissingStore_MalformedFilesystemPersistencePayloadNonFatal)
TEST_P(BluetoothLegacyPersistenceMigrationParseParamTest, legacyPersistenceMigrationMissingStore_MalformedFilesystemPersistencePayloadNonFatal)
{
EXPECT_CALL(*p_storeMock, GetValue(::testing::_, ::testing::_, ::testing::_))
.WillOnce(::testing::Return(Core::ERROR_NOT_EXIST));
.WillOnce(::testing::Return(GetParam()));

const std::string malformedPayload = "{\"pairedDevices\":[{\"deviceAddr\":\"123\"";
if (!initializeFromFilesystemPersistencePayload(malformedPayload)) {
Expand All @@ -1355,7 +1368,7 @@ TEST_F(BluetoothLegacyPersistenceMigrationParseTest, legacyPersistenceMigrationM
EXPECT_TRUE(response.find("\"pairedDevices\"") != string::npos);
}

TEST_F(BluetoothLegacyPersistenceMigrationParseTest, rollbackSyncMutations_WriteFilesystemPersistenceWhenFlagOn)
TEST_P(BluetoothLegacyPersistenceMigrationParseParamTest, rollbackSyncMutations_WriteFilesystemPersistenceWhenFlagOn)
{
const std::string seedPayload =
"{\"pairedDevices\":[{\"deviceAddr\":\"123\",\"deviceType\":\"HEADPHONES\","
Expand All @@ -1365,7 +1378,7 @@ TEST_F(BluetoothLegacyPersistenceMigrationParseTest, rollbackSyncMutations_Write
}

EXPECT_CALL(*p_storeMock, GetValue(::testing::_, ::testing::_, ::testing::_))
.WillOnce(::testing::Return(Core::ERROR_NOT_EXIST));
.WillOnce(::testing::Return(GetParam()));

EXPECT_TRUE(plugin->Initialize(&service).empty());

Expand Down Expand Up @@ -1652,7 +1665,7 @@ TEST_F(BluetoothLegacyPersistenceMigrationParseTest, parse_FriendlyName_Persiste
// Data mapping tests: write (cache → filesystem file)
// ============================================================================

TEST_F(BluetoothLegacyPersistenceMigrationParseTest, write_InitialDeviceAutoConnectUnset_WritesAutoConnectFalseToFilesystem)
TEST_P(BluetoothLegacyPersistenceMigrationParseParamTest, write_InitialDeviceAutoConnectUnset_WritesAutoConnectFalseToFilesystem)
{
// A device entry is present in the filesystem file without an explicit autoConnectStatus
// field (UNSET). Write() must serialize UNSET as false when there is no prior
Expand All @@ -1665,7 +1678,7 @@ TEST_F(BluetoothLegacyPersistenceMigrationParseTest, write_InitialDeviceAutoConn
}

EXPECT_CALL(*p_storeMock, GetValue(::testing::_, ::testing::_, ::testing::_))
.WillOnce(::testing::Return(Core::ERROR_NOT_EXIST));
.WillOnce(::testing::Return(GetParam()));

EXPECT_TRUE(plugin->Initialize(&service).empty());

Expand All @@ -1675,7 +1688,7 @@ TEST_F(BluetoothLegacyPersistenceMigrationParseTest, write_InitialDeviceAutoConn
EXPECT_TRUE(filesystemPayload.find("\"autoConnectStatus\":false") != string::npos);
}

TEST_F(BluetoothLegacyPersistenceMigrationParseTest, write_LastConnectTimeUtcEmpty_WritesZeroToFilesystem)
TEST_P(BluetoothLegacyPersistenceMigrationParseParamTest, write_LastConnectTimeUtcEmpty_WritesZeroToFilesystem)
{
// When the filesystem file entry has no lastConnectionTimeUTC field, the cache
// entry has an empty lastConnectTimeUtc. Write() must emit lastConnectionTimeUTC
Expand All @@ -1688,7 +1701,7 @@ TEST_F(BluetoothLegacyPersistenceMigrationParseTest, write_LastConnectTimeUtcEmp
}

EXPECT_CALL(*p_storeMock, GetValue(::testing::_, ::testing::_, ::testing::_))
.WillOnce(::testing::Return(Core::ERROR_NOT_EXIST));
.WillOnce(::testing::Return(GetParam()));

EXPECT_TRUE(plugin->Initialize(&service).empty());

Expand Down
72 changes: 72 additions & 0 deletions Tests/L2Tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# If not stated otherwise in this file or this component's LICENSE file the
# following copyright and licenses apply:
#
# Copyright 2025 RDK Management
#
# 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.


set(PLUGIN_NAME L2TestsBT)
set(MODULE_NAME ${NAMESPACE}${PLUGIN_NAME})
set(THUNDER_PORT 9998)

find_package(${NAMESPACE}Plugins REQUIRED)

if(PLUGIN_BLUETOOTH)
set(SRC_FILES ${SRC_FILES} tests/Bluetooth_L2Test.cpp)
endif()

add_library(${MODULE_NAME} SHARED ${SRC_FILES})

Comment on lines +25 to +30
set_target_properties(${MODULE_NAME} PROPERTIES
CXX_STANDARD 14
CXX_STANDARD_REQUIRED YES)

target_compile_definitions(${MODULE_NAME}
PRIVATE
MODULE_NAME=Plugin_${PLUGIN_NAME}
THUNDER_PORT="${THUNDER_PORT}")

target_compile_options(${MODULE_NAME} PRIVATE -Wno-error)
target_link_libraries(${MODULE_NAME} PRIVATE ${NAMESPACE}Plugins::${NAMESPACE}Plugins)

if (NOT L2_TEST_OOP_RPC)
find_library(TESTMOCKLIB_LIBRARIES NAMES TestMocklib)
if (TESTMOCKLIB_LIBRARIES)
message ("Found mock library - ${TESTMOCKLIB_LIBRARIES}")
target_link_libraries(${MODULE_NAME} PRIVATE ${TESTMOCKLIB_LIBRARIES})
else (TESTMOCKLIB_LIBRARIES)
message ("Require ${TESTMOCKLIB_LIBRARIES} library")
endif (TESTMOCKLIB_LIBRARIES)
endif (NOT L2_TEST_OOP_RPC)

find_library(MOCKACCESSOR_LIBRARIES NAMES MockAccessor)
if (MOCKACCESSOR_LIBRARIES)
message ("Found MockAccessor library - ${MOCKACCESSOR_LIBRARIES}")
target_link_libraries(${MODULE_NAME} PRIVATE ${MOCKACCESSOR_LIBRARIES})
else (MOCKACCESSOR_LIBRARIES)
message ("Require ${MOCKACCESSOR_LIBRARIES} library")
endif (MOCKACCESSOR_LIBRARIES)

target_include_directories(
${MODULE_NAME} PRIVATE ./
../../helpers
../../../entservices-testframework/Tests/mocks
../../../entservices-testframework/Tests/mocks/thunder
../../../entservices-testframework/Tests/mocks/devicesettings
../../../entservices-testframework/Tests/mocks/MockPlugin
../../../entservices-testframework/Tests/L2Tests/L2TestsPlugin
${CMAKE_INSTALL_PREFIX}/include
)

install(TARGETS ${MODULE_NAME} DESTINATION lib)
Comment on lines +29 to +72
Loading
Loading