#!/bin/sh

# $Id: runall.sh 580483 2007-09-28 20:55:52Z sebor $
#
##############################################################################
#
# 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.
#
# Copyright 1999-2007 Rogue Wave Software, Inc.
#
##############################################################################
#
# NAME
#     runall.sh, run - run programs and analyze output
#
# SYNOPSIS
#     runall.sh [option(s)...] [file(s)...]
#
# DESCRIPTION
#    Takes a list  of files as arguments. The files  are assumed to be
#    executable; if a  file isn't executable, it is  assumed to have a
#    .o suffix, in  which case it is considered as  an object file and
#    its existence is  taken as success. If the  file isn't executable
#    and doesn't have a .o suffix, it is considered a failure.
#
#    Runs  all executables  named on  the command  line giving  each a
#    fixed amount  of time to complete. If  the program fails  to exit
#    within  the  alotted   amount  of  time,  it  is   killed  via  a
#    signal. Collects  and reports statistics on  each executable with
#    the -p option tries to post  a result file into a revison control
#    system (Perforce).
#
# OPTIONS
#    -a <age>
#            Do not  run programs,  just collect output  unless output
#            file is older than `age' minutes.
#
#    -c or --color
#            Produce colorized output (default). Requires xterm.
#
#    -d <text>
#            Use   `text'  as   a   description   when   submitting  a
#            report. If `text' is  an existing file, use  the contents
#            of the file as the description.  Meaningful only with -p.
#
#    -l <fname> or --log <fname>
#            Append the file  named `fname'  to the end  of the result
#            file before posting. Meaningful only with -p.
#
#    --label <name>
#            Label files with the  label `name', or generate own label
#            name if  "--label -".  The generated label  will have the
#            following   format:   stdlib-<compiler>-<os>-<timestamp>.
#            Meaningful only with -p.
#
#    -m or --mono or --nocolor
#            Produce monochrome output (no escape sequences).  Default
#            for all terminals other than xterm.
#
#    -o <fname>
#            Redirect  process output  to the  named file.  The option
#            affects only the  next executable to be run,  it is reset
#            for all others.
#
#    -p <postdir>
#            Run  tests and examples  and post  results in  a revision
#            control system (Perforce by default).
#
#    --ph    Run plumhall LVS tests
#
#    --rate <percent>
#            Minimum pass rate (in percent) to post.  If the pass rate
#            is  lower,  send email  to  $P4USER  notifying that  post
#            failed. Meaningful only with -p.
#
#    -r <fname>
#           Compare  results against `fname' and report differences as
#           regressions.
#
#    -s <signo>
#           Set the  signal to  be sent to  the hung  process. SIGKILL
#           (signo 9) is default.
#
#    -t <sec>
#           Set process  timeout to  `sec' seconds. Negative  value of
#           `sec' allows an infinite timeout.
#
#    -T <ts0,ts1,ts2>
#           Specifies the timestamps  corresponding to the local times
#           at which the following events started:  configuration, lib
#           build,  and  tests  (and examples) build,  in  that order.
#           Meaningful only with -p.
#
#    -X <text>
#           Specifies additional  command line options to be passed to
#           the executable being run.
#
#    -x <dir>
#           Run  subsequent filenames as examples  and  diff   output  
#           against  files  in  the respective subdirectories of `dir' 
#           (manual/ or tutorial/), if they exist. 
#
##############################################################################


##############################################################################
# GLOBAL VARIABLES
##############################################################################

TIMEOUT=10               # seconds by default
SIGNO=9                  # signal to be sent by default (SIGKILL by default)
REGRESS="."              # regression file (cwd by default, ignored)
LINES=24                 # default number of continuous lines
MANDIR="-"               # directory containing manual (none by default)
MIN_PASS_RATE=70         # minimum acceptable pass rate
POSTFILE="."             # posted results file name
POSTDESC="test results"  # description used when posting
LABEL="-"                # generate a label when posting
OUTFILE="."              # default output file name
AGEMAX=0                 # if non-zero will not run tests, just looks at
                         # test results not older than AGEMAX minutes
