#!/usr/bin/env perl

## @file testing-reporter.pl
# @par DESCRIPTION
# Report testing results that are stored in the results database.

use strict;
our $VERSION = sprintf "%d", q$Revision: $ =~ /(\d+)/g;

use Net::SMTP;

# Global variables.
our $g_smtp_server="hale.ifa.hawaii.edu"; # Email SMTP server.
our $g_email_from="nobody\@schmopc00.ifa.hawaii.edu"; # Email sender.
our $g_total_test_count=0; # Total number of tests with pass or fail result.
our $g_body=""; # Body of text for report.
our $opt_TO=$ARGV[0];

# Connect to database.
use DBI;
my $dbuser="root";
my $dbpass=get_db_password();
my $dbname="psmops_testing_results";
my $dbhost="schmopc01.ifa.hawaii.edu";
my $dbconnect="DBI:mysql:$dbname:$dbhost";
my $dbh=DBI->connect($dbconnect,$dbuser,$dbpass) || die "ERROR: Cannot connect to database $dbname.";

our %g_test_run_ids=();

# Retrieve unit test results to be reported that are pass or fail.

# Select all test results to be reported.
my $sql="
SELECT
unit_test_results.id,
unit_test_results.test_run,
unit_test_results.number,
unit_test_results.psmops_function,
unit_test_results.module,
unit_test_results.cvs_version,
unit_test_results.cvs_time,
unit_test_results.test,
unit_test_results.modifier1,
unit_test_results.modifier2,
unit_test_results.result,
unit_test_results.wall,
unit_test_results.cpu
FROM
test_runs
Inner Join unit_test_results ON unit_test_results.test_run = test_runs.id
WHERE
test_runs.report =  1
AND
(unit_test_results.result='P' OR unit_test_results.result='F')
ORDER BY time ASC";

my $sth=$dbh->prepare($sql);
my $result=$sth->execute();
my $unit_tests=$sth->fetchall_arrayref();
$g_total_test_count=$#{@{$unit_tests}}+1;

# Process unit test results.
foreach my $row (@{$unit_tests}){
    $g_test_run_ids{$row->[1]}=1; # Record existing Test Run IDs as hash keys.
}

# Create report.
my $subject="subject: PS-MOPS Testing Results\n";
my $from="from: \"PS-MOPS Testing System\" <nobody\@schmopc00.ifa.hawaii.edu>\n";
my $reply_to="reply-to: ps-mops-dev\@ifa.hawaii.edu\n";
my $to="to: $opt_TO\n";
$g_body.="This is an automated email reporting testing results from the PS-MOPS Testing System.\n";

if($g_total_test_count>0){ report_unit_testing(); }
else { $g_body.="\nThere are no unit test results to be reported.\n"; }

$g_body.="\nDetails available on schmopc00.ifa.hawaii.edu using http://localhost:81/testing/ or http://sponge:81/testing/ as the URL.\n";

print $g_body;

# Email report.
if($opt_TO ne "STDOUT"){
    print "Sending email to $opt_TO...\n";

    my $smtp=Net::SMTP->new($g_smtp_server,
                            Hello=>'schmopc00.ifa.hawaii.edu',
                            Timeout=>60);

    $smtp->mail($g_email_from);
    $smtp->recipient($opt_TO);
    $smtp->data;
    $smtp->datasend($from);
    $smtp->datasend($to);
    $smtp->datasend($subject);
    $smtp->datasend("\n");
    $smtp->datasend($g_body);
    $smtp->dataend;
    $smtp->quit;

    update_report_status();
}

