Start supporting server testing.

This commit is contained in:
Valentin Lorentz 2015-12-19 23:09:06 +01:00
parent 858aaf0367
commit a8f8d7c077
8 changed files with 155 additions and 38 deletions

View File

@ -17,11 +17,11 @@ def main(args):
controller_class = module.get_irctest_controller_class()
if issubclass(controller_class, BaseClientController):
module = 'irctest.client_tests'
elif issubclass(controller_class, BaseClientController):
module = 'irctest.servertests'
elif issubclass(controller_class, BaseServerController):
module = 'irctest.server_tests'
else:
print('{}.Controller should be a subclass of '
'irctest.basecontroller.Base{Client,Server}Controller'
print(r'{}.Controller should be a subclass of '
r'irctest.basecontroller.Base{{Client,Server}}Controller'
.format(args.module),
file=sys.stderr)
exit(1)

View File

@ -1,6 +1,38 @@
import os
import shutil
import socket
import tempfile
import subprocess
class _BaseController:
pass
class DirectoryBasedController(_BaseController):
def __init__(self):
super().__init__()
self.directory = None
self.proc = None
def kill(self):
if self.proc:
self.proc.terminate()
try:
self.proc.wait(5)
except subprocess.TimeoutExpired:
self.proc.kill()
self.proc = None
if self.directory:
shutil.rmtree(self.directory)
def open_file(self, name, mode='a'):
assert self.directory
if os.sep in name:
dir_ = os.path.join(self.directory, os.path.dirname(name))
if not os.path.isdir(dir_):
os.makedirs(dir_)
assert os.path.isdir(dir_)
return open(os.path.join(self.directory, name), mode)
def create_config(self):
self.directory = tempfile.mkdtemp()
class BaseClientController(_BaseController):
def run(self, hostname, port, auth):
raise NotImplementedError()

View File

@ -1,5 +1,6 @@
import socket
import unittest
import collections
from . import authentication
from .irc_utils import message_parser
@ -9,18 +10,18 @@ class _IrcTestCase(unittest.TestCase):
def getLine(self):
raise NotImplementedError()
def getMessage(self, filter_pred=None):
def getMessage(self, *args, filter_pred=None):
"""Gets a message and returns it. If a filter predicate is given,
fetches messages until the predicate returns a False on a message,
and returns this message."""
while True:
msg = message_parser.parse_message(self.getLine())
msg = message_parser.parse_message(self.getLine(*args))
if not filter_pred or filter_pred(msg):
return msg
class BaseClientTestCase(_IrcTestCase):
"""Basic class for client tests. Handles spawning a client and getting
messages from it."""
"""Basic class for client tests. Handles spawning a client and exchanging
messages with it."""
nick = None
user = None
def setUp(self):
@ -50,9 +51,11 @@ class BaseClientTestCase(_IrcTestCase):
print('C: {}'.format(line.strip()))
return line
def sendLine(self, line):
assert self.conn.sendall(line.encode()) is None
ret = self.conn.sendall(line.encode())
assert ret is None
if not line.endswith('\r\n'):
assert self.conn.sendall(b'\r\n') is None
ret = self.conn.sendall(b'\r\n')
assert ret is None
if self.show_io:
print('S: {}'.format(line.strip()))
@ -122,3 +125,54 @@ class ClientNegociationHelper:
else:
return m
Client = collections.namedtuple('Client',
'conn conn_file')
class BaseServerTestCase(_IrcTestCase):
"""Basic class for server tests. Handles spawning a server and exchanging
messages with it."""
def setUp(self):
self.find_hostname_and_port()
self.controller = self.controllerClass()
self.controller.run(self.hostname, self.port)
self.clients = {}
def tearDown(self):
self.controller.kill()
for client in list(self.clients):
self.removeClient(client)
def find_hostname_and_port(self):
"""Find available hostname/port to listen on."""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("",0))
(self.hostname, self.port) = s.getsockname()
s.close()
def addClient(self, name=None):
"""Connects a client to the server and adds it to the dict.
If 'name' is not given, uses the lowest unused non-negative integer."""
if not name:
name = max(map(int, list(self.clients)+[0]))+1
conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
conn.connect((self.hostname, self.port))
conn_file = conn.makefile(newline='\r\n', encoding='utf8')
self.clients[name] = Client(conn=conn, conn_file=conn_file)
def removeClient(self, name):
assert name in self.clients
self.clients[name].conn.close()
del self.clients[name]
def getLine(self, client):
assert client in self.clients
line = self.clients[client].conn_file.readline()
if self.show_io:
print('S -> {}: {}'.format(client, line.strip()))
return line
def sendLine(self, client, line):
ret = self.clients[client].conn.sendall(line.encode())
assert ret is None
if not line.endswith('\r\n'):
ret = self.clients[client].conn.sendall(b'\r\n')
assert ret is None
if self.show_io:
print('{} -> S: {}'.format(client, line.strip()))

