2015-12-19 22:09:06 +00:00
|
|
|
import os
|
|
|
|
import shutil
|
|
|
|
import socket
|
|
|
|
import tempfile
|
2015-12-20 12:35:33 +00:00
|
|
|
import time
|
2015-12-19 22:09:06 +00:00
|
|
|
import subprocess
|
|
|
|
|
2015-12-22 17:54:06 +00:00
|
|
|
from .runner import NotImplementedByController
|
2015-12-20 12:12:54 +00:00
|
|
|
|
2015-12-19 00:11:57 +00:00
|
|
|
class _BaseController:
|
2015-12-20 12:47:30 +00:00
|
|
|
"""Base class for software controllers.
|
|
|
|
|
|
|
|
A software controller is an object that handles configuring and running
|
|
|
|
a process (eg. a server or a client), as well as sending it instructions
|
|
|
|
that are not part of the IRC specification."""
|
2021-02-15 22:03:11 +00:00
|
|
|
def __init__(self, test_config):
|
|
|
|
self.test_config = test_config
|
2015-12-19 00:11:57 +00:00
|
|
|
|
2015-12-19 22:09:06 +00:00
|
|
|
class DirectoryBasedController(_BaseController):
|
2015-12-20 12:47:30 +00:00
|
|
|
"""Helper for controllers whose software configuration is based on an
|
|
|
|
arbitrary directory."""
|
2021-02-15 22:03:11 +00:00
|
|
|
def __init__(self, test_config):
|
|
|
|
super().__init__(test_config)
|
2015-12-19 22:09:06 +00:00
|
|
|
self.directory = None
|
|
|
|
self.proc = None
|
2015-12-20 00:48:56 +00:00
|
|
|
|
|
|
|
def kill_proc(self):
|
2015-12-20 12:47:30 +00:00
|
|
|
"""Terminates the controlled process, waits for it to exit, and
|
|
|
|
eventually kills it."""
|
2015-12-20 00:48:56 +00:00
|
|
|
self.proc.terminate()
|
|
|
|
try:
|
|
|
|
self.proc.wait(5)
|
|
|
|
except subprocess.TimeoutExpired:
|
|
|
|
self.proc.kill()
|
|
|
|
self.proc = None
|
2015-12-19 22:09:06 +00:00
|
|
|
def kill(self):
|
2015-12-20 12:47:30 +00:00
|
|
|
"""Calls `kill_proc` and cleans the configuration."""
|
2015-12-19 22:09:06 +00:00
|
|
|
if self.proc:
|
2015-12-20 00:48:56 +00:00
|
|
|
self.kill_proc()
|
2015-12-19 22:09:06 +00:00
|
|
|
if self.directory:
|
|
|
|
shutil.rmtree(self.directory)
|
2019-12-08 20:26:21 +00:00
|
|
|
def terminate(self):
|
|
|
|
"""Stops the process gracefully, and does not clean its config."""
|
|
|
|
self.proc.terminate()
|
|
|
|
self.proc.wait()
|
|
|
|
self.proc = None
|
2015-12-19 22:09:06 +00:00
|
|
|
def open_file(self, name, mode='a'):
|
2015-12-20 12:47:30 +00:00
|
|
|
"""Open a file in the configuration directory."""
|
2015-12-19 22:09:06 +00:00
|
|
|
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):
|
2019-12-08 20:26:21 +00:00
|
|
|
"""If there is no config dir, creates it and returns True.
|
|
|
|
Else returns False."""
|
|
|
|
if self.directory:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
self.directory = tempfile.mkdtemp()
|
|
|
|
return True
|
2015-12-19 22:09:06 +00:00
|
|
|
|
2015-12-25 14:45:06 +00:00
|
|
|
def gen_ssl(self):
|
|
|
|
self.csr_path = os.path.join(self.directory, 'ssl.csr')
|
|
|
|
self.key_path = os.path.join(self.directory, 'ssl.key')
|
|
|
|
self.pem_path = os.path.join(self.directory, 'ssl.pem')
|
|
|
|
self.dh_path = os.path.join(self.directory, 'dh.pem')
|
2017-11-02 00:07:06 +00:00
|
|
|
subprocess.check_output([self.openssl_bin, 'req', '-new', '-newkey', 'rsa',
|
2015-12-25 14:45:06 +00:00
|
|
|
'-nodes', '-out', self.csr_path, '-keyout', self.key_path,
|
|
|
|
'-batch'],
|
|
|
|
stderr=subprocess.DEVNULL)
|
2017-11-02 00:07:06 +00:00
|
|
|
subprocess.check_output([self.openssl_bin, 'x509', '-req',
|
2015-12-25 14:45:06 +00:00
|
|
|
'-in', self.csr_path, '-signkey', self.key_path,
|
|
|
|
'-out', self.pem_path],
|
|
|
|
stderr=subprocess.DEVNULL)
|
2017-11-02 00:07:06 +00:00
|
|
|
subprocess.check_output([self.openssl_bin, 'dhparam',
|
2015-12-25 14:45:06 +00:00
|
|
|
'-out', self.dh_path, '128'],
|
|
|
|
stderr=subprocess.DEVNULL)
|
|
|
|
|
2015-12-19 00:11:57 +00:00
|
|
|
class BaseClientController(_BaseController):
|
2015-12-20 12:47:30 +00:00
|
|
|
"""Base controller for IRC clients."""
|
2015-12-19 16:52:38 +00:00
|
|
|
def run(self, hostname, port, auth):
|
2015-12-19 00:11:57 +00:00
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
class BaseServerController(_BaseController):
|
2015-12-20 12:47:30 +00:00
|
|
|
"""Base controller for IRC server."""
|
2020-09-14 08:41:08 +00:00
|
|
|
_port_wait_interval = .1
|
2015-12-23 00:54:10 +00:00
|
|
|
port_open = False
|
2015-12-22 21:33:23 +00:00
|
|
|
def run(self, hostname, port, password,
|
|
|
|
valid_metadata_keys, invalid_metadata_keys):
|
2015-12-20 12:12:54 +00:00
|
|
|
raise NotImplementedError()
|
2015-12-20 14:11:56 +00:00
|
|
|
def registerUser(self, case, username, password=None):
|
2017-11-02 00:07:06 +00:00
|
|
|
raise NotImplementedByController('account registration')
|
2015-12-23 00:54:10 +00:00
|
|
|
def wait_for_port(self):
|
|
|
|
while not self.port_open:
|
2020-09-14 08:41:08 +00:00
|
|
|
time.sleep(self._port_wait_interval)
|
2020-09-13 10:47:50 +00:00
|
|
|
try:
|
|
|
|
c = socket.create_connection(('localhost', self.port), timeout=1.0)
|
2021-02-19 21:26:57 +00:00
|
|
|
c.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
|
|
|
|
|
|
|
|
# Make sure the server properly processes the disconnect.
|
|
|
|
# Otherwise, it may still count it in LUSER and fail tests in
|
|
|
|
# test_lusers.py (eg. this happens with Charybdis 3.5.0)
|
|
|
|
c.send(b"QUIT :chkport\r\n")
|
|
|
|
data = b""
|
|
|
|
while b"chkport" not in data:
|
|
|
|
data += c.recv(1024)
|
|
|
|
|
2020-09-13 10:47:50 +00:00
|
|
|
c.close()
|
|
|
|
self.port_open = True
|
|
|
|
except Exception as e:
|
|
|
|
continue
|