mirror of
https://github.com/systemd/systemd.git
synced 2024-12-22 17:35:35 +03:00
test: rewrite test-exec-deserialization.py
Rewrite the test in bash and make it part of our integration test suite, so it's actually executed in all our upstream CI environments. The original test is flaky in environments where daemon-reload might occur during the test runtime (e.g. when running the test in parallel with the systemd-networkd test suite). Also, it was run only in CentOS CI in limited way (i.e. without sanitizers), since it tests the host's systemd, instead of the just built one. Resolves: #29943
This commit is contained in:
parent
0e3638380d
commit
995bf013a1
@ -1,214 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# Copyright © 2017 Michal Sekletar <msekleta@redhat.com>
|
||||
|
||||
# ATTENTION: This uses the *installed* systemd, not the one from the built
|
||||
# source tree.
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import unittest
|
||||
import uuid
|
||||
from enum import Enum
|
||||
|
||||
class InstallChange(Enum):
|
||||
NO_CHANGE = 0
|
||||
LINES_SWAPPED = 1
|
||||
COMMAND_ADDED_BEFORE = 2
|
||||
COMMAND_ADDED_AFTER = 3
|
||||
COMMAND_INTERLEAVED = 4
|
||||
REMOVAL = 5
|
||||
|
||||
class ExecutionResumeTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.unit = 'test-issue-518.service'
|
||||
self.unitfile_path = f'/run/systemd/system/{self.unit}'
|
||||
self.output_file = f"/tmp/test-issue-518-{uuid.uuid4()}"
|
||||
self.unit_files = {}
|
||||
|
||||
unit_file_content = f'''
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/bin/sleep 3
|
||||
ExecStart=/bin/bash -c "echo foo >>{self.output_file}"
|
||||
'''
|
||||
self.unit_files[InstallChange.NO_CHANGE] = unit_file_content
|
||||
|
||||
unit_file_content = f'''
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/bin/bash -c "echo foo >>{self.output_file}"
|
||||
ExecStart=/bin/sleep 3
|
||||
'''
|
||||
self.unit_files[InstallChange.LINES_SWAPPED] = unit_file_content
|
||||
|
||||
unit_file_content = f'''
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/bin/bash -c "echo bar >>{self.output_file}"
|
||||
ExecStart=/bin/sleep 3
|
||||
ExecStart=/bin/bash -c "echo foo >>{self.output_file}"
|
||||
'''
|
||||
self.unit_files[InstallChange.COMMAND_ADDED_BEFORE] = unit_file_content
|
||||
|
||||
unit_file_content = f'''
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/bin/sleep 3
|
||||
ExecStart=/bin/bash -c "echo foo >>{self.output_file}"
|
||||
ExecStart=/bin/bash -c "echo bar >>{self.output_file}"
|
||||
'''
|
||||
self.unit_files[InstallChange.COMMAND_ADDED_AFTER] = unit_file_content
|
||||
|
||||
unit_file_content = f'''
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/bin/bash -c "echo baz >>{self.output_file}"
|
||||
ExecStart=/bin/sleep 3
|
||||
ExecStart=/bin/bash -c "echo foo >>{self.output_file}"
|
||||
ExecStart=/bin/bash -c "echo bar >>{self.output_file}"
|
||||
'''
|
||||
self.unit_files[InstallChange.COMMAND_INTERLEAVED] = unit_file_content
|
||||
|
||||
unit_file_content = f'''
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/bin/bash -c "echo bar >>{self.output_file}"
|
||||
ExecStart=/bin/bash -c "echo baz >>{self.output_file}"
|
||||
'''
|
||||
self.unit_files[InstallChange.REMOVAL] = unit_file_content
|
||||
|
||||
def reload(self):
|
||||
subprocess.check_call(['systemctl', 'daemon-reload'])
|
||||
|
||||
def write_unit_file(self, unit_file_change):
|
||||
if not isinstance(unit_file_change, InstallChange):
|
||||
raise ValueError('Unknown unit file change')
|
||||
|
||||
content = self.unit_files[unit_file_change]
|
||||
|
||||
with open(self.unitfile_path, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
self.reload()
|
||||
|
||||
def check_output(self, expected_output):
|
||||
for _ in range(15):
|
||||
# Wait until the unit finishes so we don't check an incomplete log
|
||||
if subprocess.call(['systemctl', '-q', 'is-active', self.unit]) == 0:
|
||||
continue
|
||||
|
||||
os.sync()
|
||||
|
||||
try:
|
||||
with open(self.output_file, 'r', encoding='utf-8') as log:
|
||||
output = log.read()
|
||||
self.assertEqual(output, expected_output)
|
||||
return
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
self.fail(f'Timed out while waiting for the output file {self.output_file} to appear')
|
||||
|
||||
def setup_unit(self):
|
||||
self.write_unit_file(InstallChange.NO_CHANGE)
|
||||
subprocess.check_call(['systemctl', '--job-mode=replace', '--no-block', 'start', self.unit])
|
||||
time.sleep(1)
|
||||
|
||||
def test_no_change(self):
|
||||
expected_output = 'foo\n'
|
||||
|
||||
self.setup_unit()
|
||||
self.reload()
|
||||
|
||||
self.check_output(expected_output)
|
||||
|
||||
def test_swapped(self):
|
||||
self.setup_unit()
|
||||
self.write_unit_file(InstallChange.LINES_SWAPPED)
|
||||
self.reload()
|
||||
|
||||
self.assertTrue(not os.path.exists(self.output_file))
|
||||
|
||||
def test_added_before(self):
|
||||
expected_output = 'foo\n'
|
||||
|
||||
self.setup_unit()
|
||||
self.write_unit_file(InstallChange.COMMAND_ADDED_BEFORE)
|
||||
self.reload()
|
||||
|
||||
self.check_output(expected_output)
|
||||
|
||||
def test_added_after(self):
|
||||
expected_output = 'foo\nbar\n'
|
||||
|
||||
self.setup_unit()
|
||||
self.write_unit_file(InstallChange.COMMAND_ADDED_AFTER)
|
||||
self.reload()
|
||||
|
||||
self.check_output(expected_output)
|
||||
|
||||
def test_interleaved(self):
|
||||
expected_output = 'foo\nbar\n'
|
||||
|
||||
self.setup_unit()
|
||||
self.write_unit_file(InstallChange.COMMAND_INTERLEAVED)
|
||||
self.reload()
|
||||
|
||||
self.check_output(expected_output)
|
||||
|
||||
def test_removal(self):
|
||||
self.setup_unit()
|
||||
self.write_unit_file(InstallChange.REMOVAL)
|
||||
self.reload()
|
||||
|
||||
self.assertTrue(not os.path.exists(self.output_file))
|
||||
|
||||
def test_issue_6533(self):
|
||||
unit = "test-issue-6533.service"
|
||||
unitfile_path = f"/run/systemd/system/{unit}"
|
||||
|
||||
content = '''
|
||||
[Service]
|
||||
ExecStart=/bin/sleep 5
|
||||
'''
|
||||
|
||||
with open(unitfile_path, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
self.reload()
|
||||
|
||||
subprocess.check_call(['systemctl', '--job-mode=replace', '--no-block', 'start', unit])
|
||||
time.sleep(2)
|
||||
|
||||
content = '''
|
||||
[Service]
|
||||
ExecStart=/bin/sleep 5
|
||||
ExecStart=/bin/true
|
||||
'''
|
||||
|
||||
with open(unitfile_path, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
self.reload()
|
||||
time.sleep(5)
|
||||
|
||||
self.assertNotEqual(subprocess.call("journalctl -b _PID=1 | grep -q 'Freezing execution'", shell=True), 0)
|
||||
|
||||
def tearDown(self):
|
||||
for f in [self.output_file, self.unitfile_path]:
|
||||
try:
|
||||
os.remove(f)
|
||||
except OSError:
|
||||
# ignore error if log file doesn't exist
|
||||
pass
|
||||
|
||||
self.reload()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=3))
|
221
test/units/testsuite-07.exec-deserialization.sh
Executable file
221
test/units/testsuite-07.exec-deserialization.sh
Executable file
@ -0,0 +1,221 @@
|
||||
#!/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
|
||||
# bar" one will have no efect and we should end up with the same output as in the previous case.
|
||||
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.
|
||||
#
|
||||
# In this case we completely drop the currently excuted "sleep 3" statement, so after reload systemd
|
||||
# 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
|
Loading…
Reference in New Issue
Block a user