#!/usr/bin/env perl
# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
use strict;
use warnings;
use Getopt::Long;
use Data::Dumper;
use Term::ANSIColor;
use Time::Local;
use File::Basename;
use File::Path;

BEGIN
{
    $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : -d '/opt/xcat' ? '/opt/xcat' : '/usr';
}
use lib "$::XCATROOT/lib/perl";
my $rootdir         = "$::XCATROOT/share/xcat/tools/autotest";
my $needhelp        = 0;
my $configinfo      = undef;
my $configfile      = undef;
my $casedir         = "$rootdir/testcase";
my $bundledir       = "$rootdir/bundle";
my $resultdir       = "$rootdir/result";
my $bundlelist      = undef;
my $caselist        = undef;
my $cmdlist         = undef;
my $showbundlesorcommands = undef;
my $needshow        = 0;
my $restore         = 0;
my $ret             = 0;
my $string1         = undef;
my $showbundlefiles  = 0;
my $showcommandslist = 0;
my $stop_to_keep_env = 0;

#-----------------------------------
=head1
    System label usage:
    In order to make install xCAT to be the first case for automation daily regression, "System" label is used in this xcattest command.
    xcattest -f /opt/xcat/share/xcat/tools/autotest/default.conf:System -t install_xCAT_on_rhels_sles
    xcattest -f /opt/xcat/share/xcat/tools/autotest/default.conf:System -t install_xCAT_on_ubuntu
    System label means only the [System] aspect will be loaded and no other inital action will be done.
=cut

#-----------------------------------
my $initallabel     = undef;
my $loadsysteminfo  = "System";

#Create result directory
mkdir $resultdir unless -d $resultdir;

# create a log for xcattest 
my $timestamp = `date +"%Y%m%d%H%M%S"`;
open(LOG, ">$resultdir/xcattest.log.$timestamp")
  or die "Can't open logfile for writing: $!";

if (
    !GetOptions("h|?" => \$needhelp,
        "f=s"     => \$configinfo,
        "b=s"     => \$bundlelist,
        "t=s"     => \$caselist,
        "c=s"     => \$cmdlist,
        "s=s"     => \$showbundlesorcommands,
        "l"       => \$needshow,
        "restore" => \$restore)
  )
{
    &usage;
    exit 1;
}

if ($needhelp)
{
    &usage;
    exit 0;
}


if (&checkoptions)
{
    &usage;
    exit 1;
}

#show bundle files under $bundledir with .bundle as file suffix
my %bundlefilesinfo = ();
if ($showbundlefiles)
{
    &listbundlefiles;
    exit 0;
}

#show commands, which is the folder name  $casedir, with case* files and test cases
if ($showcommandslist)
{
    &listcommands;
    exit 0;
}

#load case to $cases
#    key                        type
#$cases[x](x>0):                hash
#    name                       string
#    os:AIX/Linux               string
#    arch:ppc64/x386            string
#    hcp:hmc/mm/bmc/fsp         string
#    cmd:                       array
#    check:                     array

my @cases = ();
if ($needshow) {
    &loadcase;
    exit 0;
}


# create a log for run test cases
&log_this("xCAT automated test started at " . scalar(localtime()));
open(LOG_ERR, ">$resultdir/failedcases.$timestamp")
  or die "Can't open error logfile for writing: $!";
  
#read config file
log_this("******************************");
log_this("Reading Configure");
log_this("******************************");
#config{object}{type}{name}{attr}
#config{table}{name}{entry}{key}
#config{script_prev}->[]
#config{script_post}->[]
#config{var}{varname}
my %config = ();
$ret = &getConfig;
if ($ret != 0) {
    goto EXIT;
}

#if not only load System aspects from config file, then init test enviroment 
if (!defined($initallabel)){
    log_this("******************************");
    log_this("Initialize xCAT test evironment");
    log_this("******************************");    
    $ret = &init;
    if ($ret != 0) {
        goto EXIT;
    }
}

my @filespath = ();
#loading and check cases
log_this("******************************");
log_this("loading test cases");
log_this("******************************");
$ret = &loadcase;
if ($ret != 0) {
    goto EXIT;
}

#run case
log_this("******************************");
log_this("Start to run test cases");
log_this("******************************");
&reordercases if (defined($bundlelist) || defined($caselist));
&runcase;

EXIT:
log_this("******************************");
log_this("un-initialize xCAT test evironment");
log_this("******************************");
if ($restore) {
    &uninit;
}

&log_this("\nxCAT automated test finished at " . scalar(localtime()));
&log_this("Please check results in the $resultdir, \nand see $resultdir/failedcases.$timestamp file for failed cases.\nsee $resultdir/performance.report.$timestamp file for time consumption");
close(LOG);
close(LOG_ERR);
my $reportfile = "$resultdir/performance.report.$timestamp";
my $tmpreport  = "$resultdir/xcattest.log.$timestamp";
&getreport($tmpreport, $reportfile);

if ($stop_to_keep_env) {
    exit 1;
} else {
    exit 0;
}

# end main

#
# logger
#

