From 96e69cb655c703d5312c4aa44912e6ce90b4f76f Mon Sep 17 00:00:00 2001
From: Frantisek Sumsal <fsumsal@redhat.com>
Date: Wed, 29 Mar 2023 22:38:55 +0200
Subject: [PATCH] test: make TEST-35-LOGIN stable again

The expect stuff was anything but expected, so let's just backport
the upstream test case and tweak it a bit to account for the missing
parts in our downstream testing infrastructure.

Follow-up to 638c2418e7.
Related: #2179309
rhel-only
---
 test/TEST-35-LOGIN/testsuite.sh | 225 ++++++++++++++++++++++++--------
 test/test-functions             |   2 +-
 2 files changed, 175 insertions(+), 52 deletions(-)

diff --git a/test/TEST-35-LOGIN/testsuite.sh b/test/TEST-35-LOGIN/testsuite.sh
index e4d72beb74..149b2354a1 100755
--- a/test/TEST-35-LOGIN/testsuite.sh
+++ b/test/TEST-35-LOGIN/testsuite.sh
@@ -3,6 +3,141 @@
 set -eux
 set -o pipefail
 
+cleanup_test_user() (
+    set +ex
+
+    pkill -u "$(id -u logind-test-user)"
+    sleep 1
+    pkill -KILL -u "$(id -u logind-test-user)"
+    userdel -r logind-test-user
+
+    return 0
+)
+
+setup_test_user() {
+    mkdir -p /var/spool/cron /var/spool/mail /var/run/console
+    useradd -m -s /bin/bash logind-test-user
+    trap cleanup_test_user EXIT
+}
+
+test_enable_debug() {
+    mkdir -p /run/systemd/system/systemd-logind.service.d
+    cat >/run/systemd/system/systemd-logind.service.d/debug.conf <<EOF
+[Service]
+Environment=SYSTEMD_LOG_LEVEL=debug
+EOF
+    systemctl daemon-reload
+    systemctl stop systemd-logind.service
+}
+
+check_session() (
+    set +ex
+
+    local seat session leader_pid
+
+    if [[ $(loginctl --no-legend | grep -c "logind-test-user") != 1 ]]; then
+        echo "no session or multiple sessions for logind-test-user." >&2
+        return 1
+    fi
+
+    seat=$(loginctl --no-legend | grep 'logind-test-user *seat' | awk '{ print $4 }')
+    if [[ -z "$seat" ]]; then
+        echo "no seat found for user logind-test-user" >&2
+        return 1
+    fi
+
+    session=$(loginctl --no-legend | awk '$3 == "logind-test-user" { print $1 }')
+    if [[ -z "$session" ]]; then
+        echo "no session found for user logind-test-user" >&2
+        return 1
+    fi
+
+    if ! loginctl session-status "$session" | grep -q "Unit: session-${session}\.scope"; then
+        echo "cannot find scope unit for session $session" >&2
+        return 1
+    fi
+
+    leader_pid=$(loginctl session-status "$session" | awk '$1 == "Leader:" { print $2 }')
+    if [[ -z "$leader_pid" ]]; then
+        echo "cannot found leader process for session $session" >&2
+        return 1
+    fi
+
+    # cgroup v1: "1:name=systemd:/user.slice/..."; unified hierarchy: "0::/user.slice"
+    if ! grep -q -E '(name=systemd|^0:):.*session.*scope' /proc/"$leader_pid"/cgroup; then
+        echo "FAIL: process $leader_pid is not in the session cgroup" >&2
+        cat /proc/self/cgroup
+        return 1
+    fi
+)
+
+create_session() {
+    # login with the test user to start a session
+    mkdir -p /run/systemd/system/getty@tty2.service.d
+    cat >/run/systemd/system/getty@tty2.service.d/override.conf <<EOF
+[Service]
+Type=simple
+ExecStart=
+ExecStart=-/sbin/agetty --autologin logind-test-user --noclear %I $TERM
+Restart=no
+EOF
+    systemctl daemon-reload
+
+    systemctl restart getty@tty2.service
+
+    # check session
+    for ((i = 0; i < 30; i++)); do
+        (( i != 0 )) && sleep 1
+        check_session && break
+    done
+    check_session
+    [[ "$(loginctl --no-legend | awk '$3=="logind-test-user" { print $5 }')" == "tty2" ]]
+}
+
+cleanup_session() (
+    set +ex
+
+    local uid s
+
+    uid=$(id -u logind-test-user)
+
+    loginctl disable-linger logind-test-user
+
+    systemctl stop getty@tty2.service
+
+    for s in $(loginctl --no-legend list-sessions | awk '$3 == "logind-test-user" { print $1 }'); do
+        echo "INFO: stopping session $s"
+        loginctl terminate-session "$s"
+    done
+
+    loginctl terminate-user logind-test-user
+
+    if ! timeout 30 bash -c "while loginctl --no-legend | grep -q logind-test-user; do sleep 1; done"; then
+        echo "WARNING: session for logind-test-user still active, ignoring."
+    fi
+
+    pkill -u "$uid"
+    sleep 1
+    pkill -KILL -u "$uid"
+
+    if ! timeout 30 bash -c "while systemctl is-active --quiet user@${uid}.service; do sleep 1; done"; then
+        echo "WARNING: user@${uid}.service is still active, ignoring."
+    fi
+
+    if ! timeout 30 bash -c "while systemctl is-active --quiet user-runtime-dir@${uid}.service; do sleep 1; done"; then
+        echo "WARNING: user-runtime-dir@${uid}.service is still active, ignoring."
+    fi
+
+    if ! timeout 30 bash -c "while systemctl is-active --quiet user-${uid}.slice; do sleep 1; done"; then
+        echo "WARNING: user-${uid}.slice is still active, ignoring."
+    fi
+
+    rm -rf /run/systemd/system/getty@tty2.service.d
+    systemctl daemon-reload
+
+    return 0
+)
+
 setup_idle_action_lock() {
     useradd testuser ||:
 
@@ -19,83 +154,71 @@ EOF
 Environment=SYSTEMD_LOG_LEVEL=debug
 EOF
 
+    systemd-analyze cat-config --no-pager systemd/logind.conf
     systemctl restart systemd-logind.service
 }
 