XOPTS="-D ."             # additional options to be passed to executable
BUILDLOG=/dev/null       # log to append to results when posting
TIMESTAMPS="-"           # times when lib and tests builds started

case "`uname`" in
    CYGWIN*)
      DIFF_STRIP_CR="--strip-trailing-cr";; # strip trailing CR on CygWin
esac


##############################################################################
# FUNCTION DEFINITIONS
##############################################################################

setcolors ()
{
    COLORS=$1

    if [ $COLORS -ne 0 ] ; then
        ESC0="[30;30;0m"          # escape sequence turning off any formatting

        CLR_BLACK="[30;30;1m"
        CLR_RED="[30;31;1m"
        CLR_GREEN="[30;32;1m"
        CLR_BLUE="[30;34;1m"
        CLR_YELLOW="[30;33;1m"
        CLR_MAGENTA="[30;35;1m"
        CLR_CYAN="[30;36;1m"
        CLR_GRAY="[30;37;1m"

        CLR_ERROR="[41;33;1m"     # serious error (core dump, etc)
        CLR_SUCCESS="[42;37;1m"   # important improvement (e.g., regress < 0)
        CLR_WARN="[41;33;7;1m"    # potential problem (decrease in assertins)
    else
        unset ESC0
        unset CLR_BLACK
        unset CLR_RED
        unset CLR_GREEN
        unset CLR_BLUE
        unset CLR_YELLOW
        unset CLR_CYAN
        unset CLR_GRAY
        unset CLR_ERROR
        unset CLR_SUCCESS
        unset CLR_WARN
    fi
}

# post results in Perforce
post_results ()
{
    p4 opened 2>&1 | grep "File(s) not opened on this client."
    if [ $? -ne 0 ] ; then
        echo "Warning: files opened on $P4CLIENT."
        # exit 1
    fi

    # create a label containing all files in our client view
    if [ "$LABEL" ] ; then
        [ "$LABEL" = "-" ] && LABEL="stdlib-$POSTFILE-`date +%m.%d.%y-%H:%M`"
        ( echo Label: $LABEL ;
          echo View: ;
          p4 client -o | sed -n "s:\(//.*\) //.*:\1:p" ) | p4 label -i
    fi

    # see if we can check the file out
    P4PATH=`p4 edit $POSTDIR/$POSTFILE 2>&1`
    echo $P4PATH | grep " - file(s) not on client." > /dev/null 2>&1

    # add the file if it is not in p4
    [ $? -eq 0 ] && P4PATH=`p4 add $POSTDIR/$POSTFILE`

    [ ! -x "$POSTDIR" ] && echo "$POSTDIR doesn't exist, can't post"

    # copy postfile (and a log, if any) where it can be checked in from
    cat $POSTFILE $BUILDLOG > $POSTDIR/$POSTFILE

    # remove junk p4 appends to the end of file names
    P4PATH=`echo $P4PATH | sed "s/#[1-9][0-9]* - .*$//"`

    echo "executing: p4 submit -i $P4PATH"

    # submit into Perforce
    cat << EOF | p4 submit -i $P4PATH
Change: new
Description:
    `cat /tmp/summary-$$`
Files: $P4PATH
EOF

    # clean up
    rm /tmp/summary-$$
}

# send mail with postfile (containing summary info) to p4 user
send_mail ()
{
    # check for mailx to use -s subject
    if type mailx > /dev/null 2>&1 ; then
        mailcmd=mailx -s "could not post $POSTFILE"
    elif [ `uname` = Linux ] ; then
        # mail on Linux understands -s
        mailcmd=mail -s "could not post $POSTFILE"
    else
        mailcmd=mail
    fi
    email=`p4 user -o | sed -n "s/^Email:.\(.*\)/\1/p"`
    cat $POSTFILE | $mailcmd $email
}


