2
#---------------------------------------------------------------------
3
# Script to run minimal Upstart user session tests.
5
# Note that this script _cannot_ be run as part of the "make check"
6
# tests since those tests stimulate functions and features of the
7
# as-yet-uninstalled version of Upstart. However, this script needs to
8
# run on a system where the version of Upstart under test has _already_
9
# been fully installed.
10
#---------------------------------------------------------------------
12
# Copyright (C) 2011 Canonical Ltd.
14
# Author: James Hunt <james.hunt@canonical.com>
16
# This program is free software: you can redistribute it and/or modify
17
# it under the terms of the GNU General Public License as published by
18
# the Free Software Foundation, version 3 of the License.
20
# This program is distributed in the hope that it will be useful,
21
# but WITHOUT ANY WARRANTY; without even the implied warranty of
22
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23
# GNU General Public License for more details.
25
# You should have received a copy of the GNU General Public License
26
# along with this program. If not, see <http://www.gnu.org/licenses/>.
28
#---------------------------------------------------------------------
31
sys_job_dir="/etc/init"
32
user_job_dir="$HOME/.init"
33
user_log_dir="$HOME/.cache/upstart/log"
34
sys_log_dir="/var/log/upstart"
35
bug_url="https://bugs.launchpad.net/upstart/+filebug"
46
# allow non-priv users to find 'initctl'
47
export PATH=$PATH:/sbin
53
echo "ERROR: $msg" >&2
60
[ "$debug_enabled" = 1 ] && echo "DEBUG: $str"
66
[ -z "$job" ] && die "need job"
68
pid=$(initctl status "$job"|grep process|awk '{print $NF}')
69
[ -z "$pid" ] && die "job $job has no pid"
74
# take a string and convert it into a valid job name
80
sed -e 's/>/ gt /g' -e 's/</ lt /g' -e 's/+/ and /g' |\
81
sed -e 's/[[:punct:]]//g' -e 's/ */ /g' |\
89
echo "$str" | sed 's!/!_!g'
92
# take a string and convert it into a valid job log file name
103
[ -z "$args" ] && die "need args"
106
echo "ERROR: TEST FAILED ('$feature')"
108
printf "BAD: ${args}\n"
109
printf "\nPlease report a bug at $bug_url including the following details:\n"
110
printf "\nUpstart:\n"
111
/sbin/init --version|head -n1
112
/sbin/initctl --version|head -n1
117
printf "Upstart Env:\n"
126
echo "ERROR: TEST FAILED ('$feature')"
135
[ -z "$name" ] && die "need name"
137
printf "Testing %s\n" "$name"
144
[ -z "$feature" ] && die "need feature"
146
printf "...%s\n" "$feature"
155
# XXX: no checks on value or expected since they might be blank
156
[ -z "$cmd" ] && die "need cmd"
158
[ "$value" = "$expected" ] && TEST_FAILED \
159
"wrong value for '$cmd', expected $expected got $value"
168
# XXX: no checks on value or expected since they might be blank
169
[ -z "$cmd" ] && die "need cmd"
171
[ "$value" != "$expected" ] && TEST_FAILED \
172
"wrong value for '$cmd', expected '$expected' got '$value'"
178
[ -z "$(command -v $cmd)" ] && die "cannot find command $cmd"
180
[ "$(id -u)" = 0 ] && die "ERROR: should not run this function as root"
182
# This will fail for a non-root user unless D-Bus is correctly
184
$cmd emit foo || die \
185
"You do not appear to have configured D-Bus for Upstart user sessions. See usage."
195
[ -z "$user_to_create" ] && die "need '-u' option when running as root"
197
getent passwd "$user_to_create" && \
198
die "user '$user_to_create' already exists"
200
echo "Creating user '$user_to_create'"
201
cmd="useradd -mU -c 'Upstart Test User' $user_to_create"
205
echo "Locking account for user '$user_to_create'"
206
cmd="usermod -L $user_to_create"
210
# Run ourselves again as the new user
211
su -c "$0 -a" "$user_to_create"
214
if [ $test_run_rc -eq 0 ]
216
echo "Deleting user '$user_to_create'"
217
cmd="userdel -r \"$user_to_create\""
228
if [ ! -d "$user_job_dir" ]
230
cmd="mkdir -p \"$user_job_dir\""
234
cmd="chmod 755 \"$user_job_dir\""
239
# create somewhere to store user jobs
240
cmd="mktemp -d --tmpdir=\"$user_job_dir\""
241
test_dir=$(eval "$cmd")
243
TEST_NE "$test_dir" "$test_dir" ""
244
test_dir_suffix=${test_dir#${user_job_dir}/}
246
# ensure files in this directory are accessible since
247
# mktemp sets directory perms to 0700 regardless of umask.
248
cmd="chmod 755 \"$test_dir\""
252
TEST_NE "HOME" "$HOME" ""
257
if [ -d "$test_dir" ]
259
echo "Removing test directory '$test_dir'"
260
cmd="rmdir \"$test_dir\""
271
[ -z "$job" ] && die "no job"
272
[ -z "$job_name" ] && die "no job name"
274
TEST_FEATURE "ensure 'initctl' recognises job"
275
initctl list|grep -q "^$job " || \
276
TEST_FAILED "job $job_name not known to initctl"
278
TEST_FEATURE "ensure 'status' recognises job"
280
eval "$cmd" >/dev/null 2>&1
285
# Note that if the specified job is *not* as task, it is expected to run
286
# indefinately. This allows us to perform PID checks, etc.
294
# XXX: env can be empty
295
[ -z "$job_name" ] && die "no job name"
296
[ -z "$job_file" ] && die "no job file"
297
[ -z "$task" ] && die "no task value"
299
job="${test_dir_suffix}/${job_name}"
301
[ -f "$job_file" ] || TEST_FAILED "job file '$job_file' does not exist"
303
ensure_job_known "$job" "$job_name"
305
TEST_FEATURE "ensure job can be started"
306
cmd="start ${job} ${env}"
307
output=$(eval "$cmd")
313
TEST_FEATURE "ensure 'start' shows job pid"
314
pid=$(echo "$output"|awk '{print $4}')
315
TEST_NE "pid" "$pid" ""
317
TEST_FEATURE "ensure 'initctl' shows job is running with pid"
318
initctl list|grep -q "^$job start/running, process $pid" || \
319
TEST_FAILED "job $job_name did not start"
321
TEST_FEATURE "ensure 'status' shows job is running with pid"
323
output=$(eval "$cmd")
324
echo "$output"|while read job_tmp state ignored status_pid
326
state=$(echo $state|tr -d ',')
327
TEST_EQ "job name" "$job_tmp" "$job"
328
TEST_EQ "job state" "$state" "start/running"
329
TEST_EQ "job pid" "$status_pid" "$pid"
332
TEST_FEATURE "ensure job pid is running with correct uids"
333
pid_uids=$(ps --no-headers -p $pid -o euid,ruid)
334
for pid_uid in $pid_uids
336
TEST_EQ "pid uid" "$pid_uid" "$uid"
339
TEST_FEATURE "ensure job pid is running with correct gids"
340
pid_gids=$(ps --no-headers -p $pid -o egid,rgid)
341
for pid_gid in $pid_gids
343
TEST_EQ "pid gid" "$pid_gid" "$gid"
346
TEST_FEATURE "ensure process is running in correct directory"
347
cwd=$(readlink /proc/$pid/cwd)
348
TEST_EQ "cwd" "$cwd" "$HOME"
350
TEST_FEATURE "ensure job can be stopped"
352
output=$(eval "$cmd")
356
TEST_FEATURE "ensure job pid no longer exists"
357
pid_ids=$(ps --no-headers -p $pid -o euid,ruid,egid,rgid)
358
TEST_EQ "pid uids+gids" "$pid_ids" ""
361
remove_job_file "$job_file"
362
ensure_job_gone "$job" "$job_name" "$env"
369
[ -z "$job_file" ] && die "no job file"
370
[ ! -f "$job_file" ] && TEST_FAILED "job file '$job_file' does not exist"
383
# XXX: no check on env since it can be empty
384
[ -z "$job" ] && die "no job"
385
[ -z "$job_name" ] && die "no job name"
387
TEST_FEATURE "ensure 'initctl' no longer recognises job"
388
initctl list|grep -q "^$job " && \
389
TEST_FAILED "deleted job $job_name still known to initctl"
391
TEST_FEATURE "ensure 'status' no longer recognises job"
393
eval "$cmd" >/dev/null 2>&1
406
# XXX: no test on script or env since they might be empty
407
[ -z "$test_group" ] && die "no test group"
408
[ -z "$job_name" ] && die "no job name"
409
[ -z "$task" ] && die "no task"
411
TEST_GROUP "$test_group"
413
job_file="${test_dir}/${job_name}.conf"
415
echo "$script" > $job_file
417
run_user_job_tests "$job_name" "$job_file" "$task" "$env"
420
test_user_job_binary()
422
group="user job running a binary"
423
job_name="binary_test"
424
script="exec sleep 999"
425
test_user_job "$group" "$job_name" "$script" no ""
428
test_user_job_binary_task()
430
group="user job running a binary task"
431
job_name="binary_task_test"
436
exec /bin/true > $OUTFILE"
438
test_user_job "$group" "$job_name" "$script" yes "OUTFILE=$OUTFILE"
442
test_user_job_single_line_script()
444
group="user job running a single-line script"
445
job_name="single_line_script_test"
450
test_user_job "$group" "$job_name" "$script" no ""
453
test_user_job_single_line_script_task()
455
group="user job running a single-line script task"
456
job_name="single_line_script_task_test"
462
exec /bin/true > $OUTFILE
464
test_user_job "$group" "$job_name" "$script" yes "OUTFILE=$OUTFILE"
468
test_user_job_multi_line_script()
470
group="user job running a multi-line script"
471
job_name="multi_line_script_test"
480
test_user_job "$group" "$job_name" "$script" no ""
483
test_user_job_multi_line_script_task()
485
group="user job running a multi-line script task"
486
job_name="multi_line_script_task_test"
498
test_user_job "$group" "$job_name" "$script" yes "OUTFILE=$OUTFILE"
502
test_user_emit_events()
504
job_name="start_on_foo"
506
TEST_GROUP "user emitting an event"
507
initctl emit foo || TEST_FAILED "failed to emit event as user"
509
TEST_GROUP "user emitting an event to start a job"
512
stop on baz cow=moo or hello
515
job_file="${test_dir}/${job_name}.conf"
516
job="${test_dir_suffix}/${job_name}"
518
echo "$script" > $job_file
520
ensure_job_known "$job" "$job_name"
522
initctl list|grep -q "^$job stop/waiting" || \
523
TEST_FAILED "job $job_name not stopped"
525
TEST_FEATURE "ensure job can be started with event"
526
initctl emit foo BAR=2 || \
527
TEST_FAILED "failed to emit event for user job"
529
initctl status "$job"|grep -q "^$job start/running" || \
530
TEST_FAILED "job $job_name failed to start"
532
TEST_FEATURE "ensure job can be stopped with event"
533
initctl emit baz cow=moo || \
534
TEST_FAILED "failed to emit event for user job"
536
initctl list|grep -q "^$job stop/waiting" || \
537
TEST_FAILED "job $job_name not stopped"
542
test_user_job_setuid_setgid()
544
group="user job with setuid and setgid me"
545
job_name="setuid_setgid_me_test"
550
test_user_job "$group" "$job_name" "$script" no ""
552
TEST_GROUP "user job with setuid and setgid root"
558
job_name="setuid_setgid_root_test"
559
job_file="${test_dir}/${job_name}.conf"
560
job="${test_dir_suffix}/${job_name}"
562
echo "$script" > $job_file
564
ensure_job_known "$job" "$job_name"
566
TEST_FEATURE "ensure job fails to start as root"
568
output=$(eval "$cmd" 2>&1)
572
TEST_FEATURE "ensure 'start' indicates job failure"
573
error=$(echo "$output"|grep failed)
574
TEST_NE "error" "$error" ""
576
TEST_FEATURE "ensure 'initctl' does not list job"
577
initctl list|grep -q "^$job stop/waiting" || \
578
TEST_FAILED "job $job_name not listed as stopped"
580
delete_job "$job_name"
587
[ -z "$job_name" ] && die "no job name"
588
echo "${test_dir}/${job_name}.conf"
597
job="${test_dir_suffix}/${job_name}"
599
create_job "$job_name" "$script"
600
start_job "$job" "$job_name" "$instance"
602
check_job_output "$job_name"
603
delete_job "$job_name"
611
# XXX: script could be empty
612
[ -z "$job_name" ] && die "no job name"
614
debug "create_job: job_name='$job_name'"
615
debug "create_job: script='$script'"
617
# Not currently possible to have a user job with the
618
# same name as a system job.
620
# XXX: Note that this test assumes that user has *not* specified
621
# XXX: an alternate configuration directory using the
622
# XXX: '--confdir' option.
623
[ -e "${sys_job_dir}/${job_name}.conf" ] && \
624
die "job '$job_name' already exists as a system job"
626
job_file="${test_dir}/${job_name}.conf"
627
job="${test_dir_suffix}/${job_name}"
629
echo "$script" > "$job_file"
637
[ -z "$job_name" ] && die "no job name"
639
job_file="$(get_job_file $job_name)"
641
rm "$job_file" || TEST_FAILED "unable to remove job file '$job_file'"
648
[ ! -z "$(ls $user_log_dir 2>/dev/null)" ] && \
649
TEST_FAILED "job $job_name created logfile unexpectedly in '$user_log_dir'"
651
# XXX: note that it might appear that checking in $sys_log_dir
652
# could result in false positives, but this isn't so since
653
# (currently) it is not possible for a user job to have the
654
# same name as a system job. start_job() will detect this
656
for dir in "$user_log_dir" "$sys_log_dir"
658
log_file="${dir}/${job_name}.log"
659
[ -f "$log_file" ] && \
660
TEST_FAILED "job $job_name created logfile unexpectedly as '$log_file'"
671
# XXX: instance may be blank
672
[ -z "$job" ] && die "no job"
673
[ -z "$job_file" ] && die "no job file"
675
debug "start_job: job='$job'"
676
debug "start_job: job_file='$job_file'"
677
debug "start_job: instance='$instance'"
678
debug "start_job: allow_failure='$allow_failure'"
680
eval output=$(mktemp)
682
# XXX: Don't quote instance as we don't want to pass a null instance to
684
cmd="start \"$job\" $instance >${output} 2>&1"
685
debug "start_job: running '$cmd'"
689
if [ $rc -ne 0 -a -z "$allow_failure" ]
691
TEST_FAILED "job $job_file not started: $(cat $output)"
697
get_job_logfile_name()
702
# XXX: instance may be null
703
[ -z "$job_name" ] && die "no job name"
705
encoded_test_dir_suffix=$(upstart_encode "${test_dir_suffix}/")
706
file_name="${encoded_test_dir_suffix}$(make_log_name $job_name)"
708
if [ ! -z "$instance_value" ]
710
log_file="${user_log_dir}/${file_name}-${instance_value}.log"
712
log_file="${user_log_dir}/${file_name}.log"
725
# XXX: script, instance might be blank
726
[ -z "$job" ] && die "no job"
727
[ -z "$job_name" ] && die "no job name"
729
debug "run_job: job='$job'"
730
debug "run_job: job_name='$job_name'"
731
debug "run_job: script='$script'"
732
debug "run_job: instance='$instance'"
734
create_job "$job_name" "$script"
735
start_job "$job" "$job_name" "$instance"
745
[ -z "$file" ] && die "no file"
746
[ -z "$expected_owner" ] && die "no expected owner"
747
[ -z "$expected_group" ] && die "no expected group"
748
[ -z "$expected_perms" ] && die "no expected perms"
750
[ ! -f "$file" ] && die "file $file does not exist"
756
if [ "$umask_value" != "$umask_expected" ]
758
msg="umask value is $umask_value -"
759
msg="${msg} changing it to $umask_expected."
761
umask "$umask_expected" || TEST_FAILED "unable to change umask"
764
owner=$(ls -l "$file"|awk '{print $3}')
765
group=$(ls -l "$file"|awk '{print $4}')
766
perms=$(stat --printf "%a\n" "$file")
768
[ "$owner" = "$expected_owner" ] || TEST_FAILED \
769
"file $file has wrong owner (expected $expected_owner, got $owner)"
771
[ "$group" = "$expected_group" ] || TEST_FAILED \
772
"file $file has wrong group (expected $expected_group, got $group)"
774
[ "$perms" = "$expected_perms" ] || TEST_FAILED \
775
"file $file has wrong group (expected $expected_perms, got $perms)"
788
# XXX: remaining args could be null
789
[ -z "$job_name" ] && die "no job name"
791
debug "ensure_output: job_name='$job_name'"
792
debug "ensure_output: script='$script'"
793
debug "ensure_output: expected_ouput='$expected_ouput'"
794
debug "ensure_output: instance='$instance'"
795
debug "ensure_output: instance_value='$instance_value'"
796
debug "ensure_output: options='$options'"
821
debug "ensure_output: regex='$regex'"
822
debug "ensure_output: retain='$retain'"
823
debug "ensure_output: unique='$unique'"
824
debug "ensure_output: use_od='$use_od'"
826
expected_owner=$(id -un)
827
expected_group=$(id -gn)
830
job="${test_dir_suffix}/${job_name}"
832
run_job "$job" "$job_name" "$script" "$instance"
834
debug "ensure_output: user_log_dir='$user_log_dir'"
835
debug "ensure_output: test_dir='$test_dir'"
836
debug "ensure_output: test_dir_suffix='$test_dir_suffix'"
838
log_file=$(get_job_logfile_name "$job_name" "$instance_value")
840
debug "ensure_output: log_file='$log_file'"
842
# Give Upstart a chance to parse the file
844
while ! status "$job" >/dev/null 2>&1
848
[ "$count" -eq 5 ] && break
851
# give job a chance to start
853
while [ ! -f "$log_file" ]
857
[ "$count" -eq 5 ] && break
860
[ ! -f "$log_file" ] && \
861
TEST_FAILED "job '$job_name' failed to create logfile"
869
# XXX: note we have to remove carriage returns added by the line
873
log=$(eval "cat $log_file|tr -d '\r' $unique")
874
msg="job '$job_name' failed to log correct data\n"
875
msg="${msg}\texpected regex: '$expected_output'\n"
876
msg="${msg}\tgot : '$log'"
877
cat "$log_file" | egrep "$expected_output" || TEST_FAILED "$msg"
878
elif [ "$use_od" = y ]
880
log=$(eval "cat $log_file|tr -d '\r' $unique|od -x")
881
msg="job '$job_name' failed to log correct data\n"
882
msg="${msg}\texpected hex: '$expected_output'\n"
883
msg="${msg}\tgot : '$log'"
884
[ "$expected_output" != "$log" ] && TEST_FAILED "$msg"
886
log=$(eval "cat $log_file|tr -d '\r' $unique")
887
msg="job '$job_name' failed to log correct data\n"
888
msg="${msg}\texpected text: '$expected_output'\n"
889
msg="${msg}\tgot : '$log'"
890
[ "$expected_output" != "$log" ] && TEST_FAILED "$msg"
895
delete_job "$job_name"
896
rm "$log_file" || TEST_FAILED "unable to remove log file '$log_file'"
900
test_ensure_no_unexpected_output()
902
#---------------------------------------------------------------------
903
feature="ensure command job does not create log file with no console"
904
TEST_FEATURE "$feature"
906
job_name=$(make_job_name "$feature")
910
exec echo hello world"
912
ensure_no_output "$job_name" "$script" ""
914
#---------------------------------------------------------------------
915
feature="ensure 1-line script job does not create log file with no console"
916
TEST_FEATURE "$feature"
918
job_name=$(make_job_name "$feature")
927
ensure_no_output "$job_name" "$script" ""
929
#---------------------------------------------------------------------
930
feature="ensure multi-line script job does not create log file with no console"
931
TEST_FEATURE "$feature"
933
job_name=$(make_job_name "$feature")
943
ensure_no_output "$job_name" "$script" ""
945
#---------------------------------------------------------------------
946
feature="ensure no output if log directory does not exist"
947
TEST_FEATURE "$feature"
949
rmdir "${user_log_dir}" || \
950
TEST_FAILED "unable to delete log directory '$user_log_dir'"
952
job_name=$(make_job_name "$feature")
958
/bin/echo hello world
962
ensure_no_output "$job_name" "$script" ""
964
mkdir "${user_log_dir}" || \
965
TEST_FAILED "unable to recreate log directory '$user_log_dir'"
967
#---------------------------------------------------------------------
968
feature="ensure command job does not create log file with invalid command"
969
TEST_FEATURE "$feature"
971
job_name=$(make_job_name "$feature")
975
exec /this/command/does/not/exist"
977
job="${test_dir_suffix}/${job_name}"
978
create_job "$job_name" "$script"
979
start_job "$job" "$job_name" "" 1
980
check_job_output "$job_name"
981
delete_job "$job_name"
986
# XXX: upstart won't create this
987
mkdir -p "$user_log_dir"
989
test_ensure_no_unexpected_output
995
test_user_job_single_line_script
996
test_user_job_multi_line_script
998
test_user_job_binary_task
999
test_user_job_single_line_script_task
1000
test_user_job_multi_line_script_task
1002
test_user_job_setuid_setgid
1004
test_user_emit_events
1012
echo -n "Running Upstart user session tests as user '`whoami`'"
1013
echo " (uid $uid, gid $gid) in directory '$test_dir'"
1019
echo "All tests completed successfully"
1026
USAGE: $script_name [options]
1030
-a : Actually run this script.
1031
-h : Show this help.
1032
-u <user> : Specify name of test user to create.
1036
Run simple set of Upstart user session tests.
1040
For this test to run, non-root users must be allowed to invoke all D-Bus
1041
methods on Upstart via configuration file:
1043
/etc/dbus-1/system.d/Upstart.conf
1045
See dbus-daemon(1) for further details.
1047
WARNING: Note that this script is unavoidably invasive, so read what
1048
WARNING: follows before running!
1050
If run as a non-root user, this script will create a uniquely-named
1051
subdirectory below "\$HOME/.init/" to run its tests in. On successful
1052
completion of these tests, the unique subdirectory and its contents will
1055
If however, this script is invoked as the root user, the script will
1056
refuse to run until given the name of a test user to create via the "-u"
1057
option. If the user specified to this option already exists, this script
1058
will exit with an error. If the user does not already exist, it will be
1059
created, the script then run *as that user* and assuming successful
1060
completion of the tests, the test user and their home directory will
1066
#---------------------------------------------------------------------
1068
#---------------------------------------------------------------------
1070
while getopts "dhu:" opt
1083
user_to_create="$OPTARG"