############################################################################           
#  Licensed to the Apache Software Foundation (ASF) under one or more                  
#  contributor license agreements.  See the NOTICE file distributed with               
#  this work for additional information regarding copyright ownership.                 
#  The ASF licenses this file to You 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.                                                      
                                                                                       
package TestDriver;

###########################################################################
# Class: TestDriver
# A base class for TestDrivers.
# 

use TestDriverFactory;
use TestReport;
use File::Path;
use Parallel::ForkManager;
use FileHandle;
use File::Copy;
use File::Basename;

my $passedStr = 'passed';
my $failedStr = 'failed';
my $abortedStr = 'aborted';
my $skippedStr = 'skipped';
my $dependStr = 'failed_dependency';

# A constant to be used as a key in hash:
my $keyGlobalSetupConditionalDone = 'globalSetupConditionaldone';

################################################################################
# Sub: appendToLength
# static mathod to make a string not shorter than N characters in length: 
# appends spaces unlit the desired length achieved.
#
# Paramaters:
# str - the string
# len - the min deired length 
#
# Returns: the modified string
#
sub appendToLength($$) {
   my ($str, $len) = @_; 
   while (length($str) < $len) {
      $str .= " ";
   }   
   return $str;
}

##############################################################################
#  Sub: printResults
#  Static function, can be used by test_harness.pl
#  Print the results so far, given the testStatuses hash.
#
# Paramaters:
# testStatuses - reference to hash of test status results.
# log    - reference to file handle to print results to.
# prefix - A title to prefix to the results
#
# Returns:
# None.
#
sub printResults
{
	my ($testStatuses, $log, $prefix, $confFile, $groupName) = @_;

	my ($pass, $fail, $abort, $depend, $skipped) = (0, 0, 0, 0, 0);

	foreach (keys(%$testStatuses)) {
		($testStatuses->{$_} eq $passedStr) && $pass++;
		($testStatuses->{$_} eq $failedStr) && $fail++;
		($testStatuses->{$_} eq $abortedStr) && $abort++;
		($testStatuses->{$_} eq $dependStr) && $depend++;
		($testStatuses->{$_} eq $skippedStr) && $skipped++;
	}

        my $context = "";
        if (defined($confFile) && (length(confFile) > 0)) {
           $context = " [$confFile-$groupName]"; 
        } 
        # XXX why comma is added there?
	my $msg = appendToLength($prefix . ",", 18) 
            . " PASSED: " . appendToLength($pass,4) 
            . " FAILED: " . appendToLength($fail,4) 
            . " SKIPPED: " . appendToLength($skipped,4) 
            . " ABORTED: " . appendToLength($abort,4) 
            . " FAILED DEPENDENCY: " . appendToLength($depend,4)
            . $context;
	print $log "$msg\n";
 	print "$msg\n";      
}

##############################################################################
# Puts all the k-v pairs from sourceHash to the targetHash.
# Returns: void
# parameters: 
#   1: targetHash, 
#   2: sourceHash.
sub putAll($$)
{
    my ($targetHash, $sourceHash) = @_;
    while (my ($key, $value) = each(%$sourceHash)) {
        $targetHash->{ $key } = $value;
    }
}

##############################################################################
# appends one file to another.
# parameters: 
#   1: sourceFileName, 
#   2: targetFileName.
# Returns: void
sub appendFile($$) {
    my ($source, $target) = @_;
    dbg("Appending file [" . Cwd::realpath($source) . "] >> [" . Cwd::realpath($target) . "]\n");
    $sourceHandle = FileHandle->new("<$source");
    if (! defined $sourceHandle) {
        die "Cannot open source file [$source].";
    }
    $targetHandle = FileHandle->new(">>$target");
    if (defined $targetHandle) {
        copy($sourceHandle, $targetHandle);
        $targetHandle->close(); 
        $sourceHandle->close(); 
    } else {
        die "cannot open target file [$target].";
    }
}

##############################################################################
# Diagnostic sub to print a hash contents
# Paramaters: 
#   1: the hash reference;
# Returns: void
sub dbgDumpHash($;$)
{
    if ($ENV{'E2E_DEBUG'} eq 'true') {
       my ($myhash, $msg) = @_;
       print "Dump of hash $msg:\n";
       while (my ($key, $value) = each(%$myhash)) {
           print "  [$key] = [$value]\n";
       }
    }
}

