From b78aa5cf225a1b05dca9d8c708af25f6252be04c Mon Sep 17 00:00:00 2001 From: David Matson Date: Fri, 17 Jan 2025 17:46:38 -0800 Subject: [PATCH] Add support for streaming results on Windows (fixes #3935). --- CONTRIBUTORS | 1 + .../include/gtest/internal/gtest-port.h | 36 ++++++- googletest/src/gtest-internal-inl.h | 14 +-- googletest/src/gtest-port.cc | 93 +++++++++++++++++++ googletest/src/gtest.cc | 33 +++---- googletest/test/gtest_help_test.py | 2 +- 6 files changed, 152 insertions(+), 27 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index ccea41ea..b76c2ed4 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -14,6 +14,7 @@ Chris Taylor Dan Egnor Dave MacLachlan David Anderson +David Matson Dean Sturtevant Eric Roman Gene Volovich diff --git a/googletest/include/gtest/internal/gtest-port.h b/googletest/include/gtest/internal/gtest-port.h index ca18513e..84d98b3c 100644 --- a/googletest/include/gtest/internal/gtest-port.h +++ b/googletest/include/gtest/internal/gtest-port.h @@ -707,7 +707,8 @@ typedef struct _RTL_CRITICAL_SECTION GTEST_CRITICAL_SECTION; #if defined(GTEST_OS_LINUX) || defined(GTEST_OS_GNU_KFREEBSD) || \ defined(GTEST_OS_DRAGONFLY) || defined(GTEST_OS_FREEBSD) || \ defined(GTEST_OS_NETBSD) || defined(GTEST_OS_OPENBSD) || \ - defined(GTEST_OS_GNU_HURD) || defined(GTEST_OS_MAC) + defined(GTEST_OS_GNU_HURD) || defined(GTEST_OS_MAC) || \ + defined(GTEST_OS_WINDOWS) #define GTEST_CAN_STREAM_RESULTS_ 1 #else #define GTEST_CAN_STREAM_RESULTS_ 0 @@ -945,6 +946,18 @@ typedef struct _RTL_CRITICAL_SECTION GTEST_CRITICAL_SECTION; #define GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ #endif +#if GTEST_CAN_STREAM_RESULTS_ +#if GTEST_OS_WINDOWS +#define NOMINMAX +#include // NOLINT +#else // GTEST_OS_WINDOWS + +#include // NOLINT +#include // NOLINT + +#endif // GTEST_OS_WINDOWS +#endif // GTEST_CAN_STREAM_RESULTS_ + namespace testing { class Message; @@ -2195,6 +2208,27 @@ GTEST_DISABLE_MSC_DEPRECATED_POP_() [[noreturn]] inline void Abort() { abort(); } #endif // GTEST_OS_WINDOWS_MOBILE +// Sockets porting. + +#if GTEST_CAN_STREAM_RESULTS_ + +int SocketStartup(); + +int Socket(int domain, int type, int protocol); + +int GetAddrInfo(const char* nodename, + const char* servname, + const struct addrinfo* hints, + struct addrinfo** res); + +void FreeAddrInfo(struct addrinfo* ai); + +int Connect(int sockfd, const struct sockaddr* addr, size_t addrlen); + +const char* GaiStrError(int errcode); + +#endif // GTEST_CAN_STREAM_RESULTS_ + } // namespace posix // MSVC "deprecates" snprintf and issues warnings wherever it is used. In diff --git a/googletest/src/gtest-internal-inl.h b/googletest/src/gtest-internal-inl.h index cc6f0048..c19387a3 100644 --- a/googletest/src/gtest-internal-inl.h +++ b/googletest/src/gtest-internal-inl.h @@ -51,11 +51,6 @@ #include "gtest/internal/gtest-port.h" -#if GTEST_CAN_STREAM_RESULTS_ -#include // NOLINT -#include // NOLINT -#endif - #ifdef GTEST_OS_WINDOWS #include // NOLINT #endif // GTEST_OS_WINDOWS @@ -1071,7 +1066,7 @@ class TestResultAccessor { #if GTEST_CAN_STREAM_RESULTS_ // Streams test results to the given port on the given host machine. -class StreamingListener : public EmptyTestEventListener { +class GTEST_API_ StreamingListener : public EmptyTestEventListener { public: // Abstract base class for writing strings to a socket. class AbstractSocketWriter { @@ -1105,8 +1100,9 @@ class StreamingListener : public EmptyTestEventListener { GTEST_CHECK_(sockfd_ != -1) << "Send() can be called only when there is a connection."; - const auto len = static_cast(message.length()); - if (write(sockfd_, message.c_str(), len) != static_cast(len)) { + const auto len = static_cast(message.length()); + if (posix::Write(sockfd_, message.c_str(), len) != + static_cast(len)) { GTEST_LOG_(WARNING) << "stream_result_to: failed to stream to " << host_name_ << ":" << port_num_; } @@ -1121,7 +1117,7 @@ class StreamingListener : public EmptyTestEventListener { GTEST_CHECK_(sockfd_ != -1) << "CloseConnection() can be called only when there is a connection."; - close(sockfd_); + posix::Close(sockfd_); sockfd_ = -1; } diff --git a/googletest/src/gtest-port.cc b/googletest/src/gtest-port.cc index 1038ad7b..b4f96be8 100644 --- a/googletest/src/gtest-port.cc +++ b/googletest/src/gtest-port.cc @@ -1430,5 +1430,98 @@ const char* StringFromGTestEnv(const char* flag, const char* default_value) { #endif // defined(GTEST_GET_STRING_FROM_ENV_) } +#if GTEST_CAN_STREAM_RESULTS_ + +namespace posix { + +#if GTEST_OS_WINDOWS + +#include // NOLINT +#include // NOLINT +#pragma comment(lib, "Ws2_32.lib") + +int SocketStartup() { + WSADATA winsockVersion{}; + + int startupError{ WSAStartup(MAKEWORD(2, 2), &winsockVersion) }; + + if (startupError) + { + return startupError; + } + + if (LOBYTE(winsockVersion.wVersion) != 2 || + HIBYTE(winsockVersion.wVersion) != 2) + { + return -1; + } + + return 0; +} + +int Socket(int domain, int type, int protocol) +{ + return static_cast(socket(domain, type, protocol)); +} + +int GetAddrInfo(const char* nodename, + const char* servname, + const struct addrinfo* hints, + struct addrinfo** res) +{ + return getaddrinfo(nodename, servname, hints, res); +} + +void FreeAddrInfo(struct addrinfo* ai) +{ + freeaddrinfo(ai); +} + +int Connect(int sockfd, const struct sockaddr* addr, size_t addrlen) +{ + return connect(sockfd, addr, static_cast(addrlen)); +} + +const char* GaiStrError(int errcode) +{ + return gai_strerrorA(errcode); +} + +#else // GTEST_OS_WINDOWS + +#include // NOLINT +#include // NOLINT +#include // NOLINT +#include // NOLINT + +int SocketStartup() { + return 0; +} + +int Socket(int domain, int type, int protocol) { + return socket(domain, type, protocol); +} + +int GetAddrInfo(const char* nodename, + const char* servname, + const struct addrinfo* hints, + struct addrinfo** res) { + return getaddrinfo(nodename, servname, hints, res); +} + +void FreeAddrInfo(struct addrinfo* ai) { freeaddrinfo(ai); } + +int Connect(int sockfd, const struct sockaddr* addr, size_t addrlen) { + return connect(sockfd, addr, static_cast(addrlen)); +} + +const char* GaiStrError(int errcode) { return gai_strerror(errcode); } + +#endif // GTEST_OS_WINDOWS + +} // namespace posix + +#endif // GTEST_CAN_STREAM_RESULTS_ + } // namespace internal } // namespace testing diff --git a/googletest/src/gtest.cc b/googletest/src/gtest.cc index 3c1cac6e..ddd7da21 100644 --- a/googletest/src/gtest.cc +++ b/googletest/src/gtest.cc @@ -122,13 +122,6 @@ #include #endif -#if GTEST_CAN_STREAM_RESULTS_ -#include // NOLINT -#include // NOLINT -#include // NOLINT -#include // NOLINT -#endif - #include "src/gtest-internal-inl.h" #ifdef GTEST_OS_WINDOWS @@ -379,7 +372,7 @@ GTEST_DEFINE_string_( testing::internal::StringFromGTestEnv("stream_result_to", ""), "This flag specifies the host name and the port number on which to stream " "test results. Example: \"localhost:555\". The flag is effective only on " - "Linux and macOS."); + "Linux, macOS, and Windows."); GTEST_DEFINE_bool_( throw_on_failure, @@ -5022,6 +5015,11 @@ void StreamingListener::SocketWriter::MakeConnection() { GTEST_CHECK_(sockfd_ == -1) << "MakeConnection() can't be called when there is already a connection."; + if (!posix::SocketStartup()) { + GTEST_LOG_(WARNING) << "Socket startup failed."; + return; + } + addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; // To allow both IPv4 and IPv6 addresses. @@ -5030,28 +5028,31 @@ void StreamingListener::SocketWriter::MakeConnection() { // Use the getaddrinfo() to get a linked list of IP addresses for // the given host name. - const int error_num = - getaddrinfo(host_name_.c_str(), port_num_.c_str(), &hints, &servinfo); + const int error_num = posix::GetAddrInfo(host_name_.c_str(), + port_num_.c_str(), + &hints, + &servinfo); if (error_num != 0) { GTEST_LOG_(WARNING) << "stream_result_to: getaddrinfo() failed: " - << gai_strerror(error_num); + << posix::GaiStrError(error_num); } // Loop through all the results and connect to the first we can. for (addrinfo* cur_addr = servinfo; sockfd_ == -1 && cur_addr != nullptr; cur_addr = cur_addr->ai_next) { - sockfd_ = socket(cur_addr->ai_family, cur_addr->ai_socktype, - cur_addr->ai_protocol); + sockfd_ = posix::Socket(cur_addr->ai_family, cur_addr->ai_socktype, + cur_addr->ai_protocol); if (sockfd_ != -1) { // Connect the client socket to the server socket. - if (connect(sockfd_, cur_addr->ai_addr, cur_addr->ai_addrlen) == -1) { - close(sockfd_); + if (posix::Connect(sockfd_, cur_addr->ai_addr, cur_addr->ai_addrlen) == + -1) { + posix::Close(sockfd_); sockfd_ = -1; } } } - freeaddrinfo(servinfo); // all done with this structure + posix::FreeAddrInfo(servinfo); // all done with this structure if (sockfd_ == -1) { GTEST_LOG_(WARNING) << "stream_result_to: failed to connect to " diff --git a/googletest/test/gtest_help_test.py b/googletest/test/gtest_help_test.py index 38fc90ff..f71932c2 100755 --- a/googletest/test/gtest_help_test.py +++ b/googletest/test/gtest_help_test.py @@ -144,7 +144,7 @@ class GTestHelpTest(gtest_test_utils.TestCase): self.assertTrue(HELP_REGEX.search(output), output) - if IS_DARWIN or IS_LINUX or IS_GNUHURD or is_bsd_based_os(): + if IS_DARWIN or IS_LINUX or IS_GNUHURD or is_bsd_based_os() or IS_WINDOWS: self.assertIn(STREAM_RESULT_TO_FLAG, output) else: self.assertNotIn(STREAM_RESULT_TO_FLAG, output)