########################################################################################
#
# scan & check iso7185pat output for test fails
#
# intended to be used as follows:
# to check an output file:
#	 awk -f patCheck.awk iso7185pat.cmp
#
#  or pipe iso7185pat's output directly into awk:
# 	 iso7185pat | awk -f patCheck.awk
#  where iso7185pat is the compiled test program
#
#  test output is assumed to be lines containing fields in one of these formats:
#
#   "Testnnn: " "result string" " s/b " "expected string"	-- (A)
#	eg
#	Integer1:   121 s/b 121
#
#   "Testnnn: " "result string"					-- (B)
#   "Testnnn: " " s/b " "expected string"
#	eg
#	Control10:  one two three four five six seven eight nine-ten nine-ten
#	Control10:  s/b one two three four five six seven eight nine-ten nine-ten
#
#   "Testnnn: " "result string"					-- (C)
#   " s/b " "expected string"
#	eg
#	Array19:  20 k 19 j 18 i 17 h 16 g 15 f 14 e 13 d 12 c 11 b
#	s/b:  20 k 19 j 18 i 17 h 16 g 15 f 14 e 13 d 12 c 11 b
#
#   "Testnnn: "							-- (D)
#    "result string" " s/b " "expected string"
#	eg
#	Array37:
#	hello, guy s/b hello, guy
#
#   "Testnnn: "							-- (E)
#    "result string1"
#    "result string2"
#    ...
#    "s/b"  possibly other stuff in the line
#    "expected string1"
#    "expected string2"
#    ...
#	eg
#	Character86:
#	   hello, world
#	  hello, world
#	 hello, world
#	hello, world
#	hello, worl
#	Character86:  s/b:
#	   hello, world
#	  hello, world
#	 hello, world
#	hello, world
#	hello, worl
#
#
# notes:
#  - test names start with a Capital letter
#  - to avoid contradiction between formats (D) & (E):
#  	format (D) 2nd line must contain both the test and expected results, separated by " s/b "
#	format (E) 2nd line must contain only the test result
#  - spaces are significant (except when booleans are tested)
#  - " s/b " is interchangeable with " s/b: "
#  - The case of all boolean values (ie 'False' & 'True') is converted to lower case when
#       comparing test results with expected values.
#  - tolerance of real value compares is determined by nr significant digits in expected result.
#  - gawk is assumed.  may need adjusting if other versions of awk are used instead
#
####################################################################################################

function abs(x) {
   return ((x < 0.0) ? -x : x)
}

BEGIN { nr_fails = 0;
        nr_passed = 0;
	    nr_unrecognised = 0;
        endFound = 0;
        metering = 1;
      }