sub log_this
{
    print LOG join("\n", @_), "\n";
    my $msg = join("\n", @_);
    if ($msg =~ /\[Pass\]/) {
        print color("green"), "$msg\n", color("reset");
    } elsif ($msg =~ /\[Failed\]/) {
        print color("red"), "$msg\n", color("reset");
    } else {
        print "$msg\n";
    }
}

sub log_error
{
    print LOG_ERR join("\n", @_), "\n";
}

sub include_file
{
    my $file = shift;
    my $idir = shift;
    my @text = ();
    unless ($file =~ /^\//) {
        $file = $idir . "/" . $file;
    }
    open(INCLUDE, $file) || return "#INCLUDEBAD:cannot open $file#";
    while (<INCLUDE>) {
        chomp($_);
        s/\s+$//;    #remove trailing spaces
        next if /^\s*$/;    #-- skip empty lines
        push(@text, $_);
    }
    close(INCLUDE);
    return join(",", @text);
}

sub sort_caserange
{
    my @caserange = @_;
    my $bundletext = join(',', @caserange);
    #handle the #INLCUDE# tag recursively
    my $idir = "$rootdir/bundle";
    my $doneincludes = 0;
    while (not $doneincludes) {
        $doneincludes = 1;
        if ($bundletext =~ /#INCLUDE:[^#^\n]+#/) {
            $doneincludes = 0;
            $bundletext =~ s/#INCLUDE:([^#^\n]+)#/include_file($1,$idir)/eg;
        }
    }
    @caserange=split(",",$bundletext);
    my @diff;
    foreach my $case (@caserange) {
        unless (grep { $_ eq $case } @diff) {
            push @diff, $case;
        }
    }
    my %index;
    my @same = grep($index{$_}++, @caserange);
    my @error =grep /INCLUDEBAD/, @caserange;
    return (\@diff, \@same, \@error);
}

sub getConfig
{
    my $type     = undef;    #Script_Prev,Script_Post,Table,Object,System,Custom
    my $sub_type = undef;    # The string after $type_
                             # Script-->
                             #       Script_Prev
                             #       Script_Post
                             # Table--->
                             #       Table_xxxxx
                             # Object-->
                             #       Object_xxxx
                             # System---->
                             # Custom---->

    my $name  = undef;
    my $attr  = undef;
    my $value = undef;
    my $c     = 0;
    my $cmd   = undef;
    my $mgt_name = undef;
    
    if (!open(FILE, "$configfile")) {
       log_this("Error: can't open xCAT config file: $configfile");
       return 1;
    }  
    
    #Only load System information 
    if (defined($initallabel) && ($initallabel eq $loadsysteminfo)) {
        while (my $line = <FILE>) {
	    $line = &trim($line);
            next if (length($line) == 0);

            #Only read System variable
            if ($line =~ /\[System\]/) {
               $type = "Varible";
            } 
            if (defined($type) && ($type eq "Varible")) {
                if ($line =~ /(\w+)\s*=\s*([\w\.\-\+\/:]+)/) {
                    $config{var}{$1} = $2;
                }
            }  
        }            
    }else{
    #Load all config files information    
        while (my $line = <FILE>) {
        $line = &trim($line);
        next if (length($line) == 0);

        #Table name can not contain "_"
        if ($line =~ /\[\s*(\w+)\_(\w+)\s*\]/) {
            $type     = $1;
            $sub_type = $2;
            $name     = undef;
            $c        = 0;
        } elsif ($line =~ /\[\s*System|Custom\s*\]/) {
            $type = "Varible";
        } elsif ($type eq "Table") {
            ##TABLE BLOCK##
            if ($line =~ /(\w+)\s*=\s*([\w\.\-]+)/) {
                $attr  = $1;
                $value = $2;
                if ($name && ($config{table}{$sub_type}{$name}{__KEY__} ne $attr)) {
                    $config{table}{$sub_type}{$name}{$attr} = $value;
                } else {
                    $name = $value;
                    $config{table}{$sub_type}{$name}{__KEY__} = $attr;
                }
            }
        } elsif ($type eq "Object") {
            ##OBJECT BLOCK##
            if ($line =~ /(\w+)\s*=\s*([:\w\.\-\/]+)/) {
                $attr  = $1;
                $value = $2;
                if ($attr eq "Name") {
                    $name = $value;
                } elsif (!defined($name)) {
                    print "Please give name for Object\n";
                    close FILE;
                    return 1;
                } else {
                    $config{object}{$sub_type}{$name}{$attr} = $value;
                }
            }
        } elsif ($type eq "Script") {
            ##SCRIPT_BLOCK##
            if ($sub_type eq "Prev") {
                $config{script_prev}->[$c] = $line;
                $c = $c + 1;
            }
            elsif ($sub_type eq "Post") {
                $config{script_post}->[$c] = $line;
                $c = $c + 1;
            }
        } elsif ($type eq "Varible") {
            ##NODE_BLOCK##
            if ($line =~ /(\w+)\s*=\s*([\w\.\-\+\/:]+)/) {
                $config{var}{$1} = $2;
            }
        }
    }
}

    if (exists $config{object}) {
        foreach my $type (keys %{ $config{object} }) {
            foreach my $name (keys %{ $config{object}{$type} }) {
                log_this("OBJECT:$name,TYPE:$type");
                foreach my $attr (keys %{ $config{object}{$type}{$name} }) {
                    log_this("    $attr = $config{object}{$type}{$name}{$attr};");
                }
            }
        }
    }
    if (exists $config{table}) {
        foreach my $type (keys %{ $config{table} }) {
            log_this("TABLE:$type");
            foreach my $name (keys %{ $config{table}{$type} }) {
                log_this("    $config{table}{$type}{$name}{__KEY__} = $name");
                foreach my $attr (keys %{ $config{table}{$type}{$name} }) {
                    if ($attr ne '__KEY__') {
                        log_this("    $attr = $config{table}{$type}{$name}{$attr}");
                    }
                }
                log_this("\n");
            }
        }
    }
    if (exists $config{script_prev}) {
        log_this("Script_Prev:");
        foreach $cmd (@{ $config{script_prev} }) {
            log_this("    $cmd");
        }
    }

    if (exists $config{script_post}) {
        log_this("Script_Post:");
        foreach $cmd (@{ $config{script_post} }) {
            log_this("    $cmd");
        }
    }

    if (exists $config{var}) {
        log_this("Varible:");
        foreach my $varname (keys %{ $config{var} }) {
            log_this("    $varname = $config{var}{$varname}");
        }
    }

    close FILE;
    return 0;
}

