#!/bin/sh
#
# Check group file and qa scripts and optionall check for a specific
# pcp command ($1).
#
# If $1 is not given, just check the group file itself.
#
# If $1 is specified  is assumed to be _both_ the name of a command
# that appears in the QA scripts (or part of a command, e.g. valgrind in
# _run_valgrind) and the name of a group in the group file
#
# Note: if $1 is of the form pcp-xxx, this is special and is the name
#	of a command, but the groups are pcp and xxx
# Note: there are some special group aliases, like folio, logutil,
#	pmclient where $1 is a group name but expands to multiple
#	commands
# Note: for the pmda.foo groups, the group name is replaced by some
#	other signature to check if the PMDA is really being tested
#
# And -d provides debug output.
#

_do_group()
{
    LC_COLLATE=POSIX; export LC_COLLATE

    # pass 1 - make sure tests in group and test scripts match
    #          and all scripts are executable
    #
    sed <group -n -e '/^[0-9]/{
s/:reserved / /
s/:retired / /
s/ .*//
p
}' \
    | sort -n \
    | uniq -c >$tmp.all-tests
    grep -v ' 1 ' <$tmp.all-tests >$tmp.tmp
    if [ -s $tmp.tmp ]
    then
	echo "Error: tests appearing more than once in group file ..."
	cat $tmp.tmp
    fi

    awk <$tmp.all-tests '{ print $2 }' \
    | sort  >$tmp.tests
    ls [0-9][0-9][0-9] [0-9][0-9][0-9][0-9] \
    | sort >$tmp.scripts
    comm -2 -3 $tmp.tests $tmp.scripts \
    | sed -e 's/.*/&: Error: in group file but there is no test script to match/'
    comm -1 -3 $tmp.tests $tmp.scripts \
    | sed -e 's/.*/&: Error: script exists but missing from group file/'
    cat $tmp.scripts \
    | while read seq
    do
	[ -x "$seq" ] || echo "$seq: Error: script is not executable"
    done

    # pass 2 - check each test in the group file only mentions defined
    #          groups and all defined groups are used at least once
    #
    # Note: #12345 and patch12345 are not "group" names
    sed -n <group  -e '/^[a-z][^ ]*$/p' \
    | sort >$tmp.groups
    sed <group \
	-e '/^$/d' \
	-e '/^#/d' \
	-e '/^[0-9][0-9]*:retired /d' \
	-e '/^[0-9][0-9]*:reserved /s/:reserved //' \
	-e 's/ #[0-9][0-9]*//g' \
	-e 's/ patch[0-9][0-9]*//g' \
    | while read seq groups
    do
	for grp in $groups
	do
	    echo "$grp $seq"
	done
    done \
    | sort -k1 >$tmp.refs
    join -j 1 -t ' ' -a 1 -a 2 -e FOO -o 1.1,2.1,2.2 $tmp.groups $tmp.refs >$tmp.out
    grep '^FOO ' <$tmp.out \
    | sed -e 's/FOO \([^ ][^ ]*\) \([0-9][0-9]*\)/\2: Warning: group file mentions unidentified group (\1)/'
    grep ' FOO FOO$' <$tmp.out \
    | sed -e 's/^\([^ ][^ ]*\).*/\1: Warning: group defined in group file but not used/'

}

