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

test: botfactory, UserFactory, and IRCFactory #1731

Merged
merged 4 commits into from
Nov 22, 2019
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
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ env/
MANIFEST
doc/build/*
logs/*
tests
sopel.egg-info/*
*.db
*.pyc
Expand Down
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ Documentation

run
package
tests
32 changes: 32 additions & 0 deletions docs/source/tests.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
=============
Testing tools
=============

.. contents::
:local:
:depth: 2

Fixtures with py.test
=====================

.. automodule:: sopel.tests.pytest_plugin
:members:

Factories
=========

.. automodule:: sopel.tests.factories
:members:

Mocks
=====

.. automodule:: sopel.tests.mocks
:members:


Old testing tools
=================

.. automodule:: sopel.test_tools
:members:
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ def read_reqs(path):
'sopel-config = sopel.cli.config:main',
'sopel-plugins = sopel.cli.plugins:main',
],
'pytest11': [
'pytest-sopel = sopel.tests.pytest_plugin',
],
},
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <4',
)
72 changes: 22 additions & 50 deletions sopel/test_tools.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
# coding=utf-8
"""This module has classes and functions that can help in writing tests.

test_tools.py - Sopel misc tools
Copyright 2013, Ari Koivula, <[email protected]>
Licensed under the Eiffel Forum License 2.

https://sopel.chat
"""
"""This module has classes and functions that can help in writing tests."""
# Copyright 2013, Ari Koivula, <[email protected]>
# Copyright 2019, Florian Strzelecki <[email protected]>
# Licensed under the Eiffel Forum License 2.
from __future__ import unicode_literals, absolute_import, print_function, division

import os
Expand All @@ -20,9 +16,9 @@
import configparser as ConfigParser

from sopel.bot import SopelWrapper
from sopel.irc.abstract_backends import AbstractIRCBackend
import sopel.config
import sopel.config.core_section
import sopel.plugins
import sopel.tools
import sopel.tools.target
import sopel.trigger
Expand All @@ -32,7 +28,6 @@
'MockConfig',
'MockSopel',
'MockSopelWrapper',
'MockIRCBackend',
'get_example_test',
'get_disable_setup',
'insert_into_module',
Expand All @@ -43,30 +38,6 @@
basestring = str


def rawlist(*args):
"""Build a list of raw IRC messages from the lines given as ``*args``.

:return: a list of raw IRC messages as seen by the bot

This is a helper function to build a list of messages without having to
care about encoding or this pesky carriage return::

>>> rawlist('PRIVMSG :Hello!')
[b'PRIVMSG :Hello!\r\n']

"""
return ['{0}\r\n'.format(arg).encode('utf-8') for arg in args]


class MockIRCBackend(AbstractIRCBackend):
def __init__(self, *args, **kwargs):
super(MockIRCBackend, self).__init__(*args, **kwargs)
self.message_sent = []

def send(self, data):
self.message_sent.append(data)


class MockConfig(sopel.config.Config):
def __init__(self):
self.filename = tempfile.mkstemp()[1]
Expand Down Expand Up @@ -149,22 +120,23 @@ class MockSopelWrapper(SopelWrapper):

def get_example_test(tested_func, msg, results, privmsg, admin,
owner, repeat, use_regexp, ignore=[]):
"""Get a function that calls tested_func with fake wrapper and trigger.

Args:
tested_func - A sopel callable that accepts SopelWrapper and Trigger.
msg - Message that is supposed to trigger the command.
results - Expected output from the callable.
privmsg - If true, make the message appear to have sent in a private
message to the bot. If false, make it appear to have come from a
channel.
admin - If true, make the message appear to have come from an admin.
owner - If true, make the message appear to have come from an owner.
repeat - How many times to repeat the test. Useful for tests that
return random stuff.
use_regexp = Bool. If true, results is in regexp format.
ignore - List of strings to ignore.

"""Get a function that calls ``tested_func`` with fake wrapper and trigger.

:param callable tested_func: a Sopel callable that accepts a
``SopelWrapper`` and a ``Trigger``
:param str msg: message that is supposed to trigger the command
:param list results: expected output from the callable
:param bool privmsg: if ``True``, make the message appear to have arrived
in a private message to the bot; otherwise make it
appear to have come from a channel
:param bool admin: make the message appear to have come from an admin
:param bool owner: make the message appear to have come from an owner
:param int repeat: how many times to repeat the test; useful for tests that
return random stuff
:param bool use_regexp: pass ``True`` if ``results`` are in regexp format
:param list ignore: strings to ignore
:return: a test function for ``tested_func``
:rtype: ``callable``
"""
def test():
bot = MockSopel("NickName", admin=admin, owner=owner)
Expand Down
18 changes: 18 additions & 0 deletions sopel/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# coding=utf-8
"""Tests tools, factories, pytest fixtures, and mocks."""
from __future__ import unicode_literals, absolute_import, print_function, division


def rawlist(*args):
"""Build a list of raw IRC messages from the lines given as ``*args``.

:return: a list of raw IRC messages as seen by the bot
:rtype: list

This is a helper function to build a list of messages without having to
care about encoding or this pesky carriage return::

>>> rawlist('PRIVMSG :Hello!')
[b'PRIVMSG :Hello!\\r\\n']
"""
return ['{0}\r\n'.format(arg).encode('utf-8') for arg in args]
120 changes: 120 additions & 0 deletions sopel/tests/factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# coding=utf-8
"""Test factories: they create objects for testing purposes."""
from __future__ import unicode_literals, absolute_import, print_function, division

import re

from sopel import bot, config, plugins, trigger
from .mocks import MockIRCServer, MockUser, MockIRCBackend


class BotFactory(object):
"""Factory to create bot.

.. seealso::

The :func:`~sopel.tests.pytest_plugin.botfactory` fixture can be used
to instantiate this factory.
"""
def preloaded(self, settings, preloads=None):
"""Create a bot and preload its plugins.

:param settings: Sopel's configuration for testing purposes
:type settings: :class:`sopel.config.Config`
:param list preloads: list of plugins to preload, setup, and register
:return: a test instance of the bot
:rtype: :class:`sopel.bot.Sopel`

This will instantiate a :class:`~sopel.bot.Sopel` object, replace its
backend with a :class:`~MockIRCBackend`, and then preload plugins.
This will automatically load the ``coretasks`` plugin, and every other
plugin from ``preloads``::

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

.. note::

This will automatically setup plugins: be careful with plugins that
require access to external services on setup.

You may also need to manually call shutdown routines for the
loaded plugins.

"""
preloads = set(preloads or []) | {'coretasks'}
mockbot = self(settings)

usable_plugins = plugins.get_usable_plugins(settings)
for name in preloads:
plugin = usable_plugins[name][0]
plugin.load()
plugin.setup(mockbot)
plugin.register(mockbot)

return mockbot

def __call__(self, settings):
obj = bot.Sopel(settings, daemon=False)
obj.backend = MockIRCBackend(obj)
return obj


class ConfigFactory(object):
"""Factory to create settings.

.. seealso::

The :func:`~sopel.tests.pytest_plugin.configfactory` fixture can be
used to instantiate this factory.
"""
def __init__(self, tmpdir):
self.tmpdir = tmpdir

def __call__(self, name, data):
tmpfile = self.tmpdir.join(name)
tmpfile.write(data)
return config.Config(tmpfile.strpath)


class TriggerFactory(object):
"""Factory to create trigger.

.. seealso::

The :func:`~sopel.tests.pytest_plugin.triggerfactory` fixture can be
used to instantiate this factory.
"""
def wrapper(self, mockbot, raw, pattern=None):
trigger = self(mockbot, raw, pattern=pattern)
return bot.SopelWrapper(mockbot, trigger)

def __call__(self, mockbot, raw, pattern=None):
return trigger.Trigger(
mockbot.settings,
trigger.PreTrigger(mockbot.nick, raw),
re.match(pattern or r'.*', raw))


class IRCFactory(object):
"""Factory to create mock IRC server.

.. seealso::

The :func:`~sopel.tests.pytest_plugin.ircfactory` fixture can be used
to create this factory.
"""
def __call__(self, mockbot):
return MockIRCServer(mockbot)


class UserFactory(object):
"""Factory to create mock user.

.. seealso::

The :func:`~sopel.tests.pytest_plugin.userfactory` fixture can be used
to create this factory.
"""
def __call__(self, nick=None, user=None, host=None):
return MockUser(nick, user, host)
Loading