// Copyright 2024 The Pigweed 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 // // https://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_async_fuchsia/dispatcher.h" #include #include "pw_async_fuchsia/util.h" #include "pw_unit_test/framework.h" #define ASSERT_CANCELLED(status) ASSERT_EQ(Status::Cancelled(), status) using namespace std::chrono_literals; namespace pw::async_fuchsia { class DispatcherFuchsiaTest : public ::testing::Test { public: async_dispatcher_t* dispatcher() { return loop_.dispatcher(); } void RunLoopUntilIdle() { loop_.RunUntilIdle(); } void RunLoopFor(zx::duration duration) { loop_.RunFor(duration); } private: ::async::TestLoop loop_; }; TEST_F(DispatcherFuchsiaTest, TimeConversions) { zx::time time{timespec{123, 456}}; chrono::SystemClock::time_point tp = pw::async_fuchsia::ZxTimeToTimepoint(time); EXPECT_EQ(tp.time_since_epoch(), 123s + 456ns); EXPECT_EQ(pw::async_fuchsia::TimepointToZxTime(tp), time); } TEST_F(DispatcherFuchsiaTest, Basic) { FuchsiaDispatcher fuchsia_dispatcher(dispatcher()); bool set = false; async::Task task([&set](async::Context& ctx, Status status) { PW_TEST_ASSERT_OK(status); set = true; }); fuchsia_dispatcher.Post(task); RunLoopUntilIdle(); EXPECT_TRUE(set); } TEST_F(DispatcherFuchsiaTest, DelayedTasks) { FuchsiaDispatcher fuchsia_dispatcher(dispatcher()); int c = 0; async::Task first([&c](async::Context& ctx, Status status) { PW_TEST_ASSERT_OK(status); c = c * 10 + 1; }); async::Task second([&c](async::Context& ctx, Status status) { PW_TEST_ASSERT_OK(status); c = c * 10 + 2; }); async::Task third([&c](async::Context& ctx, Status status) { PW_TEST_ASSERT_OK(status); c = c * 10 + 3; }); fuchsia_dispatcher.PostAfter(third, 20ms); fuchsia_dispatcher.PostAfter(first, 5ms); fuchsia_dispatcher.PostAfter(second, 10ms); RunLoopFor(zx::msec(25)); EXPECT_EQ(c, 123); } TEST_F(DispatcherFuchsiaTest, CancelTask) { FuchsiaDispatcher fuchsia_dispatcher(dispatcher()); async::Task task([](async::Context& ctx, Status status) { FAIL(); }); fuchsia_dispatcher.Post(task); EXPECT_TRUE(fuchsia_dispatcher.Cancel(task)); RunLoopUntilIdle(); } class DestructionChecker { public: explicit DestructionChecker(bool* flag) : flag_(flag) {} DestructionChecker(DestructionChecker&& other) { flag_ = other.flag_; other.flag_ = nullptr; } ~DestructionChecker() { if (flag_) { *flag_ = true; } } private: bool* flag_; }; TEST_F(DispatcherFuchsiaTest, HeapAllocatedTasks) { FuchsiaDispatcher fuchsia_dispatcher(dispatcher()); int c = 0; for (int i = 0; i < 3; i++) { Post(&fuchsia_dispatcher, [&c](async::Context& ctx, Status status) { PW_TEST_ASSERT_OK(status); c++; }); } EXPECT_EQ(c, 0); RunLoopUntilIdle(); EXPECT_EQ(c, 3); // Test that the lambda is destroyed after being called. bool flag = false; Post(&fuchsia_dispatcher, [checker = DestructionChecker(&flag)](async::Context& ctx, Status status) {}); EXPECT_FALSE(flag); RunLoopUntilIdle(); EXPECT_TRUE(flag); } TEST_F(DispatcherFuchsiaTest, ChainedTasks) { FuchsiaDispatcher fuchsia_dispatcher(dispatcher()); int c = 0; Post(&fuchsia_dispatcher, [&c](async::Context& ctx, Status status) { PW_TEST_ASSERT_OK(status); c++; Post(ctx.dispatcher, [&c](async::Context& ctx, Status status) { PW_TEST_ASSERT_OK(status); c++; Post(ctx.dispatcher, [&c](async::Context& ctx, Status status) { PW_TEST_ASSERT_OK(status); c++; }); }); }); RunLoopUntilIdle(); EXPECT_EQ(c, 3); } } // namespace pw::async_fuchsia