##############################################################################
# Diagnostic sub to print a debug output.
# This is useful when debugging the harness perl scripts.
# Paramaters: 
#   1*: object(s) to be printed, typically one string;
# Returns: void
sub dbg(@) 
{
    if ($ENV{'E2E_DEBUG'} eq 'true') {
       print @_;
    }
}

##############################################################################
#  Sub: printGroupResultsXml
#  Print the results for the group using junit xml schema using values from the testStatuses hash.
#
# Paramaters:
# $report       - the report object to use to generate the report
# $groupName    - the name of the group to report totals for
# $testStatuses - the hash containing the results for the tests run so far
# $totalDuration- The total time it took to run the group of tests
#
# Returns:
# None.
#
sub printGroupResultsXml
{
	my ( $report, $groupName, $testStatuses,  $totalDuration) = @_;
        $totalDuration=0 if  ( !$totalDuration );

	my ($pass, $fail, $abort, $depend) = (0, 0, 0, 0);

	foreach my $key (keys(%$testStatuses)) {
              if ( $key =~ /^$groupName/ ){
		($testStatuses->{$key} eq $passedStr) && $pass++;
		($testStatuses->{$key} eq $failedStr) && $fail++;
		($testStatuses->{$key} eq $abortedStr) && $abort++;
		($testStatuses->{$key} eq $dependStr) && $depend++;
               }
	}

        my $total= $pass + $fail + $abort;
        $report->totals( $groupName, $total, $fail, $abort, $totalDuration );
}

##############################################################################
#  Sub: new
# Constructor, should only be called by TestDriverFactory.
#
# Paramaters:
# None
#
# Returns:
# None.
sub new
{
	my $proto = shift;
	my $class = ref($proto) || $proto;
	my $self = {};

	bless($self, $class);

    $self->{'wrong_execution_mode'} = "_xyz_wrong_execution_mode_zyx_";

	return $self;
}

##############################################################################
#  Sub: globalSetup
# Set up before any tests from the group are run.  This gives each individual driver a chance to do
# setup.  This function will only be called once, before all the tests are
# run.  A driver need not implement it.  It is a virtual function.
#
# This method invoked unconditionally (always), even if there are no test to run in the group. See
# also #globalSetupConditional() description. 
#
# Paramaters:
# globalHash - Top level hash from config file (does not have any group
# or test information in it).
# log - log file handle
#
# Returns:
# None
#
sub globalSetup
{
}

##############################################################################
#  Sub: globalSetupConditional 
# Set up before any tests from the test config file (in sequential mode) or test group (in parallel mode) are run. 
# Executes after #globalSetup(). Executes *only* if there is at least one test to run. Introduced for performance
# optimization in parallel execution mode.
# It is a virtual function. Subclasses may override it.
#
# Paramaters:
# globalHash - Top level hash from config file (does not have any group
# or test information in it).
# log - log file handle
#
# Returns:
# None
#
sub globalSetupConditional
{
}

###############################################################################
# Sub: globalCleanup
# Clean up after all tests have run.  This gives each individual driver a chance to do
# cleanup.  This function will only be called once, after all the tests are
# run.  A driver need not implement it.  It is a virtual function.
# This method invoked unconditionally, even if no test in the group was executed. 
# See #globalCleanupConditional() method description.
#
# Paramaters:
# globalHash - Top level hash from config file (does not have any group
# or test information in it).
# log - log file handle
#
# Returns:
# None
sub globalCleanup
{
}

###############################################################################
# Sub: globalCleanupConditional
# Clean up after all tests have run, before #globalCleanup(). Invoked iff #globalSetupConditional()
# was previously invoked gor this config file (sequential mode) or test group (parallel mode).
# It is a virtual function. Subclasses may override it.
#
# Paramaters:
# globalHash - Top level hash from config file (does not have any group
# or test information in it).
# log - log file handle
#
# Returns:
# None
sub globalCleanupConditional() {
}

