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