#!/usr/bin/perl

#Whether or not to perform interactive tests
$interactive=1;

#The number of sensors being tested
$total_sensor_count = 107;
$total_vv_sensor_count = 134;

#
## This script utilizes several intermediate files to collect and 
## then analyze output from the sensors command. All file names and
## the function used to clean up after execution can be found here
## at some point in the futire we should look into using pipes
## instead of these txt files
#

#The files storing results from some prior successful run that
#can be used for automated testing
$prev_output_default  = "valid-default.txt";
$prev_output_verbose  = "valid-verbose.txt";
$prev_output_vverbose = "valid-vverbose.txt";

#The files used when doing simple option tests
$short_form = "short.txt";
$long_form  = "long.txt";

#The file to store results from individual queries
$default = "_default.txt";
#The file to store results from -a queries
$default_a = "_d.txt";

#The file to store results from individual queries
$verbose = "_verbose.txt";
#The file to store results from -a queries
$verbose_a = "_v.txt";

#The file to store results from individual queries
$vverbose = "_vverbose.txt";
#The file to store results from -a queries
$vverbose_a = "_vv.txt";

#The file used to store results from -l query for all group names
$group_file = "_groups.txt";

sub cleanup{
  my $rm_files = "$default $default_a $verbose $verbose_a $vverbose $vverbose_a " .
                 "$group_file $short_form $long_form";

  system(" rm -f $rm_files");
}

#
## Used to differentiate between the type of sensors output
## we are currently parsing. Necessary for determining tivial changes
#
$default_mode = "default_mode";
$verbose_mode = "verbose_mode";

#
## The units that linear sensors use, so we can ignore trival reading 
## differences. We compare individual output against group output and 
## the sensor reading itself may have changed. The threshold values 
## are the range that the values can differ by without us prompting the
## user for input.
#

$temp = "Degrees C";
$temp_threshold = 10;

$volt = "Volts";
$volt_threshold = 2.0;

$rpms = "RPM";
$rpms_threshold = 100;

#
##The path to the sensors executable. Sensors can be in many places.
#

#print "Please enter the full path to sensors, include sudo if necessary:";
#chomp($sensors = <STDIN>);
$sensors = "sudo /usr/sbin/sensors";

#
## We need to determine if this test run utilizes interaction with the user.
## There are several times we can prompt the user for whether or not a case
## result is acceptable. y is default value. If n is entered, no prompts will
## occur, issues will just be logged.
#
sub query_interaction_level {
  print "Would you like to run the interactive portions of the test(y/n)[y]:";
INTERACTIVE:  
  chomp($in = <STDIN>);

  if($in ne "n" && $in ne "y" && $in ne ""){
    print "Please enter (y/n)[y]:";
    goto INTERACTIVE;
  }

  if($in eq "n"){ $interactive = 0;}
  elsif($in eq "y" || $in eq "") {$interactive = 1;}
}

#
## This function is used by the interactive mode to  
## prompt the user whether an occurence is acceptable
#
sub verify_correct{
INPUT:  
  chomp($in = <STDIN>);
  
  if($in ne "n" && $in ne "y" && $in ne ""){
    print "Please enter (y/n)[y]:";
    goto INPUT;
  }

  if ($in eq "y" || $in eq "") {return 1;}
  else {return 0;}
} 

#
## Clear the decks for test start
#
#query_interaction_level();
$interactive = @ARGV[0];
system("clear");


#
## Test simple stuff first, basically need to check off 
## that both option forms are equivalent
#
test_simple_option("-u", "--usage");
test_simple_option("-h", "--help");
test_simple_option("-V", "--version");
test_simple_option("-l", "--list-groups");
test_simple_option("-i", "--sdr-info");
test_simple_option("-v", "--verbose");

test_individual_sensors();
test_all_sensors();
test_group();

#test_flush_cache;
cleanup;

