Implement E2E tests for lacre.daemon

- Add a dedicated configuration file for lacre.daemon.

- Implement test/daemon_test.py like test/e2e_test.py, to automate the
following procedure:

    1. Start up lacre.daemon.
    2. For each test case, send test message to the daemon and verify that the
       output received by test/utils/relay.py contains expected pattern.

- Simplify Makefile.

- Fix indentation here and there.
This commit is contained in:
Piotr F. Mieszkowski 2022-07-11 20:27:27 +02:00 committed by Gitea
parent a131cd66d3
commit 414f1d5921
6 changed files with 201 additions and 67 deletions

View file

@ -46,9 +46,7 @@ $(TEST_DB):
# Run an e2e test of Advanced Content Filter. # Run an e2e test of Advanced Content Filter.
# #
daemontest: daemontest:
$(PYTHON) test/relay.py 2500 $(PYTHON) test/daemon_test.py
PYTHONPATH=`pwd` $(PYTHON) -m lacre.daemon
$(PYTHON) test/sendmail.py
# Before running the crontest goal we need to make sure that the # Before running the crontest goal we need to make sure that the
# database gets regenerated. # database gets regenerated.

View file

@ -17,15 +17,99 @@
# along with gpg-mailgate source code. If not, see <http://www.gnu.org/licenses/>. # along with gpg-mailgate source code. If not, see <http://www.gnu.org/licenses/>.
# #
import configparser
import logging import logging
import subprocess
import os
import time
def _spawn(cmd):
env_dict = {
"PATH": os.getenv("PATH"),
"PYTHONPATH": os.getcwd(),
"GPG_MAILGATE_CONFIG": "test/gpg-mailgate-daemon-test.conf"
}
logging.debug(f"Spawning command: {cmd} with environment: {env_dict!r}")
return subprocess.Popen(cmd,
stdin=None,
stdout=subprocess.PIPE,
env=env_dict)
def _interrupt(proc):
# proc.send_signal(signal.SIGINT)
proc.terminate()
def _load(name):
logging.debug(f"Loading file {name}")
f = open(name, "r")
contents = f.read()
f.close()
return contents
def _send(host, port, mail_from, mail_to, message):
logging.debug(f"Sending message to {host}:{port}")
_spawn([os.getenv("PYTHON") or "python",
"test/utils/sendmail.py",
"-f", mail_from,
"-t", mail_to,
"-m", message])
def _load_test_config():
cp = configparser.ConfigParser()
cp.read("test/e2e.ini")
return cp
def _report_result(message_file, expected, test_output):
status = None
if expected in test_output:
status = "Success"
else:
status = "Failure"
print(message_file.ljust(30), status)
def _execute_case(config, case_name):
logging.info(f"Executing case {case_name}")
python = os.getenv("PYTHON", "python")
relay_mock = _spawn([python, "test/utils/relay.py", "2500"])
time.sleep(1) # Wait for the relay to start up.
_send("localhost", 10025, "dave@disposlab", "alice@disposlab", "test/msgin/clear2rsa.msg")
relay_mock.wait()
(test_out, _) = relay_mock.communicate()
test_out = test_out.decode('utf-8')
logging.debug(f"Read {len(test_out)} characters of output: '{test_out}'")
_report_result(config.get(case_name, "in"), config.get(case_name, "out"), test_out)
def _main(): def _main():
conf = _load_test_config()
logging.basicConfig(filename="test/logs/daemon-test.log", logging.basicConfig(filename="test/logs/daemon-test.log",
format="%(asctime)s %(pathname)s:%(lineno)d %(levelname)s [%(funcName)s] %(message)s", format="%(asctime)s %(pathname)s:%(lineno)d %(levelname)s [%(funcName)s] %(message)s",
datefmt="%Y-%m-%d %H:%M:%S", datefmt="%Y-%m-%d %H:%M:%S",
level=logging.DEBUG) level=logging.DEBUG)
logging.info("Starting Lacre Daemon tests...") logging.info("Starting Lacre Daemon tests...")
python = os.getenv("PYTHON", "python")
server = _spawn([python, "-m", "lacre.daemon"])
for case_no in range(1, conf.getint("tests", "cases")):
_execute_case(conf, case_name=f"case-{case_no}")
_interrupt(server)
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -111,17 +111,17 @@ def _execute_e2e_test(case_name, config, config_path):
logging.debug(f"Spawning relay: {relay_cmd}") logging.debug(f"Spawning relay: {relay_cmd}")
relay_proc = subprocess.Popen(relay_cmd, relay_proc = subprocess.Popen(relay_cmd,
stdin = None, stdin=None,
stdout = subprocess.PIPE) stdout=subprocess.PIPE)
logging.debug(f"Spawning GPG-Lacre: {gpglacre_cmd}, stdin = {config.get(case_name, 'in')}") logging.debug(f"Spawning GPG-Lacre: {gpglacre_cmd}, stdin = {config.get(case_name, 'in')}")
# pass PATH because otherwise it would be dropped # pass PATH because otherwise it would be dropped
gpglacre_proc = subprocess.run(gpglacre_cmd, gpglacre_proc = subprocess.run(gpglacre_cmd,
input = _load_file(config.get(case_name, "in")), input=_load_file(config.get(case_name, "in")),
capture_output = True, capture_output=True,
env = {"GPG_MAILGATE_CONFIG": config_path, env={"GPG_MAILGATE_CONFIG": config_path,
"PATH": os.getenv("PATH")}) "PATH": os.getenv("PATH")})
# Let the relay process the data. # Let the relay process the data.
relay_proc.wait() relay_proc.wait()