sub init
{
    if ($restore) {
        log_this("******************************");
        log_this("Backup current xCAT database");
        log_this("******************************");
        &runcmd("mkdir -p /tmp/xCATdbbackup");
        &runcmd("dumpxCATdb -p /tmp/xCATdbbackup");
        if ($::RUNCMD_RC != 0) {
            &log_this("Fail to backup xCAT database");
            &runcmd("rm -rf /tmp/xCATdbbackup");
            $restore = 0;
            return 1;
        }
    }
    my $cmd = undef;
    foreach $cmd (@{ $config{script_prev} }) {
        log_this("$cmd");
        &runcmd($cmd);
        if ($::RUNCMD_RC != 0) {
            &log_this("Fail to run $cmd");
            return 1;
        }
    }
    if (exists $config{object}) {
        foreach my $type (keys %{ $config{object} }) {
            foreach my $name (keys %{ $config{object}{$type} }) {
                $cmd = "chdef -t $type -o $name";
                foreach my $attr (keys %{ $config{object}{$type}{$name} }) {
                    $cmd = $cmd . " $attr=$config{object}{$type}{$name}{$attr}";
                }
                log_this($cmd);
                runcmd($cmd);
                if ($::RUNCMD_RC != 0) {
                    log_this("Fail to run $cmd");
                    return 1;
                }
            }
        }
    }
    if (exists $config{table}) {
        foreach my $type (keys %{ $config{table} }) {
            foreach my $name (keys %{ $config{table}{$type} }) {
                $cmd = "chtab $config{table}{$type}{$name}{__KEY__}=$name";
                foreach my $attr (keys %{ $config{table}{$type}{$name} }) {
                    if ($attr ne '__KEY__') {
                        $cmd = $cmd . " $type.$attr=$config{table}{$type}{$name}{$attr}";
                    }
                }
                log_this($cmd);
                &runcmd($cmd);
                if ($::RUNCMD_RC != 0) {
                    &log_this("Fail to run $cmd");
                    return 1;
                }
            }
        }
    }

    if (!exists $config{var}{OS}) {
        my @output = runcmd("uname");
        $config{var}{OS} = $output[0];
        log_this("Detecting: OS = $config{var}{OS}");
    } else {
        $config{var}{OS} = lc($config{var}{OS});
    }

    if (!exists $config{var}{ARCH}) {
        if (!exists $config{var}{CN}) {
            $config{var}{ARCH} = "Unknown";
            log_this("Error: No compute node defined, can't get ARCH of compute node");
        } else {
            $config{var}{ARCH} = getnodeattr($config{var}{CN}, "arch");
            if ($config{var}{ARCH} =~ /ppc/) {
                $config{var}{ARCH} = 'ppc';
            } elsif ($config{var}{ARCH} =~ /86/) {
                $config{var}{ARCH} = 'x86';
            }
            log_this("Detecting: ARCH = $config{var}{ARCH}");
        }
    }
    if (!exists $config{var}{HCP}) {
        if (!exists $config{var}{CN}) {
            $config{var}{HCP} = "Unknown";
            log_this("Error: No compute node defined, can't get HCP TYPE of compute node");
        } else {
            $config{var}{HCP} = getnodeattr($config{var}{CN}, "mgt");
            log_this("Detecting: HCP = $config{var}{HCP}");
        }

    }
    return 0;
}

