Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tests: MockIRCServer methods should block by default #2065

Merged
merged 5 commits into from
May 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions sopel/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def preloaded(self, settings, preloads=None):
every other plugin from ``preloads``::

factory = BotFactory()
bot = factory.with_autoloads(settings, ['emoticons', 'remind'])
bot = factory.preloaded(settings, ['emoticons', 'remind'])

.. note::

Expand Down Expand Up @@ -108,8 +108,8 @@ class IRCFactory(object):
The :func:`~sopel.tests.pytest_plugin.ircfactory` fixture can be used
to create this factory.
"""
def __call__(self, mockbot):
return MockIRCServer(mockbot)
def __call__(self, mockbot, join_threads=True):
return MockIRCServer(mockbot, join_threads)


class UserFactory(object):
Expand Down
129 changes: 122 additions & 7 deletions sopel/tests/mocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,28 +35,50 @@ class MockIRCServer(object):

:param bot: test bot instance to send messages to
:type bot: :class:`sopel.bot.Sopel`
:param bool join_threads: whether message functions should join running
threads before returning (default: ``True``)

This mock object helps developers when they want to simulate an IRC server
sending messages to the bot.

The default ``join_threads`` behavior is suitable for testing most common
plugin callables, and ensures that all callables dispatched by the ``bot``
in response to messages sent via this ``MockIRCServer`` are finished
running before execution can continue. If set to ``False``, the mock
server will not wait for the bot to finish processing threaded
:term:`callables <Plugin callable>` before returning.

.. note::

You can override ``join_threads`` on a per-method-call basis with the
``blocking`` arguments to the instance methods below.

The :class:`~sopel.tests.factories.IRCFactory` factory can be used to
create such mock object, either directly or by using ``py.test`` and the
:func:`~sopel.tests.pytest_plugin.ircfactory` fixture.

.. versionadded:: 7.1

The ``join_threads`` parameter.
"""
def __init__(self, bot):
def __init__(self, bot, join_threads=True):
self.bot = bot
self.join_threads = join_threads
# TODO: `blocking` method args below should be made kwarg-ONLY in py3

@property
def chanserv(self):
"""ChanServ's message prefix."""
return 'ChanServ!ChanServ@services.'

def channel_joined(self, channel, users=None):
def channel_joined(self, channel, users=None, blocking=None):
"""Send events as if the bot just joined a channel.

:param str channel: channel to send message for
:param list users: list (or tuple) of nicknames that will be present
in the ``RPL_NAMREPLY`` event
:param bool blocking: whether to block until all triggered threads
have finished (optional)

This will send 2 messages to the bot:

Expand All @@ -75,9 +97,22 @@ def channel_joined(self, channel, users=None):
of users automatically, and you **should not** pass it in the ``users``
parameter.

This is particulary useful to populate the bot's memory of who is in
This is particularly useful to populate the bot's memory of who is in
a channel.

If ``blocking`` is ``True``, this method will wait to join all running
triggers' threads before returning. Setting it to ``False`` will skip
this step. If not specified, this :class:`MockIRCServer` instance's
``join_threads`` argument will be obeyed.

.. versionadded:: 7.1

The ``blocking`` parameter.

.. seealso::

The ``join_threads`` argument to :class:`MockIRCServer`.

.. note::

To add a user to a channel after using this method, you should
Expand All @@ -102,19 +137,38 @@ def channel_joined(self, channel, users=None):
)
self.bot.on_message(message)

def mode_set(self, channel, flags, users):
if (blocking is None and self.join_threads) or blocking:
for t in self.bot.running_triggers:
t.join()

def mode_set(self, channel, flags, users, blocking=None):
"""Send a MODE event for a ``channel``

:param str channel: channel receiving the MODE event
:param str flags: MODE flags set
:param list users: users getting the MODE flags
:param bool blocking: whether to block until all triggered threads
have finished (optional)

This will send a MODE message as if ``ChanServ`` added/removed channel
modes for a set of ``users``. This method assumes the ``flags``
parameter follows the `IRC specification for MODE`__::

