From ce0a1584ba7ec961cd700a411692d0d289264a1c Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Mon, 21 Apr 2025 15:47:21 +0100 Subject: [PATCH] Cygwin: try a horrible hack to fix integration test with unix sockets --- test/integration/CMakeLists.txt | 7 ++ test/integration/framework/cygwin.cpp | 99 ++++++++++++++++++++++++++ test/integration/framework/cygwin.h | 11 +++ test/integration/framework/znctest.cpp | 8 ++- test/integration/tests/modules.cpp | 2 + 5 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 test/integration/framework/cygwin.cpp create mode 100644 test/integration/framework/cygwin.h diff --git a/test/integration/CMakeLists.txt b/test/integration/CMakeLists.txt index af4679cb..495ec762 100644 --- a/test/integration/CMakeLists.txt +++ b/test/integration/CMakeLists.txt @@ -56,3 +56,10 @@ target_include_directories(inttest PUBLIC "${GMOCK_ROOT}" "${GMOCK_ROOT}/include") target_compile_definitions(inttest PRIVATE "ZNC_BIN_DIR=\"${ZNC_BIN_DIR}\"") + +if(CYGWIN) + # This workaround contains a sizeable modified copypaste of Qt's qlocalsocket_unix.cpp, which is LGPL, so has to be in a separate shared library. + add_library(inttest_cygwin SHARED framework/cygwin.cpp) + target_link_libraries(inttest_cygwin Qt${ZNC_QT_VER}::NetworkPrivate) + target_link_libraries(inttest inttest_cygwin) +endif() diff --git a/test/integration/framework/cygwin.cpp b/test/integration/framework/cygwin.cpp new file mode 100644 index 00000000..da4746de --- /dev/null +++ b/test/integration/framework/cygwin.cpp @@ -0,0 +1,99 @@ +// This file is LGPL, as it's based on Qt's qlocalsocket_unix.cpp +// The original header follows: + +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "cygwin.h" + +#include +#include +#include +#include + +namespace znc_inttest_cygwin { +// https://stackoverflow.com/questions/424104/can-i-access-private-members-from-outside-the-class-without-using-friends +template +struct Rob { + friend typename Tag::type get(Tag) { return M; } +}; +struct A_member { + using type = QScopedPointer QObject::*; + friend type get(A_member); +}; +template struct Rob; + +// This function is inspired by QLocalSocket::connectToServer() and QLocalSocketPrivate::_q_connectToSocket() +void CygwinWorkaroundLocalConnect(QLocalSocket& sock) { + QObjectData* o = (sock.*get(A_member())).get(); + QLocalSocketPrivate* d = reinterpret_cast(o); + d->unixSocket.setSocketState(QAbstractSocket::ConnectingState); + d->state = QLocalSocket::ConnectingState; + sock.stateChanged(d->state); + + if ((d->connectingSocket = qt_safe_socket(PF_UNIX, SOCK_STREAM, 0, 0)) == + -1) { + sock.errorOccurred(QLocalSocket::UnsupportedSocketOperationError); + return; + } + + d->fullServerName = d->serverName; + + const QByteArray encodedConnectingPathName = + QFile::encodeName(d->serverName); + struct sockaddr_un name; + name.sun_family = PF_UNIX; + ::memcpy(name.sun_path, encodedConnectingPathName.constData(), + encodedConnectingPathName.size() + 1); + if (qt_safe_connect(d->connectingSocket, (struct sockaddr*)&name, + sizeof(name)) == -1) { + sock.errorOccurred(QLocalSocket::UnknownSocketError); + return; + } + + ::fcntl(d->connectingSocket, F_SETFL, + ::fcntl(d->connectingSocket, F_GETFL) | O_NONBLOCK); + d->unixSocket.setSocketDescriptor(d->connectingSocket, + QAbstractSocket::ConnectedState); + sock.QIODevice::open(QLocalSocket::ReadWrite | QLocalSocket::Unbuffered); + sock.connected(); + d->connectingSocket = -1; + d->connectingName.clear(); +} +} // namespace znc_inttest_cygwin diff --git a/test/integration/framework/cygwin.h b/test/integration/framework/cygwin.h new file mode 100644 index 00000000..ff42e65b --- /dev/null +++ b/test/integration/framework/cygwin.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace znc_inttest_cygwin { +// Qt uses non-blocking sockets for unix sockets, but cygwin emulates them via +// AF_INET sockets, so connect() fails. This function connects the socket by +// reaching into private parts of QLocalSocket, and sets it non-blocking only +// after connect(). +void CygwinWorkaroundLocalConnect(QLocalSocket& sock); +} diff --git a/test/integration/framework/znctest.cpp b/test/integration/framework/znctest.cpp index ffc8d625..e52732fd 100644 --- a/test/integration/framework/znctest.cpp +++ b/test/integration/framework/znctest.cpp @@ -17,6 +17,7 @@ #include #include #include "znctest.h" +#include "cygwin.h" #ifndef ZNC_BIN_DIR #define ZNC_BIN_DIR "" @@ -76,7 +77,12 @@ Socket ZNCTest::ConnectIRCd() { Socket ZNCTest::ConnectClient() { m_clients.emplace_back(); QLocalSocket& sock = m_clients.back(); - sock.connectToServer(m_dir.path() + "/inttest.znc"); + sock.setServerName(m_dir.path() + "/inttest.znc"); +#ifdef __CYGWIN__ + znc_inttest_cygwin::CygwinWorkaroundLocalConnect(sock); +#else + sock.connectToServer(); +#endif [&] { ASSERT_TRUE(sock.waitForConnected()) << sock.errorString().toStdString(); diff --git a/test/integration/tests/modules.cpp b/test/integration/tests/modules.cpp index f000ecb5..5815c088 100644 --- a/test/integration/tests/modules.cpp +++ b/test/integration/tests/modules.cpp @@ -242,6 +242,8 @@ TEST_F(ZNCTest, KeepNickModule) { } TEST_F(ZNCTest, ModuleCSRFOverride) { + // TODO: Qt 6.8 introduced QNetworkRequest::FullLocalServerNameAttribute to + // let it connect to unix socket int port = PickPortNumber(); auto znc = Run(); auto ircd = ConnectIRCd();