#!/usr/bin/env perl

use strict;
use warnings;
use Benchmark qw(:all :hireswallclock);
use lib './support';
use Testcase;
use JSON;
use File::Find;

use Getopt::Long qw( GetOptions :config require_order no_ignore_case );



my $printquick = 1;
my $errors = 0;

###############################################################################
###############################################################################
## Main routine
###############################################################################
###############################################################################

require Cwd;
my $InstallTop=Cwd::abs_path( $0 =~ m{(?:^|/+)[^/]+$} ? ($` || ".") : "." );
my $buildroot= $ENV{MPTOPCOM_BUILD_ROOT} ? "$InstallTop/build.$ENV{MPTOPCOM_BUILD_ROOT}" : "$InstallTop/build";
do "$InstallTop/support/install_utils.pl";
my %ConfigFlags=load_config_file("$buildroot/config.ninja", $InstallTop);
my $soplex = $ConfigFlags{WITH_SOPLEX};

my $full = 0;
my $debug = 0;
my $xml = 0;
my $jdr = ".";
my $buildmode = "Opt";
my $errfile = "/tmp/error$$.log";
my $mpiflags = "";

# enable autoflush for output 
$|++;

# testsuite options
my($jenkins_report, $report_fh, $report_writer, $only, $inverted);
if ( !GetOptions( 'full' => \$full,
                  'xml' => \$xml,
                  'build-mode:s' => \$buildmode,
                  'mpi-flags:s' => \$mpiflags,
                  'jenkins-report-dir:s' => \$jdr,
                  'only:s' => \$only
                ) ) {
   die "usage: perl testsuite.pl [--xml] [--full] [--build-mode=<string=Opt>] [--mpi-flags=<string>] [--jenkins-report-dir] [--only=<string>]\n";
}


# Do not invert test results:
# (We do this sometimes to check whether tests can also fail)
$inverted = 0;
my($ntests, $nfailedtests, $nskippedtests) = (0,0,0);
my %failedtests = ();

my $binDir = "$buildroot/$buildmode/bin";

if($xml){
   use XML::Writer;
   start_testsuite("$jdr/junit_mptopcom");
}

my($mpirun, $nSlots);
init_mpi();

if($soplex){
   print "Using soplex for regularity checks via polymake.\n\n";
}




if($only){
   if(-e "testfiles/quick/$only" || -e "testfiles/extended/$only"){
      if(-e "testfiles/quick/$only"){
         print "Running tests from 'testfiles/quick/$only'.\n";
         run_package("quick.$only", "testfiles/quick/$only");
      }
      if(-e "testfiles/extended/$only"){
         print "Running tests from 'testfiles/extended/$only'.\n";
         run_package("extended.$only", "testfiles/extended/$only");
      }
   } elsif(-e $only){
      my @files;
      find( { wanted => sub { push @files, $_ }, no_chdir => 1 }, $only );
      @files = grep($_ =~ m/test$/, @files);
      foreach my $file (@files){
         print $file,": ";
         run_json_testfile($file, "Run '$only' tests");
         print "\n";
      }
      print "\n";
   } else {
      print "No such folder 'testfiles/[quick|extended]/$only'.\n";
      print "No such file or directory '$only'.\n";
   }
} else {
   if($full){
      print "Running full testsuite.\n";
      print "\n";
      run_testfolder("quick");
      run_testfolder("extended");
   } else {
      print "Running quick testsuite.\n\n";
      run_testfolder("quick");
   }
}



if($xml){
   close_testsuite();
}

print "Ran $ntests tests.";
if($nskippedtests != 0){ print " (Skipped $nskippedtests tests)";}
print "\n\n";
if($nfailedtests == 0){
   print_green("All tests successful\n");
} else {
   foreach my $key (keys %failedtests){
      print "Test in '$key' failed:\n";
      print $failedtests{$key};
      print "\n---\n";
   }
   print_red("$nfailedtests tests failed of $ntests tests.\n");
}

exit(0);



###############################################################################
###############################################################################
## Submethods
###############################################################################
###############################################################################

sub run_testfolder {
   my($prefix) = @_;
   my $folder = "testfiles/$prefix";
   foreach my $packagefolder (glob("$folder/*")) {
      my($package) = $packagefolder =~ m/$folder\/(.*)/;
      $package = "$prefix.$package";
      print "Package: $package\n";
      run_package($package, $packagefolder);
      print "\n";
   }
}

sub run_package {
   my($package, $folder) = @_;
   foreach my $classfolder (glob("$folder/*")) {
      my($classname) = $classfolder =~ m/$folder\/(.*)/;
      $classname = "$package.$classname";
      print "Class: $classname\n";
      if($xml){
         $report_writer->startTag("testsuite", package=>$package, name=>$classname);
      }
      foreach my $testfile (glob("$classfolder/*.test")) {
         run_json_testfile($testfile, $classname);
      }
      print "\n";
      if($xml){
         $report_writer->endTag();
      }
   }
}

sub run_json_testfile {
   my($testfile, $classname) = @_;
   my($testname) = $testfile =~ m/\/?([^\/]*)\.test/;
   my $newTC = Testcase->read_json_file($testfile);
   if(!defined($ConfigFlags{MPICXX}) && $newTC->{BINARY} eq "mptopcom"){
      # Skip mptopcom tests if there is no mpi.
      $nskippedtests++;
      return;
   }
   $ntests++;
   if($xml){
      $report_writer->startTag("testcase", classname=>$classname, name=>$testfile);
   }
   my $cmd = $newTC->assemble_cmd($binDir, $mpirun, $errfile);
   # print "CMD $cmd\n";
   my($output, $error, $errcode) = run_cmd($cmd);
   my($check, $errorMsg) = $newTC->validate($output, $error, $errcode);
   if($check) {
      print_green("$testname ");
   } else {
      report_failed_test($testfile, $errorMsg);
   }
   if($xml){
      $report_writer->endTag();
   }
}

sub report_failed_test {
   my($testfile, $errorMsg) = @_;
   my($testname) = $testfile =~ m/\/?([^\/]*)\.test/;
   my $tmpTC = Testcase->read_json_file($testfile);
   my $cmd = $tmpTC->assemble_cmd($binDir, $mpirun, $errfile);
   my $msg = "Command '$cmd' failed:\n$errorMsg";
   if ($xml) {
      $report_writer->emptyTag("failure", message=>"$msg");
   }
   print_red("$testname ");
   $failedtests{$testfile} = $msg;
   $nfailedtests++;
}

sub run_cmd {
   my($cmd) = @_;
   # print "CMDIN $cmd\n";
   my($output, $error);
   $output = `$cmd 2>$errfile`;
   my $errcode = $?;
   $error = `tail -n 20 $errfile`;
   # print "OUTPUT:\n$output\n";
   # print "ERROR:\n$error\n";
   # print "ERRCODE:$errcode\n";
   # Remove leading line containing version.
   $error =~ s/^(?:.*\n)//;
   unlink $errfile;
   return ($output, $error, $errcode);
}


###############################################################################
# Setup testing environment.
sub init_mpi {
   if(!defined($ConfigFlags{MPICXX})){
      print "MPI was disabled during configuration. Not running mpi tests.\n";
      return;
   }
   $mpirun = defined($ConfigFlags{MPIRUN}) ? $ConfigFlags{MPIRUN} : "mpirun";
   if(defined($ConfigFlags{MPI_PATH})){
      $mpirun = "$ConfigFlags{MPI_PATH}/bin/mpirun";
   }
   my $slotTest = `$mpirun $mpiflags --display-allocation true`;
   print $slotTest,"\n";
   # ompi 1.5.5
   #======================   ALLOCATED NODES   ======================
   #
   # Data for node: cluster-i	Num slots: 1	Max slots: 0
   #
   #=================================================================
   # ompi 3.0.2
   #======================   ALLOCATED NODES   ======================
   #	cluster-i: flags=0x11 slots=8 max_slots=0 slots_inuse=0 state=UP
   #=================================================================
   ($nSlots) = $slotTest =~ m/slots=(\d+) max_slots/;
   ($nSlots) = $slotTest =~ m/Num slots: (\d+)\s+Max slots:/ unless defined($nSlots);
   system("$mpirun --oversubscribe -np 20 echo x > /dev/null 2>&1");
   my $useoversub = ($? == 0);
   if($nSlots < 12 && $useoversub){
      print "Not enough slots available for all tests, will use --oversubscribe.\n";
      $mpirun .= " --oversubscribe";
   }
   $mpirun .= " ".$mpiflags;
}


###############################################################################
# Printing the test result in desired color
sub print_red{
   my($text) = @_;
   $errors++;
   print "\033[91m",$text,"\033[0m";
}

sub print_green{
   my($text) = @_;
   print "\033[92m",$text,"\033[0m";
}


###############################################################################
# XML stuff
sub start_testsuite {
   my ($jenkins_report)=@_;
   my $report_file="${jenkins_report}.xml";
   open $report_fh, ">", $report_file or die "can't create $report_file: $!\n";
   binmode $report_fh, ":utf8";
   $report_writer=new XML::Writer(OUTPUT => $report_fh, NAMESPACES => 0, DATA_MODE => 1, DATA_INDENT => 2, ENCODING => 'utf-8', UNSAFE => 0);
   $report_writer->xmlDecl;
   $report_writer->startTag("testsuites");
}

sub close_testsuite {
   if ($report_writer) {
      $report_writer->endTag("testsuites");
      $report_writer->end;
      undef $report_writer;
      close $report_fh;
      undef $report_fh;
   }
}