sub uninit
{
    my $cmd = undef;

    #    if(exists $config{object}){
    #        foreach my $type (keys %{$config{object}}){
    #            foreach my $name (keys %{$config{object}{$type}}){
    #                $cmd = "rmdef -t $type -o $name";
    #                log_this($cmd);
    #                runcmd($cmd);
    #                if($::RUNCMD_RC != 0){
    #                    log_this("Fail to run $cmd");
    #                    return 1;
    #                }
    #            }
    #        }
    #    }
    #    if(exists $config{table}){
    #        foreach my $type (keys %{$config{table}}){
    #            foreach my $name (keys %{$config{table}{$type}}){
    #                $cmd = "chtab -d $config{table}{$type}{$name}{__KEY__}=$name $type";
    #                log_this($cmd);
    #                runcmd($cmd);
    #                if($::RUNCMD_RC != 0){
    #                    log_this("Fail to run $cmd");
    #                    return 1;
    #                }
    #            }
    #        }
    #    }
    foreach $cmd (@{ $config{script_post} }) {
        log_this($cmd);
        runcmd($cmd);
        if ($::RUNCMD_RC != 0) {
            log_this("Error: Fail to run $cmd");
            return 1;
        }
    }

    &runcmd("restorexCATdb -p /tmp/xCATdbbackup");
    &runcmd("rm -rf /tmp/xCATdbbackup");
    return 0;
}

sub Get_Files_Recursive
{
    my $dir = $_[0];
    foreach $dir (@_)
    {
        opendir(my $d, $dir);
        for (; ;)
        {
            my $direntry = readdir($d);
            last unless defined $direntry;
            next if $direntry =~ m/^\.\w*/;
            next if $direntry eq '..';
            if (-d $dir . "/" . $direntry)
            {
                Get_Files_Recursive($dir . "/" . $direntry);
            }
            else
            { my $dirpath = $dir . '/' . $direntry . "\n";
                #print $dirpath;

                #print $dir."\n";
                push(@filespath, glob("$dirpath"));

            }
        }
        closedir($d);
    }
}

