| //===-- MainLoopTest.cpp ----------------------------------------*- C++ -*-===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "lldb/Host/MainLoop.h" | |
| #include "lldb/Host/ConnectionFileDescriptor.h" | |
| #include "lldb/Host/PseudoTerminal.h" | |
| #include "lldb/Host/common/TCPSocket.h" | |
| #include "llvm/Testing/Support/Error.h" | |
| #include "gtest/gtest.h" | |
| #include <future> | |
| using namespace lldb_private; | |
| namespace { | |
| class MainLoopTest : public testing::Test { | |
| public: | |
| static void SetUpTestCase() { | |
| ASSERT_THAT_ERROR(Socket::Initialize(), llvm::Succeeded()); | |
| } | |
| static void TearDownTestCase() { Socket::Terminate(); } | |
| void SetUp() override { | |
| bool child_processes_inherit = false; | |
| Status error; | |
| std::unique_ptr<TCPSocket> listen_socket_up( | |
| new TCPSocket(true, child_processes_inherit)); | |
| ASSERT_TRUE(error.Success()); | |
| error = listen_socket_up->Listen("localhost:0", 5); | |
| ASSERT_TRUE(error.Success()); | |
| Socket *accept_socket; | |
| std::future<Status> accept_error = std::async(std::launch::async, [&] { | |
| return listen_socket_up->Accept(accept_socket); | |
| }); | |
| std::unique_ptr<TCPSocket> connect_socket_up( | |
| new TCPSocket(true, child_processes_inherit)); | |
| error = connect_socket_up->Connect( | |
| llvm::formatv("localhost:{0}", listen_socket_up->GetLocalPortNumber()) | |
| .str()); | |
| ASSERT_TRUE(error.Success()); | |
| ASSERT_TRUE(accept_error.get().Success()); | |
| callback_count = 0; | |
| socketpair[0] = std::move(connect_socket_up); | |
| socketpair[1].reset(accept_socket); | |
| } | |
| void TearDown() override { | |
| socketpair[0].reset(); | |
| socketpair[1].reset(); | |
| } | |
| protected: | |
| MainLoop::Callback make_callback() { | |
| return [&](MainLoopBase &loop) { | |
| ++callback_count; | |
| loop.RequestTermination(); | |
| }; | |
| } | |
| std::shared_ptr<Socket> socketpair[2]; | |
| unsigned callback_count; | |
| }; | |
| } // namespace | |
| TEST_F(MainLoopTest, ReadObject) { | |
| char X = 'X'; | |
| size_t len = sizeof(X); | |
| ASSERT_TRUE(socketpair[0]->Write(&X, len).Success()); | |
| MainLoop loop; | |
| Status error; | |
| auto handle = loop.RegisterReadObject(socketpair[1], make_callback(), error); | |
| ASSERT_TRUE(error.Success()); | |
| ASSERT_TRUE(handle); | |
| ASSERT_TRUE(loop.Run().Success()); | |
| ASSERT_EQ(1u, callback_count); | |
| } | |
| TEST_F(MainLoopTest, TerminatesImmediately) { | |
| char X = 'X'; | |
| size_t len = sizeof(X); | |
| ASSERT_TRUE(socketpair[0]->Write(&X, len).Success()); | |
| ASSERT_TRUE(socketpair[1]->Write(&X, len).Success()); | |
| MainLoop loop; | |
| Status error; | |
| auto handle0 = loop.RegisterReadObject(socketpair[0], make_callback(), error); | |
| ASSERT_TRUE(error.Success()); | |
| auto handle1 = loop.RegisterReadObject(socketpair[1], make_callback(), error); | |
| ASSERT_TRUE(error.Success()); | |
| ASSERT_TRUE(loop.Run().Success()); | |
| ASSERT_EQ(1u, callback_count); | |
| } | |
| #ifdef LLVM_ON_UNIX | |
| // NetBSD currently does not report slave pty EOF via kevent | |
| // causing this test to hang forever. | |
| #ifndef __NetBSD__ | |
| TEST_F(MainLoopTest, DetectsEOF) { | |
| PseudoTerminal term; | |
| ASSERT_TRUE(term.OpenFirstAvailableMaster(O_RDWR, nullptr, 0)); | |
| ASSERT_TRUE(term.OpenSlave(O_RDWR | O_NOCTTY, nullptr, 0)); | |
| auto conn = std::make_unique<ConnectionFileDescriptor>( | |
| term.ReleaseMasterFileDescriptor(), true); | |
| Status error; | |
| MainLoop loop; | |
| auto handle = | |
| loop.RegisterReadObject(conn->GetReadObject(), make_callback(), error); | |
| ASSERT_TRUE(error.Success()); | |
| term.CloseSlaveFileDescriptor(); | |
| ASSERT_TRUE(loop.Run().Success()); | |
| ASSERT_EQ(1u, callback_count); | |
| } | |
| #endif | |
| TEST_F(MainLoopTest, Signal) { | |
| MainLoop loop; | |
| Status error; | |
| auto handle = loop.RegisterSignal(SIGUSR1, make_callback(), error); | |
| ASSERT_TRUE(error.Success()); | |
| kill(getpid(), SIGUSR1); | |
| ASSERT_TRUE(loop.Run().Success()); | |
| ASSERT_EQ(1u, callback_count); | |
| } | |
| // Test that a signal which is not monitored by the MainLoop does not | |
| // cause a premature exit. | |
| TEST_F(MainLoopTest, UnmonitoredSignal) { | |
| MainLoop loop; | |
| Status error; | |
| struct sigaction sa; | |
| sa.sa_sigaction = [](int, siginfo_t *, void *) { }; | |
| sa.sa_flags = SA_SIGINFO; // important: no SA_RESTART | |
| sigemptyset(&sa.sa_mask); | |
| ASSERT_EQ(0, sigaction(SIGUSR2, &sa, nullptr)); | |
| auto handle = loop.RegisterSignal(SIGUSR1, make_callback(), error); | |
| ASSERT_TRUE(error.Success()); | |
| std::thread killer([]() { | |
| sleep(1); | |
| kill(getpid(), SIGUSR2); | |
| sleep(1); | |
| kill(getpid(), SIGUSR1); | |
| }); | |
| ASSERT_TRUE(loop.Run().Success()); | |
| killer.join(); | |
| ASSERT_EQ(1u, callback_count); | |
| } | |
| #endif |