From 0352a83a73f962ddac64b8b23db22e2a21b44ab1 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sat, 27 Feb 2021 10:36:30 +0100 Subject: [PATCH] Change IRCv3 marks to reference capabilities instead of v3.1 / v3.2 --- irctest/cases.py | 38 ++++++++++++++-- irctest/client_tests/test_cap.py | 4 +- irctest/runner.py | 5 +++ irctest/server_tests/test_account_tag.py | 2 +- irctest/server_tests/test_away_notify.py | 4 +- irctest/server_tests/test_cap.py | 10 ++--- .../server_tests/test_channel_operations.py | 8 +++- .../test_connection_registration.py | 19 ++++++-- irctest/server_tests/test_echo_message.py | 2 +- irctest/server_tests/test_extended_join.py | 4 +- .../server_tests/test_labeled_responses.py | 24 +++++----- irctest/server_tests/test_message_tags.py | 4 +- irctest/server_tests/test_metadata.py | 18 ++++---- irctest/server_tests/test_monitor.py | 30 ++++++++----- irctest/server_tests/test_multi_prefix.py | 2 +- irctest/server_tests/test_multiline.py | 4 +- irctest/server_tests/test_regressions.py | 2 +- irctest/server_tests/test_sasl.py | 10 ++--- irctest/specifications.py | 45 ++++++++++++++++--- pytest.ini | 24 ++++++++-- 20 files changed, 188 insertions(+), 71 deletions(-) diff --git a/irctest/cases.py b/irctest/cases.py index a3701dd..93b5f39 100644 --- a/irctest/cases.py +++ b/irctest/cases.py @@ -20,7 +20,7 @@ from .numerics import ( ERR_NOSUCHCHANNEL, ERR_TOOMANYCHANNELS, ) -from .specifications import Specifications +from .specifications import Capabilities, IsupportTokens, Specifications CHANNEL_JOIN_FAIL_NUMERICS = frozenset( [ @@ -489,7 +489,7 @@ class BaseServerTestCase(_IrcTestCase): ) except AssertionError: if skip_if_cap_nak: - raise runner.NotImplementedByController(", ".join(capabilities)) + raise runner.CapabilityNotSupported(" or ".join(capabilities)) else: raise self.sendLine(client, "CAP END") @@ -614,7 +614,7 @@ class OptionalityHelper: def mark_specifications(*specifications, deprecated=False, strict=False): specifications = frozenset( - Specifications.of_name(s) if isinstance(s, str) else s for s in specifications + Specifications.from_name(s) if isinstance(s, str) else s for s in specifications ) if None in specifications: raise ValueError("Invalid set of specifications: {}".format(specifications)) @@ -629,3 +629,35 @@ def mark_specifications(*specifications, deprecated=False, strict=False): return f return decorator + + +def mark_capabilities(*capabilities, deprecated=False, strict=False): + capabilities = frozenset( + Capabilities.from_name(c) if isinstance(c, str) else c for c in capabilities + ) + if None in capabilities: + raise ValueError("Invalid set of capabilities: {}".format(capabilities)) + + def decorator(f): + for capability in capabilities: + f = getattr(pytest.mark, capability.value)(f) + # Support for any capability implies IRCv3 + f = pytest.mark.IRCv3(f) + return f + + return decorator + + +def mark_isupport(*tokens, deprecated=False, strict=False): + tokens = frozenset( + IsupportTokens.from_name(c) if isinstance(c, str) else c for c in tokens + ) + if None in tokens: + raise ValueError("Invalid set of isupport tokens: {}".format(tokens)) + + def decorator(f): + for token in tokens: + f = getattr(pytest.mark, token.value)(f) + return f + + return decorator diff --git a/irctest/client_tests/test_cap.py b/irctest/client_tests/test_cap.py index 0e07894..1914ff3 100644 --- a/irctest/client_tests/test_cap.py +++ b/irctest/client_tests/test_cap.py @@ -3,12 +3,12 @@ from irctest.irc_utils.message_parser import Message class CapTestCase(cases.BaseClientTestCase, cases.ClientNegociationHelper): - @cases.mark_specifications("IRCv3.1", "IRCv3.2") + @cases.mark_specifications("IRCv3") def testSendCap(self): """Send CAP LS 302 and read the result.""" self.readCapLs() - @cases.mark_specifications("IRCv3.1", "IRCv3.2") + @cases.mark_specifications("IRCv3") def testEmptyCapLs(self): """Empty result to CAP LS. Client should send CAP END.""" m = self.negotiateCapabilities([]) diff --git a/irctest/runner.py b/irctest/runner.py index 5d45723..910c3b8 100644 --- a/irctest/runner.py +++ b/irctest/runner.py @@ -30,6 +30,11 @@ class CapabilityNotSupported(unittest.SkipTest): return "Unsupported capability: {}".format(self.args[0]) +class IsupportTokenNotSupported(unittest.SkipTest): + def __str__(self): + return "Unsupported ISUPPORT token: {}".format(self.args[0]) + + class NotRequiredBySpecifications(unittest.SkipTest): def __str__(self): return "Tests not required by the set of tested specification(s)." diff --git a/irctest/server_tests/test_account_tag.py b/irctest/server_tests/test_account_tag.py index 1eb7651..f043ca6 100644 --- a/irctest/server_tests/test_account_tag.py +++ b/irctest/server_tests/test_account_tag.py @@ -32,7 +32,7 @@ class AccountTagTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): self.sendLine(2, "CAP END") self.skipToWelcome(2) - @cases.mark_specifications("IRCv3.2") + @cases.mark_specifications("IRCv3") @cases.OptionalityHelper.skipUnlessHasMechanism("PLAIN") def testPrivmsg(self): self.connectClient("foo", capabilities=["account-tag"], skip_if_cap_nak=True) diff --git a/irctest/server_tests/test_away_notify.py b/irctest/server_tests/test_away_notify.py index bb2a586..8204e66 100644 --- a/irctest/server_tests/test_away_notify.py +++ b/irctest/server_tests/test_away_notify.py @@ -6,7 +6,7 @@ from irctest import cases class AwayNotifyTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): - @cases.mark_specifications("IRCv3.1") + @cases.mark_capabilities("away-notify") def testAwayNotify(self): """Basic away-notify test.""" self.connectClient("foo", capabilities=["away-notify"], skip_if_cap_nak=True) @@ -29,7 +29,7 @@ class AwayNotifyTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): "Unexpected away-notify source: %s" % (awayNotify.prefix,), ) - @cases.mark_specifications("IRCv3.2") + @cases.mark_capabilities("away-notify") def testAwayNotifyOnJoin(self): """The away-notify specification states: "Clients will be sent an AWAY message [...] when a user joins diff --git a/irctest/server_tests/test_cap.py b/irctest/server_tests/test_cap.py index 014f147..30cd0e9 100644 --- a/irctest/server_tests/test_cap.py +++ b/irctest/server_tests/test_cap.py @@ -3,7 +3,7 @@ from irctest.runner import CapabilityNotSupported, ImplementationChoice class CapTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): - @cases.mark_specifications("IRCv3.1") + @cases.mark_specifications("IRCv3") def testNoReq(self): """Test the server handles gracefully clients which do not send REQs. @@ -23,7 +23,7 @@ class CapTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): m, command="001", fail_msg="Expected 001 after sending CAP END, got {msg}." ) - @cases.mark_specifications("IRCv3.1") + @cases.mark_specifications("IRCv3") def testReqUnavailable(self): """Test the server handles gracefully clients which request capabilities that are not available. @@ -50,7 +50,7 @@ class CapTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): m, command="001", fail_msg="Expected 001 after sending CAP END, got {msg}." ) - @cases.mark_specifications("IRCv3.1") + @cases.mark_specifications("IRCv3") def testNakExactString(self): """“The argument of the NAK subcommand MUST consist of at least the first 100 characters of the capability list in the REQ subcommand which @@ -73,7 +73,7 @@ class CapTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): "sending “CAP REQ :foo qux bar baz qux quux”, but got {msg}.", ) - @cases.mark_specifications("IRCv3.1") + @cases.mark_specifications("IRCv3") def testNakWhole(self): """“The capability identifier set must be accepted as a whole, or rejected entirely.” @@ -124,7 +124,7 @@ class CapTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): "sending “CAP REQ :multi-prefix”, but got {msg}.", ) - @cases.mark_specifications("IRCv3.1") + @cases.mark_specifications("IRCv3") def testCapRemovalByClient(self): """Test CAP LIST and removal of caps via CAP REQ :-tagname.""" cap1 = "echo-message" diff --git a/irctest/server_tests/test_channel_operations.py b/irctest/server_tests/test_channel_operations.py index 4e9087b..4357dc0 100644 --- a/irctest/server_tests/test_channel_operations.py +++ b/irctest/server_tests/test_channel_operations.py @@ -712,9 +712,13 @@ class testChannelCaseSensitivity(cases.BaseServerTestCase): class InviteTestCase(cases.BaseServerTestCase): - @cases.mark_specifications("IRCv3.2") + @cases.mark_specifications("Modern") def testInvites(self): - """Test some basic functionality related to INVITE and the +i mode.""" + """Test some basic functionality related to INVITE and the +i mode. + + https://modern.ircdocs.horse/#invite-only-channel-mode + https://modern.ircdocs.horse/#rplinviting-341 + """ self.connectClient("foo") self.joinChannel(1, "#chan") self.sendLine(1, "MODE #chan +i") diff --git a/irctest/server_tests/test_connection_registration.py b/irctest/server_tests/test_connection_registration.py index b919660..e35732b 100644 --- a/irctest/server_tests/test_connection_registration.py +++ b/irctest/server_tests/test_connection_registration.py @@ -128,9 +128,22 @@ class ConnectionRegistrationTestCase(cases.BaseServerTestCase): "both got 001.", ) - @cases.mark_specifications("IRCv3.1", "IRCv3.2") + @cases.mark_specifications("IRCv3") def testIrc301CapLs(self): - """IRCv3.1: “The LS subcommand is used to list the capabilities + """ + Current version: + + "The LS subcommand is used to list the capabilities supported by the server. + The client should send an LS subcommand with no other arguments to solicit + a list of all capabilities." + + "If a client has not indicated support for CAP LS 302 features, + the server MUST NOT send these new features to the client." + -- + + Before the v3.1 / v3.2 merge: + + IRCv3.1: “The LS subcommand is used to list the capabilities supported by the server. The client should send an LS subcommand with no other arguments to solicit a list of all capabilities.” -- @@ -156,7 +169,7 @@ class ConnectionRegistrationTestCase(cases.BaseServerTestCase): "request: {}".format(m), ) - @cases.mark_specifications("IRCv3.1") + @cases.mark_specifications("IRCv3") def testEmptyCapList(self): """“If no capabilities are active, an empty parameter must be sent.” -- diff --git a/irctest/server_tests/test_echo_message.py b/irctest/server_tests/test_echo_message.py index 75a6a52..20ed113 100644 --- a/irctest/server_tests/test_echo_message.py +++ b/irctest/server_tests/test_echo_message.py @@ -58,7 +58,7 @@ class DMEchoMessageTestCase(cases.BaseServerTestCase): class EchoMessageTestCase(cases.BaseServerTestCase): def _testEchoMessage(command, solo, server_time): - @cases.mark_specifications("IRCv3.2") + @cases.mark_capabilities("echo-message") def f(self): """""" self.addClient() diff --git a/irctest/server_tests/test_extended_join.py b/irctest/server_tests/test_extended_join.py index bb14f28..884fc44 100644 --- a/irctest/server_tests/test_extended_join.py +++ b/irctest/server_tests/test_extended_join.py @@ -32,7 +32,7 @@ class MetadataTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): self.sendLine(2, "CAP END") self.skipToWelcome(2) - @cases.mark_specifications("IRCv3.1") + @cases.mark_capabilities("extended-join") def testNotLoggedIn(self): self.connectClient("foo", capabilities=["extended-join"], skip_if_cap_nak=True) self.joinChannel(1, "#chan") @@ -47,7 +47,7 @@ class MetadataTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): "unregistered user joined, got: {msg}", ) - @cases.mark_specifications("IRCv3.1") + @cases.mark_capabilities("extended-join") @cases.OptionalityHelper.skipUnlessHasMechanism("PLAIN") def testLoggedIn(self): self.connectClient("foo", capabilities=["extended-join"], skip_if_cap_nak=True) diff --git a/irctest/server_tests/test_labeled_responses.py b/irctest/server_tests/test_labeled_responses.py index 29dd916..63dcbb3 100644 --- a/irctest/server_tests/test_labeled_responses.py +++ b/irctest/server_tests/test_labeled_responses.py @@ -8,7 +8,7 @@ from irctest import cases class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): - @cases.mark_specifications("IRCv3.2") + @cases.mark_capabilities("batch", "echo-message", "labeled-response") def testLabeledPrivmsgResponsesToMultipleClients(self): self.connectClient( "foo", @@ -92,7 +92,7 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper m, command="BATCH", fail_msg="No BATCH echo received after sending one out" ) - @cases.mark_specifications("IRCv3.2") + @cases.mark_capabilities("echo-message", "labeled-response") def testLabeledPrivmsgResponsesToClient(self): self.connectClient( "foo", @@ -152,7 +152,7 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper ), ) - @cases.mark_specifications("IRCv3.2") + @cases.mark_capabilities("echo-message", "labeled-response") def testLabeledPrivmsgResponsesToChannel(self): self.connectClient( "foo", @@ -220,7 +220,7 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper ), ) - @cases.mark_specifications("IRCv3.2") + @cases.mark_capabilities("echo-message", "labeled-response") def testLabeledPrivmsgResponsesToSelf(self): self.connectClient( "foo", @@ -263,7 +263,7 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper ).format(number_of_labels), ) - @cases.mark_specifications("IRCv3.2") + @cases.mark_capabilities("echo-message", "labeled-response") def testLabeledNoticeResponsesToClient(self): self.connectClient( "foo", @@ -323,7 +323,7 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper ), ) - @cases.mark_specifications("IRCv3.2") + @cases.mark_capabilities("echo-message", "labeled-response") def testLabeledNoticeResponsesToChannel(self): self.connectClient( "foo", @@ -391,7 +391,7 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper ), ) - @cases.mark_specifications("IRCv3.2") + @cases.mark_capabilities("echo-message", "labeled-response") def testLabeledNoticeResponsesToSelf(self): self.connectClient( "foo", @@ -432,7 +432,7 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper ).format(number_of_labels), ) - @cases.mark_specifications("IRCv3.2") + @cases.mark_capabilities("echo-message", "labeled-response", "message-tags") def testLabeledTagMsgResponsesToClient(self): self.connectClient( "foo", @@ -540,7 +540,7 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper fail_msg="React tag wasn't the same on the source user's TAGMSG: {msg}", ) - @cases.mark_specifications("IRCv3.2") + @cases.mark_capabilities("echo-message", "labeled-response", "message-tags") def testLabeledTagMsgResponsesToChannel(self): self.connectClient( "foo", @@ -606,7 +606,7 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper ), ) - @cases.mark_specifications("IRCv3.2") + @cases.mark_capabilities("echo-message", "labeled-response", "message-tags") def testLabeledTagMsgResponsesToSelf(self): self.connectClient( "foo", @@ -647,7 +647,9 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper ).format(number_of_labels), ) - @cases.mark_specifications("IRCv3.2") + @cases.mark_capabilities( + "echo-message", "labeled-response", "message-tags", "server-time" + ) def testBatchedJoinMessages(self): self.connectClient( "bar", diff --git a/irctest/server_tests/test_message_tags.py b/irctest/server_tests/test_message_tags.py index 8b6b91d..9395463 100644 --- a/irctest/server_tests/test_message_tags.py +++ b/irctest/server_tests/test_message_tags.py @@ -8,7 +8,7 @@ from irctest.numerics import ERR_INPUTTOOLONG class MessageTagsTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): - @cases.mark_specifications("message-tags") + @cases.mark_capabilities("message-tags") def testBasic(self): def getAllMessages(): for name in ["alice", "bob", "carol", "dave"]: @@ -87,7 +87,7 @@ class MessageTagsTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): self.assertTrue(alice_msg.tags["msgid"]) self.assertEqual(alice_msg.tags["msgid"], bob_msg.tags["msgid"]) - @cases.mark_specifications("message-tags") + @cases.mark_capabilities("message-tags") def testLengthLimits(self): self.connectClient( "alice", diff --git a/irctest/server_tests/test_metadata.py b/irctest/server_tests/test_metadata.py index 0f49075..5d05095 100644 --- a/irctest/server_tests/test_metadata.py +++ b/irctest/server_tests/test_metadata.py @@ -10,7 +10,7 @@ class MetadataTestCase(cases.BaseServerTestCase): valid_metadata_keys = {"valid_key1", "valid_key2"} invalid_metadata_keys = {"invalid_key1", "invalid_key2"} - @cases.mark_specifications("IRCv3.2", deprecated=True) + @cases.mark_specifications("IRCv3", deprecated=True) def testInIsupport(self): """“If METADATA is supported, it MUST be specified in RPL_ISUPPORT using the METADATA key.” @@ -33,7 +33,7 @@ class MetadataTestCase(cases.BaseServerTestCase): ) self.getMessages(1) - @cases.mark_specifications("IRCv3.2", deprecated=True) + @cases.mark_specifications("IRCv3", deprecated=True) def testGetOneUnsetValid(self): """""" self.connectClient("foo") @@ -46,7 +46,7 @@ class MetadataTestCase(cases.BaseServerTestCase): "request to an unset valid METADATA key.", ) - @cases.mark_specifications("IRCv3.2", deprecated=True) + @cases.mark_specifications("IRCv3", deprecated=True) def testGetTwoUnsetValid(self): """“Multiple keys may be given. The response will be either RPL_KEYVALUE, ERR_KEYINVALID or ERR_NOMATCHINGKEY for every key in order.” @@ -83,7 +83,7 @@ class MetadataTestCase(cases.BaseServerTestCase): "did not respond to valid_key2 as second response: {msg}", ) - @cases.mark_specifications("IRCv3.2", deprecated=True) + @cases.mark_specifications("IRCv3", deprecated=True) def testListNoSet(self): """“This subcommand MUST list all currently-set metadata keys along with their values. The response will be zero or more RPL_KEYVALUE @@ -100,7 +100,7 @@ class MetadataTestCase(cases.BaseServerTestCase): "762 (RPL_METADATAEND) but: {msg}", ) - @cases.mark_specifications("IRCv3.2", deprecated=True) + @cases.mark_specifications("IRCv3", deprecated=True) def testListInvalidTarget(self): """“In case of invalid target RPL_METADATAEND MUST NOT be sent.” -- @@ -187,13 +187,13 @@ class MetadataTestCase(cases.BaseServerTestCase): self.assertSetValue(target, key, value, displayable_value) self.assertGetValue(target, key, value, displayable_value) - @cases.mark_specifications("IRCv3.2", deprecated=True) + @cases.mark_specifications("IRCv3", deprecated=True) def testSetGetValid(self): """""" self.connectClient("foo") self.assertSetGetValue("*", "valid_key1", "myvalue") - @cases.mark_specifications("IRCv3.2", deprecated=True) + @cases.mark_specifications("IRCv3", deprecated=True) def testSetGetZeroCharInValue(self): """“Values are unrestricted, except that they MUST be UTF-8.” -- @@ -201,7 +201,7 @@ class MetadataTestCase(cases.BaseServerTestCase): self.connectClient("foo") self.assertSetGetValue("*", "valid_key1", "zero->\0<-zero", "zero->\\0<-zero") - @cases.mark_specifications("IRCv3.2", deprecated=True) + @cases.mark_specifications("IRCv3", deprecated=True) def testSetGetHeartInValue(self): """“Values are unrestricted, except that they MUST be UTF-8.” -- @@ -215,7 +215,7 @@ class MetadataTestCase(cases.BaseServerTestCase): "zero->{}<-zero".format(heart.encode()), ) - @cases.mark_specifications("IRCv3.2", deprecated=True) + @cases.mark_specifications("IRCv3", deprecated=True) def testSetInvalidUtf8(self): """“Values are unrestricted, except that they MUST be UTF-8.” -- diff --git a/irctest/server_tests/test_monitor.py b/irctest/server_tests/test_monitor.py index 9ee1443..083e48c 100644 --- a/irctest/server_tests/test_monitor.py +++ b/irctest/server_tests/test_monitor.py @@ -66,7 +66,8 @@ class MonitorTestCase(cases.BaseServerTestCase): extra_format=(nick,), ) - @cases.mark_specifications("IRCv3.2") + @cases.mark_specifications("IRCv3") + @cases.mark_isupport("MONITOR") def testMonitorOneDisconnected(self): """“If any of the targets being added are online, the server will generate RPL_MONONLINE numerics listing those targets that are @@ -86,7 +87,8 @@ class MonitorTestCase(cases.BaseServerTestCase): pass self.assertMonoffline(1, "bar") - @cases.mark_specifications("IRCv3.2") + @cases.mark_specifications("IRCv3") + @cases.mark_isupport("MONITOR") def testMonitorOneConnection(self): self.connectClient("foo") self.check_server_support() @@ -95,7 +97,8 @@ class MonitorTestCase(cases.BaseServerTestCase): self.connectClient("bar") self.assertMononline(1, "bar") - @cases.mark_specifications("IRCv3.2") + @cases.mark_specifications("IRCv3") + @cases.mark_isupport("MONITOR") def testMonitorOneConnected(self): """“If any of the targets being added are offline, the server will generate RPL_MONOFFLINE numerics listing those targets that are @@ -114,7 +117,8 @@ class MonitorTestCase(cases.BaseServerTestCase): pass self.assertMonoffline(1, "bar") - @cases.mark_specifications("IRCv3.2") + @cases.mark_specifications("IRCv3") + @cases.mark_isupport("MONITOR") def testMonitorOneConnectionWithQuit(self): self.connectClient("foo") self.check_server_support() @@ -130,7 +134,8 @@ class MonitorTestCase(cases.BaseServerTestCase): self.connectClient("bar") self.assertMononline(1, "bar") - @cases.mark_specifications("IRCv3.2") + @cases.mark_specifications("IRCv3") + @cases.mark_isupport("MONITOR") def testMonitorConnectedAndDisconnected(self): """“If any of the targets being added are online, the server will generate RPL_MONONLINE numerics listing those targets that are @@ -185,7 +190,8 @@ class MonitorTestCase(cases.BaseServerTestCase): "“MONITOR + bar,baz” and “baz” is disconnected: {msg}", ) - @cases.mark_specifications("IRCv3.2") + @cases.mark_specifications("IRCv3") + @cases.mark_isupport("MONITOR") def testUnmonitor(self): self.connectClient("foo") self.check_server_support() @@ -210,7 +216,8 @@ class MonitorTestCase(cases.BaseServerTestCase): fail_msg="Got messages after disconnection of unmonitored " "nick: {got}", ) - @cases.mark_specifications("IRCv3.2") + @cases.mark_specifications("IRCv3") + @cases.mark_isupport("MONITOR") def testMonitorForbidsMasks(self): """“The MONITOR implementation also enhances user privacy by disallowing subscription to hostmasks, allowing users to avoid @@ -247,7 +254,8 @@ class MonitorTestCase(cases.BaseServerTestCase): "was requested via hostmask connected: {}".format(m) ) - @cases.mark_specifications("IRCv3.2") + @cases.mark_specifications("IRCv3") + @cases.mark_isupport("MONITOR") def testTwoMonitoringOneRemove(self): """Tests the following scenario: * foo MONITORs qux @@ -286,7 +294,8 @@ class MonitorTestCase(cases.BaseServerTestCase): extra_format=(messages,), ) - @cases.mark_specifications("IRCv3.2") + @cases.mark_specifications("IRCv3") + @cases.mark_isupport("MONITOR") def testMonitorList(self): def checkMonitorSubjects(messages, client_nick, expected_targets): # collect all the RPL_MONLIST nicks into a set: @@ -320,7 +329,8 @@ class MonitorTestCase(cases.BaseServerTestCase): self.sendLine(1, "MONITOR L") checkMonitorSubjects(self.getMessages(1), "bar", {"bazbat"}) - @cases.mark_specifications("IRCv3.2") + @cases.mark_specifications("IRCv3") + @cases.mark_isupport("MONITOR") def testNickChange(self): # see oragono issue #1076: nickname changes must trigger RPL_MONOFFLINE self.connectClient("bar") diff --git a/irctest/server_tests/test_multi_prefix.py b/irctest/server_tests/test_multi_prefix.py index c6e4964..7171e77 100644 --- a/irctest/server_tests/test_multi_prefix.py +++ b/irctest/server_tests/test_multi_prefix.py @@ -7,7 +7,7 @@ from irctest import cases class MultiPrefixTestCase(cases.BaseServerTestCase): - @cases.mark_specifications("IRCv3.1") + @cases.mark_capabilities("multi-prefix") def testMultiPrefix(self): """“When requested, the multi-prefix client capability will cause the IRC server to send all possible prefixes which apply to a user in NAMES diff --git a/irctest/server_tests/test_multiline.py b/irctest/server_tests/test_multiline.py index 482f936..2c4189e 100644 --- a/irctest/server_tests/test_multiline.py +++ b/irctest/server_tests/test_multiline.py @@ -12,7 +12,7 @@ base_caps = ["message-tags", "batch", "echo-message", "server-time", "labeled-re class MultilineTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): - @cases.mark_specifications("multiline") + @cases.mark_capabilities("draft/multiline") def testBasic(self): self.connectClient( "alice", capabilities=(base_caps + [CAP_NAME]), skip_if_cap_nak=True @@ -79,7 +79,7 @@ class MultilineTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): self.assertNotIn(CONCAT_TAG, msg.tags) self.assertEqual(relayed_fmsgids, [msgid] + [None] * (len(fallback_relay) - 1)) - @cases.mark_specifications("multiline") + @cases.mark_capabilities("draft/multiline") def testBlankLines(self): self.connectClient( "alice", capabilities=(base_caps + [CAP_NAME]), skip_if_cap_nak=True diff --git a/irctest/server_tests/test_regressions.py b/irctest/server_tests/test_regressions.py index 613cff2..547d5f0 100644 --- a/irctest/server_tests/test_regressions.py +++ b/irctest/server_tests/test_regressions.py @@ -52,7 +52,7 @@ class RegressionsTestCase(cases.BaseServerTestCase): ms = self.getMessages(2) self.assertEqual(ms, []) - @cases.mark_specifications("IRCv3.2") + @cases.mark_capabilities("message-tags", "batch", "echo-message", "server-time") def testTagCap(self): # regression test for oragono #754 self.connectClient( diff --git a/irctest/server_tests/test_sasl.py b/irctest/server_tests/test_sasl.py index a68a754..fc5a351 100644 --- a/irctest/server_tests/test_sasl.py +++ b/irctest/server_tests/test_sasl.py @@ -9,7 +9,7 @@ class RegistrationTestCase(cases.BaseServerTestCase): class SaslTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): - @cases.mark_specifications("IRCv3.1") + @cases.mark_specifications("IRCv3") @cases.OptionalityHelper.skipUnlessHasMechanism("PLAIN") def testPlain(self): """PLAIN authentication with correct username/password.""" @@ -54,7 +54,7 @@ class SaslTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): "({expects}), not {got}: {msg}", ) - @cases.mark_specifications("IRCv3.1") + @cases.mark_specifications("IRCv3") @cases.OptionalityHelper.skipUnlessHasMechanism("PLAIN") def testPlainNoAuthzid(self): """“message = [authzid] UTF8NUL authcid UTF8NUL passwd @@ -119,7 +119,7 @@ class SaslTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): "({expects}), not {got}: {msg}", ) - @cases.mark_specifications("IRCv3.1") + @cases.mark_specifications("IRCv3") def testMechanismNotAvailable(self): """“If authentication fails, a 904 or 905 numeric will be sent” -- @@ -141,7 +141,7 @@ class SaslTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): fail_msg="Did not reply with 904 to “AUTHENTICATE FOO”: {msg}", ) - @cases.mark_specifications("IRCv3.1") + @cases.mark_specifications("IRCv3") @cases.OptionalityHelper.skipUnlessHasMechanism("PLAIN") def testPlainLarge(self): """Test the client splits large AUTHENTICATE messages whose payload @@ -202,7 +202,7 @@ class SaslTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): # I don't know how to do it, because it would make the registration # message's length too big for it to be valid. - @cases.mark_specifications("IRCv3.1") + @cases.mark_specifications("IRCv3") @cases.OptionalityHelper.skipUnlessHasMechanism("PLAIN") def testPlainLargeEquals400(self): """Test the client splits large AUTHENTICATE messages whose payload diff --git a/irctest/specifications.py b/irctest/specifications.py index 5e2a437..028c7b8 100644 --- a/irctest/specifications.py +++ b/irctest/specifications.py @@ -5,16 +5,51 @@ import enum class Specifications(enum.Enum): RFC1459 = "RFC1459" RFC2812 = "RFC2812" - IRC301 = "IRCv3.1" - IRC302 = "IRCv3.2" + IRCv3 = "IRCv3" # Mark with capabilities whenever possible Oragono = "Oragono" - Multiline = "multiline" - MessageTags = "message-tags" + + Ircdocs = "ircdocs" + """Any document on ircdocs.horse (especially defs.ircdocs.horse), + excluding modern.ircdocs.horse""" + + Modern = "modern" @classmethod - def of_name(cls, name): + def from_name(cls, name): name = name.upper() for spec in cls: if spec.value.upper() == name: return spec raise ValueError(name) + + +@enum.unique +class Capabilities(enum.Enum): + AWAY_NOTIFY = "away-notify" + BATCH = "batch" + ECHO_MESSAGE = "echo-message" + EXTENDED_JOIN = "extended-join" + LABELED_RESPONSE = "labeled-response" + MESSAGE_TAGS = "message-tags" + MULTILINE = "draft/multiline" + MULTI_PREFIX = "multi-prefix" + SERVER_TIME = "server-time" + + @classmethod + def from_name(cls, name): + try: + return cls(name.lower()) + except ValueError: + raise ValueError(name) from None + + +@enum.unique +class IsupportTokens(enum.Enum): + MONITOR = "MONITOR" + + @classmethod + def from_name(cls, name): + try: + return cls(name.upper()) + except ValueError: + raise ValueError(name) from None diff --git a/pytest.ini b/pytest.ini index 8cafb3b..c07dabb 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,11 +1,27 @@ [pytest] markers = + # specifications RFC1459 RFC2812 - IRCv3.1 - IRCv3.2 - message-tags - multiline + IRCv3 + modern + ircdocs Oragono + + # misc marks strict deprecated + + # capabilities + away-notify + batch + echo-message + extended-join + labeled-response + message-tags + draft/multiline + multi-prefix + server-time + + # isupport tokens + MONITOR