More tests

This commit is contained in:
dave 2017-12-03 20:54:39 -08:00
parent 585efb2c18
commit e8652c37c8
13 changed files with 262 additions and 26 deletions

View File

@ -31,6 +31,8 @@ class IRCCore(object):
self.rate_max = float(rate_max) self.rate_max = float(rate_max)
self.rate_int = float(rate_int) self.rate_int = float(rate_int)
self.reconnect_delay = 3.0
self.connected = False self.connected = False
"""If we're connected or not""" """If we're connected or not"""
@ -60,7 +62,7 @@ class IRCCore(object):
self.initHooks() self.initHooks()
self.outputq = asyncio.Queue() self.outputq = asyncio.Queue()
self._loop.call_soon(asyncio.ensure_future, self.outputqueue()) self._loop.call_soon_threadsafe(asyncio.ensure_future, self.outputqueue())
async def loop(self, loop): async def loop(self, loop):
while self.alive: while self.alive:
@ -94,8 +96,8 @@ class IRCCore(object):
self.writer.close() self.writer.close()
if self.alive: if self.alive:
# TODO ramp down reconnect attempts # TODO ramp down reconnect attempts
logging.info("Reconnecting in 3s...") logging.info("Reconnecting in {}s...".format(self.reconnect_delay))
await asyncio.sleep(3) await asyncio.sleep(self.reconnect_delay)
async def outputqueue(self): async def outputqueue(self):
bucket = burstbucket(self.rate_max, self.rate_int) bucket = burstbucket(self.rate_max, self.rate_int)

View File

@ -13,7 +13,7 @@ from .common import load as pload
from .common import messageHasCommand from .common import messageHasCommand
class ModuleBase: class ModuleBase(object):
"""All modules will extend this class """All modules will extend this class
:param bot: A reference to the main bot passed when this module is created :param bot: A reference to the main bot passed when this module is created

View File

@ -198,7 +198,7 @@ class Remind(ModuleBase):
else: else:
return zonestr return zonestr
@info("after <duration> have the bot remind after", cmds=["after", "in"]) @info("after <duration> <reminder> have the bot remind after", cmds=["after", "in"])
@command("after", "in", allow_private=True) @command("after", "in", allow_private=True)
def remindin(self, msg, cmd): def remindin(self, msg, cmd):
replyTo = msg.args[0] replyTo = msg.args[0]

View File

@ -64,13 +64,7 @@ class Connection:
c = self.connection.cursor() c = self.connection.cursor()
return c return c
def escape(self, s): # Opens the sqlite database / attempts to create it if it doesn't exist yet
raise NotImplementedError
def ondisable(self):
self.connection.close()
# Connects to the database server, and selects a database (Or attempts to create it if it doesn't exist yet)
def _connect(self): def _connect(self):
self.log.info("Sqlite: opening database %s" % self.master.getFilePath(self.dbname)) self.log.info("Sqlite: opening database %s" % self.master.getFilePath(self.dbname))
self.connection = sqlite3.connect(self.master.getFilePath(self.dbname), check_same_thread=False) self.connection = sqlite3.connect(self.master.getFilePath(self.dbname), check_same_thread=False)

View File

@ -1,3 +1,4 @@
apipkg==1.4
appdirs==1.4.3 appdirs==1.4.3
certifi==2017.4.17 certifi==2017.4.17
chardet==3.0.4 chardet==3.0.4
@ -5,6 +6,7 @@ cheroot==5.9.1
CherryPy==12.0.1 CherryPy==12.0.1
coverage==4.4.2 coverage==4.4.2
decorator==4.0.11 decorator==4.0.11
execnet==1.5.0
idna==2.5 idna==2.5
ipdb==0.10.3 ipdb==0.10.3
ipython==6.0.0 ipython==6.0.0
@ -30,6 +32,8 @@ pyparsing==2.2.0
PySocks==1.6.7 PySocks==1.6.7
pytest==3.2.5 pytest==3.2.5
pytest-cov==2.5.1 pytest-cov==2.5.1
pytest-forked==0.2
pytest-xdist==1.20.1
pytz==2017.3 pytz==2017.3
pyzmq==16.0.2 pyzmq==16.0.2
requests==2.18.1 requests==2.18.1

View File

@ -3,4 +3,4 @@
export PYTHONUNBUFFERED=1 export PYTHONUNBUFFERED=1
export PYTHONPATH=. export PYTHONPATH=.
py.test --cov=pyircbot --cov-report html tests/ py.test --cov=pyircbot --cov-report html -n 4 tests/

View File

