2022-10-17 08:40:09 +00:00
#!/bin/bash
2020-10-19 13:23:42 +00:00
2022-11-25 22:37:58 +00:00
load " ${ REPOSITORY_ROOT } /test/test_helper/bats-support/load "
load " ${ REPOSITORY_ROOT } /test/test_helper/bats-assert/load "
2019-08-08 19:33:55 +00:00
2021-01-18 19:51:56 +00:00
NAME = ${ NAME :- mailserver -testing : ci }
2019-08-08 19:33:55 +00:00
2019-09-05 19:35:23 +00:00
# default timeout is 120 seconds
2022-10-14 07:48:28 +00:00
TEST_TIMEOUT_IN_SECONDS = ${ TEST_TIMEOUT_IN_SECONDS :- 120 }
NUMBER_OF_LOG_LINES = ${ NUMBER_OF_LOG_LINES :- 10 }
2019-08-08 19:33:55 +00:00
2023-01-12 21:10:58 +00:00
function _reload_postfix( ) {
local CONTAINER_NAME = $1
# Reloading Postfix config after modifying it in <2 sec will cause Postfix to delay, workaround that:
docker exec " ${ CONTAINER_NAME } " touch -d '2 seconds ago' /etc/postfix/main.cf
docker exec " ${ CONTAINER_NAME } " postfix reload
}
2020-10-18 00:08:11 +00:00
# @param ${1} timeout
2020-09-17 21:40:39 +00:00
# @param --fatal-test <command eval string> additional test whose failure aborts immediately
# @param ... test to run
2019-08-08 19:33:55 +00:00
function repeat_until_success_or_timeout {
2022-05-30 00:53:30 +00:00
local FATAL_FAILURE_TEST_COMMAND
if [ [ " ${ 1 } " = = "--fatal-test" ] ] ; then
FATAL_FAILURE_TEST_COMMAND = " ${ 2 } "
shift 2
fi
if ! [ [ " ${ 1 } " = ~ ^[ 0-9] +$ ] ] ; then
2022-06-28 11:36:57 +00:00
echo " First parameter for timeout must be an integer, received \" ${ 1 } \" "
2022-05-30 00:53:30 +00:00
return 1
fi
local TIMEOUT = ${ 1 }
local STARTTIME = ${ SECONDS }
shift 1
2023-05-26 05:42:03 +00:00
until " ${ @ } " ; do
2022-05-30 00:53:30 +00:00
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
2020-09-18 00:21:24 +00:00
fi
2022-05-30 00:53:30 +00:00
sleep 1
if [ [ $(( SECONDS - STARTTIME )) -gt ${ TIMEOUT } ] ] ; then
echo " Timed out on command: ${ * } " >& 2
return 1
2019-08-08 19:33:55 +00:00
fi
2022-05-30 00:53:30 +00:00
done
2019-08-08 19:33:55 +00:00
}
2020-09-19 23:10:05 +00:00
# like repeat_until_success_or_timeout but with wrapping the command to run into `run` for later bats consumption
2020-10-18 00:08:11 +00:00
# @param ${1} timeout
2020-09-19 23:10:05 +00:00
# @param ... test command to run
function run_until_success_or_timeout {
2022-05-30 00:53:30 +00:00
if ! [ [ ${ 1 } = ~ ^[ 0-9] +$ ] ] ; then
2022-06-28 11:36:57 +00:00
echo " First parameter for timeout must be an integer, received \" ${ 1 } \" "
2022-05-30 00:53:30 +00:00
return 1
fi
local TIMEOUT = ${ 1 }
local STARTTIME = ${ SECONDS }
shift 1
2023-05-26 05:42:03 +00:00
until run " ${ @ } " && [ [ $status -eq 0 ] ] ; do
2022-05-30 00:53:30 +00:00
sleep 1
if ( ( SECONDS - STARTTIME > TIMEOUT ) ) ; then
echo " Timed out on command: ${ * } " >& 2
return 1
2020-09-19 23:10:05 +00:00
fi
2022-05-30 00:53:30 +00:00
done
2020-09-19 23:10:05 +00:00
}
2020-10-18 00:08:11 +00:00
# @param ${1} timeout
# @param ${2} container name
2020-09-17 22:02:38 +00:00
# @param ... test command for container
function repeat_in_container_until_success_or_timeout( ) {
2022-05-30 00:53:30 +00:00
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 } " " ${ @ } "
2020-09-17 22:02:38 +00:00
}
2020-09-17 21:40:39 +00:00
function container_is_running( ) {
2022-05-30 00:53:30 +00:00
[ [ " $( docker inspect -f '{{.State.Running}}' " ${ 1 } " ) " = = "true" ] ]
2020-09-17 21:40:39 +00:00
}
2020-10-18 00:08:11 +00:00
# @param ${1} port
# @param ${2} container name
2019-10-08 21:11:27 +00:00
function wait_for_tcp_port_in_container( ) {
2022-05-30 00:53:30 +00:00
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 } "
2019-10-08 21:11:27 +00:00
}
2020-10-18 00:08:11 +00:00
# @param ${1} name of the postfix container
2019-08-08 19:33:55 +00:00
function wait_for_smtp_port_in_container( ) {
2022-05-30 00:53:30 +00:00
wait_for_tcp_port_in_container 25 " ${ 1 } "
2019-10-08 21:11:27 +00:00
}
2021-09-06 10:13:12 +00:00
# @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
2023-05-24 07:06:59 +00:00
if [ [ $COUNT -eq 20 ] ] ; then
2021-09-06 10:13:12 +00:00
echo "Unable to receive a valid response from 'nc localhost 25' within 20 seconds"
return 1
fi
2022-05-30 00:53:30 +00:00
2021-09-06 10:13:12 +00:00
sleep 1
( ( COUNT += 1) )
done
}
2020-10-18 00:08:11 +00:00
# @param ${1} name of the postfix container
2019-10-08 21:11:27 +00:00
function wait_for_amavis_port_in_container( ) {
2022-05-30 00:53:30 +00:00
wait_for_tcp_port_in_container 10024 " ${ 1 } "
2019-08-08 19:33:55 +00:00
}
2021-09-20 07:35:03 +00:00
# TODO: Should also fail early on "docker logs ${1} | egrep '^[ FATAL ]'"?
2020-10-18 00:08:11 +00:00
# @param ${1} name of the postfix container
2019-08-08 19:33:55 +00:00
function wait_for_finished_setup_in_container( ) {
2022-05-30 00:53:30 +00:00
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 }
2019-08-08 23:34:04 +00:00
}
2020-10-18 00:08:11 +00:00
SETUP_FILE_MARKER = " ${ BATS_TMPDIR } / $( basename " ${ BATS_TEST_FILENAME } " ) .setup_file "
2019-09-10 23:01:51 +00:00
2020-09-17 23:37:42 +00:00
# get the private config path for the given container or test file, if no container name was given
function private_config_path( ) {
2022-05-30 00:53:30 +00:00
echo " ${ PWD } /test/duplicate_configs/ ${ 1 :- $( basename " ${ BATS_TEST_FILENAME } " ) } "
2020-09-17 23:37:42 +00:00
}
2020-10-18 00:08:11 +00:00
# @param ${1} relative source in test/config folder
# @param ${2} (optional) container name, defaults to ${BATS_TEST_FILENAME}
2020-09-17 23:37:42 +00:00
# @return path to the folder where the config is duplicated
function duplicate_config_for_container( ) {
2022-05-30 00:53:30 +00:00
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 } "
2020-10-01 12:57:05 +00:00
}
function container_has_service_running( ) {
2022-05-30 00:53:30 +00:00
local CONTAINER_NAME = " ${ 1 } "
local SERVICE_NAME = " ${ 2 } "
docker exec " ${ CONTAINER_NAME } " /usr/bin/supervisorctl status " ${ SERVICE_NAME } " | grep RUNNING >/dev/null
2020-10-01 12:57:05 +00:00
}
function wait_for_service( ) {
2022-05-30 00:53:30 +00:00
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 } "
2020-10-01 12:57:05 +00:00
}
chore: `addmailuser` - Remove delaying completion until `/var/mail` is ready (#2729)
## Quick Summary
Resolves a `TODO` task with `addmailuser`.
## Overview
The main change is adding three new methods in `common.bash`, which replace the completion delay in `addmailuser` / `setup email add` command.
Other than that:
- I swapped `sh -c 'addmailuser ...'` to `setup email add ...`.
- Improved three tests in `setup-cli.bats` for `setup email add|update|del` (_logic remains effectively the same still_).
- Rewrote the `TODO` comment for `setup-cli.bats` test on `setup email del` to better clarify the concern, but the test itself was no longer affected due to changes prior to this PR, so I enabled the commented out assertion.
- Removed unnecessary waits. The two `skip` tests in `test/tests.bats` could be enabled again after this PR.
- Additional fixes to tests were made during the PR (see discussion comments for details), resolving race conditions.
Individual commit messages of the PR provide additional details if helpful.
---
## Relevant commit messages
* chore: Remove creation delay in `addmailuser`
This was apparently only for supporting tests that need to wait on account creation being ready to test against.
As per the removed inline docs, it should be fine to remove once tests are updated to work correctly without it.
* tests(feat): Add two new common helper methods
`wait_until_account_maildir_exists()` provides the same logic `addmailuser` command was carrying, to wait upon the account dir creation in `/var/mail`.
As this was specifically to support tests, it makes more sense as a test method.
`add_mail_account_then_wait_until_ready()` was added to handle the common pattern of creating account and waiting on it. An internal assert will ensure the account was successfully created first during the test before attempting to wait.
* tests(feat): Add common helper for waiting on change event to be processed
The current helper is more complicated for no real benefit, it only detects when a change is made that would trigger a change event in the `changedetector` service. Our usage of this in tests however is only interested in waiting out the completion of the change event.
Remove unnecessary change event waits. These waits should not be necessary if handled correctly.
* tests: `addmailuser` to `add_mail_account_then_wait_until_ready mail()`
This helper method is used where appropriate.
- A password is not relevant (optional).
- We need to wait on the creation on the account (Dovecot and `/var/mail` directory).
* tests: `setup-cli` revise `add`, `update`, `del` tests
The delete test was failing as the `/var/mail` directory did not yet exist.
There is now a proper delay imposed in the `add` test now shares the same account for both `update` and `del` tests resolving that failure.
Additionally tests use better asserts where appropriate and the wait + sleep logic in `add` has been improved (now takes 10 seconds to complete, approx half the time than before).
The `del` test TODO while not technically addressed is no longer relevant due to the tests being switched to `-c` option (there is a separate `no container` test file, but it doesn't provide a `del` test).
* tests(fix): Ensure Postfix is reachable after waiting on ClamAV
There is not much reason to check before waiting on ClamAV.
It is more helpful to debug failures from `nc` mail send commands if we know that nothing went wrong inbetween the ClamAV wait time.
Additionally added an assertion which should provide more information if this part of the test setup fails again.
* tests(fix): Move health check to the top
This test is a bit fragile. It relies on defaults for the healthcheck with intervals of 30 seconds.
If the check occurs while Postfix is down due a change event from earlier tests and the healthcheck kicks in at that point, then if there is not enough time to refresh the health status from `unhealthy`, the test will fail with a false-positive as Postfix is actually working and up again..
* tests(fix): Wait on directory to be removed
Workaround that tries not to introduce heavier delays by waiting on a full change event to complete in the previous `email update` if possible.
There is a chance that the account has the folder deleted, but restored from an active change event (for password update, then the account delete).
2022-08-22 22:15:06 +00:00
# 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 } "
}
2020-10-19 21:33:54 +00:00
function wait_for_empty_mail_queue_in_container( ) {
2022-05-30 00:53:30 +00:00
local CONTAINER_NAME = " ${ 1 } "
local TIMEOUT = ${ TEST_TIMEOUT_IN_SECONDS }
2021-01-16 09:16:05 +00:00
2022-05-30 00:53:30 +00:00
# shellcheck disable=SC2016
repeat_in_container_until_success_or_timeout " ${ TIMEOUT } " " ${ CONTAINER_NAME } " bash -c '[[ $(mailq) == *"Mail queue is empty"* ]]'
2020-10-19 21:33:54 +00:00
}