sub loadcase
{
    my @files   = ();

    #if($cmdlist){
    #    my @cmds = split /,/,$cmdlist;
    #    for my $cmd (@cmds){
    #         push (@files, glob("$casedir/$cmd/*"));
    #    }
    #} else {
    #    @files = glob("$casedir/*/*");
    #}
    Get_Files_Recursive("$casedir");
    for (my $countfile = 0 ; $countfile < @filespath ; $countfile++)
    {
        #TODO:if commands are not right, no action or message now
        if ($cmdlist) {
            my @cmds = split /,/, $cmdlist;
            for (my $countcmd = 0 ; $countcmd < @cmds ; $countcmd++) {
                if ($filespath[$countfile] =~ m/\/$cmds[$countcmd]\/case/) {
                    push(@files, glob("$filespath[$countfile]"));
                }
            }
        } else {
            push(@files, glob("$filespath[$countfile]"));
        }
    }


    my $file;
    my $line;
    my $i    = 0;
    my $j    = -1;
    my $z    = 0;
    my $skip = 0;

    my @caserange    = ();
    my @rightcase    = ();
    my @notrightcase = ();
    my @wrongnamecase= ();
    my @samecase = ();
    my @errorcase = ();
    if ($bundlelist) {
        my @bundles = split /,/, $bundlelist;
        foreach my $bundle (@bundles) {
            if (!open(FILE, "<$bundledir/$bundle")) {
                log_this("can't open $bundledir/$bundle");
                return 1;
            }
            while ($line = <FILE>) {
                chomp($line);
                next if (length($line) == 0);
                push(@caserange, $line);
            }
            close(FILE);
            my @refs=sort_caserange(@caserange);
            @caserange=@{$refs[0]};
            @samecase=@{$refs[1]};
            @errorcase=@{$refs[2]};
        }
    }
    
    #TODO:if cases are not existed, no action or message.
    if ($caselist) {
        @caserange = split /,/, $caselist;
    }
    
    foreach $file (@files) {
        if (!open(FILE, "<$file")) {
            log_this("can't open $file");
            return 1;
        }
        while ($line = <FILE>) {
            $line = &trim($line);
            next if (length($line) == 0);

            #skip comment lines
            next if ($line =~ /^\s*#/);

            #TODO: description line is treated as a comment line for now
            next if ($line =~ /^description\s*:/);

            if ($line =~ /^start\s*:\s*(.*)/) {
                my $name =$1;
                if ($name =~ /[^a-zA-Z0-9_-]/) {
		    $skip = 1;
		    push(@wrongnamecase, $name);
		    next;
		}else {
                    $skip = 0;
		    if ($caserange[0] && !(grep { /^$name$/ } @caserange)) {
                        $skip = 1;
                        next;
                    }
                    $j                     = -1;
                    $cases[$i]             = {};
                    $cases[$i]->{name}     = $name;
                    $cases[$i]->{filename} = $file;
                    if (!$needshow) {
			$cases[$i]->{cmd}      = [];
			$cases[$i]->{check}    = [];
			$cases[$i]->{cmdcheck} = [];
			push(@rightcase, $name);
			} else {
			$skip = 1;
			$i    = $i + 1;
	            }
	        }
            } elsif ($line =~ /^os\s*:\s*(\w[\w\,]+)/) {
                next if $skip;
                $string1 = $1;
                if ($string1 =~ /^rhels\s*/ && -f "/etc/redhat-release") {
                    $cases[$i]->{os} = "rhels";
                } elsif ($string1 =~ /^sles\s*/ && -f "/etc/SuSE-release") {
                    $cases[$i]->{os} = "sles";
                } elsif ($string1 =~ /^ubuntu\s*/ && -f "/etc/lsb-release") {
                    $cases[$i]->{os} = "ubuntu";
                }
                else {
                    $cases[$i]->{os} = $string1;

                }

                chomp($cases[$i]->{os});
                chomp($config{var}{OS});
                if ($cases[$i]->{os} !~ /$config{var}{OS}/) {
                    if ((($config{var}{OS} =~ /^Linux\s*/i) && ($cases[$i]->{os} =~ /^aix\s*/i)) || (($config{var}{OS} =~ /^aix\s*/i) && ($cases[$i]->{os} !~ /^aix\s*/i)) || (($config{var}{OS} =~ /^rhels\s*/i) && ($cases[$i]->{os} !~ /^Linux\s*/i)) || (($config{var}{OS} =~ /^sles\s*/i) && ($cases[$i]->{os} !~ /^Linux\s*/i)) || (($config{var}{OS} =~ /^ubuntu\s*/i) && ($cases[$i]->{os} !~ /^Linux\s*/i))) {
                        push(@notrightcase, $cases[$i]->{name});
                        pop(@rightcase);
                        $skip = 1;
                    }
                }

            } elsif ($line =~ /^arch\s*:\s*(\w[\w\,]+)/) {
                next if $skip;
                $cases[$i]->{arch} = $1;
                if ($cases[$i]->{arch} !~ /$config{var}{ARCH}/) {
                    push(@notrightcase, $cases[$i]->{name});
                    pop(@rightcase);
                    $skip = 1;
                }
            } elsif ($line =~ /^hcp\s*:\s*(\w[\w\,]+)/) {
                next if $skip;
                $cases[$i]->{hcp} = $1;
                if ($cases[$i]->{hcp} !~ /$config{var}{HCP}/) {
                    push(@notrightcase, $cases[$i]->{name});
                    pop(@rightcase);
                    $skip = 1;
                }
            } elsif ($line =~ /^type\s*:\s*(\w[\w\,-]+)/) {
                next if $skip;
                $cases[$i]->{type} = $1;
                if ($cases[$i]->{type} !~ /$config{var}{TYPE}/) {
                    push(@notrightcase, $cases[$i]->{name});
                    pop(@rightcase);
                    $skip = 1;
                }
            } elsif ($line =~ /^stop\s*:\s*(\w[\w\,]+)/) {
                next if $skip;
                $cases[$i]->{stop} = $1;
            } elsif ($line =~ /^cmd\s*:\s*([\/\$\w].+)/) {
                next if $skip;
                $j                      = $j + 1;
                $z                      = 0;
                $cases[$i]->{cmd}->[$j] = &getvar($1);
                if ($cases[$i]->{cmd}->[$j] eq '') {
                    close(FILE);
                    return 1;
                }
            } elsif ($line =~ /^check\s*:\s*(\w.+)/) {
                next if $skip;
                $cases[$i]->{check}->[$j][$z] = &getvar($1);
                if ($cases[$i]->{check}->[$j][$z] eq '') {
                    close(FILE);
                    return 1;
                }
                $z = $z + 1;
            } elsif ($line =~ /^cmdcheck\s*:\s*(\w.+)/) {
                next if $skip;
                $cases[$i]->{cmdcheck}->[$j][$z] = &getvar($1);
                if ($cases[$i]->{cmdcheck}->[$j][$z] eq '') {
                    close(FILE);
                    return 1;
                }
                $z = $z + 1;
            } elsif ($line =~ /^end/) {
                next if $skip;
                $i = $i + 1;
            }
        }

        close(FILE);
    }
    
    if ($needshow) {
        if (@cases) {
            foreach my $case (@cases) {
                log_this("$case->{name}");
            }
        }else {
            log_this("Error: Please check the case name or command name");
        }   
        log_this("Case name not supported:", @wrongnamecase) if (@wrongnamecase);
        return 0;
    }
    
    if (@rightcase) {
        log_this("To run:",     @rightcase);
    }else {
        log_this("Error: No case to run, please check the case name or command name");
    }
    log_this("Not to run:", @notrightcase) if (@notrightcase);
    log_this("Duplicated case:", @samecase) if (@samecase);
    log_this("INCLUDEBAD case:", @errorcase) if (@errorcase);
    log_this("Case name not supported:", @wrongnamecase) if (@wrongnamecase);
    return 0;
}
sub getnodeattr
{
    my ($node, $attr) = @_;
    my @output = runcmd("lsdef -t node -o $node -i $attr");
    my $t;
    if ($::RUNCMD_RC) {

        #    return "Unknown";
        foreach $t (1 .. 40) {
            log_this("Error: could not get node attr $attr ");
            @output = runcmd("lsdef -t node -o $node -i $attr");
            last if ($::RUNCMD_RC == 0);
        }

    }
    if ($::RUNCMD_RC == 0) {
        foreach my $output1 (@output) {
            if ($output1 =~ /$attr=(\w.+)/) {
                log_this("$attr is $1");
                return $1;
            }
        }
    }
    return "Unknown";
}

sub gettablevalue
{
    my ($keyname, $key, $colname, $table) = @_;
    my @output = runcmd("gettab $keyname=$key $table\.$colname");
    return $output[0];
}

#to remove space and comment
sub trim
{
    my $str = shift @_;
    if ($str) {

        #       $str =~ s/\#/__wellnumber__/g;
        $str =~ s/^\s+|#.+|\s+$//g;

        #      $str =~ s/__wellnumber__/#/g;
    }
    return $str;
}

sub getvar
{
    my $str = shift @_;
    while ($str =~ /\$\$(\w+)/) {
        my $varname = $1;
        if (exists($config{var}{$varname})) {
            $str =~ s/\$\$$varname/$config{var}{$varname}/g;
        } else {
            log_this("Error: can't get varible $varname");
            return '';
        }
    }
    return $str;
}

sub getfunc
{
    my $str       = shift @_;
    my $func      = undef;
    my @para      = ();
    my $parameter = undef;
    my $value     = undef;
    while ($str =~ /__(\w+)\(([\s\,\w\$]*)\)__/) {
        $func      = $1;
        $parameter = $2;
        log_this("parameter is $parameter,fun is $func");
        @para = split /\s*,\s*/, trim($parameter);
        if ($func eq "GETNODEATTR") {
            $value = getnodeattr(@para);
            log_this("value is $value");
            if ($value eq "Unknown") {
                $value = '';
            }
        } elsif ($func eq "INC") {
            $value = $para[0] + 1;
        } elsif ($func eq "GETTABLEVALUE") {
            $value = gettablevalue(@para);
        }
        $str =~ s/__$func\($parameter\)__/$value/g;
    }
    return $str;
}

sub runcase
{
    my @output  = ();
    my $rc      = 0;
    my $j       = 0;
    my $z       = 0;
    my $lvalue  = undef;
    my $rvalue  = undef;
    my $op      = undef;
    my $failed  = 0;
    my $total   = 0;
    my $failnum = 0;
    foreach my $case (@cases) {
        my @record = ();
        $failed = 0;
        $j      = 0;
        $total  = $total + 1;
        my $now1  = timelocal(localtime());
        my $time1 = scalar(localtime());
        log_this("------START:$$case{name}::Time:$time1------");
        push @record, "------START:$$case{name}::Time:$time1------";
        push @record, "FILENAME:$$case{filename}";

        foreach my $cmd (@{ $$case{cmd} }) {
            $cmd = getfunc($cmd);

            #by
            my $runstart = timelocal(localtime());
            log_this("\nRUN:$cmd");
            push(@record, "\nRUN:$cmd");
            @output = &runcmd($cmd);
            $rc     = $::RUNCMD_RC;

            #by
            my $runstop      = timelocal(localtime());
            my $diffduration = $runstop - $runstart;
            log_this("\n[$cmd] Running Time:$diffduration sec");
            push(@record, ("\n[$cmd] Running Time:$diffduration sec"));

            log_this("RETURN: rc = $rc", "OUTPUT:", @output);
            push(@record, ("RETURN rc = $rc", "OUTPUT:", @output));


            foreach my $check (@{ $$case{check}->[$j] }) {
                if ($failed) {
                    last;
                }
                if ($check =~ /rc\s*([=!]+)\s*(\d+)/) {
                    $lvalue = $rc;
                    $op     = $1;
                    $rvalue = $2;
                    if ((($op eq '!=') && ($lvalue == $rvalue))
                        || (($op eq '==') && ($lvalue != $rvalue))) {
                        $failed = 1;
                    }
                    if ($failed) {
                        log_this("CHECK:rc $op $rvalue\t[Failed]");
                        push(@record, "CHECK:rc $op $rvalue\t[Failed]");
                        last;
                    } else {
                        log_this("CHECK:rc $op $rvalue\t[Pass]");
                        push(@record, "CHECK:rc $op $rvalue\t[Pass]");
                    }
                } elsif ($check =~ /output\s*([=!~]+)\s*(\S.*)/
                    && $check !~ /output\s*([=!~])\1/) {
                    $lvalue = join("\n", @output);
                    $op     = $1;
                    $rvalue = $2;
                    $rvalue = getfunc($rvalue);
                    if ((($op eq '=~') && ($lvalue !~ /$rvalue/))
                        || (($op eq '!~') && ($lvalue =~ /$rvalue/))
                        || (($op eq '==') && ($lvalue ne $rvalue))
                        || (($op eq '!=') && ($lvalue eq $rvalue))) {
                        $failed = 1;
                    }
                    if ($failed) {
                        log_this("CHECK:output $op $rvalue\t[Failed]");
                        push(@record, "CHECK:output $op $rvalue\t[Failed]");
                        last;
                    } else {
                        log_this("CHECK:output $op $rvalue\t[Pass]");
                        push(@record, "CHECK:output $op $rvalue\t[Pass]");
                    }
                } elsif ($check =~ /output\s*~~\s*(\S.*)/) {
                    $op     = "~~";
                    $failed = 1;
                    $rvalue = $1;
                    $rvalue = getfunc($rvalue);
                    my $num;
                    if ($rvalue =~ /(\d+)/) {
                        $num = $1;
                    }
                    $rvalue =~ s/(\d+)//;
                    foreach my $line (@output) {
                        chomp($line);
                        if ($line =~ /$rvalue/) {
                            if ($num =~ /^\d+$/) {
                                my $max = $num * 1.1;
                                my $min = $num * 0.9;
                                $line =~ /.*:.*: (\d+) /;
                                if ($1 < $max && $1 > $min) {
                                    $failed = 0;
                                    last;
                                }
                            } else {
                                next;
                            }
                        }
                    }
                    if ($failed) {
                        log_this("CHECK:output $op $rvalue $num\t[Failed]");
                        push(@record, "CHECK:output $op $rvalue\t[Failed]");
                        last;
                    } else {
                        log_this("CHECK:output $op $rvalue $num\t[Pass]");
                        push(@record, "CHECK:output $op $rvalue\t[Pass]");
                    }
                }
            }
            foreach my $cmdcheck (@{ $$case{cmdcheck}->[$j] }) {
                if ($cmdcheck) {
                    &runcmd($cmdcheck);
                    $rc = $::RUNCMD_RC;
                    if ($rc == 1) {
                        log_this("CMDCHECK:output $cmdcheck\t[Failed]");
                        push(@record, "CHECK:output $cmdcheck\t[Failed]");
                    } elsif ($rc == 0) {
                        log_this("CMDCHECK:output $cmdcheck\t[Pass]");
                        push(@record, "CHECK:output $cmdcheck\t[Pass]");
                    }
                }
            }
            $j = $j + 1;
        }
        my $now2  = timelocal(localtime());
        my $time2 = scalar(localtime());
        my $diff  = $now2 - $now1;
        if ($failed) {
            log_this("------END::$$case{name}::Failed::Time:$time2 ::Duration::$diff sec------");
            push(@record, "------END::$$case{name}::Failed::Time:$time2 ::Duration::$diff sec------");
        } else {
            log_this("------END::$$case{name}::Passed::Time:$time2 ::Duration::$diff sec------");
            push(@record, "------END::$$case{name}::Passed::Time:$time2 ::Duration::$diff sec------");
        }
        if ($failed) {
            $failnum = $failnum + 1;
            log_error(@record);
            if (defined($$case{stop}) && ($$case{stop} =~ /^yes$/)) {
                $stop_to_keep_env = 1;
                last;
            }
        }
    }
    log_this("\n\n");
    log_this("------Total: $total , Failed: $failnum------");
}

sub runcmd
{
    my ($cmd) = @_;
    my $rc = 0;
    $::RUNCMD_RC = 0;
    my $outref = [];
    @$outref = `$cmd 2>&1`;
    if ($?)
    {
        $rc          = $?;
        $rc          = $rc >> 8;
        $::RUNCMD_RC = $rc;
    }
    chomp(@$outref);
    return @$outref;

}

sub usage
{
    log_this("Usage:Run xcat test cases:");
    log_this("  xcattest [-?|-h]");
    log_this("  xcattest [-f configure file] [-b case bundle files]");
    log_this("  xcattest [-f configure file] [-t cases list]");
    log_this("  xcattest [-f configure file] [-c cmds list]");
    log_this("Show xcat test cases, bundle files, commands lists:");
    log_this("  xcattest [-b case bundle files] [-l]");
    log_this("  xcattest [-t cases list] [-l]");
    log_this("  xcattest [-c cmds list] [-l]");
    log_this("Show all bundle files, all commands:");
    log_this("  xcattest [-s command|bundle]");
    log_this("");
    return;
}

sub listbundlefiles
{
    my @bundlefiles     = ();
    
    #get all .bundle files from /opt/xcat/share/xcat/tools/autotest/bundle/
    opendir(DIR, $bundledir);
    my @files = readdir(DIR);
    foreach my $file (@files){
        next if (-d $file);
        if ($file =~ /\.bundle$/){
            push(@bundlefiles, $file);
         }
    }
    closedir(DIR);
    
    #read all .bundle files, get descriptions for each file.
    my $skip =0;
    my $line;
    foreach my $bundlefile (@bundlefiles) {
        if (!open(FILE, "<$bundledir/$bundlefile")) {
            log_this("Error: Can't open bundle file: $bundlefile");
            log_this("Use 'xcattest -s bundle' to list out available bundles");
            return 1;
        }
        $skip = 0;
        while ($line = <FILE>) {
            $line = &trim($line);
            next if (length($line) == 0);

            #description line is treated as a comment line for now
            if ($line =~ /^description\s*:\s*(.*)/){
                if (length($1) != 0) {
                    $bundlefilesinfo{$bundlefile} = $1;
                    $skip=1;
                    last; 
                }             
            }
         }
         close(FILE);
         if (!$skip){
             $bundlefilesinfo{$bundlefile} = "No description, add with \"description: details\" in bundle file";
         }
     }
     &listformatbundleinfo(%bundlefilesinfo);
    
    return 0;
}

sub listformatbundleinfo 
{
    my $maxlen = 0;
    foreach my $filename (keys %bundlefilesinfo) {
      $maxlen = length($filename) if (length($filename) > $maxlen);
    }
    $maxlen += 4;

    my $desiredwidth = 120;
    my $screenwidth  = (`tput cols` + 0);
    my $finallen = ($screenwidth > $desiredwidth ? $desiredwidth : $screenwidth);

    print "bundle files and descriptions:\n";
    
    foreach my $filename (sort { $a cmp $b } keys %bundlefilesinfo ) { 
        my @desc       = split(" ", $bundlefilesinfo{$filename});
        my $str        = "";
        my @formatdesc = ();
        foreach my $word (@desc) {
        if (length($str) + length($word) > $finallen - $maxlen) {
                $str =~ s/([^\s]+)\s$/$1/g;
                push @formatdesc, $str;
                $str = "";
            }
            $str .= $word . " ";
        }
        $str =~ s/([^\s]+)\s$/$1/g;
        push @formatdesc, $str;

        print "$filename";

        my $space = " " x ($maxlen - length($filename));
        print "$space$formatdesc[0]\n";
        delete $formatdesc[0];
        $space = " " x $maxlen;
        foreach my $line (@formatdesc) {
            print "$space$line\n" if (length($line));
        }
    }
}

sub listcommands
{
    my @files   = ();
  
    Get_Files_Recursive("$casedir");
    for (my $countfile = 0 ; $countfile < @ filespath ; $countfile++)
    {
        if ($filespath[$countfile] =~ /testcase\/(.*)\/case/) {
            log_this("$1");
            }
    }
}

sub getreport
{
    open(INDOC, ">$_[1]") || die("open STDOUT failed");
    print INDOC "Testcase                                                  Duration\n";
    print INDOC "------------------------------------------------------------------------------\n";
    close(INDOC);
    open(STDOUT, ">>$_[1]") || die("open STDOUT failed");
    open FD, "<$_[0]" or die "$?";
    while (<FD>) {
        if (/Time/) {
            s/------//g;
            $_ .= "\n" if /END/;
            print STDOUT $_;
        }
    }

    close(FD);
    close(STDOUT);
}


sub reordercases 
{
    my @caserange = ();
    my $line;
    if ($bundlelist) {
        my @bundles = split /,/, $bundlelist;
        foreach my $bundle (@bundles) {
            if (!open(FILE, "<$bundledir/$bundle")){
                log_this("Error: Can't open bundle file: $bundle");
                return 1;
            }
            while ($line = <FILE>) {
                chomp($line);
                next if ((length($line) == 0) || ($line =~ /^description\s*:\s*(.*)/));
                push(@caserange, $line);
            }
            close(FILE);
            my @refs=sort_caserange(@caserange);
            @caserange=@{$refs[0]};
        }
    }
    if ($caselist) {
        @caserange = split /,/, $caselist;
    }

    my @tmpcases = ();
    foreach my $case (@caserange) {
        my $i   = 0;
        my $hit = 0;
        foreach my $runcase (@cases) {
            if ($runcase->{name} eq $case) {
                $hit = 1;
                last;
            }
            $i++;
            next;
        }
        push(@tmpcases, $cases[$i]) if ($hit);
    }
    @cases = @tmpcases;
}

sub checkoptions 
{
    #-b,-c,-t,-s should be defined at least one
    if (!defined($bundlelist) && !defined($caselist) && !defined($cmdlist) && !defined($showbundlesorcommands)) {
        log_this("Error: please define options correctly, see below usage information:");
        return 1;
    }elsif ((defined($bundlelist) || defined($caselist) || defined($cmdlist)) && (! $needshow) && !defined($configinfo)) {
        #config file must be defined if the using -c,-b,-t to run test cases
        log_this("Error: To run test cases, please define the config file with -f option");
        log_this("       To show test cases, please use the -l option");
        return 1;
    }elsif (defined($showbundlesorcommands) && (defined($bundlelist) || defined($caselist) || defined($cmdlist))) {
        #-s can't be used together with -c,-b,-t 
        log_this("Error: -b,-c or -t can't be used together with -s");
        return 1;
    }elsif (defined($showbundlesorcommands) && $needshow) {
	log_this("Error: -s, -l can't be used together");
	return 1;
    } 
    
    #check the -s option, the value should be bundle or command
    if (defined($showbundlesorcommands)) {
       if (($showbundlesorcommands ne "bundle") && ($showbundlesorcommands ne "command")) {
           log_this("Error: please use \"bundle\" or \"command\" as the \-s value");
           return 1;
       }elsif ($showbundlesorcommands eq "bundle") {
           $showbundlefiles = 1;
       }else {
       }          
           $showcommandslist= 1;
    }

    #get and check config file and System label
    #this System label means only the [System] variable will be loaded
    if (defined($configinfo)) {   
        if ($configinfo =~ /(.*):(.*)/) {
            $configfile  = $1;
            $initallabel = $2;
            if ($initallabel ne $loadsysteminfo) {
                log_this("Error: $initallabel is not supported!");
                return 1;
            }
        }else {
            $configfile = $configinfo;
        }
        if (!(-e $configfile)) {
            log_this("Error: Can't open config file: $configfile");
            return 1;
        }        
    }
    
    #check bundle files
    if (defined($bundlelist)) {
        my @bundles = split /,/, $bundlelist;
        foreach my $bundle (@bundles){
            if ("$bundledir/$bundle" !~ /\.bundle$/) {
                log_this("Error: please input the .bundle file");
                log_this("Use 'xcattest -s bundle' to list all bundle files");
                return 1;
            }
            if (!(-e "$bundledir/$bundle")) {
                log_this("Error: Can't open bundle file: $bundle");
                log_this("Use 'xcattest -s bundle' to list all bundle files");
                return 1;
            }
        }
    }
    
    return 0;
}