#
## The majority of trivial differences come from linear
## sensors. This function is used when comparing linear
## sensors in verbose mode
#
sub check_linear_verbose{
  my $str_1 = @_[0];
  my $str_2 = @_[1];

  #get the actual reading
  my @words_1 = split(/ /, $str_1);
  my @words_2 = split(/ /, $str_2);

  #get rid of the units
  my $unit_1 = pop(@words_1);
  #print "Popped $unit_1\n";
  my $unit_2 = pop(@words_2);
  #print "Popped $unit_2\n";

  #get the two values
  my $val_1 = pop(@words_1);
  my $val_2 = pop(@words_2);

  #need an extra pop since Temp has a two word unit
  if($val_1 =~/Degrees/){
    $val_1 = pop(@words_1);
    $val_2 = pop(@words_2);
  }

  #print "Checking $val_1 : $val_2\n";
  my $diff  = abs($val_1 - $val_2);

  if($unit_1 =~/C/){
    #print "temp diff $diff\n";
    return ($diff < $temp_threshold);
  }
  elsif($unit_1 =~/$volt/){
    #print "volt diff $diff\n";
    return ($diff < $volt_threshold);
  }
  elsif($unit_1 =~/$rpms/){
    #print "rpms diff $diff\n";
    return ($diff < $rpms_threshold);
  }
  else{
    return 0;
  } 
}

