# **********************************************************
# Copyright (c) 2018-2022 Google, Inc. All rights reserved.
# Copyright (c) 2009-2010 VMware, Inc. All rights reserved.
# **********************************************************
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of VMware, Inc. nor the names of its contributors may be
# used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
# DAMAGE.
# For testing apps that need to be running while another action is taken
# in a separate process.
# FIXME i#120: add in all the runall/runalltest.sh tests
# input:
# * cmd = command to run in background that uses run_in_bg, which will
# print the pid to stdout.
# should have intra-arg space=@@ and inter-arg space=@ and ;=!
# * toolbindir
# * out = file where output of background process will be sent
# * pidfile = file where the pid of the background process will be written
# * nudge = arguments to drnudgeunix or drconfig
# * clear = dir to clear ahead of time
# intra-arg space=@@ and inter-arg space=@
string(REGEX REPLACE "@@" " " cmd "${cmd}")
string(REGEX REPLACE "@" ";" cmd "${cmd}")
string(REGEX REPLACE "!" "\\\;" cmd "${cmd}")
# we must remove so we know when the background process has re-created it
file(REMOVE "${out}")
if (NOT "${clear}" STREQUAL "")
# clear out dir
file(GLOB files "${clear}/*")
if (NOT "${files}" STREQUAL "")
file(REMOVE_RECURSE ${files})
endif ()
endif ()
# Remove any stale files.
if (pidfile)
file(REMOVE ${pidfile})
endif ()
file(REMOVE ${out})
# Run the target in the background.
execute_process(COMMAND ${cmd}
RESULT_VARIABLE cmd_result
ERROR_VARIABLE cmd_err
OUTPUT_VARIABLE pid OUTPUT_STRIP_TRAILING_WHITESPACE
TIMEOUT 90)
if (cmd_result)
message(FATAL_ERROR "*** ${cmd} failed (${cmd_result}): ${cmd_err}***\n")
endif (cmd_result)
if (UNIX)
find_program(SLEEP "sleep")
if (NOT SLEEP)
message(FATAL_ERROR "cannot find 'sleep'")
endif ()
set(nudge_cmd drnudgeunix)
else (UNIX)
# We use "ping" on Windows to "sleep" :)
find_program(PING "ping")
if (NOT PING)
message(FATAL_ERROR "cannot find 'ping'")
endif ()
set(nudge_cmd drconfig)
endif (UNIX)
if (UNIX)
set(MAX_ITERS 50000)
else ()
# Sleeping in longer units.
set(MAX_ITERS 1000)
endif()
function (do_sleep ms)
if (UNIX)
execute_process(COMMAND "${SLEEP}" ${ms})
else ()
# XXX: ping's units are secs. For now we always do 1 sec.
# We could try to use cygwin bash or perl.
execute_process(COMMAND ${PING} 127.0.0.1 -n 2 OUTPUT_QUIET)
endif ()
endfunction (do_sleep)
function (kill_background_process force)
if (UNIX)
find_program(KILL "kill")
if (NOT KILL)
message(FATAL_ERROR "cannot find 'kill'")
endif (NOT KILL)
execute_process(COMMAND "${KILL}" ${pid}
RESULT_VARIABLE kill_result
ERROR_VARIABLE kill_err
OUTPUT_VARIABLE kill_out
)
# combine out and err
set(kill_err "${kill_out}${kill_err}")
if (kill_result)
message(FATAL_ERROR "*** kill failed (${kill_result}): ${kill_err}***\n")
endif (kill_result)
else (UNIX)
# win32.infloop has a title with the pid in it so we can uniquely target it
# for a cleaner exit than using drkill.
execute_process(COMMAND "${toolbindir}/closewnd.exe" "Infloop pid=${pid}" 10
RESULT_VARIABLE kill_result
ERROR_VARIABLE kill_err
OUTPUT_VARIABLE kill_out)
set(kill_err "${kill_out}${kill_err}")
if (kill_result)
message(FATAL_ERROR "*** kill failed (${kill_result}): ${kill_err}***\n")
endif (kill_result)
if (force)
# However, if infloop hung before it drew the window, it might still be up.
# Ensure it's not.
execute_process(COMMAND "${toolbindir}/drkill" -pid ${pid})
endif ()
endif (UNIX)
endfunction ()
if (pidfile)
set(iters 0)
while (NOT EXISTS "${pidfile}")
do_sleep(0.1)
math(EXPR iters "${iters}+1")
if (${iters} GREATER ${MAX_ITERS})
kill_background_process(ON)
message(FATAL_ERROR "Timed out waiting for ${pidfile}")
endif ()
endwhile ()
file(READ "${pidfile}" pid)
set(iters 0)
while ("${pid}" STREQUAL "")
do_sleep(0.1)
math(EXPR iters "${iters}+1")
if (${iters} GREATER ${MAX_ITERS})
kill_background_process(ON)
message(FATAL_ERROR "Timed out waiting for ${pidfile} content")
endif ()
file(READ "${pidfile}" pid)
endwhile ()
string(REGEX REPLACE "\n" "" pid ${pid})
endif ()
set(iters 0)
while (NOT EXISTS "${out}")
do_sleep(0.1)
math(EXPR iters "${iters}+1")
if (${iters} GREATER ${MAX_ITERS})
kill_background_process(ON)
message(FATAL_ERROR "Timed out waiting for ${out}")
endif ()
endwhile ()
file(READ "${out}" output)
# we require that all runall tests write at least one line up front
set(iters 0)
while (NOT "${output}" MATCHES "\n")
do_sleep(0.1)
file(READ "${out}" output)
math(EXPR iters "${iters}+1")
if (${iters} GREATER ${MAX_ITERS})
kill_background_process(ON)
message(FATAL_ERROR "Timed out waiting for newline")
endif ()
endwhile()
set(orig_nudge "${nudge}")
if ("${nudge}" MATCHES "<use-persisted>")
# ensure using pcaches, instead of nudging
file(READ "/proc/${pid}/maps" maps)
if (NOT "${maps}" MATCHES "\\.dpc\n")
set(fail_msg "no .dpc files found in ${maps}: not using pcaches!")
endif ()
elseif ("${nudge}" MATCHES "<attach>")
set(nudge_cmd run_in_bg)
string(REGEX REPLACE "<attach>"
"${toolbindir}/drrun@-attach@${pid}@-takeover_sleep@-takeovers@1000"
nudge "${nudge}")
string(REGEX REPLACE "@" ";" nudge "${nudge}")
execute_process(COMMAND "${toolbindir}/${nudge_cmd}" ${nudge}
RESULT_VARIABLE nudge_result
ERROR_VARIABLE nudge_err
OUTPUT_VARIABLE nudge_out
)
# combine out and err
set(nudge_err "${nudge_out}${nudge_err}")
if (nudge_result)
kill_background_process(ON)
message(FATAL_ERROR "*** ${nudge_cmd} failed (${nudge_result}): ${nudge_err}***\n")
endif (nudge_result)
elseif ("${nudge}" MATCHES "<detach>")
set(nudge_cmd run_in_bg)
string(REGEX REPLACE "<detach>"
"${toolbindir}/drrun@-attach@${pid}@-takeover_sleep@-takeovers@1000"
nudge "${nudge}")
string(REGEX REPLACE "@" ";" nudge "${nudge}")
execute_process(COMMAND "${toolbindir}/${nudge_cmd}" ${nudge}
RESULT_VARIABLE nudge_result
ERROR_VARIABLE nudge_err
OUTPUT_VARIABLE nudge_out
)
# Combine out and err.
set(nudge_err "${nudge_out}${nudge_err}")
if (nudge_result)
kill_background_process(ON)
message(FATAL_ERROR "*** ${nudge_cmd} failed (${nudge_result}): ${nudge_err}***\n")
endif (nudge_result)
else ()
# drnudgeunix and drconfig have different syntax:
if (WIN32)
# XXX i#120: expand beyond -client.
string(REGEX REPLACE "-client" "-nudge_timeout;30000;-nudge_pid;${pid}"
nudge "${nudge}")
else ()
set(nudge "-pid;${pid};${nudge}")
endif ()
execute_process(COMMAND "${toolbindir}/${nudge_cmd}" ${nudge}
RESULT_VARIABLE nudge_result
ERROR_VARIABLE nudge_err
OUTPUT_VARIABLE nudge_out
)
# combine out and err
set(nudge_err "${nudge_out}${nudge_err}")
if (nudge_result)
kill_background_process(ON)
message(FATAL_ERROR "*** ${nudge_cmd} failed (${nudge_result}): ${nudge_err}***\n")
endif (nudge_result)
endif ()
if ("${orig_nudge}" MATCHES "-client")
# wait for more output to file
string(LENGTH "${output}" prev_outlen)
file(READ "${out}" output)
string(LENGTH "${output}" new_outlen)
set(iters 0)
while (NOT ${new_outlen} GREATER ${prev_outlen})
do_sleep(0.1)
file(READ "${out}" output)
string(LENGTH "${output}" new_outlen)
math(EXPR iters "${iters}+1")
if (${iters} GREATER ${MAX_ITERS})
kill_background_process(ON)
message(FATAL_ERROR "Timed out waiting for more output")
endif ()
endwhile()
elseif ("${orig_nudge}" MATCHES "<attach>" OR "${orig_nudge}" MATCHES "<detach>")
# Wait until attached.
set(iters 0)
while (NOT "${output}" MATCHES "attach\n")
do_sleep(0.1)
file(READ "${out}" output)
math(EXPR iters "${iters}+1")
if (${iters} GREATER ${MAX_ITERS})
kill_background_process(ON)
message(FATAL_ERROR "Timed out waiting for attach")
endif ()
endwhile()
# Wait until thread init.
set(iters 0)
while (NOT "${output}" MATCHES "thread init\n")
do_sleep(0.1)
file(READ "${out}" output)
math(EXPR iters "${iters}+1")
if (${iters} GREATER ${MAX_ITERS})
kill_background_process(ON)
message(FATAL_ERROR "Timed out waiting for attach")
endif ()
endwhile()
else ()
# for reset or other DR tests there won't be further output
# so we have to guess how long to wait.
# FIXME: should we instead turn on stderr_mask?
do_sleep(0.5)
endif ()
if ("${orig_nudge}" MATCHES "<detach>")
execute_process(COMMAND "${toolbindir}/drconfig.exe" "-detach" ${pid}
RESULT_VARIABLE detach_result
ERROR_VARIABLE detach_err
OUTPUT_VARIABLE detach_out)
set(detach_err "${detach_out}${detach_err}")
if (detach_result)
message(FATAL_ERROR "*** detach failed (${detach_result}): ${detach_err}***\n")
endif (detach_result)
# Wait until detach.
set(iters 0)
while (NOT "${output}" MATCHES "detach\n")
do_sleep(0.1)
file(READ "${out}" output)
math(EXPR iters "${iters}+1")
if (${iters} GREATER ${MAX_ITERS})
kill_background_process(ON)
message(FATAL_ERROR "Timed out waiting for attach")
endif ()
endwhile()
endif()
kill_background_process(OFF)
if (NOT "${fail_msg}" STREQUAL "")
message(FATAL_ERROR "${fail_msg}")
endif ()
# we require that test print "done" as last line once done
file(READ "${out}" output)
set(iters 0)
while (NOT "${output}" MATCHES "\ndone\n")
do_sleep(0.1)
file(READ "${out}" output)
if ("${output}" MATCHES "Internal Error")
string(REGEX MATCH "Application .*Internal Error.*\n" msg "${output}")
message(FATAL_ERROR "Found assert: |${msg}|")
endif ()
math(EXPR iters "${iters}+1")
if (${iters} GREATER ${MAX_ITERS})
message(FATAL_ERROR "Timed out waiting for \"done\"")
endif ()
endwhile()
# message() adds a newline so removing any trailing newline
string(REGEX REPLACE "[ \n]+$" "" output "${output}")
message("${output}")
# Sometimes infloop keeps running: FIXME: figure out why.
if (UNIX)
execute_process(COMMAND "${KILL}" -9 ${pid} ERROR_QUIET OUTPUT_QUIET)
# we can't run pkill b/c there are other tests running infloop (i#1341)
else ()
execute_process(COMMAND "${toolbindir}/DRkill.exe" -pid ${pid} ERROR_QUIET OUTPUT_QUIET)
endif ()