From e012c5248ba80055fea8a5b44dd064f8e1d8b3b1 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sun, 28 Feb 2021 19:09:32 +0100 Subject: [PATCH] Move list_match to its own module, and prepare generalizing AnyStr --- irctest/cases.py | 31 +++---------------- irctest/patma.py | 51 ++++++++++++++++++++++++++++++++ irctest/server_tests/test_cap.py | 20 ++++++------- 3 files changed, 65 insertions(+), 37 deletions(-) create mode 100644 irctest/patma.py diff --git a/irctest/cases.py b/irctest/cases.py index 78be536..6e292ed 100644 --- a/irctest/cases.py +++ b/irctest/cases.py @@ -24,7 +24,7 @@ import unittest import pytest -from . import basecontrollers, client_mock, runner, tls +from . import basecontrollers, client_mock, patma, runner, tls from .authentication import Authentication from .basecontrollers import TestCaseControllerConfig from .exceptions import ConnectionClosed @@ -65,13 +65,6 @@ TController = TypeVar("TController", bound=basecontrollers._BaseController) T = TypeVar("T") -class AnyStr: - """Used as a wildcard when matching message arguments - (see assertMessageMatch and listMatch)""" - - pass - - class ChannelJoinException(Exception): def __init__(self, code: str, params: List[str]): super().__init__(f"Failed to join channel ({code}): {params}") @@ -116,7 +109,7 @@ class _IrcTestCase(unittest.TestCase, Generic[TController]): Takes the message as first arguments, and comparisons to be made as keyword arguments. - Uses self.listMatch on the params argument. + Uses patma.list_match on the params argument. """ error = self.messageDiffers(msg, **kwargs) if error: @@ -130,7 +123,7 @@ class _IrcTestCase(unittest.TestCase, Generic[TController]): def messageDiffers( self, msg: Message, - params: Optional[List[Union[str, Type[AnyStr]]]] = None, + params: Optional[List[Union[str, patma.Operator]]] = None, target: Optional[str] = None, nick: Optional[str] = None, fail_msg: Optional[str] = None, @@ -152,7 +145,7 @@ class _IrcTestCase(unittest.TestCase, Generic[TController]): msg=msg, ) - if params and not self.listMatch(msg.params, params): + if params and not patma.list_match(msg.params, params): fail_msg = fail_msg or "params to be {expects}, got {got}: {msg}" return fail_msg.format( *extra_format, got=msg.params, expects=params, msg=msg @@ -170,22 +163,6 @@ class _IrcTestCase(unittest.TestCase, Generic[TController]): return None - def listMatch( - self, got: List[str], expected: List[Union[str, Type[AnyStr]]] - ) -> bool: - """Returns True iff the list are equal. - The ellipsis (aka. "..." aka triple dots) can be used on the 'expected' - side as a wildcard, matching any *single* value.""" - if len(got) != len(expected): - return False - for (got_value, expected_value) in zip(got, expected): - if expected_value is AnyStr: - # wildcard - continue - if got_value != expected_value: - return False - return True - def assertIn( self, member: Any, diff --git a/irctest/patma.py b/irctest/patma.py new file mode 100644 index 0000000..fe34a47 --- /dev/null +++ b/irctest/patma.py @@ -0,0 +1,51 @@ +"""Pattern-matching utilities""" + +import dataclasses +import re +from typing import List, Union + + +class Operator: + """Used as a wildcards and operators when matching message arguments + (see assertMessageMatch and match_list)""" + + def __init__(self) -> None: + pass + + +class AnyStr(Operator): + """Wildcard matching any string""" + + def __repr__(self) -> str: + return "AnyStr" + + +@dataclasses.dataclass +class StrRe(Operator): + regexp: str + + def __repr__(self) -> str: + return f"StrRe(r'{self.regexp}')" + + +ANYSTR = AnyStr() +"""Singleton, spares two characters""" + + +def match_list(got: List[str], expected: List[Union[str, Operator]]) -> bool: + """Returns True iff the list are equal. + The ellipsis (aka. "..." aka triple dots) can be used on the 'expected' + side as a wildcard, matching any *single* value.""" + if len(got) != len(expected): + return False + for (got_value, expected_value) in zip(got, expected): + if isinstance(expected_value, AnyStr): + # wildcard + continue + elif isinstance(expected_value, StrRe): + if not re.match(expected_value.regexp, got_value): + return False + else: + if got_value != expected_value: + return False + return True diff --git a/irctest/server_tests/test_cap.py b/irctest/server_tests/test_cap.py index 8726260..aaa9860 100644 --- a/irctest/server_tests/test_cap.py +++ b/irctest/server_tests/test_cap.py @@ -1,5 +1,5 @@ from irctest import cases -from irctest.cases import AnyStr +from irctest.patma import ANYSTR from irctest.runner import CapabilityNotSupported, ImplementationChoice @@ -40,7 +40,7 @@ class CapTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): self.assertMessageMatch( m, command="CAP", - params=[AnyStr, "NAK", "foo"], + params=[ANYSTR, "NAK", "foo"], fail_msg="Expected CAP NAK after requesting non-existing " "capability, got {msg}.", ) @@ -67,7 +67,7 @@ class CapTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): self.assertMessageMatch( m, command="CAP", - params=[AnyStr, "NAK", "foo qux bar baz qux quux"], + params=[ANYSTR, "NAK", "foo qux bar baz qux quux"], fail_msg="Expected “CAP NAK :foo qux bar baz qux quux” after " "sending “CAP REQ :foo qux bar baz qux quux”, but got {msg}.", ) @@ -86,7 +86,7 @@ class CapTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): self.assertMessageMatch( m, command="CAP", - params=[AnyStr, "NAK", "foo multi-prefix bar"], + params=[ANYSTR, "NAK", "foo multi-prefix bar"], fail_msg="Expected “CAP NAK :foo multi-prefix bar” after " "sending “CAP REQ :foo multi-prefix bar”, but got {msg}.", ) @@ -95,7 +95,7 @@ class CapTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): self.assertMessageMatch( m, command="CAP", - params=[AnyStr, "NAK", "multi-prefix bar"], + params=[ANYSTR, "NAK", "multi-prefix bar"], fail_msg="Expected “CAP NAK :multi-prefix bar” after " "sending “CAP REQ :multi-prefix bar”, but got {msg}.", ) @@ -104,7 +104,7 @@ class CapTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): self.assertMessageMatch( m, command="CAP", - params=[AnyStr, "NAK", "foo multi-prefix"], + params=[ANYSTR, "NAK", "foo multi-prefix"], fail_msg="Expected “CAP NAK :foo multi-prefix” after " "sending “CAP REQ :foo multi-prefix”, but got {msg}.", ) @@ -114,7 +114,7 @@ class CapTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): self.assertMessageMatch( m, command="CAP", - params=[AnyStr, "ACK", "multi-prefix"], + params=[ANYSTR, "ACK", "multi-prefix"], fail_msg="Expected “CAP ACK :multi-prefix” after " "sending “CAP REQ :multi-prefix”, but got {msg}.", ) @@ -134,7 +134,7 @@ class CapTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): self.sendLine(1, "user user 0 * realname") self.sendLine(1, "CAP END") m = self.getRegistrationMessage(1) - self.assertMessageMatch(m, command="CAP", params=[AnyStr, "ACK", AnyStr]) + self.assertMessageMatch(m, command="CAP", params=[ANYSTR, "ACK", ANYSTR]) self.assertEqual( set(m.params[2].split()), {cap1, cap2}, "Didn't ACK both REQed caps" ) @@ -152,9 +152,9 @@ class CapTestCase(cases.BaseServerTestCase, cases.OptionalityHelper): self.sendLine(1, f"CAP REQ :-{cap2}") m = self.getMessage(1) # Must be either ACK or NAK - if self.messageDiffers(m, command="CAP", params=[AnyStr, "ACK", f"-{cap2}"]): + if self.messageDiffers(m, command="CAP", params=[ANYSTR, "ACK", f"-{cap2}"]): self.assertMessageMatch( - m, command="CAP", params=[AnyStr, "NAK", f"-{cap2}"] + m, command="CAP", params=[ANYSTR, "NAK", f"-{cap2}"] ) raise ImplementationChoice(f"Does not support CAP REQ -{cap2}")