docker-mailserver/test/helper/setup.bash

208 lines
7 KiB
Bash
Raw Normal View History

#!/bin/bash
# ? ABOUT: Functions defined here should be used when initializing tests.
# ! ATTENTION: Functions prefixed with `__` are intended for internal use within this file only, not in tests.
# ! ATTENTION: This script must not use functions from `common.bash` to
# ! avoid dependency hell.
# ! -------------------------------------------------------------------
# ? >> Miscellaneous initialization functionality
# Does pre-flight checks for each test: check whether certain required variables
# are set and exports other variables.
#
# ## Note
#
# This function is internal and should not be used in tests.
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
export SETUP_FILE_MARKER TEST_TIMEOUT_IN_SECONDS NUMBER_OF_LOG_LINES
SETUP_FILE_MARKER="${BATS_TMPDIR:?}/$(basename "${BATS_TEST_FILENAME:?}").setup_file"
TEST_TIMEOUT_IN_SECONDS=${TEST_TIMEOUT_IN_SECONDS:-120}
NUMBER_OF_LOG_LINES=${NUMBER_OF_LOG_LINES:-10}
}
# ? << Miscellaneous initialization functionality
# ! -------------------------------------------------------------------
# ? >> File setup
# Print the private config path for the given container or test file,
# if no container name was given.
#
# @param ${1} = container name [OPTIONAL]
function _print_private_config_path() {
local TARGET_NAME=${1:-$(basename "${BATS_TEST_FILENAME}")}
echo "${REPOSITORY_ROOT}/test/duplicate_configs/${TARGET_NAME}"
}
# Create a dedicated configuration directory for a test 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=$(_print_private_config_path "${2}")
if [[ -z ${OUTPUT_FOLDER} ]]
then
echo "'OUTPUT_FOLDER' in '_duplicate_config_for_container' is empty" >&2
return 1
fi
rm -rf "${OUTPUT_FOLDER:?}/"
mkdir -p "${OUTPUT_FOLDER}"
cp -r "${REPOSITORY_ROOT}/test/config/${1:?}/." "${OUTPUT_FOLDER}" || return $?
echo "${OUTPUT_FOLDER}"
}
# Common defaults appropriate for most tests.
#
# Override variables in test cases within a file when necessary:
# - Use `export <VARIABLE>` in `setup_file()` to overrides for all test cases.
# - Use `local <VARIABLE>` to override within a specific test case.
#
# ## Attenton
#
# The ENV `CONTAINER_NAME` must be set before this method is called. It only affects the
# `TEST_TMP_CONFIG` directory created, but will be used in `common_container_create()`
# and implicitly in other helper methods.
tests(refactor): Adjust `mail_changedetector` + change detection helpers (#2997) * tests(refactor): `mail_changedetector.bats` - Leverage DRY methods `supervisorctl tail` is not the most reliably way to get logs for the latest change detection and has been known to be fragile in the past. We can instead read the full log for the service directly with `tac` and `sed` to extract all log content since the last change detection. Common asserts have also been extracted out into separate methods. * tests(chore): Remove sleep and redundant change event Container 1 is still blocked at this point from an existing lock and change event. Make the lock stale immediately and no extra sleep is required when paired with the helper method to wait until the event is processed (which should remove the stale lock). * tests(refactor): Add more DRY methods - Simplify the test case so it's easier to grok. - 2nd test case (blocking) extracts out initial setup into a separate method and merges the later service restart logic which is redundant. - Additional comments for improved context of what is going on / expected. * tests(chore): Revise the change detection helper method - Add explicit counting arg to change detection support. - Extract revised logic into it's own generic helper method. - Utilize this for a separate method that monitors for a change event having started, but not waiting for completion. This allows dropping the 40 sec of remaining `sleep` in `mail_changedetector` test. It was also required due to potentially missing the timing of a change event completing concurrently in a 2nd container that needed to be waited on and then checked. * tests(chore): Migrate to current test conventions - Switch to common container setup helpers - Update container name and change usage to variables instead. - Adopt the new convention of prefix variable for test cases (revised test case descriptions). * tests(chore): Remove legacy change detection This has since been replaced with the new helper watches the `changedetector` service logs directly instead of only detecting a change has occurred via checksum comparison. No tests use this method anymore, it was originally for `tests.bats`. Thus the tests in `test_helper.bats` are being dropped too. The new helper has test coverage in `changedetector` tests. * chore: Lock removal should not incur `sleep 5` afterwards - A new lock should be created by this script after removal. The sleep doesn't help avoid a race condition with lock file creation after removal. - Reduces test time as a bonus. - Added some additional comments to test. * tests(chore): `tls_letsencrypt.bats` leverage improved change detection - No need to wait on the change detection service anymore during container startup. - No need to count change events processed either as waiting a fixed duration is no longer relied on. - This makes the reload count method redundant, dropped. * tests(chore): Convert `setup-cli.bats` to new test conventions This test file was already adapted to the original common setup helpers. - `TEST_NAME` replaced with `CONTAINER_NAME`. - Prefix var added, test case descriptions drop explicit prefix. - No other changes. * tests(chore): Extract out helpers related to change-detection - New helper file for sharing these helpers to tests. - Includes the helpful log method from changedetector tests. - No longer need to maintain duplicate copies of these methods during the test migration. All tests that use them are now importing the separate helper file. - `tls_letsencrypt.bats` has switched to using the log helper. - Generic log count helper is removed from `test_helper/common.bash` as any test that needs it in future can adapt to `helper/common.bash`. * tests(refactor): `tls_letsencrypt.bats` remove `_get_service_logs()` This helper does not seem useful as moving away from `supervisorctl tail` and no other tests had a need for it. * tests(chore): Remove common setup methods from `test_helper/common.bash` No other tests depend on this. Future tests will adopt the revised versions from `helper/setup.bash`. Additionally updates `helper/setup.bash` comments that are no longer applicable to `TEST_TMP_CONFIG` and `CONTAINER_NAME`. * chore: Use `|| true` to simplify setting `EXPECTED_COUNT` correctly
2023-01-16 07:39:46 +00:00
#
# ## Example
#
# 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
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"
}
# ? << File setup
# ! -------------------------------------------------------------------
# ? >> Container startup
# Waits until the container has finished starting up.
#
# @param ${1} = container name
#
# TODO: Should also fail early on "docker logs ${1} | egrep '^[ FATAL ]'"?
function _wait_for_finished_setup_in_container() {
local TARGET_CONTAINER_NAME=${1:?Container name must be provided}
local STATUS=0
_repeat_until_success_or_timeout \
--fatal-test "_container_is_running ${1}" \
"${TEST_TIMEOUT_IN_SECONDS}" \
bash -c "docker logs ${TARGET_CONTAINER_NAME} | grep 'is up and running'" || STATUS=1
if [[ ${STATUS} -eq 1 ]]; then
echo "Last ${NUMBER_OF_LOG_LINES} lines of container (${TARGET_CONTAINER_NAME}) log"
docker logs "${1}" | tail -n "${NUMBER_OF_LOG_LINES}"
fi
return "${STATUS}"
}
# Uses `docker create` to create a container with proper defaults without starting it instantly.
#
# @param ${1} = 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 [OPTIONAL]
#
# ## 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.example.test}" \
--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 POSTFIX_INET_PROTOCOLS=ipv4 \
--env DOVECOT_INET_PROTOCOLS=ipv4 \
--env LOG_LEVEL=debug \
"${X_EXTRA_ARGS[@]}" \
"${IMAGE_NAME}"
assert_success
}
# Starts a container given by it's name.
# Uses `CONTAINER_NAME` as the name for the `docker start` command.
#
# ## Attenton
#
# The ENV `CONTAINER_NAME` must be set before this method is called.
function _common_container_start() {
run docker start "${CONTAINER_NAME:?Container name must be set}"
assert_success
_wait_for_finished_setup_in_container "${CONTAINER_NAME}"
}
# Using `create` and `start` instead of only `run` allows to modify
# the container prior to starting it. Otherwise use this combined method.
#
# ## Note
#
# This function forwards all arguments to `_common_container_create` at present.
function _common_container_setup() {
_common_container_create "${@}"
_common_container_start
}
# Can be used in BATS' `teardown_file` function as a default value.
#
# @param ${1} = container name [OPTIONAL]
function _default_teardown() {
local TARGET_CONTAINER_NAME=${1:-${CONTAINER_NAME}}
docker rm -f "${TARGET_CONTAINER_NAME}"
}