From 414f1d59213c2a823c584996b8d7c64e772a5c47 Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Mon, 11 Jul 2022 20:27:27 +0200 Subject: [PATCH] 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. --- Makefile | 4 +- test/daemon_test.py | 84 ++++++++++++++++++++++++++++ test/e2e_test.py | 12 ++-- test/gpg-mailgate-daemon-test.conf | 31 ++++++++++ test/utils/relay.py | 90 +++++++++++++++--------------- test/utils/sendmail.py | 47 +++++++++++----- 6 files changed, 201 insertions(+), 67 deletions(-) create mode 100644 test/gpg-mailgate-daemon-test.conf diff --git a/Makefile b/Makefile index 1ba8e9a..ada48bb 100644 --- a/Makefile +++ b/Makefile @@ -46,9 +46,7 @@ $(TEST_DB): # Run an e2e test of Advanced Content Filter. # daemontest: - $(PYTHON) test/relay.py 2500 - PYTHONPATH=`pwd` $(PYTHON) -m lacre.daemon - $(PYTHON) test/sendmail.py + $(PYTHON) test/daemon_test.py # Before running the crontest goal we need to make sure that the # database gets regenerated. diff --git a/test/daemon_test.py b/test/daemon_test.py index 45c8e8e..4c6f429 100644 --- a/test/daemon_test.py +++ b/test/daemon_test.py @@ -17,15 +17,99 @@ # along with gpg-mailgate source code. If not, see . # +import configparser 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(): + conf = _load_test_config() + logging.basicConfig(filename="test/logs/daemon-test.log", format="%(asctime)s %(pathname)s:%(lineno)d %(levelname)s [%(funcName)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S", level=logging.DEBUG) + 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__': diff --git a/test/e2e_test.py b/test/e2e_test.py index 7515f21..0cd044f 100644 --- a/test/e2e_test.py +++ b/test/e2e_test.py @@ -111,17 +111,17 @@ def _execute_e2e_test(case_name, config, config_path): logging.debug(f"Spawning relay: {relay_cmd}") relay_proc = subprocess.Popen(relay_cmd, - stdin = None, - stdout = subprocess.PIPE) + stdin=None, + stdout=subprocess.PIPE) logging.debug(f"Spawning GPG-Lacre: {gpglacre_cmd}, stdin = {config.get(case_name, 'in')}") # pass PATH because otherwise it would be dropped gpglacre_proc = subprocess.run(gpglacre_cmd, - input = _load_file(config.get(case_name, "in")), - capture_output = True, - env = {"GPG_MAILGATE_CONFIG": config_path, - "PATH": os.getenv("PATH")}) + input=_load_file(config.get(case_name, "in")), + capture_output=True, + env={"GPG_MAILGATE_CONFIG": config_path, + "PATH": os.getenv("PATH")}) # Let the relay process the data. relay_proc.wait() diff --git a/test/gpg-mailgate-daemon-test.conf b/test/gpg-mailgate-daemon-test.conf new file mode 100644 index 0000000..0ba4a52 --- /dev/null +++ b/test/gpg-mailgate-daemon-test.conf @@ -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 + diff --git a/test/utils/relay.py b/test/utils/relay.py index 7e850dc..a250e5b 100644 --- a/test/utils/relay.py +++ b/test/utils/relay.py @@ -24,79 +24,79 @@ LAST_LINE = -3 def welcome(msg): - return b"220 %b\r\n" % (msg) + return b"220 %b\r\n" % (msg) def ok(msg = b"OK"): - return b"250 %b\r\n" % (msg) + return b"250 %b\r\n" % (msg) def bye(): - return b"251 Bye" + return b"251 Bye" 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): - session.recv(BUFFER_SIZE) - session.sendall(ok()) + session.recv(BUFFER_SIZE) + session.sendall(ok()) def localhost_at(port): - return ('127.0.0.1', port) + return ('127.0.0.1', port) def serve(port): - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - s.bind(localhost_at(port)) - logging.info(f"Listening on localhost, port {port}") - s.listen(1) - logging.info("Listening...") - except socket.error as e: - print("Cannot connect", e) - logging.error(f"Cannot connect {e}") - sys.exit(EXIT_UNAVAILABLE) + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + s.bind(localhost_at(port)) + logging.info(f"Listening on localhost, port {port}") + s.listen(1) + logging.info("Listening...") + except socket.error as e: + print("Cannot connect", e) + logging.error(f"Cannot connect {e}") + sys.exit(EXIT_UNAVAILABLE) - logging.debug("About to accept a connection...") - (conn, addr) = s.accept() - logging.debug(f"Accepting connection from {conn}") - conn.sendall(welcome(b"TEST SERVER")) + logging.debug("About to accept a connection...") + (conn, addr) = s.accept() + logging.debug(f"Accepting connection from {conn}") + conn.sendall(welcome(b"TEST SERVER")) - receive_and_confirm(conn) # Ignore HELO/EHLO - receive_and_confirm(conn) # Ignore sender address - receive_and_confirm(conn) # Ignore recipient address + receive_and_confirm(conn) # Ignore HELO/EHLO + receive_and_confirm(conn) # Ignore sender address + receive_and_confirm(conn) # Ignore recipient address - data = conn.recv(BUFFER_SIZE) - conn.sendall(provide_message()) + conn.recv(BUFFER_SIZE) + conn.sendall(provide_message()) - # Consume until we get ., the end-of-message marker. - message = '' - while not message.endswith(EOM): - message += conn.recv(BUFFER_SIZE).decode(ENCODING) - conn.sendall(ok(b"OK, id=test")) + # Consume until we get ., the end-of-message marker. + message = '' + while not message.endswith(EOM): + message += conn.recv(BUFFER_SIZE).decode(ENCODING) + conn.sendall(ok(b"OK, id=test")) - conn.recv(BUFFER_SIZE) - conn.sendall(bye()) + conn.recv(BUFFER_SIZE) + 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. - return message[:-len(EOM)] + # Trim EOM marker as we're only interested in the message body. + return message[:-len(EOM)] def error(msg, exit_code): - logging.error(msg) - print("ERROR: %s" % (msg)) - sys.exit(exit_code) + logging.error(msg) + print("ERROR: %s" % (msg)) + sys.exit(exit_code) # filename is relative to where we run the tests from, i.e. the project root # directory -logging.basicConfig(filename = 'test/logs/relay.log', - format = '%(asctime)s %(pathname)s:%(lineno)d %(levelname)s [%(funcName)s] %(message)s', - datefmt = '%Y-%m-%d %H:%M:%S', - level = logging.DEBUG) +logging.basicConfig(filename='test/logs/relay.log', + format='%(asctime)s %(pathname)s:%(lineno)d %(levelname)s [%(funcName)s] %(message)s', + datefmt='%Y-%m-%d %H:%M:%S', + level=logging.DEBUG) 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]) body = serve(port) diff --git a/test/utils/sendmail.py b/test/utils/sendmail.py index edacf3b..ac5d36d 100644 --- a/test/utils/sendmail.py +++ b/test/utils/sendmail.py @@ -1,27 +1,48 @@ +import logging import smtplib import sys import getopt -def _send(host, port, from_addr, recipients, message): - smtp = smtplib.SMTP(host, port) - # smtp.starttls() - # try: - # breakpoint() - smtp.sendmail(from_addr, recipients, message) - # except smtplib.SMTPDataError as e: - # print(f"Couldn't deliver message.\nGot error: {e}\n") +def _load_file(name): + f = open(name, 'r') + contents = f.read() + f.close() + return contents +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 -for opt, value in getopt.getopt(sys.argv[1:], "f:t:m:"): - if opt == "f": +opts, _ = getopt.getopt(sys.argv[1:], "f:t:m:") +for opt, value in opts: + if opt == "-f": sender = value - if opt == "t": + logging.debug(f"Sender is {sender}") + if opt == "-t": recipient = value - if opt == "m": - message = value + logging.debug(f"Recipient is {recipient}") + if opt == "-m": + message = _load_file(value) + logging.debug(f"Message is {message}") if message is None: message = """\