#!/usr/bin/perl -w
######################################################################
#
# $Id: test_harness,v 1.22 2012/01/07 08:01:24 mavrik Exp $
#
######################################################################
#
# Copyright 2005-2012 The WebJob Project, All Rights Reserved.
#
######################################################################
#
# Purpose: A generic test harness.
#
######################################################################

use strict;
use File::Basename;
use File::Path;
use Getopt::Std;

BEGIN
{
  my (%hProperties, %hTestNumbers, %hTestDescriptions, %hTestProperties, %hTestTargetValues);

  sub GetProperties
  {
    return \%hProperties;
  }

  sub GetTestNumbers
  {
    return \%hTestNumbers;
  }

  sub GetTestDescriptions
  {
    return \%hTestDescriptions;
  }

  sub GetTestProperties
  {
    return \%hTestProperties;
  }

  sub GetTestTargetValues
  {
    return \%hTestTargetValues;
  }
}

######################################################################
#
# Main Routine
#
######################################################################

  ####################################################################
  #
  # Punch in and go to work.
  #
  ####################################################################

  my ($phProperties);

  $phProperties = GetProperties();

  $$phProperties{'Program'} = basename(__FILE__);

  ####################################################################
  #
  # Set platform-specific variables.
  #
  ####################################################################

  if ($^O =~ /MSWin32/i)
  {
    $$phProperties{'OsClass'} = "WINDOWS";
    $$phProperties{'Newline'} = "\r\n";
  }
  else
  {
    $$phProperties{'OsClass'} = "UNIX";
    $$phProperties{'Newline'} = "\n";
  }

  ####################################################################
  #
  # Get Options.
  #
  ####################################################################

  my (%hOptions);

  if (!getopts("d:l:m:p:s:t:w:", \%hOptions))
  {
    Usage($$phProperties{'Program'});
  }

  ####################################################################
  #
  # DebugLevel, '-d', is optional.
  #
  ####################################################################

  $$phProperties{'DebugLevel'} = (exists($hOptions{'d'})) ? $hOptions{'d'} : 1;

  if ($$phProperties{'DebugLevel'} !~ /^\d+$/)
  {
    print STDERR "$$phProperties{'Program'}: Error='The debug level ($$phProperties{'DebugLevel'}) is not valid.'\n";
    exit(2);
  }

  ####################################################################
  #
  # LibDir, '-s', is optional.
  #
  ####################################################################

  $$phProperties{'LibDir'} = (exists($hOptions{'l'})) ? $hOptions{'l'} : ".";

  ####################################################################
  #
  # A RunMode, -m, is required.
  #
  ####################################################################

  $$phProperties{'RunMode'} = (exists($hOptions{'m'})) ? $hOptions{'m'} : "unknown";

  if ($$phProperties{'RunMode'} !~ /^(?:check|clean|setup)$/)
  {
    Usage($$phProperties{'Program'});
  }

  ####################################################################
  #
  # A TargetProgram, -p, is required.
  #
  ####################################################################

  my ($sTargetProgram);

  $sTargetProgram = $$phProperties{'TargetProgram'} = (exists($hOptions{'p'})) ? $hOptions{'p'} : undef;

  if (!defined($$phProperties{'TargetProgram'}))
  {
    Usage($$phProperties{'Program'});
  }
  if ($$phProperties{'RunMode'} =~ /^check$/)
  {
    if ($$phProperties{'OsClass'} =~ /^WINDOWS$/ && !-f $sTargetProgram && $sTargetProgram !~ /\.exe$/)
    {
      $sTargetProgram .= ".exe";
    }
    if (!-f $sTargetProgram)
    {
      print STDERR "$$phProperties{'Program'}: Error='The target program ($sTargetProgram) is not a file or does not exist.'\n";
      exit(2);
    }
    if ($$phProperties{'OsClass'} =~ /^UNIX$/ && !-x $sTargetProgram)
    {
      print STDERR "$$phProperties{'Program'}: Error='The target program ($sTargetProgram) is not executable.'\n";
      exit(2);
    }
  }

  ####################################################################
  #
  # SrcDir, '-s', is optional.
  #
  ####################################################################

  $$phProperties{'SrcDir'} = (exists($hOptions{'s'})) ? $hOptions{'s'} : ".";

  ####################################################################
  #
  # HarnessType, '-t', is optional.
  #
  ####################################################################

  $$phProperties{'HarnessType'} = (exists($hOptions{'t'})) ? $hOptions{'t'} : "4";

  if ($$phProperties{'HarnessType'} !~ /^[34]$/)
  {
    print STDERR "$$phProperties{'Program'}: Error='Invalid harness type ($$$phProperties{'HarnessType'}). Type must be 3 or 4.'\n";
    exit(2);
  }

  ####################################################################
  #
  # WorkDir, '-w', is optional.
  #
  ####################################################################

  $$phProperties{'WorkDir'} = (exists($hOptions{'w'})) ? $hOptions{'w'} : "work";

  ####################################################################
  #
  # If any arguments remain, it's an error.
  #
  ####################################################################

  if (scalar(@ARGV) > 0)
  {
    Usage($$phProperties{'Program'});
  }

  ####################################################################
  #
  # Conditionally create a work directory.
  #
  ####################################################################

  if ($$phProperties{'RunMode'} =~ /^setup$/)
  {
    CreateWorkArea($$phProperties{'WorkDir'});
  }

  ####################################################################
  #
  # Pull in local the test harness. Then, walk up the tree looking
  # for additional harness files.
  #
  ####################################################################

  $$phProperties{'HarnessFile'} = "test_harness.local";

  require $$phProperties{'SrcDir'} . "/" . $$phProperties{'HarnessFile'};

  my @aDirs = ("..", "../..", "../../..");

  push(@aDirs, "../../../..") if ($$phProperties{'HarnessType'} == 4);

  foreach my $sDir (@aDirs)
  {
    my $sHarnessFile = $$phProperties{'SrcDir'} . "/" . $sDir . "/" . $$phProperties{'HarnessFile'};

    if (-f $sHarnessFile)
    {
      require $sHarnessFile;
    }
  }

  ####################################################################
  #
  # Run through the hitching routines.
  #
  ####################################################################

  my ($phTestNumbers);

  $phTestNumbers = GetTestNumbers();

  foreach my $sSubTest (sort({ $a <=> $b } keys(%$phTestNumbers)))
  {
    my $sRoutine = \&{("Hitch_" . $$phTestNumbers{$sSubTest})};
    &$sRoutine();
  }

  ####################################################################
  #
  # Run through the mode-specific routines.
  #
  ####################################################################

  my ($phTestDescriptions, $sFailCount, $sPassCount, $sSkipCount, $sTestCount);

  $phTestDescriptions = GetTestDescriptions();

  $sFailCount = $sPassCount = $sSkipCount = $sTestCount = 0;

  foreach my $sSubTest (sort({ $a <=> $b } keys(%$phTestNumbers)))
  {
    $sTestCount++;
    my $sRoutine = \&{(MakeModeName($$phProperties{'RunMode'}) . $$phTestNumbers{$sSubTest})};
    my $sResults = &$sRoutine($phProperties);
    if ($$phProperties{'DebugLevel'} >= 2)
    {
      printf("%s %s %s,%s,%s,%d.%d - %s\n",
        $sResults,
        $$phProperties{'RunMode'},
        GetToolName(),
        GetPlatformName(),
        GetModeName(),
        GetTestNumber(),
        $sSubTest,
        $$phTestDescriptions{$$phTestNumbers{$sSubTest}},
        );
    }
    if ($sResults =~ /^pass$/)
    {
      $sPassCount++;
    }
    elsif ($sResults =~ /^skip$/)
    {
      $sSkipCount++;
    }
    else
    {
      $sFailCount++;
    }
  }

  ####################################################################
  #
  # Print out the test results. A single failure renders the entire
  # test group a failure. If all tests were skipped, then the group
  # is deemed to be a skip. If there was a combination of pass/skip
  # results, the group is deemed to be a pass.
  #
  ####################################################################

  my ($sGroupResults);

  if ($sFailCount)
  {
    $sGroupResults = "fail";
  }
  else
  {
    if ($sTestCount == $sSkipCount)
    {
      $sGroupResults = "skip";
    }
    else
    {
      if ($sTestCount == ($sPassCount + $sSkipCount))
      {
        $sGroupResults = "pass";
      }
      else
      {
        print STDERR "$$phProperties{'Program'}: Error='The test results don't add up ($sTestCount != $sPassCount + $sSkipCount). That should not happen.'\n";
        exit(2);
      }
    }
  }

  if ($$phProperties{'DebugLevel'} >= 1)
  {
    printf("%s %s %s,%s,%s,%d\n",
      $sGroupResults,
      $$phProperties{'RunMode'},
      GetToolName(),
      GetPlatformName(),
      GetModeName(),
      GetTestNumber(),
      );
  }

  ####################################################################
  #
  # Conditionally, create a done file.
  #
  ####################################################################

  if ($sFailCount == 0)
  {
    CreateDoneFile($$phProperties{'RunMode'});
  }

  ####################################################################
  #
  # Cleanup and go home.
  #
  ####################################################################

  exit(($sFailCount == 0) ? 0 : 3);