{
   if( /^\*+ / ) {
      # test separator line
      #print;
      metering = 0;
      FS = ": | s/b: | s/b ";
   }
   else if( metering ) {
      # metering lines
      #print;

      # find and store boolean output format.
      # Use this later to make test string match expected results string
      if( $1 ~ /Boolean/) {
	 # found the boolean default outputs
	 getline; getline; # skip counter lines
	 getline;
	 false_str = $1;
	 getline;
	 true_str = $1;
	 #print "default false is '"false_str"', default true is '"true_str"'";
      }

      # maybe we could figure out real nr formats and possibly other stuff to help when
      # comparing test results with expected values where there are compiler dependencies
   }
   else if( /^[A-Z]\w+[0-9]+/ && NF==3 ) {
      # format (A), $1 = testnnn, $2 = result, $3 = expected
      pass = 1;

      # make booleans a special case
      # result case stored in false_str, convert to lower case to match expected string
      # too bad for test strings like 'this is TRUE', where the TRUE is not a boolean output
      if( $3 ~ /\<false\>/ ) {
	 gsub( "\\<"false_str"\\>", "false", $2);
      }
      if( $3 ~ /\<true\>/ ) {
	 gsub( "\\<"true_str"\\>", "true", $2);
      }
      #print "format (A), $1 is '", $1, "' $2 is '", $2, "' $3 is '", $3, "'";

      if( $3 ~ /^ *(false|true)([ \t]+(false|true))* *$/ ) {
	 #print "comparing booleans: '"$2"' vs '"$3"'" ;
	 # field width unknown, so split & compare each element
	 # but only when test contains only booleans
	 # add non boolean value to test string if you need to compare field widths
	 n2 = split( $2, b2, " " );
	 n3 = split( $3, b3, " " );
	 #print "n2 is", n2, "n3 is ", n3, "b2[1] is '"b2[1]"' b3[1] is '"b3[1]"'";
	 if( n2 == n3 ) {
	    for( i=1; i<=n2; i++ ) {
	       #print "comparing booleans: '"b2[i]"' vs '"b3[i]"'" ;
	       if( b2[i] != b3[i] )
		  pass = 0;
	    }
	 }
	 else {
	    #print "result & expected have different nr elements:", n2, "vs ", n3;
	    pass = 0;
	 }
      }
      else if( /^Real/ && $3 ~ /^ *[-+]?[0-9]+\.[0-9]+e[+-][0-9]+ *$/ ) {
	 #print "comparing real numbers, tolerance depends on nr sig digits in test string
	 n = split( $3, r, "[.eE]");
	 m =2;
	 for( i=length(r[2]); i>0; i-- ) m = m*10;
	 #print "m is", m;
	 if( abs($3 - $2)*m > abs($3) ) {
	    print "difference is", abs($3 - $2)*m
	    pass = 0;
	 }
      }
      else if( $2 != $3 ) {
	 #print "result != expected";
	 pass = 0;
      }

      if( pass == 1 ) {
	 print $1, "passed";
	 nr_passed++;
      }
      else {
	 print "n>", "'"$1"'", "r>", "'"$2"'", "e>", "'"$3"'";
	 print $1, " ------ fail\n";
	 nr_fails++;
      }
   }

   else if( /^[A-Z]\w+[0-9]+/ ) {
      # format (B), format(C) $1 = Testnnn, $2 = result
      # format (D), format(E) $1 = Testnnn, NF = 1 or $2 = ""
      pass = 1;
      #print "2a>", "'"$1"'", "2a>", "'"$2"'";
      test_name = $1;
      sub(/:$/, "", test_name); # remove possible trailing ':'
      result_str = NF < 2 ? "" :$2;
      getline;
      if( NF==1 ) {
	 # format (E), $1 = result
	 #print "first line of multiline result>", $1;
	 i = 1;
	 do {
	    r[i] = $1;
	    #print "r"i">", r[i];
	    i++;
	    getline;
	 } while( !/s\/b/ );
	 n = i-1; #nr test lines
	 # print "$1 is '"$1"'", "test_name is '"test_name"'";
	 i = 1;
	 for( i=1; i<=n; i++ ) {
	    getline;
	    # if result expected to contain booleans, make them lower case
	    if( $1 ~ /\<(false|true)\>/ ) {
	       gsub( "\\<"true_str"\\>", "true", r[i]);
	       gsub( "\\<"false_str"\\>", "false", r[i]);
	    }

	    if( r[i] != $1 ) {
	       print test_name"["i"] r>", "'"r[i]"'", "e>", "'"$1"'";
	       pass = 0;
	    }
	    else {
	       #print "r"i">", $1, "OK";
	    }
	 }
      }
      else {
	 # format (B), $1 == Testnnn, result_str found earlier
	 # format (C), result_str found earlier
	 # Format(D), $1 == result string
	 # expected_str is last field in current line
	 #for( i=1; i<=NF; i++ ) print "2b>", "'"$i"' ";
	 if( result_str == "" ) {
	    #format (D)
	    result_str = $1;
	 }
	 expected_str = $NF;
	 # if result expected to contain booleans, make them lower case
	 if( expected_str ~ /\<(false|true)\>/ ) {
	    gsub( "\\<"true_str"\\>", "true", result_str );
	    gsub( "\\<"false_str"\\>", "false", result_str );
	 }
	 #print " n>", "'"test_name"'", "r>", "'"result_str"'", "e>", "'"expected_str"'";
	 if( result_str != expected_str ) {
	    pass = 0;
	    print "r>", "'"result_str"'", "e>", "'"expected_str"'";
	 }
      }

      if( pass == 1 ) {
	 print test_name, "passed";
	 nr_passed++;
      }
      else {
	 print test_name, " ------ fail\n";
	 nr_fails++;
      }
   }
   else if( $0 ~ "========= tests completed =========") {
       endFound = 1;
   }
   else if( NF != 0 ) {
      print "x>", "'"$0"'", "xxxxxxxxxxxxxx unrecognised output line";
      nr_unrecognised++;
   }
   else {
      # it's a blank line - ignore it
   }
}


END {
   #print "\n" strftime("%Y-%m-%d %H:%M:%S"), "Test Results:\n"
   print "\n - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ";
   print "\nPascal Acceptance Test:", strftime();
   if( endFound != 1 )
       print ">>>> tests incomplete >>>>>>", nr_passed, "passed,", nr_fails, "failed\n";
   else if( nr_fails == 0)
      print "all " nr_passed " tests passed, no failures\n";
   else if( nr_fails == 1 )
      print nr_passed, "tests passed, 1 test failed\n";
   else
      print nr_passed, "tests passed ,", nr_fails, " tests failed\n";
   if( nr_unrecognised != 0 )
      print  nr_unrecognised,"unrecognised test line",(nr_unrecognised==1)? "\n": "s\n";
}