View file

@ -0,0 +1,31 @@
[logging]
config = test/gpg-lacre-log.ini
file = test/logs/gpg-mailgate.log
format = %(asctime)s %(module)s[%(process)d]: %(message)s
date_format = ISO
[gpg]
keyhome = test/keyhome
[smime]
cert_path = test/certs
[database]
enabled = yes
url = sqlite:///test/lacre.db
[relay]
host = localhost
port = 2500
[daemon]
host = localhost
port = 10025
[cron]
send_email = no
[enc_keymap]
alice@disposlab = 1CD245308F0963D038E88357973CF4D9387C44D7
bob@disposlab = 19CF4B47ECC9C47AFA84D4BD96F39FDA0E31BB67

View file

@ -24,79 +24,79 @@ LAST_LINE = -3
def welcome(msg): def welcome(msg):
return b"220 %b\r\n" % (msg) return b"220 %b\r\n" % (msg)
def ok(msg = b"OK"): def ok(msg = b"OK"):
return b"250 %b\r\n" % (msg) return b"250 %b\r\n" % (msg)
def bye(): def bye():
return b"251 Bye" return b"251 Bye"
def provide_message(): def provide_message():
return b"354 Enter a message, ending it with a '.' on a line by itself\r\n" return b"354 Enter a message, ending it with a '.' on a line by itself\r\n"
def receive_and_confirm(session): def receive_and_confirm(session):
session.recv(BUFFER_SIZE) session.recv(BUFFER_SIZE)
session.sendall(ok()) session.sendall(ok())
def localhost_at(port): def localhost_at(port):
return ('127.0.0.1', port) return ('127.0.0.1', port)
def serve(port): def serve(port):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try: try:
s.bind(localhost_at(port)) s.bind(localhost_at(port))
logging.info(f"Listening on localhost, port {port}") logging.info(f"Listening on localhost, port {port}")
s.listen(1) s.listen(1)
logging.info("Listening...") logging.info("Listening...")
except socket.error as e: except socket.error as e:
print("Cannot connect", e) print("Cannot connect", e)
logging.error(f"Cannot connect {e}") logging.error(f"Cannot connect {e}")
sys.exit(EXIT_UNAVAILABLE) sys.exit(EXIT_UNAVAILABLE)
logging.debug("About to accept a connection...") logging.debug("About to accept a connection...")
(conn, addr) = s.accept() (conn, addr) = s.accept()
logging.debug(f"Accepting connection from {conn}") logging.debug(f"Accepting connection from {conn}")
conn.sendall(welcome(b"TEST SERVER")) conn.sendall(welcome(b"TEST SERVER"))
receive_and_confirm(conn) # Ignore HELO/EHLO receive_and_confirm(conn) # Ignore HELO/EHLO
receive_and_confirm(conn) # Ignore sender address receive_and_confirm(conn) # Ignore sender address
receive_and_confirm(conn) # Ignore recipient address receive_and_confirm(conn) # Ignore recipient address
data = conn.recv(BUFFER_SIZE) conn.recv(BUFFER_SIZE)
conn.sendall(provide_message()) conn.sendall(provide_message())
# Consume until we get <CR><LF>.<CR><LF>, the end-of-message marker. # Consume until we get <CR><LF>.<CR><LF>, the end-of-message marker.
message = '' message = ''
while not message.endswith(EOM): while not message.endswith(EOM):
message += conn.recv(BUFFER_SIZE).decode(ENCODING) message += conn.recv(BUFFER_SIZE).decode(ENCODING)
conn.sendall(ok(b"OK, id=test")) conn.sendall(ok(b"OK, id=test"))
conn.recv(BUFFER_SIZE) conn.recv(BUFFER_SIZE)
conn.sendall(bye()) conn.sendall(bye())
conn.close() conn.close()
logging.debug(f"Received {len(message)} characters of data") logging.debug(f"Received {len(message)} characters of data")
# Trim EOM marker as we're only interested in the message body. # Trim EOM marker as we're only interested in the message body.
return message[:-len(EOM)] return message[:-len(EOM)]
def error(msg, exit_code): def error(msg, exit_code):
logging.error(msg) logging.error(msg)
print("ERROR: %s" % (msg)) print("ERROR: %s" % (msg))
sys.exit(exit_code) sys.exit(exit_code)
# filename is relative to where we run the tests from, i.e. the project root # filename is relative to where we run the tests from, i.e. the project root
# directory # directory
logging.basicConfig(filename = 'test/logs/relay.log', logging.basicConfig(filename='test/logs/relay.log',
format = '%(asctime)s %(pathname)s:%(lineno)d %(levelname)s [%(funcName)s] %(message)s', format='%(asctime)s %(pathname)s:%(lineno)d %(levelname)s [%(funcName)s] %(message)s',
datefmt = '%Y-%m-%d %H:%M:%S', datefmt='%Y-%m-%d %H:%M:%S',
level = logging.DEBUG) level=logging.DEBUG)
if len(sys.argv) < 2: if len(sys.argv) < 2:
error("Usage: relay.py PORT_NUMBER", EXIT_UNAVAILABLE) error("Usage: relay.py PORT_NUMBER", EXIT_UNAVAILABLE)
port = int(sys.argv[1]) port = int(sys.argv[1])
body = serve(port) body = serve(port)