# format a line on output
#   $1: STATUS, $2: ASSRTS, $3 HIGH, $4 DELTA, $5 FAILED, $6 LOW,
#   $7: REGRESS, $8: PERCNT
format ()
{
    if [ $4 -ne 0 ] ; then
        ESC2="$CLR_BLACK"
        ESC4="$ESC2"
    fi

    # colorize regression field (numeric $1 okay, non-numeric considered error)
    if [ $7 -gt 0 ] || [ `expr $1 + 0 >/dev/null 2>&1; echo $?` -eq 2 ] ; then
        ESC7="$CLR_ERROR"
    elif [ $7 -lt 0 ] ; then
        ESC7="$CLR_SUCCESS"
    fi

    printf "$ESC1" >&2 ; printf " %6.6s" $1 ; printf "$ESC0" >&2

    # skip negative value (indicates that values don't make sense)
    if [ $2 -ge 0 ] ; then
        printf "$ESC2" >&2 ; printf " %6d" $2 ; printf "$ESC0" >&2
        printf "$ESC3" >&2 ; printf " %6d" $3 ; printf "$ESC0" >&2
        printf "$ESC4" >&2 ; printf " %6d" $4 ; printf "$ESC0" >&2
        printf "$ESC5" >&2 ; printf " %6d" $5 ; printf "$ESC0" >&2
        printf "$ESC6" >&2 ; printf " %6d" $6 ; printf "$ESC0" >&2
        printf "$ESC7" >&2 ; printf " %6d" $7 ; printf "$ESC0" >&2
        printf "$ESC8" >&2 ; printf " %5d%%" $8 ; printf "$ESC0" >&2
        printf "$ESC0" >&2
    fi

    echo
}

# get signal name from signal number (first argument)
signame ()
{
    n=`expr \( \( $1 - 1 \) % 4 + 1 \) \* 2`
    n=`kill -l | grep ".$1)" | head -1 | awk "{ print $"$n" }" | sed "s/SIG//"`

    [ "$n" = "" ] && n="SIG$1"
    echo $n
}

