From a9506805fb90e197723375011648925117bc0821 Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Thu, 4 Nov 2021 22:39:02 +0100 Subject: [PATCH 01/14] Write a test mail relay Provide a simple Python script that would linsten on a given port and print to standard output any message received via SMTP on that port. This script will then be used to automatically test gpg-mailgate with different scenarios (unknown recipient key, RSA key, elliptic curve key). --- test/relay.py | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 test/relay.py diff --git a/test/relay.py b/test/relay.py new file mode 100644 index 0000000..cfbe02a --- /dev/null +++ b/test/relay.py @@ -0,0 +1,68 @@ +#!/usr/local/bin/python2 +# +# This quick-and-dirty script supports only the happy case of SMTP session, +# i.e. what gpg-mailgate/gpg-lacre needs to deliver encrypted email. +# +# It listens on the port given as the only command-line argument and consumes a +# message, then prints it to standard output. The goal is to be able to +# compare that output with expected clear-text or encrypted message body. +# + +import sys +import socket + + +BUFFER_SIZE = 4096 +EOM = "\r\n.\r\n" +LAST_LINE = -3 + + +def welcome(msg): + return "220 %s\r\n" % (msg) + +def ok(msg = "OK"): + return "250 %s\r\n" % (msg) + +def provide_message(): + return "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()) + +def serve(port): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.bind(('', port)) + s.listen(1) + + (conn, addr) = s.accept() + conn.sendall(welcome("TEST SERVER")) + + 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()) + + # Consume until we get ., the end-of-message marker. + message = '' + while not message.endswith(EOM): + message += conn.recv(BUFFER_SIZE) + conn.sendall(ok("OK, id=test")) + + # Trim EOM marker as we're only interested in the message body. + return message[:-len(EOM)] + +def error(msg): + print "ERROR: %s" % (msg) + sys.exit(1) + + +if len(sys.argv) < 2: + error("Usage: relay.py PORT_NUMBER") + +port = int(sys.argv[1]) +body = serve(port) + +print body -- 2.30.2 From f3f56a47bcffaa99550db789f8d89aa9e97edc1c Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Mon, 8 Nov 2021 22:52:22 +0100 Subject: [PATCH 02/14] Implement an E2E testing script --- test/e2e_test.py | 110 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 test/e2e_test.py diff --git a/test/e2e_test.py b/test/e2e_test.py new file mode 100644 index 0000000..e37f7ff --- /dev/null +++ b/test/e2e_test.py @@ -0,0 +1,110 @@ +#!/usr/local/bin/python2 + +import os +import sys + +import difflib + +import ConfigParser +import logging + +TEST_PORT = 2500 + +EOL = "\n" + +RELAY_SCRIPT = "test/relay.py" +CONFIG_FILE = "test/gpg-mailgate.conf" + +PYTHON_BIN = "python2.7" + +def build_config(config): + cp = ConfigParser.ConfigParser() + + cp.add_section("logging") + cp.set("logging", "file", "/dev/stout") + cp.set("logging", "verbose", "yes") + + cp.add_section("gpg") + cp.set("gpg", "keyhome", config["gpg_keyhome"]) + + cp.add_section("smime") + cp.set("smime", "cert_path", config["smime_certpath"]) + + cp.add_section("relay") + cp.set("relay", "host", "localhost") + cp.set("relay", "port", config["port"]) + + logging.debug("Created config with keyhome=%s, cert_path=%s and relay at port %d" % + (config["gpg_keyhome"], config["smime_certpath"], config["port"])) + return cp + +def write_test_config(outfile, **config): + logging.debug("Generating configuration with %s" % repr(config)) + + out = open(outfile, "w+") + cp = build_config(config) + cp.write(out) + out.close() + + logging.debug("Wrote configuration to %s" % outfile) + +def load_file(name): + f = open(name, 'r') + contents = f.read() + f.close() + + return contents + +def strip_eols(strings): + return map(lambda s: s.strip("\r"), strings) + +def compare(result, expected): + result_lines = strip_eols(result.split(EOL)) + expected_lines = strip_eols(expected.split(EOL)) + + return difflib.unified_diff(expected_lines, result_lines, + fromfile='expected', + tofile='output') + +def report_result(message_file, test_output): + expected = load_file(message_file) + diff = compare(test_output, expected) + if len(list(diff)) > 0: + print "Output and the expected message don't match:" + else: + print "Message %s processed properly" % (message_file) + for diff_line in diff: + print diff_line + +def execute_e2e_test(message_file, **kwargs): + test_command = "%s gpg-mailgate.py %s < %s" % (PYTHON_BIN, kwargs["from_addr"], message_file) + result_command = "%s %s %d" % (PYTHON_BIN, RELAY_SCRIPT, kwargs["port"]) + + logging.debug("Spawning: '%s'" % (result_command)) + pipe = os.popen(result_command, 'r') + + logging.debug("Spawning: '%s'" % (test_command)) + msgin = os.popen(test_command, 'w') + msgin.write(load_file(message_file)) + msgin.close() + + testout = pipe.read() + pipe.close() + + logging.debug("Read %d characters of test output: '%s'" % (len(testout), testout)) + + report_result(message_file, testout) + + +logging.basicConfig(filename = "e2e_test.log", + datefmt = "%Y-%m-%d %H:%M:%S", + level = logging.DEBUG) + +write_test_config(os.getcwd() + "/" + CONFIG_FILE, + port = TEST_PORT, + gpg_keyhome = "test/keyhome", + smime_certpath = "test/certs") + +execute_e2e_test("test.msg", + from_addr = "alice@localhost", + port = TEST_PORT) -- 2.30.2 From 7a063a91b844b54a940be3f50ded62287d9a6f17 Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Tue, 9 Nov 2021 21:25:41 +0100 Subject: [PATCH 03/14] Polish E2E testing script and make it configurable --- Makefile | 6 ++++++ gpg-mailgate.py | 7 ++++++- test/e2e.ini | 10 ++++++++++ test/e2e_test.py | 32 +++++++++++++++++++++----------- test/msgin/clear2clear.msg | 5 +++++ test/msgout/clear2clear.msg | 5 +++++ 6 files changed, 53 insertions(+), 12 deletions(-) create mode 100644 Makefile create mode 100644 test/e2e.ini create mode 100644 test/msgin/clear2clear.msg create mode 100644 test/msgout/clear2clear.msg diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1a4bc8a --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +PYTHON = python2.7 + +.PHONY: test + +test: + $(PYTHON) test/e2e_test.py diff --git a/gpg-mailgate.py b/gpg-mailgate.py index 9830262..b39c786 100755 --- a/gpg-mailgate.py +++ b/gpg-mailgate.py @@ -39,9 +39,14 @@ import traceback from M2Crypto import BIO, Rand, SMIME, X509 from email.mime.message import MIMEMessage +# Environment variable name we read to retrieve configuration path. This is to +# enable non-root users to set up and run GPG Mailgate and to make the software +# testable. +CONFIG_PATH_ENV = "GPG_MAILGATE_CONFIG" + # Read configuration from /etc/gpg-mailgate.conf _cfg = RawConfigParser() -_cfg.read('/etc/gpg-mailgate.conf') +_cfg.read(os.getenv(CONFIG_PATH_ENV, '/etc/gpg-mailgate.conf')) cfg = dict() for sect in _cfg.sections(): cfg[sect] = dict() diff --git a/test/e2e.ini b/test/e2e.ini new file mode 100644 index 0000000..6f3574d --- /dev/null +++ b/test/e2e.ini @@ -0,0 +1,10 @@ +[relay] +port = 2500 + +[tests] +cases = 1 + +[case-1] +from = alice@localhost +in = test/msgin/clear2clear.msg +out = test/msgout/clear2clear.msg diff --git a/test/e2e_test.py b/test/e2e_test.py index e37f7ff..40964ad 100644 --- a/test/e2e_test.py +++ b/test/e2e_test.py @@ -8,8 +8,6 @@ import difflib import ConfigParser import logging -TEST_PORT = 2500 - EOL = "\n" RELAY_SCRIPT = "test/relay.py" @@ -66,17 +64,17 @@ def compare(result, expected): fromfile='expected', tofile='output') -def report_result(message_file, test_output): - expected = load_file(message_file) +def report_result(message_file, expected_file, test_output): + expected = load_file(expected_file) diff = compare(test_output, expected) if len(list(diff)) > 0: - print "Output and the expected message don't match:" + print "Output and the expected message (%s) don't match:" % (expected_file) else: print "Message %s processed properly" % (message_file) for diff_line in diff: print diff_line -def execute_e2e_test(message_file, **kwargs): +def execute_e2e_test(message_file, expected_file, **kwargs): test_command = "%s gpg-mailgate.py %s < %s" % (PYTHON_BIN, kwargs["from_addr"], message_file) result_command = "%s %s %d" % (PYTHON_BIN, RELAY_SCRIPT, kwargs["port"]) @@ -93,18 +91,30 @@ def execute_e2e_test(message_file, **kwargs): logging.debug("Read %d characters of test output: '%s'" % (len(testout), testout)) - report_result(message_file, testout) + report_result(message_file, expected_file, testout) +def load_config(): + cp = ConfigParser.ConfigParser() + cp.read("test/e2e.ini") + + return cp + + +config = load_config() logging.basicConfig(filename = "e2e_test.log", + format = "%(pathname)s:%(lineno)d %(levelname)s [%(funcName)s] %(message)s", datefmt = "%Y-%m-%d %H:%M:%S", level = logging.DEBUG) write_test_config(os.getcwd() + "/" + CONFIG_FILE, - port = TEST_PORT, + port = config.getint("relay", "port"), gpg_keyhome = "test/keyhome", smime_certpath = "test/certs") -execute_e2e_test("test.msg", - from_addr = "alice@localhost", - port = TEST_PORT) +for case_no in range(1, config.getint("tests", "cases")+1): + case_name = "case-%d" % (case_no) + + execute_e2e_test(config.get(case_name, "in"), config.get(case_name, "out"), + from_addr = config.get(case_name, "from"), + port = config.getint("relay", "port")) diff --git a/test/msgin/clear2clear.msg b/test/msgin/clear2clear.msg new file mode 100644 index 0000000..5fe7ff7 --- /dev/null +++ b/test/msgin/clear2clear.msg @@ -0,0 +1,5 @@ +From: Bob +To: Alice +Subject: Test + +Body of the message. diff --git a/test/msgout/clear2clear.msg b/test/msgout/clear2clear.msg new file mode 100644 index 0000000..5fe7ff7 --- /dev/null +++ b/test/msgout/clear2clear.msg @@ -0,0 +1,5 @@ +From: Bob +To: Alice +Subject: Test + +Body of the message. -- 2.30.2 From 27d7481078a4dff419efa960734009f4817d0c7d Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Thu, 11 Nov 2021 10:57:00 +0100 Subject: [PATCH 04/14] Set up ground for E2E tests - Use an environment variable to point at the configuration file while strating gpg-mailgate.py. - Unify paths: store temporary config, logs and anything else under 'test' directory. - Configure more tests (RSA, Ed25519). - Add test descriptions to be shown before they're started. --- Makefile | 16 ++++++++++++++-- test/e2e.ini | 17 +++++++++++++++-- test/e2e_test.py | 27 +++++++++++++++++++-------- test/msgin/clear2ed.msg | 5 +++++ test/msgin/clear2rsa.msg | 5 +++++ test/msgout/clear2ed.msg | 5 +++++ test/msgout/clear2rsa.msg | 5 +++++ test/relay.py | 10 ++++++++-- 8 files changed, 76 insertions(+), 14 deletions(-) create mode 100644 test/msgin/clear2ed.msg create mode 100644 test/msgin/clear2rsa.msg create mode 100644 test/msgout/clear2ed.msg create mode 100644 test/msgout/clear2rsa.msg diff --git a/Makefile b/Makefile index 1a4bc8a..5c50c1d 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,18 @@ PYTHON = python2.7 -.PHONY: test +.PHONY: test pre-clean clean -test: +test: test/tmp test/logs pre-clean $(PYTHON) test/e2e_test.py + +pre-clean: + rm -fv test/gpg-mailgate.conf + +test/tmp: + mkdir test/tmp + +test/logs: + mkdir test/logs + +clean: pre-clean + rm -rfv test/tmp test/logs diff --git a/test/e2e.ini b/test/e2e.ini index 6f3574d..3e0899f 100644 --- a/test/e2e.ini +++ b/test/e2e.ini @@ -2,9 +2,22 @@ port = 2500 [tests] -cases = 1 +cases = 3 [case-1] -from = alice@localhost +descr = Clear text message to a user without a key +to = carlos@disposlab in = test/msgin/clear2clear.msg out = test/msgout/clear2clear.msg + +[case-2] +descr = Clear text message to a user with an RSA key +to = alice@disposlab +in = test/msgin/clear2rsa.msg +out = test/msgout/clear2rsa.msg + +[case-3] +descr = Clear text message to a user with an Ed25519 key +to = bob@disposlab +in = test/msgin/clear2ed.msg +out = test/msgout/clear2ed.msg diff --git a/test/e2e_test.py b/test/e2e_test.py index 40964ad..37ccbb0 100644 --- a/test/e2e_test.py +++ b/test/e2e_test.py @@ -8,8 +8,12 @@ import difflib import ConfigParser import logging +from time import sleep + EOL = "\n" +DELAY = 3 + RELAY_SCRIPT = "test/relay.py" CONFIG_FILE = "test/gpg-mailgate.conf" @@ -19,7 +23,7 @@ def build_config(config): cp = ConfigParser.ConfigParser() cp.add_section("logging") - cp.set("logging", "file", "/dev/stout") + cp.set("logging", "file", config["log_file"]) cp.set("logging", "verbose", "yes") cp.add_section("gpg") @@ -75,7 +79,7 @@ def report_result(message_file, expected_file, test_output): print diff_line def execute_e2e_test(message_file, expected_file, **kwargs): - test_command = "%s gpg-mailgate.py %s < %s" % (PYTHON_BIN, kwargs["from_addr"], message_file) + test_command = "GPG_MAILGATE_CONFIG=%s %s gpg-mailgate.py %s < %s" % (kwargs["config_path"], PYTHON_BIN, kwargs["to_addr"], message_file) result_command = "%s %s %d" % (PYTHON_BIN, RELAY_SCRIPT, kwargs["port"]) logging.debug("Spawning: '%s'" % (result_command)) @@ -102,19 +106,26 @@ def load_config(): config = load_config() -logging.basicConfig(filename = "e2e_test.log", - format = "%(pathname)s:%(lineno)d %(levelname)s [%(funcName)s] %(message)s", +logging.basicConfig(filename = "test/logs/e2e.log", + format = "%(asctime)s %(pathname)s:%(lineno)d %(levelname)s [%(funcName)s] %(message)s", datefmt = "%Y-%m-%d %H:%M:%S", level = logging.DEBUG) -write_test_config(os.getcwd() + "/" + CONFIG_FILE, +config_path = os.getcwd() + "/" + CONFIG_FILE + +write_test_config(config_path, port = config.getint("relay", "port"), gpg_keyhome = "test/keyhome", - smime_certpath = "test/certs") + smime_certpath = "test/certs", + log_file = "test/logs/gpg-mailgate.log") for case_no in range(1, config.getint("tests", "cases")+1): case_name = "case-%d" % (case_no) + print "Executing: %s" % (config.get(case_name, "descr")) execute_e2e_test(config.get(case_name, "in"), config.get(case_name, "out"), - from_addr = config.get(case_name, "from"), - port = config.getint("relay", "port")) + config_path = config_path, + to_addr = config.get(case_name, "to"), + port = config.getint("relay", "port")) + + sleep(DELAY) diff --git a/test/msgin/clear2ed.msg b/test/msgin/clear2ed.msg new file mode 100644 index 0000000..5fe7ff7 --- /dev/null +++ b/test/msgin/clear2ed.msg @@ -0,0 +1,5 @@ +From: Bob +To: Alice +Subject: Test + +Body of the message. diff --git a/test/msgin/clear2rsa.msg b/test/msgin/clear2rsa.msg new file mode 100644 index 0000000..5fe7ff7 --- /dev/null +++ b/test/msgin/clear2rsa.msg @@ -0,0 +1,5 @@ +From: Bob +To: Alice +Subject: Test + +Body of the message. diff --git a/test/msgout/clear2ed.msg b/test/msgout/clear2ed.msg new file mode 100644 index 0000000..5fe7ff7 --- /dev/null +++ b/test/msgout/clear2ed.msg @@ -0,0 +1,5 @@ +From: Bob +To: Alice +Subject: Test + +Body of the message. diff --git a/test/msgout/clear2rsa.msg b/test/msgout/clear2rsa.msg new file mode 100644 index 0000000..5fe7ff7 --- /dev/null +++ b/test/msgout/clear2rsa.msg @@ -0,0 +1,5 @@ +From: Bob +To: Alice +Subject: Test + +Body of the message. diff --git a/test/relay.py b/test/relay.py index cfbe02a..eca73ec 100644 --- a/test/relay.py +++ b/test/relay.py @@ -12,6 +12,8 @@ import sys import socket +EXIT_UNAVAILABLE = 1 + BUFFER_SIZE = 4096 EOM = "\r\n.\r\n" LAST_LINE = -3 @@ -32,8 +34,12 @@ def receive_and_confirm(session): def serve(port): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.bind(('', port)) - s.listen(1) + try: + s.bind(('', port)) + s.listen(1) + except socket.error, e: + print "Cannot connect", e + sys.exit(EXIT_UNAVAILABLE) (conn, addr) = s.accept() conn.sendall(welcome("TEST SERVER")) -- 2.30.2 From f41adc0d53d04644c6b2fa1db9385379d7ba15a1 Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Thu, 11 Nov 2021 11:03:53 +0100 Subject: [PATCH 05/14] Configure git to ignore temporary files and Emacs files --- .gitignore | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.gitignore b/.gitignore index 7808c4b..6b8344e 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,16 @@ pip-log.txt .tox nosetests.xml +# GPG-Mailgate test files +test/logs +test/gpg-mailgate.conf + +# Emacs files +*~ +TAGS +TAGS-Python +TAGS-PHP + # Translations *.mo -- 2.30.2 From f1a799d8647f959c51f0444c5b8b8c6250b58603 Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Thu, 6 Jan 2022 16:23:10 +0100 Subject: [PATCH 06/14] Adjust E2E tests to work with all scenarios Since it's not so easy to encrypt a message exactly the same way twice, we only verify if the message has been encrypted or not. Introduce minor changes to the library itself, because it doesn't work very well with modern GnuPG. Also, include GnuPG directory (pointed at by --homedir option). --- GnuPG/__init__.py | 20 ++++++++++++--- Makefile | 1 + test/e2e.ini | 6 ++--- test/e2e_test.py | 50 +++++++++++++++++++++++------------- test/keyhome/crls.d/DIR.txt | 1 + test/keyhome/pubring.kbx | Bin 0 -> 3088 bytes test/keyhome/random_seed | Bin 0 -> 600 bytes test/keyhome/tofu.db | Bin 0 -> 49152 bytes test/keyhome/trustdb.gpg | Bin 0 -> 1200 bytes test/relay.py | 8 ++++++ 10 files changed, 62 insertions(+), 24 deletions(-) create mode 100644 test/keyhome/crls.d/DIR.txt create mode 100644 test/keyhome/pubring.kbx create mode 100644 test/keyhome/random_seed create mode 100644 test/keyhome/tofu.db create mode 100644 test/keyhome/trustdb.gpg diff --git a/GnuPG/__init__.py b/GnuPG/__init__.py index c9bbee0..294f8c9 100644 --- a/GnuPG/__init__.py +++ b/GnuPG/__init__.py @@ -23,6 +23,13 @@ import subprocess import shutil import random import string +import sys + + +LINE_FINGERPRINT = 'fpr' +LINE_USER_ID = 'uid' + +POS_FINGERPRINT = 9 def private_keys( keyhome ): cmd = ['/usr/bin/gpg', '--homedir', keyhome, '--list-secret-keys', '--with-colons'] @@ -42,14 +49,21 @@ def public_keys( keyhome ): cmd = ['/usr/bin/gpg', '--homedir', keyhome, '--list-keys', '--with-colons'] p = subprocess.Popen( cmd, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) p.wait() + keys = dict() + fingerprint = None + email = None for line in p.stdout.readlines(): - if line[0:3] == 'uid' or line[0:3] == 'pub': + if line[0:3] == LINE_FINGERPRINT: + fingerprint = line.split(':')[POS_FINGERPRINT] + if line[0:3] == LINE_USER_ID: if ('<' not in line or '>' not in line): continue email = line.split('<')[1].split('>')[0] - fingerprint = line.split(':')[4] + if not (fingerprint is None or email is None): keys[fingerprint] = email + fingerprint = None + email = None return keys # confirms a key has a given email address @@ -147,4 +161,4 @@ class GPGDecryptor: def _command(self): cmd = ["/usr/bin/gpg", "--trust-model", "always", "--homedir", self._keyhome, "--batch", "--yes", "--no-secmem-warning", "-a", "-d"] - return cmd \ No newline at end of file + return cmd diff --git a/Makefile b/Makefile index 5c50c1d..629a04b 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ test: test/tmp test/logs pre-clean pre-clean: rm -fv test/gpg-mailgate.conf + rm -f test/logs/*.log test/tmp: mkdir test/tmp diff --git a/test/e2e.ini b/test/e2e.ini index 3e0899f..b65d0dc 100644 --- a/test/e2e.ini +++ b/test/e2e.ini @@ -8,16 +8,16 @@ cases = 3 descr = Clear text message to a user without a key to = carlos@disposlab in = test/msgin/clear2clear.msg -out = test/msgout/clear2clear.msg +out = Body of the message. [case-2] descr = Clear text message to a user with an RSA key to = alice@disposlab in = test/msgin/clear2rsa.msg -out = test/msgout/clear2rsa.msg +out = -----BEGIN PGP MESSAGE----- [case-3] descr = Clear text message to a user with an Ed25519 key to = bob@disposlab in = test/msgin/clear2ed.msg -out = test/msgout/clear2ed.msg +out = -----BEGIN PGP MESSAGE----- diff --git a/test/e2e_test.py b/test/e2e_test.py index 37ccbb0..c730144 100644 --- a/test/e2e_test.py +++ b/test/e2e_test.py @@ -36,6 +36,10 @@ def build_config(config): cp.set("relay", "host", "localhost") cp.set("relay", "port", config["port"]) + cp.add_section("enc_keymap") + cp.set("enc_keymap", "alice@disposlab", "1CD245308F0963D038E88357973CF4D9387C44D7") + cp.set("enc_keymap", "bob@disposlab", "19CF4B47ECC9C47AFA84D4BD96F39FDA0E31BB67") + logging.debug("Created config with keyhome=%s, cert_path=%s and relay at port %d" % (config["gpg_keyhome"], config["smime_certpath"], config["port"])) return cp @@ -68,18 +72,27 @@ def compare(result, expected): fromfile='expected', tofile='output') -def report_result(message_file, expected_file, test_output): - expected = load_file(expected_file) - diff = compare(test_output, expected) - if len(list(diff)) > 0: - print "Output and the expected message (%s) don't match:" % (expected_file) +def report_result(message_file, expected, test_output): + status = None + if expected in test_output: + status = "Success" else: - print "Message %s processed properly" % (message_file) - for diff_line in diff: - print diff_line + status = "Failure" -def execute_e2e_test(message_file, expected_file, **kwargs): - test_command = "GPG_MAILGATE_CONFIG=%s %s gpg-mailgate.py %s < %s" % (kwargs["config_path"], PYTHON_BIN, kwargs["to_addr"], message_file) + print "%s %s" % (message_file.ljust(30, '.'), status) + +def frozen_time_expr(timestamp): + if timestamp is None: + return "" + else: + return "GPG_FROZEN_TIME=%s" % (timestamp) + +def execute_e2e_test(message_file, expected, **kwargs): + test_command = "GPG_MAILGATE_CONFIG=%s %s gpg-mailgate.py %s < %s" % ( + kwargs["config_path"], + PYTHON_BIN, + kwargs["to_addr"], + message_file) result_command = "%s %s %d" % (PYTHON_BIN, RELAY_SCRIPT, kwargs["port"]) logging.debug("Spawning: '%s'" % (result_command)) @@ -95,16 +108,16 @@ def execute_e2e_test(message_file, expected_file, **kwargs): logging.debug("Read %d characters of test output: '%s'" % (len(testout), testout)) - report_result(message_file, expected_file, testout) + report_result(message_file, expected, testout) -def load_config(): +def load_test_config(): cp = ConfigParser.ConfigParser() cp.read("test/e2e.ini") return cp -config = load_config() +config = load_test_config() logging.basicConfig(filename = "test/logs/e2e.log", format = "%(asctime)s %(pathname)s:%(lineno)d %(levelname)s [%(funcName)s] %(message)s", @@ -123,9 +136,10 @@ for case_no in range(1, config.getint("tests", "cases")+1): case_name = "case-%d" % (case_no) print "Executing: %s" % (config.get(case_name, "descr")) - execute_e2e_test(config.get(case_name, "in"), config.get(case_name, "out"), - config_path = config_path, - to_addr = config.get(case_name, "to"), - port = config.getint("relay", "port")) + execute_e2e_test(config.get(case_name, "in"), + config.get(case_name, "out"), + config_path = config_path, + to_addr = config.get(case_name, "to"), + port = config.getint("relay", "port")) - sleep(DELAY) + # sleep(DELAY) diff --git a/test/keyhome/crls.d/DIR.txt b/test/keyhome/crls.d/DIR.txt new file mode 100644 index 0000000..2a29a47 --- /dev/null +++ b/test/keyhome/crls.d/DIR.txt @@ -0,0 +1 @@ +v:1: diff --git a/test/keyhome/pubring.kbx b/test/keyhome/pubring.kbx new file mode 100644 index 0000000000000000000000000000000000000000..83e1be16f71dfa5c39e580f11efc92404693ebe4 GIT binary patch literal 3088 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 literal 0 HcmV?d00001 diff --git a/test/keyhome/random_seed b/test/keyhome/random_seed new file mode 100644 index 0000000000000000000000000000000000000000..960176317c5bd65a613745e2a1e40002c132c17a GIT binary patch literal 600 zcmV-e0;m0x7F#IZ%)U+ZOVphWztfSW%lqKz4IZtwX4T{y12-_@Hyy%MB#xsB_C0V%YsYhNnj25XQn? z6|qjVS~1x#705R9-Igwmkt|E=T7)XY8>Uw32WdzC=xx{=dpd*!SOx-$&n<$BaFazd zNbBP(a5{d0t1ZsHIaU(E#1Xh&|C|;aaUVykp2cNVVcJpXXwn`20BcUhV`Bi^1k+#( z|M3=TpA_`n)e8gXwrFmvS!VnHWYU#^0EGLrsV!?eQo|BlG7lr-B0Q|Tzlwe;r%fzE zEgFvGr{_Xm_sL^Ct-6RUmaT_{@3xs2#{^IvcR(V3RNvO7O=x-opoFLNA9!#(+1cii zf4d2*SgC;=5IZ*{QozD^?l~=lf2>j6MY3IY&!0b||@`^B&VW`#{a`OQxPY-eDG7K^iRr06BdDisnY@ zV-L@wph$J_P3XvZ;I>pdDh);p|M*s#9J{lB4NU5S*6#2}HSAe5VuL$tPmD0;XQD|0u0ft|&FQRsQ mc35E8&ZulJ|9348SS32JwKm?Ha2J`1dz3JZ^b0M<=Oib=*CGJ` literal 0 HcmV?d00001 diff --git a/test/keyhome/tofu.db b/test/keyhome/tofu.db new file mode 100644 index 0000000000000000000000000000000000000000..16104b58d2fd2658e1693b733251d6a74c9db7f1 GIT binary patch literal 49152 zcmeI&!Ef4D9KdlqZ6P5^*-k!GXul{ItXf+$wo?x)OT7%05=x2GoGc2D$SRl=o2ne9 zDb@Nv?7shD(k}bUcGxr6!Css7T3KH!i0Aj7f6wpp`w7W%o21od-{_eJGyZ@J<;Fs_@@rj`1#9evePx6^kjB4Zs+%`fME5J}&C z(RV~m2mT~!FG4l#Wnw4DgG6UCPu4y}`ed(OF+m1*!3HH?w<+62fV7_au)(QQKU=S_l zD%`ZTbE|Cn<^NXE)4Y*?p~AE%WH$FNwD-CY;^FLlIJvvhH>vAsI+!ozm0DgeL5I_I{1Q0*~0R#|0009ILD82yi a|BIhvc8CB12q1s}0tg_000IagfWW^;-obqU literal 0 HcmV?d00001 diff --git a/test/keyhome/trustdb.gpg b/test/keyhome/trustdb.gpg new file mode 100644 index 0000000000000000000000000000000000000000..769130320fc347ef0dcc957040c55c75dea82098 GIT binary patch literal 1200 zcmZQfFGy!*W@Ke#Vqi$@G5gAZ9WZiX7sn7CRfiEIV1dza84VXu2#lr!%F+P Date: Fri, 7 Jan 2022 12:00:50 +0100 Subject: [PATCH 07/14] Add a test overview, extract constants To let the user know that tests produce logs, include a message at the end of the test output informing about locations of E2E and Mailgate logs. Also, extract some constants. --- test/e2e_test.py | 53 ++++++++++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/test/e2e_test.py b/test/e2e_test.py index c730144..68044d4 100644 --- a/test/e2e_test.py +++ b/test/e2e_test.py @@ -1,5 +1,24 @@ #!/usr/local/bin/python2 +# +# gpg-mailgate +# +# This file is part of the gpg-mailgate source code. +# +# gpg-mailgate is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# gpg-mailgate source code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with gpg-mailgate source code. If not, see . +# + import os import sys @@ -10,13 +29,12 @@ import logging from time import sleep -EOL = "\n" - -DELAY = 3 - RELAY_SCRIPT = "test/relay.py" CONFIG_FILE = "test/gpg-mailgate.conf" +KEY_HOME = "test/keyhome" +CERT_HOME = "test/certs" + PYTHON_BIN = "python2.7" def build_config(config): @@ -61,17 +79,6 @@ def load_file(name): return contents -def strip_eols(strings): - return map(lambda s: s.strip("\r"), strings) - -def compare(result, expected): - result_lines = strip_eols(result.split(EOL)) - expected_lines = strip_eols(expected.split(EOL)) - - return difflib.unified_diff(expected_lines, result_lines, - fromfile='expected', - tofile='output') - def report_result(message_file, expected, test_output): status = None if expected in test_output: @@ -95,10 +102,10 @@ def execute_e2e_test(message_file, expected, **kwargs): message_file) result_command = "%s %s %d" % (PYTHON_BIN, RELAY_SCRIPT, kwargs["port"]) - logging.debug("Spawning: '%s'" % (result_command)) + logging.debug("Spawning relay: '%s'" % (result_command)) pipe = os.popen(result_command, 'r') - logging.debug("Spawning: '%s'" % (test_command)) + logging.debug("Spawning GPG-Lacre: '%s'" % (test_command)) msgin = os.popen(test_command, 'w') msgin.write(load_file(message_file)) msgin.close() @@ -118,8 +125,10 @@ def load_test_config(): config = load_test_config() +log_paths = {"e2e": "test/logs/e2e.log", + "lacre": "test/logs/gpg-mailgate.log"} -logging.basicConfig(filename = "test/logs/e2e.log", +logging.basicConfig(filename = log_paths["e2e"], format = "%(asctime)s %(pathname)s:%(lineno)d %(levelname)s [%(funcName)s] %(message)s", datefmt = "%Y-%m-%d %H:%M:%S", level = logging.DEBUG) @@ -128,9 +137,9 @@ config_path = os.getcwd() + "/" + CONFIG_FILE write_test_config(config_path, port = config.getint("relay", "port"), - gpg_keyhome = "test/keyhome", - smime_certpath = "test/certs", - log_file = "test/logs/gpg-mailgate.log") + gpg_keyhome = KEY_HOME, + smime_certpath = CERT_HOME, + log_file = log_paths["lacre"]) for case_no in range(1, config.getint("tests", "cases")+1): case_name = "case-%d" % (case_no) @@ -142,4 +151,4 @@ for case_no in range(1, config.getint("tests", "cases")+1): to_addr = config.get(case_name, "to"), port = config.getint("relay", "port")) - # sleep(DELAY) +print "See diagnostic output for details. Tests: '%s', Lacre: '%s'" % (log_paths["e2e"], log_paths["lacre"]) -- 2.30.2 From 01377f4dd2038f78e313cfee20d3457bef62135a Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Fri, 7 Jan 2022 12:03:38 +0100 Subject: [PATCH 08/14] Keep test/certs directory --- test/certs/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/certs/.keep diff --git a/test/certs/.keep b/test/certs/.keep new file mode 100644 index 0000000..e69de29 -- 2.30.2 From e90a29c9ff8fec78ee7a0c022f39c02027cb5600 Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Fri, 7 Jan 2022 12:04:14 +0100 Subject: [PATCH 09/14] Ignore temporary test directory --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6b8344e..3cafdc4 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ nosetests.xml # GPG-Mailgate test files test/logs +test/tmp test/gpg-mailgate.conf # Emacs files -- 2.30.2 From 98c4580775f949b5857670f1eead82b5083f0e9f Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Fri, 7 Jan 2022 12:10:30 +0100 Subject: [PATCH 10/14] Document E2E tests --- doc/testing.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 doc/testing.md diff --git a/doc/testing.md b/doc/testing.md new file mode 100644 index 0000000..1d84099 --- /dev/null +++ b/doc/testing.md @@ -0,0 +1,31 @@ +# Testing + +First tests have been set up to cover GPG Mailgate with at least basic test +that would be easy to run. The tests are called "end-to-end", meaning that we +feed some input to GPG Mailgate and inspect the output. + +## Running tests + +To run tests, use command `make test`. + +Tests produce some helpful logs, so inspect contents of `test/logs` directory +if something goes wrong. + +## Key building blocks + +- *Test Script* (`test/e2e_test.py`) that orchestrates the other components. + It performs test cases described in the *Test Configuration*. It spawns + *Test Mail Relay* and *GPG Mailgate* in appropriate order. +- *Test Mail Relay* (`test/relay.py`), a simplistic mail daemon that only + supports the happy path. It accepts a mail message and prints it to + stdandard output. +- *Test Configuration* (`test/e2e.ini`) specifies test cases: their input, + expected results and helpful documentation. It also specifies the port that + the *Test Mail Relay* should listen on. + +## Limitations + +Currently tests only check if the message has been encrypted, without +verifying that the correct key has been used. That's because we don't know +(yet) how to have a reproducible encrypted message. Option +`--faked-system-time` wasn't enough to produce identical output. -- 2.30.2 From fc2779ef7d0dd2aca5a98480e478d1d1e6785f70 Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Sat, 8 Jan 2022 13:42:23 +0100 Subject: [PATCH 11/14] Improve test code structure - Move things to configuration file where appropriate (logging format, etc.). - Rework execute_e2e_test signature to simplify it and get rid of keyword arguments. - Simplify output. - Include a header comment in configuration file. --- test/e2e.ini | 60 +++++++++++++++++++++++++++++++++++++----------- test/e2e_test.py | 57 +++++++++++++++++++++------------------------ 2 files changed, 72 insertions(+), 45 deletions(-) diff --git a/test/e2e.ini b/test/e2e.ini index b65d0dc..2d6fb0b 100644 --- a/test/e2e.ini +++ b/test/e2e.ini @@ -1,23 +1,55 @@ +# +# gpg-mailgate +# +# This file is part of the gpg-mailgate source code. +# +# gpg-mailgate is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# gpg-mailgate source code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with gpg-mailgate source code. If not, see . +# + +# NOTE: We use : syntax, because some values contain +# colons and that is default ConfigParser key-value separator. + [relay] -port = 2500 +port: 2500 +script: test/relay.py + +[dirs] +keys: test/keyhome +certs: test/certs [tests] -cases = 3 +# Number of "test-*" sections in this file, describing test cases. +cases: 3 +e2e_log: test/logs/e2e.log +e2e_log_format: %(asctime)s %(pathname)s:%(lineno)d %(levelname)s [%(funcName)s] %(message)s +e2e_log_datefmt: %Y-%m-%d %H:%M:%S +lacre_log: test/logs/gpg-mailgate.log [case-1] -descr = Clear text message to a user without a key -to = carlos@disposlab -in = test/msgin/clear2clear.msg -out = Body of the message. +descr: Clear text message to a user without a key +to: carlos@disposlab +in: test/msgin/clear2clear.msg +out: Body of the message. [case-2] -descr = Clear text message to a user with an RSA key -to = alice@disposlab -in = test/msgin/clear2rsa.msg -out = -----BEGIN PGP MESSAGE----- +descr: Clear text message to a user with an RSA key +to: alice@disposlab +in: test/msgin/clear2rsa.msg +out: -----BEGIN PGP MESSAGE----- [case-3] -descr = Clear text message to a user with an Ed25519 key -to = bob@disposlab -in = test/msgin/clear2ed.msg -out = -----BEGIN PGP MESSAGE----- +descr: Clear text message to a user with an Ed25519 key +to: bob@disposlab +in: test/msgin/clear2ed.msg +out: -----BEGIN PGP MESSAGE----- diff --git a/test/e2e_test.py b/test/e2e_test.py index 68044d4..d53d6d9 100644 --- a/test/e2e_test.py +++ b/test/e2e_test.py @@ -32,9 +32,6 @@ from time import sleep RELAY_SCRIPT = "test/relay.py" CONFIG_FILE = "test/gpg-mailgate.conf" -KEY_HOME = "test/keyhome" -CERT_HOME = "test/certs" - PYTHON_BIN = "python2.7" def build_config(config): @@ -86,28 +83,29 @@ def report_result(message_file, expected, test_output): else: status = "Failure" - print "%s %s" % (message_file.ljust(30, '.'), status) + print message_file.ljust(30), status -def frozen_time_expr(timestamp): - if timestamp is None: - return "" - else: - return "GPG_FROZEN_TIME=%s" % (timestamp) +def execute_e2e_test(case_name, config, config_path): + """Read test case configuration from config and run that test case. + + Parameter case_name should refer to a section in test + config file. Each of these sections should contain + following properties: 'descr', 'to', 'in' and 'out'. + """ -def execute_e2e_test(message_file, expected, **kwargs): test_command = "GPG_MAILGATE_CONFIG=%s %s gpg-mailgate.py %s < %s" % ( - kwargs["config_path"], + config_path, PYTHON_BIN, - kwargs["to_addr"], - message_file) - result_command = "%s %s %d" % (PYTHON_BIN, RELAY_SCRIPT, kwargs["port"]) + config.get(case_name, "to"), + config.get(case_name, "in")) + result_command = "%s %s %d" % (PYTHON_BIN, config.get("relay", "script"), config.getint("relay", "port")) logging.debug("Spawning relay: '%s'" % (result_command)) pipe = os.popen(result_command, 'r') logging.debug("Spawning GPG-Lacre: '%s'" % (test_command)) msgin = os.popen(test_command, 'w') - msgin.write(load_file(message_file)) + msgin.write(load_file(config.get(case_name, "in"))) msgin.close() testout = pipe.read() @@ -115,7 +113,7 @@ def execute_e2e_test(message_file, expected, **kwargs): logging.debug("Read %d characters of test output: '%s'" % (len(testout), testout)) - report_result(message_file, expected, testout) + report_result(config.get(case_name, "in"), config.get(case_name, "out"), testout) def load_test_config(): cp = ConfigParser.ConfigParser() @@ -125,30 +123,27 @@ def load_test_config(): config = load_test_config() -log_paths = {"e2e": "test/logs/e2e.log", - "lacre": "test/logs/gpg-mailgate.log"} -logging.basicConfig(filename = log_paths["e2e"], - format = "%(asctime)s %(pathname)s:%(lineno)d %(levelname)s [%(funcName)s] %(message)s", - datefmt = "%Y-%m-%d %H:%M:%S", +logging.basicConfig(filename = config.get("tests", "e2e_log"), + # Get raw values of log and date formats because they + # contain %-sequences and we don't want them to be expanded + # by the ConfigParser. + format = config.get("tests", "e2e_log_format", True), + datefmt = config.get("tests", "e2e_log_datefmt", True), level = logging.DEBUG) config_path = os.getcwd() + "/" + CONFIG_FILE write_test_config(config_path, port = config.getint("relay", "port"), - gpg_keyhome = KEY_HOME, - smime_certpath = CERT_HOME, - log_file = log_paths["lacre"]) + gpg_keyhome = config.get("dirs", "keys"), + smime_certpath = config.get("dirs", "certs"), + log_file = config.get("tests", "lacre_log")) for case_no in range(1, config.getint("tests", "cases")+1): case_name = "case-%d" % (case_no) - print "Executing: %s" % (config.get(case_name, "descr")) + logging.info("Executing %s: %s", case_name, config.get(case_name, "descr")) - execute_e2e_test(config.get(case_name, "in"), - config.get(case_name, "out"), - config_path = config_path, - to_addr = config.get(case_name, "to"), - port = config.getint("relay", "port")) + execute_e2e_test(case_name, config, config_path) -print "See diagnostic output for details. Tests: '%s', Lacre: '%s'" % (log_paths["e2e"], log_paths["lacre"]) +print "See diagnostic output for details. Tests: '%s', Lacre: '%s'" % (config.get("tests", "e2e_log"), config.get("tests", "lacre_log")) -- 2.30.2 From 2cf60dec40d00de7c8b7b08a8abe421cb7c8af7d Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Sun, 9 Jan 2022 21:00:51 +0100 Subject: [PATCH 12/14] Add unit tests for GnuPG command-line generator Extract a function to calculate GPG commands to be executed and cover it with unit tests. --- GnuPG/__init__.py | 20 +++++++++++--------- Makefile | 13 +++++++++++++ test/test_gnupg.py | 16 ++++++++++++++++ 3 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 test/test_gnupg.py diff --git a/GnuPG/__init__.py b/GnuPG/__init__.py index 294f8c9..540622e 100644 --- a/GnuPG/__init__.py +++ b/GnuPG/__init__.py @@ -31,8 +31,12 @@ LINE_USER_ID = 'uid' POS_FINGERPRINT = 9 +def build_command(key_home, *args, **kwargs): + cmd = ["gpg", '--homedir', key_home] + list(args) + return cmd + def private_keys( keyhome ): - cmd = ['/usr/bin/gpg', '--homedir', keyhome, '--list-secret-keys', '--with-colons'] + cmd = build_command(keyhome, '--list-secret-keys', '--with-colons') p = subprocess.Popen( cmd, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) p.wait() keys = dict() @@ -46,7 +50,7 @@ def private_keys( keyhome ): return keys def public_keys( keyhome ): - cmd = ['/usr/bin/gpg', '--homedir', keyhome, '--list-keys', '--with-colons'] + cmd = build_command(keyhome, '--list-keys', '--with-colons') p = subprocess.Popen( cmd, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) p.wait() @@ -78,7 +82,7 @@ def confirm_key( content, email ): os.mkdir(tmpkeyhome) localized_env = os.environ.copy() localized_env["LANG"] = "C" - p = subprocess.Popen( ['/usr/bin/gpg', '--homedir', tmpkeyhome, '--import', '--batch'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=localized_env ) + p = subprocess.Popen( build_command(tmpkeyhome, '--import', '--batch'), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=localized_env ) result = p.communicate(input=content)[1] confirmed = False @@ -97,7 +101,7 @@ def confirm_key( content, email ): # adds a key and ensures it has the given email address def add_key( keyhome, content ): - p = subprocess.Popen( ['/usr/bin/gpg', '--homedir', keyhome, '--import', '--batch'], stdin=subprocess.PIPE, stdout=subprocess.PIPE,stderr=subprocess.PIPE ) + p = subprocess.Popen( build_command(keyhome, '--import', '--batch'), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) p.communicate(input=content) p.wait() @@ -107,7 +111,7 @@ def delete_key( keyhome, email ): if result[1]: # delete all keys matching this email address - p = subprocess.Popen( ['/usr/bin/gpg', '--homedir', keyhome, '--delete-key', '--batch', '--yes', result[1]], stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) + p = subprocess.Popen( build_command(keyhome, '--delete-key', '--batch', '--yes', result[1]), stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) p.wait() return True @@ -131,7 +135,7 @@ class GPGEncryptor: return (encdata, p.returncode) def _command(self): - cmd = ["/usr/bin/gpg", "--trust-model", "always", "--homedir", self._keyhome, "--batch", "--yes", "--pgp7", "--no-secmem-warning", "-a", "-e"] + cmd = build_command(self._keyhome, "--trust-model", "always", "--batch", "--yes", "--pgp7", "--no-secmem-warning", "-a", "-e") # add recipients for recipient in self._recipients: @@ -159,6 +163,4 @@ class GPGDecryptor: return (decdata, p.returncode) def _command(self): - cmd = ["/usr/bin/gpg", "--trust-model", "always", "--homedir", self._keyhome, "--batch", "--yes", "--no-secmem-warning", "-a", "-d"] - - return cmd + return build_command(self._keyhome, "--trust-model", "always", "--batch", "--yes", "--no-secmem-warning", "-a", "-d") diff --git a/Makefile b/Makefile index 629a04b..a14332a 100644 --- a/Makefile +++ b/Makefile @@ -2,9 +2,22 @@ PYTHON = python2.7 .PHONY: test pre-clean clean +# +# Run a set of end-to-end tests. +# +# Test scenarios are described and configured by the test/e2e.ini +# file. Basically this is just a script that feeds GPG Mailgate with +# known input and checks whether output meets expectations. +# test: test/tmp test/logs pre-clean $(PYTHON) test/e2e_test.py +# +# Run unit tests +# +unittest: + $(PYTHON) -m unittest discover -s test + pre-clean: rm -fv test/gpg-mailgate.conf rm -f test/logs/*.log diff --git a/test/test_gnupg.py b/test/test_gnupg.py new file mode 100644 index 0000000..5712acd --- /dev/null +++ b/test/test_gnupg.py @@ -0,0 +1,16 @@ +import GnuPG + +import unittest + +class GnuPGUtilitiesTest(unittest.TestCase): + def test_build_default_command(self): + cmd = GnuPG.build_command("test/keyhome") + self.assertEqual(cmd, ["gpg", "--homedir", "test/keyhome"]) + + def test_build_command_extended_with_args(self): + cmd = GnuPG.build_command("test/keyhome", "--foo", "--bar") + self.assertEqual(cmd, ["gpg", "--homedir", "test/keyhome", "--foo", "--bar"]) + + +if __name__ == '__main__': + unittest.main() -- 2.30.2 From 1002da78fa2c309842f734cca89c839fb57a53dc Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Sun, 9 Jan 2022 21:58:14 +0100 Subject: [PATCH 13/14] Clean up test messages Remove unused msgout files, fix addresses in msgin files. --- doc/testing.md | 2 +- test/msgin/clear2clear.msg | 4 ++-- test/msgin/clear2ed.msg | 4 ++-- test/msgin/clear2rsa.msg | 2 +- test/msgout/clear2clear.msg | 5 ----- test/msgout/clear2ed.msg | 5 ----- test/msgout/clear2rsa.msg | 5 ----- 7 files changed, 6 insertions(+), 21 deletions(-) delete mode 100644 test/msgout/clear2clear.msg delete mode 100644 test/msgout/clear2ed.msg delete mode 100644 test/msgout/clear2rsa.msg diff --git a/doc/testing.md b/doc/testing.md index 1d84099..a462b0f 100644 --- a/doc/testing.md +++ b/doc/testing.md @@ -6,7 +6,7 @@ feed some input to GPG Mailgate and inspect the output. ## Running tests -To run tests, use command `make test`. +To run tests, use command `make test` or `make unittest`. Tests produce some helpful logs, so inspect contents of `test/logs` directory if something goes wrong. diff --git a/test/msgin/clear2clear.msg b/test/msgin/clear2clear.msg index 5fe7ff7..ae577d1 100644 --- a/test/msgin/clear2clear.msg +++ b/test/msgin/clear2clear.msg @@ -1,5 +1,5 @@ -From: Bob -To: Alice +From: Dave +To: Carlos Subject: Test Body of the message. diff --git a/test/msgin/clear2ed.msg b/test/msgin/clear2ed.msg index 5fe7ff7..63b6c66 100644 --- a/test/msgin/clear2ed.msg +++ b/test/msgin/clear2ed.msg @@ -1,5 +1,5 @@ -From: Bob -To: Alice +From: Dave +To: Bob Subject: Test Body of the message. diff --git a/test/msgin/clear2rsa.msg b/test/msgin/clear2rsa.msg index 5fe7ff7..9dfd134 100644 --- a/test/msgin/clear2rsa.msg +++ b/test/msgin/clear2rsa.msg @@ -1,4 +1,4 @@ -From: Bob +From: Dave To: Alice Subject: Test diff --git a/test/msgout/clear2clear.msg b/test/msgout/clear2clear.msg deleted file mode 100644 index 5fe7ff7..0000000 --- a/test/msgout/clear2clear.msg +++ /dev/null @@ -1,5 +0,0 @@ -From: Bob -To: Alice -Subject: Test - -Body of the message. diff --git a/test/msgout/clear2ed.msg b/test/msgout/clear2ed.msg deleted file mode 100644 index 5fe7ff7..0000000 --- a/test/msgout/clear2ed.msg +++ /dev/null @@ -1,5 +0,0 @@ -From: Bob -To: Alice -Subject: Test - -Body of the message. diff --git a/test/msgout/clear2rsa.msg b/test/msgout/clear2rsa.msg deleted file mode 100644 index 5fe7ff7..0000000 --- a/test/msgout/clear2rsa.msg +++ /dev/null @@ -1,5 +0,0 @@ -From: Bob -To: Alice -Subject: Test - -Body of the message. -- 2.30.2 From 3b9f714cdb2c340dac8c15f229121f3863be6bbb Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Mon, 10 Jan 2022 17:47:23 +0100 Subject: [PATCH 14/14] Ignore random_seed Do not keep test/keyhome/random_seed in repository. --- .gitignore | 1 + test/keyhome/random_seed | Bin 600 -> 0 bytes 2 files changed, 1 insertion(+) delete mode 100644 test/keyhome/random_seed diff --git a/.gitignore b/.gitignore index 3cafdc4..140af95 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ nosetests.xml test/logs test/tmp test/gpg-mailgate.conf +test/keyhome/random_seed # Emacs files *~ diff --git a/test/keyhome/random_seed b/test/keyhome/random_seed deleted file mode 100644 index 960176317c5bd65a613745e2a1e40002c132c17a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 600 zcmV-e0;m0x7F#IZ%)U+ZOVphWztfSW%lqKz4IZtwX4T{y12-_@Hyy%MB#xsB_C0V%YsYhNnj25XQn? z6|qjVS~1x#705R9-Igwmkt|E=T7)XY8>Uw32WdzC=xx{=dpd*!SOx-$&n<$BaFazd zNbBP(a5{d0t1ZsHIaU(E#1Xh&|C|;aaUVykp2cNVVcJpXXwn`20BcUhV`Bi^1k+#( z|M3=TpA_`n)e8gXwrFmvS!VnHWYU#^0EGLrsV!?eQo|BlG7lr-B0Q|Tzlwe;r%fzE zEgFvGr{_Xm_sL^Ct-6RUmaT_{@3xs2#{^IvcR(V3RNvO7O=x-opoFLNA9!#(+1cii zf4d2*SgC;=5IZ*{QozD^?l~=lf2>j6MY3IY&!0b||@`^B&VW`#{a`OQxPY-eDG7K^iRr06BdDisnY@ zV-L@wph$J_P3XvZ;I>pdDh);p|M*s#9J{lB4NU5S*6#2}HSAe5VuL$tPmD0;XQD|0u0ft|&FQRsQ mc35E8&ZulJ|9348SS32JwKm?Ha2J`1dz3JZ^b0M<=Oib=*CGJ` -- 2.30.2