184 lines
5.9 KiB
Python
184 lines
5.9 KiB
Python
|
# 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, <ari@koivu.la>
|
||
|
Licensed under the Eiffel Forum License 2.
|
||
|
|
||
|
https://sopel.chat
|
||
|
"""
|
||
|
from __future__ import unicode_literals, absolute_import, print_function, division
|
||
|
|
||
|
import os
|
||
|
import re
|
||
|
import sys
|
||
|
import tempfile
|
||
|
|
||
|
try:
|
||
|
import ConfigParser
|
||
|
except ImportError:
|
||
|
import configparser as ConfigParser
|
||
|
|
||
|
import config
|
||
|
import config.core_section
|
||
|
import tools
|
||
|
import trigger
|
||
|
|
||
|
|
||
|
class MockConfig(config.Config):
|
||
|
def __init__(self):
|
||
|
self.filename = tempfile.mkstemp()[1]
|
||
|
#self._homedir = tempfile.mkdtemp()
|
||
|
#self.filename = os.path.join(self._homedir, 'test.cfg')
|
||
|
self.parser = ConfigParser.RawConfigParser(allow_no_value=True)
|
||
|
self.parser.add_section('core')
|
||
|
self.parser.set('core', 'owner', 'Embolalia')
|
||
|
self.define_section('core', sopel.config.core_section.CoreSection)
|
||
|
self.get = self.parser.get
|
||
|
|
||
|
def define_section(self, name, cls_):
|
||
|
if not self.parser.has_section(name):
|
||
|
self.parser.add_section(name)
|
||
|
setattr(self, name, cls_(self, name))
|
||
|
|
||
|
|
||
|
class MockSopel(object):
|
||
|
def __init__(self, nick, admin=False, owner=False):
|
||
|
self.nick = nick
|
||
|
self.user = "sopel"
|
||
|
|
||
|
self.channels = ["#channel"]
|
||
|
|
||
|
self.memory = sopel.tools.SopelMemory()
|
||
|
|
||
|
self.ops = {}
|
||
|
self.halfplus = {}
|
||
|
self.voices = {}
|
||
|
|
||
|
self.config = MockConfig()
|
||
|
self._init_config()
|
||
|
|
||
|
if admin:
|
||
|
self.config.core.admins = [self.nick]
|
||
|
if owner:
|
||
|
self.config.core.owner = self.nick
|
||
|
|
||
|
def _init_config(self):
|
||
|
cfg = self.config
|
||
|
cfg.parser.set('core', 'admins', '')
|
||
|
cfg.parser.set('core', 'owner', '')
|
||
|
home_dir = os.path.join(os.path.expanduser('~'), '.sopel')
|
||
|
if not os.path.exists(home_dir):
|
||
|
os.mkdir(home_dir)
|
||
|
cfg.parser.set('core', 'homedir', home_dir)
|
||
|
|
||
|
|
||
|
class MockSopelWrapper(object):
|
||
|
def __init__(self, bot, pretrigger):
|
||
|
self.bot = bot
|
||
|
self.pretrigger = pretrigger
|
||
|
self.output = []
|
||
|
|
||
|
def _store(self, string, recipent=None):
|
||
|
self.output.append(string.strip())
|
||
|
|
||
|
say = reply = action = _store
|
||
|
|
||
|
def __getattr__(self, attr):
|
||
|
return getattr(self.bot, attr)
|
||
|
|
||
|
|
||
|
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. Usefull for tests that
|
||
|
return random stuff.
|
||
|
use_regexp = Bool. If true, results is in regexp format.
|
||
|
ignore - List of strings to ignore.
|
||
|
|
||
|
"""
|
||
|
def test():
|
||
|
bot = MockSopel("NickName", admin=admin, owner=owner)
|
||
|
|
||
|
match = None
|
||
|
if hasattr(tested_func, "commands"):
|
||
|
for command in tested_func.commands:
|
||
|
regexp = sopel.tools.get_command_regexp(".", command)
|
||
|
match = regexp.match(msg)
|
||
|
if match:
|
||
|
break
|
||
|
assert match, "Example did not match any command."
|
||
|
|
||
|
sender = bot.nick if privmsg else "#channel"
|
||
|
hostmask = "%s!%s@%s " % (bot.nick, "UserName", "example.com")
|
||
|
# TODO enable message tags
|
||
|
full_message = ':{} PRIVMSG {} :{}'.format(hostmask, sender, msg)
|
||
|
|
||
|
pretrigger = sopel.trigger.PreTrigger(bot.nick, full_message)
|
||
|
trigger = sopel.trigger.Trigger(bot.config, pretrigger, match)
|
||
|
|
||
|
module = sys.modules[tested_func.__module__]
|
||
|
if hasattr(module, 'setup'):
|
||
|
module.setup(bot)
|
||
|
|
||
|
def isnt_ignored(value):
|
||
|
"""Return True if value doesn't match any re in ignore list."""
|
||
|
for ignored_line in ignore:
|
||
|
if re.match(ignored_line, value):
|
||
|
return False
|
||
|
return True
|
||
|
|
||
|
for _i in range(repeat):
|
||
|
wrapper = MockSopelWrapper(bot, trigger)
|
||
|
tested_func(wrapper, trigger)
|
||
|
wrapper.output = list(filter(isnt_ignored, wrapper.output))
|
||
|
assert len(wrapper.output) == len(results)
|
||
|
for result, output in zip(results, wrapper.output):
|
||
|
if type(output) is bytes:
|
||
|
output = output.decode('utf-8')
|
||
|
if use_regexp:
|
||
|
if not re.match(result, output):
|
||
|
assert result == output
|
||
|
else:
|
||
|
assert result == output
|
||
|
|
||
|
return test
|
||
|
|
||
|
|
||
|
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)
|
||
|
|
||
|
|
||
|
def run_example_tests(filename, tb='native', multithread=False, verbose=False):
|
||
|
# These are only required when running tests, so import them here rather
|
||
|
# than at the module level.
|
||
|
import pytest
|
||
|
from multiprocessing import cpu_count
|
||
|
|
||
|
args = [filename, "-s"]
|
||
|
args.extend(['--tb', tb])
|
||
|
if verbose:
|
||
|
args.extend(['-v'])
|
||
|
if multithread and cpu_count() > 1:
|
||
|
args.extend(["-n", str(cpu_count())])
|
||
|
|
||
|
pytest.main(args)
|