Add tests for ELIST

This commit is contained in:
Valentin Lorentz 2022-03-20 14:07:46 +01:00
parent a9a7a2a187
commit af001fad2e
6 changed files with 326 additions and 7 deletions

View File

@ -179,6 +179,7 @@ SOPEL_SELECTORS := \
# testChathistory[AROUND] fails: https://bugs.unrealircd.org/view.php?id=5953
# testWhoAllOpers fails because Unreal skips results when the mask is too broad
# HELP and HELPOP tests fail because Unreal uses custom numerics https://github.com/unrealircd/unrealircd/pull/184
# testListTopicTime fails because Unreal mistakenly advertises it as available https://github.com/unrealircd/unrealircd/pull/193
UNREALIRCD_SELECTORS := \
not Ergo \
and not deprecated \
@ -194,6 +195,7 @@ UNREALIRCD_SELECTORS := \
and not (testChathistory and (between or around)) \
and not testWhoAllOpers \
and not HelpTestCase \
and not testListTopicTime \
$(EXTRA_SELECTORS)
.PHONY: all flakes bahamut charybdis ergo inspircd ircu2 snircd irc2 mammon limnoria sopel solanum unrealircd

View File

@ -74,7 +74,7 @@ operator {{
class Plexus4Controller(BaseHybridController):
software_name = "Hybrid"
software_name = "Plexus4"
binary_name = "ircd"
services_protocol = "plexus"

View File

@ -1,12 +1,8 @@
import functools
import os
<<<<<<< HEAD
import pathlib
import shutil
import signal
=======
import shutil
>>>>>>> 96e6642 (Add support for 'faketime', to avoid long sleeps in upcoming ELIST tests)
import subprocess
import textwrap
from typing import Optional, Set, Type

View File

@ -1,8 +1,26 @@
from irctest import cases
import time
from irctest import cases, runner
from irctest.numerics import RPL_LIST, RPL_LISTEND, RPL_LISTSTART
class ListTestCase(cases.BaseServerTestCase):
class _BasedListTestCase(cases.BaseServerTestCase):
def _parseChanList(self, client):
channels = set()
while True:
m = self.getMessage(client)
if m.command == RPL_LISTEND:
break
if m.command == RPL_LIST:
if m.params[1].startswith("&"):
# skip local pseudo-channels listed by ngircd and ircu
continue
channels.add(m.params[1])
return channels
class ListTestCase(_BasedListTestCase):
@cases.mark_specifications("RFC1459", "RFC2812")
def testListEmpty(self):
"""<https://tools.ietf.org/html/rfc1459#section-4.2.6>
@ -77,3 +95,304 @@ class ListTestCase(cases.BaseServerTestCase):
fail_msg="Third reply to LIST is not 322 (RPL_LIST) "
"or 323 (RPL_LISTEND), or but: {msg}",
)
@cases.mark_isupport("ELIST")
@cases.mark_specifications("Modern")
def testListMask(self):
"""
"M: Searching based on mask."
-- <https://modern.ircdocs.horse/#elist-parameter>
-- https://datatracker.ietf.org/doc/html/draft-hardy-irc-isupport-00#section-4.8
"""
self.connectClient("foo")
if "M" not in self.server_support.get("ELIST", ""):
raise runner.OptionalExtensionNotSupported("ELIST=M")
self.connectClient("bar")
self.sendLine(1, "JOIN #chan1")
self.getMessages(1)
self.sendLine(1, "JOIN #chan2")
self.getMessages(1)
self.sendLine(2, "LIST *an1")
self.assertEqual(self._parseChanList(2), {"#chan1"})
self.sendLine(2, "LIST *an2")
self.assertEqual(self._parseChanList(2), {"#chan2"})
self.sendLine(2, "LIST #c*n2")
self.assertEqual(self._parseChanList(2), {"#chan2"})
self.sendLine(2, "LIST *an3")
self.assertEqual(self._parseChanList(2), set())
self.sendLine(2, "LIST #ch*")
self.assertEqual(self._parseChanList(2), {"#chan1", "#chan2"})
@cases.mark_isupport("ELIST")
@cases.mark_specifications("Modern")
def testListNotMask(self):
"""
" N: Searching based on a non-matching mask. i.e., the opposite of M."
-- <https://modern.ircdocs.horse/#elist-parameter>
-- https://datatracker.ietf.org/doc/html/draft-hardy-irc-isupport-00#section-4.8
"""
self.connectClient("foo")
if "N" not in self.server_support.get("ELIST", ""):
raise runner.OptionalExtensionNotSupported("ELIST=N")
self.sendLine(1, "JOIN #chan1")
self.getMessages(1)
self.sendLine(1, "JOIN #chan2")
self.getMessages(1)
self.connectClient("bar")
self.sendLine(2, "LIST !*an1")
self.assertEqual(self._parseChanList(2), {"#chan2"})
self.sendLine(2, "LIST !*an2")
self.assertEqual(self._parseChanList(2), {"#chan1"})
self.sendLine(2, "LIST !#c*n2")
self.assertEqual(self._parseChanList(2), {"#chan1"})
self.sendLine(2, "LIST !*an3")
self.assertEqual(self._parseChanList(2), {"#chan1", "#chan2"})
self.sendLine(2, "LIST !#ch*")
self.assertEqual(self._parseChanList(2), set())
@cases.mark_isupport("ELIST")
@cases.mark_specifications("Modern")
def testListUsers(self):
"""
"U: Searching based on user count within the channel, via the "<val" and
">val" modifiers to search for a channel that has less or more than val users,
respectively."
-- <https://modern.ircdocs.horse/#elist-parameter>
-- https://datatracker.ietf.org/doc/html/draft-hardy-irc-isupport-00#section-4.8
"""
self.connectClient("foo")
if "M" not in self.server_support.get("ELIST", ""):
raise runner.OptionalExtensionNotSupported("ELIST=M")
self.sendLine(1, "JOIN #chan1")
self.getMessages(1)
self.sendLine(1, "JOIN #chan2")
self.getMessages(1)
self.connectClient("bar")
self.sendLine(2, "JOIN #chan2")
self.getMessages(2)
self.connectClient("baz")
self.sendLine(3, "LIST >0")
self.assertEqual(self._parseChanList(3), {"#chan1", "#chan2"})
self.sendLine(3, "LIST <1")
self.assertEqual(self._parseChanList(3), set())
self.sendLine(3, "LIST <100")
self.assertEqual(self._parseChanList(3), {"#chan1", "#chan2"})
self.sendLine(3, "LIST >1")
self.assertEqual(self._parseChanList(3), {"#chan2"})
self.sendLine(3, "LIST <2")
self.assertEqual(self._parseChanList(3), {"#chan1"})
self.sendLine(3, "LIST <100")
self.assertEqual(self._parseChanList(3), {"#chan1", "#chan2"})
class FaketimeListTestCase(_BasedListTestCase):
faketime = "+1y x30" # for every wall clock second, 1 minute passed for the server
def _sleep_minutes(self, n):
for _ in range(n):
if self.controller.faketime_enabled:
# From the server's point of view, 1 min will pass
time.sleep(2)
else:
time.sleep(60)
# reply to pings
self.getMessages(1)
self.getMessages(2)
@cases.mark_isupport("ELIST")
@cases.mark_specifications("Modern")
def testListCreationTime(self):
"""
" C: Searching based on channel creation time, via the "C<val" and "C>val"
modifiers to search for a channel creation time that is higher or lower
than val."
-- <https://modern.ircdocs.horse/#elist-parameter>
-- https://datatracker.ietf.org/doc/html/draft-hardy-irc-isupport-00#section-4.8
Unfortunately, this is ambiguous, because "val" is a time delta (in minutes),
not a timestamp.
On InspIRCd and Charybdis/Solanum, "C<val" is interpreted as "the channel was
created less than <val> minutes ago
On UnrealIRCd, Plexus, and Hybrid, it is interpreted as "the channel's creation
time is a timestamp lower than <val> minutes ago" (ie. the exact opposite)
"C: Searching based on channel creation time, via the "C<val" and "C>val"
modifiers to search for a channel that was created either less than `val`
minutes ago, or more than `val` minutes ago, respectively"
-- https://github.com/ircdocs/modern-irc/pull/171
"""
self.connectClient("foo")
if "C" not in self.server_support.get("ELIST", ""):
raise runner.OptionalExtensionNotSupported("ELIST=C")
self.connectClient("bar")
self.sendLine(1, "JOIN #chan1")
self.getMessages(1)
# Helps debugging
self.sendLine(1, "TIME")
self.getMessages(1)
self._sleep_minutes(2)
# Helps debugging
self.sendLine(1, "TIME")
self.getMessages(1)
self.sendLine(1, "JOIN #chan2")
self.getMessages(1)
self._sleep_minutes(1)
if self.controller.software_name in ("UnrealIRCd", "Plexus4", "Hybrid"):
self.sendLine(2, "LIST C<2")
self.assertEqual(self._parseChanList(2), {"#chan1"})
self.sendLine(2, "LIST C>2")
self.assertEqual(self._parseChanList(2), {"#chan2"})
self.sendLine(2, "LIST C>0")
self.assertEqual(self._parseChanList(2), set())
self.sendLine(2, "LIST C<0")
self.assertEqual(self._parseChanList(2), {"#chan1", "#chan2"})
self.sendLine(2, "LIST C>10")
self.assertEqual(self._parseChanList(2), {"#chan1", "#chan2"})
elif self.controller.software_name in ("Solanum", "Charybdis", "InspIRCd"):
self.sendLine(2, "LIST C>2")
self.assertEqual(self._parseChanList(2), {"#chan1"})
self.sendLine(2, "LIST C<2")
self.assertEqual(self._parseChanList(2), {"#chan2"})
self.sendLine(2, "LIST C<0")
if self.controller.software_name == "InspIRCd":
self.assertEqual(self._parseChanList(2), {"#chan1", "#chan2"})
else:
self.assertEqual(self._parseChanList(2), set())
self.sendLine(2, "LIST C>0")
self.assertEqual(self._parseChanList(2), {"#chan1", "#chan2"})
self.sendLine(2, "LIST C<10")
self.assertEqual(self._parseChanList(2), {"#chan1", "#chan2"})
else:
assert False, f"{self.controller.software_name} not supported"
@cases.mark_isupport("ELIST")
@cases.mark_specifications("Modern")
def testListTopicTime(self):
"""
"T: Searching based on topic time, via the "T<val" and "T>val"
modifiers to search for a topic time that is lower or higher than
val respectively."
-- <https://modern.ircdocs.horse/#elist-parameter>
-- https://datatracker.ietf.org/doc/html/draft-hardy-irc-isupport-00#section-4.8
See testListCreationTime's docstring for comments on this.
"T: Searching based on topic set time, via the "T<val" and "T>val" modifiers
to search for a topic time that was set less than `val` minutes ago, or more
than `val` minutes ago, respectively."
-- https://github.com/ircdocs/modern-irc/pull/171
"""
self.connectClient("foo")
if "T" not in self.server_support.get("ELIST", ""):
raise runner.OptionalExtensionNotSupported("ELIST=T")
self.connectClient("bar")
self.sendLine(1, "JOIN #chan1")
self.sendLine(1, "JOIN #chan2")
self.getMessages(1)
self.sendLine(1, "TOPIC #chan1 :First channel")
self.getMessages(1)
# Helps debugging
self.sendLine(1, "TIME")
self.getMessages(1)
self._sleep_minutes(2)
# Helps debugging
self.sendLine(1, "TIME")
self.getMessages(1)
self.sendLine(1, "TOPIC #chan2 :Second channel")
self.getMessages(1)
self._sleep_minutes(1)
if self.controller.software_name in ("UnrealIRCd",):
self.sendLine(1, "LIST T<2")
self.assertEqual(self._parseChanList(1), {"#chan1"})
self.sendLine(1, "LIST T>2")
self.assertEqual(self._parseChanList(1), {"#chan2"})
self.sendLine(1, "LIST T>0")
self.assertEqual(self._parseChanList(1), set())
self.sendLine(1, "LIST T<0")
self.assertEqual(self._parseChanList(1), {"#chan1", "#chan2"})
self.sendLine(1, "LIST T>10")
self.assertEqual(self._parseChanList(1), {"#chan1", "#chan2"})
elif self.controller.software_name in (
"Solanum",
"Charybdis",
"InspIRCd",
"Plexus4",
"Hybrid",
):
self.sendLine(1, "LIST T>2")
self.assertEqual(self._parseChanList(1), {"#chan1"})
self.sendLine(1, "LIST T<2")
self.assertEqual(self._parseChanList(1), {"#chan2"})
self.sendLine(1, "LIST T<0")
if self.controller.software_name == "InspIRCd":
# Insp internally represents "LIST T>0" like "LIST"
self.assertEqual(self._parseChanList(1), {"#chan1", "#chan2"})
else:
self.assertEqual(self._parseChanList(1), set())
self.sendLine(1, "LIST T>0")
self.assertEqual(self._parseChanList(1), {"#chan1", "#chan2"})
self.sendLine(1, "LIST T<10")
self.assertEqual(self._parseChanList(1), {"#chan1", "#chan2"})
else:
assert False, f"{self.controller.software_name} not supported"

View File

@ -50,6 +50,7 @@ class Capabilities(enum.Enum):
@enum.unique
class IsupportTokens(enum.Enum):
BOT = "BOT"
ELIST = "ELIST"
PREFIX = "PREFIX"
MONITOR = "MONITOR"
STATUSMSG = "STATUSMSG"

View File

@ -32,6 +32,7 @@ markers =
# isupport tokens
BOT
ELIST
MONITOR
PREFIX
STATUSMSG