Make all tests pass with Unreal (minus service tests)

This commit is contained in:
Valentin Lorentz 2021-07-02 21:41:35 +02:00
parent 2d2e788275
commit f83f2a4edf
12 changed files with 139 additions and 44 deletions

View File

@ -63,16 +63,22 @@ SOPEL_SELECTORS := \
# testNoticeNonexistentChannel fails: https://bugs.unrealircd.org/view.php?id=5949
# test_regressions::testTagCap fails: https://bugs.unrealircd.org/view.php?id=5948
# test_messages::testLineTooLong fails: https://bugs.unrealircd.org/view.php?id=5947
UNREAL_SELECTORS := \
# testCapRemovalByClient and testNakWhole fail pending https://github.com/unrealircd/unrealircd/pull/148
# Tests marked with arbitrary_client_tags can't pass because Unreal whitelists tags it relays
UNREALIRCD_SELECTORS := \
not Ergo \
and not deprecated \
and not strict \
and not testNoticeNonexistentChannel \
and not (test_regressions and testTagCap) \
and not (test_messages and testLineTooLong) \
and not (test_cap and (testCapRemovalByClient or testNakWhole)) \
and not arbitrary_client_tags \
$(EXTRA_SELECTORS)
.PHONY: all flakes ergo charybdis
.PHONY: all flakes charybdis ergo inspircd mammon limnoria sopel solanum unrealircd
all: flakes ergo inspircd limnoria sopel solanum
all: flakes charybdis ergo inspircd mammon limnoria sopel solanum unrealircd
flakes:
pyflakes3 irctest
@ -97,3 +103,6 @@ solanum:
sopel:
$(PYTEST) $(PYTEST_ARGS) --controller=irctest.controllers.sopel -k '$(SOPEL_SELECTORS)'
unrealircd:
$(PYTEST) $(PYTEST_ARGS) --controller=irctest.controllers.unrealircd -k '$(UNREALIRCD_SELECTORS)'

View File

