2024-01-02 20:27:45 +03:00
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
# shellcheck disable=SC2016
set -eux
set -o pipefail
# shellcheck source=test/units/test-control.sh
. " $( dirname " $0 " ) " /test-control.sh
# shellcheck source=test/units/util.sh
. " $( dirname " $0 " ) " /util.sh
setup_base_unit( ) {
local unit_path = " ${ 1 : ? } "
local log_file = " ${ 2 : ? } "
local unit_name = " ${ unit_path ##*/ } "
cat >" $unit_path " <<EOF
[ Service]
Type = oneshot
ExecStart = sleep 3
ExecStart = bash -c " echo foo >> $log_file "
EOF
systemctl daemon-reload
systemctl --job-mode= replace --no-block start " $unit_name "
# Wait until the unit leaves the "inactive" state
timeout 5s bash -xec " while [[ \"\$(systemctl show -P ActiveState $unit_name )\" == inactive ]]; do sleep .1; done "
# Sleep for 1 second from the unit start to get well "into" the first (or second) ExecStart= directive
sleep 1
}
check_output( ) {
local unit_name = " ${ 1 : ? } "
local log_file = " ${ 2 : ? } "
local expected = " ${ 3 ? } "
local unit_name = " ${ unit_path ##*/ } "
# Wait until the unit becomes inactive before checking the log
timeout 10s bash -xec " while [[ \"\$(systemctl show -P ActiveState $unit_name )\" != inactive ]]; do sleep .5; done "
diff " $log_file " <( echo -ne " $expected " )
}
testcase_no_change( ) {
local unit_path log_file
unit_path = " $( mktemp /run/systemd/system/test-deserialization-no-change-XXX.service) "
log_file = " $( mktemp) "
setup_base_unit " $unit_path " " $log_file "
# Simple sanity test without any reordering shenanignans, to check if the base unit works as expected.
check_output " $unit_path " " $log_file " "foo\n"
rm -f " $unit_path " " $log_file "
}
testcase_swapped( ) {
local unit_path log_file
unit_path = " $( mktemp /run/systemd/system/test-deserialization-swapped-XXX.service) "
log_file = " $( mktemp) "
setup_base_unit " $unit_path " " $log_file "
# Swap the two ExecStart= lines.
#
# Since we should be in the first "sleep" of the base unit, after replacing the unit with the following
# one we should continue running from the respective "ExecStart=sleep 3" line, which is now the last
# one, resulting no output in the final log file.
cat >" $unit_path " <<EOF
[ Service]
Type = oneshot
ExecStart = bash -c " echo foo >> $log_file "
ExecStart = sleep 3
EOF
systemctl daemon-reload
check_output " $unit_path " " $log_file " ""
rm -f " $unit_path " " $log_file "
}
testcase_added_before( ) {
local unit_path log_file
unit_path = " $( mktemp /run/systemd/system/test-deserialization-added-before-XXX.service) "
log_file = " $( mktemp) "
setup_base_unit " $unit_path " " $log_file "
# Add one new ExecStart= before the existing ones.
#
# Since, after reload, we should continue running from the "sleep 3" statement, the newly added "echo
2024-01-05 13:07:37 +03:00
# bar" one will have no effect and we should end up with the same output as in the previous case.
2024-01-02 20:27:45 +03:00
cat >" $unit_path " <<EOF
[ Service]
Type = oneshot
ExecStart = bash -c " echo bar >> $log_file "
ExecStart = sleep 3
ExecStart = bash -c " echo foo >> $log_file "
EOF
systemctl daemon-reload
check_output " $unit_path " " $log_file " "foo\n"
rm -f " $unit_path " " $log_file "
}
testcase_added_after( ) {
local unit_path log_file
unit_path = " $( mktemp /run/systemd/system/test-deserialization-added-after-XXX.service) "
log_file = " $( mktemp) "
setup_base_unit " $unit_path " " $log_file "
# Add an ExecStart= line after the existing ones.
#
# Same case as above, except the newly added ExecStart= should get executed, as it was added after the
# "sleep 3" statement.
cat >" $unit_path " <<EOF
[ Service]
Type = oneshot
ExecStart = sleep 3
ExecStart = bash -c " echo foo >> $log_file "
ExecStart = bash -c " echo bar >> $log_file "
EOF
systemctl daemon-reload
check_output " $unit_path " " $log_file " "foo\nbar\n"
rm -f " $unit_path " " $log_file "
}
testcase_interleaved( ) {
local unit_path log_file
unit_path = " $( mktemp /run/systemd/system/test-deserialization-interleaved-XXX.service) "
log_file = " $( mktemp) "
setup_base_unit " $unit_path " " $log_file "
# Combination of the two previous cases.
cat >" $unit_path " <<EOF
[ Service]
Type = oneshot
ExecStart = bash -c " echo baz >> $log_file "
ExecStart = sleep 3
ExecStart = bash -c " echo foo >> $log_file "
ExecStart = bash -c " echo bar >> $log_file "
EOF
systemctl daemon-reload
check_output " $unit_path " " $log_file " "foo\nbar\n"
rm -f " $unit_path " " $log_file "
}
testcase_removal( ) {
local unit_path log_file
unit_path = " $( mktemp /run/systemd/system/test-deserialization-removal-XXX.service) "
log_file = " $( mktemp) "
setup_base_unit " $unit_path " " $log_file "
# Remove the currently executed ExecStart= line.
#
2024-01-05 13:07:37 +03:00
# In this case we completely drop the currently executed "sleep 3" statement, so after reload systemd
2024-01-02 20:27:45 +03:00
# should complain that the currently executed command vanished and simply finish executing the unit,
# resulting in an empty log.
cat >" $unit_path " <<EOF
[ Service]
Type = oneshot
ExecStart = bash -c " echo bar >> $log_file "
ExecStart = bash -c " echo baz >> $log_file "
EOF
systemctl daemon-reload
check_output " $unit_path " " $log_file " ""
rm -f " $unit_path " " $log_file "
}
testcase_issue_6533( ) {
local unit_path unit_name log_file
unit_path = " $( mktemp /run/systemd/system/test-deserialization-issue-6533-XXX.service) "
unit_name = " ${ unit_path ##*/ } "
log_file = " $( mktemp) "
cat >" $unit_path " <<EOF
[ Service]
Type = simple
ExecStart = /bin/sleep 5
EOF
systemctl daemon-reload
systemctl --job-mode= replace --no-block start " $unit_name "
sleep 2
# Make sure we try to execute the next command only for oneshot services, as for other types we allow
# only one ExecStart= directive.
#
# See: https://github.com/systemd/systemd/issues/6533
cat >" $unit_path " <<EOF
[ Service]
Type = simple
ExecStart = /bin/sleep 5
ExecStart = bash -c " echo foo >> $log_file "
EOF
systemctl daemon-reload
check_output " $unit_path " " $log_file " ""
( ! journalctl -b --grep "Freezing execution" _PID = 1)
}
mkdir -p /run/systemd/system/
run_testcases
systemctl daemon-reload