###############################################################################
# Sub: runTest
# Run a test.  This is a pure virtual function.
#
# Parameters:
# testcmd - reference to hash with meta tags and command to run tests.
# Interpretation of the tags and the command are up to the subclass.
# log - reference to a stream pointer for the logs
#
# Returns:
# @returns reference to hash.  Contents of hash are defined by the subclass.
#
sub runTest
{
	die "$0 INFO : This is a virtual function!";
}

###############################################################################
# Sub: generateBenchmark
# Generate benchmark results.  This is a pure virtual function.
#
# Parameters:
# benchmarkcmd - reference to hash with meta tags and command to
# generate benchmark.  Interpretation of the tags and the command are up to
# log - reference to a stream pointer for the logs
# the subclass.
#
# Returns:
# @returns reference to hash.  Contents of hash are defined by the subclass.
#
sub generateBenchmark
{
	die "$0 INFO: This is a virtual function!";
}

###############################################################################
# Sub: compare
# Compare the results of the test run with the generated benchmark results.  
# This is a pure virtual function.
#
# Parameters:
# benchmarkcmd - reference to hash with meta tags and command to
# testResult - reference to hash returned by runTest.
# benchmarkResult - reference to hash returned by generateBenchmark.
# log - reference to a stream pointer for the logs
# testHash - reference to hash with meta tags and commands
#
# Returns:
# @returns reference true if results are the same, false otherwise.  "the
# same" is defined by the subclass.
#
sub compare
{
	die "$0 INFO: This is a virtual function!";
}

###############################################################################
# Sub: recordResults
# Record results of the test run.  The required fields are filled in by the
# test harness.  This call gives an individual driver a chance to fill in
# additional fields of cmd, cmd_id, expected_results, and actual_results.
# this function does not have to be implemened.
# This is a virtual function.
#
# Parameters:
# status - status of test passing, true or false
# testResult - reference to hash returned by runTest.
# benchmarkResult - reference to hash returned by generateBenchmark.
# dbinfo - reference to hash that will be used to fill in db.
# log - reference to hash that will be used to fill in db.
#
# Returns:
# None
#
sub recordResults
{
}

###############################################################################
# Sub: cleanup
# Clean up after a test.  This gives each individual driver a chance to do
# cleanup.  A driver need not implement it.  It is a virtual function.
#
# Parameters:
# status - status of test passing, true or false
# testHash - reference to hash that was passed to runTest() and
# generateBenchmark().
# testResult - reference to hash returned by runTest.
# benchmarkResult - reference to hash returned by generateBenchmark.
# log - reference to a stream pointer for the logs
# 
# Returns:
# None
#
sub cleanup
{
}