#
## The majority of trivial differences come from linear
## sensors. This function is used when comparing linear
## sensors in default mode
#
sub check_linear_default{
  my $str_1 = @_[0];
  my $str_2 = @_[1];

  #get the actual reading
  my @words_1 = split(/ /, $str_1);
  my @words_2 = split(/ /, $str_2);
  my $unit_1 = @words_1[$#words_1-2];
  my $unit_2 = @words_2[$#words_2-2];
  my $val_1 = @words_1[$#words_1-3];
  my $val_2 = @words_2[$#words_2-3];

  #get the units
  my $i;
  #print "\n\n";
  #print"Entered linear_default\n";
  #print "Checking $val_1 : $val_2\n";
  
  my $diff  = abs($val_1 - $val_2);

  #print "About to check for units: $unit_1\n";

  if($unit_1 =~/C/){
    #print "temp diff $diff\n";
    return ($diff < $temp_threshold);
  }
  elsif($unit_1 =~/V/){
    #print "volt diff $diff\n";
    return ($diff < $volt_threshold);
  }
  elsif($unit_1 =~/RPM/){
    #print "rpms diff $diff\n";
    return ($diff < $rpms_threshold);
  }
  else{
    return 0;
  } 

}

#
## When comparing current sensors output to a previously vetted output
## we may encounter some acceptable differences. Most of the times this
## is due to linear sensors and we can automatically accept the difference
## if it falls within the preset threshold. Otherwise we can prompt the 
## user
#
sub check_trivial {
  my $trivial = 0;
  my $sensor_a = @_[0];
  my $sensor_b = @_[1];
  my $mode     = @_[2];   
  
  #print"Entered check_trivial\n";

  #if the lines are an actual linear reading we can 
  #probably just determine if its trivial automatically
  #The method we have to use depends on whether or not the output is 
  #default or verbose mode 
  if( ($sensor_a =~/low\S*nom\S*high/ && 
       $sensor_b =~/low\S*nom\S*high/) && 
	     $mode eq $default_mode
    )
  { return check_linear_default($sensor_a, $sensor_b);}
  elsif($mode eq $verbose_mode && 
        ( ($sensor_a =~/$temp/ && $sensor_b =~/$temp/) ||
          ($sensor_a =~/$volt/ && $sensor_b =~/$volt/) ||
          ($sensor_a =~/$rpms/ && $sensor_b =~/$rpms/) 
	)
       )
  {return check_linear_verbose($sensor_a, $sensor_b);}
  else {return 0;}
}

#
## Diff two files line by line
#
sub interactive_line_diff {

  my $mode = @_[2];
  open(D1, @_[0]) or die "Can't open file @_[0]";
  open(D2, @_[1]) or die "Can't open file @_[1]";
  
  $current_record_id = -1;

  #read in and compare the two files line-by-line
  while(<D1>){
    $a = <D2>;
    print ".";
    
    if($mode eq $verbose_mode &&
        $_ =~/Record ID/){
      my @id = split(/:/, $_);
      chomp($current_record_id = @id[1]);
    }   

    #If the files differ than it might just be trivial
    #if not we must either go interactive or dump to stdout
    if($_ ne $a && !check_trivial($_, $a, $mode) )
    {

      if($mode eq $verbose_mode){ print "\nIn sensor record $current_record_id:\n"; }
      else{print "\n";}
      
      print "$_ doesn't match\n$a\n";
      
      if($interactive){
        print("Is the difference trivial? (y/n)[y]:");
        verify_correct();
        print"\nResuming diff:\n";
      }

    }
  }

  print "\n";
}

sub sensor_diff {

  my $SEN_A = @_[0];
  my $SEN_B = @_[1]; 
 
  #print "In sensor diff\n";
  do{
    chomp($sensor_a = <$SEN_A>);
    chomp($sensor_b = <$SEN_B>);
    #print "READ $sensor_a  :  $sensor_b\n";
    
    if($sensor_a ne $sensor_b){
      check_trivial($sensor_a, $sensor_b);
    }
  
  }while($sensor_a ne "");

  #update on progress
  select(STDOUT);
  $|=1;
  print ".";
}

sub sensor_diff_file {
  my $new_file   = @_[0];
  my $check_file = @_[1];
  my $ordered    = @_[2];
  my $rand_sensors = @_[3];


  #print "About to diff $new_file : $check_file\n";
  if($ordered){
    open(NEW, $new_file) or die "Can't open file @_[0]";
    open(CHECK, $check_file) or die "Can't open file @_[1]";
    do{
      chomp($a = <NEW>);
      chomp($b = <CHECK>);

      #print "READ $a  :  $b\n";
      
      if($a =~/Record ID/){
        print "Checking sensor: $a\n";
        sensor_diff(NEW, CHECK);
      }
      
    }while(!eof(NEW));
  }
  else{
    open(NEW, $new_file) or die "Can't open file @_[0]";
    my @sens = split(/ /, $rand_sensors);
    
    do{
      #make sure that this is a sensor we requested and in the order we requested
      chomp($a = <NEW>);
      ($a =~/$expected_sensor/) or die "sensors -s $rand_sensors option resulted in improper output\n";
      
      #need to index into the file used to validate output 
      #for the record that matches the current record 
      open(CHECK, $check_file) or die "Can't open file @_[1]";
      chomp($b = <CHECK>);
      while($a ne $b){
       chomp($b = <CHECK>);
       #print "Read $b\n";
      }

      #print "READ $a  :  $b\n";
     
      #now that the proper spot has been found
      sensor_diff(NEW, CHECK);
      close(CHECK);
      
    }while(!eof(NEW));
  }
  print "\nOutput Matches!\n\n";
}

#
## This function will test the output for each sensor individually
## using sensors -s. There are several ways this can happen
## 
## 1. In interactive mode, if the $prev_output_<mode> is not present, the user 
##    will be presented with and asked to confirm the output of each individual call.
##    The option will be presented to create $prev_output_<mode>
##
## 2. In interactive mode, if the $prev_output_<mode> file is present,
##    the user will be given the option of only vetting non-trivial differences
##
## 3. In non-interactive mode, if the $prev_output_<mode> file is present, only
##    non-trivial differences will be logged to stdout, otherwise all output will
##    be logged
#
sub test_individual_sensors(){

  print "Test: About to test the output that results when individually " .
        "querying each sensor. If a compilation of previously validated " .
        "results is available this step can be mostly automated.\n\n";
  
  #first figure out if we have prev_output to work with
  my $prev_output_avail = 0;
  if( -e $prev_output_default && 
      -e $prev_output_verbose && -e $prev_output_vverbose){
    $prev_output_avail = 1;
  }
 
  #figure out if we should save files we generated for ease later
  my $construct_prev_files = 0;
  if(!$prev_output_avail){
    if($interactive){
      print "Would you like to contruct a set a files that you can use in the" .
            " future to automate this test? (y/n)[y]:";
      $construct_prev_files = verify_correct();
    }
    else{$construct_prev_files = 1;}
  }

  #figure out if we should use the prev files in interactive mode
  my $full_interactive = 0;
	if ($interactive && $prev_output_avail){
    print "Would you like to use previous output instead of going " .
		      "full interactive? (y/n)[y]:";
		$full_interactive = !verify_correct();
  }
	elsif($interactive && !$prev_output_avail){$full_interactive=1;}
 
  #
	## Woof! Now we can finally get down to buisness
	## Actions taken based on value
	#
	## $prev_output_avail    $full_interactive  Action Taken
	##       0                       0          Output all results to stdout
	##       0                       1          Query after each result for approval
	##       1                       0          Only dump non-trivial differences to stdout
	##       1                       1          Query after each result for approval
	#
  print "Individually querying each sensor:\n";
  for($i=1;$i<=$total_sensor_count;$i++){
    if($full_interactive){system("clear");}
    elsif($interactive){print ".";}

    #
		## If there is no 
		#
    if(!$prev_output_avail || $full_interactive){

      if($i==52) {goto INNER_HACK;}
      #
      ##query the default output level
      #
			system("$sensors -s $i");
		  if($full_interactive){
        print("Is the default level output for sensor $i correct? (y/n)[y]:");
        if(!verify_correct()){$construct_prev_files=0;}
		  }

      #
      ##query the verbose output level
      #
      system("$sensors -v -s $i");
			if($full_interactive){
        print("Is the verbose level output for sensor $i correct? (y/n)[y]:");
        if(!verify_correct()){$construct_prev_files=0;}
      }
    
		  #
      ##query the very verbose output level
      #
INNER_HACK:
      if($interactive){
        system("$sensors -vv -s $i");
        print("Is the very verbose level output for sensor $i correct? (y/n)[y]:");
        if(!verify_correct()){$construct_prev_files=0;}
      }
    }

    #
		## We always collect the output into files
		#
    if($i != 52){
    system("$sensors -s $i >> $default");
    system("$sensors -v -s $i >> $verbose");
    }
    system("$sensors -vv -s $i >> $vverbose");
    
  }

  #need to finish up the sensors that are only displayed in -vv mode
  for($i=$total_sensor_count+1;$i<=$total_vv_sensor_count;$i++){
    if(!$full_interactive){print ".";}

    if(!$prev_output_avail || $full_interactive){
      
			#
      ##query the very verbose output level
      #
      system("$sensors -vv -s $i");
      if($full_interactive){
        print("Is the very verbose level output for sensor $i correct? (y/n)[y]:");
        verify_correct();
      }
	  }
    system("$sensors -vv -s $i >> $vverbose");
  }

	#now for post-processing
  if($prev_output_avail && !$full_interactive){
    print "\n\nChecking default individual queries against previous results:\n";
    interactive_line_diff($default, $prev_output_default, $default_mode);
    print "\n\nChecking verbose individual queries against previous results:\n";
    interactive_line_diff($verbose, $prev_output_verbose, $verbose_mode);
    print "\n\nChecking very verbose individual queries against previous results:\n";
    interactive_line_diff($vverbose, $prev_output_vverbose, $verbose_mode);
    print "\n";
	}

  # if we elected to to interactively verify each result and
	# none of them failed we can save these results for the 
	# next time we run this test
	if($construct_prev_files){
	  system("cp -i $default  $prev_output_default");
	  system("cp -i $verbose  $prev_output_verbose");
	  system("cp -i $vverbose $prev_output_vverbose");
	}

}

sub test_all_sensors {
  print "Test: About to test the output that results when querying" .
        "all sensors. Automatically verified against output of individual queries\n\n";
  #
  ##Test the default output
  #
  system("$sensors > $default_a");
  print "\nChecking if the default sensors output is correct:\n";
  interactive_line_diff($default, $default_a, $default_mode);

  #
  ##Test the verbose level output
  #
  system("$sensors -v > $verbose_a");
  print "\nChecking if the verbose sensors output is correct:\n";
  $mode=$verbose_mode;
  interactive_line_diff($verbose, $verbose_a, $verbose_mode);

  #
  ##Test the very verbose level -a output
  #
  system("$sensors -vv > $vverbose_a");
  print "\nChecking if the very verbose output is correct:\n";
  interactive_line_diff($vverbose, $vverbose_a, $verbose_mode);
  print "\n";
}

sub test3 {
  print "How many sensors do you want to test the -s option with:";
  my $s_count = <STDIN>;
  chomp($s_count);
  
  for($i=1;$i<=$s_count;$i++){
    my $rand_sensors = "";
    for($j=1;$j<=$i;$j++){
      #rand will return from 0-$total_sensor_count-1
      $rand_sensors .= int(rand($total_sensor_count)) + 1;
      $rand_sensors .= " ";
    }
    
    #print "Querying sensors $rand_sensors\n";
    print "Attempting sensors -s with $i sensors specified \n";
    $file_out = "s_$i.txt";
    system("sudo $sensors -s \"$rand_sensors\" > $file_out");
    sensor_diff_file($file_out, $default, 0, $rand_sensors);
  }
}

sub test_simple_option{
  my $short = @_[0];
  my $long  = @_[1];
  print "Test: testing that <sensors $short> is the same as <sensors $long>\n";

  print"\n<$sensors $short>\n";
  system("$sensors $short");
  if($interactive){
    print("Is the output for <$sensors $short> correct? (y/n)[y]:");
    verify_correct;
  }

  system("rm -f $short_form $long_form");
  system("$sensors $short > $short_form");
  system("$sensors $long  > $long_form");
	interactive_line_diff($short_form, $long_form);

}

sub test_group{
  system("$sensors --list-groups >groups.txt");
  open(GROUP, "groups.txt") or die "Can't open groups.txt";

  #get rid of the first fluff line
  <GROUP>;

  while(<GROUP>){
    if($interactive){
      system("clear");
    }
   chomp($_);
    #print "String is $_\n";

    if( ($_ =~/System groups/) ||
        ($_ =~/Aliases/) ||
	($_ !~/\S*/)){
      next;
    }

    if(/\=/){
      #print "This is an alias is $_\n";
      @sp = split(/\=/, $_);
      #print "First element is: @sp[0]\n";
      $_ = @sp[0];
      chop;
    }

    print "ABOUT TO CHECK GROUP \"$_\"\n\n";

    #default
    print("$sensors -g \"$_\"\n");
    system("$sensors -g \"$_\"");
    if($interactive){
      print("Is the output for sensor group \"$_\" correct? (y/n)[y]:");
      verify_correct;
    }

    #verbose
    print("\n$sensors -v -g \"$_\"\n");
    system("$sensors -v -g \"$_\"");
    if($interactive){
      print("Is the -v output for sensor group \"$_\" correct? (y/n)[y]:");
      verify_correct;
    }
    
    #very verbose
    print("\n$sensors -vv -g \"$_\"\n");
    system("$sensors -vv -g \"$_\"");
    if($interactive){
      print("Is the -vv output for sensor group \"$_\" correct? (y/n)[y]:");
      verify_correct;
    }
  }
}


sub test_flush_cache {

 my $good = 0;
 my $bad  = 0;
 
 for($z=0; $z<$cache_flush_count; $z++){
   system("$sensors -f");
   system("$sensors >cache.txt");

   open(CACHE, "cache.txt") or die "Can't open cache file cache.txt\n";

   my $sensor_count = 0;
   while(<CACHE>){$sensor_count++;}

   if($sensor_count == 107){
     print "Sensors cache correctly retrieved after flushing on run $z\n";
     $good++;
   }
   else{
     print "FAILURE: only $sensor_count sensors cached after flushing on run $z\n";
     $bad++;
   }

   close(CACHE);
   system("rm -f cache.txt");
 }

 print "Total of $good successes $bad failures.\n";
}
