#! /usr/bin/perl ############################################################################### # Copyright (c) 2018 NVIDIA Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ############################################################################### use strict; use warnings; print(`perl --version`); use Getopt::Long; use Cwd; use Cwd "abs_path"; use Config; # For signal names and numbers. use IPC::Open2; use File::Temp; use POSIX "strftime"; my $have_time_hi_res = 0; if (eval { require Time::HiRes }) { printf("#### CONFIG timestamp `gettimeofday`\n"); import Time::HiRes "gettimeofday"; $have_time_hi_res = 1; } else { printf("#### CONFIG timestamp `time`\n"); } sub timestamp() { if ($have_time_hi_res) { return gettimeofday(); } else { return time(); } } my %CmdLineOption; my $arch = ""; my $abi = ""; my $os = ""; my $build = "release"; my $bin_path; my $filecheck_path; my $filecheck_data_path = "internal/test"; my $timeout_min = 15; # https://stackoverflow.com/questions/29862178/name-of-signal-number-2 my @sig_names; @sig_names[ split ' ', $Config{sig_num} ] = split ' ', $Config{sig_name}; my %sig_nums; @sig_nums{ split ' ', $Config{sig_name} } = split ' ', $Config{sig_num}; if (`uname` =~ m/CYGWIN/) { $os = "win32"; } elsif ($^O eq "MSWin32") { $os = "win32"; } else { $os = `uname`; chomp($os); } if ($os eq "win32") { $ENV{'PROCESSOR_ARCHITECTURE'} ||= ""; $ENV{'PROCESSOR_ARCHITEW6432'} ||= ""; if ((lc($ENV{PROCESSOR_ARCHITECTURE}) ne "x86") || (lc($ENV{PROCESSOR_ARCHITECTURE}) eq "amd64") || (lc($ENV{PROCESSOR_ARCHITEW6432}) eq "amd64")) { $arch = "x86_64"; } else { $arch = "i686"; } } else { $arch = `uname -m`; chomp($arch); } sub usage() { printf("Usage: thrust_nightly.pl \n"); printf("Options:\n"); printf(" -help : Print help message\n"); printf(" -forcearch : i686|x86_64|ARMv7|aarch64 (default: $arch)\n"); printf(" -forceabi : Specify abi to be used for arm (gnueabi|gnueabihf)\n"); printf(" -forceos : win32|Linux|Darwin (default: $os)\n"); printf(" -build : (default: debug)\n"); printf(" -bin-path : Specify location of test binaries\n"); printf(" -filecheck-path : Specify location of filecheck binary\n"); printf(" -filecheck-data-path : Specify location of filecheck data (default: $filecheck_data_path)\n"); printf(" -timeout-min : timeout in minutes for each individual test\n"); } GetOptions(\%CmdLineOption, 'help' => sub { usage() and exit 0 }, "forcearch=s" => \$arch, "forceabi=s" => \$abi, "forceos=s" => \$os, "build=s" => \$build, "bin-path=s" => \$bin_path, "filecheck-path=s" => \$filecheck_path, "filecheck-data-path=s" => \$filecheck_data_path, "timeout-min=i" => \$timeout_min, ); my $pwd = getcwd(); my $bin_path_root = abs_path ("${pwd}/.."); if ($arch eq "ARMv7") { if ($abi eq "") { $abi = "_gnueabi"; #Use default abi for arm if not specified } else { $abi = "_${abi}"; } } else { $abi = ""; #Ignore abi for architectures other than arm } my $uname = ""; $uname = $arch; chomp($uname); if (not $bin_path) { $bin_path = "${bin_path_root}/bin/${uname}_${os}${abi}_${build}"; } if (not $filecheck_path) { $filecheck_path = "${bin_path}/nvvm/tools"; } sub process_return_code { my ($name, $ret, $msg) = @_; if ($ret != 0) { my $signal = $ret & 127; my $app_exit = $ret >> 8; my $dumped_core = $ret & 0x80; if (($app_exit != 0) && ($app_exit != 0)) { if ($msg ne "") { printf("#### ERROR $name exited with return value $app_exit. $msg\n"); } else { printf("#### ERROR $name exited with return value $app_exit.\n"); } } if ($signal != 0) { if ($msg ne "") { printf("#### ERROR $name received signal SIG$sig_names[$signal] ($signal). $msg\n"); } else { printf("#### ERROR $name received signal SIG$sig_names[$signal] ($signal).\n"); } if ($sig_nums{'INT'} eq $signal) { die("Terminating testing due to SIGINT."); } } if ($dumped_core != 0) { if ($msg ne "") { printf("#### ERROR $name generated a core dump. $msg\n"); } else { printf("#### ERROR $name generated a core dump.\n"); } } } } my $have_filecheck = 1; sub filecheck_sanity { my $filecheck_cmd = "$filecheck_path/FileCheck $filecheck_data_path/thrust.sanity.filecheck"; my $filecheck_pid = open(my $filecheck_stdin, "|-", "$filecheck_cmd 2>&1"); print $filecheck_stdin "SANITY"; my $filecheck_ret = 0; if (close($filecheck_stdin) == 0) { $filecheck_ret = $?; } if ($filecheck_ret == 0) { printf("#### SANE FileCheck\n"); } else { # Use a temporary file to send the output to # FileCheck so we can get the output this time, # because Perl and bidirectional pipes suck. my $tmp = File::Temp->new(); my $tmp_filename = $tmp->filename; print $tmp "SANITY"; printf("********************************************************************************\n"); print `$filecheck_cmd -input-file $tmp_filename`; printf("********************************************************************************\n"); process_return_code("FileCheck Sanity", $filecheck_ret, ""); printf("#### INSANE FileCheck\n"); $have_filecheck = 0; } } # Wrapper for system that logs the commands so you can see what it did sub run_cmd { my ($cmd) = @_; my $ret = 0; my @executable; my @output; my $syst_cmd; my $start = timestamp(); eval { local $SIG{ALRM} = sub { die("Command timed out (received SIGALRM).\n") }; alarm (60 * $timeout_min); $syst_cmd = $cmd; @executable = split(' ', $syst_cmd, 2); open(my $child, "-|", "$syst_cmd") or die("Could not execute $syst_cmd.\n"); if ($child) { @output = <$child>; } if (close($child) == 0) { $ret = $?; } alarm 0; }; my $elapsed = timestamp() - $start; if ($@) { printf("\n#### ERROR Command timeout reached, killing $executable[0].\n"); system("killall ".$executable[0]); return ($sig_nums{'KILL'}, $elapsed, @output); } return ($ret, $elapsed, @output); } sub current_time { return strftime("%x %X %Z", localtime()); } my $failures = 0; my $known_failures = 0; my $errors = 0; my $passes = 0; sub run_examples { # Get list of tests in binary folder. my $dir = cwd(); chdir $bin_path; my @examplelist; if ($os eq "win32") { @examplelist = glob('thrust.example.*.exe'); } else { @examplelist = glob('thrust.example.*'); } chdir $dir; my $test; foreach $test (@examplelist) { my $test_exe = $test; # Ignore FileCheck files. if ($test =~ /[.]filecheck$/) { next; } if ($os eq "win32") { $test =~ s/\.exe//g; } # Check the test actually exists. if (!-e "${bin_path}/${test_exe}") { next; } my $cmd = "${bin_path}/${test_exe} --verbose 2>&1"; printf("&&&& RUNNING $test\n"); printf("#### CURRENT_TIME " . current_time() . "\n"); my ($ret, $elapsed, @output) = run_cmd($cmd); printf("********************************************************************************\n"); print @output; printf("********************************************************************************\n"); if ($ret != 0) { process_return_code($test, $ret, "Example crash?"); printf("&&&& FAILED $test\n"); printf("#### WALLTIME $test %.2f [s]\n", $elapsed); $errors = $errors + 1; } else { printf("&&&& PASSED $test\n"); printf("#### WALLTIME $test %.2f [s]\n", $elapsed); $passes = $passes + 1; if ($have_filecheck) { # Check output with LLVM FileCheck. printf("&&&& RUNNING FileCheck $test\n"); if (-f "${filecheck_data_path}/${test}.filecheck") { # If the filecheck file is empty, don't use filecheck, just # check if the output file is also empty. if (-z "${filecheck_data_path}/${test}.filecheck") { if (join("", @output) eq "") { printf("&&&& PASSED FileCheck $test\n"); $passes = $passes + 1; } else { printf("#### ERROR Output received but not expected.\n"); printf("&&&& FAILED FileCheck $test\n"); $failures = $failures + 1; } } else { my $filecheck_cmd = "$filecheck_path/FileCheck $filecheck_data_path/$test.filecheck"; my $filecheck_pid = open(my $filecheck_stdin, "|-", "$filecheck_cmd 2>&1"); print $filecheck_stdin @output; my $filecheck_ret = 0; if (close($filecheck_stdin) == 0) { $filecheck_ret = $?; } if ($filecheck_ret == 0) { printf("&&&& PASSED FileCheck $test\n"); $passes = $passes + 1; } else { # Use a temporary file to send the output to # FileCheck so we can get the output this time, # because Perl and bidirectional pipes suck. my $tmp = File::Temp->new(); my $tmp_filename = $tmp->filename; print $tmp @output; printf("********************************************************************************\n"); print `$filecheck_cmd -input-file $tmp_filename`; printf("********************************************************************************\n"); process_return_code("FileCheck $test", $filecheck_ret, ""); printf("&&&& FAILED FileCheck $test\n"); $failures = $failures + 1; } } } else { printf("#### ERROR $test has no FileCheck comparison.\n"); printf("&&&& FAILED FileCheck $test\n"); $errors = $errors + 1; } } } printf("\n"); } } sub run_unit_tests { # Get list of tests in binary folder. my $dir = cwd(); chdir $bin_path; my @unittestlist; if ($os eq "win32") { @unittestlist = glob('thrust.test.*.exe'); } else { @unittestlist = glob('thrust.test.*'); } chdir $dir; my $test; foreach $test (@unittestlist) { my $test_exe = $test; # Ignore FileCheck files. if ($test =~ /[.]filecheck$/) { next; } if ($os eq "win32") { $test =~ s/\.exe//g; } # Check the test actually exists. if (!-e "${bin_path}/${test_exe}") { next; } # Check the test actually exists next unless (-e "${bin_path}/${test_exe}"); my $cmd = "${bin_path}/${test_exe} --verbose 2>&1"; printf("&&&& RUNNING $test\n"); printf("#### CURRENT_TIME " . current_time() . "\n"); my ($ret, $elapsed, @output) = run_cmd($cmd); printf("********************************************************************************\n"); print @output; printf("********************************************************************************\n"); my $fail = 0; my $known_fail = 0; my $error = 0; my $pass = 0; my $found_totals = 0; foreach my $line (@output) { if (($fail, $known_fail, $error, $pass) = $line =~ /Totals: ([0-9]+) failures, ([0-9]+) known failures, ([0-9]+) errors, and ([0-9]+) passes[.]/igs) { $found_totals = 1; $failures = $failures + $fail; $known_failures = $known_failures + $known_fail; $errors = $errors + $error; $passes = $passes + $pass; last; } else { $fail = 0; $known_fail = 0; $error = 0; $pass = 0; } } if ($ret == 0) { if ($found_totals == 0) { $errors = $errors + 1; printf("#### ERROR $test returned 0 and no summary line was found. Invalid test?\n"); printf("&&&& FAILED $test\n"); printf("#### WALLTIME $test %.2f [s]\n", $elapsed); } else { if ($fail != 0 or $error != 0) { $errors = $errors + 1; printf("#### ERROR $test returned 0 and had failures or errors. Test driver error?\n"); printf("&&&& FAILED $test\n"); printf("#### WALLTIME $test %.2f [s]\n", $elapsed); } elsif ($known_fail == 0 and $pass == 0) { printf("#### DISABLED $test returned 0 and had no failures, known failures, errors or passes.\n"); printf("&&&& PASSED $test\n"); printf("#### WALLTIME $test %.2f [s]\n", $elapsed); } else { printf("&&&& PASSED $test\n"); printf("#### WALLTIME $test %.2f [s]\n", $elapsed); if ($have_filecheck) { # Check output with LLVM FileCheck if the test has a FileCheck input. if (-f "${filecheck_data_path}/${test}.filecheck") { printf("&&&& RUNNING FileCheck $test\n"); # If the filecheck file is empty, don't use filecheck, # just check if the output file is also empty. if (! -z "${filecheck_data_path}/${test}.filecheck") { if (@output) { printf("&&&& PASSED FileCheck $test\n"); $passes = $passes + 1; } else { printf("#### Output received but not expected.\n"); printf("&&&& FAILED FileCheck $test\n"); $failures = $failures + 1; } } else { my $filecheck_cmd = "$filecheck_path/FileCheck $filecheck_data_path/$test.filecheck"; my $filecheck_pid = open(my $filecheck_stdin, "|-", "$filecheck_cmd 2>&1"); print $filecheck_stdin @output; my $filecheck_ret = 0; if (close($filecheck_stdin) == 0) { $filecheck_ret = $?; } if ($filecheck_ret == 0) { printf("&&&& PASSED FileCheck $test\n"); $passes = $passes + 1; } else { # Use a temporary file to send the output to # FileCheck so we can get the output this time, # because Perl and bidirectional pipes suck. my $tmp = File::Temp->new(); my $tmp_filename = $tmp->filename; print $tmp @output; printf("********************************************************************************\n"); print `$filecheck_cmd -input-file $tmp_filename`; printf("********************************************************************************\n"); process_return_code("FileCheck $test", $filecheck_ret, ""); printf("&&&& FAILED FileCheck $test\n"); $failures = $failures + 1; } } } } } } } else { $errors = $errors + 1; process_return_code($test, $ret, "Test crash?"); printf("&&&& FAILED $test\n"); printf("#### WALLTIME $test %.2f [s]\n", $elapsed); } printf("\n"); } } sub dvs_summary { my $dvs_score = 0; my $denominator = $failures + $known_failures + $errors + $passes; if ($denominator == 0) { $dvs_score = 0; } else { $dvs_score = 100 * (($passes + $known_failures) / $denominator); } printf("\n"); printf("%*%*%*%* FA!LUR3S $failures\n"); printf("%*%*%*%* KN0WN FA!LUR3S $known_failures\n"); printf("%*%*%*%* 3RR0RS $errors\n"); printf("%*%*%*%* PASS3S $passes\n"); printf("\n"); printf("CUDA DVS BASIC SANITY SCORE : %.1f\n", $dvs_score); if ($failures + $errors > 0) { exit(1); } } ############################################################################### printf("#### CONFIG arch `%s`\n", $arch); printf("#### CONFIG abi `%s`\n", $abi); printf("#### CONFIG os `%s`\n", $os); printf("#### CONFIG build `%s`\n", $build); printf("#### CONFIG bin_path `%s`\n", $bin_path); printf("#### CONFIG have_filecheck `$have_filecheck`\n"); printf("#### CONFIG filecheck_path `%s`\n", $filecheck_path); printf("#### CONFIG filecheck_data_path `%s`\n", $filecheck_data_path); printf("#### CONFIG have_time_hi_res `$have_time_hi_res`\n"); printf("#### CONFIG timeout_min `%s`\n", $timeout_min); printf("#### ENV PATH `%s`\n", defined $ENV{'PATH'} ? $ENV{'PATH'} : ''); printf("#### ENV LD_LIBRARY_PATH `%s`\n", defined $ENV{'LD_LIBRARY_PATH'} ? $ENV{'LD_LIBRARY_PATH'} : ''); printf("\n"); filecheck_sanity(); printf("\n"); my $START_TIME = current_time(); run_examples(); run_unit_tests(); my $STOP_TIME = current_time(); printf("#### START_TIME $START_TIME\n"); printf("#### STOP_TIME $STOP_TIME\n"); dvs_summary();