###############################################################################
# Sub: run
# Run all the tests in the configuration file.
#
# Parameters:
# testsToRun - reference to array of test groups and ids to run
# testsToMatch - reference to array of test groups and ids to match.
# If a test group_num matches any of these regular expressions it will be run.
# cfg - reference to contents of cfg file
# log - reference to a stream pointer for the logs
# dbh - reference database connection
# testStatuses- reference to hash of test statuses
# confFile - config file name
# startat - test to start at.
# logname - name of the xml log for reporting results
#
# Returns:
# @returns nothing
# failed.
#
sub run
{
	my ($self, $testsToRun, $testsToMatch, $cfg, $log, $dbh, $testStatuses,
		$confFile, $startat, $logname, $resources ) = @_;

    my $subName = (caller(0))[3];
    my $msg="";
	# Rather than make each driver handle our multi-level cfg, we'll flatten
	# the hashes into one for it.
	my %globalHash;

	my $runAll = ((scalar(@$testsToRun) == 0) && (scalar(@$testsToMatch) == 0));

	# Read the global keys
	foreach (keys(%$cfg)) {
		next if $_ eq 'groups';
		$globalHash{$_} = $cfg->{$_};
	}

    my $report=0;
    my $properties= new Properties(0, $globalHash{'propertiesFile'});

    my $fileForkFactor = int($ENV{'FORK_FACTOR_FILE'});
    my $groupForkFactor = int($ENV{'FORK_FACTOR_GROUP'});
    # NB: this is to distinguish the sequential mode from parallel one: 
    my $productForkFactor = $fileForkFactor * $groupForkFactor;
    my $pm;
    if ($groupForkFactor > 1) {
        print $log "Group fork factor: $groupForkFactor\n";
        # Create the fork manager:
        $pm = new Parallel::ForkManager($groupForkFactor);
        # this is a callback method that will run in the main process on each job subprocess completion:
        $pm -> run_on_finish (
           sub {
              my ($pid, $exit_code, $identification, $exit_signal, $core_dump, $data_structure_reference) = @_; 
              # see what the child sent us, if anything
              if (defined($data_structure_reference)) { 
                  dbg("Group subprocess [$identification] finished, pid=${pid}, sent back: [$data_structure_reference].\n");
                  dbgDumpHash($data_structure_reference, "The hash passed in in the run_on_finish callback:");
                  putAll($testStatuses, $data_structure_reference);
                  dbgDumpHash($testStatuses, "The statuses after merge in the run_on_finish callback:");
              } else {
                  print "ERROR: Group subprocess [$identification] did not send back anything. Exit code = $exit_code\n";
              }
              my $subLogAgain = "$logname-$identification";
              appendFile($subLogAgain,$logname);
           }
       );
    } else {
    	# Do the global setup:
    	$self->globalSetup(\%globalHash, $log);
    }

    my $localStartAt = $startat;
	foreach my $group (@{$cfg->{'groups'}}) {
        my $groupName = $group->{'name'};

        my $subLog;
        my $subLogName;
        if ($groupForkFactor > 1) {
            # use group name as the Job id:
            my $jobId = $groupName;

            $subLogName = "$logname-$jobId";
            open $subLog, ">$subLogName" or die "FATAL ERROR $0 at ".__LINE__." : Can't open $subLogName, $!\n";

            dbg("**** Logging to [$subLogName].\n");
            # PARALLEL SECTION START: ===============================================================================
            $pm->start($jobId) and next;
            dbg("Started test group job \"$jobId\"\n");

            dbg("Doing setup for test group [$groupName]...\n");
            # Set the group-specific ID:
            # NB: note that '$globalHash' here is an object cloned for this subprocess.
            # So, there is no concurrency issue in using '$globalHash' there:
            $globalHash{'job-id'} = $globalHash{'job-id'} . "-" . $jobId;
            # Do the global setup which is specific for *this group*:
        	$self->globalSetup(\%globalHash, $subLog);
        } else {
            $subLog = $log;
            $subLogName = $logname;
        }

        # Run the group of tests.
        # NB: the processing of $localStartAt parameter happens only if the groupForkFactor < 1.
        my $sawStart = $self -> runTestGroup($groupName, $subLog, $confFile, \%globalHash, $group, $runAll, $testsToRun, $testsToMatch, $localStartAt, $testStatuses, $productForkFactor, $resources);
        if ((defined $localStartAt) && $sawStart) {
            undef $localStartAt;
        }

        if ($groupForkFactor > 1) {
            # do the clanups that are specific for *this group*.
            dbg("Doing cleanup for test group [$groupName]...\n");
            # NB: invoke it in such way to emphasize the fact that this method is not virtual:
            globalCleanupConditionalIf($self, \%globalHash, $subLog);
            $self->globalCleanup(\%globalHash, $subLog);

            dbg("Finishing test group [$groupName].\n");
            dbgDumpHash($testStatuses, "The satatuses hash at the fork section end");
            # NB: send the "testStatuses" hash object reference (which is local to this subprocess) to the parent process: 
            $subLog -> close();

            # TODO: may also consider the #runTestGroup block exit status and use it there.
            $pm -> finish(0, $testStatuses);
            # PARALLEL SECTION END. ===============================================================================
        }
	} # foreach $group  

    if ($groupForkFactor > 1) {
        $pm->wait_all_children;
    } else {
    	# Do the global cleanups:
        # NB: invoke it in such way to emphasize the fact that this method is not virtual:
        globalCleanupConditionalIf($self, \%globalHash, $log);
    	$self->globalCleanup(\%globalHash, $log);
    }
}

