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: deprecate test_tools; replace with tests.pytest_plugin #2003

Merged
merged 3 commits into from
Jan 11, 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
15 changes: 9 additions & 6 deletions sopel/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1134,16 +1134,16 @@ def __call__(self, func):

import sys

import sopel.test_tools # TODO: fix circular import with sopel.bot and sopel.test_tools

# only inject test-related stuff if we're running tests
# see https://stackoverflow.com/a/44595269/5991
if 'pytest' in sys.modules and self.result:
from sopel.tests import pytest_plugin

# avoids doing `import pytest` and causing errors when
# dev-dependencies aren't installed
pytest = sys.modules['pytest']

test = sopel.test_tools.get_example_test(
test = pytest_plugin.get_example_test(
func, self.msg, self.result, self.privmsg, self.admin,
self.owner, self.repeat, self.use_re, self.ignore
)
Expand All @@ -1154,11 +1154,14 @@ def __call__(self, func):
if self.vcr:
test = pytest.mark.vcr(test)

sopel.test_tools.insert_into_module(
pytest_plugin.insert_into_module(
test, func.__module__, func.__name__, 'test_example'
)
sopel.test_tools.insert_into_module(
sopel.test_tools.get_disable_setup(), func.__module__, func.__name__, 'disable_setup'
pytest_plugin.insert_into_module(
pytest_plugin.get_disable_setup(),
func.__module__,
func.__name__,
'disable_setup',
)

record = {
Expand Down
172 changes: 46 additions & 126 deletions sopel/test_tools.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
# coding=utf-8
"""This module has classes and functions that can help in writing tests.
"""This module provided tools that helped to write tests.

.. note::
.. deprecated:: 7.1

This module formerly contained mock classes for bot, bot wrapper, and config
objects. Those are deprecated, and will be removed in Sopel 8.0. New code
should use the new :mod:`.mocks`, :mod:`.factories`, and
:mod:`.pytest_plugin` added in Sopel 7.0.
This module will be **removed in Sopel 8**.

It formerly contained mock classes for the bot, its wrapper, and its config
object. As the module is deprecated, so are they, and they will be removed
as well.

New code should use the :mod:`pytest plugin <sopel.tests.pytest_plugin>`
for Sopel; or should take advantage of the :mod:`~sopel.tests.mocks` and
:mod:`~sopel.tests.factories` modules, both added in Sopel 7.0.

"""
# Copyright 2013, Ari Koivula, <ari@koivu.la>
Expand All @@ -24,7 +29,7 @@
except ImportError:
import configparser as ConfigParser

from sopel import bot, config, loader, plugins, tools, trigger
from sopel import bot, config, tools


__all__ = [
Expand Down Expand Up @@ -133,131 +138,46 @@ def __init__(self, *args, **kwargs):
"""


def get_example_test(tested_func, msg, results, privmsg, admin,
owner, repeat, use_regexp, ignore=[]):
@tools.deprecated('this is now part of sopel.tests.pytest_plugin', '7.1', '8.0')
def get_example_test(*args, **kwargs):
"""Get a function that calls ``tested_func`` with fake wrapper and trigger.

:param callable tested_func: a Sopel callable that accepts a
:class:`~.bot.SopelWrapper` and a :class:`~.trigger.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: :term:`function`
.. deprecated:: 7.1

This is now part of the Sopel pytest plugin at
:mod:`sopel.tests.pytest_plugin`.

"""
def test(configfactory, botfactory, ircfactory):
test_config = TEST_CONFIG.format(
name='NickName',
admin=admin,
owner=owner,
)
settings = configfactory('default.cfg', test_config)
url_schemes = settings.core.auto_url_schemes
mockbot = botfactory(settings)
server = ircfactory(mockbot)
server.channel_joined('#Sopel')

if not hasattr(tested_func, 'commands'):
raise AssertionError('Function is not a command.')

loader.clean_callable(tested_func, settings)
test_rule = plugins.rules.Command.from_callable(settings, tested_func)
parse_results = list(test_rule.parse(msg))
assert parse_results, "Example did not match any command."

match = parse_results[0]
sender = mockbot.nick if privmsg else "#channel"
hostmask = "%s!%s@%s" % (mockbot.nick, "UserName", "example.com")

# TODO enable message tags
full_message = ':{} PRIVMSG {} :{}'.format(hostmask, sender, msg)
pretrigger = trigger.PreTrigger(
mockbot.nick, full_message, url_schemes=url_schemes)
test_trigger = trigger.Trigger(mockbot.settings, pretrigger, match)
pattern = re.compile(r'^%s: ' % re.escape(mockbot.nick))

# setup module
module = sys.modules[tested_func.__module__]
if hasattr(module, 'setup'):
module.setup(mockbot)

def isnt_ignored(value):
"""Return True if value doesn't match any re in ignore list."""
return not any(
re.match(ignored_line, value)
for ignored_line in ignore)

expected_output_count = 0
for _i in range(repeat):
expected_output_count += len(results)
wrapper = bot.SopelWrapper(mockbot, test_trigger)
tested_func(wrapper, test_trigger)

output_triggers = (
trigger.PreTrigger(
mockbot.nick,
message.decode('utf-8'),
url_schemes=url_schemes,
)
for message in wrapper.backend.message_sent
)
output_texts = (
# subtract "Sopel: " when necessary
pattern.sub('', output_trigger.args[-1])
for output_trigger in output_triggers
)
outputs = [text for text in output_texts if isnt_ignored(text)]

# output length
assert len(outputs) == expected_output_count

# output content
for expected, output in zip(results, outputs):
if use_regexp:
message = (
"Output does not match the regex:\n"
"Pattern: %s\n"
"Output: %s"
) % (expected, output)
if not re.match(expected, output):
raise AssertionError(message)
else:
assert expected == output

return test
from sopel.tests import pytest_plugin
return pytest_plugin.get_example_test(*args, **kwargs)


@tools.deprecated('this is now part of sopel.tests.pytest_plugin', '7.1', '8.0')
def get_disable_setup():
import pytest
import py

@pytest.fixture(autouse=True)
def disable_setup(request, monkeypatch):
setup = getattr(request.module, "setup", None)
isfixture = hasattr(setup, "_pytestfixturefunction")
if setup is not None and not isfixture and py.builtin.callable(setup):
monkeypatch.setattr(setup, "_pytestfixturefunction", pytest.fixture(), raising=False)
return disable_setup


def insert_into_module(func, module_name, base_name, prefix):
"""Add a function into a module."""
func.__module__ = module_name
module = sys.modules[module_name]
# Make sure the func method does not overwrite anything.
for i in range(1000):
func.__name__ = str("%s_%s_%s" % (prefix, base_name, i))
if not hasattr(module, func.__name__):
break
setattr(module, func.__name__, func)
"""Get a function to prevent conflict between pytest and plugin's setup.

.. deprecated:: 7.1

This is now part of the Sopel pytest plugin at
:mod:`sopel.tests.pytest_plugin`.

"""
from sopel.tests import pytest_plugin
return pytest_plugin.get_disable_setup()


@tools.deprecated('this is now part of sopel.tests.pytest_plugin', '7.1', '8.0')
def insert_into_module(*args, **kwargs):
"""Add a function into a module.

.. deprecated:: 7.1

This is now part of the Sopel pytest plugin at
:mod:`sopel.tests.pytest_plugin`.

"""
from sopel.tests import pytest_plugin
return pytest_plugin.insert_into_module(*args, **kwargs)


@tools.deprecated('pytest now runs @plugin.example tests directly', '7.1', '8.0')
Expand Down
Loading