From 9868f6a1d4124141f578a6ab97b040f9d3c24df3 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sat, 19 Dec 2015 17:52:38 +0100 Subject: [PATCH] Add SASL PLAIN test. --- irctest/basecontrollers.py | 2 +- irctest/cases.py | 41 +++++++++++++++++++----------- irctest/controllers/limnoria.py | 44 +++++++++++++++++++++++++++------ irctest/controllers/sopel.py | 13 +++++++--- setup.py | 2 +- 5 files changed, 75 insertions(+), 27 deletions(-) diff --git a/irctest/basecontrollers.py b/irctest/basecontrollers.py index 9d7c6fa..9d3ab3f 100644 --- a/irctest/basecontrollers.py +++ b/irctest/basecontrollers.py @@ -2,7 +2,7 @@ class _BaseController: pass class BaseClientController(_BaseController): - def run(self, hostname, port, authentication): + def run(self, hostname, port, auth): raise NotImplementedError() class BaseServerController(_BaseController): diff --git a/irctest/cases.py b/irctest/cases.py index 364555e..c809999 100644 --- a/irctest/cases.py +++ b/irctest/cases.py @@ -1,6 +1,7 @@ import socket import unittest +from . import authentication from .irc_utils import message_parser class _IrcTestCase(unittest.TestCase): @@ -20,11 +21,14 @@ class _IrcTestCase(unittest.TestCase): class BaseClientTestCase(_IrcTestCase): """Basic class for client tests. Handles spawning a client and getting messages from it.""" + nick = None + user = None def setUp(self): self.controller = self.controllerClass() self._setUpServer() def tearDown(self): - del self.controller + self.controller.kill() + self.conn.sendall(b'QUIT :end of test.') self.conn_file.close() self.conn.close() self.server.close() @@ -49,16 +53,17 @@ class BaseClientTestCase(_IrcTestCase): assert self.conn.sendall(line.encode()) is None if not line.endswith('\r\n'): assert self.conn.sendall(b'\r\n') is None - print('S: {}'.format(line.strip())) + if self.show_io: + print('S: {}'.format(line.strip())) class ClientNegociationHelper: """Helper class for tests handling capabilities negociation.""" - def readCapLs(self): + def readCapLs(self, auth=None): (hostname, port) = self.server.getsockname() self.controller.run( hostname=hostname, port=port, - authentication=None, + auth=auth, ) self.acceptClient() m = self.getMessage() @@ -83,26 +88,34 @@ class ClientNegociationHelper: return False elif msg.command == 'USER': self.assertEqual(len(msg.params), 4, msg) - self.nick = msg.params + self.user = msg.params return False else: return True - def negotiateCapabilities(self, cap_ls): - self.readCapLs() - if not self.protocol_version: - # No negotiation. - return - self.sendLine('CAP * LS :') + def negotiateCapabilities(self, capabilities, cap_ls=True, auth=None): + if cap_ls: + self.readCapLs(auth) + if not self.protocol_version: + # No negotiation. + return + self.sendLine('CAP * LS :{}'.format(' '.join(capabilities))) while True: m = self.getMessage(filter_pred=self.userNickPredicate) - self.assertEqual(m.command, 'CAP') + if m.command != 'CAP': + return m self.assertGreater(len(m.params), 0, m) if m.params[0] == 'REQ': self.assertEqual(len(m.params), 2, m) requested = frozenset(m.params[1].split()) - if not requested.issubset(cap_ls): - self.sendLine('CAP * NAK :{}'.format(m.params[1])[0:100]) + if not requested.issubset(capabilities): + self.sendLine('CAP {} NAK :{}'.format( + self.nick or '*', + m.params[1][0:100])) + else: + self.sendLine('CAP {} ACK :{}'.format( + self.nick or '*', + m.params[1])) else: return m diff --git a/irctest/controllers/limnoria.py b/irctest/controllers/limnoria.py index f3b66b8..774c880 100644 --- a/irctest/controllers/limnoria.py +++ b/irctest/controllers/limnoria.py @@ -1,12 +1,18 @@ import os +import shutil import tempfile import subprocess +from irctest import authentication from irctest.basecontrollers import BaseClientController TEMPLATE_CONFIG = """ +supybot.log.stdout.level: {loglevel} supybot.networks: testnet supybot.networks.testnet.servers: {hostname}:{port} +supybot.networks.testnet.sasl.username: {username} +supybot.networks.testnet.sasl.password: {password} +supybot.networks.testnet.sasl.mechanisms: {mechanisms} """ class LimnoriaController(BaseClientController): @@ -14,30 +20,52 @@ class LimnoriaController(BaseClientController): super().__init__() self.directory = None self.proc = None - def __del__(self): + def kill(self): if self.proc: - self.proc.kill() + self.proc.terminate() + try: + self.proc.wait(5) + except subprocess.TimeoutExpired: + self.proc.kill() + self.proc = None if self.directory: - self.directory.cleanup() - def open_file(self, name): + shutil.rmtree(self.directory) + def open_file(self, name, mode='a'): assert self.directory - return open(os.path.join(self.directory.name, name), 'a') + 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.TemporaryDirectory() + self.directory = tempfile.mkdtemp() with self.open_file('bot.conf'): pass + with self.open_file('conf/users.conf'): + pass - def run(self, hostname, port, authentication): + def run(self, hostname, port, auth): # Runs a client with the config given as arguments + assert self.proc is None self.create_config() + if auth: + mechanisms = ' '.join(map(authentication.Mechanisms.as_string, + auth.mechanisms)) + else: + mechanisms = '' with self.open_file('bot.conf') as fd: fd.write(TEMPLATE_CONFIG.format( + loglevel='CRITICAL', hostname=hostname, port=port, + username=auth.username if auth else '', + password=auth.password if auth else '', + mechanisms=mechanisms.lower(), )) self.proc = subprocess.Popen(['supybot', - os.path.join(self.directory.name, 'bot.conf')]) + os.path.join(self.directory, 'bot.conf')]) def get_irctest_controller_class(): return LimnoriaController diff --git a/irctest/controllers/sopel.py b/irctest/controllers/sopel.py index f4e3c9f..e782fc0 100644 --- a/irctest/controllers/sopel.py +++ b/irctest/controllers/sopel.py @@ -12,14 +12,17 @@ use_ssl = false port = {port} owner = me channels = +auth_username = {username} +auth_password = {password} +{auth_method} """ class SopelController(BaseClientController): def __init__(self): super().__init__() - self.filename = next(tempfile._get_candidate_names()) + self.filename = next(tempfile._get_candidate_names()) + '.cfg' self.proc = None - def __del__(self): + def kill(self): if self.proc: self.proc.kill() if self.filename: @@ -38,13 +41,17 @@ class SopelController(BaseClientController): with self.open_file(self.filename) as fd: pass - def run(self, hostname, port, authentication): + def run(self, hostname, port, auth): # Runs a client with the config given as arguments + assert self.proc is None self.create_config() with self.open_file(self.filename) as fd: fd.write(TEMPLATE_CONFIG.format( hostname=hostname, port=port, + username=auth.username if auth else '', + password=auth.password if auth else '', + auth_method='auth_method = sasl' if auth else '', )) self.proc = subprocess.Popen(['sopel', '-c', self.filename]) diff --git a/setup.py b/setup.py index b18e4f0..2135614 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ import os import sys from distutils.core import setup -if sys.version_info < (3, 2, 0): +if sys.version_info < (3, 4, 0): sys.stderr.write("This script requires Python 3.2 or newer.") sys.stderr.write(os.linesep) sys.exit(-1)