1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381
|
# 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 <https://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 '("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"
|