diff --git a/.github/workflows/generic_test.yml b/.github/workflows/generic_test.yml index 0e4f1ab4..d862a2a6 100644 --- a/.github/workflows/generic_test.yml +++ b/.github/workflows/generic_test.yml @@ -14,6 +14,9 @@ jobs: run-tests: name: 'Test' runs-on: ubuntu-20.04 + strategy: + matrix: + part: [serial, parallel/set1, parallel/set2, parallel/set3] steps: - name: Checkout uses: actions/checkout@v3 @@ -45,6 +48,6 @@ jobs: cache-from: type=local,src=/tmp/.buildx-cache - name: 'Run tests' - run: make generate-accounts tests + run: make generate-accounts tests/${{ matrix.part }} env: CI: true diff --git a/Makefile b/Makefile index ce7ee47a..1577ab7c 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,12 @@ -SHELL := /bin/bash -.SHELLFLAGS += -e -u -o pipefail +SHELL := /bin/bash +.SHELLFLAGS += -e -u -o pipefail -export IMAGE_NAME := mailserver-testing:ci -export NAME ?= $(IMAGE_NAME) +PARALLEL_JOBS ?= 2 +export REPOSITORY_ROOT := $(CURDIR) +export IMAGE_NAME ?= mailserver-testing:ci +export NAME ?= $(IMAGE_NAME) + +.PHONY: ALWAYS_RUN # ----------------------------------------------- # --- Generic Targets --------------------------- @@ -17,7 +21,7 @@ build: --build-arg VCS_REVISION=$(shell cat VERSION) \ . -generate-accounts: +generate-accounts: ALWAYS_RUN @ cp test/config/templates/postfix-accounts.cf test/config/postfix-accounts.cf @ cp test/config/templates/dovecot-masters.cf test/config/dovecot-masters.cf @@ -33,14 +37,29 @@ clean: -@ while read -r LINE; do [[ $${LINE} =~ test/.+ ]] && sudo rm -rf $${LINE}; done < .gitignore # ----------------------------------------------- -# --- Tests & Lints ---------------------------- +# --- Tests ------------------------------------ # ----------------------------------------------- -tests: - @ ./test/bats/bin/bats --timing test/*.bats +tests: ALWAYS_RUN +# See https://github.com/docker-mailserver/docker-mailserver/pull/2857#issuecomment-1312724303 +# on why `generate-accounts` is run before each set (TODO/FIXME) + @ $(MAKE) generate-accounts tests/serial + @ $(MAKE) generate-accounts tests/parallel/set1 + @ $(MAKE) generate-accounts tests/parallel/set2 + @ $(MAKE) generate-accounts tests/parallel/set3 -test/%: - @ ./test/bats/bin/bats --timing $@.bats +tests/serial: ALWAYS_RUN + @ shopt -s globstar ; ./test/bats/bin/bats --timing --jobs 1 test/$@/**.bats + +tests/parallel/set%: ALWAYS_RUN + @ shopt -s globstar ; ./test/bats/bin/bats --timing --jobs $(PARALLEL_JOBS) test/$@/**.bats + +test/%: ALWAYS_RUN + @ shopt -s globstar nullglob ; ./test/bats/bin/bats --timing test/tests/**/{$*,}.bats + +# ----------------------------------------------- +# --- Lints ------------------------------------- +# ----------------------------------------------- lint: eclint hadolint shellcheck diff --git a/docs/content/contributing/general.md b/docs/content/contributing/general.md index da3b00fc..7270fcb8 100644 --- a/docs/content/contributing/general.md +++ b/docs/content/contributing/general.md @@ -24,6 +24,12 @@ To run the test suite, you will need to We do not support running linting, tests, etc on macOS at this time. Please use a linux VM. +??? tip "Running a Specific Test" + + To run a specific test, use `make build generate-accounts test/`, where `` is the file name of the test (_for more precision use a relative path: `test/test/`_) excluding the `.bats` suffix. + + To run only the tests in `template.bats`, use `make test/template` (or `make test/parallel/set2/template`). + [Install Docker]: https://docs.docker.com/get-docker/ ## Documentation diff --git a/test/clamav.bats b/test/clamav.bats deleted file mode 100644 index 2a42cc13..00000000 --- a/test/clamav.bats +++ /dev/null @@ -1,79 +0,0 @@ -load 'test_helper/common' - -TEST_NAME_PREFIX='ClamAV:' -CONTAINER_NAME='dms-test-clamav' -RUN_COMMAND=('run' 'docker' 'exec' "${CONTAINER_NAME}") - -function setup_file() { - local PRIVATE_CONFIG - PRIVATE_CONFIG=$(duplicate_config_for_container . "${CONTAINER_NAME}") - - docker run --rm --detach --tty \ - --name "${CONTAINER_NAME}" \ - --hostname mail.my-domain.com \ - --volume "${PRIVATE_CONFIG}:/tmp/docker-mailserver" \ - --volume "${PWD}/test/test-files:/tmp/docker-mailserver-test:ro" \ - --env ENABLE_AMAVIS=1 \ - --env AMAVIS_LOGLEVEL=2 \ - --env ENABLE_CLAMAV=1 \ - --env ENABLE_UPDATE_CHECK=0 \ - --env ENABLE_SPAMASSASSIN=0 \ - --env ENABLE_FAIL2BAN=0 \ - --env PERMIT_DOCKER=host \ - --env CLAMAV_MESSAGE_SIZE_LIMIT=30M \ - --env LOG_LEVEL=debug \ - "${IMAGE_NAME}" - - wait_for_finished_setup_in_container "${CONTAINER_NAME}" - - # wait for ClamAV to be fully setup or we will get errors on the log - repeat_in_container_until_success_or_timeout 60 "${CONTAINER_NAME}" test -e /var/run/clamav/clamd.ctl - - wait_for_service "${CONTAINER_NAME}" postfix - wait_for_smtp_port_in_container "${CONTAINER_NAME}" - - "${RUN_COMMAND[@]}" bash -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/amavis-virus.txt" - assert_success - - wait_for_empty_mail_queue_in_container "${CONTAINER_NAME}" -} - -function teardown_file() { - docker rm -f "${CONTAINER_NAME}" -} - -@test "${TEST_NAME_PREFIX} process clamd is running" { - "${RUN_COMMAND[@]}" bash -c "ps aux --forest | grep -v grep | grep '/usr/sbin/clamd'" - assert_success -} - -@test "${TEST_NAME_PREFIX} log files exist at /var/log/mail directory" { - "${RUN_COMMAND[@]}" bash -c "ls -1 /var/log/mail/ | grep -E 'clamav|freshclam|mail.log'| wc -l" - assert_success - assert_output 3 -} - -@test "${TEST_NAME_PREFIX} should be identified by Amavis" { - "${RUN_COMMAND[@]}" grep -i 'Found secondary av scanner ClamAV-clamscan' /var/log/mail/mail.log - assert_success -} - -@test "${TEST_NAME_PREFIX} freshclam cron is enabled" { - "${RUN_COMMAND[@]}" bash -c "grep '/usr/bin/freshclam' -r /etc/cron.d" - assert_success -} - -@test "${TEST_NAME_PREFIX} env CLAMAV_MESSAGE_SIZE_LIMIT is set correctly" { - "${RUN_COMMAND[@]}" grep -q '^MaxFileSize 30M$' /etc/clamav/clamd.conf - assert_success -} - -@test "${TEST_NAME_PREFIX} rejects virus" { - "${RUN_COMMAND[@]}" bash -c "grep 'Blocked INFECTED' /var/log/mail/mail.log | grep ' -> '" - assert_success -} - -@test "${TEST_NAME_PREFIX} process clamd restarts when killed" { - "${RUN_COMMAND[@]}" bash -c "pkill clamd && sleep 10 && ps aux --forest | grep -v grep | grep '/usr/sbin/clamd'" - assert_success -} diff --git a/test/default_relay_host.bats b/test/default_relay_host.bats deleted file mode 100644 index b22420ed..00000000 --- a/test/default_relay_host.bats +++ /dev/null @@ -1,28 +0,0 @@ -load 'test_helper/common' - -function setup() { - local PRIVATE_CONFIG - PRIVATE_CONFIG=$(duplicate_config_for_container relay-hosts) - - docker run -d --name mail_with_default_relay \ - -v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \ - -v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \ - -e DEFAULT_RELAY_HOST=default.relay.host.invalid:25 \ - -e PERMIT_DOCKER=host \ - -h mail.my-domain.com -t "${NAME}" - - wait_for_finished_setup_in_container mail_with_default_relay -} - -function teardown() { - docker rm -f mail_with_default_relay -} - -# -# default relay host -# - -@test "checking default relay host: default relay host is added to main.cf" { - run docker exec mail_with_default_relay /bin/sh -c 'grep -e "^relayhost =" /etc/postfix/main.cf' - assert_output 'relayhost = default.relay.host.invalid:25' -} diff --git a/test/dovecot_inet_protocol.bats b/test/dovecot_inet_protocol.bats deleted file mode 100644 index ab31db4a..00000000 --- a/test/dovecot_inet_protocol.bats +++ /dev/null @@ -1,54 +0,0 @@ -load 'test_helper/common' - -function setup_file() { - local PRIVATE_CONFIG - export ALL IPV4 IPV6 - - PRIVATE_CONFIG=$(duplicate_config_for_container . "${IPV4}") - ALL="mail_dovecot_all_protocols" - IPV4="mail_dovecot_ipv4" - IPV6="mail_dovecot_ipv6" - - docker run --rm -d --name "${ALL}" \ - -v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \ - -e DOVECOT_INET_PROTOCOLS= \ - -h mail.my-domain.com \ - -t "${NAME}" - - docker run --rm -d --name "${IPV4}" \ - -v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \ - -e DOVECOT_INET_PROTOCOLS=ipv4 \ - -h mail.my-domain.com \ - -t "${NAME}" - - docker run --rm -d --name "${IPV6}" \ - -v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \ - -e DOVECOT_INET_PROTOCOLS=ipv6 \ - -h mail.my-domain.com \ - -t "${NAME}" -} - -@test 'checking dovecot IP configuration' { - wait_for_finished_setup_in_container "${ALL}" - run docker exec "${ALL}" grep '^#listen = \*, ::' /etc/dovecot/dovecot.conf - assert_success - assert_output '#listen = *, ::' -} - -@test 'checking dovecot IPv4 configuration' { - wait_for_finished_setup_in_container "${IPV4}" - run docker exec "${IPV4}" grep '^listen = \*$' /etc/dovecot/dovecot.conf - assert_success - assert_output 'listen = *' -} - -@test 'checking dovecot IPv6 configuration' { - wait_for_finished_setup_in_container "${IPV6}" - run docker exec "${IPV6}" grep '^listen = \[::\]$' /etc/dovecot/dovecot.conf - assert_success - assert_output 'listen = [::]' -} - -function teardown_file { - docker rm -f "${ALL}" "${IPV4}" "${IPV6}" -} diff --git a/test/helper-functions.bats b/test/helper-functions.bats deleted file mode 100644 index 2c10c9e1..00000000 --- a/test/helper-functions.bats +++ /dev/null @@ -1,30 +0,0 @@ -load 'test_helper/common' - -function setup_file() { - local PRIVATE_CONFIG - PRIVATE_CONFIG=$(duplicate_config_for_container .) - - docker run -d --name mail_helper_functions \ - --cap-add=NET_ADMIN \ - -v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \ - -v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \ - -e ENABLE_FETCHMAIL=1 \ - -h mail.my-domain.com -t "${NAME}" - - wait_for_finished_setup_in_container mail_helper_functions -} - -function teardown_file() { - docker rm -f mail_helper_functions -} - -@test "check helper functions (network.sh): _sanitize_ipv4_to_subnet_cidr" { - run docker exec mail_helper_functions bash -c "source /usr/local/bin/helpers/index.sh; _sanitize_ipv4_to_subnet_cidr 255.255.255.255/0" - assert_output "0.0.0.0/0" - - run docker exec mail_helper_functions bash -c "source /usr/local/bin/helpers/index.sh; _sanitize_ipv4_to_subnet_cidr 192.168.255.14/20" - assert_output "192.168.240.0/20" - - run docker exec mail_helper_functions bash -c "source /usr/local/bin/helpers/index.sh; _sanitize_ipv4_to_subnet_cidr 192.168.255.14/32" - assert_output "192.168.255.14/32" -} diff --git a/test/helper/common.bash b/test/helper/common.bash new file mode 100644 index 00000000..6df8a2de --- /dev/null +++ b/test/helper/common.bash @@ -0,0 +1,229 @@ +#!/bin/bash + +function __load_bats_helper() { + load "${REPOSITORY_ROOT}/test/test_helper/bats-support/load" + load "${REPOSITORY_ROOT}/test/test_helper/bats-assert/load" +} + +__load_bats_helper + +# ------------------------------------------------------------------- + +function _run_in_container() { + run docker exec "${CONTAINER_NAME}" "${@}" +} + +function _default_teardown() { + docker rm -f "${CONTAINER_NAME}" +} + +# ------------------------------------------------------------------- + +# @param ${1} timeout +# @param --fatal-test additional test whose failure aborts immediately +# @param ... test to run +function repeat_until_success_or_timeout { + local FATAL_FAILURE_TEST_COMMAND + + if [[ "${1}" == "--fatal-test" ]]; then + FATAL_FAILURE_TEST_COMMAND="${2}" + shift 2 + fi + + if ! [[ "${1}" =~ ^[0-9]+$ ]]; then + echo "First parameter for timeout must be an integer, received \"${1}\"" + return 1 + fi + + local TIMEOUT=${1} + local STARTTIME=${SECONDS} + shift 1 + + until "${@}" + do + if [[ -n ${FATAL_FAILURE_TEST_COMMAND} ]] && ! eval "${FATAL_FAILURE_TEST_COMMAND}"; then + echo "\`${FATAL_FAILURE_TEST_COMMAND}\` failed, early aborting repeat_until_success of \`${*}\`" >&2 + return 1 + fi + + sleep 1 + + if [[ $(( SECONDS - STARTTIME )) -gt ${TIMEOUT} ]]; then + echo "Timed out on command: ${*}" >&2 + return 1 + fi + done +} + +# like repeat_until_success_or_timeout but with wrapping the command to run into `run` for later bats consumption +# @param ${1} timeout +# @param ... test command to run +function run_until_success_or_timeout { + if ! [[ ${1} =~ ^[0-9]+$ ]]; then + echo "First parameter for timeout must be an integer, received \"${1}\"" + return 1 + fi + + local TIMEOUT=${1} + local STARTTIME=${SECONDS} + shift 1 + + until run "${@}" && [[ $status -eq 0 ]] + do + sleep 1 + + if (( SECONDS - STARTTIME > TIMEOUT )); then + echo "Timed out on command: ${*}" >&2 + return 1 + fi + done +} + +# @param ${1} timeout +# @param ${2} container name +# @param ... test command for container +function repeat_in_container_until_success_or_timeout() { + local TIMEOUT="${1}" + local CONTAINER_NAME="${2}" + shift 2 + + repeat_until_success_or_timeout --fatal-test "container_is_running ${CONTAINER_NAME}" "${TIMEOUT}" docker exec "${CONTAINER_NAME}" "${@}" +} + +function container_is_running() { + [[ "$(docker inspect -f '{{.State.Running}}' "${1}")" == "true" ]] +} + +# @param ${1} port +# @param ${2} container name +function wait_for_tcp_port_in_container() { + repeat_until_success_or_timeout --fatal-test "container_is_running ${2}" "${TEST_TIMEOUT_IN_SECONDS}" docker exec "${2}" /bin/sh -c "nc -z 0.0.0.0 ${1}" +} + +# @param ${1} name of the postfix container +function wait_for_smtp_port_in_container() { + wait_for_tcp_port_in_container 25 "${1}" +} + +# @param ${1} name of the postfix container +function wait_for_smtp_port_in_container_to_respond() { + local COUNT=0 + until [[ $(docker exec "${1}" timeout 10 /bin/sh -c "echo QUIT | nc localhost 25") == *"221 2.0.0 Bye"* ]]; do + if [[ $COUNT -eq 20 ]] + then + echo "Unable to receive a valid response from 'nc localhost 25' within 20 seconds" + return 1 + fi + + sleep 1 + ((COUNT+=1)) + done +} + +# @param ${1} name of the postfix container +function wait_for_amavis_port_in_container() { + wait_for_tcp_port_in_container 10024 "${1}" +} + +# get the private config path for the given container or test file, if no container name was given +function private_config_path() { + echo "${PWD}/test/duplicate_configs/${1:-$(basename "${BATS_TEST_FILENAME}")}" +} + +function container_has_service_running() { + local CONTAINER_NAME="${1}" + local SERVICE_NAME="${2}" + + docker exec "${CONTAINER_NAME}" /usr/bin/supervisorctl status "${SERVICE_NAME}" | grep RUNNING >/dev/null +} + +function wait_for_service() { + local CONTAINER_NAME="${1}" + local SERVICE_NAME="${2}" + + repeat_until_success_or_timeout --fatal-test "container_is_running ${CONTAINER_NAME}" "${TEST_TIMEOUT_IN_SECONDS}" \ + container_has_service_running "${CONTAINER_NAME}" "${SERVICE_NAME}" +} + +function wait_for_changes_to_be_detected_in_container() { + local CONTAINER_NAME="${1}" + local TIMEOUT=${TEST_TIMEOUT_IN_SECONDS} + + # shellcheck disable=SC2016 + repeat_in_container_until_success_or_timeout "${TIMEOUT}" "${CONTAINER_NAME}" bash -c 'source /usr/local/bin/helpers/index.sh; _obtain_hostname_and_domainname; cmp --silent -- <(_monitored_files_checksums) "${CHKSUM_FILE}" >/dev/null' +} + +# Relies on ENV `LOG_LEVEL=debug` or higher +function wait_until_change_detection_event_completes() { + local CONTAINER_NAME="${1}" + # Ensure early failure if arg is missing: + assert_not_equal "${CONTAINER_NAME}" "" + + # Ensure the container is configured with the required `LOG_LEVEL` ENV: + assert_regex \ + $(docker exec "${CONTAINER_NAME}" env | grep '^LOG_LEVEL=') \ + '=(debug|trace)$' + + local CHANGE_EVENT_START='Change detected' + local CHANGE_EVENT_END='Completed handling of detected change' # debug log + + function __change_event_status() { + docker exec "${CONTAINER_NAME}" \ + grep -oE "${CHANGE_EVENT_START}|${CHANGE_EVENT_END}" /var/log/supervisor/changedetector.log \ + | tail -1 + } + + function __is_changedetector_processing() { + [[ $(__change_event_status) == "${CHANGE_EVENT_START}" ]] + } + + function __is_changedetector_finished() { + [[ $(__change_event_status) == "${CHANGE_EVENT_END}" ]] + } + + if [[ ! $(__is_changedetector_processing) ]] + then + # A new change event is expected, wait for it: + repeat_until_success_or_timeout 60 __is_changedetector_processing + fi + + # Change event is in progress, wait until it finishes: + repeat_until_success_or_timeout 60 __is_changedetector_finished + + # NOTE: Although the change event has completed, services like Postfix and Dovecot + # may still be in the process of restarting. + # You may still want to wait longer if depending on those to be ready. +} + +# An account added to `postfix-accounts.cf` must wait for the `changedetector` service +# to process the update before Dovecot creates the mail account and associated storage dir: +function wait_until_account_maildir_exists() { + local CONTAINER_NAME=$1 + local MAIL_ACCOUNT=$2 + + local LOCAL_PART="${MAIL_ACCOUNT%@*}" + local DOMAIN_PART="${MAIL_ACCOUNT#*@}" + local MAIL_ACCOUNT_STORAGE_DIR="/var/mail/${DOMAIN_PART}/${LOCAL_PART}" + + repeat_in_container_until_success_or_timeout 60 "${CONTAINER_NAME}" bash -c "[[ -d ${MAIL_ACCOUNT_STORAGE_DIR} ]]" +} + +function add_mail_account_then_wait_until_ready() { + local CONTAINER_NAME=$1 + local MAIL_ACCOUNT=$2 + # Password is optional (omit when the password is not needed during the test) + local MAIL_PASS="${3:-password_not_relevant_to_test}" + + run docker exec "${CONTAINER_NAME}" setup email add "${MAIL_ACCOUNT}" "${MAIL_PASS}" + assert_success + + wait_until_account_maildir_exists "${CONTAINER_NAME}" "${MAIL_ACCOUNT}" +} + +function wait_for_empty_mail_queue_in_container() { + local CONTAINER_NAME="${1}" + local TIMEOUT=${TEST_TIMEOUT_IN_SECONDS} + + # shellcheck disable=SC2016 + repeat_in_container_until_success_or_timeout "${TIMEOUT}" "${CONTAINER_NAME}" bash -c '[[ $(mailq) == *"Mail queue is empty"* ]]' +} diff --git a/test/helper/setup.bash b/test/helper/setup.bash new file mode 100644 index 00000000..061c07ac --- /dev/null +++ b/test/helper/setup.bash @@ -0,0 +1,133 @@ +#!/bin/bash + +# ------------------------------------------------------------------- + +function __initialize_variables() { + function __check_if_set() { + if [[ ${!1+set} != 'set' ]] + then + echo "ERROR: (helper/setup.sh) '${1:?No variable name given to __check_if_set}' is not set" >&2 + exit 1 + fi + } + + local REQUIRED_VARIABLES_FOR_TESTS=( + 'REPOSITORY_ROOT' + 'IMAGE_NAME' + 'CONTAINER_NAME' + ) + + for VARIABLE in "${REQUIRED_VARIABLES_FOR_TESTS}" + do + __check_if_set "${VARIABLE}" + done + + TEST_TIMEOUT_IN_SECONDS=${TEST_TIMEOUT_IN_SECONDS:-120} + NUMBER_OF_LOG_LINES=${NUMBER_OF_LOG_LINES:-10} + SETUP_FILE_MARKER="${BATS_TMPDIR:?}/$(basename "${BATS_TEST_FILENAME:?}").setup_file" +} + +# ------------------------------------------------------------------- + +# @param ${1} relative source in test/config folder +# @param ${2} (optional) container name, defaults to ${BATS_TEST_FILENAME} +# @return path to the folder where the config is duplicated +function duplicate_config_for_container() { + local OUTPUT_FOLDER + OUTPUT_FOLDER=$(private_config_path "${2}") || return $? + + rm -rf "${OUTPUT_FOLDER:?}/" || return $? # cleanup + mkdir -p "${OUTPUT_FOLDER}" || return $? + cp -r "${PWD}/test/config/${1:?}/." "${OUTPUT_FOLDER}" || return $? + + echo "${OUTPUT_FOLDER}" +} + +# TODO: Should also fail early on "docker logs ${1} | egrep '^[ FATAL ]'"? +# @param ${1} name of the postfix container +function wait_for_finished_setup_in_container() { + local STATUS=0 + repeat_until_success_or_timeout --fatal-test "container_is_running ${1}" "${TEST_TIMEOUT_IN_SECONDS}" sh -c "docker logs ${1} | grep 'is up and running'" || STATUS=1 + + if [[ ${STATUS} -eq 1 ]]; then + echo "Last ${NUMBER_OF_LOG_LINES} lines of container \`${1}\`'s log" + docker logs "${1}" | tail -n "${NUMBER_OF_LOG_LINES}" + fi + + return ${STATUS} +} + +# Common defaults appropriate for most tests, override vars in each test when necessary. +# For all tests override in `setup_file()` via an `export` var. +# For individual test override the var via `local` var instead. +# +# For example, if you need an immutable config volume that can't be affected by other tests +# in the file, then use `local TEST_TMP_CONFIG=$(duplicate_config_for_container . "${UNIQUE_ID_HERE}")` +function init_with_defaults() { + __initialize_variables + + export TEST_TMP_CONFIG + + # In `setup_file()` the default name to use for the currently tested docker container + # is `${CONTAINER_NAME}` global defined here. It derives the name from the test filename: + # `basename` to ignore absolute dir path and file extension, only extract filename. + # In `setup_file()` creates a single copy of the test config folder to use for an entire test file: + TEST_TMP_CONFIG=$(duplicate_config_for_container . "${CONTAINER_NAME}") + + # Common complimentary test files, read-only safe to share across containers: + export TEST_FILES_CONTAINER_PATH='/tmp/docker-mailserver-test' + export TEST_FILES_VOLUME="${REPOSITORY_ROOT}/test/test-files:${TEST_FILES_CONTAINER_PATH}:ro" + + # The config volume cannot be read-only as some data needs to be written at container startup + # - two sed failures (unknown lines) + # - dovecot-quotas.cf (setup-stack.sh:_setup_dovecot_quotas) + # - postfix-aliases.cf (setup-stack.sh:_setup_postfix_aliases) + # TODO: Check how many tests need write access. Consider using `docker create` + `docker cp` for easier cleanup. + export TEST_CONFIG_VOLUME="${TEST_TMP_CONFIG}:/tmp/docker-mailserver" + + # Default Root CA cert used in TLS tests with `openssl` commands: + export TEST_CA_CERT="${TEST_FILES_CONTAINER_PATH}/ssl/example.test/with_ca/ecdsa/ca-cert.ecdsa.pem" +} + +# Using `create` and `start` instead of only `run` allows to modify +# the container prior to starting it. Otherwise use this combined method. +# NOTE: Forwards all args to the create method at present. +function common_container_setup() { + common_container_create "${@}" + common_container_start +} + +# Common docker setup is centralized here. +# +# `X_EXTRA_ARGS` - Optional: Pass an array by it's variable name as a string, it will +# be used as a reference for appending extra config into the `docker create` below: +# +# NOTE: Using array reference for a single input parameter, as this method is still +# under development while adapting tests to it and requirements it must serve (eg: support base config matrix in CI) +function common_container_create() { + [[ -n ${1} ]] && local -n X_EXTRA_ARGS=${1} + + run docker create \ + --tty \ + --name "${CONTAINER_NAME}" \ + --hostname "${TEST_FQDN:-mail.my-domain.com}" \ + --volume "${TEST_FILES_VOLUME}" \ + --volume "${TEST_CONFIG_VOLUME}" \ + --env ENABLE_AMAVIS=0 \ + --env ENABLE_CLAMAV=0 \ + --env ENABLE_UPDATE_CHECK=0 \ + --env ENABLE_SPAMASSASSIN=0 \ + --env ENABLE_FAIL2BAN=0 \ + --env LOG_LEVEL=debug \ + "${X_EXTRA_ARGS[@]}" \ + "${IMAGE_NAME}" + + assert_success +} + +function common_container_start() { + run docker start "${CONTAINER_NAME}" + assert_success + + wait_for_finished_setup_in_container "${CONTAINER_NAME}" +} diff --git a/test/mail_spam_bounced.bats b/test/mail_spam_bounced.bats deleted file mode 100644 index 4848087e..00000000 --- a/test/mail_spam_bounced.bats +++ /dev/null @@ -1,49 +0,0 @@ -load 'test_helper/common' -# Globals referenced from `test_helper/common`: -# TEST_NAME - -# Can run tests in parallel?: No -# Shared static container name: TEST_NAME - -# Test case -# --------- -# When SPAMASSASSIN_SPAM_TO_INBOX=0, spam messages must be bounced (rejected). -# SPAMASSASSIN_SPAM_TO_INBOX=1 is covered in `mail_spam_junk_folder.bats`. -# Original test PR: https://github.com/docker-mailserver/docker-mailserver/pull/1485 - -function teardown() { - docker rm -f "${TEST_NAME}" -} - -function setup_file() { - init_with_defaults -} - -# Not used -# function teardown_file() { -# } - -@test "checking amavis: spam message is bounced (rejected)" { - # shellcheck disable=SC2034 - local TEST_DOCKER_ARGS=( - --env ENABLE_SPAMASSASSIN=1 - --env PERMIT_DOCKER=container - --env SPAMASSASSIN_SPAM_TO_INBOX=0 - ) - - common_container_setup 'TEST_DOCKER_ARGS' - - _should_bounce_spam -} - -function _should_bounce_spam() { - wait_for_smtp_port_in_container_to_respond "${TEST_NAME}" - - # send a spam message - run docker exec "${TEST_NAME}" /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/amavis-spam.txt" - assert_success - - # message will be added to a queue with varying delay until amavis receives it - run repeat_until_success_or_timeout 60 sh -c "docker logs ${TEST_NAME} | grep 'Blocked SPAM {NoBounceInbound,Quarantined}'" - assert_success -} diff --git a/test/test_helper/common.bash b/test/test_helper/common.bash index 46b92317..1ce9d587 100644 --- a/test/test_helper/common.bash +++ b/test/test_helper/common.bash @@ -1,7 +1,7 @@ #!/bin/bash -load 'test_helper/bats-support/load' -load 'test_helper/bats-assert/load' +load "${REPOSITORY_ROOT}/test/test_helper/bats-support/load" +load "${REPOSITORY_ROOT}/test/test_helper/bats-assert/load" NAME=${NAME:-mailserver-testing:ci} diff --git a/test/test_helper/tls.bash b/test/test_helper/tls.bash index 7ced47fb..4987a66f 100644 --- a/test/test_helper/tls.bash +++ b/test/test_helper/tls.bash @@ -1,6 +1,6 @@ #!/bin/bash -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" # Helper methods for testing TLS. # `_should_*` methods are useful for common high-level functionality. diff --git a/test/tests/parallel/set1/clamav.bats b/test/tests/parallel/set1/clamav.bats new file mode 100644 index 00000000..42450926 --- /dev/null +++ b/test/tests/parallel/set1/clamav.bats @@ -0,0 +1,71 @@ +load "${REPOSITORY_ROOT}/test/helper/setup" +load "${REPOSITORY_ROOT}/test/helper/common" + +TEST_NAME_PREFIX='ClamAV:' +CONTAINER_NAME='dms-test-clamav' + +function setup_file() { + init_with_defaults + + # Comment for maintainers about `PERMIT_DOCKER=host`: + # https://github.com/docker-mailserver/docker-mailserver/pull/2815/files#r991087509 + local CUSTOM_SETUP_ARGUMENTS=( + --env ENABLE_CLAMAV=1 + --env ENABLE_AMAVIS=1 + --env PERMIT_DOCKER=host + --env AMAVIS_LOGLEVEL=2 + --env CLAMAV_MESSAGE_SIZE_LIMIT=30M + --env LOG_LEVEL=trace + ) + + common_container_setup 'CUSTOM_SETUP_ARGUMENTS' + + # wait for ClamAV to be fully setup or we will get errors on the log + repeat_in_container_until_success_or_timeout 60 "${CONTAINER_NAME}" test -e /var/run/clamav/clamd.ctl + + wait_for_service "${CONTAINER_NAME}" postfix + wait_for_smtp_port_in_container "${CONTAINER_NAME}" + + _run_in_container bash -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/amavis-virus.txt" + assert_success + + wait_for_empty_mail_queue_in_container "${CONTAINER_NAME}" +} + +function teardown_file() { _default_teardown ; } + +@test "${TEST_NAME_PREFIX} process clamd is running" { + _run_in_container bash -c "ps aux --forest | grep -v grep | grep '/usr/sbin/clamd'" + assert_success +} + +@test "${TEST_NAME_PREFIX} log files exist at /var/log/mail directory" { + _run_in_container bash -c "ls -1 /var/log/mail/ | grep -E 'clamav|freshclam|mail.log' | wc -l" + assert_success + assert_output 3 +} + +@test "${TEST_NAME_PREFIX} should be identified by Amavis" { + _run_in_container grep -i 'Found secondary av scanner ClamAV-clamscan' /var/log/mail/mail.log + assert_success +} + +@test "${TEST_NAME_PREFIX} freshclam cron is enabled" { + _run_in_container bash -c "grep '/usr/bin/freshclam' -r /etc/cron.d" + assert_success +} + +@test "${TEST_NAME_PREFIX} env CLAMAV_MESSAGE_SIZE_LIMIT is set correctly" { + _run_in_container grep -q '^MaxFileSize 30M$' /etc/clamav/clamd.conf + assert_success +} + +@test "${TEST_NAME_PREFIX} rejects virus" { + _run_in_container bash -c "grep 'Blocked INFECTED' /var/log/mail/mail.log | grep ' -> '" + assert_success +} + +@test "${TEST_NAME_PREFIX} process clamd restarts when killed" { + _run_in_container bash -c "pkill clamd && sleep 10 && ps aux --forest | grep -v grep | grep '/usr/sbin/clamd'" + assert_success +} diff --git a/test/tests/parallel/set1/default_relay_host.bats b/test/tests/parallel/set1/default_relay_host.bats new file mode 100644 index 00000000..ead7925e --- /dev/null +++ b/test/tests/parallel/set1/default_relay_host.bats @@ -0,0 +1,23 @@ +load "${REPOSITORY_ROOT}/test/helper/setup" +load "${REPOSITORY_ROOT}/test/helper/common" + +export TEST_NAME_PREFIX='default relay host:' +export CONTAINER_NAME='dms-test-default_relay_host' + +function setup_file() { + init_with_defaults + + local CUSTOM_SETUP_ARGUMENTS=( + --env DEFAULT_RELAY_HOST=default.relay.host.invalid:25 \ + --env PERMIT_DOCKER=host \ + ) + + common_container_setup 'CUSTOM_SETUP_ARGUMENTS' +} + +function teardown_file() { _default_teardown ; } + +@test "${TEST_NAME_PREFIX} default relay host is added to main.cf" { + _run_in_container bash -c 'grep -e "^relayhost =" /etc/postfix/main.cf' + assert_output 'relayhost = default.relay.host.invalid:25' +} diff --git a/test/tests/parallel/set2/spam_bounced.bats b/test/tests/parallel/set2/spam_bounced.bats new file mode 100644 index 00000000..321d2981 --- /dev/null +++ b/test/tests/parallel/set2/spam_bounced.bats @@ -0,0 +1,36 @@ +load "${REPOSITORY_ROOT}/test/helper/setup" +load "${REPOSITORY_ROOT}/test/helper/common" + +TEST_NAME_PREFIX='spam (Amavis):' +CONTAINER_NAME='dms-test-spam_bounced' + +function setup_file() { + init_with_defaults + + local CUSTOM_SETUP_ARGUMENTS=( + --env ENABLE_AMAVIS=1 + --env ENABLE_SPAMASSASSIN=1 + --env PERMIT_DOCKER=container + --env SPAMASSASSIN_SPAM_TO_INBOX=0 + ) + + common_container_setup 'CUSTOM_SETUP_ARGUMENTS' + wait_for_smtp_port_in_container_to_respond "${CONTAINER_NAME}" +} + +function teardown_file() { _default_teardown ; } + +# Test case +# --------- +# When SPAMASSASSIN_SPAM_TO_INBOX=0, spam messages must be bounced (rejected). +# SPAMASSASSIN_SPAM_TO_INBOX=1 is covered in `mail_spam_junk_folder.bats`. +# Original test PR: https://github.com/docker-mailserver/docker-mailserver/pull/1485 +@test "${TEST_NAME_PREFIX} spam message is bounced (rejected)" { + # send a spam message + _run_in_container /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/amavis-spam.txt" + assert_success + + # message will be added to a queue with varying delay until amavis receives it + run repeat_until_success_or_timeout 60 sh -c "docker logs ${CONTAINER_NAME} | grep 'Blocked SPAM {NoBounceInbound,Quarantined}'" + assert_success +} diff --git a/test/tests/parallel/set2/template.bats b/test/tests/parallel/set2/template.bats new file mode 100644 index 00000000..eb19cb58 --- /dev/null +++ b/test/tests/parallel/set2/template.bats @@ -0,0 +1,39 @@ +# ? load the BATS helper +load "${REPOSITORY_ROOT}/test/helper/setup" +load "${REPOSITORY_ROOT}/test/helper/common" + +# ? global variable initialization +# ? to identify the test easily +TEST_NAME_PREFIX='template:' +# ? must be unique +CONTAINER_NAME='dms-test-template' + +# ? test setup + +function setup_file() { + # ? optional setup before container is started + + # ? initialize the test helpers + init_with_defaults + + # ? add custom arguments supplied to `docker run` here + local CUSTOM_SETUP_ARGUMENTS=( + --env LOG_LEVEL=trace + ) + + # ? use a helper to correctly setup the container + common_container_setup 'CUSTOM_SETUP_ARGUMENTS' + + # ? optional setup after the container is started +} + +# ? test finalization + +function teardown_file() { _default_teardown ; } + +# ? actual unit tests + +@test "${TEST_NAME_PREFIX} default check" { + _run_in_container bash -c "true" + assert_success +} diff --git a/test/tests/parallel/set3/dovecot_inet_protocol.bats b/test/tests/parallel/set3/dovecot_inet_protocol.bats new file mode 100644 index 00000000..b6fdf3cb --- /dev/null +++ b/test/tests/parallel/set3/dovecot_inet_protocol.bats @@ -0,0 +1,46 @@ +load "${REPOSITORY_ROOT}/test/helper/setup" +load "${REPOSITORY_ROOT}/test/helper/common" + +TEST_NAME_PREFIX='Dovecot protocols:' + +@test "${TEST_NAME_PREFIX} dual-stack IP configuration" { + local CONTAINER_NAME='dms-test-dovecot_protocols_all' + local CUSTOM_SETUP_ARGUMENTS=(--env DOVECOT_INET_PROTOCOLS=) + + init_with_defaults + common_container_setup 'CUSTOM_SETUP_ARGUMENTS' + + _run_in_container grep '^#listen = \*, ::' /etc/dovecot/dovecot.conf + assert_success + assert_output '#listen = *, ::' + + docker rm -f "${CONTAINER_NAME}" +} + +@test "${TEST_NAME_PREFIX} IPv4 configuration" { + local CONTAINER_NAME='dms-test-dovecot_protocols_ipv4' + local CUSTOM_SETUP_ARGUMENTS=(--env DOVECOT_INET_PROTOCOLS=ipv4) + + init_with_defaults + common_container_setup 'CUSTOM_SETUP_ARGUMENTS' + + _run_in_container grep '^listen = \*$' /etc/dovecot/dovecot.conf + assert_success + assert_output 'listen = *' + + docker rm -f "${CONTAINER_NAME}" +} + +@test "${TEST_NAME_PREFIX} IPv6 configuration" { + local CONTAINER_NAME='dms-test-dovecot_protocols_ipv6' + local CUSTOM_SETUP_ARGUMENTS=(--env DOVECOT_INET_PROTOCOLS=ipv6) + + init_with_defaults + common_container_setup 'CUSTOM_SETUP_ARGUMENTS' + + _run_in_container grep '^listen = \[::\]$' /etc/dovecot/dovecot.conf + assert_success + assert_output 'listen = [::]' + + docker rm -f "${CONTAINER_NAME}" +} diff --git a/test/tests/parallel/set3/helper-functions.bats b/test/tests/parallel/set3/helper-functions.bats new file mode 100644 index 00000000..24ee2f68 --- /dev/null +++ b/test/tests/parallel/set3/helper-functions.bats @@ -0,0 +1,23 @@ +load "${REPOSITORY_ROOT}/test/helper/setup" +load "${REPOSITORY_ROOT}/test/helper/common" + +TEST_NAME_PREFIX='helper functions inside container:' +CONTAINER_NAME='dms-test-helper_functions' + +function setup_file() { + init_with_defaults + common_container_setup +} + +function teardown_file() { _default_teardown ; } + +@test "${TEST_NAME_PREFIX} _sanitize_ipv4_to_subnet_cidr" { + _run_in_container bash -c "source /usr/local/bin/helpers/index.sh; _sanitize_ipv4_to_subnet_cidr 255.255.255.255/0" + assert_output "0.0.0.0/0" + + _run_in_container bash -c "source /usr/local/bin/helpers/index.sh; _sanitize_ipv4_to_subnet_cidr 192.168.255.14/20" + assert_output "192.168.240.0/20" + + _run_in_container bash -c "source /usr/local/bin/helpers/index.sh; _sanitize_ipv4_to_subnet_cidr 192.168.255.14/32" + assert_output "192.168.255.14/32" +} diff --git a/test/mail_changedetector.bats b/test/tests/serial/mail_changedetector.bats similarity index 98% rename from test/mail_changedetector.bats rename to test/tests/serial/mail_changedetector.bats index 3a3be488..2a48bd07 100644 --- a/test/mail_changedetector.bats +++ b/test/tests/serial/mail_changedetector.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" # Note if tests fail asserting against `supervisorctl tail changedetector` output, # use `supervisorctl tail - changedetector` instead to increase log output. diff --git a/test/mail_disabled_clamav_spamassassin.bats b/test/tests/serial/mail_disabled_clamav_spamassassin.bats similarity index 97% rename from test/mail_disabled_clamav_spamassassin.bats rename to test/tests/serial/mail_disabled_clamav_spamassassin.bats index 13d3e73e..83e6a398 100644 --- a/test/mail_disabled_clamav_spamassassin.bats +++ b/test/tests/serial/mail_disabled_clamav_spamassassin.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" setup_file() { local PRIVATE_CONFIG diff --git a/test/mail_dnsbl.bats b/test/tests/serial/mail_dnsbl.bats similarity index 97% rename from test/mail_dnsbl.bats rename to test/tests/serial/mail_dnsbl.bats index 1df1d4e3..d4c3a5d4 100644 --- a/test/mail_dnsbl.bats +++ b/test/tests/serial/mail_dnsbl.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" CONTAINER="mail_dnsbl_enabled" CONTAINER2="mail_dnsbl_disabled" diff --git a/test/mail_fail2ban.bats b/test/tests/serial/mail_fail2ban.bats similarity index 99% rename from test/mail_fail2ban.bats rename to test/tests/serial/mail_fail2ban.bats index 11405d61..a3f927d7 100644 --- a/test/mail_fail2ban.bats +++ b/test/tests/serial/mail_fail2ban.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" function setup_file() { local PRIVATE_CONFIG diff --git a/test/mail_fetchmail.bats b/test/tests/serial/mail_fetchmail.bats similarity index 96% rename from test/mail_fetchmail.bats rename to test/tests/serial/mail_fetchmail.bats index 2eac5534..77c468f2 100644 --- a/test/mail_fetchmail.bats +++ b/test/tests/serial/mail_fetchmail.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" function setup_file() { local PRIVATE_CONFIG diff --git a/test/mail_fetchmail_parallel.bats b/test/tests/serial/mail_fetchmail_parallel.bats similarity index 98% rename from test/mail_fetchmail_parallel.bats rename to test/tests/serial/mail_fetchmail_parallel.bats index 68f98c52..b1792de2 100644 --- a/test/mail_fetchmail_parallel.bats +++ b/test/tests/serial/mail_fetchmail_parallel.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" function setup_file() { local PRIVATE_CONFIG diff --git a/test/mail_hostname.bats b/test/tests/serial/mail_hostname.bats similarity index 99% rename from test/mail_hostname.bats rename to test/tests/serial/mail_hostname.bats index 31df350c..f55a69c3 100644 --- a/test/mail_hostname.bats +++ b/test/tests/serial/mail_hostname.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" function setup_file() { diff --git a/test/mail_lmtp_ip.bats b/test/tests/serial/mail_lmtp_ip.bats similarity index 97% rename from test/mail_lmtp_ip.bats rename to test/tests/serial/mail_lmtp_ip.bats index 91981d12..2ceca916 100644 --- a/test/mail_lmtp_ip.bats +++ b/test/tests/serial/mail_lmtp_ip.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" setup_file() { local PRIVATE_CONFIG PRIVATE_ETC diff --git a/test/mail_pop3.bats b/test/tests/serial/mail_pop3.bats similarity index 97% rename from test/mail_pop3.bats rename to test/tests/serial/mail_pop3.bats index d4873d47..ab10b19d 100644 --- a/test/mail_pop3.bats +++ b/test/tests/serial/mail_pop3.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" function setup_file() { local PRIVATE_CONFIG diff --git a/test/mail_postfix_inet.bats b/test/tests/serial/mail_postfix_inet.bats similarity index 97% rename from test/mail_postfix_inet.bats rename to test/tests/serial/mail_postfix_inet.bats index 24dd463f..22656aa9 100644 --- a/test/mail_postfix_inet.bats +++ b/test/tests/serial/mail_postfix_inet.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" # Test case # --------- diff --git a/test/mail_postscreen.bats b/test/tests/serial/mail_postscreen.bats similarity index 97% rename from test/mail_postscreen.bats rename to test/tests/serial/mail_postscreen.bats index d1e3b6e1..aa2f958a 100644 --- a/test/mail_postscreen.bats +++ b/test/tests/serial/mail_postscreen.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" setup() { # Getting mail container IP diff --git a/test/mail_privacy.bats b/test/tests/serial/mail_privacy.bats similarity index 96% rename from test/mail_privacy.bats rename to test/tests/serial/mail_privacy.bats index e8d560f8..f5195916 100644 --- a/test/mail_privacy.bats +++ b/test/tests/serial/mail_privacy.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" function setup_file() { local PRIVATE_CONFIG diff --git a/test/mail_quotas_disabled.bats b/test/tests/serial/mail_quotas_disabled.bats similarity index 96% rename from test/mail_quotas_disabled.bats rename to test/tests/serial/mail_quotas_disabled.bats index 3afac83b..a012614e 100644 --- a/test/mail_quotas_disabled.bats +++ b/test/tests/serial/mail_quotas_disabled.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" # Test case # --------- diff --git a/test/mail_smtponly.bats b/test/tests/serial/mail_smtponly.bats similarity index 97% rename from test/mail_smtponly.bats rename to test/tests/serial/mail_smtponly.bats index ecdb2625..fe258036 100644 --- a/test/mail_smtponly.bats +++ b/test/tests/serial/mail_smtponly.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" function setup_file() { docker run --rm -d --name mail_smtponly \ diff --git a/test/mail_spam_junk_folder.bats b/test/tests/serial/mail_spam_junk_folder.bats similarity index 98% rename from test/mail_spam_junk_folder.bats rename to test/tests/serial/mail_spam_junk_folder.bats index 8dcf454f..e335d06f 100644 --- a/test/mail_spam_junk_folder.bats +++ b/test/tests/serial/mail_spam_junk_folder.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" # Test case # --------- diff --git a/test/mail_special_use_folders.bats b/test/tests/serial/mail_special_use_folders.bats similarity index 96% rename from test/mail_special_use_folders.bats rename to test/tests/serial/mail_special_use_folders.bats index e0ef03f8..b4bd2ac2 100644 --- a/test/mail_special_use_folders.bats +++ b/test/tests/serial/mail_special_use_folders.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" setup_file() { local PRIVATE_CONFIG diff --git a/test/mail_ssl_letsencrypt.bats b/test/tests/serial/mail_ssl_letsencrypt.bats similarity index 99% rename from test/mail_ssl_letsencrypt.bats rename to test/tests/serial/mail_ssl_letsencrypt.bats index 06053de5..c16b9625 100644 --- a/test/mail_ssl_letsencrypt.bats +++ b/test/tests/serial/mail_ssl_letsencrypt.bats @@ -1,5 +1,5 @@ -load 'test_helper/common' -load 'test_helper/tls' +load "${REPOSITORY_ROOT}/test/test_helper/common" +load "${REPOSITORY_ROOT}/test/test_helper/tls" # Globals referenced from `test_helper/common`: # TEST_NAME TEST_FQDN TEST_TMP_CONFIG diff --git a/test/mail_ssl_manual.bats b/test/tests/serial/mail_ssl_manual.bats similarity index 98% rename from test/mail_ssl_manual.bats rename to test/tests/serial/mail_ssl_manual.bats index 52f07913..d2c34cc5 100644 --- a/test/mail_ssl_manual.bats +++ b/test/tests/serial/mail_ssl_manual.bats @@ -1,5 +1,5 @@ #!/usr/bin/env bats -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" function setup_file() { # Internal copies made by `start-mailserver.sh`: diff --git a/test/mail_time.bats b/test/tests/serial/mail_time.bats similarity index 93% rename from test/mail_time.bats rename to test/tests/serial/mail_time.bats index 9e7f22a9..6bf7eb42 100644 --- a/test/mail_time.bats +++ b/test/tests/serial/mail_time.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" setup_file() { local PRIVATE_CONFIG diff --git a/test/mail_tls_dhparams.bats b/test/tests/serial/mail_tls_dhparams.bats similarity index 98% rename from test/mail_tls_dhparams.bats rename to test/tests/serial/mail_tls_dhparams.bats index 9edd0e16..274b1dd1 100644 --- a/test/mail_tls_dhparams.bats +++ b/test/tests/serial/mail_tls_dhparams.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" # Test case # --------- diff --git a/test/mail_undef_spam_subject.bats b/test/tests/serial/mail_undef_spam_subject.bats similarity index 97% rename from test/mail_undef_spam_subject.bats rename to test/tests/serial/mail_undef_spam_subject.bats index b2831e27..301e0ddc 100644 --- a/test/mail_undef_spam_subject.bats +++ b/test/tests/serial/mail_undef_spam_subject.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" function setup() { local PRIVATE_CONFIG diff --git a/test/mail_with_imap.bats b/test/tests/serial/mail_with_imap.bats similarity index 96% rename from test/mail_with_imap.bats rename to test/tests/serial/mail_with_imap.bats index ffb609b7..1b3d54d5 100644 --- a/test/mail_with_imap.bats +++ b/test/tests/serial/mail_with_imap.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" setup_file() { local PRIVATE_CONFIG diff --git a/test/mail_with_ldap.bats b/test/tests/serial/mail_with_ldap.bats similarity index 99% rename from test/mail_with_ldap.bats rename to test/tests/serial/mail_with_ldap.bats index 882beddc..1f206c24 100644 --- a/test/mail_with_ldap.bats +++ b/test/tests/serial/mail_with_ldap.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" function setup_file() { pushd test/docker-openldap/ || return 1 diff --git a/test/mail_with_mdbox.bats b/test/tests/serial/mail_with_mdbox.bats similarity index 95% rename from test/mail_with_mdbox.bats rename to test/tests/serial/mail_with_mdbox.bats index dd751adb..eaec9ef6 100644 --- a/test/mail_with_mdbox.bats +++ b/test/tests/serial/mail_with_mdbox.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" setup_file() { local PRIVATE_CONFIG diff --git a/test/mail_with_postgrey.bats b/test/tests/serial/mail_with_postgrey.bats similarity index 98% rename from test/mail_with_postgrey.bats rename to test/tests/serial/mail_with_postgrey.bats index 8a5e79d8..9fd4fb12 100644 --- a/test/mail_with_postgrey.bats +++ b/test/tests/serial/mail_with_postgrey.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" function setup_file() { local PRIVATE_CONFIG diff --git a/test/mail_with_postgrey_disabled_by_default.bats b/test/tests/serial/mail_with_postgrey_disabled_by_default.bats similarity index 93% rename from test/mail_with_postgrey_disabled_by_default.bats rename to test/tests/serial/mail_with_postgrey_disabled_by_default.bats index 2ebbdc30..82041ccc 100644 --- a/test/mail_with_postgrey_disabled_by_default.bats +++ b/test/tests/serial/mail_with_postgrey_disabled_by_default.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" function setup() { local PRIVATE_CONFIG diff --git a/test/mail_with_relays.bats b/test/tests/serial/mail_with_relays.bats similarity index 98% rename from test/mail_with_relays.bats rename to test/tests/serial/mail_with_relays.bats index 0122e23e..ae42d812 100644 --- a/test/mail_with_relays.bats +++ b/test/tests/serial/mail_with_relays.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" function setup_file() { # We use a temporary config directory since we'll be dynamically editing diff --git a/test/mail_with_sdbox.bats b/test/tests/serial/mail_with_sdbox.bats similarity index 95% rename from test/mail_with_sdbox.bats rename to test/tests/serial/mail_with_sdbox.bats index 487fb4de..5fd416c2 100644 --- a/test/mail_with_sdbox.bats +++ b/test/tests/serial/mail_with_sdbox.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" setup_file() { local PRIVATE_CONFIG diff --git a/test/no_container.bats b/test/tests/serial/no_container.bats similarity index 99% rename from test/no_container.bats rename to test/tests/serial/no_container.bats index 723a1146..2b31879e 100644 --- a/test/no_container.bats +++ b/test/tests/serial/no_container.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" function setup_file() { # Fail early if the test image is already running: diff --git a/test/open_dkim.bats b/test/tests/serial/open_dkim.bats similarity index 99% rename from test/open_dkim.bats rename to test/tests/serial/open_dkim.bats index 9d1e68e7..535a2092 100644 --- a/test/open_dkim.bats +++ b/test/tests/serial/open_dkim.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" export IMAGE_NAME CONTAINER_NAME TEST_FILE diff --git a/test/permit_docker.bats b/test/tests/serial/permit_docker.bats similarity index 98% rename from test/permit_docker.bats rename to test/tests/serial/permit_docker.bats index c4cff178..fb18d072 100644 --- a/test/permit_docker.bats +++ b/test/tests/serial/permit_docker.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" NON_DEFAULT_DOCKER_MAIL_NETWORK_NAME=non-default-docker-mail-network setup_file() { diff --git a/test/security_tls_cipherlists.bats b/test/tests/serial/security_tls_cipherlists.bats similarity index 99% rename from test/security_tls_cipherlists.bats rename to test/tests/serial/security_tls_cipherlists.bats index a60e1f14..c13cf7bb 100644 --- a/test/security_tls_cipherlists.bats +++ b/test/tests/serial/security_tls_cipherlists.bats @@ -1,5 +1,5 @@ #!/usr/bin/env bats -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" # Globals ${BATS_TMPDIR} and ${NAME} # `${NAME}` defaults to `mailserver-testing:ci` diff --git a/test/sedfile.bats b/test/tests/serial/sedfile.bats similarity index 98% rename from test/sedfile.bats rename to test/tests/serial/sedfile.bats index c19c2fc1..5b8c593f 100644 --- a/test/sedfile.bats +++ b/test/tests/serial/sedfile.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" CONTAINER='sedfile' TEST_FILE='/tmp/sedfile-test.txt' diff --git a/test/setup-cli.bats b/test/tests/serial/setup-cli.bats similarity index 99% rename from test/setup-cli.bats rename to test/tests/serial/setup-cli.bats index 4805b3c9..083a3754 100644 --- a/test/setup-cli.bats +++ b/test/tests/serial/setup-cli.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" # Globals referenced from `test_helper/common`: # TEST_NAME (should match the filename, minus the bats extension) diff --git a/test/test_helper.bats b/test/tests/serial/test_helper.bats similarity index 81% rename from test/test_helper.bats rename to test/tests/serial/test_helper.bats index fa6dd12c..252f6102 100644 --- a/test/test_helper.bats +++ b/test/tests/serial/test_helper.bats @@ -1,14 +1,14 @@ -load 'test_helper/bats-support/load' -load 'test_helper/bats-assert/load' -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" -@test "repeat_until_success_or_timeout returns instantly on success" { +TEST_NAME_PREFIX='test helper functions:' + +@test "${TEST_NAME_PREFIX} repeat_until_success_or_timeout returns instantly on success" { SECONDS=0 repeat_until_success_or_timeout 1 true [[ ${SECONDS} -le 1 ]] } -@test "repeat_until_success_or_timeout waits for timeout on persistent failure" { +@test "${TEST_NAME_PREFIX} repeat_until_success_or_timeout waits for timeout on persistent failure" { SECONDS=0 run repeat_until_success_or_timeout 2 false [[ ${SECONDS} -ge 2 ]] @@ -16,7 +16,7 @@ load 'test_helper/common' assert_output --partial "Timed out on command" } -@test "repeat_until_success_or_timeout aborts immediately on fatal failure" { +@test "${TEST_NAME_PREFIX} repeat_until_success_or_timeout aborts immediately on fatal failure" { SECONDS=0 run repeat_until_success_or_timeout --fatal-test false 2 false [[ ${SECONDS} -le 1 ]] @@ -24,7 +24,7 @@ load 'test_helper/common' assert_output --partial "early aborting" } -@test "repeat_until_success_or_timeout expects integer timeout" { +@test "${TEST_NAME_PREFIX} repeat_until_success_or_timeout expects integer timeout" { run repeat_until_success_or_timeout 1 true assert_success @@ -35,27 +35,27 @@ load 'test_helper/common' assert_failure } -@test "run_until_success_or_timeout returns instantly on success" { +@test "${TEST_NAME_PREFIX} run_until_success_or_timeout returns instantly on success" { SECONDS=0 run_until_success_or_timeout 2 true [[ ${SECONDS} -le 1 ]] assert_success } -@test "run_until_success_or_timeout waits for timeout on persistent failure" { +@test "${TEST_NAME_PREFIX} run_until_success_or_timeout waits for timeout on persistent failure" { SECONDS=0 ! run_until_success_or_timeout 2 false [[ ${SECONDS} -ge 2 ]] assert_failure } -@test "repeat_in_container_until_success_or_timeout fails immediately for non-running container" { +@test "${TEST_NAME_PREFIX} repeat_in_container_until_success_or_timeout fails immediately for non-running container" { SECONDS=0 ! repeat_in_container_until_success_or_timeout 10 name-of-non-existing-container true [[ ${SECONDS} -le 1 ]] } -@test "repeat_in_container_until_success_or_timeout run command in container" { +@test "${TEST_NAME_PREFIX} repeat_in_container_until_success_or_timeout run command in container" { local CONTAINER_NAME CONTAINER_NAME=$(docker run --rm -d alpine sleep 100) SECONDS=0 @@ -65,7 +65,7 @@ load 'test_helper/common' assert_output "${CONTAINER_NAME}" } -@test "container_is_running" { +@test "${TEST_NAME_PREFIX} container_is_running" { local CONTAINER_NAME CONTAINER_NAME=$(docker run --rm -d alpine sleep 100) container_is_running "${CONTAINER_NAME}" @@ -73,7 +73,7 @@ load 'test_helper/common' ! container_is_running "${CONTAINER_NAME}" } -@test "wait_for_smtp_port_in_container aborts wait after timeout" { +@test "${TEST_NAME_PREFIX} wait_for_smtp_port_in_container aborts wait after timeout" { local CONTAINER_NAME CONTAINER_NAME=$(docker run --rm -d alpine sleep 100) SECONDS=0 @@ -84,7 +84,7 @@ load 'test_helper/common' } # NOTE: Test requires external network access available -@test "wait_for_smtp_port_in_container returns immediately when port found" { +@test "${TEST_NAME_PREFIX} wait_for_smtp_port_in_container returns immediately when port found" { local CONTAINER_NAME CONTAINER_NAME=$(docker run --rm -d alpine sh -c "sleep 10") @@ -97,7 +97,7 @@ load 'test_helper/common' assert_success } -@test "wait_for_finished_setup_in_container" { +@test "${TEST_NAME_PREFIX} wait_for_finished_setup_in_container" { # variable not local to make visible to teardown local PRIVATE_CONFIG PRIVATE_CONFIG=$(duplicate_config_for_container .) @@ -119,7 +119,7 @@ load 'test_helper/common' [[ ${SECONDS} -gt 0 ]] } -@test "duplicate_config_for_container" { +@test "${TEST_NAME_PREFIX} duplicate_config_for_container" { local path path=$(duplicate_config_for_container duplicate_config_test) @@ -130,7 +130,7 @@ load 'test_helper/common' assert_failure } -@test "container_has_service_running/wait_for_service" { +@test "${TEST_NAME_PREFIX} container_has_service_running/wait_for_service" { local PRIVATE_CONFIG PRIVATE_CONFIG=$(duplicate_config_for_container .) @@ -158,7 +158,7 @@ load 'test_helper/common' assert_failure } -@test "wait_for_changes_to_be_detected_in_container fails when timeout is reached" { +@test "${TEST_NAME_PREFIX} wait_for_changes_to_be_detected_in_container fails when timeout is reached" { local PRIVATE_CONFIG PRIVATE_CONFIG=$(duplicate_config_for_container .) @@ -184,7 +184,7 @@ load 'test_helper/common' ! TEST_TIMEOUT_IN_SECONDS=0 wait_for_changes_to_be_detected_in_container "${CONTAINER_NAME}" } -@test "wait_for_changes_to_be_detected_in_container succeeds within timeout" { +@test "${TEST_NAME_PREFIX} wait_for_changes_to_be_detected_in_container succeeds within timeout" { local PRIVATE_CONFIG PRIVATE_CONFIG=$(duplicate_config_for_container .) @@ -210,7 +210,7 @@ load 'test_helper/common' } # TODO investigate why this test fails -@test "wait_for_empty_mail_queue_in_container fails when timeout reached" { +@test "${TEST_NAME_PREFIX} wait_for_empty_mail_queue_in_container fails when timeout reached" { skip 'disabled as it fails randomly: https://github.com/docker-mailserver/docker-mailserver/pull/2177' local PRIVATE_CONFIG @@ -242,7 +242,7 @@ load 'test_helper/common' } # TODO investigate why this test fails -@test "wait_for_empty_mail_queue_in_container succeeds within timeout" { +@test "${TEST_NAME_PREFIX} wait_for_empty_mail_queue_in_container succeeds within timeout" { skip 'disabled as it fails randomly: https://github.com/docker-mailserver/docker-mailserver/pull/2177' local PRIVATE_CONFIG diff --git a/test/tests.bats b/test/tests/serial/tests.bats similarity index 99% rename from test/tests.bats rename to test/tests/serial/tests.bats index d085c96c..71c70d47 100644 --- a/test/tests.bats +++ b/test/tests/serial/tests.bats @@ -1,4 +1,4 @@ -load 'test_helper/common' +load "${REPOSITORY_ROOT}/test/test_helper/common" setup_file() { local PRIVATE_CONFIG