# Servce method to conditionally perform the virtual #globalCleanupConditional().
# NB: This sub should be "final" in Java terms,
# subclasses should not override it.
sub globalCleanupConditionalIf() {
    my ($self, $globalHash, $log) = @_;
    if (defined($globalHash->{$keyGlobalSetupConditionalDone})) {
        $self -> globalCleanupConditional($globalHash, $log);
    }
}

################################################################################
# Separated sub to run a test group.
# Parameters: (same named values from #run(...) sub with the same meaning).
# Returns: 'true' if the test defined by '$startat' was found, and 'false' otherwise.
#   (If the '$startat' is null, always returns true.)   
sub runTestGroup() {
        my ($self, $groupName, $subLog, $confFile, $globalHash, $group, $runAll, $testsToRun, $testsToMatch, $startat, $testStatuses, $productForkFactor, $resources) = @_;

        my $subName = (caller(0))[3];
        print $subLog "INFO $subName at ".__LINE__.": Running TEST GROUP(".$groupName.")\n";
        my $sawstart = !(defined $startat);
                
		my %groupHash = %$globalHash; 
		$groupHash{'group'} = $groupName;

		# Read the group keys
		foreach (keys(%$group)) {
			next if $_ eq 'tests';
			$groupHash{$_} = $group->{$_};
		}

        my $groupDuration=0;
        my $duration=0;

		# Run each test in the group:
		foreach my $test (@{$group->{'tests'}}) {
			# Check if we're supposed to run this one or not.
			if (!$runAll) {
				# check if we are supposed to run this test or not.
				my $foundIt = 0;
				foreach (@$testsToRun) {
					if (/^$groupHash{'group'}(_[0-9]+)?$/) {
						if (not defined $1) {
							# In this case it's just the group name, so we'll
							# run every test in the group
							$foundIt = 1;
							last;
						} else {
							# maybe, it at least matches the group
							my $num = "_" . $test->{'num'};
							if ($num eq $1) {
								$foundIt = 1;
								last;
							}
						}
					}
				}
				foreach (@$testsToMatch) {
					my $protoName = $groupHash{'group'} . "_" .  $test->{'num'};
					if ($protoName =~ /$_/) {
						if (not defined $1) {
							# In this case it's just the group name, so we'll
							# run every test in the group
							$foundIt = 1;
							last;
						} else {
							# maybe, it at least matches the group
							my $num = "_" . $test->{'num'};
							if ($num eq $1) {
								$foundIt = 1;
								last;
							}
						}
					}
				}
				next unless $foundIt;
			}

			# This is a test, so run it.
			my %testHash = %groupHash;
            my $tmpTestHash = \%testHash;

			foreach (keys(%$test)) {
				$testHash{$_} = $test->{$_};
			}

			my $testName = $testHash{'group'} . "_" . $testHash{'num'};
            dbg("################### Executing test [$testName]...\n");

#            if ( $groupExecuted{ $group->{'name'} }== 0 ){
#                $groupExecuted{ $group->{'name'} }=1;
#               
#                my $xmlDir= $globalHash{'localxmlpathbase'}."/run".$globalHash->{'UID'};
#                mkpath( [ $xmlDir ] , 1, 0777) if ( ! -e $xmlDir );
#
#                my $filename = $group->{'name'}.".xml";
#                $report = new TestReport ( $properties, "$xmlDir/$filename" );
#                $report->purge();
#            }

			# Check that ignore isn't set for this file, group, or test
			if (defined $testHash{'ignore'}) {
				print $subLog "Ignoring test $testName, ignore message: " .
					$testHash{'ignore'} . "\n";
				next;
			}

			# Have we not reached the starting point yet?
			if (!$sawstart) { 
				if ($testName eq $startat) {
					$sawstart = 1;
				} else {
					next;
				}
			}

			# Check that this test doesn't depend on an earlier test or tests
			# that failed.  Don't abort if that test wasn't run, just assume the
			# user knew what they were doing and set it up right.
			my $skipThisOne = 0;
			foreach (keys(%testHash)) {
				if (/^depends_on/ && defined($testStatuses->{$testHash{$_}}) &&
						$testStatuses->{$testHash{$_}} ne $passedStr) {
					print $subLog "Skipping test $testName, it depended on " .
						"$testHash{$_} which returned a status of " .
						"$testStatuses->{$testHash{$_}}\n";
					$testStatuses->{$testName} = $dependStr;
					$skipThisOne = 1;
					last;
				}
			}
			if ($skipThisOne) {
                            if ($productForkFactor > 1) {
				printResults($testStatuses, $subLog, "Results so far", basename($confFile), $groupName);
                            } else {
				printResults($testStatuses, $subLog, "Results so far");
                            }
			    next;
			}

			print $subLog "\n******************************************************\n";
			print $subLog "\nTEST: $confFile::$testName\n";
			print $subLog  "******************************************************\n";
			print $subLog "Beginning test $testName at " . time . "\n";
            
            # At this point we're going to run the test for sure. 
            # So, do the preparation for that, if not yet done: 
            if (!defined($globalHash->{$keyGlobalSetupConditionalDone})) {
                $self -> globalSetupConditional($globalHash, $subLog);
                # this preparation should be done only *once* per each $globalHash instance,
                # so, set special flag to prevent #globalSetupConditional from being executed again:
                $globalHash->{$keyGlobalSetupConditionalDone} = 'true';
            }

			my %dbinfo = (
				'testrun_id' => $testHash{'trid'},
				'test_type' => $testHash{'driver'},
				#'test_file' => $testHash{'file'},
				'test_file' => $confFile,
				'test_group' => $testHash{'group'},
				'test_num' => $testHash{'num'},
			);
			my $beginTime = time;
			my $endTime = 0;
			my ($testResult, $benchmarkResult);
			eval {
				$testResult = $self->runTest(\%testHash, $subLog, $resources);
				$endTime = time;
				$benchmarkResult = $self->generateBenchmark(\%testHash, $subLog);
				my $result =
					$self->compare($testResult, $benchmarkResult, $subLog, \%testHash, $resources);
				$msg = "INFO: $subName() at ".__LINE__.":Test $testName";

                if ($result eq $self->{'wrong_execution_mode'}) {
					$msg .= " SKIPPED";
					$testStatuses->{$testName} = $skippedStr;
                } elsif ($result) {
					$msg .= " SUCCEEDED";
					$testStatuses->{$testName} = $passedStr;

				} else {
					$msg .= " FAILED";
					$testStatuses->{$testName} = $failedStr;

				}
                $msg .= " at " . time . "\nEnding test $testName at " . time ."\n";
				print $subLog $msg;
				$duration = $endTime - $beginTime;
				$dbinfo{'duration'} = $duration;
				$self->recordResults($result, $testResult
                                          , $benchmarkResult, \%dbinfo, $subLog);
			};

			if ($@) {
				$msg= "ERROR $subName at : ".__LINE__." Failed to run test $testName <$@>\n";
                                $msg .= "\nEnding test $testName at " . time ."\n";
				#print $msg;
				print $subLog $msg;
				$testStatuses->{$testName} = $abortedStr;
				$dbinfo{'duration'} = $duration;
			}

			eval {
				$dbinfo{'status'} = $testStatuses->{$testName};
				if($dbh) {
					$dbh->insertTestCase(\%dbinfo);
				}
			};
			if ($@) {
				chomp $@;
				warn "Failed to insert test case info, error <$@>\n";
			}

			$self->cleanup($testStatuses->{$testName}, \%testHash, $testResult,
				$benchmarkResult, $subLog);
            #$report->testcase( $group->{'name'}, $testName, $duration, $msg, $testStatuses->{$testName}, $testResult ) if ( $report );
            $report->testcase( $group->{'name'}, $testName, $duration, $msg, $testStatuses->{$testName} ) if ( $report );
            $groupDuration = $groupDuration + $duration;
                        if ($productForkFactor > 1) {
			   printResults( $testStatuses, $subLog, "Results so far", basename($confFile), $groupName );
                        } else {
			   printResults( $testStatuses, $subLog, "Results so far" );
                        }
		}  # for each test

        if ( $report ) {
            $report->systemOut( $subLogName, $group->{'name'});
            printGroupResultsXml( $report, $group->{'name'}, $testStatuses, $groupDuration );
        }
        $report = 0;
        return $sawstart;
}