@ -183,6 +183,8 @@ class BaseServerController(_BaseController):
port: int
hostname: str
services_controller: BaseServicesController
extban_mute_char: Optional[str] = None
"""Character used for the 'mute' extban"""
def run(
self,

View File

@ -73,6 +73,7 @@ class CharybdisController(BaseServerController, DirectoryBasedController):
binary_name = "charybdis"
supported_sasl_mechanisms = {"PLAIN"}
supports_sts = False
extban_mute_char = None
def create_config(self) -> None:
super().create_config()

View File

@ -68,6 +68,7 @@ class InspircdController(BaseServerController, DirectoryBasedController):
software_name = "InspIRCd"
supported_sasl_mechanisms = {"PLAIN"}
supports_sts = False
extban_mute_char = "m"
def create_config(self) -> None:
super().create_config()

View File

@ -78,14 +78,17 @@ set {{
default-server "irc.example.org";
help-channel "#Help";
cloak-keys {{ "aaaA1"; "bbbB2"; "cccC3"; }}
anti-flood {{
// Prevent throttling, especially test_buffering.py which
// triggers anti-flood with its very long lines
unknown-users {{
lag-penalty 1;
lag-penalty-bytes 10000;
}}
options {{
identd-check; // Disable it, so it doesn't prefix idents with a tilde
}}
anti-flood {{
// Prevent throttling, especially test_buffering.py which
// triggers anti-flood with its very long lines
unknown-users {{
lag-penalty 1;
lag-penalty-bytes 10000;
}}
}}
}}
tld {{
@ -102,6 +105,8 @@ class UnrealircdController(BaseServerController, DirectoryBasedController):
supported_sasl_mechanisms = {"PLAIN"}
supports_sts = False
extban_mute_char = "q"
def create_config(self) -> None:
super().create_config()
with self.open_file("server.conf"):
@ -170,7 +175,7 @@ class UnrealircdController(BaseServerController, DirectoryBasedController):
)
if run_services:
assert False
raise NotImplementedByController("Registration services")
def get_irctest_controller_class() -> Type[UnrealircdController]:

View File

@ -54,6 +54,15 @@ ANYDICT = {RemainingKeys(ANYSTR): AnyOptStr()}
`match_dict(got_tags, {"label": "foo", **ANYDICT})`"""
class _AnyListRemainder:
def __repr__(self) -> str:
return "*ANYLIST"
ANYLIST = [_AnyListRemainder()]
"""Matches any list remainder"""
def match_string(got: Optional[str], expected: Union[str, Operator, None]) -> bool:
if isinstance(expected, AnyOptStr):
return True
@ -78,6 +87,9 @@ def match_list(
The ANYSTR operator can be used on the 'expected' side as a wildcard,
matching any *single* value; and StrRe("<regexp>") can be used to match regular
expressions"""
if expected[-1] is ANYLIST[0]:
expected = expected[0:-1]
got = got[0 : len(expected)] # Ignore remaining
if len(got) != len(expected):
return False
return all(

View File

@ -26,7 +26,7 @@ from irctest.numerics import (
RPL_TOPIC,
RPL_TOPICTIME,
)
from irctest.patma import ANYSTR, StrRe
from irctest.patma import ANYLIST, ANYSTR, StrRe
MODERN_CAPS = [
"server-time",
@ -1296,16 +1296,13 @@ class OpModerated(cases.BaseServerTestCase):
class MuteExtban(cases.BaseServerTestCase):
"""https://defs.ircdocs.horse/defs/isupport.html#extban
These tests assume that if the server advertizes the 'm' extban,
then it supports mute.
It magically guesses what char the IRCd uses for mutes."""
This is not true of Charybdis, which introduced a conflicting 'm'
exban for matching hostmasks in 2015
(e2a9fa9cab3720215d8081e940109416e8214a29).
But Unreal was already using 'm' for muting since 2008
(f474e7e6dc2d36f96150ebe33b23b4ea76814415) and it is the most popular
definition so we're going with that one."""
def char(self):
if self.controller.extban_mute_char is None:
raise runner.ExtbanNotSupported("", "mute")
else:
return self.controller.extban_mute_char
@cases.mark_specifications("Ergo")
def testISupport(self):
@ -1313,7 +1310,7 @@ class MuteExtban(cases.BaseServerTestCase):
isupport = self.server_support
token = isupport["EXTBAN"]
prefix, comma, types = token.partition(",")
self.assertIn("m", types, "Missing 'm' in ISUPPORT EXTBAN")
self.assertIn(self.char, types, f"Missing '{self.char()}' in ISUPPORT EXTBAN")
self.assertEqual(prefix, "")
self.assertEqual(comma, ",")
@ -1325,15 +1322,15 @@ class MuteExtban(cases.BaseServerTestCase):
isupport = self.server_support
token = isupport.get("EXTBAN", "")
prefix, comma, types = token.partition(",")
if "m" not in types:
raise runner.ExtbanNotSupported("m", "mute")
if self.char() not in types:
raise runner.ExtbanNotSupported(self.char(), "mute")
clients = ("chanop", "bar")
# Mute "bar"
self.joinChannel("chanop", "#chan")
self.getMessages("chanop")
self.sendLine("chanop", "MODE #chan +b m:bar!*@*")
self.sendLine("chanop", f"MODE #chan +b {prefix}{self.char()}:bar!*@*")
replies = {msg.command for msg in self.getMessages("chanop")}
self.assertIn("MODE", replies)
self.assertNotIn(ERR_CHANOPRIVSNEEDED, replies)
@ -1344,6 +1341,21 @@ class MuteExtban(cases.BaseServerTestCase):
for client in clients:
self.getMessages(client)
# "bar" sees the MODE too
self.sendLine("bar", "MODE #chan +b")
self.assertMessageMatch(
self.getMessage("bar"),
command="367",
params=[
"bar",
"#chan",
f"{prefix}{self.char()}:bar!*@*",
"chanop",
*ANYLIST,
],
)
self.getMessages("bar")
# "bar" talks: rejected
self.sendLine("bar", "PRIVMSG #chan :hi from bar")
replies = self.getMessages("bar")
@ -1354,7 +1366,7 @@ class MuteExtban(cases.BaseServerTestCase):
# remove mute on "bar" with -b
self.getMessages("chanop")
self.sendLine("chanop", "MODE #chan -b m:bar!*@*")
self.sendLine("chanop", f"MODE #chan -b {prefix}{self.char()}:bar!*@*")
replies = {msg.command for msg in self.getMessages("chanop")}
self.assertIn("MODE", replies)
self.assertNotIn(ERR_CHANOPRIVSNEEDED, replies)
@ -1378,15 +1390,15 @@ class MuteExtban(cases.BaseServerTestCase):
isupport = self.server_support
token = isupport.get("EXTBAN", "")
prefix, comma, types = token.partition(",")
if "m" not in types:
raise runner.ExtbanNotSupported("m", "mute")
if self.char() not in types:
raise runner.ExtbanNotSupported(self.char(), "mute")
clients = ("chanop", "qux")
# Mute "qux"
self.joinChannel("chanop", "#chan")
self.getMessages("chanop")
self.sendLine("chanop", "MODE #chan +b m:qux!*@*")
self.sendLine("chanop", f"MODE #chan +b {prefix}{self.char()}:qux!*@*")
replies = {msg.command for msg in self.getMessages("chanop")}
self.assertIn("MODE", replies)
self.assertNotIn(ERR_CHANOPRIVSNEEDED, replies)
@ -1437,17 +1449,17 @@ class MuteExtban(cases.BaseServerTestCase):
isupport = self.server_support
token = isupport.get("EXTBAN", "")
prefix, comma, types = token.partition(",")
if "m" not in types:
raise runner.ExtbanNotSupported("m", "mute")
if self.char() not in types:
raise runner.ExtbanNotSupported(self.char(), "mute")
if "e" not in self.server_support["CHANMODES"]:
raise runner.ChannelModeNotSupported("m", "mute")
raise runner.ChannelModeNotSupported(self.char(), "mute")
clients = ("chanop", "qux")
# Mute "qux"
self.joinChannel("chanop", "#chan")
self.getMessages("chanop")
self.sendLine("chanop", "MODE #chan +b m:qux!*@*")
self.sendLine("chanop", f"MODE #chan +b {prefix}{self.char()}:qux!*@*")
replies = {msg.command for msg in self.getMessages("chanop")}
self.assertIn("MODE", replies)
self.assertNotIn(ERR_CHANOPRIVSNEEDED, replies)
@ -1472,11 +1484,13 @@ class MuteExtban(cases.BaseServerTestCase):
self.getMessages(client)
# +e grants an exemption to +b
self.sendLine("chanop", "MODE #chan +e m:*!~evan@*")
self.sendLine("chanop", f"MODE #chan +e {prefix}{self.char()}:*!~evan@*")
replies = {msg.command for msg in self.getMessages("chanop")}
self.assertIn("MODE", replies)
self.assertNotIn(ERR_CHANOPRIVSNEEDED, replies)
self.getMessages("qux")
# so "qux" can now talk
self.sendLine("qux", "PRIVMSG #chan :thanks for mute-excepting me")
replies = self.getMessages("qux")
@ -1499,10 +1513,14 @@ class MuteExtban(cases.BaseServerTestCase):
"""
clients = ("chanop", "bar")
isupport = self.server_support
token = isupport.get("EXTBAN", "")
prefix, comma, types = token.partition(",")
self.connectClient("chanop", name="chanop")
self.joinChannel("chanop", "#chan")
self.getMessages("chanop")
self.sendLine("chanop", "MODE #chan +b m:BAR!*@*")
self.sendLine("chanop", f"MODE #chan +b {prefix}{self.char()}:BAR!*@*")
replies = {msg.command for msg in self.getMessages("chanop")}
self.assertIn("MODE", replies)
self.assertNotIn(ERR_CHANOPRIVSNEEDED, replies)
@ -1521,7 +1539,7 @@ class MuteExtban(cases.BaseServerTestCase):
self.assertEqual(self.getMessages("chanop"), [])
# remove mute with -b
self.sendLine("chanop", "MODE #chan -b m:bar!*@*")
self.sendLine("chanop", f"MODE #chan -b {prefix}{self.char()}:bar!*@*")
replies = {msg.command for msg in self.getMessages("chanop")}
self.assertIn("MODE", replies)
self.assertNotIn(ERR_CHANOPRIVSNEEDED, replies)

View File

@ -119,15 +119,38 @@ class ConnectionRegistrationTestCase(cases.BaseServerTestCase):
self.sendLine(2, "NICK foo")
self.sendLine(1, "USER username * * :Realname")
self.sendLine(2, "USER username * * :Realname")
m1 = self.getRegistrationMessage(1)
m2 = self.getRegistrationMessage(2)
try:
m1 = self.getRegistrationMessage(1)
except (ConnectionClosed, ConnectionResetError):
# Unreal closes the connection, see
# https://bugs.unrealircd.org/view.php?id=5950
command1 = None
else:
command1 = m1.command
try:
m2 = self.getRegistrationMessage(2)
except (ConnectionClosed, ConnectionResetError):
# ditto
command2 = None
else:
command2 = m2.command
self.assertNotEqual(
(m1.command, m2.command),
(command1, command2),
("001", "001"),
"Two concurrently registering requesting the same nickname "
"both got 001.",
)
self.assertIn(
"001",
(command1, command2),
"Two concurrently registering requesting the same nickname "
"neither got 001.",
)
@cases.mark_specifications("IRCv3")
def testIrc301CapLs(self):
"""

View File

@ -2,6 +2,8 @@
<http://ircv3.net/specs/extensions/echo-message-3.2.html>
"""
import pytest
from irctest import cases
from irctest.basecontrollers import NotImplementedByController
from irctest.irc_utils.junkdrawer import random_name
@ -97,6 +99,7 @@ def _testEchoMessage(command, solo, server_time):
class EchoMessageTestCase(cases.BaseServerTestCase):
@pytest.mark.arbitrary_client_tags
@cases.mark_capabilities(
"batch", "labeled-response", "echo-message", "message-tags"
)

View File

@ -282,7 +282,14 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper
)
self.getMessages(2)
self.sendLine(1, "@label=12345;+draft/reply=123;+draft/react=l😃l TAGMSG bar")
# Need to get a valid msgid because Unreal validates them
self.sendLine(1, "PRIVMSG bar :hi")
msgid = self.getMessage(1).tags["msgid"]
assert msgid == self.getMessage(2).tags["msgid"]
self.sendLine(
1, f"@label=12345;+draft/reply={msgid};+draft/react=l😃l TAGMSG bar"
)
m = self.getMessage(1)
m2 = self.getMessage(2)
@ -290,7 +297,7 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper
self.assertMessageMatch(
m2,
command="TAGMSG",
tags={"+draft/reply": "123", "+draft/react": "l😃l", **ANYDICT},
tags={"+draft/reply": msgid, "+draft/react": "l😃l", **ANYDICT},
)
self.assertNotIn(
"label",
@ -308,7 +315,7 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper
command="TAGMSG",
tags={
"label": "12345",
"+draft/reply": "123",
"+draft/reply": msgid,
"+draft/react": "l😃l",
**ANYDICT,
},
@ -338,7 +345,14 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper
self.getMessages(2)
self.getMessages(1)
self.sendLine(1, "@label=12345;+draft/reply=123;+draft/react=l😃l TAGMSG #test")
# Need to get a valid msgid because Unreal validates them
self.sendLine(1, "PRIVMSG #test :hi")
msgid = self.getMessage(1).tags["msgid"]
assert msgid == self.getMessage(2).tags["msgid"]
self.sendLine(
1, f"@label=12345;+draft/reply={msgid};+draft/react=l😃l TAGMSG #test"
)
ms = self.getMessage(1)
mt = self.getMessage(2)
@ -361,7 +375,9 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper
# ensure sender correctly receives msg
self.assertMessageMatch(
ms, command="TAGMSG", tags={"label": "12345", **ANYDICT}
ms,
command="TAGMSG",
tags={"label": "12345", "+draft/reply": msgid, **ANYDICT},
)
@cases.mark_capabilities(

View File

@ -2,6 +2,8 @@
https://ircv3.net/specs/extensions/message-tags.html
"""
import pytest
from irctest import cases
from irctest.irc_utils.message_parser import parse_message
from irctest.numerics import ERR_INPUTTOOLONG
@ -9,6 +11,7 @@ from irctest.patma import ANYDICT, ANYSTR, StrRe
class MessageTagsTestCase(cases.BaseServerTestCase, cases.OptionalityHelper):
@pytest.mark.arbitrary_client_tags
@cases.mark_capabilities("message-tags")
def testBasic(self):
def getAllMessages():
@ -107,6 +110,7 @@ class MessageTagsTestCase(cases.BaseServerTestCase, cases.OptionalityHelper):
self.assertNotIn("cat", msg.tags)
self.assertEqual(alice_msg.tags["msgid"], bob_msg.tags["msgid"])
@pytest.mark.arbitrary_client_tags
@cases.mark_capabilities("message-tags")
@cases.mark_specifications("ircdocs")
def testLengthLimits(self):

View File

@ -12,6 +12,7 @@ markers =
strict
deprecated
services
arbitrary_client_tags
# capabilities
account-tag