# GNU Shepherd --- Test basic communication capabilities.
# Copyright © 2013-2014, 2016-2019, 2022-2025 Ludovic Courtès <ludo@gnu.org>
# Copyright © 2016 Mathieu Lirzin <mthl@gnu.org>
# Copyright © 2014 Alex Sassmannshausen <alex.sassmannshausen@gmail.com>
#
# This file is part of the GNU Shepherd.
#
# The GNU Shepherd is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or (at
# your option) any later version.
#
# The GNU Shepherd is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with the GNU Shepherd.  If not, see <http://www.gnu.org/licenses/>.

shepherd --version
herd --version

socket="t-socket-$$"
conf="t-conf-$$"
confdir="t-confdir-$$"
datadir="t-datadir-$$"
log="t-log-$$"
stamp="t-stamp-$$"
pid="t-pid-$$"

herd="herd -s $socket"

trap "cat $log || true; rm -f $socket $conf $stamp $log;
      test -f $pid && kill \`cat $pid\` || true; rm -f $pid" EXIT

cat > "$conf"<<EOF
(use-modules (srfi srfi-26))
(register-services
 (list (service
	 '(test)
	 #:start (lambda _
		   (call-with-output-file "$stamp"
		     (cut display "foo" <>))
		   #t)
	 #:stop  (lambda _
		   (delete-file "$stamp"))
	 #:respawn? #f)
       (service
	 '(test-2)
	 #:requirement '(test)
	 #:start (lambda _
		   (call-with-output-file "$stamp-2"
		     (cut display "bar" <>))
		   #t)
	 #:stop  (lambda _
		   (delete-file "$stamp-2"))
	 #:actions (actions (hi "Say hi."
				(lambda _
				  (display "start\n\nend\n")
				  #t))
			    (fail "Fail." (const #f)))
	 #:respawn? #f)
       (service
	 '(spawn-with-system)
	 #:start (make-system-constructor "echo starting from $PWD")
	 #:stop (make-system-destructor "echo stopping from $PWD"))
       (service
	 '(broken)
	 #:requirement '()
	 #:start (lambda _
		   (mkdir "/this/throws/a/system/error"))
	 #:stop  (const #f)
	 #:respawn? #f)))
EOF

shepherd -I -s /does/not/exist/sock -c "$conf" && false

rm -f "$pid"
shepherd -I -s "$socket" -c "$conf" -l "$log" --pid="$pid" &

# Wait till it's ready.
while ! test -f "$pid" ; do sleep 0.3 ; done

shepherd_pid="`cat $pid`"

kill -0 $shepherd_pid
test -S "$socket"

# Attempt to reuse $socket should fail.
shepherd -I -s "$socket" -c "$conf" && false

pristine_status=`$herd status -n 0` # Prep for 'reload' test.
echo $pristine_status | grep -E '(Start.*root|Stop.*test)'

# This command should display shepherd's own PID.
$herd status root | grep "PID: $(cat "$pid")"

# The "Recent messages" bit should contain that phrase.
$herd status root | grep "Service root started"

# The "Custom actions" bit should list these and more.
$herd status root | grep "actions: .*eval"
$herd status root | grep "actions: .*load"
$herd status root | grep "actions: .*halt"

$herd graph | grep '"test-2" -> "test"'

$herd start test
test -f "$stamp"
$herd status test | grep running

$herd status root | grep "Service test started" # from "Recent messages"

$herd stop test
test -f "$stamp" && false

$herd log
$herd log | grep "service test is running"
$herd log | grep "service test is stopped"

$herd status test | grep stopped

# Stopping a stopped service should be a no-op.
$herd stop test
test -z "`$herd stop test 2>&1`"

# Disable a service and make sure it cannot be started.
$herd disable test-2
if $herd start test-2
then false; else true; fi
$herd start test-2 | grep "test-2 is currently disabled"

$herd enable test-2
$herd start test-2

# Make sure we didn't emit empty lines in the log (strip the timestamp that
# prefixes each line.)
test `wc -l < "$log"` -gt 0
test `cut -c 21- < "$log" | grep "^$" | wc -l` -eq 0

# Try a custom action; make sure we get all the lines, including the empty
# lines (this was not the case in 0.4.0.)
$herd doc test-2 action hi | grep "Say hi\."
$herd hi test-2
$herd hi test-2 | grep '^start$'
$herd hi test-2 | grep '^end$'
test `$herd hi test-2 | wc -l` -eq 3

# An action that returns false must lead to a non-zero exit code.
if $herd fail test-2; then false; else true; fi

# This used to crash shepherd: <http://bugs.gnu.org/24684>.
if $herd enable test-2 with extra arguments
then false; else true; fi

$herd status test-2 | grep running

# Make sure extra arguments lead to an error.
if $herd status test-2 something else that is useless
then false; else true; fi

for action in status start stop
do
    if $herd $action does-not-exist
    then false; else true; fi

    $herd $action does-not-exist 2>&1 | grep "does-not-exist.*not.*found"
done

if $herd an-action-that-does-not-exist root
then false; else true; fi

# Check the behavior for a service whose 'start' method throws.
$herd start broken && false
$herd status broken | grep "stopped"

# Check 'make-system-constructor' and 'make-system-destructor'.
$herd start spawn-with-system
$herd status spawn-with-system | grep running
$herd status spawn-with-system | grep "starting from " # recent messages
$herd stop spawn-with-system
$herd status spawn-with-system | grep "stopped"

# Wrong number of arguments for an action.
if $herd status root foo bar baz;
then false; else true; fi

# Asking for the doc of specific actions.
$herd doc root action status
if $herd doc root action an-action-that-does-not-exist
then false; else true; fi

# Make sure the error message is correct.
$herd doc root action an-action-that-does-not-exist 2>&1 | \
    grep "does not have an action 'an-action-that-does-not-exist'"

$herd doc root list-actions | grep ^daemonize:
$herd doc root list-actions | grep ^halt:

# Loading nonexistent file.
if $herd load root /does/not/exist.scm;
then false; else true; fi

# Unload two services, make sure the other it still around.
$herd unload root broken
$herd unload root test
$herd status -n 0 | grep -e "- test-2"

$herd reload root "$conf"
test "`$herd status -n 0`" == "$pristine_status"

# Dynamically loading code.

mkdir -p "$confdir"
cat > "$confdir/some-conf.scm" <<EOF
(register-services
 (service '(test-loaded)  ;passing a rest list, which is deprecated
   #:start (λ _ (display "Greek letter λ\n") 'abc)
   #:stop (const #f)))
EOF

if $herd status test-loaded
then false; else true; fi

$herd load root "$confdir/some-conf.scm"
rm "$confdir/some-conf.scm"

# First, a deprecation warning should have been logged.
grep "register-services.*rest list.*deprecated" "$log"

# The new service should be loaded now.
$herd status test-loaded
$herd status test-loaded | grep stopped

$herd start test-loaded
$herd status test-loaded | grep -i 'running.*abc'
grep "Greek letter" "$log"
$herd stop test-loaded
$herd unload root test-loaded

# Load a service meant to execute from a nonexistent directory.
cat > "$confdir/some-conf.scm" <<EOF
(register-services
 (list (service '(test-run-from-nonexistent-directory)
	 #:start (make-forkexec-constructor '("$(type -P pwd)")
                                            #:directory "/does-not-exist")
	 #:stop (make-kill-destructor)
         #:respawn? #f)))
EOF

$herd load root "$confdir/some-conf.scm"

# 'start' succeeds (because fork(2) does) but it should stop immediately after
# with the process marked as having failed.
$herd start test-run-from-nonexistent-directory
until $herd status test-run-from-nonexistent-directory | \
	grep 'exited with code 127'
do sleep 0.5; done

# Check that we're still talking to the same shepherd process: it used to be
# that 'fork+exec-command' would leave the faulty child process behind it when
# 'chdir' throws.
for i in $(seq 1 10)
do
    test $($herd eval root '(getpid)' | grep '^[0-9]') -eq $shepherd_pid
done
$herd unload root test-run-from-nonexistent-directory

# Load a service whose running value does not have a valid read syntax, and
# make sure that the running value is clamped before being sent over the wire.
cat > "$confdir/some-conf.scm" <<EOF
(register-services
 (list (service '(test-loaded)
	 #:start (const (if #f #f))  ;#<undefined>
	 #:stop (const #f))))
EOF

$herd load root "$confdir/some-conf.scm"
$herd start test-loaded
$herd status test-loaded | grep -i "running.*#<unspecified>"
$herd stop test-loaded

# Deregister 'test-loaded' via 'eval'.
$herd eval root "(perform-service-action root-service 'unload \"test-loaded\")"
if $herd status test-loaded
then false; else true; fi

# Load code that triggers a syntax error and make sure that shepherd survives.
cat > "$confdir/some-conf.scm" <<EOF
(define x y z)
EOF

if $herd load root "$confdir/some-conf.scm"
then false; else true; fi
$herd status			# still here?

# Load code that throws an object with no read syntax.
cat > "$confdir/some-conf.scm" <<EOF
(use-modules (srfi srfi-9))
(define-record-type <something> (something) something?)
(throw 'what?! (something))
EOF

if $herd load root "$confdir/some-conf.scm"
then false; else true; fi
$herd status			# still here?

# Evaluate silly code, make sure nothing breaks.
if $herd eval root '(/ 0 0)'
then false; else true; fi

if $herd eval root '(no closing paren'
then false; else true; fi

$herd eval root '(values)'

# Make sure we can suspend from an action.
$herd eval root '((@ (fibers) sleep) 1)'

# Unload everything and make sure only 'root' is left.
$herd unload root all
if $herd status | grep "Stopped:"
then false; else true; fi
$herd status | grep -e "+ root"

# Check that the "Service * already running" message gets printed only when
# explicitly starting an already-running service.
if grep "already running" "$log"
then false; else true; fi

$herd start root | grep "already running"
grep "already running" "$log"

$herd stop root

# The shepherd process should be done now, or real soon.
if kill -0 $shepherd_pid
then
    sleep 3
    kill -0 $shepherd_pid && false
fi

test -f "$log"

## ------------------------ ##
## Usage of XDG variables.  ##
## ------------------------ ##

# Set XDG_CONFIG_HOME for configuration files.
export XDG_CONFIG_HOME=$confdir
export XDG_DATA_HOME=$datadir
# Reuse XDG_DATA_HOME as XDG_STATE_HOME for backwards compatibility.
export XDG_STATE_HOME=$datadir
mkdir -p $confdir/shepherd
mkdir -p $datadir/shepherd
mv $conf $confdir/shepherd/init.scm
rm -f "$pid" "$socket"
shepherd -I -s "$socket" --pid="$pid" &

# Wait till it's ready.
while ! test -f "$pid" ; do sleep 0.3 ; done

# Launch a service from $confdir/shepherd/init.scm.
$herd start test
test -f "$stamp"
$herd status test | grep running

$herd stop test
test -f "$stamp" && false

shepherd_pid="`cat $pid`"

$herd stop root
if kill -0 $shepherd_pid
then
    sleep 3
    kill -0 $shepherd_pid && false
fi

rm -rf "$confdir" "$datadir"