@ -1,8 +1,9 @@
import os import os
import sys import sys
import pytest import pytest
from random import randint
from threading import Thread from threading import Thread
from random import randint
from pyircbot import PyIRCBot
from pyircbot.pyircbot import PrimitiveBot from pyircbot.pyircbot import PrimitiveBot
from pyircbot.irccore import IRCEvent, UserPrefix from pyircbot.irccore import IRCEvent, UserPrefix
from unittest.mock import MagicMock from unittest.mock import MagicMock
@ -20,6 +21,7 @@ class FakeBaseBot(PrimitiveBot):
def __init__(self, config): def __init__(self, config):
super().__init__(config) super().__init__(config)
self.act_PRIVMSG = MagicMock() self.act_PRIVMSG = MagicMock()
self._modules = []
def feed_line(self, trailing, cmd="PRIVMSG", args=["#test"], sender=("chatter", "root", "cia.gov")): def feed_line(self, trailing, cmd="PRIVMSG", args=["#test"], sender=("chatter", "root", "cia.gov")):
""" """
@ -36,13 +38,27 @@ class FakeBaseBot(PrimitiveBot):
if validation: if validation:
hook.method(msg, validation) hook.method(msg, validation)
def closeAllModules(self):
for modname in self._modules:
self.unloadmodule(modname)
def loadmodule(self, module_name):
super().loadmodule(module_name)
self._modules.append(module_name)
def unloadmodule(self, module_name):
super().unloadmodule(module_name)
self._modules.remove(module_name)
@pytest.fixture @pytest.fixture
def fakebot(): def fakebot(tmpdir):
# TODO copy data tree to isolated place so each fakebot() is isolated # TODO copy data tree to isolated place so each fakebot() is isolated
bot = FakeBaseBot({"bot": {"datadir": "./examples/data/"}, os.mkdir(os.path.join(tmpdir, "data"))
bot = FakeBaseBot({"bot": {"datadir": tmpdir},
"module_configs": {}}) "module_configs": {}})
yield bot yield bot
bot.closeAllModules()
@pytest.fixture @pytest.fixture
@ -79,3 +95,70 @@ def ircserver():
server_t.start() server_t.start()
yield port, server yield port, server
server.stop() server.stop()
@pytest.fixture
def livebot(ircserver, tmpdir):
port, server = ircserver
channel = "#test" + str(randint(100000, 1000000))
nick = "testbot" + str(randint(100000, 1000000))
config = {
"bot": {
"datadir": tmpdir,
"rpcbind": "0.0.0.0",
"rpcport": -1,
"usermodules": []
},
"connection": {
"servers": [
["localhost", port]
],
"force_ipv6": False,
"rate_limit": {
"rate_max": 5.0,
"rate_int": 1.1
}
},
"modules": [
"PingResponder",
"Services"
],
"module_configs": {
"Services": {
"user": {
"nick": [
nick,
nick + "_",
nick + "__"
],
"password": "nickservpassword",
"username": "pyircbot3",
"hostname": "pyircbot3.domain.com",
"realname": "pyircbot3"
},
"ident": {
"enable": "no",
"to": "nickserv",
"command": "identify %(password)s",
"ghost": "no",
"ghost_to": "nickserv",
"ghost_cmd": "ghost %(nick)s %(password)s"
},
"channels": [
channel
],
"privatechannels": {
"to": "chanserv",
"command": "invite %(channel)s",
"list": []
}
}
}
}
bot = PyIRCBot(config)
bot_t = Thread(target=bot.run, daemon=True)
# bot_t.start()
yield port, server, bot, bot_t, channel, nick
bot.kill(message="bye", forever=True)

View File

@ -1,9 +1,20 @@
import os
import pytest import pytest
from tests.lib import * # NOQA - fixtures from tests.lib import * # NOQA - fixtures
@pytest.fixture @pytest.fixture
def bot(fakebot): def bot(fakebot):
fakebot.botconfig["module_configs"]["ASCII"] = {
"line_delay": 1.1,
"allow_parallel": False,
"allow_hilight": True,
"list_max": 15
}
adir = os.path.join(fakebot.botconfig["bot"]["datadir"], "data", "ASCII")
os.makedirs(adir, exist_ok=True)
with open(os.path.join(adir, "test.txt"), "w") as f:
f.write("hello world!")
fakebot.loadmodule("ASCII") fakebot.loadmodule("ASCII")
return fakebot return fakebot
@ -11,3 +22,8 @@ def bot(fakebot):
def test_ascii(bot): def test_ascii(bot):
bot.feed_line(".ascii test") bot.feed_line(".ascii test")
bot.act_PRIVMSG.assert_called_once_with('#test', 'hello world!') bot.act_PRIVMSG.assert_called_once_with('#test', 'hello world!')
def test_listascii(bot):
bot.feed_line(".listascii")
bot.act_PRIVMSG.assert_called_once_with('#test', 'Avalable asciis: test')

