setup() { load test_helper/common_setup common_setup # shellcheck source=./src/procmon.bash source "${LIB_SRC}/procmon.bash" } @test "should kill processes recursively" { # Note that this is fragile. We need to structure # the process tree such that the parent does not exit # before the child, or the child will be reparented to # init and won't be killed. The defensive thing to do # here is to wait on any child you'd like cleaned before # exiting its parent subshell. ( # each backgrounded process generates two processes: # the process itself, and its subshell. sleep 500 & sl1=$! ( sleep 500 & await $! ) & sh1=$! ( sleep 500 & await $! ) & sh2=$! await $sl1 await $sh1 await $sh2 ) & parent=$! pm_list_descendants "$parent" assert_equal "${#result[@]}" 9 pm_kill_rec "$parent" await "$parent" 5 pm_list_descendants "$parent" # the parent will still show amongst its descendants, # even though it is dead. assert_equal "${#result[@]}" 1 } @test "should not start process monitor twice" { assert_equal "$(pm_state)" "halted" assert pm_start assert_equal "$(pm_state)" "running" refute pm_start assert pm_stop assert_equal "$(pm_state)" "halted" } @test "should not stop the process monitor if it wasn't started" { refute pm_stop } @test "should keep track of process IDs" { assert pm_start pm_known_pids assert [ ${#result[@]} -eq 0 ] ( while [ ! -f "${_pm_output}/sync" ]; do sleep 0.1 done pm_job_exit 0 ) & pm_track_last_job p1=$! ( while [ ! -f "${_pm_output}/sync" ]; do sleep 0.1 done pm_job_exit 0 ) & pm_track_last_job p2=$! pm_known_pids assert [ ${#result[@]} -eq 2 ] touch "${_pm_output}/sync" await "$p1" await "$p2" # This should be more than enough for the process monitor to # catch the exits. The alternative would be implementing temporal # predicates. sleep 1 pm_known_pids assert [ ${#result[@]} -eq 0 ] pm_stop } @test "should stop the monitor and all other processes if one process fails" { assert pm_start ( while [ ! -f "${_pm_output}/sync" ]; do sleep 0.1 done pm_job_exit 1 ) & pm_track_last_job p1=$! ( while [ ! -f "${_pm_output}/sync" ]; do sleep 1 done pm_job_exit 0 ) & pm_track_last_job p2=$! touch "${_pm_output}/sync" await "$p1" await "$p2" pm_join 3 assert_equal "$(pm_state)" "halted_process_failure" } @test "should no longer track a process if requested" { assert pm_start ( while true; do sleep 1 done pm_job_exit 1 ) & pid1=$! pm_track_last_job pm_stop_tracking $pid1 kill -SIGKILL $pid1 await "$pid1" # Again, we need to allow time for the procmon # to pick up on the kill. sleep 1 pm_stop pm_join 3 assert_equal "$(pm_state)" "halted" } @test "should call lifecycle callbacks when processes start and stop" { callback() { local event="$1" proc_type="$2" pid="$3" exit_code if [ "$event" = "start" ]; then touch "${_pm_output}/${pid}-${proc_type}-start" elif [ "$event" = "exit" ]; then exit_code="$4" touch "${_pm_output}/${pid}-${proc_type}-${exit_code}-exit" fi } pm_register_callback "sleepy" "callback" pm_start pid1=$(pm_async sleep 0.1 -%- "sleepy") pid2=$(pm_async sleep 0.1 -%- "sleepy") pid3=$(pm_async sleep 0.1 -%- "awake") await "$pid1" await "$pid2" await "$pid3" pm_stop assert_equal "$(pm_state)" "halted" assert [ -f "${_pm_output}/${pid1}-sleepy-start" ] assert [ -f "${_pm_output}/${pid1}-sleepy-0-exit" ] assert [ -f "${_pm_output}/${pid1}-sleepy-start" ] assert [ -f "${_pm_output}/${pid2}-sleepy-0-exit" ] assert [ ! -f "${_pm_output}/${pid3}-awake-start" ] assert [ ! -f "${_pm_output}/${pid3}-awake-0-exit" ] }