| /* |
| * Copyright (c) 2024 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 <system/SystemConfig.h> |
| |
| // EventLoopHandlers are only supported by a select-based LayerSocketsLoop |
| #if CHIP_SYSTEM_CONFIG_USE_SOCKETS && !CHIP_SYSTEM_CONFIG_USE_DISPATCH |
| // The fake PlatformManagerImpl does not drive the system layer event loop |
| #if !CHIP_DEVICE_LAYER_TARGET_FAKE |
| |
| #include <lib/support/CodeUtils.h> |
| #include <platform/CHIPDeviceLayer.h> |
| |
| #include <functional> |
| #include <string> |
| |
| using namespace chip; |
| using namespace chip::System::Clock; |
| using namespace chip::System::Clock::Literals; |
| |
| class TestEventLoopHandler : public ::testing::Test |
| { |
| public: |
| static void SetUpTestSuite() |
| { |
| ASSERT_EQ(Platform::MemoryInit(), CHIP_NO_ERROR); |
| ASSERT_EQ(DeviceLayer::PlatformMgr().InitChipStack(), CHIP_NO_ERROR); |
| } |
| |
| static void TearDownTestSuite() |
| { |
| DeviceLayer::PlatformMgr().Shutdown(); |
| Platform::MemoryShutdown(); |
| } |
| |
| static System::LayerSocketsLoop & SystemLayer() { return static_cast<System::LayerSocketsLoop &>(DeviceLayer::SystemLayer()); } |
| |
| // Schedules a call to the provided lambda and returns a cancel function. |
| template <class Lambda> |
| static std::function<void()> Schedule(Timeout delay, Lambda lambda) |
| { |
| System::TimerCompleteCallback callback = [](System::Layer * layer, void * ctx) { |
| auto * function = static_cast<std::function<void()> *>(ctx); |
| (*function)(); |
| delete function; |
| }; |
| auto * function = new std::function<void()>(lambda); |
| SystemLayer().StartTimer(delay, callback, function); |
| return [=] { |
| SystemLayer().CancelTimer(callback, function); |
| delete function; |
| }; |
| } |
| |
| template <class Lambda> |
| static void ScheduleNextTick(Lambda lambda) |
| { |
| // ScheduleLambda is based on device events, which are greedily processed until the |
| // queue is empty, so we can't use it to wait for the next event loop tick. Just use |
| // a timer with a very short delay. |
| Schedule(1_ms, lambda); |
| } |
| }; |
| |
| TEST_F(TestEventLoopHandler, EventLoopHandlerSequence) |
| { |
| struct : public System::EventLoopHandler |
| { |
| std::string trace; |
| Timestamp PrepareEvents(Timestamp now) override |
| { |
| trace.append("P"); |
| return Timestamp::max(); |
| } |
| void HandleEvents() override { trace.append("H"); } |
| } loopHandler; |
| |
| ScheduleNextTick([&] { |
| loopHandler.trace.append("1"); |
| SystemLayer().AddLoopHandler(loopHandler); |
| loopHandler.trace.append("A"); |
| ScheduleNextTick([&] { // "P" |
| loopHandler.trace.append("2"); |
| ScheduleNextTick([&] { // "H", "P" |
| loopHandler.trace.append("3"); |
| SystemLayer().RemoveLoopHandler(loopHandler); |
| loopHandler.trace.append("R"); |
| ScheduleNextTick([&] { |
| loopHandler.trace.append("4"); |
| DeviceLayer::PlatformMgr().StopEventLoopTask(); |
| }); |
| }); |
| }); |
| }); |
| |
| chip::DeviceLayer::PlatformMgr().RunEventLoop(); |
| EXPECT_EQ(loopHandler.trace, std::string("1AP2HP3R4")); |
| } |
| |
| TEST_F(TestEventLoopHandler, EventLoopHandlerWake) |
| { |
| struct : public System::EventLoopHandler |
| { |
| Timestamp startTimestamp = System::SystemClock().GetMonotonicTimestamp(); |
| Timestamp wakeTimestamp = Timestamp::max(); |
| |
| Timestamp PrepareEvents(Timestamp now) override { return now + 400_ms; } |
| void HandleEvents() override |
| { |
| // StartTimer() (called by Schedule()) is liable to causes an immediate |
| // wakeup via Signal(), so ignore this call if it's only been a few ms. |
| auto now = System::SystemClock().GetMonotonicTimestamp(); |
| if (now - startTimestamp >= 100_ms) |
| { |
| wakeTimestamp = now; |
| DeviceLayer::PlatformMgr().StopEventLoopTask(); |
| } |
| } |
| } loopHandler; |
| |
| // Schedule a fallback timer to ensure the test stops |
| auto cancelFallback = Schedule(1000_ms, [] { DeviceLayer::PlatformMgr().StopEventLoopTask(); }); |
| SystemLayer().AddLoopHandler(loopHandler); |
| chip::DeviceLayer::PlatformMgr().RunEventLoop(); |
| SystemLayer().RemoveLoopHandler(loopHandler); |
| cancelFallback(); // avoid leaking the fallback timer |
| |
| Timestamp sleepDuration = loopHandler.wakeTimestamp - loopHandler.startTimestamp; |
| EXPECT_GE(sleepDuration.count(), 400u); // loopHandler requested wake-up after 400ms |
| EXPECT_LE(sleepDuration.count(), 500u); // allow some slack for test machine load |
| } |
| |
| #endif // !CHIP_DEVICE_LAYER_TARGET_FAKE |
| #endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS && !CHIP_SYSTEM_CONFIG_USE_DISPATCH |