From 8cb529be2aebe5a8dddc752ec12eca2df053e437 Mon Sep 17 00:00:00 2001 From: Yakun Xu Date: Wed, 9 Oct 2024 12:00:35 +0800 Subject: [PATCH] [test] add fake platform (#10594) This commit adds a fake platform for unit/component tests. This platform uses virtual time so it should be able to run fast. Note that the fake platforms leverage C++ virtual methods to allow mocking. This commit verifies setting active dataset triggers the active dataset change event by the notifier. --- .code-spell-ignore | 1 + .github/workflows/unit.yml | 4 +- src/lib/spinel/CMakeLists.txt | 1 + tests/CMakeLists.txt | 5 + tests/gtest/CMakeLists.txt | 67 +++++ tests/gtest/dataset_test.cpp | 88 ++++++ tests/gtest/fake_platform.cpp | 509 ++++++++++++++++++++++++++++++++++ tests/gtest/fake_platform.hpp | 117 ++++++++ 8 files changed, 790 insertions(+), 2 deletions(-) create mode 100644 tests/gtest/CMakeLists.txt create mode 100644 tests/gtest/dataset_test.cpp create mode 100644 tests/gtest/fake_platform.cpp create mode 100644 tests/gtest/fake_platform.hpp diff --git a/.code-spell-ignore b/.code-spell-ignore index ae1c08b1f..8e2fc3933 100644 --- a/.code-spell-ignore +++ b/.code-spell-ignore @@ -12,6 +12,7 @@ aparent apending asender asent +atleast ect intialize nd diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 2e7bdcee7..d7d11c790 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -79,11 +79,11 @@ jobs: sudo rm /etc/apt/sources.list.d/* && sudo apt-get update sudo apt-get --no-install-recommends install -y ninja-build lcov - name: Build Simulation - run: ./script/cmake-build simulation -DOT_BORDER_ROUTING=ON -DOT_NCP_INFRA_IF=ON + run: ./script/cmake-build simulation -DOT_BUILD_GTEST=ON -DOT_BORDER_ROUTING=ON -DOT_NCP_INFRA_IF=ON - name: Test Simulation run: cd build/simulation && ninja test - name: Build Multipan Simulation - run: ./script/cmake-build simulation -DOT_MULTIPAN_TEST=ON + run: ./script/cmake-build simulation -DOT_BUILD_GTEST=ON -DOT_MULTIPAN_TEST=ON - name: Test Multipan Simulation run: cd build/simulation && ninja test - name: Build POSIX diff --git a/src/lib/spinel/CMakeLists.txt b/src/lib/spinel/CMakeLists.txt index 70f6c677a..e09473258 100644 --- a/src/lib/spinel/CMakeLists.txt +++ b/src/lib/spinel/CMakeLists.txt @@ -104,6 +104,7 @@ target_link_libraries(openthread-radio-spinel INTERFACE ot-lib-config PRIVATE + openthread-platform ot-config ) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 840537647..47424b8ee 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -26,8 +26,13 @@ # POSSIBILITY OF SUCH DAMAGE. # +option(OT_BUILD_GTEST "enable gtest") + if(OT_FTD AND BUILD_TESTING) add_subdirectory(unit) + if(OT_BUILD_GTEST) + add_subdirectory(gtest) + endif() endif() option(OT_FUZZ_TARGETS "enable fuzz targets" OFF) diff --git a/tests/gtest/CMakeLists.txt b/tests/gtest/CMakeLists.txt new file mode 100644 index 000000000..89fefb921 --- /dev/null +++ b/tests/gtest/CMakeLists.txt @@ -0,0 +1,67 @@ +# +# Copyright (c) 2024, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +cmake_minimum_required(VERSION 3.14) +project(openthread-gtest) + +# GoogleTest requires at least C++14 +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip +) +# For Windows: Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) + +include(GoogleTest) + +add_library(ot-fake-platform + fake_platform.cpp +) +target_link_libraries(ot-fake-platform + ot-config +) + +add_library(ot-fake-ftd INTERFACE) +target_link_libraries(ot-fake-ftd INTERFACE + openthread-ftd + ot-fake-platform +) + +add_executable(ot-ftd-gtest + dataset_test.cpp +) +target_link_libraries(ot-ftd-gtest + ot-fake-ftd + GTest::gmock_main +) +gtest_discover_tests(ot-ftd-gtest) diff --git a/tests/gtest/dataset_test.cpp b/tests/gtest/dataset_test.cpp new file mode 100644 index 000000000..95f14a37d --- /dev/null +++ b/tests/gtest/dataset_test.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include "gmock/gmock.h" + +#include "fake_platform.hpp" + +using namespace ot; +using ::testing::AnyNumber; +using ::testing::AtLeast; +using ::testing::MockFunction; +using ::testing::Truly; + +template class MockCallback : public testing::MockFunction +{ +public: + static R CallWithContext(A... aArgs, void *aContext) + { + return static_cast(aContext)->Call(aArgs...); + }; +}; + +TEST(otDatasetSetActiveTlvs, shouldTriggerStateCallbackOnSuccess) +{ + FakePlatform fakePlatform; + + typedef MockCallback MockStateCallback; + + MockStateCallback mockStateCallback; + + EXPECT_CALL(mockStateCallback, Call).Times(AnyNumber()); + + EXPECT_CALL(mockStateCallback, Call(Truly([](otChangedFlags aChangedFlags) -> bool { + return (aChangedFlags & OT_CHANGED_ACTIVE_DATASET); + }))) + .Times(AtLeast(1)); + + otError error = otSetStateChangedCallback(FakePlatform::CurrentInstance(), MockStateCallback::CallWithContext, + &mockStateCallback); + assert(error == OT_ERROR_NONE); + + otOperationalDataset dataset; + otOperationalDatasetTlvs datasetTlvs; + + error = otDatasetCreateNewNetwork(FakePlatform::CurrentInstance(), &dataset); + assert(error == OT_ERROR_NONE); + + otDatasetConvertToTlvs(&dataset, &datasetTlvs); + error = otDatasetSetActiveTlvs(FakePlatform::CurrentInstance(), &datasetTlvs); + assert(error == OT_ERROR_NONE); + + fakePlatform.GoInMs(10000); +} diff --git a/tests/gtest/fake_platform.cpp b/tests/gtest/fake_platform.cpp new file mode 100644 index 000000000..9ba765040 --- /dev/null +++ b/tests/gtest/fake_platform.cpp @@ -0,0 +1,509 @@ +/* + * Copyright (c) 2024, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "fake_platform.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "openthread/error.h" + +using namespace ot; + +namespace ot { + +FakePlatform *FakePlatform::sPlatform = nullptr; + +FakePlatform::FakePlatform() +{ + assert(sPlatform == nullptr); + sPlatform = this; + + mTransmitFrame.mPsdu = mTransmitBuffer; + +#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE +#if OPENTHREAD_CONFIG_MULTIPLE_STATIC_INSTANCE_ENABLE + mInstance = otInstanceInitMultiple(0); +#else + { + size_t instanceBufferLength = 0; + void *instanceBuffer = nullptr; + + otInstanceInit(nullptr, &instanceBufferLength); + + instanceBuffer = malloc(instanceBufferLength); + assert(instanceBuffer != nullptr); + memset(instanceBuffer, 0, instanceBufferLength); + + mInstance = otInstanceInit(instanceBuffer, &instanceBufferLength); + } +#endif +#else + mInstance = otInstanceInitSingle(); +#endif +} + +FakePlatform::~FakePlatform() +{ + otInstanceFinalize(mInstance); + sPlatform = nullptr; +} + +void FakePlatform::StartMicroAlarm(uint32_t aT0, uint32_t aDt) +{ + uint64_t start = mNow; + uint32_t now = mNow; + + if (static_cast(aT0 - now) > 0 || static_cast(aT0 - now) + static_cast(aDt) > 0) + { + start += static_cast(aDt) + static_cast(aT0 - now); + } + + mMicroAlarmStart = start; +} +void FakePlatform::StartMilliAlarm(uint32_t aT0, uint32_t aDt) +{ + uint64_t start = mNow - (mNow % OT_US_PER_MS); + uint32_t now = (mNow / OT_US_PER_MS); + + if (static_cast(aT0 - now) > 0 || static_cast(aT0 - now) + static_cast(aDt) > 0) + { + start += (static_cast(aDt) + static_cast(aT0 - now)) * OT_US_PER_MS; + } + + mMilliAlarmStart = start; +} + +void FakePlatform::StopMicroAlarm() { mMicroAlarmStart = kAlarmStop; } + +void FakePlatform::StopMilliAlarm() { mMilliAlarmStart = kAlarmStop; } + +void FakePlatform::ProcessAlarm(uint64_t &aTimeout) +{ + uint64_t end = mNow + aTimeout; + + uint64_t *alarm = &end; + + if (mMicroAlarmStart < *alarm) + { + alarm = &mMicroAlarmStart; + } + + if (mMilliAlarmStart < *alarm) + { + alarm = &mMilliAlarmStart; + } + + if (mNow < *alarm) + { + aTimeout -= *alarm - mNow; + mNow = *alarm; + } + *alarm = kAlarmStop; + + if (alarm == &mMicroAlarmStart) + { + otPlatAlarmMicroFired(mInstance); + } + else if (alarm == &mMilliAlarmStart) + { + otPlatAlarmMilliFired(mInstance); + } +} + +uint64_t FakePlatform::Run(uint64_t aTimeoutInUs) +{ + if (otTaskletsArePending(mInstance)) + { + otTaskletsProcess(mInstance); + } + else + { + ProcessAlarm(aTimeoutInUs); + } + + return aTimeoutInUs; +} + +void FakePlatform::GoInUs(uint64_t aTimeoutInUs) +{ + while ((aTimeoutInUs = Run(aTimeoutInUs)) > 0) + { + // nothing + } +} + +otError FakePlatform::Transmit(otRadioFrame *aFrame) +{ + otPlatRadioTxStarted(mInstance, aFrame); + return OT_ERROR_NONE; +} + +otError FakePlatform::SettingsGet(uint16_t aKey, uint16_t aIndex, uint8_t *aValue, uint16_t *aValueLength) const +{ + auto setting = mSettings.find(aKey); + + if (setting == mSettings.end()) + { + return OT_ERROR_NOT_FOUND; + } + + if (aIndex > setting->second.size()) + { + return OT_ERROR_NOT_FOUND; + } + + if (aValueLength == nullptr) + { + return OT_ERROR_NONE; + } + + const auto &data = setting->second[aIndex]; + + if (aValue == nullptr) + { + *aValueLength = data.size(); + return OT_ERROR_NONE; + } + + if (*aValueLength >= data.size()) + { + *aValueLength = data.size(); + } + + memcpy(aValue, &data[0], *aValueLength); + + return OT_ERROR_NONE; +} + +otError FakePlatform::SettingsSet(uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength) +{ + auto setting = std::vector(aValue, aValue + aValueLength); + + mSettings[aKey].clear(); + mSettings[aKey].push_back(setting); + + return OT_ERROR_NONE; +} + +otError FakePlatform::SettingsAdd(uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength) +{ + auto setting = std::vector(aValue, aValue + aValueLength); + + mSettings[aKey].push_back(setting); + + return OT_ERROR_NONE; +} + +otError FakePlatform::SettingsDelete(uint16_t aKey, int aIndex) +{ + auto setting = mSettings.find(aKey); + if (setting == mSettings.end()) + { + return OT_ERROR_NOT_FOUND; + } + + if (aIndex >= setting->second.size()) + { + return OT_ERROR_NOT_FOUND; + } + setting->second.erase(setting->second.begin() + aIndex); + return OT_ERROR_NONE; +} + +void FakePlatform::SettingsWipe() { mSettings.clear(); } + +void FakePlatform::FlashInit() { memset(mFlash, 0xff, sizeof(mFlash)); } +void FakePlatform::FlashErase(uint8_t aSwapIndex) +{ + uint32_t address; + + assert(aSwapIndex < kFlashSwapNum); + + address = aSwapIndex ? kFlashSwapSize : 0; + + memset(mFlash + address, 0xff, kFlashSwapSize); +} + +void FakePlatform::FlashRead(uint8_t aSwapIndex, uint32_t aOffset, void *aData, uint32_t aSize) const +{ + uint32_t address; + + assert(aSwapIndex < kFlashSwapNum); + assert(aSize <= kFlashSwapSize); + assert(aOffset <= (kFlashSwapSize - aSize)); + + address = aSwapIndex ? kFlashSwapSize : 0; + + memcpy(aData, mFlash + address + aOffset, aSize); +} + +void FakePlatform::FlashWrite(uint8_t aSwapIndex, uint32_t aOffset, const void *aData, uint32_t aSize) +{ + uint32_t address; + + assert(aSwapIndex < kFlashSwapNum); + assert(aSize <= kFlashSwapSize); + assert(aOffset <= (kFlashSwapSize - aSize)); + + address = aSwapIndex ? kFlashSwapSize : 0; + + for (uint32_t index = 0; index < aSize; index++) + { + mFlash[address + aOffset + index] &= static_cast(aData)[index]; + } +} + +} // namespace ot + +extern "C" { + +void otTaskletsSignalPending(otInstance *) {} + +void otPlatAlarmMilliStop(otInstance *) { FakePlatform::CurrentPlatform().StopMilliAlarm(); } + +void otPlatAlarmMilliStartAt(otInstance *, uint32_t aT0, uint32_t aDt) +{ + FakePlatform::CurrentPlatform().StartMilliAlarm(aT0, aDt); +} + +uint32_t otPlatAlarmMilliGetNow(void) { return FakePlatform::CurrentPlatform().GetNow() / OT_US_PER_MS; } +void otPlatAlarmMicroStop(otInstance *) { FakePlatform::CurrentPlatform().StopMicroAlarm(); } + +void otPlatAlarmMicroStartAt(otInstance *, uint32_t aT0, uint32_t aDt) +{ + FakePlatform::CurrentPlatform().StartMicroAlarm(aT0, aDt); +} + +uint64_t otPlatTimeGet(void) { return FakePlatform::CurrentPlatform().GetNow(); } +uint16_t otPlatTimeGetXtalAccuracy(void) { return 0; } + +uint32_t otPlatAlarmMicroGetNow(void) { return otPlatTimeGet(); } + +void otPlatRadioGetIeeeEui64(otInstance *, uint8_t *aIeeeEui64) +{ + uint64_t eui64 = FakePlatform::CurrentPlatform().GetEui64(); + memcpy(aIeeeEui64, &eui64, sizeof(eui64)); +} + +void otPlatRadioSetPanId(otInstance *, uint16_t) {} + +void otPlatRadioSetExtendedAddress(otInstance *, const otExtAddress *) {} + +void otPlatRadioSetShortAddress(otInstance *, uint16_t) {} + +void otPlatRadioSetPromiscuous(otInstance *, bool) {} + +void otPlatRadioSetRxOnWhenIdle(otInstance *, bool) {} + +bool otPlatRadioIsEnabled(otInstance *) { return true; } + +otError otPlatRadioEnable(otInstance *) { return OT_ERROR_NONE; } + +otError otPlatRadioDisable(otInstance *) { return OT_ERROR_NONE; } + +otError otPlatRadioSleep(otInstance *) { return OT_ERROR_NONE; } + +otError otPlatRadioReceive(otInstance *, uint8_t) { return OT_ERROR_NONE; } + +otError otPlatRadioTransmit(otInstance *, otRadioFrame *aFrame) +{ + return FakePlatform::CurrentPlatform().Transmit(aFrame); +} + +otRadioFrame *otPlatRadioGetTransmitBuffer(otInstance *) { return FakePlatform::CurrentPlatform().GetTransmitBuffer(); } + +int8_t otPlatRadioGetRssi(otInstance *) { return 0; } + +otRadioCaps otPlatRadioGetCaps(otInstance *) { return OT_RADIO_CAPS_NONE; } + +bool otPlatRadioGetPromiscuous(otInstance *) { return false; } + +void otPlatRadioEnableSrcMatch(otInstance *, bool) {} + +otError otPlatRadioAddSrcMatchShortEntry(otInstance *, uint16_t) { return OT_ERROR_NONE; } + +otError otPlatRadioAddSrcMatchExtEntry(otInstance *, const otExtAddress *) { return OT_ERROR_NONE; } + +otError otPlatRadioClearSrcMatchShortEntry(otInstance *, uint16_t) { return OT_ERROR_NONE; } + +otError otPlatRadioClearSrcMatchExtEntry(otInstance *, const otExtAddress *) { return OT_ERROR_NONE; } + +void otPlatRadioClearSrcMatchShortEntries(otInstance *) {} + +void otPlatRadioClearSrcMatchExtEntries(otInstance *) {} + +otError otPlatRadioEnergyScan(otInstance *, uint8_t, uint16_t) { return OT_ERROR_NOT_IMPLEMENTED; } + +otError otPlatRadioSetTransmitPower(otInstance *, int8_t) { return OT_ERROR_NOT_IMPLEMENTED; } + +int8_t otPlatRadioGetReceiveSensitivity(otInstance *) { return -100; } + +otError otPlatRadioSetCcaEnergyDetectThreshold(otInstance *, int8_t) { return OT_ERROR_NONE; } + +otError otPlatRadioGetCcaEnergyDetectThreshold(otInstance *, int8_t *) { return OT_ERROR_NONE; } + +otError otPlatRadioGetCoexMetrics(otInstance *, otRadioCoexMetrics *) { return OT_ERROR_NONE; } + +otError otPlatRadioGetTransmitPower(otInstance *, int8_t *) { return OT_ERROR_NONE; } + +bool otPlatRadioIsCoexEnabled(otInstance *) { return true; } + +otError otPlatRadioSetCoexEnabled(otInstance *, bool) { return OT_ERROR_NOT_IMPLEMENTED; } + +void otPlatReset(otInstance *) {} + +otPlatResetReason otPlatGetResetReason(otInstance *) { return OT_PLAT_RESET_REASON_POWER_ON; } + +void otPlatWakeHost(void) {} + +otError otPlatEntropyGet(uint8_t *aOutput, uint16_t aOutputLength) +{ + otError error = OT_ERROR_NONE; + + assert(aOutput != nullptr); + + for (uint16_t length = 0; length < aOutputLength; length++) + { + aOutput[length] = static_cast(rand()); + } + + return error; +} + +void otPlatDiagSetOutputCallback(otInstance *, otPlatDiagOutputCallback, void *) {} + +void otPlatDiagModeSet(bool) {} + +bool otPlatDiagModeGet() { return false; } + +void otPlatDiagChannelSet(uint8_t) {} + +void otPlatDiagTxPowerSet(int8_t) {} + +void otPlatDiagRadioReceived(otInstance *, otRadioFrame *, otError) {} + +void otPlatDiagAlarmCallback(otInstance *) {} + +OT_TOOL_WEAK void otPlatLog(otLogLevel aLevel, otLogRegion aRegion, const char *aFormat, ...) {} + +void *otPlatCAlloc(size_t aNum, size_t aSize) { return calloc(aNum, aSize); } + +void otPlatFree(void *aPtr) { free(aPtr); } + +bool otPlatInfraIfHasAddress(uint32_t, const otIp6Address *) { return false; } + +otError otPlatInfraIfSendIcmp6Nd(uint32_t, const otIp6Address *, const uint8_t *, uint16_t) { return OT_ERROR_FAILED; } + +otError otPlatInfraIfDiscoverNat64Prefix(uint32_t) { return OT_ERROR_FAILED; } + +void otPlatDsoEnableListening(otInstance *aInstance, bool) {} + +void otPlatDsoConnect(otPlatDsoConnection *, const otSockAddr *) {} + +void otPlatDsoSend(otPlatDsoConnection *, otMessage *) {} + +void otPlatDsoDisconnect(otPlatDsoConnection *, otPlatDsoDisconnectMode) {} + +otError otPlatBleEnable(otInstance *) { return OT_ERROR_NONE; } + +otError otPlatBleDisable(otInstance *) { return OT_ERROR_NONE; } + +otError otPlatBleGetAdvertisementBuffer(otInstance *, uint8_t **aAdvertisementBuffer) { return OT_ERROR_NO_BUFS; } + +otError otPlatBleGapAdvStart(otInstance *, uint16_t) { return OT_ERROR_NONE; } + +otError otPlatBleGapAdvStop(otInstance *) { return OT_ERROR_NONE; } + +otError otPlatBleGapDisconnect(otInstance *) { return OT_ERROR_NONE; } + +otError otPlatBleGattMtuGet(otInstance *, uint16_t *) { return OT_ERROR_NONE; } + +otError otPlatBleGattServerIndicate(otInstance *, uint16_t, const otBleRadioPacket *) { return OT_ERROR_NONE; } + +void otPlatBleGetLinkCapabilities(otInstance *, otBleLinkCapabilities *) {} + +bool otPlatBleSupportsMultiRadio(otInstance *) { return false; } + +otError otPlatBleGapAdvSetData(otInstance *, uint8_t *, uint16_t) { return OT_ERROR_NONE; } + +otError otPlatSettingsGet(otInstance *, uint16_t aKey, int aIndex, uint8_t *aValue, uint16_t *aValueLength) +{ + return FakePlatform::CurrentPlatform().SettingsGet(aKey, aIndex, aValue, aValueLength); +} + +otError otPlatSettingsSet(otInstance *, uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength) +{ + return FakePlatform::CurrentPlatform().SettingsSet(aKey, aValue, aValueLength); +} + +otError otPlatSettingsAdd(otInstance *, uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength) +{ + return FakePlatform::CurrentPlatform().SettingsAdd(aKey, aValue, aValueLength); +} + +otError otPlatSettingsDelete(otInstance *, uint16_t aKey, int aIndex) +{ + return FakePlatform::CurrentPlatform().SettingsDelete(aKey, aIndex); +} + +void otPlatSettingsWipe(otInstance *) { FakePlatform::CurrentPlatform().SettingsWipe(); } + +void otPlatFlashInit(otInstance *) { return FakePlatform::CurrentPlatform().FlashInit(); } + +uint32_t otPlatFlashGetSwapSize(otInstance *) { return FakePlatform::CurrentPlatform().FlashGetSwapSize(); } + +void otPlatFlashErase(otInstance *, uint8_t aSwapIndex) { FakePlatform::CurrentPlatform().FlashErase(aSwapIndex); } + +void otPlatFlashRead(otInstance *, uint8_t aSwapIndex, uint32_t aOffset, void *aData, uint32_t aSize) +{ + FakePlatform::CurrentPlatform().FlashRead(aSwapIndex, aOffset, aData, aSize); +} + +void otPlatFlashWrite(otInstance *, uint8_t aSwapIndex, uint32_t aOffset, const void *aData, uint32_t aSize) +{ + FakePlatform::CurrentPlatform().FlashWrite(aSwapIndex, aOffset, aData, aSize); +} + +} // extern "C" diff --git a/tests/gtest/fake_platform.hpp b/tests/gtest/fake_platform.hpp new file mode 100644 index 000000000..99559578d --- /dev/null +++ b/tests/gtest/fake_platform.hpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2024, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef OT_GTEST_FAKE_PLATFORM_HPP_ +#define OT_GTEST_FAKE_PLATFORM_HPP_ + +#include + +#include +#include + +#include + +#include +#include +#include + +namespace ot { + +class FakePlatform +{ +public: + FakePlatform(); + virtual ~FakePlatform(); + + static FakePlatform &CurrentPlatform() { return *sPlatform; } + static otInstance *CurrentInstance() { return CurrentPlatform().mInstance; } + + /** + * Run until something happened or timeout. + * + * @param aTimeout The timeout in us. + * + * @returns the remaining timeout. + */ + uint64_t Run(uint64_t aTimeoutInUs = 0); + + void GoInUs(uint64_t aTimeoutInUs = 0); + + void GoInMs(uint32_t aTimeoutInMs = 0) { GoInUs(aTimeoutInMs * OT_US_PER_MS); } + + virtual uint64_t GetNow() const { return mNow; } + + virtual void StartMilliAlarm(uint32_t aT0, uint32_t aDt); + virtual void StopMilliAlarm(); + virtual void StartMicroAlarm(uint32_t aT0, uint32_t aDt); + virtual void StopMicroAlarm(); + + virtual otRadioFrame *GetTransmitBuffer() { return &mTransmitFrame; } + virtual otError Transmit(otRadioFrame *aFrame); + + virtual otError SettingsGet(uint16_t aKey, uint16_t aIndex, uint8_t *aValue, uint16_t *aValueLength) const; + virtual otError SettingsSet(uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength); + virtual otError SettingsAdd(uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength); + virtual otError SettingsDelete(uint16_t aKey, int aIndex); + virtual void SettingsWipe(); + + virtual void FlashInit(); + virtual void FlashErase(uint8_t aSwapIndex); + virtual void FlashRead(uint8_t aSwapIndex, uint32_t aOffset, void *aData, uint32_t aSize) const; + virtual void FlashWrite(uint8_t aSwapIndex, uint32_t aOffset, const void *aData, uint32_t aSize); + virtual uint32_t FlashGetSwapSize() const { return kFlashSwapSize; } + + virtual uint64_t GetEui64() const { return 0; } + +protected: + void ProcessAlarm(uint64_t &aTimeout); + + static constexpr uint64_t kAlarmStop = 0xffffffffffffffffUL; + + static constexpr uint32_t kFlashSwapSize = 2048; + static constexpr uint32_t kFlashSwapNum = 2; + + static FakePlatform *sPlatform; + + otInstance *mInstance = nullptr; + + uint64_t mNow = 0; + uint64_t mMicroAlarmStart = kAlarmStop; + uint64_t mMilliAlarmStart = kAlarmStop; + + otRadioFrame mTransmitFrame; + uint8_t mTransmitBuffer[OT_RADIO_FRAME_MAX_SIZE]; + + uint8_t mFlash[kFlashSwapSize * kFlashSwapNum]; + + std::map>> mSettings; +}; + +} // namespace ot + +#endif // OT_GTEST_FAKE_PLATFORM_HPP_