# TODO These should be removed

sub tmpIPCRun(){

  my $self = shift;
  my $subName      = (caller(0))[3];
  my $runningSubName= shift;
  my $refCmd  = shift;
  my @cmd     = @$refCmd;
  my $log    = shift;
  my $msg    = shift;

  print $log "$0::$subName INFO Running ( @cmd )\n";

   my $result= `@cmd`;
   if ( $@ ) {
      my $msg= "$0::$subName FATAL Failed to run from $runningSubName $msg < $@ >\n$result\n";
      print $log $msg;
      die "$msg";
   }

   return $?;
}

sub tmpIPCRunSplitStdoe {

  my $self = shift;
  my $subName      = (caller(0))[3];
  my $runningSubName= shift;
  my $refCmd  = shift;
  my @cmd     = @$refCmd;
  my $dir    = shift;
  my $log    = shift;
  my $msg    = shift;
  my $die    = shift;


  my $failed = 0;
  
  my $outfilename = $dir."out.tmp";
  my $errfilename = $dir."err.tmp";
  print $log "$0::$subName INFO Running from $runningSubName ( @cmd 1>$outfilename 2>$errfilename )\n";
  #make sure files are writeable
  open( TMP, ">$outfilename" ) || die "$0::$subName FATAL: Cannot open $outfilename for writing\n";
  close( TMP );
  open( TMP, ">$errfilename" ) || die "$0::$subName FATAL: Cannot open $errfilename for writing\n";
  close( TMP );

  #RUN CMD
  my $msg;
  print $log `@cmd 1>$outfilename 2>$errfilename`;
   
  my $failed=0;
  if ( $@ ) { 
      $msg= "$0::$subName FATAL < $@ >\n"; 
      $failed++;
   }
   
   #READ FILES
   my $stdout=""; 
   my $stderr="";;
   open( TMP, "$outfilename" ) || die "$0::$subName FATAL: Cannot open $outfilename for reading\n";
   while ( <TMP> ){ $stdout .= $_; }
   close( TMP );
 
   open( TMP, "$errfilename" ) || die "$0::$subName FATAL: Cannot open $errfilename for reading\n";
   while ( <TMP> ){
       $stderr .= $_;
   }
   close( TMP );

   #DIE IF Test Failed, otherwise return stdout and stderr
   if ( $failed ){

      $msg = "$0::$subName FATAL: Faied from $runningSubName \nSTDOUT:".$result{'stdout'}."\nSTDERR:".$result{'stderr'}."\n" if ( $failed );
      print $log "$msg";
      die $msg if ( $die != "1" ); #die by default
      return ( -1, $stdout, $stderr );

   }

   return ( $?, $stdout, $stderr);
}