######################################################################
#
# CreateDoneFile
#
######################################################################

sub CreateDoneFile
{
  my ($sRunMode) = @_;

  if ($sRunMode =~ /^(?:clean|check|setup)$/)
  {
    my $sFile = "_" . $sRunMode . "_done";
    if (!open(FH, "> $sFile"))
    {
      return undef;
    }
    close(FH);
  }
}


######################################################################
#
# CreateWorkArea
#
######################################################################

sub CreateWorkArea
{
  my ($sDir) = @_;

  if (!-d $sDir)
  {
    mkdir($sDir, 0755);
  }
}


######################################################################
#
# DebugPrint
#
######################################################################

sub DebugPrint
{
  my ($sDebugLevel, $sMessage) = @_;

  my $phProperties = GetProperties();

  if ($$phProperties{'DebugLevel'} >= $sDebugLevel)
  {
    printf("     %s%s", $sMessage, $$phProperties{'Newline'});
  }
}


######################################################################
#
# MakeModeName
#
######################################################################

sub MakeModeName
{
  my ($sMode) = @_;

  $sMode =~ s/^(.)(.*)$/uc($1).$2."_"/e;

  return $sMode;
}


######################################################################
#
# MakeTestName
#
######################################################################

sub MakeTestName
{
  my ($phProperties) = @_;

  my ($sFile, $sName);

  $sName = (caller(1))[3];
  $sName =~ s/^.+(?:Check|Clean|Setup)_//;
  $sFile = $$phProperties{'WorkDir'} . "/" . $sName;

  return ($sFile, $sName);
}


######################################################################
#
# UnlinkDoneFile
#
######################################################################

sub UnlinkDoneFile
{
  my ($sRunMode) = @_;

  if ($sRunMode =~ /^(?:clean|check|setup)$/)
  {
    my $sFile = "_" . $sRunMode . "_done";
    if (-f $sFile && !unlink($sFile))
    {
      return undef;
    }
  }
}


######################################################################
#
# Usage
#
######################################################################

sub Usage
{
  my ($sProgram) = @_;
  print STDERR "\n";
  print STDERR "Usage: $sProgram [-d debug-level] [-l lib-dir] [-s src-dir] [-t {3|4}] [-w work-dir] -m {check|clean|setup} -p target-program\n";
  print STDERR "\n";
  exit(1);
}