factory.mode_set('#test', '+vo-v', ['UserV', UserOP', 'UserAnon'])

If ``blocking`` is ``True``, this method will wait to join all running
triggers' threads before returning. Setting it to ``False`` will skip
this step. If not specified, this :class:`MockIRCServer` instance's
``join_threads`` argument will be obeyed.

.. versionadded:: 7.1

The ``blocking`` parameter.

.. seealso::

The ``join_threads`` argument to :class:`MockIRCServer`.

.. __: https://tools.ietf.org/html/rfc1459#section-4.2.3
"""
message = ':{chanserv} MODE {channel} {flags} {users}'.format(
Expand All @@ -125,57 +179,114 @@ def mode_set(self, channel, flags, users):
)
self.bot.on_message(message)

def join(self, user, channel):
if (blocking is None and self.join_threads) or blocking:
for t in self.bot.running_triggers:
t.join()

def join(self, user, channel, blocking=None):
"""Send a ``channel`` JOIN event from ``user``.

:param user: factory for the user who joins the ``channel``
:type user: :class:`MockUser`
:param str channel: channel the ``user`` joined
:param bool blocking: whether to block until all triggered threads
have finished (optional)

This will send a ``JOIN`` message as if ``user`` just joined the
channel::

factory.join(MockUser('NewUser'), '#test')

If ``blocking`` is ``True``, this method will wait to join all running
triggers' threads before returning. Setting it to ``False`` will skip
this step. If not specified, this :class:`MockIRCServer` instance's
``join_threads`` argument will be obeyed.

.. versionadded:: 7.1

The ``blocking`` parameter.

.. seealso::

The ``join_threads`` argument to :class:`MockIRCServer`.

.. seealso::

This function is a shortcut to call the bot with the result from
the user factory's :meth:`~MockUser.join` method.
"""
self.bot.on_message(user.join(channel))

def say(self, user, channel, text):
if (blocking is None and self.join_threads) or blocking:
for t in self.bot.running_triggers:
t.join()

def say(self, user, channel, text, blocking=None):
"""Send a ``PRIVMSG`` to ``channel`` by ``user``.

:param user: factory for the user who sends a message to ``channel``
:type user: :class:`MockUser`
:param str channel: recipient of the ``user``'s ``PRIVMSG``
:param str text: content of the message sent to the ``channel``
:param bool blocking: whether to block until all triggered threads
have finished (optional)

This will send a ``PRIVMSG`` message as if ``user`` sent it to the
``channel``, and the server forwarded it to its clients::

factory.say(MockUser('NewUser'), '#test', '.shrug')

If ``blocking`` is ``True``, this method will wait to join all running
triggers' threads before returning. Setting it to ``False`` will skip
this step. If not specified, this :class:`MockIRCServer` instance's
``join_threads`` argument will be obeyed.

.. versionadded:: 7.1

The ``blocking`` parameter.

.. seealso::

The ``join_threads`` argument to :class:`MockIRCServer`.

.. seealso::

This function is a shortcut to call the bot with the result from
the user's :meth:`~MockUser.privmsg` method.
"""
self.bot.on_message(user.privmsg(channel, text))

def pm(self, user, text):
if (blocking is None and self.join_threads) or blocking:
for t in self.bot.running_triggers:
t.join()

def pm(self, user, text, blocking=None):
"""Send a ``PRIVMSG`` to the bot by a ``user``.

:param user: factory for the user object who sends a message
:type user: :class:`MockUser`
:param str text: content of the message sent to the bot
:param bool blocking: whether to block until all triggered threads
have finished (optional)

This will send a ``PRIVMSG`` message as forwarded by the server for
a ``user`` sending it to the bot::

factory.pm(MockUser('NewUser'), 'A private word.')

If ``blocking`` is ``True``, this method will wait to join all running
triggers' threads before returning. Setting it to ``False`` will skip
this step. If not specified, this :class:`MockIRCServer` instance's
``join_threads`` argument will be obeyed.

.. versionadded:: 7.1

The ``blocking`` parameter.

.. seealso::

The ``join_threads`` argument to :class:`MockIRCServer`.

.. seealso::

This function is a shortcut to call the bot with the result from
Expand All @@ -184,6 +295,10 @@ def pm(self, user, text):
"""
self.bot.on_message(user.privmsg(self.bot.nick, text))

if (blocking is None and self.join_threads) or blocking:
for t in self.bot.running_triggers:
t.join()


class MockUser(object):
"""Fake user that can generate messages to send to a bot.
Expand Down
2 changes: 1 addition & 1 deletion sopel/tests/pytest_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ def test_mycommand(configfactory, botfactory, ircfactory, userfactory):
irc = ircfactory(bot)
user = userfactory('User')

irc.say(user, '#test', '.mycommand'))
irc.say(user, '#test', '.mycommand')

assert bot.backend.message_sent == rawlist(
'PRIVMSG #test :My plugin replied this.'
Expand Down
20 changes: 0 additions & 20 deletions test/modules/test_modules_isup.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,6 @@ def test_isup_command_ok(irc, bot, user, requests_mock):

irc.pm(user, '.isup example.com')

for t in bot.running_triggers:
# TODO: remove when botfactory can force everything to be unthreaded
t.join()

assert len(bot.backend.message_sent) == 1, (
'.isup command should output exactly one line')
assert bot.backend.message_sent == rawlist(
Expand All @@ -108,10 +104,6 @@ def test_isup_command_http_error(irc, bot, user, requests_mock):

irc.pm(user, '.isup example.com')

for t in bot.running_triggers:
# TODO: remove when botfactory can force everything to be unthreaded
t.join()

assert len(bot.backend.message_sent) == 1, (
'.isup command should output exactly one line')
assert bot.backend.message_sent == rawlist(
Expand All @@ -128,10 +120,6 @@ def test_isup_command_unparseable(irc, bot, user, requests_mock):

irc.pm(user, '.isup .foo')

for t in bot.running_triggers:
# TODO: remove when botfactory can force everything to be unthreaded
t.join()

assert len(bot.backend.message_sent) == 1, (
'.isup command should output exactly one line')
assert bot.backend.message_sent == rawlist(
Expand Down Expand Up @@ -170,10 +158,6 @@ def test_isup_command_requests_error(irc, bot, user, requests_mock, exc, result)

irc.pm(user, '.isup {}'.format(url))

for t in bot.running_triggers:
# TODO: remove when botfactory can force everything to be unthreaded
t.join()

assert len(bot.backend.message_sent) == 1, (
'.isup command should output exactly one line')
assert bot.backend.message_sent == rawlist(
Expand All @@ -189,10 +173,6 @@ def test_isupinsecure_command(irc, bot, user, requests_mock):

irc.pm(user, '.isupinsecure https://example.com')

for t in bot.running_triggers:
# TODO: remove when botfactory can force everything to be unthreaded
t.join()

assert len(bot.backend.message_sent) == 1, (
'.isupinsecure command should output exactly one line')
assert bot.backend.message_sent == rawlist(
Expand Down