Files
znc/test/integration/tests/scripting.cpp
Alexey Sokolov b642d92ce7 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.
2025-04-21 00:19:46 +01:00

557 lines
16 KiB
C++

/*
* Copyright (C) 2004-2025 ZNC, see the NOTICE file for details.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "znctest.h"
#include "znctestconfig.h"
namespace znc_inttest {
namespace {
TEST_F(ZNCTest, Modperl) {
#ifndef WANT_PERL
GTEST_SKIP() << "Modperl is disabled";
#endif
auto znc = Run();
znc->CanLeak();
auto ircd = ConnectIRCd();
auto client = LoginClient();
client.Write("znc loadmod modperl");
client.Write("znc loadmod perleval");
client.Write("PRIVMSG *perleval :2+2");
client.ReadUntil(":*perleval!perleval@znc.in PRIVMSG nick :Result: 4");
client.Write("PRIVMSG *perleval :$self->GetUser->GetUsername");
client.ReadUntil("Result: user");
}
TEST_F(ZNCTest, Modpython) {
#ifndef WANT_PYTHON
GTEST_SKIP() << "Modpython is disabled";
#endif
auto znc = Run();
znc->CanLeak();
auto ircd = ConnectIRCd();
auto client = LoginClient();
client.Write("znc loadmod modpython");
client.Write("znc loadmod pyeval");
client.Write("PRIVMSG *pyeval :2+2");
client.ReadUntil(":*pyeval!pyeval@znc.in PRIVMSG nick :4");
client.Write("PRIVMSG *pyeval :module.GetUser().GetUsername()");
client.ReadUntil("nick :'user'");
ircd.Write(":server 001 nick :Hello");
ircd.Write(":n!u@h PRIVMSG nick :Hi\xF0, github issue #1229");
// "replacement character"
client.ReadUntil("Hi\xEF\xBF\xBD, github issue");
// Non-existing encoding
client.Write("PRIVMSG *controlpanel :Set ClientEncoding $me Western");
client.Write("JOIN #a\342");
client.ReadUntil(
":*controlpanel!controlpanel@znc.in PRIVMSG nick :ClientEncoding = UTF-8");
ircd.ReadUntil("JOIN #a\xEF\xBF\xBD");
}
TEST_F(ZNCTest, ModpythonSocket) {
#ifndef WANT_PYTHON
GTEST_SKIP() << "Modpython is disabled";
#endif
auto znc = Run();
znc->CanLeak();
InstallModule("socktest.py", R"(
import znc
class acc(znc.Socket):
def OnReadData(self, data):
self.GetModule().PutModule('received {} bytes'.format(len(data)))
self.Close()
class lis(znc.Socket):
def OnAccepted(self, host, port):
sock = self.GetModule().CreateSocket(acc)
sock.DisableReadLine()
return sock
class socktest(znc.Module):
def OnLoad(self, args, ret):
listen = self.CreateSocket(lis)
self.port = listen.Listen()
return True
def OnModCommand(self, cmd):
sock = self.CreateSocket()
sock.Connect('127.0.0.1', self.port)
sock.WriteBytes(b'blah')
)");
auto ircd = ConnectIRCd();
auto client = LoginClient();
client.Write("znc loadmod modpython");
client.Write("znc loadmod socktest");
client.Write("PRIVMSG *socktest :foo");
client.ReadUntil("received 4 bytes");
}
TEST_F(ZNCTest, ModperlSocket) {
#ifndef WANT_PERL
GTEST_SKIP() << "Modperl is disabled";
#endif
auto znc = Run();
znc->CanLeak();
InstallModule("socktest.pm", R"(
package socktest::acc;
use base 'ZNC::Socket';
sub OnReadData {
my ($self, $data, $len) = @_;
$self->GetModule->PutModule("received $len bytes");
$self->Close;
}
package socktest::lis;
use base 'ZNC::Socket';
sub OnAccepted {
my $self = shift;
return $self->GetModule->CreateSocket('socktest::acc');
}
package socktest::conn;
use base 'ZNC::Socket';
package socktest;
use base 'ZNC::Module';
sub OnLoad {
my $self = shift;
my $listen = $self->CreateSocket('socktest::lis');
$self->{port} = $listen->Listen;
return 1;
}
sub OnModCommand {
my ($self, $cmd) = @_;
my $sock = $self->CreateSocket('socktest::conn');
$sock->Connect('127.0.0.1', $self->{port});
$sock->Write('blah');
}
1;
)");
auto ircd = ConnectIRCd();
auto client = LoginClient();
client.Write("znc loadmod modperl");
client.Write("znc loadmod socktest");
client.Write("PRIVMSG *socktest :foo");
client.ReadUntil("received 4 bytes");
}
TEST_F(ZNCTest, ModpythonUnixSocket) {
#ifndef WANT_PYTHON
GTEST_SKIP() << "Modpython is disabled";
#endif
auto znc = Run();
znc->CanLeak();
InstallModule("socktest.py", R"(
import znc
class acc(znc.Socket):
def OnReadData(self, data):
self.GetModule().PutModule('received {} bytes'.format(len(data)))
self.Close()
class lis(znc.Socket):
def OnAccepted(self, host, port):
sock = self.GetModule().CreateSocket(acc)
sock.DisableReadLine()
return sock
class socktest(znc.Module):
def OnLoad(self, args, ret):
listen = self.CreateSocket(lis)
return listen.Listen(addrtype='unix', path=self.GetSavePath() + "/sock")
def OnModCommand(self, cmd):
sock = self.CreateSocket()
sock.ConnectUnix(self.GetSavePath() + "/sock")
sock.WriteBytes(b'blah')
)");
auto ircd = ConnectIRCd();
auto client = LoginClient();
client.Write("znc loadmod modpython");
client.Write("znc loadmod socktest");
client.Write("PRIVMSG *socktest :foo");
client.ReadUntil("received 4 bytes");
}
TEST_F(ZNCTest, ModperlUnixSocket) {
#ifndef WANT_PERL
GTEST_SKIP() << "Modperl is disabled";
#endif
auto znc = Run();
znc->CanLeak();
InstallModule("socktest.pm", R"(
package socktest::acc;
use base 'ZNC::Socket';
sub OnReadData {
my ($self, $data, $len) = @_;
$self->GetModule->PutModule("received $len bytes");
$self->Close;
}
package socktest::lis;
use base 'ZNC::Socket';
sub OnAccepted {
my $self = shift;
return $self->GetModule->CreateSocket('socktest::acc');
}
package socktest::conn;
use base 'ZNC::Socket';
package socktest;
use base 'ZNC::Module';
sub OnLoad {
my $self = shift;
my $listen = $self->CreateSocket('socktest::lis');
$listen->Listen(addrtype=>'unix', path=>$self->GetSavePath . "/sock");
}
sub OnModCommand {
my ($self, $cmd) = @_;
my $sock = $self->CreateSocket('socktest::conn');
$sock->ConnectUnix($self->GetSavePath . "/sock");
$sock->Write('blah');
}
1;
)");
auto ircd = ConnectIRCd();
auto client = LoginClient();
client.Write("znc loadmod modperl");
client.Write("znc loadmod socktest");
client.Write("PRIVMSG *socktest :foo");
client.ReadUntil("received 4 bytes");
}
TEST_F(ZNCTest, ModpythonVCString) {
#ifndef WANT_PYTHON
GTEST_SKIP() << "Modpython is disabled";
#endif
auto znc = Run();
znc->CanLeak();
InstallModule("test.py", R"(
import znc
class test(znc.Module):
def OnUserRawMessage(self, msg):
self.PutModule(str(msg.GetParams()))
return znc.CONTINUE
)");
auto ircd = ConnectIRCd();
auto client = LoginClient();
client.Write("znc loadmod modpython");
client.Write("znc loadmod test");
client.Write("PRIVMSG *test :foo");
client.ReadUntil("'*test', 'foo'");
}
TEST_F(ZNCTest, ModperlVCString) {
#ifndef WANT_PERL
GTEST_SKIP() << "Modperl is disabled";
#endif
auto znc = Run();
znc->CanLeak();
InstallModule("test.pm", R"(
package test;
use base 'ZNC::Module';
sub OnUserRawMessage {
my ($self, $msg) = @_;
my @params = $msg->GetParams;
$self->PutModule("@params");
return $ZNC::CONTINUE;
}
1;
)");
auto ircd = ConnectIRCd();
auto client = LoginClient();
client.Write("znc loadmod modperl");
client.Write("znc loadmod test");
client.Write("PRIVMSG *test :foo");
client.ReadUntil(":*test foo");
}
TEST_F(ZNCTest, ModperlNV) {
#ifndef WANT_PERL
GTEST_SKIP() << "Modperl is disabled";
#endif
auto znc = Run();
znc->CanLeak();
InstallModule("test.pm", R"(
package test;
use base 'ZNC::Module';
sub OnLoad {
my $self = shift;
$self->SetNV('a', 'X');
$self->NV->{b} = 'Y';
my @k = keys %{$self->NV};
$self->PutModule("@k");
return $ZNC::CONTINUE;
}
1;
)");
auto ircd = ConnectIRCd();
auto client = LoginClient();
client.Write("znc loadmod modperl");
client.Write("znc loadmod test");
client.ReadUntil(":a b");
}
TEST_F(ZNCTest, ModpythonPackage) {
#ifndef WANT_PYTHON
GTEST_SKIP() << "Modpython is disabled";
#endif
auto znc = Run();
znc->CanLeak();
QDir dir(m_dir.path());
ASSERT_TRUE(dir.mkpath("modules"));
ASSERT_TRUE(dir.cd("modules"));
ASSERT_TRUE(dir.mkpath("packagetest"));
InstallModule("packagetest/__init__.py", R"(
import znc
from .data import value
class packagetest(znc.Module):
def OnModCommand(self, cmd):
self.PutModule('value = ' + value)
)");
InstallModule("packagetest/data.py", "value = 'a'");
auto ircd = ConnectIRCd();
auto client = LoginClient();
client.Write("znc loadmod modpython");
client.Write("znc loadmod packagetest");
client.Write("PRIVMSG *packagetest :foo");
client.ReadUntil("value = a");
InstallModule("packagetest/data.py", "value = 'b'");
client.Write("PRIVMSG *packagetest :foo");
client.ReadUntil("value = a");
client.Write("znc updatemod packagetest");
client.Write("PRIVMSG *packagetest :foo");
client.ReadUntil("value = b");
// Test if python modules are viewable via *status.
// https://github.com/znc/znc/issues/1884
client.Write("znc listavailmods");
client.ReadUntil(":*status!status@znc.in PRIVMSG nick :\x02 packagetest");
client.ReadUntil(":*status!status@znc.in PRIVMSG nick :\x02 pyeval\x0F: Evaluates python code");
}
TEST_F(ZNCTest, ModpythonModperl) {
#ifndef WANT_PYTHON
GTEST_SKIP() << "Modpython is disabled";
#endif
#ifndef WANT_PERL
GTEST_SKIP() << "Modperl is disabled";
#endif
auto znc = Run();
znc->CanLeak();
auto ircd = ConnectIRCd();
auto client = LoginClient();
// https://github.com/znc/znc/issues/1757
client.Write("znc loadmod modpython");
client.ReadUntil("Loaded module modpython");
client.Write("znc loadmod modperl");
client.ReadUntil("Loaded module modperl");
}
TEST_F(ZNCTest, ModpythonCommand) {
#ifndef WANT_PYTHON
GTEST_SKIP() << "Modpython is disabled";
#endif
auto znc = Run();
znc->CanLeak();
InstallModule("cmdtest.py", R"(
import znc
class cmdtest(znc.Module):
def OnLoad(self, args, message):
self.AddHelpCommand()
self.AddCommand(testcmd)
return True
class testcmd(znc.Command):
command = 'ping'
args = cmdtest.t_d('ar')
description = cmdtest.t_d('blah')
def __call__(self, line):
self.GetModule().PutModule(line + cmdtest.t_s(' pong'))
)");
auto ircd = ConnectIRCd();
auto client = LoginClient();
client.Write("znc loadmod modpython");
client.Write("znc loadmod cmdtest");
client.Write("PRIVMSG *cmdtest :ping or");
client.ReadUntil(":*cmdtest!cmdtest@znc.in PRIVMSG nick :ping or pong");
InstallTranslation("cmdtest", "ru_RU", R"(
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 "
"&& n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 "
"&& n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
"Language: ru_RU\n"
msgid "ar"
msgstr "аргумент"
msgid "blah"
msgstr "бла"
msgid " pong"
msgstr " понг"
)");
client.Write("PRIVMSG *controlpanel :set language $me ru-RU");
client.Write("PRIVMSG *cmdtest :help");
client.ReadUntil(":*cmdtest!cmdtest@znc.in PRIVMSG nick :\x02ping аргумент\x0F: бла");
client.Write("PRIVMSG *cmdtest :ping");
client.ReadUntil(":*cmdtest!cmdtest@znc.in PRIVMSG nick :ping понг");
}
TEST_F(ZNCTest, ModpythonSaslAuth) {
#ifndef WANT_PYTHON
GTEST_SKIP() << "Modpython is disabled";
#endif
auto znc = Run();
znc->CanLeak();
InstallModule("sasltest.py", R"(
import znc
class sasltest(znc.Module):
module_types = [znc.CModInfo.GlobalModule]
def OnClientGetSASLMechanisms(self, ssMechanisms):
ssMechanisms.insert("FOO")
def OnClientSASLServerInitialChallenge(self, sMechanism, sResponse):
if sMechanism == "FOO":
sResponse.s = "Welcome"
return znc.CONTINUE
def OnClientSASLAuthenticate(self, sMechanism, sMessage):
if sMechanism == "FOO":
user = znc.CZNC.Get().FindUser("user")
self.GetClient().AcceptSASLLogin(user)
return znc.HALT
return znc.CONTINUE
)");
auto ircd = ConnectIRCd();
auto client = LoginClient();
client.Write("znc loadmod modpython");
client.Write("znc loadmod sasltest");
client.ReadUntil("Loaded");
auto client2 = ConnectClient();
client2.Write("CAP LS 302");
client2.Write("NICK nick");
client2.ReadUntil(" sasl=FOO,PLAIN ");
client2.Write("CAP REQ :sasl");
client2.Write("AUTHENTICATE FOO");
client2.ReadUntil("AUTHENTICATE " + QByteArrayLiteral("Welcome").toBase64());
client2.Write("AUTHENTICATE +");
client2.ReadUntil(
":irc.znc.in 900 nick nick!user@localhost user :You are now logged in "
"as user");
}
TEST_F(ZNCTest, ModperlSaslAuth) {
#ifndef WANT_PERL
GTEST_SKIP() << "Modperl is disabled";
#endif
auto znc = Run();
znc->CanLeak();
InstallModule("sasltest.pm", R"(
package sasltest;
use base 'ZNC::Module';
sub module_types { $ZNC::CModInfo::GlobalModule }
sub OnClientGetSASLMechanisms {
my $self = shift;
my $mechs = shift;
$mechs->insert('FOO');
}
sub OnClientSASLServerInitialChallenge {
if ($_[1] eq "FOO") {
$_[2] = "Welcome";
}
return $ZNC::CONTINUE;
}
sub OnClientSASLAuthenticate {
my $self = $_[0];
if ($_[1] eq "FOO") {
my $user = ZNC::CZNC::Get()->FindUser("user");
$self->GetClient->AcceptSASLLogin($user);
return $ZNC::HALT;
}
return $ZNC::CONTINUE;
}
1;
)");
auto ircd = ConnectIRCd();
auto client = LoginClient();
client.Write("znc loadmod modperl");
client.Write("znc loadmod sasltest");
client.ReadUntil("Loaded");
auto client2 = ConnectClient();
client2.Write("CAP LS 302");
client2.Write("NICK nick");
client2.ReadUntil(" sasl=FOO,PLAIN ");
client2.Write("CAP REQ :sasl");
client2.Write("AUTHENTICATE FOO");
client2.ReadUntil("AUTHENTICATE " + QByteArrayLiteral("Welcome").toBase64());
client2.Write("AUTHENTICATE +");
client2.ReadUntil(
":irc.znc.in 900 nick nick!user@localhost user :You are now logged in "
"as user");
}
} // namespace
} // namespace znc_inttest