-teardown_idle_action_lock() {(
-    set +ex
+teardown_lock_idle_action() (
+    set +eux
+
     rm -f /run/systemd/logind.conf.d/idle-action-lock.conf
-    rm -f /run/systemd/systemd-logind.service.d/debug.conf
-    pkill -9 -u "$(id -u testuser)"
-    userdel -r testuser
     systemctl restart systemd-logind.service
-)}
 
-test_lock_idle_action() {
-    if ! command -v expect >/dev/null ; then
-        echo >&2 "expect not installed, skiping test ${FUNCNAME[0]}"
-        return 0
-    fi
+    cleanup_session
 
-    setup_idle_action_lock
-    trap teardown_idle_action_lock RETURN
+    return 0
+)
 
-    if loginctl --no-legend | awk '{ print $3; }' | sort -u | grep -q testuser ; then
-        echo >&2 "Session of the \'testuser\' is already present."
-        return 1
+test_lock_idle_action() {
+    local ts
+
+    if [[ ! -c /dev/tty2 ]]; then
+        echo "/dev/tty2 does not exist, skipping test ${FUNCNAME[0]}."
+        return
     fi
 
-    # IdleActionSec is set 1s but the accuracy of associated timer is 30s so we
-    # need to sleep in worst case for 31s to make sure timer elapsed. We sleep
-    # here for 35s to accomodate for any possible scheudling delays.
-    cat > /tmp/test.exp <<EOF
-spawn systemd-run -G -t -p PAMName=login -p User=testuser bash
-send "sleep 35\r"
-send "echo foobar\r"
-send "sleep 35\r"
-send "exit\r"
-interact
-wait
-EOF
+    if loginctl --no-legend | grep -q logind-test-user; then
+        echo >&2 "Session of the \'logind-test-user\' is already present."
+        exit 1
+    fi
 
-    ts="$(date '+%H:%M:%S')"
-    busctl --match "type='signal',sender='org.freedesktop.login1',interface='org.freedesktop.login1.Session',member='Lock'" monitor  > dbus.log &
+    trap teardown_lock_idle_action RETURN
 
-    expect /tmp/test.exp &
+    create_session
 
-    # Sleep a bit to give expect time to spawn systemd-run before we check for
-    # the presence of resulting session.
-    sleep 2
-    if [ "$(loginctl --no-legend | awk '{ print $3; }' | sort -u | grep -c testuser)" != 1 ] ; then
-        echo >&2 "\'testuser\' is expected to have exactly one session running."
-        return 1
-    fi
+    ts="$(date '+%H:%M:%S')"
 
-    wait %2
-    sleep 20
-    kill %1
+    mkdir -p /run/systemd/logind.conf.d
+    cat >/run/systemd/logind.conf.d/idle-action-lock.conf <<EOF
+[Login]
+IdleAction=lock
+IdleActionSec=1s
+EOF
+    systemctl restart systemd-logind.service
 
-    # We slept for 35s , in that interval all sessions should have become idle
+    # Wait for 35s, in that interval all sessions should have become idle
     # and "Lock" signal should have been sent out. Then we wrote to tty to make
     # session active again and next we slept for another 35s so sessions have
     # become idle again. 'Lock' signal is sent out for each session, we have at
     # least one session, so minimum of 2 "Lock" signals must have been sent.
-    if [ "$(grep -c Member=Lock dbus.log)" -lt 2 ]; then
-        echo >&2 "Too few 'Lock' D-Bus signal sent, expected at least 2."
-        return 1
-    fi
+    timeout 35 bash -c "while [[ \"\$(journalctl -b -u systemd-logind.service --since=$ts | grep -c 'Sent message type=signal .* member=Lock')\" -lt 1 ]]; do sleep 1; done"
+
+    # Wakeup
+    touch /dev/tty2
+
+    # Wait again
+    timeout 35 bash -c "while [[ \"\$(journalctl -b -u systemd-logind.service --since=$ts | grep -c 'Sent message type=signal .* member=Lock')\" -lt 2 ]]; do sleep 1; done"
 
-    journalctl -b -u systemd-logind.service --since="$ts" > logind.log
-    if [ "$(grep -c 'System idle. Doing lock operation.' logind.log)" -lt 2 ]; then
+    if [[ "$(journalctl -b -u systemd-logind.service --since="$ts" | grep -c 'System idle. Doing lock operation.')" -lt 2 ]]; then
         echo >&2 "System haven't entered idle state at least 2 times."
-        return 1
+        exit 1
     fi
-
-    rm -f dbus.log logind.log
 }
 
 : >/failed
 
+setup_test_user
+test_enable_debug
 test_lock_idle_action
 
 touch /testok
diff --git a/test/test-functions b/test/test-functions
index 9606a1b085..e5d4d28a5f 100644
--- a/test/test-functions
+++ b/test/test-functions
@@ -23,7 +23,7 @@ fi
 
 PATH_TO_INIT=$ROOTLIBDIR/systemd
 
-BASICTOOLS="test sh bash setsid loadkeys setfont login sulogin gzip sleep echo mount umount cryptsetup date dmsetup modprobe sed cmp tee rm true false chmod chown ln xargs env mktemp mountpoint useradd userdel"
+BASICTOOLS="test sh bash setsid loadkeys setfont login sulogin gzip sleep echo mount umount cryptsetup date dmsetup modprobe sed cmp tee rm true false chmod chown ln xargs env mktemp mountpoint useradd userdel timeout"
 DEBUGTOOLS="df free ls stty cat ps ln ip route dmesg dhclient mkdir cp ping dhclient strace less grep id tty touch du sort hostname find"
 
 STATEDIR="${BUILD_DIR:-.}/test/$(basename $(dirname $(realpath $0)))"