sub tmpIPCRunJoinStdoe {

  my $self = shift;
  my $subName      = (caller(0))[3];
  my $runningSubName= shift;
  my $refCmd  = shift;
  my @cmd     = @$refCmd;
  my $outfilename= shift;
  my $log    = shift;
  my $msg    = shift;
  my $die    = shift;

  #make sure files are writeable
  open( TMP, ">$outfilename" ) || die "$0::$subName FATAL: Cannot open $outfilename for writing\n";
  close( TMP );

	  #RUN CMD
	  my $msg;
	  my $failed=0;
	  print $log "$0::$subName INFO Running ( @cmd 2>&1$outfilename 2>/dev/null )\n";
	  print $log `@cmd 2>&1 > $outfilename 2>/dev/null`;
	  if ( $@ ) { 
	      $failed++;
	      $msg= "$0::$subName FATAL < $@ >\n"; 
	   }
	   
	   #READ FILES
	   my $stdoe=""; 
	   open( TMP, "$outfilename" ) || die "$0::$subName FATAL: Cannot open $outfilename for reading\n";
	   while ( <TMP> ){ $stdoe .= $_; }
	   close( TMP );

	   if ( $failed ){
	      print $log "$msg";
	      die $msg if ( $die != "1" ); #die by default
	      return ( -1 ); 
	   }
	   return ( $? );
	}


	1;