View File

@ -15,13 +15,13 @@ def calcbot(fakebot):
"delayCalcSpecific": 0, "delayCalcSpecific": 0,
"delayMatch": 0} "delayMatch": 0}
fakebot.loadmodule("SQLite") fakebot.loadmodule("SQLite")
tables = ["calc_addedby", "calc_channels", "calc_definitions", "calc_words"]
with closing(fakebot.moduleInstances["SQLite"].opendb("calc.db")) as db: with closing(fakebot.moduleInstances["SQLite"].opendb("calc.db")) as db:
for q in ["DROP TABLE calc_addedby;", for t in tables:
"DROP TABLE calc_channels;", db.query("DROP TABLE IF EXISTS `{}`;".format(t))
"DROP TABLE calc_definitions;",
"DROP TABLE calc_words;"]:
db.query(q)
fakebot.loadmodule("Calc") fakebot.loadmodule("Calc")
for t in tables:
assert fakebot.moduleInstances["Calc"].sql.tableExists("calc_addedby")
return fakebot return fakebot

View File

@ -19,10 +19,8 @@ def nickbot(fakebot):
"delayMatch": 0} "delayMatch": 0}
fakebot.loadmodule("SQLite") fakebot.loadmodule("SQLite")
with closing(fakebot.moduleInstances["SQLite"].opendb("attributes.db")) as db: with closing(fakebot.moduleInstances["SQLite"].opendb("attributes.db")) as db:
for q in ["DROP TABLE attribute;", for table in ["attribute", "items", "values"]:
"DROP TABLE items;", db.query("DROP TABLE IF EXISTS `{}`;".format(table))
"DROP TABLE `values`;"]:
db.query(q)
fakebot.loadmodule("AttributeStorageLite") fakebot.loadmodule("AttributeStorageLite")
fakebot.loadmodule("NickUser") fakebot.loadmodule("NickUser")
return fakebot return fakebot
@ -37,7 +35,15 @@ def test_blind_login(nickbot):
nickbot.act_PRIVMSG.assert_called_once_with('chatter', '.login: You must first set a password with .setpass') nickbot.act_PRIVMSG.assert_called_once_with('chatter', '.login: You must first set a password with .setpass')
def test_no_pms(nickbot):
nickbot.feed_line(".login foobar")
nickbot.act_PRIVMSG.assert_not_called()
def test_register(nickbot): def test_register(nickbot):
pm(nickbot, ".setpass")
nickbot.act_PRIVMSG.assert_called_once_with('chatter', '.setpass: usage: ".setpass newpass" or ".setpass oldpass newpass"')
nickbot.act_PRIVMSG.reset_mock()
pm(nickbot, ".setpass foobar") pm(nickbot, ".setpass foobar")
nickbot.act_PRIVMSG.assert_called_once_with('chatter', '.setpass: Your password has been set to "foobar".') nickbot.act_PRIVMSG.assert_called_once_with('chatter', '.setpass: Your password has been set to "foobar".')
nickbot.act_PRIVMSG.reset_mock() nickbot.act_PRIVMSG.reset_mock()
@ -45,6 +51,9 @@ def test_register(nickbot):
def test_register_login(nickbot): def test_register_login(nickbot):
test_register(nickbot) test_register(nickbot)
pm(nickbot, ".login")
nickbot.act_PRIVMSG.assert_called_once_with('chatter', '.login: usage: ".login password"')
nickbot.act_PRIVMSG.reset_mock()
pm(nickbot, ".login foobar") pm(nickbot, ".login foobar")
nickbot.act_PRIVMSG.assert_called_once_with('chatter', '.login: You have been logged in from: cia.gov') nickbot.act_PRIVMSG.assert_called_once_with('chatter', '.login: You have been logged in from: cia.gov')
nickbot.act_PRIVMSG.reset_mock() nickbot.act_PRIVMSG.reset_mock()

View File

