+function print_info_from_core_file {
+
+ #### temporary
+ if [ "$ARCH" == "arm64" ]; then
+ echo "Not inspecting core dumps on arm64 at the moment."
+ return
+ fi
+ ####
+
+ local core_file_name=$1
+ local executable_name=$2
+
+ if ! [ -e $executable_name ]; then
+ echo "Unable to find executable $executable_name"
+ return
+ elif ! [ -e $core_file_name ]; then
+ echo "Unable to find core file $core_file_name"
+ return
+ fi
+
+ # Use LLDB to inspect the core dump on Mac, and GDB everywhere else.
+ if [[ "$OSName" == "Darwin" ]]; then
+ hash lldb 2>/dev/null || { echo >&2 "LLDB was not found. Unable to print core file."; return; }
+
+ echo "Printing info from core file $core_file_name"
+ lldb -c $core_file_name -b -o 'bt'
+ else
+ # Use GDB to print the backtrace from the core file.
+ hash gdb 2>/dev/null || { echo >&2 "GDB was not found. Unable to print core file."; return; }
+
+ echo "Printing info from core file $core_file_name"
+ gdb --batch -ex "thread apply all bt full" -ex "quit" $executable_name $core_file_name
+ fi
+}
+
+function download_dumpling_script {
+ echo "Downloading latest version of dumpling script."
+ wget "https://dumpling.azurewebsites.net/api/client/dumpling.py"
+
+ local dumpling_script="dumpling.py"
+ chmod +x $dumpling_script
+}
+
+function upload_core_file_to_dumpling {
+ local core_file_name=$1
+ local dumpling_script="dumpling.py"
+ local dumpling_file="local_dumplings.txt"
+
+ # dumpling requires that the file exist before appending.
+ touch ./$dumpling_file
+
+ if [ ! -x $dumpling_script ]; then
+ download_dumpling_script
+ fi
+
+ if [ ! -x $dumpling_script ]; then
+ echo "Failed to download dumpling script. Dump cannot be uploaded."
+ return
+ fi
+
+ echo "Uploading $core_file_name to dumpling service."
+
+ local paths_to_add=""
+ if [ -d "$coreClrBinDir" ]; then
+ echo "Uploading CoreCLR binaries with dump."
+ paths_to_add=$coreClrBinDir
+ fi
+
+ # Ensure the script has Unix line endings
+ perl -pi -e 's/\r\n|\n|\r/\n/g' "$dumpling_script"
+
+ # The output from this will include a unique ID for this dump.
+ ./$dumpling_script "upload" "--dumppath" "$core_file_name" "--incpaths" $paths_to_add "--properties" "Project=CoreCLR" "--squelch" | tee -a $dumpling_file
+}
+
+function preserve_core_file {
+ local core_file_name=$1
+ local storage_location="/tmp/coredumps_coreclr"
+
+ # Create the directory (this shouldn't fail even if it already exists).
+ mkdir -p $storage_location
+
+ # Only preserve the dump if the directory is empty. Otherwise, do nothing.
+ # This is a way to prevent us from storing/uploading too many dumps.
+ if [ ! "$(ls -A $storage_location)" ]; then
+ echo "Copying core file $core_file_name to $storage_location"
+ cp $core_file_name $storage_location
+
+ upload_core_file_to_dumpling $core_file_name
+ fi
+}
+
+function inspect_and_delete_core_files {
+ # This function prints some basic information from core files in the current
+ # directory and deletes them immediately. Based on the state of the system, it may
+ # also upload a core file to the dumpling service.
+ # (see preserve_core_file).
+
+ # Depending on distro/configuration, the core files may either be named "core"
+ # or "core.<PID>" by default. We will read /proc/sys/kernel/core_uses_pid to
+ # determine which one it is.
+ # On OS X/macOS, we checked the kern.corefile value before enabling core dump
+ # generation, so we know it always includes the PID.
+ local core_name_uses_pid=0
+ if [[ (( -e /proc/sys/kernel/core_uses_pid ) && ( "1" == $(cat /proc/sys/kernel/core_uses_pid) ))
+ || ( "$(uname -s)" == "Darwin" ) ]]; then
+ core_name_uses_pid=1
+ fi
+
+ if [ $core_name_uses_pid == "1" ]; then
+ # We don't know what the PID of the process was, so let's look at all core
+ # files whose name matches core.NUMBER
+ for f in core.*; do
+ [[ $f =~ core.[0-9]+ ]] && print_info_from_core_file "$f" $CORE_ROOT/"corerun" && preserve_core_file "$f" && rm "$f"
+ done
+ elif [ -f core ]; then
+ print_info_from_core_file "core" $CORE_ROOT/"corerun"
+ preserve_core_file "core"
+ rm "core"
+ fi
+}
+
+function run_test {
+ # This function runs in a background process. It should not echo anything, and should not use global variables.
+
+ local scriptFilePath=$1
+ local outputFilePath=$2
+
+ # Switch to directory where the script is
+ cd "$(dirname "$scriptFilePath")"
+
+ local scriptFileName=$(basename "$scriptFilePath")
+ local outputFileName=$(basename "$outputFilePath")
+
+ if [ "$limitedCoreDumps" == "ON" ]; then
+ set_up_core_dump_generation
+ fi
+
+ "./$scriptFileName" >"$outputFileName" 2>&1
+ local testScriptExitCode=$?
+
+ # We will try to print some information from generated core dumps if a debugger
+ # is available, and possibly store a dump in a non-transient location.
+ if [ "$limitedCoreDumps" == "ON" ]; then
+ inspect_and_delete_core_files
+ fi
+
+ return $testScriptExitCode
+}
+
+# Variables for running tests in the background
+if [ `uname` = "NetBSD" ]; then
+ NumProc=$(getconf NPROCESSORS_ONLN)
+elif [ `uname` = "Darwin" ]; then
+ NumProc=$(getconf _NPROCESSORS_ONLN)
+else
+ if [ -x "$(command -v nproc)" ]; then
+ NumProc=$(nproc --all)
+ elif [ -x "$(command -v getconf)" ]; then
+ NumProc=$(getconf _NPROCESSORS_ONLN)
+ else
+ NumProc=1
+ fi
+fi
+((maxProcesses = $NumProc * 3 / 2)) # long tests delay process creation, use a few more processors
+
+((processCount = 0))
+declare -a scriptFilePaths
+declare -a outputFilePaths
+declare -a processIds
+declare -a testStartTimes
+waitProcessIndex=
+pidNone=0
+
+function waitany {
+ local pid
+ local exitcode
+ while true; do
+ for (( i=0; i<$maxProcesses; i++ )); do
+ pid=${processIds[$i]}
+ if [ -z "$pid" ] || [ "$pid" == "$pidNone" ]; then
+ continue
+ fi
+ if ! kill -0 $pid 2>/dev/null; then
+ wait $pid
+ exitcode=$?
+ waitProcessIndex=$i
+ processIds[$i]=$pidNone
+ return $exitcode
+ fi
+ done
+ sleep 0.1
+ done
+}
+
+function get_available_process_index {
+ local pid
+ local i=0
+ for (( i=0; i<$maxProcesses; i++ )); do
+ pid=${processIds[$i]}
+ if [ -z "$pid" ] || [ "$pid" == "$pidNone" ]; then
+ break
+ fi
+ done
+ echo $i
+}
+
+function finish_test {
+ waitany
+ local testScriptExitCode=$?
+ local finishedProcessIndex=$waitProcessIndex
+ ((--processCount))
+
+ local scriptFilePath=${scriptFilePaths[$finishedProcessIndex]}
+ local outputFilePath=${outputFilePaths[$finishedProcessIndex]}
+ local scriptFileName=$(basename "$scriptFilePath")
+
+ local testEndTime=
+ local testRunningTime=
+ local header=
+
+ if ((verbose == 1)); then
+ header=$(printf "[%4d]" $countTotalTests)
+ fi
+
+ if [ "$showTime" == "ON" ]; then
+ testEndTime=$(date +%s)
+ testRunningTime=$(( $testEndTime - ${testStartTimes[$finishedProcessIndex]} ))
+ header=$header$(printf "[%4ds]" $testRunningTime)
+ fi
+
+ local testResult
+ case $testScriptExitCode in
+ 0)
+ let countPassedTests++
+ testResult='Pass'
+ if ((verbose == 1 || runFailingTestsOnly == 1)); then
+ echo "PASSED - ${header}${scriptFilePath}"
+ else
+ echo " - ${header}${scriptFilePath}"
+ fi
+ ;;
+ 2)
+ let countSkippedTests++
+ testResult='Skip'
+ echo "SKIPPED - ${header}${scriptFilePath}"
+ ;;
+ *)
+ let countFailedTests++
+ testResult='Fail'
+ echo "FAILED - ${header}${scriptFilePath}"
+ ;;
+ esac
+ let countTotalTests++
+
+ if ((verbose == 1 || testScriptExitCode != 0)); then
+ while IFS='' read -r line || [ -n "$line" ]; do
+ echo " $line"
+ done <"$outputFilePath"
+ fi
+
+ xunit_output_add_test "$scriptFilePath" "$outputFilePath" "$testResult" "$testScriptExitCode" "$testRunningTime"
+ text_file_output_add_test "$scriptFilePath" "$testResult"
+}
+
+function finish_remaining_tests {
+ # Finish the remaining tests in the order in which they were started
+ while ((processCount > 0)); do
+ finish_test
+ done
+}
+
+function prep_test {
+ local scriptFilePath=$1
+ local scriptFileDir=$(dirname "$scriptFilePath")
+
+ test "$verbose" == 1 && echo "Preparing $scriptFilePath"
+
+ if [ ! "$noLFConversion" == "ON" ]; then
+ # Convert DOS line endings to Unix if needed
+ perl -pi -e 's/\r\n|\n|\r/\n/g' "$scriptFilePath"
+ fi
+
+ # Add executable file mode bit if needed
+ chmod +x "$scriptFilePath"
+
+ #remove any NI and Locks
+ rm -f $scriptFileDir/*.ni.*
+ rm -rf $scriptFileDir/lock
+}
+
+function start_test {
+ local nextProcessIndex=$(get_available_process_index)
+ local scriptFilePath=$1
+ if ((runFailingTestsOnly == 1)) && ! is_failing_test "$scriptFilePath"; then
+ return
+ fi
+
+ # Skip any test that's not in the current playlist, if a playlist was
+ # given to us.
+ if [ -n "$playlistFile" ] && ! is_playlist_test "$scriptFilePath"; then
+ return
+ fi
+
+ if ((nextProcessIndex == maxProcesses)); then
+ finish_test
+ nextProcessIndex=$(get_available_process_index)
+ fi
+
+ scriptFilePaths[$nextProcessIndex]=$scriptFilePath
+ local scriptFileName=$(basename "$scriptFilePath")
+ local outputFilePath=$(dirname "$scriptFilePath")/${scriptFileName}.out
+ outputFilePaths[$nextProcessIndex]=$outputFilePath
+
+ if [ "$showTime" == "ON" ]; then
+ testStartTimes[$nextProcessIndex]=$(date +%s)
+ fi
+
+ test "$verbose" == 1 && echo "Starting $scriptFilePath"
+ if is_unsupported_test "$scriptFilePath"; then
+ skip_unsupported_test "$scriptFilePath" "$outputFilePath" &
+ elif ((runFailingTestsOnly == 0)) && is_failing_test "$scriptFilePath"; then
+ skip_failing_test "$scriptFilePath" "$outputFilePath" &
+ else
+ run_test "$scriptFilePath" "$outputFilePath" &
+ fi
+ processIds[$nextProcessIndex]=$!
+
+ ((++processCount))
+}
+
+# Get a list of directories in which to scan for tests by reading the
+# specified file line by line.
+function set_test_directories {
+ local errorSource='set_test_directories'
+
+ local listFileName=$1
+
+ if [ ! -f "$listFileName" ]
+ then
+ exit_with_error "$errorSource" "Test directories file not found at $listFileName"
+ fi
+ testDirectories=($(read_array "$listFileName"))
+}
+
+function run_tests_in_directory {
+ local testDir=$1
+
+ # Recursively search through directories for .sh files to prepare them.
+ # Note: This needs to occur before any test runs as some of the .sh files
+ # depend on other .sh files
+ for scriptFilePath in $(find "$testDir" -type f -iname '*.sh' | sort)
+ do
+ prep_test "${scriptFilePath:2}"
+ done
+ echo "The tests have been prepared"
+ # Recursively search through directories for .sh files to run.
+ for scriptFilePath in $(find "$testDir" -type f -iname '*.sh' | sort)
+ do
+ start_test "${scriptFilePath:2}"
+ done
+}
+
+function coreclr_code_coverage {
+ local coverageDir="$coverageOutputDir/Coverage"
+ local toolsDir="$coverageOutputDir/Coverage/tools"
+ local reportsDir="$coverageOutputDir/Coverage/reports"
+ local packageName="unix-code-coverage-tools.1.0.0.nupkg"
+
+ rm -rf $coverageDir
+ mkdir -p $coverageDir
+ mkdir -p $toolsDir
+ mkdir -p $reportsDir
+ pushd $toolsDir > /dev/null
+
+ echo "Pulling down code coverage tools"
+ wget -q https://www.myget.org/F/dotnet-buildtools/api/v2/package/unix-code-coverage-tools/1.0.0 -O $packageName
+ echo "Unzipping to $toolsDir"
+ unzip -q -o $packageName
+
+ # Invoke gcovr
+ chmod a+rwx ./gcovr
+ chmod a+rwx ./$OSName/llvm-cov
+
+ echo
+ echo "Generating coreclr code coverage reports at $reportsDir/coreclr.html"
+ echo "./gcovr $coreClrObjs --gcov-executable=$toolsDir/$OS/llvm-cov -r $coreClrSrc --html --html-details -o $reportsDir/coreclr.html"
+ echo
+ ./gcovr $coreClrObjs --gcov-executable=$toolsDir/$OSName/llvm-cov -r $coreClrSrc --html --html-details -o $reportsDir/coreclr.html
+ exitCode=$?
+ popd > /dev/null
+ exit $exitCode
+}
+