# compmute the elapsed time between adjacent arguments, each of which is
# expected to be in date +%T format, result is sent to stdout in +%T format
elapsed_time ()
{
    first=$1
    t0=`echo $1 | awk 'BEGIN { FS=":" } { print $1 * 3600 + $2 * 60 + $3 }'`
    shift
    for t in $* ; do
        t1=`echo $t | awk 'BEGIN { FS=":" } { print $1 * 3600 + $2 * 60 + $3 }'`
        [ $t1 -lt $t0 ] && t1=`expr $t1 + 24 \* 60 \* 60`
        hrs=`expr \( $t1 - $t0 \) / 3600`
        if [ $hrs -gt 0 ] ; then
            printf "%d:%02d:%02d " $hrs                     \
                   `expr \( \( $t1 - $t0 \) % 3600 \) / 60` \
                   `expr \( $t1 - $t0 \) % 60`
        else
            printf "%d:%02d " `expr \( \( $t1 - $t0 \) % 3600 \) / 60` \
                              `expr \( $t1 - $t0 \) % 60`
        fi
        t0=$t1
    done

    # compute the difference between the first and the last args
    [ $# -gt 2 ] && elapsed_time $first $t
}


fmat_diff ()
{
    # search file for the first occurrence of a number followed by
    # the string in $2; keep number and exit immediately to save time
    d=`sed -n "/$2/{s/[^0-9]*\([1-9][0-9]*\).*$2.*/\1/p;q
                   }" $REGRESS`

    if [ "$d" != "" ] ; then
        d=`expr $1 - $d`
        # output only if difference is non-zero
        [ $d -ne 0 ] && printf "(%+d) " $d
    fi
    unset d
}

##############################################################################
# MAIN LOOP
##############################################################################

# ensure consistent behavior of POSIX utilities such as grep and sed
LANG=POSIX

# show help and exit when invoked with no arguments
if [ $# -eq 0 ] ; then
    # print out top of this file (between the ###-lines)
    # sed expression split up to satisfy non-GNU sed
    sed -n "/^###/!D; {: print
                         n; /^###/q; s/^#//p; t print
                      }" $0
    exit 0
fi

# reset the terminal settings on exit 
trap 'printf "$ESC0" >&2 && exit' 0 2 3 15

SIGNAME=`signame $SIGNO`

HEADER="NAME               STATUS ASSRTS   HIGH  DELTA FAILED    LOW RGRESS PERCNT"

# write out our command line - uncomment for debugging
# echo $0 $*

tst_count=0   # number of tests tried
tst_pass=0    # number of successfully run tests (perhaps with assertions)
tst_ncomp=0   # number of tests that failed to compile
tst_nlink=0   # number of tests that failed to link
tst_fail=0    # number of tests run to completion but with failures
tst_core=0    # number of tests that dumped core
tst_kill=0    # number of tests that were forcefully killed
tst_rgrs=0    # number of tests with regressions

asrt_count=0  # total number of assertions
asrt_dlta=0   # increase in assertions from last run
asrt_pass=0   # total number of passed assertions
asrt_fail=0   # total number of failed assertions
asrt_rgrs=0   # increase in failed assertions from last run
asrt_hi=0     # the total highest number of failed assertions ever
asrt_lo=0     # the total lowest number of failed assertions ever

t0=`date +%T` # timestamp

# get $LINES and $COLUMNS set if possible
type resize > /dev/null 2>&1 && eval `resize` 2>&1 > /dev/null  
[ -z "$LINES" ] && LINES=40
[ -z "$COLUMNS" ] && COLUMNS=80

if [ "$TERM" = "xterm" ] ;then
    setcolors 1   # turn on colorized output
else
    setcolors 0   # disable colorization
fi

for i do
    # process command line options

    # avoid the unportable [ ! "$foo" ]
    if [ "" = "$AGEMAX" ] ; then
        AGEMAX=$i
        continue
    elif [ "$i" = "-a" ] ; then
        unset AGEMAX
        continue
    elif [ "" = "$POSTDESC" ] ; then
        POSTDESC="$i"
        continue
    elif [ "$i" = "-d" ] ; then
        unset POSTDESC
        continue
    elif [ "$i" = "-m" ] || [ "$i" = "--mono" ] || [ "$i" = "--nocolor" ]; then
        setcolors 0
        continue
    elif [ "$i" = "-c" ] || [ "$i" = "--color" ] ; then
        setcolors 1
        continue
    elif [ "" = "$BUILDLOG" ] ; then
        BUILDLOG=$i
        continue
    elif [ "$i" = "-l" ] || [ "$i" = "--log" ]; then
        unset BUILDLOG
        continue
    elif [ "" = "$LABEL" ] ; then
        LABEL=$i
        continue
    elif [ "$i" = "--label" ] ; then
        unset LABEL
        continue
    elif [ "" = "$REGRESS" ] ; then
        REGRESS=$i
        continue
    elif [ "$i" = "-r" ] ; then
        unset REGRESS
        continue
    elif [ "" = "$MIN_PASS_RATE" ] ; then
        MIN_PASS_RATE=$i
        continue
    elif [ "$i" = "--rate" ] ; then
        unset MIN_PASS_RATE
        continue
    elif [ "" = "$TIMEOUT" ] ; then
        TIMEOUT=$i
        continue
    elif [ "$i" = "-t" ] ; then
        unset TIMEOUT
        continue;
    elif [ "" = "$TIMESTAMPS" ] ; then
        TIMESTAMPS=$i
        continue
    elif [ "$i" = "-T" ] ; then
        unset TIMESTAMPS
        continue;
    elif [ "" = "$SIGNO" ] ; then
        SIGNO=$i
        SIGNAME=`signame $SIGNO`
        continue
    elif [ "$i" = "-s" ] ; then
        unset SIGNO
        continue
    elif [ "" = "$MANDIR" ] ; then
        MANDIR=$i/manual
        TUTDIR=$i/tutorial
        continue
    elif [ "" = "$XOPTS" ] ; then
        XOPTS="$i"
        continue
    elif [ "$i" = "-X" ] ; then
        unset XOPTS
        continue
    elif [ "$i" = "-x" ] ; then
        unset MANDIR
        continue
    elif [ "$i" = "--ph" ] ; then
        PLUMHALL=1              
        MANDIR='-'              # indicates we're running tests
        continue
    elif [ "" = "$POSTFILE" ] ; then
        POSTDIR=`dirname $i`
        POSTFILE=`basename $i`
        continue
    elif [ "$i" = "-p" ] ; then
        unset POSTFILE
        continue
    elif [ "" = "$OUTFILE" ] ; then
        OUTFILE=$i
        continue
    elif [ "$i" = "-o" ] ; then
        unset OUTFILE
        continue
    fi;

    n_asrt=0      # number of assertions in this test
    d_asrt=0      # increase in assertions from last run
    p_asrt=0      # passed assertions in this test
    f_asrt=0      # failed assertions in this test
    r_asrt=0      # number of regressions
    h_asrt=0      # the highest number of assertions ever tested
    l_asrt=0      # the least number of assertions ever tested
    pcnt_asrt=0   # percentage of passed assertions in this test
    no_asserts=0  # non-zero if output doesn't contain assertion tags
    exit_status=0 # successful exit status by default (will be overridden)

    ESC1="$ESC0" ; ESC2="$ESC0" ; ESC3="$ESC0" ; ESC4="$ESC0"
    ESC5="$ESC0" ; ESC6="$ESC0" ; ESC7="$ESC0" ; ESC8="$ESC0"

    if [ `expr $tst_count % $LINES` -eq 0 ] ; then
        # redirect test number to stderr in order not to mess up diff
        echo ;           printf "$CLR_BLACK" >&2 ; printf "%s" "--- " >&2
        echo "$HEADER" ; printf "$ESC0" >&2
    fi

    tst_count=`expr $tst_count + 1`
    printf "%3d " $tst_count 1>&2

    # strip trailing `.sh' from filename if present
    file=`basename $i | sed "s/\.sh//"`
    printf "%-18.18s" $file

    # if an executable file exists _or_ if AGEMAX is not 0
    if [ -x "$i" ] || [ $AGEMAX -ne 0 ] ; then
        # get the name of input file if any (/dev/null by default)
        if [ -f $MANDIR/in/$file.in ] ; then
            input=$MANDIR/in/$file.in
        elif [ -f $TUTDIR/in/$file.in ] ; then
            input=$TUTDIR/in/$file.in
        else
            input=/dev/null
        fi

        if [ "$OUTFILE" = "." ] ; then
            output="$i.out"
        else
            output=$OUTFILE
        fi

        if [ $AGEMAX -eq 0 ] ; then

            # save cursor position in case the shell (bash 2 on SunOS)
            # reports when a process gets a signal despite redirection
            [ "$COLORS" != "0" ] && printf "7" >&2

            # set alarm in process (in case we can't kill it if it hangs)
            alrm=`expr $TIMEOUT + 3`

            # run test in the background, redirect I/O to a file
            # stderr redirected to /dev/null to prevent compilation
            # warnings in "script" mode from causing false errors
            $i $XOPTS -O $output -L $alrm -M "`uname -a`" \
               > $output < $input 2>/dev/null &

            # get the background process' pid
            testpid=$!

            # start a subshell to kill hung process
            if [ $TIMEOUT -ge 0 ] ; then
                (t=$TIMEOUT
                 while [ $t -ne 0 ]; do
                     # exit right away if process doesn't exist
                     ps -p $testpid > /dev/null 2>&1 || exit 0 

                     # show countdown if colorized
                     [ "$COLORS" != "0" ] \
                         && printf "$CLR_WARN %6d$ESC0[7D" $t >&2
                     t=`expr $t - 1`
                     sleep 1
                 done
                 kill -s $SIGNO $testpid > /dev/null 2>&1
                )&
            fi

            # we'll test for this later
            kill_status=`expr 128 + $SIGNO`

            # wait for test to exit (on its own or forcefully)
            wait $testpid
            exit_status=$?

            # restore cursor position
            [ "$COLORS" != "0" ] && printf "8" >&2
        else
            # set kill status to be different than exit_status
            kill_status=1
            found=`find ./$output -mmin -$AGEMAX`
            if [ "" = "$found" ] ; then
                exit_status=-1
            else
                # indicate that output file was stale (older than AGEMAX)
                exit_status=0
            fi
        fi
	
	if [ $exit_status -lt 126 ] && [ "$PLUMHALL" ]; then
	    exit_status=0   #plum hall exit status is number of failed tests
	fi

        if [ $exit_status -eq 0 ]; then
            if [ "$MANDIR" = "-" ] ; then   # running tests

                # make sure output file exists
                if [ -f "$output" ] ; then
                    tst_pass=`expr $tst_pass + 1`

                    # grep for the number of total and failed assertions
		    if [ "$PLUMHALL" ]; then
			f_asrt=`grep "errors detected" $output \
                            | sed "s/\*\*\*\*\* \([0-9]*\) .*/\1/"`
			n_success=`grep "successful test case" $output \
                            | sed "s/\*\*\*\*\* \([0-9]*\) .*/\1/"`
			if [ "$f_asrt" ] && [ "$n_success" ]; then
			    n_asrt=`expr $n_success + $f_asrt`
			fi
		    else
			n_asrt=`sed "s/[^ -~]/ /g" $output \
                            | grep "## Assertions" \
                            | tail -1 | sed "s/[^0-9]//g"`
			f_asrt=`sed "s/[^ -~]/ /g" $output \
                            | grep "## FailedAssertions" \
                            | tail -1 | sed "s/[^0-9]//g"`
		    fi

                    if [ "" = "$n_asrt" ] ; then
                        n_asrt=0
                        no_assert=1
                        # no regression line found, something's fishy
                        exit_status="FORMAT" ; ESC1="$CLR_WARN"
                    fi

                    [ "" = "$f_asrt" ] && f_asrt=0

                    # increment the number of tests not passing at 100%
                    [ $f_asrt -gt 0 ] && tst_fail=`expr $tst_fail + 1`
                
                    # compute percentage of passed assertions
                    p_asrt=`expr $n_asrt - $f_asrt`
                    asrt_count=`expr $asrt_count + $n_asrt`
                    asrt_pass=`expr $asrt_pass + $p_asrt`
                    asrt_fail=`expr $asrt_fail + $f_asrt`

                    [ $f_asrt -gt 0 ] && ESC5="$CLR_RED"

                    # prevent division by zero
                    [ $n_asrt -ne 0 ] \
                        && pcnt_asrt=`expr 100 \* $p_asrt / $n_asrt`
                else   # output file not found
                    exit_status="OUTPUT" ; ESC1="$CLR_ERROR"
                fi
            else   # running examples (expect output)
                no_asserts=1

                # get the name of file against which to diff the output
                if [ -f $MANDIR/out/$file.out ] ; then
                    diff_file=$MANDIR/out/$file.out
                elif [ -f $TUTDIR/out/$file.out ] ; then
                    diff_file=$TUTDIR/out/$file.out
                else
                    unset diff_file
                fi

                if [ "$diff_file" != "" ] ; then
                    diff $DIFF_STRIP_CR $diff_file $output >/dev/null 2>&1
                    if [ $? -eq 0 ] ; then
                        tst_pass=`expr $tst_pass + 1`
                    else
                        exit_status="DIFF" ; ESC1="$CLR_ERROR"
                    fi
                else
                    exit_status="OUTPUT" ; ESC1="$CLR_WARN"
                    tst_pass=`expr $tst_pass + 1`
                fi
                ESC2="$ESC0" ; ESC3="$ESC2" ; ESC4="$ESC2" ; ESC5="$ESC2"
                ESC6="$ESC2" ; ESC7="$ESC2" ; ESC8="$ESC2"
            fi
        elif [ $exit_status -eq 126 ] ; then
            exit_status="EXIST"    # can't find executable
            ESC1="$CLR_WARN"
        elif [ $exit_status -eq 127 ] ; then
            exit_status="EXEC"     # can't execute (check LD_LIBRARY_PATH)
            ESC1="$CLR_WARN"
        elif [ $exit_status -eq $kill_status ] ; then
            exit_status=`signame $SIGNO`   # forcefully killed
            ESC1="$CLR_ERROR"
            tst_kill=`expr $tst_kill + 1`
        elif [ $exit_status -ge 128 ] ; then
            n=`expr $exit_status - 128`
            exit_status=`signame $n`
            ESC1="$CLR_ERROR"
            tst_core=`expr $tst_core + 1`
        elif [ $exit_status -eq -1 ] ; then
            exit_status="STALE"
            ESC1="$CLR_WARN"
        else
            ESC1="$CLR_WARN"
        fi
    elif [ -f $i.o ] ; then
        exit_status="LINK"   # .o exists but no executable, failed to link
        ESC1="$CLR_ERROR"
        tst_nlink=`expr $tst_nlink + 1`
    else
        # file isn't executable, see if it's an object file...
        suffix=`echo $i | sed "s/.*\(\..*\)/\1/"`
        if [ "$suffix" = ".o" ] && [ -f "$i" ] ; then
            no_asserts=1
            tst_pass=`expr $tst_pass + 1`
        else
            exit_status="COMP"   # no .o, failed to compile
            ESC1="$CLR_ERROR"
            tst_ncomp=`expr $tst_ncomp + 1`
        fi
    fi

    if [ -f "$REGRESS" ] ; then
        # find line for same executable in a previous report file
        line=`grep "^$file[ .] " $REGRESS | head -n 1`

        # compute difference in tried assertions
        d_asrt=`echo $line | awk '{ print $3 }'`
        d_asrt=`expr $n_asrt - 0$d_asrt`

        # only consider if current exit status is 0 (success)
        if [ "$exit_status" = "0" ] ; then

            # regressions make sense only if previous exit status was 0
            if  [ "`echo $line | awk '{ print $2 }'`" = "0" ] ; then
                r_asrt=`echo $line | awk '{ print $6 }'`
                r_asrt=`expr $f_asrt - 0$r_asrt`
            fi

            # see if the lowest number of failed assertions improved
            l_asrt=`echo $line | awk '{ print $7 }'`
            [ "" = "$l_asrt" ] && l_asrt=0
            [ $l_asrt -gt $f_asrt ] && l_asrt=$f_asrt && ESC6="$CLR_SUCCESS"
        else
            # carry over previous low assertions on failure
            l_asrt=`echo $line | awk '{ print $7 }'`
            [ "" = "$l_asrt" ] && l_asrt=0

            # count the test as regressing if the last exit status was 0
            old_status=`echo $line | awk '{ print $2 }'`
            [ "$old_status" = "0" ] && tst_rgrs=`expr $tst_rgrs + 1`
        fi

        # adjust highest number of assertions achieved
        h_asrt=`echo $line | awk '{ print $4 }'`
        [ "" = "$h_asrt" ] && h_asrt=0
        [ $h_asrt -lt $n_asrt ] && h_asrt=$n_asrt && ESC3="$CLR_BLACK"

        # increment cumulative totals
        asrt_dlta=`expr $asrt_dlta + $d_asrt`
        asrt_rgrs=`expr $asrt_rgrs + $r_asrt`

        [ $r_asrt -gt 0 ] && tst_rgrs=`expr $tst_rgrs + 1`
    else
        l_asrt=$f_asrt
        h_asrt=$n_asrt
    fi

    # get rid of leading zeros (to prevent octal conversion by printf)
    l_asrt=`expr $l_asrt + 0`

    asrt_hi=`expr $h_asrt + $asrt_hi`
    asrt_lo=`expr $l_asrt + $asrt_lo`

    # set number of assertions negative if assertion tags not found
    [ $no_asserts -ne 0 ] && n_asrt=-1

    # careful with leading zeros (interpreted as octal with %d)
    format $exit_status $n_asrt $h_asrt $d_asrt \
           $f_asrt $l_asrt $r_asrt $pcnt_asrt

    # reset name of output file
    OUTFILE="."
done

# compute pass rate as percentage of tests with perfect score
# out of the total number of tests tried
pass_rate=0
[ $tst_count -gt 0 ] && pass_rate=`expr \( 100 \* \( $tst_pass - $tst_fail \) \) / $tst_count`

if [ $tst_count -gt 1 ] ; then

    [ $asrt_count -ne 0 ] && pcnt_asrt=`expr 100 \* $asrt_pass / $asrt_count`

    # show cumulative results
    printf "\n--- " 1>&2
    printf "%-18.18s" ""

    ESC1="" ; ESC2="" ; ESC3="" ; ESC4=""
    ESC5="" ; ESC6="" ; ESC7="" ; ESC8=""

    # careful with leading zeros (interpreted as octal with %d)
    format $tst_pass $asrt_count $asrt_hi $asrt_dlta \
           $asrt_fail $asrt_lo $asrt_rgrs $pcnt_asrt

    echo ; printf "${CLR_BLACK}    " >&2
    echo "                     FAIL   COMP   LINK ASSERT   CORE   KILL RGRESS";
    printf "$ESC0      " >&2
    printf "                 %6d %6d %6d %6d %6d %6d %6d\n" \
           `expr $tst_count - $tst_pass` \
           $tst_ncomp $tst_nlink $tst_fail $tst_core $tst_kill $tst_rgrs
    echo

    if [ -f "$REGRESS" ] ; then

        # compute and format summary regressions wrt the previous report
        # each var will be empty when there's no change, else "(+/-N) "
        d_count=`fmat_diff $tst_count "programs"`
        d_ncomp=`fmat_diff $tst_ncomp "failed to compile"`
        d_nlink=`fmat_diff $tst_nlink "failed to link"`
        d_fail=`fmat_diff $tst_fail "failed at least one assertion"`
        d_core=`fmat_diff $tst_core "exited with a signal"`
        d_kill=`fmat_diff $tst_kill "killed with signal"`
        d_rate=`fmat_diff $pass_rate "pass rate"`

        # special treatment (no variable $tst_nzero exists)
        d_nzero=`expr $tst_count - $tst_pass`
        d_nzero=`fmat_diff $d_nzero "exited with non-zero status"`

        if [ -f "$POSTFILE" ] ; then
            # modify REGRESS to be the p4 path name and rev # of what
            # regressions are being reported against (used in description)
            tmp=`p4 have $REGRESS | sed "s/ - .*//" 2>/dev/null`
            [ $? -eq 0 ] && REGRESS="$tmp"
        fi
        regress_line="$asrt_rgrs regressions (baseline was $REGRESS)"
    else
        regress_line="no regressions (this is baseline)"
    fi

    # present summary only for more files than fits on screen or when posting
    if [ $tst_count -ge $LINES -o -f "$POSTFILE" ] ; then

        # compute and format timestamps
        if [ "$TIMESTAMPS" != "-" ] ; then
            # expect: "h0:m0:s0,h1:m1:s1,h2:m2:s2"
            TIMESTAMPS="$TIMESTAMPS $t0 `date +%T`"
            # replace commas with spaces
            TIMESTAMPS=`echo $TIMESTAMPS | sed "s/,/ /g"`
            TIMESTAMPS=`elapsed_time $TIMESTAMPS | awk '{ print \
                "cfg: " $1 " lib: " $2 " tst: " $3 " run: " $4 " all: " $5 }'`
        fi

        # create a file containing summary info about this run
        cat << EOF | tee /tmp/summary-$$

        Generated by $LOGNAME@`uname -n` running `uname -sr`

        `if [ -f "$POSTDESC" ]; then cat "$POSTDESC" ; else echo $POSTDESC; fi`

        `[ "$TIMESTAMPS" != "-" ] && echo "Times: $TIMESTAMPS"`

        Summary: out of $tst_count ${d_count}programs
            `expr $tst_count - $tst_pass` ${d_nzero}exited with non-zero status
            $tst_ncomp ${d_ncomp}failed to compile
            $tst_nlink ${d_nlink}failed to link
            $tst_fail ${d_fail}failed at least one assertion
            $tst_core ${d_core}exited with a signal
            $tst_kill ${d_kill}killed with signal $SIGNO
            $regress_line
            $pass_rate% ${d_rate}pass rate

EOF
    fi

else
    echo
fi


# post results in revision control system
if [ -f "$POSTFILE" ] ; then
    # make sure minimum pass rate is achieved
    if [ $pass_rate -lt $MIN_PASS_RATE ] ; then
        # send email to p4 user if not passing at a desired rate
        send_mail
        echo "pass rate < $MIN_PASS_RATE%, can't post"
    elif [ "$P4CLIENT" != "" ] ; then
        # post results in Perforce
        post_results
    else
        send_mail
        echo "P4CLIENT not defined, can't post"
    fi

elif [ "$POSTFILE" != "." ] ; then
    echo "$POSTFILE not found, can't post"
    exit 1
fi