@ -0,0 +1,41 @@
import pytest
from contextlib import closing
from tests.lib import * # NOQA - fixtures
from time import sleep
import datetime
@pytest.fixture
def rbot(fakebot):
"""
Provide a bot loaded with the Calc module. Clear the database.
"""
fakebot.botconfig["module_configs"]["Remind"] = {"mytimezone": "US/Pacific", "precision": 0.2}
fakebot.loadmodule("SQLite")
with closing(fakebot.moduleInstances["SQLite"].opendb("remind.db")) as db:
db.query("DROP TABLE IF EXISTS `reminders`;")
fakebot.loadmodule("Remind")
return fakebot
@pytest.mark.slow
def test_remind_in(rbot):
rbot.feed_line(".in 3s frig off")
rbot.act_PRIVMSG.assert_called_once_with('#test', 'chatter: Ok, talk to you in approx 0h0m')
rbot.act_PRIVMSG.reset_mock()
sleep(2.5)
rbot.act_PRIVMSG.assert_not_called()
sleep(1)
rbot.act_PRIVMSG.assert_called_once_with('#test', 'chatter: Reminder: frig off')
@pytest.mark.slow
def test_remind_at(rbot):
then = datetime.datetime.now() + datetime.timedelta(seconds=3)
rbot.feed_line(".at {} frig off".format(then.strftime("%H:%M:%SPDT")))
rbot.act_PRIVMSG.assert_called_once_with('#test', 'chatter: Ok, will do. Approx 0h0m to go.')
rbot.act_PRIVMSG.reset_mock()
sleep(2)
rbot.act_PRIVMSG.assert_not_called()
sleep(2)
rbot.act_PRIVMSG.assert_called_once_with('#test', 'chatter: Reminder: frig off')

View File

@ -11,7 +11,7 @@ def tellbot(fakebot):
fakebot.botconfig["module_configs"]["Tell"] = {"max": 10, "maxage": 2678400} fakebot.botconfig["module_configs"]["Tell"] = {"max": 10, "maxage": 2678400}
fakebot.loadmodule("SQLite") fakebot.loadmodule("SQLite")
with closing(fakebot.moduleInstances["SQLite"].opendb("tell.db")) as db: with closing(fakebot.moduleInstances["SQLite"].opendb("tell.db")) as db:
db.query("DROP TABLE tells;") db.query("DROP TABLE IF EXISTS tells;")
fakebot.loadmodule("Tell") fakebot.loadmodule("Tell")
return fakebot return fakebot

87
tests/test_rpcclient.py Normal file
View File

@ -0,0 +1,87 @@
from tests.lib import * # NOQA - fixtures
from unittest.mock import MagicMock, call
from pyircbot.rpc import BotRPC
from pyircbot.rpcclient import connect
from random import randint
from time import sleep
def test_rpc(monkeypatch):
port = randint(40000, 65000)
m = MagicMock()
m.botconfig = {"bot": {"rpcbind": "127.0.0.1", "rpcport": port}}
server = BotRPC(m)
sleep(0.05)
calltrack = MagicMock()
def fake(*args):
calltrack(*args)
return args
for k, v in server.server.funcs.items():
server.server.funcs[k] = fake
methods = [["importModule", "foo"],
["deportModule", "foo"],
["loadModule", "foo"],
["unloadModule", "foo"],
["reloadModule", "foo"],
["redoModule", "foo"],
["getLoadedModules"],
["pluginCommand", "foo", "foo", "foo"],
["setPluginVar", "foo", "foo"],
["getPluginVar", "foo", "foo", "foo"],
["eval", "foo"],
["exec", "foo"],
["quit", "foo"]]
client = connect("127.0.0.1", port)
for test in methods:
method = test[0]
args = test[1:]
server.server.funcs[method] = fake
print("Calling {} with: {}".format(method, args))
getattr(client, method)(*args)
calltrack.assert_called_once_with(*args)
calltrack.reset_mock()
def test_rpc_internal(monkeypatch):
port = randint(40000, 65000)
m = MagicMock()
m.botconfig = {"bot": {"rpcbind": "127.0.0.1", "rpcport": port}}
server = BotRPC(m)
methods = [["importModule", "foo"],
["deportModule", "foo"],
["loadModule", "foo"],
["unloadModule", "foo"],
["redoModule", "foo"],]
for test in methods:
method = test[0]
args = test[1:]
getattr(server, method)(*args)
getattr(m, method.lower()).assert_called_once_with(*args)
getattr(m, method.lower()).reset_mock()
m.moduleInstances = {"Foo": None, "Bar": None}
assert server.getLoadedModules() == ["Foo", "Bar"]
m.reset_mock()
server.reloadModule("Foo")
m.unloadmodule.assert_called_once_with("Foo")
m.loadmodule.assert_called_once_with("Foo")
m.reset_mock()
# ["pluginCommand", "foo", "foo", "foo"],
# ["setPluginVar", "foo", "foo"],
# ["getPluginVar", "foo", "foo", "foo"]
# ["eval", "foo"],
# ["exec", "foo"],
# ["quit", "foo"]]