View File

@ -0,0 +1,35 @@
import os
import time
import shutil
import tempfile
import subprocess
from irctest import authentication
from irctest.basecontrollers import BaseServerController, DirectoryBasedController
TEMPLATE_CONFIG = """
<bind address="{hostname}" port="{port}" type="clients">
<module name="cap">
"""
class InspircdController(BaseServerController, DirectoryBasedController):
def create_config(self):
super().create_config()
with self.open_file('server.conf'):
pass
def run(self, hostname, port):
assert self.proc is None
self.create_config()
with self.open_file('server.conf') as fd:
fd.write(TEMPLATE_CONFIG.format(
hostname=hostname,
port=port,
))
self.proc = subprocess.Popen(['inspircd', '--nofork', '--config',
os.path.join(self.directory, 'server.conf')])
time.sleep(0.1) # FIXME: do better than this to wait for InspIRCd to start
def get_irctest_controller_class():
return InspircdController

View File

@ -1,10 +1,8 @@
import os
import shutil
import tempfile
import subprocess
from irctest import authentication
from irctest.basecontrollers import BaseClientController
from irctest.basecontrollers import BaseClientController, DirectoryBasedController
TEMPLATE_CONFIG = """
supybot.log.stdout.level: {loglevel}
@ -15,32 +13,9 @@ supybot.networks.testnet.sasl.password: {password}
supybot.networks.testnet.sasl.mechanisms: {mechanisms}
"""
class LimnoriaController(BaseClientController):
def __init__(self):
super().__init__()
self.directory = None
self.proc = None
def kill(self):
if self.proc:
self.proc.terminate()
try:
self.proc.wait(5)
except subprocess.TimeoutExpired:
self.proc.kill()
self.proc = None
if self.directory:
shutil.rmtree(self.directory)
def open_file(self, name, mode='a'):
assert self.directory
if os.sep in name:
dir_ = os.path.join(self.directory, os.path.dirname(name))
if not os.path.isdir(dir_):
os.makedirs(dir_)
assert os.path.isdir(dir_)
return open(os.path.join(self.directory, name), mode)
class LimnoriaController(BaseClientController, DirectoryBasedController):
def create_config(self):
self.directory = tempfile.mkdtemp()
super().create_config()
with self.open_file('bot.conf'):
pass
with self.open_file('conf/users.conf'):

View File

@ -0,0 +1,8 @@
import os
import unittest
def discover():
ts = unittest.TestSuite()
ts.addTests(unittest.defaultTestLoader.discover(os.path.dirname(__file__)))
return ts

View File

@ -0,0 +1,12 @@
from irctest import cases
from irctest.irc_utils.message_parser import Message
class CapTestCase(cases.BaseServerTestCase):
def testNoCap(self):
self.addClient(1)
self.sendLine(1, 'CAP LS 302')
while True: # Ignore boring connection messages
m = self.getMessage(1)
if m.command != 'NOTICE':
break
print(m)

View File

@ -43,6 +43,7 @@ setup(
'irctest.client_tests',
'irctest.controllers',
'irctest.irc_utils',
'irctest.server_tests',
],
)