| // Copyright 2018 The Fuchsia Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include <fcntl.h> | |
| #include <time.h> | |
| #include <unistd.h> | |
| #include <gtest/gtest.h> | |
| #include "src/developer/debug/shared/fd_watcher.h" | |
| #include "src/developer/debug/shared/platform_message_loop.h" | |
| #if defined(__Fuchsia__) | |
| #include <lib/zx/socket.h> | |
| #include "src/developer/debug/shared/socket_watcher.h" | |
| #endif | |
| namespace debug_ipc { | |
| namespace { | |
| // This helper class sets a boolean when it's destructed. Tests can use it to ensure that the | |
| // lifetime of a lambda is correct. | |
| class SetOnDestruct { | |
| public: | |
| // The flag must outlive this class. | |
| explicit SetOnDestruct(bool* flag) : flag_(flag) {} | |
| ~SetOnDestruct() { *flag_ = true; } | |
| private: | |
| bool* flag_; | |
| }; | |
| } // namespace | |
| // This test either passes or hangs forever because the post didn't work. | |
| // We could add a timer timeout, but if regular task posting doesn't work it's | |
| // not clear why timer tasks would. | |
| TEST(MessageLoop, PostQuit) { | |
| PlatformMessageLoop loop; | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| loop.PostTask(FROM_HERE, [loop_ptr = &loop]() { loop_ptr->QuitNow(); }); | |
| loop.Run(); | |
| loop.Cleanup(); | |
| } | |
| // Like the above but expresses the task as a fit::promise. | |
| TEST(MessageLoop, PostPendingTaskQuit) { | |
| PlatformMessageLoop loop; | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| loop.PostTask(FROM_HERE, fit::make_promise([&loop]() { loop.QuitNow(); })); | |
| loop.Run(); | |
| loop.Cleanup(); | |
| } | |
| TEST(MessageLoop, TimerQuit) { | |
| const uint64_t kNano = 1000000000; | |
| PlatformMessageLoop loop; | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| struct timespec start; | |
| ASSERT_FALSE(clock_gettime(CLOCK_MONOTONIC, &start)); | |
| loop.PostTimer(FROM_HERE, 50, [loop_ptr = &loop]() { loop_ptr->QuitNow(); }); | |
| loop.Run(); | |
| struct timespec end; | |
| ASSERT_FALSE(clock_gettime(CLOCK_MONOTONIC, &end)); | |
| ASSERT_GE(end.tv_sec, start.tv_sec); | |
| uint64_t nsec = (end.tv_sec - start.tv_sec) * kNano; | |
| nsec += end.tv_nsec; | |
| nsec -= start.tv_nsec; | |
| EXPECT_GE(nsec, 50u); | |
| // If we test an upper bound for nsec this test could potentially be flaky. | |
| // We don't actually make any guarantees about the upper bound anyway. | |
| loop.Cleanup(); | |
| } | |
| // Tests a promise that suspends itself and then continues. | |
| TEST(MessageLoop, SuspendPromise) { | |
| PlatformMessageLoop loop; | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| bool lambda_destructed = false; | |
| fit::suspended_task suspended; | |
| int run_count = 0; | |
| bool should_complete = false; | |
| loop.PostTask(FROM_HERE, fit::make_promise( | |
| [&should_complete, &run_count, &suspended, | |
| destructed = std::make_shared<SetOnDestruct>(&lambda_destructed)]( | |
| fit::context& context) -> fit::result<> { | |
| run_count++; | |
| if (should_complete) | |
| return fit::ok(); | |
| suspended = context.suspend_task(); // So we can signal later. | |
| return fit::pending(); | |
| })); | |
| // Should not have run yet. | |
| EXPECT_EQ(0, run_count); | |
| // Pulse the message loop. The task should have run once (responded with "suspend") and set the | |
| // suspended_task. | |
| loop.PostTask(FROM_HERE, [&loop]() { loop.QuitNow(); }); | |
| loop.Run(); | |
| EXPECT_EQ(1, run_count); | |
| EXPECT_TRUE(suspended); | |
| // Run the loop again without doing anything. Nothing should have happened because the task was | |
| // not unsuspended. | |
| loop.PostTask(FROM_HERE, [&loop]() { loop.QuitNow(); }); | |
| loop.Run(); | |
| EXPECT_EQ(1, run_count); | |
| // Mark the task as runnable again. It should run once but still report pending. Note that we | |
| // run the task synchronously from the resume_task() message as explained in | |
| // MessageLoop::resolve_ticket(). | |
| suspended.resume_task(); | |
| EXPECT_EQ(2, run_count); | |
| EXPECT_TRUE(suspended); | |
| EXPECT_FALSE(lambda_destructed); // Lambda should not be deleted. | |
| // Tell the task to complete and signal again. It should be done. | |
| should_complete = true; | |
| suspended.resume_task(); | |
| loop.PostTask(FROM_HERE, [&loop]() { loop.QuitNow(); }); | |
| loop.Run(); | |
| EXPECT_EQ(3, run_count); | |
| EXPECT_FALSE(suspended); | |
| EXPECT_TRUE(lambda_destructed); // Lambda should be deleted. | |
| loop.Cleanup(); | |
| } | |
| // Duplicates the suspended_task controlling the suspended promise. | |
| TEST(MessageLoop, DuplicateSuspendedPromise) { | |
| PlatformMessageLoop loop; | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| bool lambda_destructed = false; | |
| fit::suspended_task suspended; | |
| int run_count = 0; | |
| bool should_complete = false; | |
| loop.PostTask(FROM_HERE, fit::make_promise( | |
| [&should_complete, &run_count, &suspended, | |
| destructed = std::make_shared<SetOnDestruct>(&lambda_destructed)]( | |
| fit::context& context) -> fit::result<> { | |
| run_count++; | |
| if (should_complete) | |
| return fit::ok(); | |
| suspended = context.suspend_task(); // So we can signal later. | |
| return fit::pending(); | |
| })); | |
| // Should not have run yet. | |
| EXPECT_EQ(0, run_count); | |
| // Pulse the message loop. The task should have run once (responded with "suspend") and set the | |
| // suspended_task. | |
| loop.PostTask(FROM_HERE, [&loop]() { loop.QuitNow(); }); | |
| loop.Run(); | |
| EXPECT_EQ(1, run_count); | |
| EXPECT_TRUE(suspended); | |
| // Duplicate the suspended task handle. | |
| fit::suspended_task suspended2 = suspended; | |
| should_complete = true; | |
| suspended.resume_task(); // Should run synchronously. | |
| EXPECT_EQ(2, run_count); | |
| // Resuming the other one does nothing. This suspend was already marked resolved from the other | |
| // token. | |
| should_complete = true; | |
| suspended2.resume_task(); | |
| loop.PostTask(FROM_HERE, [&loop]() { loop.QuitNow(); }); | |
| loop.Run(); | |
| EXPECT_EQ(2, run_count); // Same as before. | |
| EXPECT_TRUE(lambda_destructed); | |
| loop.Cleanup(); | |
| } | |
| // Tests a promise that suspends itself and then becomes abandoned (deleted before it's runnable). | |
| TEST(MessageLoop, AbandonPromise) { | |
| PlatformMessageLoop loop; | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| bool lambda_destructed = false; | |
| fit::suspended_task suspended; | |
| int run_count = 0; | |
| loop.PostTask(FROM_HERE, fit::make_promise( | |
| [&run_count, &suspended, | |
| destructed = std::make_shared<SetOnDestruct>(&lambda_destructed)]( | |
| fit::context& context) -> fit::result<> { | |
| run_count++; | |
| suspended = context.suspend_task(); // So we can signal later. | |
| return fit::pending(); | |
| })); | |
| // Should not have run yet. | |
| EXPECT_EQ(0, run_count); | |
| // Pulse the message loop. The task should have run once (responded with "suspend") and set the | |
| // suspended_task. | |
| loop.PostTask(FROM_HERE, [&loop]() { loop.QuitNow(); }); | |
| loop.Run(); | |
| EXPECT_EQ(1, run_count); | |
| EXPECT_TRUE(suspended); | |
| // Free the suspended task. This should free the lambda and not run it. | |
| suspended = fit::suspended_task(); | |
| EXPECT_EQ(1, run_count); | |
| EXPECT_TRUE(lambda_destructed); | |
| loop.Cleanup(); | |
| } | |
| // Runs a promise right away without posting to the message loop. | |
| TEST(MessageLoop, RunPromiseSync) { | |
| PlatformMessageLoop loop; | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| bool lambda_destructed = false; | |
| fit::suspended_task suspended; | |
| int run_count = 0; | |
| bool should_complete = false; | |
| loop.RunTask(FROM_HERE, | |
| fit::make_promise([&run_count, &suspended, &should_complete, | |
| destructed = std::make_shared<SetOnDestruct>(&lambda_destructed)]( | |
| fit::context& context) -> fit::result<> { | |
| run_count++; | |
| if (should_complete) | |
| return fit::ok(); | |
| suspended = context.suspend_task(); // So we can signal later. | |
| return fit::pending(); | |
| })); | |
| // Should have run but not completed. | |
| EXPECT_EQ(1, run_count); | |
| EXPECT_FALSE(lambda_destructed); | |
| EXPECT_TRUE(suspended); | |
| // Pulse the message loop. | |
| loop.PostTask(FROM_HERE, [&loop]() { loop.QuitNow(); }); | |
| loop.Run(); | |
| EXPECT_EQ(1, run_count); // Same as before. | |
| // Unsuspend, the task should complete. | |
| should_complete = true; | |
| suspended.resume_task(); | |
| loop.PostTask(FROM_HERE, [&loop]() { loop.QuitNow(); }); | |
| loop.Run(); | |
| EXPECT_EQ(2, run_count); | |
| EXPECT_FALSE(suspended); | |
| EXPECT_TRUE(lambda_destructed); | |
| loop.Cleanup(); | |
| } | |
| // Runs a promise without posting from inside another promise. | |
| TEST(MessageLoop, RunNestedPromiseSync) { | |
| PlatformMessageLoop loop; | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| fit::suspended_task inner_suspended; | |
| int inner_run_count = 0; | |
| bool inner_should_complete = false; | |
| fit::suspended_task outer_suspended; | |
| int outer_run_count = 0; | |
| bool outer_should_complete = false; | |
| loop.PostTask(FROM_HERE, fit::make_promise([&](fit::context& context) -> fit::result<> { | |
| outer_run_count++; | |
| if (outer_should_complete) | |
| return fit::ok(); | |
| int old_inner_run_count = inner_run_count; | |
| loop.RunTask(FROM_HERE, | |
| fit::make_promise([&](fit::context& context) -> fit::result<> { | |
| inner_run_count++; | |
| if (inner_should_complete) | |
| return fit::ok(); | |
| inner_suspended = context.suspend_task(); | |
| return fit::pending(); | |
| })); | |
| EXPECT_EQ(inner_run_count, old_inner_run_count + 1); // Should have run once. | |
| outer_suspended = context.suspend_task(); // So we can signal later. | |
| return fit::pending(); | |
| })); | |
| // Nothing should have happened yet. | |
| EXPECT_EQ(0, inner_run_count); | |
| EXPECT_EQ(0, outer_run_count); | |
| // Pulse the message loop. Both outer and inner loops should run once and suspend. | |
| loop.PostTask(FROM_HERE, [&loop]() { loop.QuitNow(); }); | |
| loop.Run(); | |
| EXPECT_EQ(1, inner_run_count); | |
| EXPECT_EQ(1, outer_run_count); | |
| // Let the inner one complete. | |
| inner_should_complete = true; | |
| inner_suspended.resume_task(); | |
| loop.PostTask(FROM_HERE, [&loop]() { loop.QuitNow(); }); | |
| loop.Run(); | |
| EXPECT_EQ(2, inner_run_count); // One more run. | |
| EXPECT_EQ(1, outer_run_count); // Same as before. | |
| // Run the outer one again but it will still return async. This will queue up another inner loop | |
| // but this time the inner loop should exit right away and not set the inner suspended. | |
| inner_suspended = fit::suspended_task(); | |
| outer_suspended.resume_task(); | |
| loop.PostTask(FROM_HERE, [&loop]() { loop.QuitNow(); }); | |
| loop.Run(); | |
| EXPECT_EQ(3, inner_run_count); // One more run. | |
| EXPECT_EQ(2, outer_run_count); // One more run. | |
| EXPECT_FALSE(inner_suspended); // Not suspended, it completed synchronously now. | |
| // Complete the outer one. | |
| outer_should_complete = true; | |
| outer_suspended.resume_task(); | |
| loop.PostTask(FROM_HERE, [&loop]() { loop.QuitNow(); }); | |
| loop.Run(); | |
| EXPECT_EQ(3, inner_run_count); // Same as before | |
| EXPECT_EQ(3, outer_run_count); // One more run. | |
| loop.Cleanup(); | |
| } | |
| TEST(MessageLoop, WatchPipeFD) { | |
| // Make a pipe to talk about. | |
| int pipefd[2] = {-1, -1}; | |
| ASSERT_EQ(0, pipe(pipefd)); | |
| ASSERT_NE(-1, pipefd[0]); | |
| ASSERT_NE(-1, pipefd[1]); | |
| int flags = fcntl(pipefd[0], F_GETFD); | |
| flags |= O_NONBLOCK; | |
| ASSERT_EQ(0, fcntl(pipefd[0], F_SETFD, flags)); | |
| flags = fcntl(pipefd[1], F_GETFD); | |
| flags |= O_NONBLOCK; | |
| ASSERT_EQ(0, fcntl(pipefd[1], F_SETFD, flags)); | |
| class ReadableWatcher : public FDWatcher { | |
| public: | |
| explicit ReadableWatcher(MessageLoop* loop) : loop_(loop) {} | |
| void OnFDReady(int fd, bool read, bool write, bool err) override { | |
| got_read = read; | |
| got_write = write; | |
| got_err = err; | |
| loop_->QuitNow(); | |
| } | |
| bool got_read = false; | |
| bool got_write = true; | |
| bool got_err = true; | |
| private: | |
| MessageLoop* loop_; | |
| }; | |
| PlatformMessageLoop loop; | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| // Scope everything to before MessageLoop::Cleanup(). | |
| { | |
| ReadableWatcher watcher(&loop); | |
| // Going to write to pipefd[1] -> read from pipefd[0]. | |
| MessageLoop::WatchHandle watch_handle = | |
| loop.WatchFD(MessageLoop::WatchMode::kRead, pipefd[0], &watcher); | |
| ASSERT_TRUE(watch_handle.watching()); | |
| // Enqueue a task that should cause pipefd[0] to become readable. | |
| loop.PostTask(FROM_HERE, [write_fd = pipefd[1]]() { write(write_fd, "Hello", 5); }); | |
| // This will quit on success because the OnFDReady callback called QuitNow, | |
| // or hang forever on failure. | |
| // TODO(brettw) add a timeout when timers are supported in the message loop. | |
| loop.Run(); | |
| EXPECT_TRUE(watcher.got_read); | |
| EXPECT_FALSE(watcher.got_write); | |
| EXPECT_FALSE(watcher.got_err); | |
| } | |
| loop.Cleanup(); | |
| } | |
| TEST(MessageLoop, RunUntilNoTasks) { | |
| PlatformMessageLoop loop; | |
| static constexpr int kCallCount = 5; | |
| struct Calls { | |
| int called[kCallCount]; | |
| }; | |
| Calls calls; | |
| for (int i = 0; i < kCallCount; i++) { | |
| calls.called[i] = -1; | |
| } | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| { | |
| loop.PostTask(FROM_HERE, [&calls]() mutable { calls.called[0] = 0; }); | |
| loop.PostTask(FROM_HERE, [&calls]() mutable { calls.called[1] = 1; }); | |
| // Nested calles should work. | |
| loop.PostTask(FROM_HERE, [&calls, &loop]() mutable { | |
| loop.PostTask(FROM_HERE, [&calls, &loop]() { | |
| loop.PostTask(FROM_HERE, [&calls]() { calls.called[4] = 4; }); | |
| calls.called[3] = 3; | |
| }); | |
| calls.called[2] = 2; | |
| }); | |
| loop.RunUntilNoTasks(); | |
| // All should've been called in the expected order. | |
| EXPECT_EQ(calls.called[0], 0); | |
| EXPECT_EQ(calls.called[1], 1); | |
| EXPECT_EQ(calls.called[2], 2); | |
| EXPECT_EQ(calls.called[3], 3); | |
| EXPECT_EQ(calls.called[4], 4); | |
| } | |
| loop.Cleanup(); | |
| } | |
| TEST(MessageLoop, RunUntilNoTasks_EmptyQueue) { | |
| PlatformMessageLoop loop; | |
| static constexpr int kCallCount = 5; | |
| struct Calls { | |
| int called[kCallCount]; | |
| }; | |
| Calls calls; | |
| for (int i = 0; i < kCallCount; i++) { | |
| calls.called[i] = -1; | |
| } | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| { loop.RunUntilNoTasks(); } | |
| loop.Cleanup(); | |
| } | |
| #if defined(__Fuchsia__) | |
| TEST(MessageLoop, ZirconSocket) { | |
| zx::socket sender, receiver; | |
| ASSERT_EQ(ZX_OK, zx::socket::create(ZX_SOCKET_STREAM, &sender, &receiver)); | |
| class ReadableWatcher : public SocketWatcher { | |
| public: | |
| explicit ReadableWatcher(MessageLoop* loop) : loop_(loop) {} | |
| void OnSocketReadable(zx_handle_t socket_handle) override { loop_->QuitNow(); } | |
| private: | |
| MessageLoop* loop_; | |
| }; | |
| PlatformMessageLoop loop; | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| // Scope everything to before MessageLoop::Cleanup(). | |
| { | |
| ReadableWatcher watcher(&loop); | |
| MessageLoop::WatchHandle watch_handle; | |
| ASSERT_EQ(ZX_OK, loop.WatchSocket(MessageLoop::WatchMode::kRead, receiver.get(), &watcher, | |
| &watch_handle)); | |
| ASSERT_TRUE(watch_handle.watching()); | |
| // Enqueue a task that should cause receiver to become readable. | |
| loop.PostTask(FROM_HERE, [&sender]() { sender.write(0, "Hello", 5, nullptr); }); | |
| // This will quit on success because the OnSocketReadable callback called | |
| // QuitNow, or hang forever on failure. | |
| // TODO(brettw) add a timeout when timers are supported in the message loop. | |
| loop.Run(); | |
| } | |
| loop.Cleanup(); | |
| } | |
| #endif | |
| } // namespace debug_ipc |