From b642d92ce71e5cd416c006e099049f1ab795fb1b Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Mon, 21 Apr 2025 00:00:14 +0100 Subject: [PATCH] Switch integration test to mostly use unix sockets By not using the same hardcoded number for every test, we can parallelize the test now. There are several cases remaining where we can't easily use unix sockets (e.g. QSslSocket or imapauth module), for that ask kernel what port number is currently free to use. This is a bit racy though. --- src/znc.cpp | 94 +++++++++++++++----------- test/integration/framework/base.cpp | 11 ++- test/integration/framework/base.h | 11 ++- test/integration/framework/znctest.cpp | 23 ++++--- test/integration/framework/znctest.h | 11 +-- test/integration/tests/core.cpp | 36 ++++++++-- test/integration/tests/modules.cpp | 47 +++++++------ test/integration/tests/scripting.cpp | 4 +- 8 files changed, 152 insertions(+), 85 deletions(-) diff --git a/src/znc.cpp b/src/znc.cpp index a7dc3ebc..9dc9daea 100644 --- a/src/znc.cpp +++ b/src/znc.cpp @@ -625,49 +625,57 @@ bool CZNC::WriteNewConfig(const CString& sConfigFile) { unsigned int uListenPort = 0; bool bSuccess; - do { - bSuccess = true; - while (true) { - if (!CUtils::GetNumInput("Listen on port", uListenPort, 1025, - 65534)) { - continue; - } - if (uListenPort == 6667 || uListenPort == 6697) { - CUtils::PrintStatus(false, - "WARNING: Some web browsers reject ports " - "6667 and 6697. If you intend to"); - CUtils::PrintStatus(false, - "use ZNC's web interface, you might want " - "to use another port."); - if (!CUtils::GetBoolInput("Proceed anyway?", - true)) { + // Unix sockets are not exposed in --makeconf by default, but it's possible + // to trigger this using env var. This is mostly useful for the integration + // test. + char* szListenUnixSocket = getenv("ZNC_LISTEN_UNIX_SOCKET"); + if (!szListenUnixSocket) { + do { + bSuccess = true; + while (true) { + if (!CUtils::GetNumInput("Listen on port", uListenPort, 1025, + 65534)) { continue; } + if (uListenPort == 6667 || uListenPort == 6697) { + CUtils::PrintStatus( + false, + "WARNING: Some web browsers reject ports " + "6667 and 6697. If you intend to"); + CUtils::PrintStatus( + false, + "use ZNC's web interface, you might want " + "to use another port."); + if (!CUtils::GetBoolInput("Proceed anyway?", true)) { + continue; + } + } + break; } - break; - } #ifdef HAVE_LIBSSL - bListenSSL = CUtils::GetBoolInput("Listen using SSL", bListenSSL); + bListenSSL = CUtils::GetBoolInput("Listen using SSL", bListenSSL); #endif #ifdef HAVE_IPV6 - b6 = CUtils::GetBoolInput("Listen using both IPv4 and IPv6", b6); + b6 = CUtils::GetBoolInput("Listen using both IPv4 and IPv6", b6); #endif - // Don't ask for listen host, it may be configured later if needed. + // Don't ask for listen host, it may be configured later if needed. - CUtils::PrintAction("Verifying the listener"); - CListener* pListener = new CTCPListener( - (unsigned short int)uListenPort, sListenHost, sURIPrefix, - bListenSSL, b6 ? ADDR_ALL : ADDR_IPV4ONLY, CListener::ACCEPT_ALL); - if (!pListener->Listen()) { - CUtils::PrintStatus(false, FormatBindError()); - bSuccess = false; - } else - CUtils::PrintStatus(true); - delete pListener; - } while (!bSuccess); + CUtils::PrintAction("Verifying the listener"); + CListener* pListener = new CTCPListener( + (unsigned short int)uListenPort, sListenHost, sURIPrefix, + bListenSSL, b6 ? ADDR_ALL : ADDR_IPV4ONLY, + CListener::ACCEPT_ALL); + if (!pListener->Listen()) { + CUtils::PrintStatus(false, FormatBindError()); + bSuccess = false; + } else + CUtils::PrintStatus(true); + delete pListener; + } while (!bSuccess); + } #ifdef HAVE_LIBSSL CString sPemFile = GetPemLocation(); @@ -679,9 +687,13 @@ bool CZNC::WriteNewConfig(const CString& sConfigFile) { #endif vsLines.push_back(""); - vsLines.push_back("\tPort = " + CString(uListenPort)); - vsLines.push_back("\tIPv4 = true"); - vsLines.push_back("\tIPv6 = " + CString(b6)); + if (szListenUnixSocket) { + vsLines.push_back("\tPath = " + CString(szListenUnixSocket)); + } else { + vsLines.push_back("\tPort = " + CString(uListenPort)); + vsLines.push_back("\tIPv4 = true"); + vsLines.push_back("\tIPv6 = " + CString(b6)); + } vsLines.push_back("\tSSL = " + CString(bListenSSL)); if (!sListenHost.empty()) { vsLines.push_back("\tHost = " + sListenHost); @@ -788,12 +800,16 @@ bool CZNC::WriteNewConfig(const CString& sConfigFile) { bSSL = CUtils::GetBoolInput("Server uses SSL?", bSSL); #endif while (!CUtils::GetNumInput("Server port", uServerPort, 1, 65535, - bSSL ? 6697 : 6667)) - ; + bSSL ? 6697 : 6667)); CUtils::GetInput("Server password (probably empty)", sPass); - vsLines.push_back("\t\tServer = " + sHost + ((bSSL) ? " +" : " ") + - CString(uServerPort) + " " + sPass); + if (sHost.StartsWith("unix:")) { + vsLines.push_back("\t\tServer = " + sHost + " " + sPass); + } else { + vsLines.push_back("\t\tServer = " + sHost + + ((bSSL) ? " +" : " ") + CString(uServerPort) + + " " + sPass); + } CString sChans; if (CUtils::GetInput("Initial channels", sChans)) { diff --git a/test/integration/framework/base.cpp b/test/integration/framework/base.cpp index ee012143..1890564a 100644 --- a/test/integration/framework/base.cpp +++ b/test/integration/framework/base.cpp @@ -14,9 +14,12 @@ * limitations under the License. */ -#include #include "base.h" +#include + +#include + using testing::AnyOf; using testing::Eq; @@ -58,4 +61,10 @@ Process::~Process() { } } +int PickPortNumber() { + QTcpServer tcp; + tcp.listen(QHostAddress::LocalHost); + return tcp.serverPort(); +} + } // namespace znc_inttest diff --git a/test/integration/framework/base.h b/test/integration/framework/base.h index f3441c2e..71098fd7 100644 --- a/test/integration/framework/base.h +++ b/test/integration/framework/base.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -49,6 +50,7 @@ class IO { // Need to flush QTcpSocket, and QIODevice doesn't have flush at all... static void FlushIfCan(QIODevice*) {} static void FlushIfCan(QTcpSocket* sock) { sock->flush(); } + static void FlushIfCan(QLocalSocket* sock) { sock->flush(); } Device* m_device; bool m_verbose; @@ -60,7 +62,7 @@ IO WrapIO(Device* d) { return IO(d); } -using Socket = IO; +using Socket = IO; class Process : public IO { public: @@ -202,6 +204,9 @@ void IO::Write(QByteArray s, bool new_line) { FlushIfCan(m_device); } +inline void DisconnectFromServer(QTcpSocket* s) { s->disconnectFromHost(); } +inline void DisconnectFromServer(QLocalSocket* s) { s->disconnectFromServer(); } + template void IO::Close() { #ifdef __CYGWIN__ @@ -209,8 +214,10 @@ void IO::Close() { // without this line sleep(1); #endif - m_device->disconnectFromHost(); + DisconnectFromServer(m_device); } +int PickPortNumber(); + } // namespace znc_inttest diff --git a/test/integration/framework/znctest.cpp b/test/integration/framework/znctest.cpp index 77628c3e..ffc8d625 100644 --- a/test/integration/framework/znctest.cpp +++ b/test/integration/framework/znctest.cpp @@ -15,6 +15,7 @@ */ #include +#include #include "znctest.h" #ifndef ZNC_BIN_DIR @@ -24,13 +25,15 @@ namespace znc_inttest { void WriteConfig(QString path) { + Process p(ZNC_BIN_DIR "/znc", + QStringList() << "--debug" << "--datadir" << path << "--makeconf", + [=](QProcess* p) { + auto env = p->processEnvironment(); + env.insert("ZNC_LISTEN_UNIX_SOCKET", + path + "/inttest.znc"); + p->setProcessEnvironment(env); + }); // clang-format off - Process p(ZNC_BIN_DIR "/znc", QStringList() << "--debug" - << "--datadir" << path - << "--makeconf"); - p.ReadUntil("Listen on port"); p.Write("12345"); - p.ReadUntil("Listen using SSL"); p.Write(); - p.ReadUntil("IPv6"); p.Write(); p.ReadUntil("Username"); p.Write("user"); p.ReadUntil("password"); p.Write("hunter2", false); p.ReadUntil("Confirm"); p.Write("hunter2", false); @@ -41,7 +44,7 @@ void WriteConfig(QString path) { p.ReadUntil("Bind host"); p.Write(); p.ReadUntil("Set up a network?"); p.Write(); p.ReadUntil("Name [libera]"); p.Write("test"); - p.ReadUntil("Server host (host only)"); p.Write("127.0.0.1"); + p.ReadUntil("Server host (host only)"); p.Write("unix:" + path.toUtf8() + "/inttest.ircd"); p.ReadUntil("Server uses SSL?"); p.Write(); p.ReadUntil("6667"); p.Write(); p.ReadUntil("password"); p.Write(); @@ -61,7 +64,7 @@ void WriteConfig(QString path) { void ZNCTest::SetUp() { WriteConfig(m_dir.path()); - ASSERT_TRUE(m_server.listen(QHostAddress::LocalHost, 6667)) + ASSERT_TRUE(m_server.listen(m_dir.path() + "/inttest.ircd")) << m_server.errorString().toStdString(); } @@ -72,8 +75,8 @@ Socket ZNCTest::ConnectIRCd() { Socket ZNCTest::ConnectClient() { m_clients.emplace_back(); - QTcpSocket& sock = m_clients.back(); - sock.connectToHost("127.0.0.1", 12345); + QLocalSocket& sock = m_clients.back(); + sock.connectToServer(m_dir.path() + "/inttest.znc"); [&] { ASSERT_TRUE(sock.waitForConnected()) << sock.errorString().toStdString(); diff --git a/test/integration/framework/znctest.h b/test/integration/framework/znctest.h index ba12d369..36352c09 100644 --- a/test/integration/framework/znctest.h +++ b/test/integration/framework/znctest.h @@ -16,17 +16,18 @@ #pragma once -#include "base.h" +#include #include -#include #include -#include +#include #include #include #include #include #include +#include "base.h" + namespace znc_inttest { void WriteConfig(QString path); @@ -52,8 +53,8 @@ class ZNCTest : public testing::Test { App m_app; QNetworkAccessManager m_network; QTemporaryDir m_dir; - QTcpServer m_server; - std::list m_clients; + QLocalServer m_server; + std::list m_clients; }; } // namespace znc_inttest diff --git a/test/integration/tests/core.cpp b/test/integration/tests/core.cpp index f1a0768e..9f056b97 100644 --- a/test/integration/tests/core.cpp +++ b/test/integration/tests/core.cpp @@ -90,7 +90,14 @@ TEST_F(ZNCTest, Channel) { TEST_F(ZNCTest, HTTP) { auto znc = Run(); auto ircd = ConnectIRCd(); - auto reply = HttpGet(QNetworkRequest(QUrl("http://127.0.0.1:12345/"))); + + auto client = LoginClient(); + int port = PickPortNumber(); + client.Write(QStringLiteral("znc addport %1 all all").arg(port).toUtf8()); + client.ReadUntil(":Port added"); + + auto reply = HttpGet(QNetworkRequest( + QUrl(QStringLiteral("http://127.0.0.1:%1/").arg(port)))); EXPECT_THAT(reply->rawHeader("Server").toStdString(), HasSubstr("ZNC")); } @@ -102,10 +109,17 @@ TEST_F(ZNCTest, FixCVE20149403) { ircd.Write(":server PING :1"); ircd.ReadUntil("PONG 1"); + auto client = LoginClient(); + int port = PickPortNumber(); + client.Write(QStringLiteral("znc addport %1 all all").arg(port).toUtf8()); + client.ReadUntil(":Port added"); + QNetworkRequest request; request.setRawHeader("Authorization", "Basic " + QByteArray("user:hunter2").toBase64()); - request.setUrl(QUrl("http://127.0.0.1:12345/mods/global/webadmin/addchan")); + request.setUrl( + QUrl(QStringLiteral("http://127.0.0.1:%1/mods/global/webadmin/addchan") + .arg(port))); HttpPost(request, { {"user", "user"}, {"network", "test"}, @@ -136,10 +150,18 @@ TEST_F(ZNCTest, FixFixOfCVE20149403) { ircd.Write(":server PING :12345"); ircd.ReadUntil("PONG 12345"); + auto client = LoginClient(); + int port = PickPortNumber(); + client.Write(QStringLiteral("znc addport %1 all all").arg(port).toUtf8()); + client.ReadUntil(":Port added"); + QNetworkRequest request; request.setRawHeader("Authorization", "Basic " + QByteArray("user:hunter2").toBase64()); - request.setUrl(QUrl("http://127.0.0.1:12345/mods/global/webadmin/addchan")); + request.setUrl( + QUrl(QStringLiteral("http://127.0.0.1:%1/mods/global/webadmin/addchan") + .arg(port) + .toUtf8())); auto reply = HttpPost(request, { {"user", "user"}, {"network", "test"}, @@ -968,13 +990,13 @@ TEST_F(ZNCTest, SpacedServerPassword) { auto znc = Run(); auto ircd = ConnectIRCd(); auto client = LoginClient(); - client.Write("znc delserver 127.0.0.1"); - client.Write("znc addserver 127.0.0.1 6667 a b"); + client.Write(("znc delserver unix:" + m_dir.path() + "/inttest.ircd").toUtf8()); + client.Write(("znc addserver unix:" + m_dir.path() + "/inttest.ircd a b").toUtf8()); client.Write("znc jump"); auto ircd2 = ConnectIRCd(); ircd2.ReadUntil("PASS :a b"); - client.Write("znc delserver 127.0.0.1"); - client.Write("znc addserver 127.0.0.1 6667 a"); + client.Write(("znc delserver unix:" + m_dir.path() + "/inttest.ircd").toUtf8()); + client.Write(("znc addserver unix:" + m_dir.path() + "/inttest.ircd a").toUtf8()); client.Write("znc jump"); auto ircd3 = ConnectIRCd(); // No : diff --git a/test/integration/tests/modules.cpp b/test/integration/tests/modules.cpp index 3b01147b..f000ecb5 100644 --- a/test/integration/tests/modules.cpp +++ b/test/integration/tests/modules.cpp @@ -20,6 +20,7 @@ #include "znctest.h" #include +#include using testing::HasSubstr; using testing::Not; @@ -38,23 +39,23 @@ TEST_F(ZNCTest, NotifyConnectModule) { client2.Write("PASS :hunter2"); client2.Write("NICK nick"); client2.Write("USER user/test x x :x"); - client.ReadUntil("NOTICE nick :*** user attached from 127.0.0.1"); + client.ReadUntil("NOTICE nick :*** user attached from localhost"); auto client3 = ConnectClient(); client3.Write("PASS :hunter2"); client3.Write("NICK nick"); client3.Write("USER user@identifier/test x x :x"); client.ReadUntil( - "NOTICE nick :*** user@identifier attached from 127.0.0.1"); + "NOTICE nick :*** user@identifier attached from localhost"); client2.ReadUntil( - "NOTICE nick :*** user@identifier attached from 127.0.0.1"); + "NOTICE nick :*** user@identifier attached from localhost"); client2.Write("QUIT"); - client.ReadUntil("NOTICE nick :*** user detached from 127.0.0.1"); + client.ReadUntil("NOTICE nick :*** user detached from localhost"); client3.Close(); client.ReadUntil( - "NOTICE nick :*** user@identifier detached from 127.0.0.1"); + "NOTICE nick :*** user@identifier detached from localhost"); } TEST_F(ZNCTest, ClientNotifyModule) { @@ -70,35 +71,35 @@ TEST_F(ZNCTest, ClientNotifyModule) { }; auto client2 = LoginClient(); - client.ReadUntil(":Another client (127.0.0.1) authenticated as your user. Use the 'ListClients' command to see all 2 clients."); + client.ReadUntil(":Another client (localhost) authenticated as your user. Use the 'ListClients' command to see all 2 clients."); auto client3 = LoginClient(); - client.ReadUntil(":Another client (127.0.0.1) authenticated as your user. Use the 'ListClients' command to see all 3 clients."); + client.ReadUntil(":Another client (localhost) authenticated as your user. Use the 'ListClients' command to see all 3 clients."); // disable notifications for every message client.Write("PRIVMSG *clientnotify :NewOnly on"); // check that we do not ge a notification after connecting from a know ip auto client4 = LoginClient(); - check_not_sent(client, ":Another client (127.0.0.1) authenticated as your user. Use the 'ListClients' command to see all 4 clients."); + check_not_sent(client, ":Another client (localhost) authenticated as your user. Use the 'ListClients' command to see all 4 clients."); // choose to notify only on new client ids client.Write("PRIVMSG *clientnotify :NotifyOnNewID on"); auto client5 = LoginClient("identifier123"); - client.ReadUntil(":Another client (127.0.0.1 / identifier123) authenticated as your user. Use the 'ListClients' command to see all 5 clients."); + client.ReadUntil(":Another client (localhost / identifier123) authenticated as your user. Use the 'ListClients' command to see all 5 clients."); auto client6 = LoginClient("identifier123"); - check_not_sent(client, ":Another client (127.0.0.1 / identifier123) authenticated as your user. Use the 'ListClients' command to see all 6 clients."); + check_not_sent(client, ":Another client (localhost / identifier123) authenticated as your user. Use the 'ListClients' command to see all 6 clients."); auto client7 = LoginClient("not_identifier123"); - client.ReadUntil(":Another client (127.0.0.1 / not_identifier123) authenticated as your user. Use the 'ListClients' command to see all 7 clients."); + client.ReadUntil(":Another client (localhost / not_identifier123) authenticated as your user. Use the 'ListClients' command to see all 7 clients."); // choose to notify from both clientids and new IPs client.Write("PRIVMSG *clientnotify :NotifyOnNewIP on"); auto client8 = LoginClient(); - check_not_sent(client, ":Another client (127.0.0.1 / identifier123) authenticated as your user. Use the 'ListClients' command to see all 8 clients."); + check_not_sent(client, ":Another client (localhost / identifier123) authenticated as your user. Use the 'ListClients' command to see all 8 clients."); auto client9 = LoginClient("definitely_not_identifier123"); - client.ReadUntil(":Another client (127.0.0.1 / definitely_not_identifier123) authenticated as your user. Use the 'ListClients' command to see all 9 clients."); + client.ReadUntil(":Another client (localhost / definitely_not_identifier123) authenticated as your user. Use the 'ListClients' command to see all 9 clients."); } TEST_F(ZNCTest, ShellModule) { @@ -241,13 +242,16 @@ TEST_F(ZNCTest, KeepNickModule) { } TEST_F(ZNCTest, ModuleCSRFOverride) { + int port = PickPortNumber(); auto znc = Run(); auto ircd = ConnectIRCd(); auto client = LoginClient(); + client.Write(QStringLiteral("znc addport %1 all all").arg(port).toUtf8()); client.Write("znc loadmod samplewebapi"); client.ReadUntil("Loaded module"); auto request = QNetworkRequest( - QUrl("http://127.0.0.1:12345/mods/global/samplewebapi/")); + QUrl(QStringLiteral("http://127.0.0.1:%1/mods/global/samplewebapi/") + .arg(port))); auto reply = HttpPost(request, {{"text", "ipsum"}})->readAll().toStdString(); EXPECT_THAT(reply, HasSubstr("ipsum")); @@ -352,9 +356,12 @@ TEST_F(ZNCTest, SaslAuthPlainImapAuth) { auto znc = Run(); auto ircd = ConnectIRCd(); QTcpServer imap; - ASSERT_TRUE(imap.listen(QHostAddress::LocalHost, 12346)) << imap.errorString().toStdString(); + ASSERT_TRUE(imap.listen(QHostAddress::LocalHost)) << imap.errorString().toStdString(); auto client = LoginClient(); - client.Write("znc loadmod imapauth 127.0.0.1 12346 %@mail.test.com"); + client.Write( + QStringLiteral("znc loadmod imapauth 127.0.0.1 %1 %@mail.test.com") + .arg(imap.serverPort()) + .toUtf8()); client.ReadUntil("Loaded"); auto client2 = ConnectClient(); @@ -375,11 +382,13 @@ TEST_F(ZNCTest, SaslAuthPlainImapAuth) { } TEST_F(ZNCTest, SaslAuthExternal) { + int port = PickPortNumber(); + auto znc = Run(); auto ircd = ConnectIRCd(); ircd.Write(":server 001 nick :Hello"); auto client = LoginClient(); - client.Write("znc addport +12346 all all"); + client.Write(QStringLiteral("znc addport +%1 all all").arg(port).toUtf8()); client.ReadUntil(":Port added"); client.Write("znc loadmod certauth"); client.ReadUntil("Loaded"); @@ -390,7 +399,7 @@ TEST_F(ZNCTest, SaslAuthExternal) { sock.setLocalCertificate(m_dir.path() + "/znc.pem"); sock.setPrivateKey(m_dir.path() + "/znc.pem"); sock.setPeerVerifyMode(QSslSocket::VerifyNone); - sock.connectToHostEncrypted("127.0.0.1", 12346); + sock.connectToHostEncrypted("127.0.0.1", port); ASSERT_TRUE(sock.waitForConnected()) << sock.errorString().toStdString(); ASSERT_TRUE(sock.waitForEncrypted()) << sock.errorString().toStdString(); auto client2 = WrapIO(&sock); @@ -404,7 +413,7 @@ TEST_F(ZNCTest, SaslAuthExternal) { client2.Close(); ASSERT_TRUE(sock.state() == QAbstractSocket::UnconnectedState || sock.waitForDisconnected()) << sock.errorString().toStdString(); - sock.connectToHostEncrypted("127.0.0.1", 12346); + sock.connectToHostEncrypted("127.0.0.1", port); ASSERT_TRUE(sock.waitForConnected()) << sock.errorString().toStdString(); ASSERT_TRUE(sock.waitForEncrypted()) diff --git a/test/integration/tests/scripting.cpp b/test/integration/tests/scripting.cpp index afe75b91..fbba2f3d 100644 --- a/test/integration/tests/scripting.cpp +++ b/test/integration/tests/scripting.cpp @@ -491,7 +491,7 @@ TEST_F(ZNCTest, ModpythonSaslAuth) { client2.ReadUntil("AUTHENTICATE " + QByteArrayLiteral("Welcome").toBase64()); client2.Write("AUTHENTICATE +"); client2.ReadUntil( - ":irc.znc.in 900 nick nick!user@127.0.0.1 user :You are now logged in " + ":irc.znc.in 900 nick nick!user@localhost user :You are now logged in " "as user"); } @@ -548,7 +548,7 @@ TEST_F(ZNCTest, ModperlSaslAuth) { client2.ReadUntil("AUTHENTICATE " + QByteArrayLiteral("Welcome").toBase64()); client2.Write("AUTHENTICATE +"); client2.ReadUntil( - ":irc.znc.in 900 nick nick!user@127.0.0.1 user :You are now logged in " + ":irc.znc.in 900 nick nick!user@localhost user :You are now logged in " "as user"); }