# Retrieve DB password.
sub get_db_password {
    open(FILE,"$ENV{HOME}/.psmops_rootpw");
    my @data=<FILE>;
    close(FILE);
    if($data[0]=~s/\s+//g){};
    if($data[0]=~s/\n//g){};
    return $data[0];
}

# Update report status. Set report field to 0 for all Test Runs that were reported.
sub update_report_status {
    foreach my $test_run_id (sort keys %g_test_run_ids){
        print STDERR "Updating report status for Test Run $test_run_id...\n";
        my $sql="update test_runs set report=0 where id=$test_run_id";
        my $sth=$dbh->prepare($sql);
        my $result=$sth->execute();
    }
}

# Report results from unit testing.
sub report_unit_testing {
    # Iterate over Test Run IDs and process each Test Run.
    foreach my $test_run_id (sort keys %g_test_run_ids){
    
        # Retrieve Test Run data.
        my $sql="select id,DATE_FORMAT(time,'%Y-%b-%d %a %H:%i'),type,log_filename
                 from test_runs where id=$test_run_id";
        my $sth=$dbh->prepare($sql);
        my $result=$sth->execute();
        my $data=$sth->fetchall_arrayref();
        my $test_run_time=$data->[0]->[1];
        my $testing_log_path=$data->[0]->[3];
        my $testing_log_data=read_log_file($testing_log_path);
    
        # Retrieve wall time for Test Run.
        $sql="
        SELECT
        sum(unit_test_results.wall)
        FROM
        test_runs
        Inner Join unit_test_results ON unit_test_results.test_run = test_runs.id
        WHERE
        test_runs.report =  1 AND
        (unit_test_results.result='P' OR unit_test_results.result='F')
        AND test_runs.id=$test_run_id";
    
        $sth=$dbh->prepare($sql);
        $result=$sth->execute();
        $data=$sth->fetchall_arrayref();
        my $wall_time=sprintf("%.5f",($data->[0]->[0]));
    
        $sql="
        SELECT
        unit_test_results.id,
        unit_test_results.test_run,
        unit_test_results.number,
        unit_test_results.psmops_function,
        unit_test_results.module,
        unit_test_results.cvs_version,
        unit_test_results.cvs_time,
        unit_test_results.test,
        unit_test_results.modifier1,
        unit_test_results.modifier2,
        unit_test_results.result,
        unit_test_results.wall,
        unit_test_results.cpu
        FROM
        test_runs
        Inner Join unit_test_results ON unit_test_results.test_run = test_runs.id
        WHERE
        test_runs.report=1
        AND
        (unit_test_results.result='P' OR unit_test_results.result='F')
        ORDER BY time ASC";
    
        my $sth=$dbh->prepare($sql);
        my $result=$sth->execute();
        my $unit_tests=$sth->fetchall_arrayref();
        my $test_count=$#{@{$unit_tests}}+1;
    
        ## @note If $test_count == -1 then no result set was obtained.
    
        # Retrieve only failed tests to be reported.
        $sql="
        SELECT
        unit_test_results.id,
        unit_test_results.test_run,
        unit_test_results.number,
        unit_test_results.psmops_function,
        unit_test_results.module,
        unit_test_results.cvs_version,
        unit_test_results.cvs_time,
        unit_test_results.test,
        unit_test_results.modifier1,
        unit_test_results.modifier2,
        unit_test_results.result,
        unit_test_results.wall,
        unit_test_results.cpu
        FROM
        test_runs
        Inner Join unit_test_results ON unit_test_results.test_run = test_runs.id
        WHERE
        test_runs.report=1
        AND
        unit_test_results.result='F'
        AND
        unit_test_results.test_run=$test_run_id
        ORDER BY time ASC";
    
        $sth=$dbh->prepare($sql);
        $result=$sth->execute();
        my $failed_unit_tests=$sth->fetchall_arrayref();
        my $failed_test_count=$#{@{$failed_unit_tests}}+1;
    
        ## @note If $failed_test_count == -1 then no result set was obtained.
    
        my $pass_cnts;
        my $test_cnts;
        my $sum_wall;
    
        $g_body.="\nTest Run ID = $test_run_id, Time = $test_run_time GMT\n";
        my $percentage=sprintf("%.1f",(($test_count-$failed_test_count)/$test_count)*100.0);
        $g_body.="Unit Test Results: ";
        $g_body.=($test_count-$failed_test_count);
        $g_body.="/$test_count";
    
        $g_body.=" or $percentage\% successful in wallclock time $wall_time s.\n\n";
    
        # Iterate over failed unit test results to be reported.
        foreach my $row (@{$failed_unit_tests}){
            $g_body.="- ";
            $g_body.=$row->[4];
            $g_body.="->";
            $g_body.=$row->[7];
    
            if(length($row->[8])>0){
                $g_body.="__$row->[8]";
            }
            $g_body.=" failed.\n";
            
            my $test_name="PS::MOPS::DC::Test::Unit::Tracklets::test_modct_selectDetracklets__target_field_id";
            my $fail_data=retrieve_failed_test_log_data($test_name,$testing_log_data);
            $g_body.="\n" . $fail_data if $fail_data;
        }
    }
}

# Read the contents of a log file and return an array reference to the data.
#
# @param log_path Full path to log file.
# @return log_data Array reference.
sub read_log_file {
    my($log_path)=@_;
    open(FILE,"<$log_path");
    my @data=<FILE>;
    close(FILE);   
    return \@data;
}

# Retrieve the relevant log data associated with a test failure.
#
# @param full_test_name Full name of test that is used to match log entry.
# @param testing_log_ref Array reference to testing log.
# @return Relevant log data for test failure.
sub retrieve_failed_test_log_data {
    my ($full_test_name,$testing_log_ref)=@_;

    my $copy_data=0; # Flag indicating data should be copied.
    my $log_data="";

    # Find the starting point in the log file for a failed test.
    foreach my $line (@{$testing_log_ref}){
        if($line=~/\: Testing/ && $copy_data){ # Start of next test is reached.
            $copy_data=0;
        }
        if($copy_data){
            $log_data.=$line;
        }
        if($line=~/Testing $full_test_name/ && !$copy_data){ # Start of target test is found.
            $copy_data=1;
        }
    }
    return $log_data if length($log_data)>0;
    return 0;
}
