| #!/bin/bash |
| # Copyright 2014 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| # Return a list of all currently existing PIDs. |
| getAllPids() { |
| exec ps --no-headers -opid --deselect -C ps |
| } |
| |
| # Poll until the provided PID appears. |
| pollForPid() { |
| local pid=$1 |
| [[ -z "${pid}" ]] && return |
| until kill -0 "${pid}" 2> /dev/null; do |
| sleep .1 |
| done |
| } |
| |
| tearDown() { |
| # TODO(cmasone): Remove ps when http://crbug.com/442027 is sorted. |
| ps -f --forest -u $(id -u) |
| pkill -KILL -e -P $$ |
| echo "------------------------------ Test complete." |
| echo |
| } |
| |
| # Asserts that the given PID is alive. |
| assertPid() { |
| [[ $# -eq 1 ]] || fail "assertPid requires 1 arg, you passed $@" |
| local pid=$1 |
| kill -0 "${pid}" |
| assertEquals "${pid} should not have been killed." 0 $? |
| } |
| |
| readonly ASSERT_NO_PID_TIMEOUT=20000 # Timeout in msec. Chosen arbitrarily. |
| # Asserts that the given PID is not alive. |
| assertNoPid() { |
| [[ $# -eq 1 ]] || fail "assertPid requires 1 arg, you passed $@" |
| local pid=$1 i=0 |
| |
| while [[ ${i} -lt ${ASSERT_NO_PID_TIMEOUT} ]]; do |
| if ! kill -0 "${pid}" 2> /dev/null; then |
| return |
| fi |
| sleep .1 |
| : $(( i += 100 )) |
| echo "Waited ${i} msec for ${pid} to die." |
| done |
| fail "${pid} should not be alive." |
| } |
| |
| # Runs a background process that holds open a file under ${SHUNIT_TMPDIR}. |
| # Passing "untermable" as the sole argument will make this process ignore TERM. |
| # Upon return, the process will be backgrounded and the pid is in $!. |
| runBackgroundProcess() { |
| local untermable |
| if [[ $# -gt 0 && "$1" == "untermable" ]]; then |
| untermable="trap : SIGTERM;" |
| fi |
| local tmpfile=$(mktemp -p "${SHUNIT_TMPDIR}" "killers_$$_XXXXXXXX") |
| touch "${tmpfile}" || fail "Could not create ${tmpfile}" |
| bash -c "exec 3<'${tmpfile}'; ${untermable} kill -STOP \$\$" & |
| pollForPid $! |
| } |
| |
| # Run a test of kill_with_open_files_on. Pass "untermable" as the sole |
| # argument to test terminating processes that ignore SIGTERM. |
| doKillWithOpenFilesTest() { |
| local pid1 pid2 |
| runBackgroundProcess "$@" |
| pid1=$! |
| runBackgroundProcess "$@" |
| pid2=$! |
| |
| kill_with_open_files_on "${SHUNIT_TMPDIR}"/* |
| |
| assertNoPid "${pid1}" |
| assertNoPid "${pid2}" |
| } |
| |
| testKillWithOpenFiles() { |
| doKillWithOpenFilesTest |
| } |
| |
| testKillWithOpenFiles_NonTERMable() { |
| doKillWithOpenFilesTest untermable |
| } |
| |
| testKillWithOpenFiles_EmptyParams() { |
| local before_pids=$(getAllPids) |
| local error_out=$(kill_with_open_files_on "" 2>&1) |
| local after_pids=$(getAllPids) |
| |
| assertNotNull "${before_pids}" |
| assertNotNull "${after_pids}" |
| assertEquals "${error_out}" "${before_pids}" "${after_pids}" |
| } |
| |
| testKillWithOpenFiles_UnresolvedGlob() { |
| local before_pids=$(getAllPids) |
| local error_out=$(kill_with_open_files_on "${SHUNIT_TMPDIR}"/* 2>&1) |
| local after_pids=$(getAllPids) |
| |
| assertNotNull "${before_pids}" |
| assertNotNull "${after_pids}" |
| assertEquals "${error_out}" "${before_pids}" "${after_pids}" |
| } |
| |
| main() { |
| if [[ $# -ne 0 ]]; then |
| echo "Usage: $0" >&2 |
| exit 1 |
| fi |
| echo "Note: Messages like 'Killed bash -c \"...\"' are normal" |
| |
| # Detect whether we're being run inside a pid namespace where we are init. |
| # If not, start such a pid namespace. This is to ensure we don't kill real |
| # processes on the system during tests. |
| if ! grep -Eqs "$0|pid ns: init" /proc/1/cmdline ]] && \ |
| [[ -z ${UNSHARE} ]]; then |
| exec sudo UNSHARE=true unshare -f -p --mount-proc "$0" "$@" |
| fi |
| |
| # Default to the temp dir ($T) that portage has set up for us. |
| # This is needed when doing out of tree builds. |
| TMPDIR="${T:-.}" |
| |
| . ./killers |
| . /usr/bin/shunit2 |
| } |
| |
| main "$@" |