View file

@ -1,27 +1,48 @@
import logging
import smtplib import smtplib
import sys import sys
import getopt import getopt
def _send(host, port, from_addr, recipients, message): def _load_file(name):
smtp = smtplib.SMTP(host, port) f = open(name, 'r')
# smtp.starttls() contents = f.read()
# try: f.close()
# breakpoint() return contents
smtp.sendmail(from_addr, recipients, message)
# except smtplib.SMTPDataError as e:
# print(f"Couldn't deliver message.\nGot error: {e}\n")
def _send(host, port, from_addr, recipients, message):
logging.info(f"From {from_addr} to {recipients} at {host}:{port}")
try:
smtp = smtplib.SMTP(host, port)
# smtp.starttls()
return smtp.sendmail(from_addr, recipients, message)
except smtplib.SMTPDataError as e:
logging.error(f"Couldn't deliver message. Got error: {e}")
return None
except ConnectionRefusedError as e:
logging.exception(f"Connection refused: {e}")
return None
logging.basicConfig(filename="test/logs/sendmail.log",
format="%(asctime)s %(pathname)s:%(lineno)d %(levelname)s [%(funcName)s] %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
level=logging.DEBUG)
sender = recipient = message = None sender = recipient = message = None
for opt, value in getopt.getopt(sys.argv[1:], "f:t:m:"): opts, _ = getopt.getopt(sys.argv[1:], "f:t:m:")
if opt == "f": for opt, value in opts:
if opt == "-f":
sender = value sender = value
if opt == "t": logging.debug(f"Sender is {sender}")
if opt == "-t":
recipient = value recipient = value
if opt == "m": logging.debug(f"Recipient is {recipient}")
message = value if opt == "-m":
message = _load_file(value)
logging.debug(f"Message is {message}")
if message is None: if message is None:
message = """\ message = """\