TEST-80-NOTIFYACCESS: Add notify-reload signal handler validation test

Add a new integration test to verify the signal handler validation for
Type=notify-reload services, as introduced in the previous commit.

The test adds a shell harness and four service files to cover all scenarios:

1. `notify-reload-no-handler.service`: Verifies that the service fails to
   start with Result=protocol when the handler is missing at the first
   READY=1.

2. `notify-reload-sigstop.service`: Verifies that SIGSTOP is exempt from
   handler validation because it cannot be caught or blocked.

3. `notify-reload-toggle-handler.service`: Verifies that removing the
   handler at runtime results in a warning on reload, but the signal is still
   sent, causing the service to terminate from unhandled SIGHUP.

4. `notify-reload-well-behaved.service`: Verifies the happy path where a
   service with a handler starts and reloads successfully.

Use bounded FIFO operations and journal cursors so failures are prompt and log
assertions cannot race a relative time window.
This commit is contained in:
Chris Down
2026-08-05 17:31:03 -07:00
parent d361e5ba34
commit 41c12e32ce
6 changed files with 150 additions and 0 deletions
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -eux
set -o pipefail
MODE="${1:-well-behaved}"
FIFO_IN="${2:-/tmp/reload-test-fifo-in}"
FIFO_OUT="${3:-/tmp/reload-test-fifo-out}"
handle_signal() {
echo "reload" >"$FIFO_OUT"
systemd-notify --reloading
systemd-notify --ready --status="Reloaded"
}
case "$MODE" in
no-handler)
;;
toggle-handler|well-behaved)
trap 'handle_signal' HUP
;;
*)
echo "Unknown mode: $MODE" >&2
exit 1
;;
esac
systemd-notify --ready --status="Started with mode=$MODE"
while read -r cmd < "$FIFO_IN"; do
case "$cmd" in
remove-handler)
trap - HUP
echo "handler-removed" >"$FIFO_OUT"
;;
exit)
break
;;
*)
echo "Unknown command: $cmd" >&2
exit 1
;;
esac
done
@@ -0,0 +1,6 @@
[Unit]
Description=Test notify-reload without signal handler
[Service]
Type=notify-reload
ExecStart=/usr/lib/systemd/tests/testdata/TEST-80-NOTIFYACCESS.units/notify-reload-harness.sh no-handler /tmp/reload-nohandler-in /tmp/reload-nohandler-out
@@ -0,0 +1,7 @@
[Unit]
Description=Test notify-reload with SIGSTOP
[Service]
Type=notify-reload
ReloadSignal=SIGSTOP
ExecStart=/usr/lib/systemd/tests/testdata/TEST-80-NOTIFYACCESS.units/notify-reload-harness.sh no-handler /tmp/reload-sigstop-in /tmp/reload-sigstop-out
@@ -0,0 +1,6 @@
[Unit]
Description=Test notify-reload with handler that can be removed
[Service]
Type=notify-reload
ExecStart=/usr/lib/systemd/tests/testdata/TEST-80-NOTIFYACCESS.units/notify-reload-harness.sh toggle-handler /tmp/reload-toggle-in /tmp/reload-toggle-out
@@ -0,0 +1,6 @@
[Unit]
Description=Test notify-reload with proper signal handler
[Service]
Type=notify-reload
ExecStart=/usr/lib/systemd/tests/testdata/TEST-80-NOTIFYACCESS.units/notify-reload-harness.sh well-behaved /tmp/reload-wellbehaved-in /tmp/reload-wellbehaved-out
+81
View File
@@ -172,4 +172,85 @@ assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 0
assert_eq "$(systemctl show fdstore-pin.service -P SubState)" dead
assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" dead
# Test notify-reload signal handler validation
cleanup_notify_reload() {
systemctl stop notify-reload-no-handler.service \
notify-reload-sigstop.service \
notify-reload-toggle-handler.service \
notify-reload-well-behaved.service || :
systemctl reset-failed notify-reload-no-handler.service \
notify-reload-sigstop.service \
notify-reload-toggle-handler.service \
notify-reload-well-behaved.service || :
rm -f /tmp/reload-nohandler-in /tmp/reload-nohandler-out \
/tmp/reload-nohandler-cursor \
/tmp/reload-sigstop-in /tmp/reload-sigstop-out \
/tmp/reload-toggle-in /tmp/reload-toggle-out \
/tmp/reload-toggle-cursor \
/tmp/reload-wellbehaved-in /tmp/reload-wellbehaved-out
}
trap cleanup_notify_reload EXIT
cleanup_notify_reload
# Test 1: Service without signal handler should fail to start
mkfifo /tmp/reload-nohandler-in /tmp/reload-nohandler-out
journalctl -q -n 0 --cursor-file=/tmp/reload-nohandler-cursor
(! systemctl start notify-reload-no-handler.service)
assert_eq "$(systemctl show notify-reload-no-handler.service -P SubState)" "failed"
assert_eq "$(systemctl show notify-reload-no-handler.service -P Result)" "protocol"
# Verify error was logged about missing signal handler
journalctl --sync
journalctl -u notify-reload-no-handler.service --cursor-file=/tmp/reload-nohandler-cursor \
--grep "lacks handler for reload signal" >/dev/null
systemctl reset-failed notify-reload-no-handler.service
rm -f /tmp/reload-nohandler-in /tmp/reload-nohandler-out
# Test 2: SIGSTOP cannot be caught or blocked, so it is exempt from handler validation
mkfifo /tmp/reload-sigstop-in /tmp/reload-sigstop-out
systemctl start notify-reload-sigstop.service
assert_eq "$(systemctl show notify-reload-sigstop.service -P SubState)" "running"
systemctl stop notify-reload-sigstop.service
rm -f /tmp/reload-sigstop-in /tmp/reload-sigstop-out
# Test 3: Service that removes handler should warn on reload but still reload
mkfifo /tmp/reload-toggle-in /tmp/reload-toggle-out
systemctl start notify-reload-toggle-handler.service
assert_eq "$(systemctl show notify-reload-toggle-handler.service -P SubState)" "running"
# Command service to remove its signal handler
timeout 10 bash -c 'echo "remove-handler" >/tmp/reload-toggle-in'
response="$(timeout 10 cat /tmp/reload-toggle-out)"
assert_eq "$response" "handler-removed"
# Reload should succeed (with warning) even though handler is gone - service will be killed by SIGHUP
# but that's intentional after the first start succeeds
journalctl -q -n 0 --cursor-file=/tmp/reload-toggle-cursor
systemctl reload --no-block notify-reload-toggle-handler.service
# The service will die from SIGHUP since the handler is removed
timeout 10 bash -c 'while systemctl is-active --quiet notify-reload-toggle-handler.service; do sleep .5; done'
# Verify warning was logged
journalctl --sync
journalctl -u notify-reload-toggle-handler.service --cursor-file=/tmp/reload-toggle-cursor \
--grep "lacks handler for reload signal" >/dev/null
rm -f /tmp/reload-toggle-in /tmp/reload-toggle-out
# Test 4: Well-behaved service with handler should work correctly
mkfifo /tmp/reload-wellbehaved-in /tmp/reload-wellbehaved-out
systemctl start notify-reload-well-behaved.service
assert_eq "$(systemctl show notify-reload-well-behaved.service -P SubState)" "running"
# Reload should succeed
systemctl reload --no-block notify-reload-well-behaved.service
timeout 10 bash -c 'until [[ $(systemctl show notify-reload-well-behaved.service -P SubState) == "reload-signal" ]]; do sleep .5; done'
response="$(timeout 10 cat /tmp/reload-wellbehaved-out)"
assert_eq "$response" "reload"
timeout 10 bash -c 'until [[ $(systemctl show notify-reload-well-behaved.service -P SubState) == "running" ]]; do sleep .5; done'
assert_eq "$(systemctl show notify-reload-well-behaved.service -P ReloadResult)" "success"
timeout 10 bash -c 'echo "exit" >/tmp/reload-wellbehaved-in'
systemctl stop notify-reload-well-behaved.service
rm -f /tmp/reload-wellbehaved-in /tmp/reload-wellbehaved-out
touch /testok