_do_app()
{
    touch $tmp.tmp
    for seq in [0-9][0-9][0-9] [0-9][0-9][0-9][0-9]
    do
	# special control lines
	# check-group-include: group ..
	# check-group-exclude: group ..
	grp="$1"
	case "$1"
	in
	    pmlogdump|pmdumplog)
		grp="pmlogdump|pmdumplog"
		;;
	    *)
		grp="$1"
		;;
	esac
	grep '# check-group-' $seq >$tmp.chk
	if [ -s $tmp.chk ]
	then
	    if grep -E "check-group-include:.* ($grp)( |\$)" <$tmp.chk >/dev/null
	    then
		$debug && echo "$seq: [explicit include]"
		echo $seq >>$tmp.tmp
		continue
	    fi
	    if grep -E "check-group-exclude:.* $1( |\$)" <$tmp.chk >/dev/null
	    then
		$debug && echo "$seq: [explicit exclude]"
		continue
	    fi
	fi

	report_group=""
	group_list=""
	# need to handle some alias cases ...
	#
	case "$1"
	in

	    folio)		# executable might be pmafm or mkaf
		( grep -v '^[ 	]*#' $seq \
		  | grep -E -q "(^[ 	]*(pmafm|mkaf)([ '\"]|$))|([ /'\"\`_=](pmafm|mkaf)([ '\"\`_]|$))" >$tmp.debug \
		) && echo $seq >>$tmp.tmp
		report_group="pmafm or mkaf"
		;;

	    logutil)	# pmlogger_ scripts
		( grep -v '^[ 	]*#' $seq \
		  | grep -E -q "(^[ 	]*(pmlogger_check|pmlogger_daily|pmlogger_daily_report|pmlogger_merge|pmlogger_rewrite)([ '\"]|$))|([ /'\"\`_=](pmlogger_check|pmlogger_daily|pmlogger_daily_report|pmlogger_merge|pmlogger_rewrite)([ '\"\`_]|$))" >$tmp.debug \
		) && echo $seq >>$tmp.tmp
		report_group="pmlogger_* scripts"
		;;

	    pmclient)	# executable might be pmlient_fg
		( grep -v '^[ 	]*#' $seq \
		  | grep -E -q "(^[ 	]*(pmclient|pmclient_fg)([ '\"]|$))|([ /'\"\`_=](pmclient|pmclient_fg)([ '\"\`_]|$))" >$tmp.debug \
		) && echo $seq >>$tmp.tmp
		report_group="pmclient et al"
		;;

	    pmdumplog|pmlogdump)
			    # these are aliases ...
			    # executable might be hidden in _exercise_compression
			    # wrapper from common.compress
		( grep -v '^[ 	]*#' $seq \
		  | grep -E -q "(^[ 	]*(pmlogdump|pmdumplog|_exercise_compression)([ '\"]|$))|([ /'\"\`_=](pmlogdump|pmdumplog|_exercise_compression)([ '\"\`_]|$))" >$tmp.debug \
		) && echo $seq >>$tmp.tmp
		;;

	    pmie)		# executable might be hidden in src/grind
		( grep -v '^[ 	]*#' $seq \
		  | grep -E -q "(^[ 	]*(pmie|src/grind-tools)([ '\"]|$))|([ /'\"\`_=](pmie|src/grind-tools)([ '\"\`_]|$))" >$tmp.debug \
		) && echo $seq >>$tmp.tmp
		;;

	    pmlogrewrite)	# executable might be hidden in mk.logfarm
		( grep -v '^[ 	]*#' $seq \
		  | grep -E -q "(^[ 	]*(pmlogrewrite|mk.logfarm)([ '\"]|$))|([ /'\"\`_=](pmlogrewrite|mk.logfarm)([ '\"\`_]|$))" >$tmp.debug \
		) && echo $seq >>$tmp.tmp
		;;

	    pmpost)		# executable might be in $PCP_BINADM_DIR
		( grep -v '^[ 	]*#' $seq \
		  | grep -E "(^[ 	]*pmpost([ '\"]|$))|([ '\"\`/]pmpost([ '\"\`]|$))" >$tmp.debug \
		) && echo $seq >>$tmp.tmp
		;;

	    valgrind)	# executable maybe hidden in wrappers, so look for
			    # cmd and guard and wrapper, but NOT if the script
			    # contains the "dual" guard ...
			    # if [ "$1" = "--valgrind" ]
			    #
		if ( grep -v '^[ 	]*#' $seq \
		  | grep -E -q "(^[ 	]*(valgrind|_check_valgrind)([ '\"]|$))|([ /'\"\`_=](valgrind|_run_valgrind)([ '\"\`_]|$))" >$tmp.debug \
		)
		then
		    if grep -q 'if \[ "$1" = "--valgrind" ]' <$seq
		    then
			# valgrind is _really_ run from the dual of this one
			#
			:
		    else
			echo $seq >>$tmp.tmp
		    fi
		fi
		;;

	    pcp-*)		# pcp-xxx is cmd, expected group is pcp AND xxx
				# pcp ... xxx is also cmd
		subcmd=`echo "$1" | sed -e 's/pcp-//'`
		( grep -v '^[ 	]*#' $seq \
		  | grep -E "(^[ 	]*$1([ '\"]|$))|([ '\"\`]$1([ '\"\`]|$))|(^[ 	]*pcp.*[- ]$subcmd([ '\"]|$))|([ '\"\`]$pcp.*[- ]$subcmd([ '\"\`]|$))" >$tmp.debug \
		) && echo $seq >>$tmp.tmp
		group_list="`echo $1 | sed -e 's/-/ /'`"
		;;

	    iostat)		# executable maybe pcp-iostat or pcp iostat or pmiostat
		( grep -v '^[ 	]*#' $seq \
		  | grep -E -q "(^[ 	]*(pcp-iostat|pcp iostat|pmiostat)([ '\"]|$))|([ /'\"\`_=](pcp-iostat|pcp iostat|pmiostat)([ '\"\`_]|$))" >$tmp.debug \
		) && echo $seq >>$tmp.tmp
		;;

	    not_in_ci)
		( grep '^[ 	]*#.*:not_in_ci:' $seq >$tmp.debug  \
		)  && echo $seq >>$tmp.tmp
		;;

	    not_in_macos)
		( grep '^[ 	]*#.*:not_in_macos:' $seq >$tmp.debug  \
		)  && echo $seq >>$tmp.tmp
		;;

	    pmlogcompress)	# this is a group
		( grep -v '^[ 	]*#' $seq \
		  | grep -E -q "(^[ 	]*(pmlogcompress|pmlogdecompress)([ '\"]|$))|([ /'\"\`_=](pmlogcompress|pmlogdecompress)([ '\"\`_]|$))" >$tmp.debug \
		) && echo $seq >>$tmp.tmp
		;;

	    pmlogmv)	# this is a group or maybe hidden in mk.logfarm
		( grep -v '^[ 	]*#' $seq \
		  | grep -E -q "(^[ 	]*(pmlogmv|pmlogcp|mk.logfarm)([ '\"]|$))|([ /'\"\`_=](pmlogmv|pmlogcp|mk.logfarm)([ '\"\`_]|$))" >$tmp.debug \
		) && echo $seq >>$tmp.tmp
		;;

	    # pmda.foo ones simpler, but need per-PMDA logic ... some are
	    # just too hard, like all of the kernel PMDAs
	    #
	    pmda.linux|pmda.darwin|pmda.freebsd|pmda.netbsd|pmda.openbsd|pmda.solaris|pmda.aix|pmda.proc)
		echo "Sorry, platform PMDAs (like `echo "$1" | sed -e 's/pmda\.//'`) are just too hard to check"
		exit
		;;

	    pmda.sample)
		echo "Sorry, the sample PMDA is omnipresent in QA tests and too hard to check"
		exit
		;;

	    pmda.bpf)
		grep -q '^_pmdabpf_check$' $seq && echo $seq >>$tmp.tmp
		;;

	    pmda.bcc)
		grep -q '^_pmdabcc_check$' $seq && echo $seq >>$tmp.tmp
		;;

	    pmda.openmetrics)
		grep -q -E '^_pmdaopenmetrics_check( |$)' $seq && echo $seq >>$tmp.tmp
		;;

	    pmda.opentelemetry)
		grep -q -E '^_pmdaopentelemetry_check( |$)' $seq && echo $seq >>$tmp.tmp
		;;

	    pmda.*)
		echo "Oops, no recipe (yet) for reliably identifying tests involving the `echo "$1" | sed -e 's/pmda\.//'` PMDA"
		exit
		;;

	    *)
		( grep -v '^[ 	]*#' $seq \
		  | grep -E "(^[ 	]*$1([ '\"]|$))|([ /'\"\`]$1([ '\"\`]|$))" >$tmp.debug \
		) && echo $seq >>$tmp.tmp
		;;
	esac
	if $debug && [ -s $tmp.debug ]
	then
	    echo "$seq: [matching lines]"
	    sed -e 's/^/  /' <$tmp.debug
	fi
	rm -f $tmp.debug
    done

    sed <group -e '/^$/d' -e '/^[^0-9]/d' -e 's/ .*//' -e 's/:/ /' \
    | while read seq state
    do
	[ -f "$seq" ] && continue
	[ "$state" = reserved ] && continue
	[ "$state" = retired ] && continue
	echo "$seq: Error: in group but file not found"
    done

    sort -o $tmp.scripts $tmp.tmp

    if [ -z "$group_list" ]
    then
	# $1 IS the group name
	#
	check -r -n -g "$1" \
	| sort \
	| while read f
	do
	    [ -f "$f" ] && echo "$f"
	done >$tmp.group
    else
	# Need to match ALL the groups in the group file ... these ones are OK
	#
	for grp in $group_list
	do
	    check -r -n -g "$grp" \
	    | while read f
	    do
		[ -f "$f" ] && echo "$f"
	    done
	done >$tmp.tmp
	xpect="`echo $group_list | wc -w | sed -e 's/ //g'`"
	sort $tmp.tmp \
	| uniq -c \
	| while read num seq
	do
	    [ "$num" = "$xpect" ] && echo $seq
	done >$tmp.group
    fi

    [ -z "$report_group" ] && report_group="$1"

    comm -23 $tmp.scripts $tmp.group >$tmp.tmp
    if [ -s $tmp.tmp ]
    then
	echo "$report_group in QA scripts and NOT in group ..."
	sort -n $tmp.tmp | sed -e 's/^/    /'
	echo
    fi

    comm -13 $tmp.scripts $tmp.group >$tmp.tmp
    # may have been retired via really-retire leaving a stub
    # test but :retired in the group file
    #
    mv $tmp.tmp $tmp.notintest
    cat $tmp.notintest \
    | while read seq
    do
	if grep -q "^$seq:retired" group
	then
	    echo "$seq" >>$tmp.retired
	else
	    echo "$seq" >>$tmp.tmp
	fi
    done
    if [ -s $tmp.tmp ]
    then
	echo "$report_group in group and NOT in QA scripts ..."
	sort -n $tmp.tmp | sed -e 's/^/    /'
	echo
    fi
    if [ -s $tmp.retired ]
    then
	echo "$report_group in group and NOT in retired QA scripts (these are OK) ..."
	sort -n $tmp.retired | sed -e 's/^/    /'
	echo
    fi

    comm -12 $tmp.scripts $tmp.group >$tmp.tmp
    echo "$report_group in group and QA scripts `wc -l <$tmp.tmp | sed -e 's/ //g'` times"

    $debug && echo "debug tmp files: " tmp.*
}

debug=false
if [ $# -gt 0 -a "X$1" = X-d ]
then
    debug=true
    shift
fi
if $debug
then
    tmp=tmp
    trap "exit 0" 0 1 2 3 15
else
    tmp=${TMPDIR:-/tmp}/check-group-$$
    trap "rm -f $tmp.*; exit 0" 0 1 2 3 15
fi
sudo rm -f $tmp.*

if [ $# -eq 0 ]
then
    _do_group
elif [ $# -eq 1 ]
then
    _do_app "$1"
else
    echo "Usage: $0 [-d] [<name>]"
    exit 1
fi
