Update CoreClr, PgoData to preview1-26004-01, master-20171204-0047, respectively...
[platform/upstream/coreclr.git] / tests / runtest.sh
1 #!/usr/bin/env bash
2
3 function print_usage {
4     echo ''
5     echo 'CoreCLR test runner script.'
6     echo ''
7     echo 'Typical command line:'
8     echo ''
9     echo 'coreclr/tests/runtest.sh'
10     echo '    --testRootDir="temp/Windows_NT.x64.Debug"'
11     echo '    --testNativeBinDir="coreclr/bin/obj/Linux.x64.Debug/tests"'
12     echo '    --coreClrBinDir="coreclr/bin/Product/Linux.x64.Debug"'
13     echo '    --mscorlibDir="windows/coreclr/bin/Product/Linux.x64.Debug"'
14     echo '    --coreFxBinDir="corefx/bin/runtime/netcoreapp-Linux-Debug-x64'
15     echo ''
16     echo 'Required arguments:'
17     echo '  --testRootDir=<path>             : Root directory of the test build (e.g. coreclr/bin/tests/Windows_NT.x64.Debug).'
18     echo '  --testNativeBinDir=<path>        : Directory of the native CoreCLR test build (e.g. coreclr/bin/obj/Linux.x64.Debug/tests).'
19     echo '  (Also required: Either --coreOverlayDir, or all of the switches --coreOverlayDir overrides)'
20     echo ''
21     echo 'Optional arguments:'
22     echo '  --coreOverlayDir=<path>          : Directory containing core binaries and test dependencies. If not specified, the'
23     echo '                                     default is testRootDir/Tests/coreoverlay. This switch overrides --coreClrBinDir,'
24     echo '                                     --mscorlibDir, and --coreFxBinDir.'
25     echo '  --coreClrBinDir=<path>           : Directory of the CoreCLR build (e.g. coreclr/bin/Product/Linux.x64.Debug).'
26     echo '  --mscorlibDir=<path>             : Directory containing the built mscorlib.dll. If not specified, it is expected to be'
27     echo '                                       in the directory specified by --coreClrBinDir.'
28     echo '  --coreFxBinDir="<path>"          : Directory with CoreFX build outputs'
29     echo '                                     (e.g. "corefx/bin/runtime/netcoreapp-Linux-Debug-x64")'
30     echo '                                     If files with the same name are present in multiple directories, the first one wins.'
31     echo '  --testDir=<path>                 : Run tests only in the specified directory. The path is relative to the directory'
32     echo '                                     specified by --testRootDir. Multiple of this switch may be specified.'
33     echo '  --testDirFile=<path>             : Run tests only in the directories specified by the file at <path>. Paths are listed'
34     echo '                                     one line, relative to the directory specified by --testRootDir.'
35     echo '  --build-overlay-only             : Build coreoverlay only, and skip running tests.'
36     echo '  --runFailingTestsOnly            : Run only the tests that are disabled on this platform due to unexpected failures.'
37     echo '                                     Failing tests are listed in coreclr/tests/failingTestsOutsideWindows.txt, one per'
38     echo '                                     line, as paths to .sh files relative to the directory specified by --testRootDir.'
39     echo '  --disableEventLogging            : Disable the events logged by both VM and Managed Code'
40     echo '  --sequential                     : Run tests sequentially (default is to run in parallel).'
41     echo '  --playlist=<path>                : Run only the tests that are specified in the file at <path>, in the same format as'
42     echo '                                     runFailingTestsOnly'
43     echo '  -v, --verbose                    : Show output from each test.'
44     echo '  -h|--help                        : Show usage information.'
45     echo '  --useServerGC                    : Enable server GC for this test run'
46     echo '  --test-env                       : Script to set environment variables for tests'
47     echo '  --crossgen                       : Precompiles the framework managed assemblies'
48     echo '  --runcrossgentests               : Runs the ready to run tests' 
49     echo '  --jitstress=<n>                  : Runs the tests with COMPlus_JitStress=n'
50     echo '  --jitstressregs=<n>              : Runs the tests with COMPlus_JitStressRegs=n'
51     echo '  --jitminopts                     : Runs the tests with COMPlus_JITMinOpts=1'
52     echo '  --jitforcerelocs                 : Runs the tests with COMPlus_ForceRelocs=1'
53     echo '  --jitdisasm                      : Runs jit-dasm on the tests'
54     echo '  --gcstresslevel=<n>              : Runs the tests with COMPlus_GCStress=n'
55     echo '  --gcname=<n>                     : Runs the tests with COMPlus_GCName=n'
56     echo '  --ilasmroundtrip                 : Runs ilasm round trip on the tests'
57     echo '    0: None                                1: GC on all allocs and '"'easy'"' places'
58     echo '    2: GC on transitions to preemptive GC  4: GC on every allowable JITed instr'
59     echo '    8: GC on every allowable NGEN instr   16: GC only on a unique stack trace'
60     echo '  --long-gc                        : Runs the long GC tests'
61     echo '  --gcsimulator                    : Runs the GCSimulator tests'
62     echo '  --tieredcompilation              : Runs the tests with COMPlus_EXPERIMENTAL_TieredCompilation=1'
63     echo '  --link <ILlink>                  : Runs the tests after linking via ILlink'
64     echo '  --show-time                      : Print execution sequence and running time for each test'
65     echo '  --no-lf-conversion               : Do not execute LF conversion before running test script'
66     echo '  --limitedDumpGeneration          : Enables the generation of a limited number of core dumps if test(s) crash, even if ulimit'
67     echo '                                     is zero when launching this script. This option is intended for use in CI.'
68     echo '  --xunitOutputPath=<path>         : Create xUnit XML report at the specifed path (default: <test root>/coreclrtests.xml)'
69     echo ''
70     echo 'Runtime Code Coverage options:'
71     echo '  --coreclr-coverage               : Optional argument to get coreclr code coverage reports'
72     echo '  --coreclr-objs=<path>            : Location of root of the object directory'
73     echo '                                     containing the linux/mac coreclr build'
74     echo '  --coreclr-src=<path>             : Location of root of the directory'
75     echo '                                     containing the coreclr source files'
76     echo '  --coverage-output-dir=<path>     : Directory where coverage output will be written to'
77     echo ''
78 }
79
80 function print_results {
81     echo ""
82     echo "======================="
83     echo "     Test Results"
84     echo "======================="
85     echo "# CoreCLR Bin Dir  : $coreClrBinDir"
86     echo "# Tests Discovered : $countTotalTests"
87     echo "# Passed           : $countPassedTests"
88     echo "# Failed           : $countFailedTests"
89     echo "# Skipped          : $countSkippedTests"
90     echo "======================="
91 }
92
93 # Initialize counters for bookkeeping.
94 countTotalTests=0
95 countPassedTests=0
96 countFailedTests=0
97 countSkippedTests=0
98
99 # Variables for xUnit-style XML output. XML format: https://xunit.github.io/docs/format-xml-v2.html
100 xunitOutputPath=
101 xunitTestOutputPath=
102
103 # libExtension determines extension for dynamic library files
104 # runtimeName determines where CoreFX Runtime files will be located
105 OSName=$(uname -s)
106 libExtension=
107 case $OSName in
108     Darwin)
109         libExtension="dylib"
110         ;;
111
112     Linux)
113         libExtension="so"
114         ;;
115
116     NetBSD)
117         libExtension="so"
118         ;;
119
120     *)
121         echo "Unsupported OS $OSName detected, configuring as if for Linux"
122         libExtension="so"
123         ;;
124 esac
125
126 function xunit_output_begin {
127     if [ -z "$xunitOutputPath" ]; then
128         xunitOutputPath=$testRootDir/coreclrtests.xml
129     fi
130     if ! [ -e $(basename "$xunitOutputPath") ]; then
131         xunitOutputPath=$testRootDir/coreclrtests.xml
132     fi
133     xunitTestOutputPath=${xunitOutputPath}.test
134     if [ -e "$xunitOutputPath" ]; then
135         rm -f -r "$xunitOutputPath"
136     fi
137     if [ -e "$xunitTestOutputPath" ]; then
138         rm -f -r "$xunitTestOutputPath"
139     fi
140 }
141
142 function xunit_output_add_test {
143     # <assemblies>
144     #   <assembly>
145     #     <collection>
146     #       <test .../> <!-- Write this element here -->
147
148     local scriptFilePath=$1
149     local outputFilePath=$2
150     local testResult=$3 # Pass, Fail, or Skip
151     local testScriptExitCode=$4
152     local testRunningTime=$5
153
154     local testPath=${scriptFilePath%.sh} # Remove trailing ".sh"
155     local testDir=$(dirname "$testPath")
156     local testName=$(basename "$testPath")
157
158     # Replace '/' with '.'
159     testPath=$(echo "$testPath" | tr / .)
160     testDir=$(echo "$testDir" | tr / .)
161
162     local line
163
164     line="      "
165     line="${line}<test"
166     line="${line} name=\"${testPath}\""
167     line="${line} type=\"${testDir}\""
168     line="${line} method=\"${testName}\""
169     line="${line} result=\"${testResult}\""
170     if [ -n "$testRunningTime" ] && [ "$testResult" != "Skip" ]; then
171         line="${line} time=\"${testRunningTime}\""
172     fi
173
174     if [ "$testResult" == "Pass" ]; then
175         line="${line}/>"
176         echo "$line" >>"$xunitTestOutputPath"
177         return
178     fi
179
180     line="${line}>"
181     echo "$line" >>"$xunitTestOutputPath"
182
183     line="        "
184     if [ "$testResult" == "Skip" ]; then
185         line="${line}<reason><![CDATA[$(cat "$outputFilePath")]]></reason>"
186         echo "$line" >>"$xunitTestOutputPath"
187     else
188         line="${line}<failure exception-type=\"Exit code: ${testScriptExitCode}\">"
189         echo "$line" >>"$xunitTestOutputPath"
190
191         line="          "
192         line="${line}<message>"
193         echo "$line" >>"$xunitTestOutputPath"
194         line="            "
195         line="${line}<![CDATA["
196         echo "$line" >>"$xunitTestOutputPath"
197         cat "$outputFilePath" >>"$xunitTestOutputPath"
198         line="            "
199         line="${line}]]>"
200         echo "$line" >>"$xunitTestOutputPath"
201         line="          "
202         line="${line}</message>"
203         echo "$line" >>"$xunitTestOutputPath"
204
205         line="        "
206         line="${line}</failure>"
207         echo "$line" >>"$xunitTestOutputPath"
208     fi
209
210     line="      "
211     line="${line}</test>"
212     echo "$line" >>"$xunitTestOutputPath"
213 }
214
215 function xunit_output_end {
216     local errorSource=$1
217     local errorMessage=$2
218
219     local errorCount
220     if [ -z "$errorSource" ]; then
221         ((errorCount = 0))
222     else
223         ((errorCount = 1))
224     fi
225
226     echo '<?xml version="1.0" encoding="utf-8"?>' >>"$xunitOutputPath"
227     echo '<assemblies>' >>"$xunitOutputPath"
228
229     local line
230
231     # <assembly ...>
232     line="  "
233     line="${line}<assembly"
234     line="${line} name=\"CoreClrTestAssembly\""
235     line="${line} total=\"${countTotalTests}\""
236     line="${line} passed=\"${countPassedTests}\""
237     line="${line} failed=\"${countFailedTests}\""
238     line="${line} skipped=\"${countSkippedTests}\""
239     line="${line} errors=\"${errorCount}\""
240     line="${line}>"
241     echo "$line" >>"$xunitOutputPath"
242
243     # <collection ...>
244     line="    "
245     line="${line}<collection"
246     line="${line} name=\"CoreClrTestCollection\""
247     line="${line} total=\"${countTotalTests}\""
248     line="${line} passed=\"${countPassedTests}\""
249     line="${line} failed=\"${countFailedTests}\""
250     line="${line} skipped=\"${countSkippedTests}\""
251     line="${line}>"
252     echo "$line" >>"$xunitOutputPath"
253
254     # <test .../> <test .../> ...
255     if [ -f "$xunitTestOutputPath" ]; then
256         cat "$xunitTestOutputPath" >>"$xunitOutputPath"
257         rm -f "$xunitTestOutputPath"
258     fi
259
260     # </collection>
261     line="    "
262     line="${line}</collection>"
263     echo "$line" >>"$xunitOutputPath"
264
265     if [ -n "$errorSource" ]; then
266         # <errors>
267         line="    "
268         line="${line}<errors>"
269         echo "$line" >>"$xunitOutputPath"
270
271         # <error ...>
272         line="      "
273         line="${line}<error"
274         line="${line} type=\"TestHarnessError\""
275         line="${line} name=\"${errorSource}\""
276         line="${line}>"
277         echo "$line" >>"$xunitOutputPath"
278
279         # <failure .../>
280         line="        "
281         line="${line}<failure>${errorMessage}</failure>"
282         echo "$line" >>"$xunitOutputPath"
283
284         # </error>
285         line="      "
286         line="${line}</error>"
287         echo "$line" >>"$xunitOutputPath"
288
289         # </errors>
290         line="    "
291         line="${line}</errors>"
292         echo "$line" >>"$xunitOutputPath"
293     fi
294
295     # </assembly>
296     line="  "
297     line="${line}</assembly>"
298     echo "$line" >>"$xunitOutputPath"
299
300     # </assemblies>
301     echo '</assemblies>' >>"$xunitOutputPath"
302 }
303
304 function exit_with_error {
305     local errorSource=$1
306     local errorMessage=$2
307     local printUsage=$3
308
309     if [ -z "$printUsage" ]; then
310         ((printUsage = 0))
311     fi
312
313     echo "$errorMessage"
314     xunit_output_end "$errorSource" "$errorMessage"
315     if ((printUsage != 0)); then
316         print_usage
317     fi
318     exit $EXIT_CODE_EXCEPTION
319 }
320
321 # Handle Ctrl-C. We will stop execution and print the results that
322 # we gathered so far.
323 function handle_ctrl_c {
324     local errorSource='handle_ctrl_c'
325
326     echo ""
327     echo "*** Stopping... ***"
328     print_results
329     exit_with_error "$errorSource" "Test run aborted by Ctrl+C."
330 }
331
332 # Register the Ctrl-C handler
333 trap handle_ctrl_c INT
334
335 function create_core_overlay {
336     local errorSource='create_core_overlay'
337     local printUsage=1
338
339     if [ -n "$coreOverlayDir" ]; then
340         export CORE_ROOT="$coreOverlayDir"
341         return
342     fi
343
344     # Check inputs to make sure we have enough information to create the core layout. $testRootDir/Tests/Core_Root should
345     # already exist and contain test dependencies that are not built.
346     local testDependenciesDir=$testRootDir/Tests/Core_Root
347     if [ ! -d "$testDependenciesDir" ]; then
348         exit_with_error "$errorSource" "Did not find the test dependencies directory: $testDependenciesDir"
349     fi
350     if [ -z "$coreClrBinDir" ]; then
351         exit_with_error "$errorSource" "One of --coreOverlayDir or --coreClrBinDir must be specified." "$printUsage"
352     fi
353     if [ ! -d "$coreClrBinDir" ]; then
354         exit_with_error "$errorSource" "Directory specified by --coreClrBinDir does not exist: $coreClrBinDir"
355     fi
356     if [ -z "$coreFxBinDir" ]; then
357         exit_with_error "$errorSource" "One of --coreOverlayDir or --coreFxBinDir must be specified." "$printUsage"
358     fi
359
360     # Create the overlay
361     coreOverlayDir=$testRootDir/Tests/coreoverlay
362     export CORE_ROOT="$coreOverlayDir"
363     if [ -e "$coreOverlayDir" ]; then
364         rm -f -r "$coreOverlayDir"
365     fi
366     mkdir "$coreOverlayDir"
367
368     cp -f -v "$coreFxBinDir/"* "$coreOverlayDir/" 2>/dev/null
369     cp -f -p -v "$coreClrBinDir/"* "$coreOverlayDir/" 2>/dev/null
370     if [ -d "$mscorlibDir/bin" ]; then
371         cp -f -v "$mscorlibDir/bin/"* "$coreOverlayDir/" 2>/dev/null
372     fi
373     cp -f -v "$testDependenciesDir/"xunit* "$coreOverlayDir/" 2>/dev/null
374     cp -n -v "$testDependenciesDir/"* "$coreOverlayDir/" 2>/dev/null
375     if [ -f "$coreOverlayDir/mscorlib.ni.dll" ]; then
376         # Test dependencies come from a Windows build, and mscorlib.ni.dll would be the one from Windows
377         rm -f "$coreOverlayDir/mscorlib.ni.dll"
378     fi
379     if [ -f "$coreOverlayDir/System.Private.CoreLib.ni.dll" ]; then
380         # Test dependencies come from a Windows build, and System.Private.CoreLib.ni.dll would be the one from Windows
381         rm -f "$coreOverlayDir/System.Private.CoreLib.ni.dll"
382     fi
383     copy_test_native_bin_to_test_root
384 }
385
386 declare -a skipCrossGenFiles
387
388 function is_skip_crossgen_test {
389     for skip in "${skipCrossGenFiles[@]}"; do
390         if [ "$1" == "$skip" ]; then
391             return 0
392         fi
393     done
394     return 1
395 }
396
397 function precompile_overlay_assemblies {
398     skipCrossGenFiles=($(read_array "$(dirname "$0")/skipCrossGenFiles.$ARCH.txt"))
399
400     if [ $doCrossgen == 1 ]; then
401         local overlayDir=$CORE_ROOT
402
403         filesToPrecompile=$(find -L $overlayDir -iname \*.dll -not -iname \*.ni.dll -not -iname \*-ms-win-\* -type f )
404         for fileToPrecompile in ${filesToPrecompile}
405         do
406             local filename=${fileToPrecompile}
407             if [ $jitdisasm == 1 ]; then
408                 $overlayDir/corerun $overlayDir/jit-dasm.dll --crossgen $overlayDir/crossgen --platform $overlayDir --output $testRootDir/dasm $filename
409                 local exitCode=$?
410                 if [ $exitCode != 0 ]; then
411                     echo Unable to generate dasm for $filename
412                 fi
413             else
414                 if is_skip_crossgen_test "$(basename $filename)"; then
415                     continue
416                 fi
417                 echo Precompiling $filename
418                 $overlayDir/crossgen /Platform_Assemblies_Paths $overlayDir $filename 1> $filename.stdout 2>$filename.stderr
419                 local exitCode=$?
420                 if [[ $exitCode != 0 ]]; then
421                     if grep -q -e '0x80131018' $filename.stderr; then
422                         printf "\n\t$filename is not a managed assembly.\n\n"
423                     else
424                         echo Unable to precompile $filename.
425                         cat $filename.stdout
426                         cat $filename.stderr
427                         exit $exitCode
428                     fi
429                 else
430                     rm $filename.{stdout,stderr}
431                 fi
432             fi
433         done
434     else
435         echo Skipping crossgen of FX assemblies.
436     fi
437 }
438
439 function copy_test_native_bin_to_test_root {
440     local errorSource='copy_test_native_bin_to_test_root'
441
442     if [ -z "$testNativeBinDir" ]; then
443         exit_with_error "$errorSource" "--testNativeBinDir is required."
444     fi
445     testNativeBinDir=$testNativeBinDir/src
446     if [ ! -d "$testNativeBinDir" ]; then
447         exit_with_error "$errorSource" "Directory specified by --testNativeBinDir does not exist: $testNativeBinDir"
448     fi
449
450     # Copy native test components from the native test build into the respective test directory in the test root directory
451     find "$testNativeBinDir" -type f -iname '*.$libExtension' |
452         while IFS='' read -r filePath || [ -n "$filePath" ]; do
453             local dirPath=$(dirname "$filePath")
454             local destinationDirPath=${testRootDir}${dirPath:${#testNativeBinDir}}
455             if [ ! -d "$destinationDirPath" ]; then
456                 exit_with_error "$errorSource" "Cannot copy native test bin '$filePath' to '$destinationDirPath/', as the destination directory does not exist."
457             fi
458             cp -f "$filePath" "$destinationDirPath/"
459         done
460 }
461
462 # Variables for unsupported and failing tests
463 declare -a unsupportedTests
464 declare -a failingTests
465 declare -a playlistTests
466 ((runFailingTestsOnly = 0))
467
468 # Get an array of items by reading the specified file line by line.
469 function read_array {
470     local theArray=()
471
472     # bash in Mac OS X doesn't support 'readarray', so using alternate way instead.
473     # readarray -t theArray < "$1"
474     while IFS='' read -r line || [ -n "$line" ]; do
475         theArray[${#theArray[@]}]=$line
476     done < "$1"
477     echo ${theArray[@]}
478 }
479
480 function load_unsupported_tests {
481     # Load the list of tests that are not supported on this platform. These tests are disabled (skipped) permanently.
482     unsupportedTests=($(read_array "$(dirname "$0")/testsUnsupportedOutsideWindows.txt"))
483     if [ "$ARCH" == "arm" ]; then
484         unsupportedTests+=($(read_array "$(dirname "$0")/testsUnsupportedOnARM32.txt"))
485     fi
486 }
487
488 function load_failing_tests {
489     # Load the list of tests that fail on this platform. These tests are disabled (skipped) temporarily, pending investigation.
490     failingTests=($(read_array "$(dirname "$0")/testsFailingOutsideWindows.txt"))
491    
492     if [ "$ARCH" == "arm64" ]; then
493         failingTests+=($(read_array "$(dirname "$0")/testsFailingOnArm64.txt"))
494     fi
495 }
496
497 function load_playlist_tests {
498     # Load the list of tests that are enabled as a part of this test playlist.
499     playlistTests=($(read_array "${playlistFile}"))
500 }
501
502 function is_unsupported_test {
503     for unsupportedTest in "${unsupportedTests[@]}"; do
504         if [ "$1" == "$unsupportedTest" ]; then
505             return 0
506         fi
507     done
508     return 1
509 }
510
511 function is_failing_test {
512     for failingTest in "${failingTests[@]}"; do
513         if [ "$1" == "$failingTest" ]; then
514             return 0
515         fi
516     done
517     return 1
518 }
519
520 function is_playlist_test {
521     for playlistTest in "${playlistTests[@]}"; do
522         if [ "$1" == "$playlistTest" ]; then
523             return 0
524         fi
525     done
526     return 1
527 }
528
529 function skip_unsupported_test {
530     # This function runs in a background process. It should not echo anything, and should not use global variables. This
531     # function is analogous to run_test, and causes the test to be skipped with the message below.
532
533     local scriptFilePath=$1
534     local outputFilePath=$2
535
536     echo "Not supported on this platform." >"$outputFilePath"
537     return 2 # skip the test
538 }
539
540 function skip_failing_test {
541     # This function runs in a background process. It should not echo anything, and should not use global variables. This
542     # function is analogous to run_test, and causes the test to be skipped with the message below.
543
544     local scriptFilePath=$1
545     local outputFilePath=$2
546
547     echo "Temporarily disabled on this platform due to unexpected failures." >"$outputFilePath"
548     return 2 # skip the test
549 }
550
551 function skip_non_playlist_test {
552     # This function runs in a background process. It should not echo anything, and should not use global variables. This
553     # function is analogous to run_test, and causes the test to be skipped with the message below.
554
555     local scriptFilePath=$1
556     local outputFilePath=$2
557
558     echo "Test is not included in the running playlist." >"$outputFilePath"
559     return 2 # skip the test
560 }
561
562 function set_up_core_dump_generation {
563     # We will only enable dump generation here if we're on Mac or Linux
564     if [[ ! ( "$(uname -s)" == "Darwin" || "$(uname -s)" == "Linux" ) ]]; then
565         return
566     fi
567
568     # We won't enable dump generation on OS X/macOS if the machine hasn't been
569     # configured with the kern.corefile pattern we expect.
570     if [[ ( "$(uname -s)" == "Darwin" && "$(sysctl -n kern.corefile)" != "core.%P" ) ]]; then
571         echo "WARNING: Core dump generation not being enabled due to unexpected kern.corefile value."
572         return
573     fi
574
575     # Allow dump generation
576     ulimit -c unlimited
577
578     if [ "$(uname -s)" == "Linux" ]; then
579         if [ -e /proc/self/coredump_filter ]; then
580             # Include memory in private and shared file-backed mappings in the dump.
581             # This ensures that we can see disassembly from our shared libraries when
582             # inspecting the contents of the dump. See 'man core' for details.
583             echo 0x3F > /proc/self/coredump_filter
584         fi
585     fi
586 }
587
588 function print_info_from_core_file {
589
590     #### temporary
591     if [ "$ARCH" == "arm64" ]; then
592         echo "Not inspecting core dumps on arm64 at the moment."
593         return
594     fi
595     ####
596
597     local core_file_name=$1
598     local executable_name=$2
599
600     if ! [ -e $executable_name ]; then
601         echo "Unable to find executable $executable_name"
602         return
603     elif ! [ -e $core_file_name ]; then
604         echo "Unable to find core file $core_file_name"
605         return
606     fi
607
608     # Use LLDB to inspect the core dump on Mac, and GDB everywhere else.
609     if [[ "$OSName" == "Darwin" ]]; then
610         hash lldb 2>/dev/null || { echo >&2 "LLDB was not found. Unable to print core file."; return; }
611
612         echo "Printing info from core file $core_file_name"
613         lldb -c $core_file_name -b -o 'bt'
614     else
615         # Use GDB to print the backtrace from the core file.
616         hash gdb 2>/dev/null || { echo >&2 "GDB was not found. Unable to print core file."; return; }
617
618         echo "Printing info from core file $core_file_name"
619         gdb --batch -ex "thread apply all bt full" -ex "quit" $executable_name $core_file_name
620     fi
621 }
622
623 function download_dumpling_script {
624     echo "Downloading latest version of dumpling script."
625     wget "https://dumpling.azurewebsites.net/api/client/dumpling.py"
626
627     local dumpling_script="dumpling.py"
628     chmod +x $dumpling_script
629 }
630
631 function upload_core_file_to_dumpling {
632     local core_file_name=$1
633     local dumpling_script="dumpling.py"
634     local dumpling_file="local_dumplings.txt"
635
636     # dumpling requires that the file exist before appending.
637     touch ./$dumpling_file
638
639     if [ ! -x $dumpling_script ]; then
640         download_dumpling_script
641     fi
642
643     if [ ! -x $dumpling_script ]; then
644         echo "Failed to download dumpling script. Dump cannot be uploaded."
645         return
646     fi
647
648     echo "Uploading $core_file_name to dumpling service."
649
650     local paths_to_add=""
651     if [ -d "$coreClrBinDir" ]; then
652         echo "Uploading CoreCLR binaries with dump."
653         paths_to_add=$coreClrBinDir
654     fi
655
656     # Ensure the script has Unix line endings
657     perl -pi -e 's/\r\n|\n|\r/\n/g' "$dumpling_script"
658
659     # The output from this will include a unique ID for this dump.
660     ./$dumpling_script "upload" "--dumppath" "$core_file_name" "--incpaths" $paths_to_add "--properties" "Project=CoreCLR" "--squelch" | tee -a $dumpling_file
661 }
662
663 function preserve_core_file {
664     local core_file_name=$1
665     local storage_location="/tmp/coredumps_coreclr"
666
667     # Create the directory (this shouldn't fail even if it already exists).
668     mkdir -p $storage_location
669
670     # Only preserve the dump if the directory is empty. Otherwise, do nothing.
671     # This is a way to prevent us from storing/uploading too many dumps.
672     if [ ! "$(ls -A $storage_location)" ]; then
673         echo "Copying core file $core_file_name to $storage_location"
674         cp $core_file_name $storage_location
675
676         upload_core_file_to_dumpling $core_file_name
677     fi
678 }
679
680 function inspect_and_delete_core_files {
681     # This function prints some basic information from core files in the current
682     # directory and deletes them immediately. Based on the state of the system, it may
683     # also upload a core file to the dumpling service.
684     # (see preserve_core_file).
685     
686     # Depending on distro/configuration, the core files may either be named "core"
687     # or "core.<PID>" by default. We will read /proc/sys/kernel/core_uses_pid to 
688     # determine which one it is.
689     # On OS X/macOS, we checked the kern.corefile value before enabling core dump
690     # generation, so we know it always includes the PID.
691     local core_name_uses_pid=0
692     if [[ (( -e /proc/sys/kernel/core_uses_pid ) && ( "1" == $(cat /proc/sys/kernel/core_uses_pid) )) 
693           || ( "$(uname -s)" == "Darwin" ) ]]; then
694         core_name_uses_pid=1
695     fi
696
697     if [ $core_name_uses_pid == "1" ]; then
698         # We don't know what the PID of the process was, so let's look at all core
699         # files whose name matches core.NUMBER
700         for f in core.*; do
701             [[ $f =~ core.[0-9]+ ]] && print_info_from_core_file "$f" $CORE_ROOT/"corerun" && preserve_core_file "$f" && rm "$f"
702         done
703     elif [ -f core ]; then
704         print_info_from_core_file "core" $CORE_ROOT/"corerun"
705         preserve_core_file "core"
706         rm "core"
707     fi
708 }
709
710 function run_test {
711     # This function runs in a background process. It should not echo anything, and should not use global variables.
712
713     local scriptFilePath=$1
714     local outputFilePath=$2
715
716     # Switch to directory where the script is
717     cd "$(dirname "$scriptFilePath")"
718
719     local scriptFileName=$(basename "$scriptFilePath")
720     local outputFileName=$(basename "$outputFilePath")
721
722     if [ "$limitedCoreDumps" == "ON" ]; then
723         set_up_core_dump_generation
724     fi
725
726     "./$scriptFileName" >"$outputFileName" 2>&1
727     local testScriptExitCode=$?
728
729     # We will try to print some information from generated core dumps if a debugger
730     # is available, and possibly store a dump in a non-transient location.
731     if [ "$limitedCoreDumps" == "ON" ]; then
732         inspect_and_delete_core_files
733     fi
734
735     return $testScriptExitCode
736 }
737
738 # Variables for running tests in the background
739 if [ `uname` = "NetBSD" ]; then
740     NumProc=$(getconf NPROCESSORS_ONLN)
741 elif [ `uname` = "Darwin" ]; then
742     NumProc=$(getconf _NPROCESSORS_ONLN)
743 else
744     if [ -x "$(command -v nproc)" ]; then
745         NumProc=$(nproc --all)
746     elif [ -x "$(command -v getconf)" ]; then
747         NumProc=$(getconf _NPROCESSORS_ONLN)
748     else
749         NumProc=1
750     fi
751 fi
752 ((maxProcesses = $NumProc * 3 / 2)) # long tests delay process creation, use a few more processors
753
754 ((processCount = 0))
755 declare -a scriptFilePaths
756 declare -a outputFilePaths
757 declare -a processIds
758 declare -a testStartTimes
759 waitProcessIndex=
760 pidNone=0
761
762 function waitany {
763     local pid
764     local exitcode
765     while true; do
766         for (( i=0; i<$maxProcesses; i++ )); do
767             pid=${processIds[$i]}
768             if [ -z "$pid" ] || [ "$pid" == "$pidNone" ]; then
769                 continue
770             fi
771             if ! kill -0 $pid 2>/dev/null; then
772                 wait $pid
773                 exitcode=$?
774                 waitProcessIndex=$i
775                 processIds[$i]=$pidNone
776                 return $exitcode
777             fi
778         done
779         sleep 0.1
780     done
781 }
782
783 function get_available_process_index {
784     local pid
785     local i=0
786     for (( i=0; i<$maxProcesses; i++ )); do
787         pid=${processIds[$i]}
788         if [ -z "$pid" ] || [ "$pid" == "$pidNone" ]; then
789             break
790         fi
791     done
792     echo $i
793 }
794
795 function finish_test {
796     waitany
797     local testScriptExitCode=$?
798     local finishedProcessIndex=$waitProcessIndex
799     ((--processCount))
800
801     local scriptFilePath=${scriptFilePaths[$finishedProcessIndex]}
802     local outputFilePath=${outputFilePaths[$finishedProcessIndex]}
803     local scriptFileName=$(basename "$scriptFilePath")
804
805     local testEndTime=
806     local testRunningTime=
807     local header=
808
809     if ((verbose == 1)); then
810         header=$(printf "[%4d]" $countTotalTests)
811     fi
812
813     if [ "$showTime" == "ON" ]; then
814         testEndTime=$(date +%s)
815         testRunningTime=$(( $testEndTime - ${testStartTimes[$finishedProcessIndex]} ))
816         header=$header$(printf "[%4ds]" $testRunningTime)
817     fi
818
819     local xunitTestResult
820     case $testScriptExitCode in
821         0)
822             let countPassedTests++
823             xunitTestResult='Pass'
824             if ((verbose == 1 || runFailingTestsOnly == 1)); then
825                 echo "PASSED   - ${header}${scriptFilePath}"
826             else
827                 echo "         - ${header}${scriptFilePath}"
828             fi
829             ;;
830         2)
831             let countSkippedTests++
832             xunitTestResult='Skip'
833             echo "SKIPPED  - ${header}${scriptFilePath}"
834             ;;
835         *)
836             let countFailedTests++
837             xunitTestResult='Fail'
838             echo "FAILED   - ${header}${scriptFilePath}"
839             ;;
840     esac
841     let countTotalTests++
842
843     if ((verbose == 1 || testScriptExitCode != 0)); then
844         while IFS='' read -r line || [ -n "$line" ]; do
845             echo "               $line"
846         done <"$outputFilePath"
847     fi
848
849     xunit_output_add_test "$scriptFilePath" "$outputFilePath" "$xunitTestResult" "$testScriptExitCode" "$testRunningTime"
850 }
851
852 function finish_remaining_tests {
853     # Finish the remaining tests in the order in which they were started
854     while ((processCount > 0)); do
855         finish_test
856     done
857 }
858
859 function prep_test {
860     local scriptFilePath=$1
861     local scriptFileDir=$(dirname "$scriptFilePath")
862
863     test "$verbose" == 1 && echo "Preparing $scriptFilePath"
864
865     if [ ! "$noLFConversion" == "ON" ]; then
866         # Convert DOS line endings to Unix if needed
867         perl -pi -e 's/\r\n|\n|\r/\n/g' "$scriptFilePath"
868     fi
869         
870     # Add executable file mode bit if needed
871     chmod +x "$scriptFilePath"
872
873     #remove any NI and Locks
874     rm -f $scriptFileDir/*.ni.*
875     rm -rf $scriptFileDir/lock
876 }
877
878 function start_test {
879     local nextProcessIndex=$(get_available_process_index)
880     local scriptFilePath=$1
881     if ((runFailingTestsOnly == 1)) && ! is_failing_test "$scriptFilePath"; then
882         return
883     fi
884
885     # Skip any test that's not in the current playlist, if a playlist was
886     # given to us.
887     if [ -n "$playlistFile" ] && ! is_playlist_test "$scriptFilePath"; then
888         return
889     fi
890
891     if ((nextProcessIndex == maxProcesses)); then
892         finish_test
893         nextProcessIndex=$(get_available_process_index)
894     fi
895
896     scriptFilePaths[$nextProcessIndex]=$scriptFilePath
897     local scriptFileName=$(basename "$scriptFilePath")
898     local outputFilePath=$(dirname "$scriptFilePath")/${scriptFileName}.out
899     outputFilePaths[$nextProcessIndex]=$outputFilePath
900
901     if [ "$showTime" == "ON" ]; then
902         testStartTimes[$nextProcessIndex]=$(date +%s)
903     fi
904
905     test "$verbose" == 1 && echo "Starting $scriptFilePath"
906     if is_unsupported_test "$scriptFilePath"; then
907         skip_unsupported_test "$scriptFilePath" "$outputFilePath" &
908     elif ((runFailingTestsOnly == 0)) && is_failing_test "$scriptFilePath"; then
909         skip_failing_test "$scriptFilePath" "$outputFilePath" &
910     else
911         run_test "$scriptFilePath" "$outputFilePath" &
912     fi
913     processIds[$nextProcessIndex]=$!
914
915     ((++processCount))
916 }
917
918 # Get a list of directories in which to scan for tests by reading the
919 # specified file line by line.
920 function set_test_directories {
921     local errorSource='set_test_directories'
922
923     local listFileName=$1
924
925     if [ ! -f "$listFileName" ]
926     then
927         exit_with_error "$errorSource" "Test directories file not found at $listFileName"
928     fi
929     testDirectories=($(read_array "$listFileName"))
930 }
931
932 function run_tests_in_directory {
933     local testDir=$1
934
935     # Recursively search through directories for .sh files to prepare them.
936     # Note: This needs to occur before any test runs as some of the .sh files
937     # depend on other .sh files
938     for scriptFilePath in $(find "$testDir" -type f -iname '*.sh' | sort)
939     do
940         prep_test "${scriptFilePath:2}"
941     done
942     echo "The tests have been prepared"
943     # Recursively search through directories for .sh files to run.
944     for scriptFilePath in $(find "$testDir" -type f -iname '*.sh' | sort)
945     do
946         start_test "${scriptFilePath:2}"
947     done
948 }
949
950 function coreclr_code_coverage {
951     local coverageDir="$coverageOutputDir/Coverage"
952     local toolsDir="$coverageOutputDir/Coverage/tools"
953     local reportsDir="$coverageOutputDir/Coverage/reports"
954     local packageName="unix-code-coverage-tools.1.0.0.nupkg"
955
956     rm -rf $coverageDir
957     mkdir -p $coverageDir
958     mkdir -p $toolsDir
959     mkdir -p $reportsDir
960     pushd $toolsDir > /dev/null
961
962     echo "Pulling down code coverage tools"
963     wget -q https://www.myget.org/F/dotnet-buildtools/api/v2/package/unix-code-coverage-tools/1.0.0 -O $packageName
964     echo "Unzipping to $toolsDir"
965     unzip -q -o $packageName
966
967     # Invoke gcovr
968     chmod a+rwx ./gcovr
969     chmod a+rwx ./$OSName/llvm-cov
970
971     echo
972     echo "Generating coreclr code coverage reports at $reportsDir/coreclr.html"
973     echo "./gcovr $coreClrObjs --gcov-executable=$toolsDir/$OS/llvm-cov -r $coreClrSrc --html --html-details -o $reportsDir/coreclr.html"
974     echo
975     ./gcovr $coreClrObjs --gcov-executable=$toolsDir/$OSName/llvm-cov -r $coreClrSrc --html --html-details -o $reportsDir/coreclr.html
976     exitCode=$?
977     popd > /dev/null
978     exit $exitCode
979 }
980
981 function check_cpu_architecture {
982     local CPUName=$(uname -m)
983     local __arch=
984
985     case $CPUName in
986         i686)
987             __arch=x86
988             ;;
989         x86_64)
990             __arch=x64
991             ;;
992         armv7l)
993             __arch=arm
994             ;;
995         aarch64)
996             __arch=arm64
997             ;;
998         *)
999             echo "Unknown CPU $CPUName detected, configuring as if for x64"
1000             __arch=x64
1001             ;;
1002     esac
1003
1004     echo "$__arch"
1005 }
1006
1007 ARCH=$(check_cpu_architecture)
1008 echo "Running on  CPU- $ARCH"
1009
1010 # Exit code constants
1011 readonly EXIT_CODE_SUCCESS=0       # Script ran normally.
1012 readonly EXIT_CODE_EXCEPTION=1     # Script exited because something exceptional happened (e.g. bad arguments, Ctrl-C interrupt).
1013 readonly EXIT_CODE_TEST_FAILURE=2  # Script completed successfully, but one or more tests failed.
1014
1015 # Argument variables
1016 testRootDir=
1017 testNativeBinDir=
1018 coreOverlayDir=
1019 coreClrBinDir=
1020 mscorlibDir=
1021 coreFxBinDir=
1022 coreClrObjs=
1023 coreClrSrc=
1024 coverageOutputDir=
1025 testEnv=
1026 playlistFile=
1027 showTime=
1028 noLFConversion=
1029 buildOverlayOnly=
1030 gcsimulator=
1031 longgc=
1032 limitedCoreDumps=
1033 illinker=
1034 ((disableEventLogging = 0))
1035 ((serverGC = 0))
1036
1037 # Handle arguments
1038 verbose=0
1039 doCrossgen=0
1040 jitdisasm=0
1041 ilasmroundtrip=
1042
1043 for i in "$@"
1044 do
1045     case $i in
1046         -h|--help)
1047             print_usage
1048             exit $EXIT_CODE_SUCCESS
1049             ;;
1050         -v|--verbose)
1051             verbose=1
1052             ;;
1053         --crossgen)
1054             doCrossgen=1
1055             ;;
1056         --jitstress=*)
1057             export COMPlus_JitStress=${i#*=}
1058             ;;
1059         --jitstressregs=*)
1060             export COMPlus_JitStressRegs=${i#*=}
1061             ;;
1062         --jitminopts)
1063             export COMPlus_JITMinOpts=1
1064             ;;
1065         --jitforcerelocs)
1066             export COMPlus_ForceRelocs=1
1067             ;;
1068         --link=*)
1069             export ILLINK=${i#*=}
1070             export DoLink=true
1071             ;;
1072         --tieredcompilation)
1073             export COMPlus_EXPERIMENTAL_TieredCompilation=1
1074             ;;
1075         --jitdisasm)
1076             jitdisasm=1
1077             ;;
1078         --ilasmroundtrip)
1079             ((ilasmroundtrip = 1))
1080             ;;
1081         --testRootDir=*)
1082             testRootDir=${i#*=}
1083             ;;
1084         --testNativeBinDir=*)
1085             testNativeBinDir=${i#*=}
1086             ;;
1087         --coreOverlayDir=*)
1088             coreOverlayDir=${i#*=}
1089             ;;
1090         --coreClrBinDir=*)
1091             coreClrBinDir=${i#*=}
1092             ;;
1093         --mscorlibDir=*)
1094             mscorlibDir=${i#*=}
1095             ;;
1096         --coreFxBinDir=*)
1097             coreFxBinDir=${i#*=}
1098             ;;
1099         --testDir=*)
1100             testDirectories[${#testDirectories[@]}]=${i#*=}
1101             ;;
1102         --testDirFile=*)
1103             set_test_directories "${i#*=}"
1104             ;;
1105         --runFailingTestsOnly)
1106             ((runFailingTestsOnly = 1))
1107             ;;
1108         --disableEventLogging)
1109             ((disableEventLogging = 1))
1110             ;;
1111         --runcrossgentests)
1112             export RunCrossGen=1
1113             ;;
1114         --sequential)
1115             ((maxProcesses = 1))
1116             ;;
1117         --useServerGC)
1118             ((serverGC = 1))
1119             ;;
1120         --long-gc)
1121             ((longgc = 1))
1122             ;;
1123         --gcsimulator)
1124             ((gcsimulator = 1))
1125             ;;
1126         --playlist=*)
1127             playlistFile=${i#*=}
1128             ;;
1129         --coreclr-coverage)
1130             CoreClrCoverage=ON
1131             ;;
1132         --coreclr-objs=*)
1133             coreClrObjs=${i#*=}
1134             ;;
1135         --coreclr-src=*)
1136             coreClrSrc=${i#*=}
1137             ;;
1138         --coverage-output-dir=*)
1139             coverageOutputDir=${i#*=}
1140             ;;
1141         --test-env=*)
1142             testEnv=${i#*=}
1143             ;;            
1144         --gcstresslevel=*)
1145             export COMPlus_GCStress=${i#*=}
1146             ;;            
1147         --gcname=*)
1148             export COMPlus_GCName=${i#*=}
1149             ;;
1150         --show-time)
1151             showTime=ON
1152             ;;
1153         --no-lf-conversion)
1154             noLFConversion=ON
1155             ;;
1156         --build-overlay-only)
1157             buildOverlayOnly=ON
1158             ;;
1159         --limitedDumpGeneration)
1160             limitedCoreDumps=ON
1161             ;;
1162         --xunitOutputPath=*)
1163             xunitOutputPath=${i#*=}
1164             ;;
1165         *)
1166             echo "Unknown switch: $i"
1167             print_usage
1168             exit $EXIT_CODE_SUCCESS
1169             ;;
1170     esac
1171 done
1172
1173 if [ -n "$coreOverlayDir" ] && [ "$buildOverlayOnly" == "ON" ]; then
1174     echo "Can not use \'--coreOverlayDir=<path>\' and \'--build-overlay-only\' at the same time."
1175     exit $EXIT_CODE_EXCEPTION
1176 fi
1177
1178 if ((disableEventLogging == 0)); then
1179     export COMPlus_EnableEventLog=1
1180 fi
1181
1182 export CORECLR_SERVER_GC="$serverGC"
1183
1184 if [ -z "$testRootDir" ]; then
1185     echo "--testRootDir is required."
1186     print_usage
1187     exit $EXIT_CODE_EXCEPTION
1188 fi
1189 if [ ! -d "$testRootDir" ]; then
1190     echo "Directory specified by --testRootDir does not exist: $testRootDir"
1191     exit $EXIT_CODE_EXCEPTION
1192 fi
1193
1194 # Copy native interop test libraries over to the mscorlib path in
1195 # order for interop tests to run on linux.
1196 if [ -z "$mscorlibDir" ]; then
1197     mscorlibDir=$coreClrBinDir
1198 fi
1199
1200 if [ ! -z "$longgc" ]; then
1201     echo "Running Long GC tests"
1202     export RunningLongGCTests=1
1203 fi
1204
1205 if [ ! -z "$gcsimulator" ]; then
1206     echo "Running GC simulator tests"
1207     export RunningGCSimulatorTests=1
1208 fi
1209
1210 if [[ ! "$jitdisasm" -eq 0 ]]; then
1211     echo "Running jit disasm"
1212     export RunningJitDisasm=1
1213 fi
1214
1215 if [ ! -z "$ilasmroundtrip" ]; then
1216     echo "Running Ilasm round trip"
1217     export RunningIlasmRoundTrip=1
1218 fi
1219
1220 # If this is a coverage run, make sure the appropriate args have been passed
1221 if [ "$CoreClrCoverage" == "ON" ]
1222 then
1223     echo "Code coverage is enabled for this run"
1224     echo ""
1225     if [ ! "$OSName" == "Darwin" ] && [ ! "$OSName" == "Linux" ]
1226     then
1227         echo "Code Coverage not supported on $OS"
1228         exit 1
1229     fi
1230
1231     if [ -z "$coreClrObjs" ]
1232     then
1233         echo "Coreclr obj files are required to generate code coverage reports"
1234         echo "Coreclr obj files root path can be passed using '--coreclr-obj' argument"
1235         exit 1
1236     fi
1237
1238     if [ -z "$coreClrSrc" ]
1239     then
1240         echo "Coreclr src files are required to generate code coverage reports"
1241         echo "Coreclr src files root path can be passed using '--coreclr-src' argument"
1242         exit 1
1243     fi
1244
1245     if [ -z "$coverageOutputDir" ]
1246     then
1247         echo "Output directory for coverage results must be specified"
1248         echo "Output path can be specified '--coverage-output-dir' argument"
1249         exit 1
1250     fi
1251 fi
1252
1253 xunit_output_begin
1254 create_core_overlay
1255 precompile_overlay_assemblies
1256
1257 if [ "$buildOverlayOnly" == "ON" ];
1258 then
1259     echo "Build overlay directory '$coreOverlayDir' complete."
1260     exit 0
1261 fi
1262
1263 if [ -n "$playlistFile" ]
1264 then
1265     # Use a playlist file exclusively, if it was provided
1266     echo "Executing playlist $playlistFile"
1267     load_playlist_tests
1268 else
1269     load_unsupported_tests
1270     load_failing_tests
1271 fi
1272
1273 # Other architectures are not supported yet.
1274 if [ "$ARCH" == "x64" ]
1275 then
1276     scriptPath=$(dirname $0)
1277     ${scriptPath}/setup-stress-dependencies.sh --outputDir=$coreOverlayDir
1278 else
1279     if [ "$ARCH" != "arm64" ]
1280     then
1281         echo "Skip preparing for GC stress test. Dependent package is not supported on this architecture."
1282     fi
1283 fi
1284
1285 export __TestEnv=$testEnv
1286
1287 cd "$testRootDir"
1288
1289 dumplingsListPath="$testRootDir/dumplings.txt"
1290
1291 # clean up any existing dumpling remnants from previous runs.
1292 rm -f "$dumplingsListPath"
1293 find $testRootDir -type f -name "local_dumplings.txt" -exec rm {} \;
1294
1295 time_start=$(date +"%s")
1296 if [ -z "$testDirectories" ]
1297 then
1298     # No test directories were specified, so run everything in the current
1299     # directory and its subdirectories.
1300     run_tests_in_directory "."
1301 else
1302     # Otherwise, run all the tests in each specified test directory.
1303     for testDir in "${testDirectories[@]}"
1304     do
1305         if [ ! -d "$testDir" ]; then
1306             echo "Test directory does not exist: $testDir"
1307         else
1308             run_tests_in_directory "./$testDir"
1309         fi
1310     done
1311 fi
1312 finish_remaining_tests
1313
1314 print_results
1315
1316 find $testRootDir -type f -name "local_dumplings.txt" -exec cat {} \; > $dumplingsListPath
1317
1318 if [ -s $dumplingsListPath ]; then
1319     cat $dumplingsListPath
1320 else
1321     rm $dumplingsListPath
1322 fi
1323
1324 time_end=$(date +"%s")
1325 time_diff=$(($time_end-$time_start))
1326 echo "$(($time_diff / 60)) minutes and $(($time_diff % 60)) seconds taken to run CoreCLR tests."
1327
1328 xunit_output_end
1329
1330 if [ "$CoreClrCoverage" == "ON" ]
1331 then
1332     coreclr_code_coverage
1333 fi
1334
1335 if ((countFailedTests > 0)); then
1336     exit $EXIT_CODE_TEST_FAILURE
1337 fi
1338
1339 exit $EXIT_CODE_SUCCESS