diff options
Diffstat (limited to 'lldb/unittests')
| -rw-r--r-- | lldb/unittests/CMakeLists.txt | 23 | ||||
| -rw-r--r-- | lldb/unittests/Host/CMakeLists.txt | 5 | ||||
| -rw-r--r-- | lldb/unittests/Host/SocketAddressTest.cpp | 77 | ||||
| -rw-r--r-- | lldb/unittests/Host/SocketTest.cpp | 132 | ||||
| -rw-r--r-- | lldb/unittests/Host/SocketTestMock.cpp | 64 | ||||
| -rw-r--r-- | lldb/unittests/Plugins/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | lldb/unittests/Plugins/Process/CMakeLists.txt | 3 | ||||
| -rw-r--r-- | lldb/unittests/Plugins/Process/Linux/CMakeLists.txt | 4 | ||||
| -rw-r--r-- | lldb/unittests/Plugins/Process/Linux/Makefile | 19 | ||||
| -rw-r--r-- | lldb/unittests/Plugins/Process/Linux/ThreadStateCoordinatorTest.cpp | 821 | ||||
| -rw-r--r-- | lldb/unittests/Plugins/Process/Linux/ThreadStateCoordinatorTestMock.cpp | 32 | ||||
| -rw-r--r-- | lldb/unittests/Utility/CMakeLists.txt | 4 | ||||
| -rw-r--r-- | lldb/unittests/Utility/StringExtractorTest.cpp | 406 | ||||
| -rw-r--r-- | lldb/unittests/Utility/UriParserTest.cpp | 135 | ||||
| -rw-r--r-- | lldb/unittests/gtest_common.h | 32 |
15 files changed, 1758 insertions, 0 deletions
diff --git a/lldb/unittests/CMakeLists.txt b/lldb/unittests/CMakeLists.txt new file mode 100644 index 00000000000..e9b37700a85 --- /dev/null +++ b/lldb/unittests/CMakeLists.txt @@ -0,0 +1,23 @@ +add_custom_target(LLDBUnitTests)
+set_target_properties(LLDBUnitTests PROPERTIES FOLDER "LLDB tests")
+
+include_directories(${LLDB_SOURCE_ROOT})
+
+set(LLDB_GTEST_COMMON_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/gtest_common.h)
+if (MSVC)
+ list(APPEND LLVM_COMPILE_FLAGS /FI ${LLDB_GTEST_COMMON_INCLUDE})
+else ()
+ list(APPEND LLVM_COMPILE_FLAGS -include ${LLDB_GTEST_COMMON_INCLUDE})
+endif ()
+
+# add_lldb_unittest(test_dirname file1.cpp file2.cpp)
+#
+# Will compile the list of files together and link against the liblldb
+function(add_lldb_unittest test_name)
+ add_unittest(LLDBUnitTests ${test_name} ${ARGN})
+ target_link_libraries(${test_name} liblldb)
+endfunction()
+
+add_subdirectory(Host)
+add_subdirectory(Plugins)
+add_subdirectory(Utility)
diff --git a/lldb/unittests/Host/CMakeLists.txt b/lldb/unittests/Host/CMakeLists.txt new file mode 100644 index 00000000000..227e48c1fd9 --- /dev/null +++ b/lldb/unittests/Host/CMakeLists.txt @@ -0,0 +1,5 @@ +add_lldb_unittest(HostTests
+ SocketAddressTest.cpp
+ SocketTest.cpp
+ SocketTestMock.cpp
+ )
diff --git a/lldb/unittests/Host/SocketAddressTest.cpp b/lldb/unittests/Host/SocketAddressTest.cpp new file mode 100644 index 00000000000..bd6bda13f44 --- /dev/null +++ b/lldb/unittests/Host/SocketAddressTest.cpp @@ -0,0 +1,77 @@ +//===-- SocketAddressTest.cpp -----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + + +#include "gtest/gtest.h" + +#include "lldb/Host/SocketAddress.h" + +namespace +{ + class SocketAddressTest: public ::testing::Test + { + }; +} + +using namespace lldb_private; + +TEST_F (SocketAddressTest, Set) +{ + SocketAddress sa; + ASSERT_TRUE (sa.SetToLocalhost (AF_INET, 1138)); + ASSERT_STREQ ("127.0.0.1", sa.GetIPAddress ().c_str ()); + ASSERT_EQ (1138, sa.GetPort ()); + + ASSERT_TRUE (sa.SetToAnyAddress (AF_INET, 0)); + ASSERT_STREQ ("0.0.0.0", sa.GetIPAddress ().c_str ()); + ASSERT_EQ (0, sa.GetPort ()); + + ASSERT_TRUE (sa.SetToLocalhost (AF_INET6, 1139)); +#ifdef _WIN32 + ASSERT_STREQ ("0:0:0:0:0:0:0:1", sa.GetIPAddress ().c_str ()); +#else + ASSERT_STREQ ("::1", sa.GetIPAddress ().c_str ()); +#endif + ASSERT_EQ (1139, sa.GetPort ()); +} + +#ifdef _WIN32 + +// we need to test our inet_ntop implementation for Windows XP +const char* inet_ntop (int af, const void * src, + char * dst, socklen_t size); + +TEST_F (SocketAddressTest, inet_ntop) +{ + const uint8_t address4[4] = {255, 0, 1, 100}; + const uint8_t address6[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 255, 0}; + + char buffer[INET6_ADDRSTRLEN]; + memset (buffer, 'x', sizeof (buffer)); + EXPECT_STREQ ("1:203:405:607:809:a0b:c0d:ff00", inet_ntop (AF_INET6, address6, buffer, sizeof (buffer))); + memset (buffer, 'x', sizeof (buffer)); + EXPECT_STREQ ("1:203:405:607:809:a0b:c0d:ff00", inet_ntop (AF_INET6, address6, buffer, 31)); + memset (buffer, 'x', sizeof (buffer)); + EXPECT_STREQ (nullptr, inet_ntop (AF_INET6, address6, buffer, 0)); + memset (buffer, 'x', sizeof (buffer)); + EXPECT_STREQ (nullptr, inet_ntop (AF_INET6, address6, buffer, 30)); + + memset (buffer, 'x', sizeof (buffer)); + EXPECT_STREQ ("255.0.1.100", inet_ntop (AF_INET, address4, buffer, sizeof (buffer))); + memset (buffer, 'x', sizeof (buffer)); + EXPECT_STREQ ("255.0.1.100", inet_ntop (AF_INET, address4, buffer, 12)); + memset (buffer, 'x', sizeof (buffer)); + EXPECT_STREQ (nullptr, inet_ntop (AF_INET, address4, buffer, 0)); + memset (buffer, 'x', sizeof (buffer)); + EXPECT_STREQ (nullptr, inet_ntop (AF_INET, address4, buffer, 11)); +} + +#endif + + diff --git a/lldb/unittests/Host/SocketTest.cpp b/lldb/unittests/Host/SocketTest.cpp new file mode 100644 index 00000000000..deed222c8c1 --- /dev/null +++ b/lldb/unittests/Host/SocketTest.cpp @@ -0,0 +1,132 @@ +//===-- SocketTest.cpp ------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include <thread> + +#include "gtest/gtest.h" + +#include "lldb/Host/Socket.h" + +class SocketTest: public ::testing::Test +{ +}; + +using namespace lldb_private; + +void AcceptThread (Socket* listen_socket, + const char* listen_remote_address, + bool child_processes_inherit, + Socket** accept_socket, + Error* error) +{ + *error = listen_socket->BlockingAccept (listen_remote_address, child_processes_inherit, *accept_socket); +} + +void CreateConnectedSockets (std::unique_ptr<Socket>* a_up, std::unique_ptr<Socket>* b_up) +{ + Predicate<uint16_t> port_predicate; + // Used when binding to port zero to wait for the thread + // that creates the socket, binds and listens to resolve + // the port number. + + port_predicate.SetValue (0, eBroadcastNever); + + bool child_processes_inherit = false; + Socket *socket = nullptr; + const char* listen_remote_address = "localhost:0"; + Error error = Socket::TcpListen (listen_remote_address, child_processes_inherit, socket, &port_predicate); + std::unique_ptr<Socket> listen_socket_up (socket); + socket = nullptr; + EXPECT_FALSE (error.Fail ()); + EXPECT_NE (nullptr, listen_socket_up.get ()); + EXPECT_TRUE (listen_socket_up->IsValid ()); + + Error accept_error; + Socket* accept_socket; + std::thread accept_thread (AcceptThread, + listen_socket_up.get (), + listen_remote_address, + child_processes_inherit, + &accept_socket, + &accept_error); + + char connect_remote_address[64]; + snprintf (connect_remote_address, sizeof (connect_remote_address), "localhost:%u", port_predicate.GetValue ()); + error = Socket::TcpConnect (connect_remote_address, child_processes_inherit, socket); + a_up->reset (socket); + socket = nullptr; + EXPECT_TRUE (error.Success ()); + EXPECT_NE (nullptr, a_up->get ()); + EXPECT_TRUE ((*a_up)->IsValid ()); + + accept_thread.join (); + b_up->reset (accept_socket); + EXPECT_TRUE (accept_error.Success ()); + EXPECT_NE (nullptr, b_up->get ()); + EXPECT_TRUE ((*b_up)->IsValid ()); + + listen_socket_up.reset (); +} + +TEST_F (SocketTest, DecodeHostAndPort) +{ + std::string host_str; + std::string port_str; + int32_t port; + Error error; + EXPECT_TRUE (Socket::DecodeHostAndPort ("localhost:1138", host_str, port_str, port, &error)); + EXPECT_STREQ ("localhost", host_str.c_str ()); + EXPECT_STREQ ("1138", port_str.c_str ()); + EXPECT_EQ (1138, port); + EXPECT_TRUE (error.Success ()); + + EXPECT_FALSE (Socket::DecodeHostAndPort ("google.com:65536", host_str, port_str, port, &error)); + EXPECT_TRUE (error.Fail ()); + EXPECT_STREQ ("invalid host:port specification: 'google.com:65536'", error.AsCString ()); + + EXPECT_FALSE (Socket::DecodeHostAndPort ("google.com:-1138", host_str, port_str, port, &error)); + EXPECT_TRUE (error.Fail ()); + EXPECT_STREQ ("invalid host:port specification: 'google.com:-1138'", error.AsCString ()); + + EXPECT_TRUE (Socket::DecodeHostAndPort ("12345", host_str, port_str, port, &error)); + EXPECT_STREQ ("", host_str.c_str ()); + EXPECT_STREQ ("12345", port_str.c_str ()); + EXPECT_EQ (12345, port); + EXPECT_TRUE (error.Success ()); + + EXPECT_TRUE (Socket::DecodeHostAndPort ("*:0", host_str, port_str, port, &error)); + EXPECT_STREQ ("*", host_str.c_str ()); + EXPECT_STREQ ("0", port_str.c_str ()); + EXPECT_EQ (0, port); + EXPECT_TRUE (error.Success ()); + +} + +TEST_F (SocketTest, Listen0ConnectAccept) +{ + std::unique_ptr<Socket> socket_a_up; + std::unique_ptr<Socket> socket_b_up; + CreateConnectedSockets (&socket_a_up, &socket_b_up); +} + +TEST_F (SocketTest, GetAddress) +{ + std::unique_ptr<Socket> socket_a_up; + std::unique_ptr<Socket> socket_b_up; + CreateConnectedSockets (&socket_a_up, &socket_b_up); + + EXPECT_EQ (socket_a_up->GetLocalPortNumber (), socket_b_up->GetRemotePortNumber ()); + EXPECT_EQ (socket_b_up->GetLocalPortNumber (), socket_a_up->GetRemotePortNumber ()); + EXPECT_NE (socket_a_up->GetLocalPortNumber (), socket_b_up->GetLocalPortNumber ()); + EXPECT_STREQ ("127.0.0.1", socket_a_up->GetRemoteIPAddress ().c_str ()); + EXPECT_STREQ ("127.0.0.1", socket_b_up->GetRemoteIPAddress ().c_str ()); +} + + + diff --git a/lldb/unittests/Host/SocketTestMock.cpp b/lldb/unittests/Host/SocketTestMock.cpp new file mode 100644 index 00000000000..d457c5161d3 --- /dev/null +++ b/lldb/unittests/Host/SocketTestMock.cpp @@ -0,0 +1,64 @@ +//===-- SocketTestMock.cpp --------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// This file provides a few necessary functions to link SocketTest.cpp +// Bringing in the real implementations results in a cascade of dependencies +// that pull in all of lldb. + +#include "lldb/Core/Log.h" + +#ifdef _WIN32 +#include <windows.h> +#endif + +using namespace lldb_private; + +void +lldb_private::Log::Error (char const*, ...) +{ +} + +void +lldb_private::Log::Printf (char const*, ...) +{ +} + +Log* +lldb_private::GetLogIfAnyCategoriesSet (unsigned int) +{ + return nullptr; +} + +#include "lldb/Host/FileSystem.h" + +#ifdef _WIN32 + +Error +FileSystem::Unlink(const char *path) +{ + Error error; + BOOL result = ::DeleteFile(path); + if (!result) + error.SetError(::GetLastError(), lldb::eErrorTypeWin32); + return error; +} + +#else + +Error +FileSystem::Unlink (const char *path) +{ + Error error; + if (::unlink (path) == -1) + error.SetErrorToErrno (); + return error; +} + +#endif + diff --git a/lldb/unittests/Plugins/CMakeLists.txt b/lldb/unittests/Plugins/CMakeLists.txt new file mode 100644 index 00000000000..422f203f227 --- /dev/null +++ b/lldb/unittests/Plugins/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(Process)
diff --git a/lldb/unittests/Plugins/Process/CMakeLists.txt b/lldb/unittests/Plugins/Process/CMakeLists.txt new file mode 100644 index 00000000000..bf3426c02d0 --- /dev/null +++ b/lldb/unittests/Plugins/Process/CMakeLists.txt @@ -0,0 +1,3 @@ +if (CMAKE_SYSTEM_NAME MATCHES "Linux")
+ add_subdirectory(Linux)
+endif()
diff --git a/lldb/unittests/Plugins/Process/Linux/CMakeLists.txt b/lldb/unittests/Plugins/Process/Linux/CMakeLists.txt new file mode 100644 index 00000000000..3d71f9a463a --- /dev/null +++ b/lldb/unittests/Plugins/Process/Linux/CMakeLists.txt @@ -0,0 +1,4 @@ +add_lldb_unittest(ProcessLinuxTests
+ ThreadStateCoordinatorTest.cpp
+ ThreadStatecoordinatorMock.cpp
+ )
diff --git a/lldb/unittests/Plugins/Process/Linux/Makefile b/lldb/unittests/Plugins/Process/Linux/Makefile new file mode 100644 index 00000000000..6817352888c --- /dev/null +++ b/lldb/unittests/Plugins/Process/Linux/Makefile @@ -0,0 +1,19 @@ +THIS_FILE_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))/ + +LEVEL := $(realpath $(THIS_FILE_DIR)../../../../make) + +CFLAGS_EXTRAS := -D__STDC_LIMIT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_CONSTANT_MACROS +ENABLE_THREADS := YES +CXX_SOURCES := $(wildcard *.cpp) \ + $(realpath $(LEVEL)/../../source/Plugins/Process/Linux/ThreadStateCoordinator.cpp) \ + $(realpath $(LEVEL)/../../source/Core/Error.cpp) +MAKE_DSYM := NO + +OS := $(shell uname -s) + +# $(info OS $(OS)) +ifeq ($(OS),Linux) + LD_EXTRAS := -lncurses -ldl +endif + +include $(LEVEL)/Makefile.rules diff --git a/lldb/unittests/Plugins/Process/Linux/ThreadStateCoordinatorTest.cpp b/lldb/unittests/Plugins/Process/Linux/ThreadStateCoordinatorTest.cpp new file mode 100644 index 00000000000..9e7cc8eb4ca --- /dev/null +++ b/lldb/unittests/Plugins/Process/Linux/ThreadStateCoordinatorTest.cpp @@ -0,0 +1,821 @@ +#include <limits.h> +#include "gtest/gtest.h" + +#include "lldb/Core/Error.h" +#include "Plugins/Process/Linux/ThreadStateCoordinator.h" + +using namespace lldb_private; + +namespace +{ + const ThreadStateCoordinator::ThreadIDSet EMPTY_THREAD_ID_SET; + + void + NOPLogger (const char *format, va_list args) + { + // Do nothing. + } + + void + StdoutLogger (const char *format, va_list args) + { + // Print to stdout. + vprintf (format, args); + printf ("\n"); + } + + // These are single-line macros so that IDE integration of gtest results puts + // the error markers on the correct failure point within the gtest. + +#define ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS() ASSERT_EQ (ThreadStateCoordinator::eventLoopResultContinue, m_coordinator.ProcessNextEvent ()); \ +if (HasError ()) { printf ("unexpected error in processing of event, error: %s\n", m_error_string.c_str ()); } \ +ASSERT_EQ (false, HasError ()); + +#define ASSERT_PROCESS_NEXT_EVENT_FAILS() ASSERT_EQ (ThreadStateCoordinator::eventLoopResultContinue, m_coordinator.ProcessNextEvent ()); \ +ASSERT_EQ (true, HasError ()); + + class ThreadStateCoordinatorTest: public ::testing::Test + { + protected: + // Constants. + const lldb::tid_t TRIGGERING_TID = 4105; + const lldb::tid_t PENDING_STOP_TID = 3; + const lldb::tid_t PENDING_STOP_TID_02 = 29016; + const lldb::tid_t NEW_THREAD_TID = 1234; + + // Member variables. + bool m_error_called = false; + std::string m_error_string; + + ThreadStateCoordinator m_coordinator; + + bool m_deferred_notification_called; + lldb::tid_t m_deferred_notification_tid; + + ThreadStateCoordinator::ThreadIDSet m_requested_stop_tids; + + // Constructors. + ThreadStateCoordinatorTest () : + m_error_called (false), + m_error_string (), + m_coordinator (StdoutLogger), + m_deferred_notification_called (false), + m_deferred_notification_tid (0), + m_requested_stop_tids () + { + } + + // Member functions. + + // Error handling. + ThreadStateCoordinator::ErrorFunction + GetErrorFunction () + { + return [this] (const std::string &error_string) + { + m_error_called = true; + m_error_string = error_string; + printf ("received error: %s (test might be expecting)\n", error_string.c_str ()); + }; + } + + bool + HasError () const + { + return m_error_called; + } + + // Deferred notification reception. + ThreadStateCoordinator::ThreadIDFunction + GetDeferredStopNotificationFunction () + { + return [this] (lldb::tid_t triggered_tid) + { + m_deferred_notification_called = true; + m_deferred_notification_tid = triggered_tid; + }; + } + + bool + DidFireDeferredNotification () const + { + return m_deferred_notification_called; + } + + lldb::tid_t + GetDeferredNotificationTID () const + { + return m_deferred_notification_tid; + } + + // Stop request call reception. + ThreadStateCoordinator::StopThreadFunction + GetStopRequestFunction () + { + return [this] (lldb::tid_t stop_tid) + { + m_requested_stop_tids.insert (stop_tid); + return Error(); + }; + } + + ThreadStateCoordinator::ThreadIDSet::size_type + GetRequestedStopCount () const + { + return m_requested_stop_tids.size(); + } + + ThreadStateCoordinator::ResumeThreadFunction + GetResumeThreadFunction (lldb::tid_t& resumed_tid, int& resume_call_count) + { + return [this, &resumed_tid, &resume_call_count] (lldb::tid_t tid, bool) + { + resumed_tid = tid; + ++resume_call_count; + return Error(); + }; + } + + bool + DidRequestStopForTid (lldb::tid_t tid) + { + return m_requested_stop_tids.find (tid) != m_requested_stop_tids.end (); + } + + // Test state initialization helpers. + void + SetupKnownRunningThread (lldb::tid_t tid) + { + NotifyThreadCreate (tid, false); + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + } + + void + SetupKnownStoppedThread (lldb::tid_t tid) + { + NotifyThreadCreate (tid, true); + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + } + + // Convenience wrappers for ThreadStateCoordinator, using defaults for expected arguments + // that plug into the test case handlers. + void + CallAfterThreadsStop (lldb::tid_t deferred_tid, + const ThreadStateCoordinator::ThreadIDSet &pending_stop_wait_tids) + { + m_coordinator.CallAfterThreadsStop (deferred_tid, + pending_stop_wait_tids, + GetStopRequestFunction (), + GetDeferredStopNotificationFunction (), + GetErrorFunction ()); + } + + void + CallAfterRunningThreadsStop (lldb::tid_t deferred_tid) + { + m_coordinator.CallAfterRunningThreadsStop (deferred_tid, + GetStopRequestFunction (), + GetDeferredStopNotificationFunction (), + GetErrorFunction ()); + } + + void + NotifyThreadCreate (lldb::tid_t stopped_tid, bool thread_is_stopped) + { + m_coordinator.NotifyThreadCreate (stopped_tid, thread_is_stopped, GetErrorFunction ()); + } + + void + NotifyThreadStop (lldb::tid_t stopped_tid) + { + m_coordinator.NotifyThreadStop (stopped_tid, false, GetErrorFunction ()); + } + + void + NotifyThreadDeath (lldb::tid_t tid) + { + m_coordinator.NotifyThreadDeath (tid, GetErrorFunction ()); + } + }; +} + +TEST_F (ThreadStateCoordinatorTest, StopCoordinatorWorksNoPriorEvents) +{ + m_coordinator.StopCoordinator (); + ASSERT_EQ (ThreadStateCoordinator::eventLoopResultStop, m_coordinator.ProcessNextEvent ()); + ASSERT_EQ (false, HasError ()); +} + +TEST_F (ThreadStateCoordinatorTest, NotifyThreadCreateSignalsErrorOnAlreadyKnownThread) +{ + // Let the coordinator know about our thread. + SetupKnownStoppedThread (TRIGGERING_TID); + + // Notify the thread was created - again. + NotifyThreadCreate (TRIGGERING_TID, true); + + // This should error out. + ASSERT_PROCESS_NEXT_EVENT_FAILS (); +} + + +TEST_F (ThreadStateCoordinatorTest, NotifyThreadDeathSignalsErrorOnUnknownThread) +{ + const lldb::tid_t UNKNOWN_TID = 678; + + // Notify an unknown thread has died. + NotifyThreadDeath (UNKNOWN_TID); + + // This should error out. + ASSERT_PROCESS_NEXT_EVENT_FAILS (); +} + +TEST_F (ThreadStateCoordinatorTest, NotifyThreadStopSignalsErrorOnUnknownThread) +{ + const lldb::tid_t UNKNOWN_TID = 678; + + // Notify an unknown thread has stopped. + NotifyThreadStop (UNKNOWN_TID); + ASSERT_PROCESS_NEXT_EVENT_FAILS (); +} + +TEST_F (ThreadStateCoordinatorTest, CallAfterTheadsStopSignalsErrorOnUnknownDeferredThread) +{ + const lldb::tid_t UNKNOWN_TRIGGER_TID = 678; + + // Defer notify for an unknown thread. + CallAfterThreadsStop (UNKNOWN_TRIGGER_TID, + EMPTY_THREAD_ID_SET); + + // Shouldn't have fired yet. + ASSERT_EQ (false, DidFireDeferredNotification ()); + + // Event should fail because trigger tid is unknown. + ASSERT_PROCESS_NEXT_EVENT_FAILS (); + + // Shouldn't have fired due to error. + ASSERT_EQ (false, DidFireDeferredNotification ()); +} + +TEST_F (ThreadStateCoordinatorTest, CallAfterTheadsStopSignalsErrorOnUnknownPendingStopThread) +{ + // Let the coordinator know about our thread. + SetupKnownStoppedThread (TRIGGERING_TID); + + // Defer notify for an unknown thread. + const lldb::tid_t UNKNOWN_PENDING_STOP_TID = 7890; + ThreadStateCoordinator::ThreadIDSet pending_stop_tids { UNKNOWN_PENDING_STOP_TID }; + + CallAfterThreadsStop (TRIGGERING_TID, + pending_stop_tids); + + // Shouldn't have fired yet. + ASSERT_EQ (false, DidFireDeferredNotification ()); + + // Event should fail because trigger tid is unknown. + ASSERT_PROCESS_NEXT_EVENT_FAILS (); + + // Shouldn't have triggered deferred notification due to error. + ASSERT_EQ (false, DidFireDeferredNotification ()); + + // Shouldn't have triggered stop request due to unknown tid. + ASSERT_EQ (0, GetRequestedStopCount ()); +} + +TEST_F (ThreadStateCoordinatorTest, CallAfterThreadsStopFiresWhenNoPendingStops) +{ + // Let the coordinator know about our thread. + SetupKnownStoppedThread (TRIGGERING_TID); + + // Notify we have a trigger that needs to be fired when all threads in the wait tid set have stopped. + CallAfterThreadsStop (TRIGGERING_TID, + EMPTY_THREAD_ID_SET); + + // Notification trigger shouldn't go off yet. + ASSERT_EQ (false, DidFireDeferredNotification ()); + + // Process next event. This will pick up the call after threads stop event. + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + + // Now the trigger should have fired, since there were no threads that needed to first stop. + ASSERT_EQ (true, DidFireDeferredNotification ()); + ASSERT_EQ (TRIGGERING_TID, GetDeferredNotificationTID ()); +} + +TEST_F (ThreadStateCoordinatorTest, CallAfterThreadsStopFiresWhenOnePendingStop) +{ + // Let the coordinator know about our thread. + SetupKnownStoppedThread (TRIGGERING_TID); + + // Let the coordinator know about a currently-running thread we'll wait on. + SetupKnownRunningThread (PENDING_STOP_TID); + + ThreadStateCoordinator::ThreadIDSet pending_stop_tids { PENDING_STOP_TID }; + + // Notify we have a trigger that needs to be fired when all threads in the wait tid set have stopped. + CallAfterThreadsStop (TRIGGERING_TID, + pending_stop_tids); + + // Neither trigger should have gone off yet. + ASSERT_EQ (false, DidFireDeferredNotification ()); + ASSERT_EQ (false, DidRequestStopForTid (PENDING_STOP_TID)); + + // Process next event. + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + + // Now the request thread stop should have been called for the pending stop. + ASSERT_EQ (true, DidRequestStopForTid (PENDING_STOP_TID)); + + // But we still shouldn't have the deferred signal call go off yet. Need to wait for the stop to be reported. + ASSERT_EQ (false, DidFireDeferredNotification ()); + + // Now report the that the pending stop occurred. + NotifyThreadStop (PENDING_STOP_TID); + + // Shouldn't take effect until after next processing step. + ASSERT_EQ (false, DidFireDeferredNotification ()); + + // Process next event. + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + + // Deferred signal notification should have fired now. + ASSERT_EQ (true, DidFireDeferredNotification ()); + ASSERT_EQ (TRIGGERING_TID, GetDeferredNotificationTID ()); +} + +TEST_F (ThreadStateCoordinatorTest, CallAfterThreadsStopFiresWhenTwoPendingStops) +{ + // Setup threads. + SetupKnownStoppedThread (TRIGGERING_TID); + SetupKnownRunningThread (PENDING_STOP_TID); + SetupKnownRunningThread (PENDING_STOP_TID_02); + + ThreadStateCoordinator::ThreadIDSet pending_stop_tids { PENDING_STOP_TID, PENDING_STOP_TID_02 }; + + // Notify we have a trigger that needs to be fired when all threads in the wait tid set have stopped. + CallAfterThreadsStop (TRIGGERING_TID, + pending_stop_tids); + + // Neither trigger should have gone off yet. + ASSERT_EQ (false, DidFireDeferredNotification ()); + ASSERT_EQ (0, GetRequestedStopCount ()); + + // Process next event. + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + + // Now the request thread stops should have been called for the pending stop tids. + ASSERT_EQ (2, GetRequestedStopCount ()); + ASSERT_EQ (true, DidRequestStopForTid (PENDING_STOP_TID)); + ASSERT_EQ (true, DidRequestStopForTid (PENDING_STOP_TID_02)); + + // But we still shouldn't have the deferred signal call go off yet. Need to wait for the stop to be reported. + ASSERT_EQ (false, DidFireDeferredNotification ()); + + // Report the that the first pending stop occurred. + NotifyThreadStop (PENDING_STOP_TID); + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + + // Shouldn't take effect until after both pending threads are notified. + ASSERT_EQ (false, DidFireDeferredNotification ()); + + // Report the that the first pending stop occurred. + NotifyThreadStop (PENDING_STOP_TID_02); + ASSERT_EQ (false, DidFireDeferredNotification ()); + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + + // Deferred signal notification should have fired now. + ASSERT_EQ (true, DidFireDeferredNotification ()); + ASSERT_EQ (TRIGGERING_TID, GetDeferredNotificationTID ()); +} + +TEST_F (ThreadStateCoordinatorTest, CallAfterThreadsStopFiresWhenPendingAlreadyStopped) +{ + // Setup threads. + SetupKnownStoppedThread (TRIGGERING_TID); + SetupKnownRunningThread (PENDING_STOP_TID); + + ThreadStateCoordinator::ThreadIDSet pending_stop_tids { PENDING_STOP_TID }; + + // Tell m_coordinator the pending stop tid is already stopped. + NotifyThreadStop (PENDING_STOP_TID); + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + + // Notify we have a trigger that needs to be fired when all threads in the wait tid set have stopped. + CallAfterThreadsStop (TRIGGERING_TID, + pending_stop_tids); + + // Neither trigger should have gone off yet. + ASSERT_EQ (false, DidFireDeferredNotification ()); + ASSERT_EQ (false, DidRequestStopForTid (PENDING_STOP_TID)); + + // Process next event. + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + + // The pending stop should *not* fire because the m_coordinator knows it has already stopped. + ASSERT_EQ (false, DidRequestStopForTid (PENDING_STOP_TID)); + + // The deferred signal notification should have fired since all requirements were met. + ASSERT_EQ (true, DidFireDeferredNotification ()); + ASSERT_EQ (TRIGGERING_TID, GetDeferredNotificationTID ()); +} + +TEST_F (ThreadStateCoordinatorTest, CallAfterThreadsStopFiresWhenTwoPendingOneAlreadyStopped) +{ + SetupKnownStoppedThread (TRIGGERING_TID); + SetupKnownRunningThread (PENDING_STOP_TID); + SetupKnownRunningThread (PENDING_STOP_TID_02); + + ThreadStateCoordinator::ThreadIDSet pending_stop_tids { PENDING_STOP_TID, PENDING_STOP_TID_02 }; + + // Tell coordinator the pending stop tid is already stopped. + NotifyThreadStop (PENDING_STOP_TID); + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + + // Notify we have a trigger that needs to be fired when all threads in the wait tid set have stopped. + CallAfterThreadsStop (TRIGGERING_TID, + pending_stop_tids); + + // Neither trigger should have gone off yet. + ASSERT_EQ (false, DidFireDeferredNotification ()); + ASSERT_EQ (0, GetRequestedStopCount ()); + + // Process next event. + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + + // The pending stop should only fire for one of the threads, the one that wasn't already stopped. + ASSERT_EQ (true, DidRequestStopForTid (PENDING_STOP_TID_02)); + ASSERT_EQ (false, DidRequestStopForTid (PENDING_STOP_TID)); + + // The deferred signal notification should not yet have fired since all pending thread stops have not yet occurred. + ASSERT_EQ (false, DidFireDeferredNotification ()); + + // Notify final thread has stopped. + NotifyThreadStop (PENDING_STOP_TID_02); + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + + // The deferred signal notification should have fired since all requirements were met. + ASSERT_EQ (true, DidFireDeferredNotification ()); + ASSERT_EQ (TRIGGERING_TID, GetDeferredNotificationTID ()); +} + +TEST_F (ThreadStateCoordinatorTest, CallAfterThreadsStopFiresWhenOnePendingThreadDies) +{ + SetupKnownStoppedThread (TRIGGERING_TID); + SetupKnownRunningThread (PENDING_STOP_TID); + + ThreadStateCoordinator::ThreadIDSet pending_stop_tids { PENDING_STOP_TID }; + + // Notify we have a trigger that needs to be fired when all threads in the wait tid set have stopped. + CallAfterThreadsStop (TRIGGERING_TID, + pending_stop_tids); + + // Neither trigger should have gone off yet. + ASSERT_EQ (false, DidFireDeferredNotification ()); + ASSERT_EQ (0, GetRequestedStopCount ()); + + // Process next event. + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + + // Now the request thread stop should have been called for the pending stop. + ASSERT_EQ (true, DidRequestStopForTid (PENDING_STOP_TID)); + + // But we still shouldn't have the deferred signal call go off yet. Need to wait for the death to be reported. + ASSERT_EQ (false, DidFireDeferredNotification ()); + + // Now report the that the thread with pending stop dies. + NotifyThreadDeath (PENDING_STOP_TID); + + // Shouldn't take effect until after next processing step. + ASSERT_EQ (false, DidFireDeferredNotification ()); + + // Process next event. + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + + // Deferred signal notification should have fired now. + ASSERT_EQ (true, DidFireDeferredNotification ()); + ASSERT_EQ (TRIGGERING_TID, GetDeferredNotificationTID ()); +} + +TEST_F (ThreadStateCoordinatorTest, ExistingPendingNotificationRequiresStopFromNewThread) +{ + SetupKnownStoppedThread (TRIGGERING_TID); + SetupKnownRunningThread (PENDING_STOP_TID); + + ThreadStateCoordinator::ThreadIDSet pending_stop_tids { PENDING_STOP_TID }; + + // Notify we have a trigger that needs to be fired when all threads in the wait tid set have stopped. + CallAfterThreadsStop (TRIGGERING_TID, + pending_stop_tids); + + // Neither trigger should have gone off yet. + ASSERT_EQ (false, DidFireDeferredNotification ()); + ASSERT_EQ (0, GetRequestedStopCount ()); + + // Process next event. + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + + // Now the request thread stop should have been called for the pending stop. + ASSERT_EQ (true, DidRequestStopForTid (PENDING_STOP_TID)); + + // But we still shouldn't have the deferred signal call go off yet. + ASSERT_EQ (false, DidFireDeferredNotification ()); + + // Indicate a new thread has just been created. + SetupKnownRunningThread (NEW_THREAD_TID); + + // We should have just received a stop request for the new thread id. + ASSERT_EQ (2, GetRequestedStopCount ()); + ASSERT_EQ (true, DidRequestStopForTid (NEW_THREAD_TID)); + + // Now report the original pending tid stopped. This should no longer + // trigger the pending notification because we should now require the + // new thread to stop too. + NotifyThreadStop (PENDING_STOP_TID); + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + ASSERT_EQ (false, DidFireDeferredNotification ()); + + // Now notify the new thread stopped. + NotifyThreadStop (NEW_THREAD_TID); + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + + // Deferred signal notification should have fired now. + ASSERT_EQ (true, DidFireDeferredNotification ()); + ASSERT_EQ (TRIGGERING_TID, GetDeferredNotificationTID ()); +} + +TEST_F (ThreadStateCoordinatorTest, DeferredNotificationRemovedByResetForExec) +{ + SetupKnownStoppedThread (TRIGGERING_TID); + + // Notify we have a trigger that needs to be fired when all threads in the wait tid set have stopped. + CallAfterThreadsStop (TRIGGERING_TID, + EMPTY_THREAD_ID_SET); + + // Notification trigger shouldn't go off yet. + ASSERT_EQ (false, DidFireDeferredNotification ()); + + // Now indicate an exec occurred, which will invalidate all state about the process and threads. + m_coordinator.ResetForExec (); + + // Verify the deferred stop notification does *not* fire with the next + // process. It will handle the reset and not the deferred signaling, which + // should now be removed. + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + ASSERT_EQ (false, DidFireDeferredNotification ()); +} + +TEST_F (ThreadStateCoordinatorTest, RequestThreadResumeSignalsErrorOnUnknownThread) +{ + const lldb::tid_t UNKNOWN_TID = 411; + + // Request a resume. + lldb::tid_t resumed_tid = 0; + int resume_call_count = 0; + + m_coordinator.RequestThreadResume (UNKNOWN_TID, + GetResumeThreadFunction(resumed_tid, resume_call_count), + GetErrorFunction ()); + // Shouldn't be called yet. + ASSERT_EQ (0, resume_call_count); + + // Process next event. This should fail since the coordinator doesn't know about the thread. + ASSERT_PROCESS_NEXT_EVENT_FAILS (); + ASSERT_EQ (0, resume_call_count); +} + +TEST_F (ThreadStateCoordinatorTest, RequestThreadResumeCallsCallbackWhenThreadIsStopped) +{ + // Initialize thread to be in stopped state. + SetupKnownStoppedThread (NEW_THREAD_TID); + + // Request a resume. + lldb::tid_t resumed_tid = 0; + int resume_call_count = 0; + + m_coordinator.RequestThreadResume (NEW_THREAD_TID, + GetResumeThreadFunction(resumed_tid, resume_call_count), + GetErrorFunction ()); + // Shouldn't be called yet. + ASSERT_EQ (0, resume_call_count); + + // Process next event. After that, the resume request call should have fired. + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + ASSERT_EQ (1, resume_call_count); + ASSERT_EQ (NEW_THREAD_TID, resumed_tid); +} + +TEST_F (ThreadStateCoordinatorTest, RequestThreadResumeSkipsCallbackOnSecondResumeAttempt) +{ + // Initialize thread to be in stopped state. + SetupKnownStoppedThread (NEW_THREAD_TID); + + // Request a resume. + lldb::tid_t resumed_tid = 0; + int resume_call_count = 0; + + m_coordinator.RequestThreadResume (NEW_THREAD_TID, + GetResumeThreadFunction(resumed_tid, resume_call_count), + GetErrorFunction ()); + // Shouldn't be called yet. + ASSERT_EQ (0, resume_call_count); + + // Process next event. After that, the resume request call should have fired. + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + ASSERT_EQ (1, resume_call_count); + ASSERT_EQ (NEW_THREAD_TID, resumed_tid); + + // Make a second resume request. + const int initial_resume_call_count = resume_call_count; + m_coordinator.RequestThreadResume (NEW_THREAD_TID, + GetResumeThreadFunction(resumed_tid, resume_call_count), + GetErrorFunction ()); + + // Process next event. This should fail since the thread should already be running. + ASSERT_PROCESS_NEXT_EVENT_FAILS (); + + // And the resume count should not have increased. + ASSERT_EQ (initial_resume_call_count, resume_call_count); +} + +TEST_F (ThreadStateCoordinatorTest, RequestThreadResumeSignalsErrorOnAlreadyRunningThread) +{ + const lldb::tid_t TEST_TID = 1234; + SetupKnownRunningThread (NEW_THREAD_TID); + + // Request a resume. + lldb::tid_t resumed_tid = 0; + int resume_call_count = 0; + + m_coordinator.RequestThreadResume (TEST_TID, + GetResumeThreadFunction(resumed_tid, resume_call_count), + GetErrorFunction ()); + + // Shouldn't be called yet. + ASSERT_EQ (0, resume_call_count); + + // Process next event. Should be an error. + ASSERT_PROCESS_NEXT_EVENT_FAILS (); + + // The resume request should not have gone off because we think it is already running. + ASSERT_EQ (0, resume_call_count); +} + +TEST_F (ThreadStateCoordinatorTest, ResumedThreadAlreadyMarkedDoesNotHoldUpPendingStopNotification) +{ + // We're going to test this scenario: + // * Deferred notification waiting on two threads, A and B. A and B currently running. + // * Thread A stops. + // * Thread A resumes. + // * Thread B stops. + // + // Here we could have forced A to stop again (after the Thread A resumes) because we had a pending stop nofication awaiting + // all those threads to stop. However, we are going to explicitly not try to restop A - somehow + // that seems wrong and possibly buggy since for that to happen, we would have intentionally called + // a resume after the stop. Instead, we'll just log and indicate this looks suspicous. We can revisit + // that decision after we see if/when that happens. + const lldb::tid_t PENDING_TID_A = 2; + const lldb::tid_t PENDING_TID_B = 89; + + SetupKnownStoppedThread (TRIGGERING_TID); + SetupKnownRunningThread (PENDING_TID_A); + SetupKnownRunningThread (PENDING_TID_B); + + ThreadStateCoordinator::ThreadIDSet pending_stop_tids { PENDING_TID_A, PENDING_TID_B }; + + // Notify we have a trigger that needs to be fired when all threads in the wait tid set have stopped. + CallAfterThreadsStop (TRIGGERING_TID, + pending_stop_tids); + + // Neither trigger should have gone off yet. + ASSERT_EQ (false, DidFireDeferredNotification ()); + ASSERT_EQ (0, GetRequestedStopCount ()); + + // Execute CallAfterThreadsStop. + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + + // Both TID A and TID B should have had stop requests made. + ASSERT_EQ (2, GetRequestedStopCount ()); + ASSERT_EQ (true, DidRequestStopForTid (PENDING_TID_A)); + ASSERT_EQ (true, DidRequestStopForTid (PENDING_TID_B)); + + // But we still shouldn't have the deferred signal call go off yet. + ASSERT_EQ (false, DidFireDeferredNotification ()); + + // Report thread A stopped. + NotifyThreadStop (PENDING_TID_A); + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + ASSERT_EQ (false, DidFireDeferredNotification ()); + + // Now report thread A is resuming. Ensure the resume is called. + lldb::tid_t resumed_tid = 0; + int resume_call_count = 0; + m_coordinator.RequestThreadResume (PENDING_TID_A, + GetResumeThreadFunction(resumed_tid, resume_call_count), + GetErrorFunction ()); + ASSERT_EQ (0, resume_call_count); + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + ASSERT_EQ (1, resume_call_count); + ASSERT_EQ (PENDING_TID_A, resumed_tid); + + // Report thread B stopped. + NotifyThreadStop (PENDING_TID_B); + ASSERT_EQ (false, DidFireDeferredNotification ()); + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + + // After notifying thread b stopped, we now have thread a resumed but thread b stopped. + // However, since thread a had stopped, we now have had both requirements stopped at some point. + // For now we'll expect this will fire the pending deferred stop notification. + ASSERT_EQ (true, DidFireDeferredNotification ()); + ASSERT_EQ (TRIGGERING_TID, GetDeferredNotificationTID ()); +} + +TEST_F (ThreadStateCoordinatorTest, CallAfterRunningThreadsStopFiresWhenNoRunningThreads) +{ + // Let the coordinator know about our thread. + SetupKnownStoppedThread (TRIGGERING_TID); + + // Notify we have a trigger that needs to be fired when all running threads have stopped. + CallAfterRunningThreadsStop (TRIGGERING_TID); + + // Notification trigger shouldn't go off yet. + ASSERT_EQ (false, DidFireDeferredNotification ()); + + // Process next event. This will pick up the call after threads stop event. + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + + // Now the trigger should have fired, since there were no threads that needed to first stop. + ASSERT_EQ (true, DidFireDeferredNotification ()); + ASSERT_EQ (TRIGGERING_TID, GetDeferredNotificationTID ()); + + // And no stop requests should have been made. + ASSERT_EQ (0, GetRequestedStopCount ()); +} + +TEST_F (ThreadStateCoordinatorTest, CallAfterRunningThreadsStopRequestsTwoPendingStops) +{ + // Let the coordinator know about our threads. + SetupKnownStoppedThread (TRIGGERING_TID); + SetupKnownRunningThread (PENDING_STOP_TID); + SetupKnownRunningThread (PENDING_STOP_TID_02); + + // Notify we have a trigger that needs to be fired when all running threads have stopped. + CallAfterRunningThreadsStop (TRIGGERING_TID); + + // Notification trigger shouldn't go off yet. + ASSERT_EQ (false, DidFireDeferredNotification ()); + + // Process next event. This will pick up the call after threads stop event. + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + + // We should have two stop requests for the two threads currently running. + ASSERT_EQ (2, GetRequestedStopCount ()); + ASSERT_EQ (true, DidRequestStopForTid (PENDING_STOP_TID)); + ASSERT_EQ (true, DidRequestStopForTid (PENDING_STOP_TID_02)); + + // But the deferred stop notification should not have fired yet. + ASSERT_EQ (false, DidFireDeferredNotification ()); + + // Now notify the two threads stopped. + NotifyThreadStop (PENDING_STOP_TID); + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + ASSERT_EQ (false, DidFireDeferredNotification ()); + + NotifyThreadStop (PENDING_STOP_TID_02); + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + + // Now the trigger should have fired, since there were no threads that needed to first stop. + ASSERT_EQ (true, DidFireDeferredNotification ()); + ASSERT_EQ (TRIGGERING_TID, GetDeferredNotificationTID ()); +} + +TEST_F (ThreadStateCoordinatorTest, CallAfterRunningThreadsStopRequestsStopTwoOtherThreadsOneRunning) +{ + // Let the coordinator know about our threads. PENDING_STOP_TID_02 will already be stopped. + SetupKnownStoppedThread (TRIGGERING_TID); + SetupKnownRunningThread (PENDING_STOP_TID); + SetupKnownStoppedThread (PENDING_STOP_TID_02); + + // Notify we have a trigger that needs to be fired when all running threads have stopped. + CallAfterRunningThreadsStop (TRIGGERING_TID); + + // Notification trigger shouldn't go off yet. + ASSERT_EQ (false, DidFireDeferredNotification ()); + + // Process next event. This will pick up the call after threads stop event. + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + + // We should have two stop requests for the two threads currently running. + ASSERT_EQ (1, GetRequestedStopCount ()); + ASSERT_EQ (true, DidRequestStopForTid (PENDING_STOP_TID)); + + // But the deferred stop notification should not have fired yet. + ASSERT_EQ (false, DidFireDeferredNotification ()); + + // Now notify the two threads stopped. + NotifyThreadStop (PENDING_STOP_TID); + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + + // Now the trigger should have fired, since there were no threads that needed to first stop. + ASSERT_EQ (true, DidFireDeferredNotification ()); + ASSERT_EQ (TRIGGERING_TID, GetDeferredNotificationTID ()); +} diff --git a/lldb/unittests/Plugins/Process/Linux/ThreadStateCoordinatorTestMock.cpp b/lldb/unittests/Plugins/Process/Linux/ThreadStateCoordinatorTestMock.cpp new file mode 100644 index 00000000000..73693609b15 --- /dev/null +++ b/lldb/unittests/Plugins/Process/Linux/ThreadStateCoordinatorTestMock.cpp @@ -0,0 +1,32 @@ +//===-- ThreadStateCoordinatorTestMock.cpp ----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// This file provides a few necessary functions to link +// ThreadStateCoordinatorTest.cpp Bringing in the real implementations results +// in a cascade of dependencies that pull in all of lldb. + +#include "lldb/Core/Log.h" + +using namespace lldb_private; + +void +lldb_private::Log::Error (char const*, ...) +{ +} + +void +lldb_private::Log::Printf (char const*, ...) +{ +} + +Log* +lldb_private::GetLogIfAnyCategoriesSet (unsigned int) +{ + return nullptr; +} diff --git a/lldb/unittests/Utility/CMakeLists.txt b/lldb/unittests/Utility/CMakeLists.txt new file mode 100644 index 00000000000..fea0f53fdd9 --- /dev/null +++ b/lldb/unittests/Utility/CMakeLists.txt @@ -0,0 +1,4 @@ +add_lldb_unittest(UtilityTests
+ StringExtractorTest.cpp
+ UriParserTest.cpp
+ )
diff --git a/lldb/unittests/Utility/StringExtractorTest.cpp b/lldb/unittests/Utility/StringExtractorTest.cpp new file mode 100644 index 00000000000..cbb3816b79b --- /dev/null +++ b/lldb/unittests/Utility/StringExtractorTest.cpp @@ -0,0 +1,406 @@ +#include <limits.h> +#include "gtest/gtest.h" + +#include "Utility/StringExtractor.h" + +namespace +{ + class StringExtractorTest: public ::testing::Test + { + }; +} + +TEST_F (StringExtractorTest, InitEmpty) +{ + const char kEmptyString[] = ""; + StringExtractor ex (kEmptyString); + + ASSERT_EQ (true, ex.IsGood()); + ASSERT_EQ (0, ex.GetFilePos()); + ASSERT_STREQ (kEmptyString, ex.GetStringRef().c_str()); + ASSERT_EQ (true, ex.Empty()); + ASSERT_EQ (0, ex.GetBytesLeft()); + ASSERT_EQ (nullptr, ex.Peek()); +} + +TEST_F (StringExtractorTest, InitMisc) +{ + const char kInitMiscString[] = "Hello, StringExtractor!"; + StringExtractor ex (kInitMiscString); + + ASSERT_EQ (true, ex.IsGood()); + ASSERT_EQ (0, ex.GetFilePos()); + ASSERT_STREQ (kInitMiscString, ex.GetStringRef().c_str()); + ASSERT_EQ (false, ex.Empty()); + ASSERT_EQ (sizeof(kInitMiscString)-1, ex.GetBytesLeft()); + ASSERT_EQ (kInitMiscString[0], *ex.Peek()); +} + +TEST_F (StringExtractorTest, DecodeHexU8_Underflow) +{ + const char kEmptyString[] = ""; + StringExtractor ex (kEmptyString); + + ASSERT_EQ (-1, ex.DecodeHexU8()); + ASSERT_EQ (true, ex.IsGood()); + ASSERT_EQ (0, ex.GetFilePos()); + ASSERT_EQ (true, ex.Empty()); + ASSERT_EQ (0, ex.GetBytesLeft()); + ASSERT_EQ (nullptr, ex.Peek()); +} + +TEST_F (StringExtractorTest, DecodeHexU8_Underflow2) +{ + const char kEmptyString[] = "1"; + StringExtractor ex (kEmptyString); + + ASSERT_EQ (-1, ex.DecodeHexU8()); + ASSERT_EQ (true, ex.IsGood()); + ASSERT_EQ (0, ex.GetFilePos()); + ASSERT_EQ (1, ex.GetBytesLeft()); + ASSERT_EQ ('1', *ex.Peek()); +} + +TEST_F (StringExtractorTest, DecodeHexU8_InvalidHex) +{ + const char kInvalidHex[] = "xa"; + StringExtractor ex (kInvalidHex); + + ASSERT_EQ (-1, ex.DecodeHexU8()); + ASSERT_EQ (true, ex.IsGood()); + ASSERT_EQ (0, ex.GetFilePos()); + ASSERT_EQ (2, ex.GetBytesLeft()); + ASSERT_EQ ('x', *ex.Peek()); +} + +TEST_F (StringExtractorTest, DecodeHexU8_InvalidHex2) +{ + const char kInvalidHex[] = "ax"; + StringExtractor ex (kInvalidHex); + + ASSERT_EQ (-1, ex.DecodeHexU8()); + ASSERT_EQ (true, ex.IsGood()); + ASSERT_EQ (0, ex.GetFilePos()); + ASSERT_EQ (2, ex.GetBytesLeft()); + ASSERT_EQ ('a', *ex.Peek()); +} + +TEST_F (StringExtractorTest, DecodeHexU8_Exact) +{ + const char kValidHexPair[] = "12"; + StringExtractor ex (kValidHexPair); + + ASSERT_EQ (0x12, ex.DecodeHexU8()); + ASSERT_EQ (true, ex.IsGood()); + ASSERT_EQ (2, ex.GetFilePos()); + ASSERT_EQ (0, ex.GetBytesLeft()); + ASSERT_EQ (nullptr, ex.Peek()); +} + +TEST_F (StringExtractorTest, DecodeHexU8_Extra) +{ + const char kValidHexPair[] = "1234"; + StringExtractor ex (kValidHexPair); + + ASSERT_EQ (0x12, ex.DecodeHexU8()); + ASSERT_EQ (true, ex.IsGood()); + ASSERT_EQ (2, ex.GetFilePos()); + ASSERT_EQ (2, ex.GetBytesLeft()); + ASSERT_EQ ('3', *ex.Peek()); +} + +TEST_F (StringExtractorTest, GetHexU8_Underflow) +{ + const char kEmptyString[] = ""; + StringExtractor ex (kEmptyString); + + ASSERT_EQ (0xab, ex.GetHexU8(0xab)); + ASSERT_EQ (false, ex.IsGood()); + ASSERT_EQ (UINT64_MAX, ex.GetFilePos()); + ASSERT_EQ (true, ex.Empty()); + ASSERT_EQ (0, ex.GetBytesLeft()); + ASSERT_EQ (nullptr, ex.Peek()); +} + +TEST_F (StringExtractorTest, GetHexU8_Underflow2) +{ + const char kOneNibble[] = "1"; + StringExtractor ex (kOneNibble); + + ASSERT_EQ (0xbc, ex.GetHexU8(0xbc)); + ASSERT_EQ (false, ex.IsGood()); + ASSERT_EQ (UINT64_MAX, ex.GetFilePos()); + ASSERT_EQ (0, ex.GetBytesLeft()); + ASSERT_EQ (nullptr, ex.Peek()); +} + +TEST_F (StringExtractorTest, GetHexU8_InvalidHex) +{ + const char kInvalidHex[] = "xx"; + StringExtractor ex (kInvalidHex); + + ASSERT_EQ (0xcd, ex.GetHexU8(0xcd)); + ASSERT_EQ (false, ex.IsGood()); + ASSERT_EQ (UINT64_MAX, ex.GetFilePos()); + ASSERT_EQ (0, ex.GetBytesLeft()); + ASSERT_EQ (nullptr, ex.Peek()); +} + +TEST_F (StringExtractorTest, GetHexU8_Exact) +{ + const char kValidHexPair[] = "12"; + StringExtractor ex (kValidHexPair); + + ASSERT_EQ (0x12, ex.GetHexU8(0x12)); + ASSERT_EQ (true, ex.IsGood()); + ASSERT_EQ (2, ex.GetFilePos()); + ASSERT_EQ (0, ex.GetBytesLeft()); + ASSERT_EQ (nullptr, ex.Peek()); +} + +TEST_F (StringExtractorTest, GetHexU8_Extra) +{ + const char kValidHexPair[] = "1234"; + StringExtractor ex (kValidHexPair); + + ASSERT_EQ (0x12, ex.GetHexU8(0x12)); + ASSERT_EQ (true, ex.IsGood()); + ASSERT_EQ (2, ex.GetFilePos()); + ASSERT_EQ (2, ex.GetBytesLeft()); + ASSERT_EQ ('3', *ex.Peek()); +} + +TEST_F (StringExtractorTest, GetHexU8_Underflow_NoEof) +{ + const char kEmptyString[] = ""; + StringExtractor ex (kEmptyString); + const bool kSetEofOnFail = false; + + ASSERT_EQ (0xab, ex.GetHexU8(0xab, kSetEofOnFail)); + ASSERT_EQ (false, ex.IsGood()); // this result seems inconsistent with kSetEofOnFail == false + ASSERT_EQ (UINT64_MAX, ex.GetFilePos()); + ASSERT_EQ (true, ex.Empty()); + ASSERT_EQ (0, ex.GetBytesLeft()); + ASSERT_EQ (nullptr, ex.Peek()); +} + +TEST_F (StringExtractorTest, GetHexU8_Underflow2_NoEof) +{ + const char kOneNibble[] = "1"; + StringExtractor ex (kOneNibble); + const bool kSetEofOnFail = false; + + ASSERT_EQ (0xbc, ex.GetHexU8(0xbc, kSetEofOnFail)); + ASSERT_EQ (true, ex.IsGood()); + ASSERT_EQ (0, ex.GetFilePos()); + ASSERT_EQ (1, ex.GetBytesLeft()); + ASSERT_EQ ('1', *ex.Peek()); +} + +TEST_F (StringExtractorTest, GetHexU8_InvalidHex_NoEof) +{ + const char kInvalidHex[] = "xx"; + StringExtractor ex (kInvalidHex); + const bool kSetEofOnFail = false; + + ASSERT_EQ (0xcd, ex.GetHexU8(0xcd, kSetEofOnFail)); + ASSERT_EQ (true, ex.IsGood()); + ASSERT_EQ (0, ex.GetFilePos()); + ASSERT_EQ (2, ex.GetBytesLeft()); + ASSERT_EQ ('x', *ex.Peek()); +} + +TEST_F (StringExtractorTest, GetHexU8_Exact_NoEof) +{ + const char kValidHexPair[] = "12"; + StringExtractor ex (kValidHexPair); + const bool kSetEofOnFail = false; + + ASSERT_EQ (0x12, ex.GetHexU8(0x12, kSetEofOnFail)); + ASSERT_EQ (true, ex.IsGood()); + ASSERT_EQ (2, ex.GetFilePos()); + ASSERT_EQ (0, ex.GetBytesLeft()); + ASSERT_EQ (nullptr, ex.Peek()); +} + +TEST_F (StringExtractorTest, GetHexU8_Extra_NoEof) +{ + const char kValidHexPair[] = "1234"; + StringExtractor ex (kValidHexPair); + const bool kSetEofOnFail = false; + + ASSERT_EQ (0x12, ex.GetHexU8(0x12, kSetEofOnFail)); + ASSERT_EQ (true, ex.IsGood()); + ASSERT_EQ (2, ex.GetFilePos()); + ASSERT_EQ (2, ex.GetBytesLeft()); + ASSERT_EQ ('3', *ex.Peek()); +} + +TEST_F (StringExtractorTest, GetHexBytes) +{ + const char kHexEncodedBytes[] = "abcdef0123456789xyzw"; + const size_t kValidHexPairs = 8; + StringExtractor ex(kHexEncodedBytes); + + uint8_t dst[kValidHexPairs]; + ASSERT_EQ(kValidHexPairs, ex.GetHexBytes (dst, kValidHexPairs, 0xde)); + EXPECT_EQ(0xab,dst[0]); + EXPECT_EQ(0xcd,dst[1]); + EXPECT_EQ(0xef,dst[2]); + EXPECT_EQ(0x01,dst[3]); + EXPECT_EQ(0x23,dst[4]); + EXPECT_EQ(0x45,dst[5]); + EXPECT_EQ(0x67,dst[6]); + EXPECT_EQ(0x89,dst[7]); + + ASSERT_EQ(true, ex.IsGood()); + ASSERT_EQ(2*kValidHexPairs, ex.GetFilePos()); + ASSERT_EQ(false, ex.Empty()); + ASSERT_EQ(4, ex.GetBytesLeft()); + ASSERT_EQ('x', *ex.Peek()); +} + +TEST_F (StringExtractorTest, GetHexBytes_Underflow) +{ + const char kHexEncodedBytes[] = "abcdef0123456789xyzw"; + const size_t kValidHexPairs = 8; + StringExtractor ex(kHexEncodedBytes); + + uint8_t dst[12]; + ASSERT_EQ(kValidHexPairs, ex.GetHexBytes (dst, sizeof(dst), 0xde)); + EXPECT_EQ(0xab,dst[0]); + EXPECT_EQ(0xcd,dst[1]); + EXPECT_EQ(0xef,dst[2]); + EXPECT_EQ(0x01,dst[3]); + EXPECT_EQ(0x23,dst[4]); + EXPECT_EQ(0x45,dst[5]); + EXPECT_EQ(0x67,dst[6]); + EXPECT_EQ(0x89,dst[7]); + // these bytes should be filled with fail_fill_value 0xde + EXPECT_EQ(0xde,dst[8]); + EXPECT_EQ(0xde,dst[9]); + EXPECT_EQ(0xde,dst[10]); + EXPECT_EQ(0xde,dst[11]); + + ASSERT_EQ(false, ex.IsGood()); + ASSERT_EQ(UINT64_MAX, ex.GetFilePos()); + ASSERT_EQ(false, ex.Empty()); + ASSERT_EQ(0, ex.GetBytesLeft()); + ASSERT_EQ(0, ex.Peek()); +} + +TEST_F (StringExtractorTest, GetHexBytes_Partial) +{ + const char kHexEncodedBytes[] = "abcdef0123456789xyzw"; + const size_t kReadBytes = 4; + StringExtractor ex(kHexEncodedBytes); + + uint8_t dst[12]; + memset(dst, 0xab, sizeof(dst)); + ASSERT_EQ(kReadBytes, ex.GetHexBytes (dst, kReadBytes, 0xde)); + EXPECT_EQ(0xab,dst[0]); + EXPECT_EQ(0xcd,dst[1]); + EXPECT_EQ(0xef,dst[2]); + EXPECT_EQ(0x01,dst[3]); + // these bytes should be unchanged + EXPECT_EQ(0xab,dst[4]); + EXPECT_EQ(0xab,dst[5]); + EXPECT_EQ(0xab,dst[6]); + EXPECT_EQ(0xab,dst[7]); + EXPECT_EQ(0xab,dst[8]); + EXPECT_EQ(0xab,dst[9]); + EXPECT_EQ(0xab,dst[10]); + EXPECT_EQ(0xab,dst[11]); + + ASSERT_EQ(true, ex.IsGood()); + ASSERT_EQ(kReadBytes*2, ex.GetFilePos()); + ASSERT_EQ(false, ex.Empty()); + ASSERT_EQ(12, ex.GetBytesLeft()); + ASSERT_EQ('2', *ex.Peek()); +} + +TEST_F (StringExtractorTest, GetHexBytesAvail) +{ + const char kHexEncodedBytes[] = "abcdef0123456789xyzw"; + const size_t kValidHexPairs = 8; + StringExtractor ex(kHexEncodedBytes); + + uint8_t dst[kValidHexPairs]; + ASSERT_EQ(kValidHexPairs, ex.GetHexBytesAvail (dst, kValidHexPairs)); + EXPECT_EQ(0xab,dst[0]); + EXPECT_EQ(0xcd,dst[1]); + EXPECT_EQ(0xef,dst[2]); + EXPECT_EQ(0x01,dst[3]); + EXPECT_EQ(0x23,dst[4]); + EXPECT_EQ(0x45,dst[5]); + EXPECT_EQ(0x67,dst[6]); + EXPECT_EQ(0x89,dst[7]); + + ASSERT_EQ(true, ex.IsGood()); + ASSERT_EQ(2*kValidHexPairs, ex.GetFilePos()); + ASSERT_EQ(false, ex.Empty()); + ASSERT_EQ(4, ex.GetBytesLeft()); + ASSERT_EQ('x', *ex.Peek()); +} + +TEST_F (StringExtractorTest, GetHexBytesAvail_Underflow) +{ + const char kHexEncodedBytes[] = "abcdef0123456789xyzw"; + const size_t kValidHexPairs = 8; + StringExtractor ex(kHexEncodedBytes); + + uint8_t dst[12]; + memset(dst, 0xef, sizeof(dst)); + ASSERT_EQ(kValidHexPairs, ex.GetHexBytesAvail (dst, sizeof(dst))); + EXPECT_EQ(0xab,dst[0]); + EXPECT_EQ(0xcd,dst[1]); + EXPECT_EQ(0xef,dst[2]); + EXPECT_EQ(0x01,dst[3]); + EXPECT_EQ(0x23,dst[4]); + EXPECT_EQ(0x45,dst[5]); + EXPECT_EQ(0x67,dst[6]); + EXPECT_EQ(0x89,dst[7]); + // these bytes should be unchanged + EXPECT_EQ(0xef,dst[8]); + EXPECT_EQ(0xef,dst[9]); + EXPECT_EQ(0xef,dst[10]); + EXPECT_EQ(0xef,dst[11]); + + ASSERT_EQ(true, ex.IsGood()); + ASSERT_EQ(kValidHexPairs*2, ex.GetFilePos()); + ASSERT_EQ(false, ex.Empty()); + ASSERT_EQ(4, ex.GetBytesLeft()); + ASSERT_EQ('x', *ex.Peek()); +} + +TEST_F (StringExtractorTest, GetHexBytesAvail_Partial) +{ + const char kHexEncodedBytes[] = "abcdef0123456789xyzw"; + const size_t kReadBytes = 4; + StringExtractor ex(kHexEncodedBytes); + + uint8_t dst[12]; + memset(dst, 0xab, sizeof(dst)); + ASSERT_EQ(kReadBytes, ex.GetHexBytesAvail (dst, kReadBytes)); + EXPECT_EQ(0xab,dst[0]); + EXPECT_EQ(0xcd,dst[1]); + EXPECT_EQ(0xef,dst[2]); + EXPECT_EQ(0x01,dst[3]); + // these bytes should be unchanged + EXPECT_EQ(0xab,dst[4]); + EXPECT_EQ(0xab,dst[5]); + EXPECT_EQ(0xab,dst[6]); + EXPECT_EQ(0xab,dst[7]); + EXPECT_EQ(0xab,dst[8]); + EXPECT_EQ(0xab,dst[9]); + EXPECT_EQ(0xab,dst[10]); + EXPECT_EQ(0xab,dst[11]); + + ASSERT_EQ(true, ex.IsGood()); + ASSERT_EQ(kReadBytes*2, ex.GetFilePos()); + ASSERT_EQ(false, ex.Empty()); + ASSERT_EQ(12, ex.GetBytesLeft()); + ASSERT_EQ('2', *ex.Peek()); +} + + diff --git a/lldb/unittests/Utility/UriParserTest.cpp b/lldb/unittests/Utility/UriParserTest.cpp new file mode 100644 index 00000000000..a11dd8e12b0 --- /dev/null +++ b/lldb/unittests/Utility/UriParserTest.cpp @@ -0,0 +1,135 @@ +#include "gtest/gtest.h" +#include "Utility/UriParser.h" + +namespace +{ + class UriParserTest: public ::testing::Test + { + }; +} + +// result strings (scheme/hostname/port/path) passed into UriParser::Parse +// are initialized to kAsdf so we can verify that they are unmodified if the +// URI is invalid +static const char* kAsdf = "asdf"; + +class UriTestCase +{ +public: + UriTestCase(const char* uri, const char* scheme, const char* hostname, int port, const char* path) : + m_uri(uri), + m_result(true), + m_scheme(scheme), + m_hostname(hostname), + m_port(port), + m_path(path) + { + } + + UriTestCase(const char* uri) : + m_uri(uri), + m_result(false), + m_scheme(kAsdf), + m_hostname(kAsdf), + m_port(1138), + m_path(kAsdf) + { + } + + const char* m_uri; + bool m_result; + const char* m_scheme; + const char* m_hostname; + int m_port; + const char* m_path; +}; + +#define VALIDATE \ + std::string scheme(kAsdf); \ + std::string hostname(kAsdf); \ + int port(1138); \ + std::string path(kAsdf); \ + EXPECT_EQ (testCase.m_result, UriParser::Parse(testCase.m_uri, scheme, hostname, port, path)); \ + EXPECT_STREQ (testCase.m_scheme, scheme.c_str()); \ + EXPECT_STREQ (testCase.m_hostname, hostname.c_str()); \ + EXPECT_EQ (testCase.m_port, port); \ + EXPECT_STREQ (testCase.m_path, path.c_str()); + +TEST_F (UriParserTest, Minimal) +{ + const UriTestCase testCase("x://y", "x", "y", -1, "/"); + VALIDATE +} + +TEST_F (UriParserTest, MinimalPort) +{ + const UriTestCase testCase("x://y:1", "x", "y", 1, "/"); + VALIDATE +} + +TEST_F (UriParserTest, MinimalPath) +{ + const UriTestCase testCase("x://y/", "x", "y", -1, "/"); + VALIDATE +} + +TEST_F (UriParserTest, MinimalPortPath) +{ + const UriTestCase testCase("x://y:1/", "x", "y", 1, "/"); + VALIDATE +} + +TEST_F (UriParserTest, TypicalPortPath) +{ + const UriTestCase testCase("connect://192.168.100.132:5432/", "connect", "192.168.100.132", 5432, "/"); + VALIDATE +} + +TEST_F (UriParserTest, SchemeHostSeparator) +{ + const UriTestCase testCase("x:/y"); + VALIDATE +} + +TEST_F (UriParserTest, SchemeHostSeparator2) +{ + const UriTestCase testCase("x:y"); + VALIDATE +} + +TEST_F (UriParserTest, SchemeHostSeparator3) +{ + const UriTestCase testCase("x//y"); + VALIDATE +} + +TEST_F (UriParserTest, SchemeHostSeparator4) +{ + const UriTestCase testCase("x/y"); + VALIDATE +} + +TEST_F (UriParserTest, BadPort) +{ + const UriTestCase testCase("x://y:a/"); + VALIDATE +} + +TEST_F (UriParserTest, BadPort2) +{ + const UriTestCase testCase("x://y:5432a/"); + VALIDATE +} + +TEST_F (UriParserTest, Empty) +{ + const UriTestCase testCase(""); + VALIDATE +} + +TEST_F (UriParserTest, PortOverflow) +{ + const UriTestCase testCase("x://y:0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789/"); + VALIDATE +} + diff --git a/lldb/unittests/gtest_common.h b/lldb/unittests/gtest_common.h new file mode 100644 index 00000000000..228dfb0d328 --- /dev/null +++ b/lldb/unittests/gtest_common.h @@ -0,0 +1,32 @@ +//===-- gtest_common.h ------------------------------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#if defined(LLDB_GTEST_COMMON_H)
+#error "gtest_common.h should not be included manually."
+#else
+#define LLDB_GTEST_COMMON_H
+#endif
+
+// This header file is force included by all of LLDB's unittest compilation
+// units. Be very leary about putting anything in this file.
+
+#if defined(_MSC_VER) && (_HAS_EXCEPTIONS == 0)
+// MSVC's STL implementation tries to work well with /EHs-c- and
+// _HAS_EXCEPTIONS=0. But <thread> in particular doesn't work with it, because
+// it relies on <concrt.h> which tries to throw an exception without checking
+// for _HAS_EXCEPTIONS=0. This causes the linker to require a definition of
+// __uncaught_exception(), but the STL doesn't define this function when
+// _HAS_EXCEPTIONS=0. The workaround here is to just provide a stub
+// implementation to get it to link.
+inline bool
+__uncaught_exception()
+{
+ return true;
+}
+#endif
|

