#!/bin/sh
# GPL_3+
cat << 'EEE' > /dev/null
/* Copyright (C) 2021 Momi-g
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
EEE

cat << 'EEE' > /dev/null
#SH_doc
title=shunit_m section=1 repnl=\040
@name shunit_m
@_brief posix & portable xunit for shell script
@_syno
	shunit_m [-hHVvF] [-A names]
@tl;dr
		@(code)@
	~$ shunit_m > funcs.sh
	
	--src.sh
	#!/bin/sh
	. ./funcs.sh
	_eq 1 1
	_neq 1 1 'write anymsg at last arg'
	ls
	_fail	#>> assert $? != 0
	_res 	#>> output result + return rc
	
	--run 
	~$ sh src.sh	#>> ok,NG fail/all=10/20 etc. (bash needs --posix opt)
	~$ echo $?  	#>> output fail num (all suc==0)
		@()
@_opt
	@(list_o)
	-h, -H, -V: usage, version
	-v level: verbose level. 0/1/2 == quite/normal/verbose. output to stderr
	-F: fallthrough. stops running if detect assert error in default
	-A names: change assert alias names. set 5 fields --
	  eg) ~$ shunit_m -A '_eq _neq _suc _fail _res'  #same as default --
	  eg) ~$ shunit_m -A 'aa bb cc dd zz'  #` _eq 1 2` changes to `aa 1 2`
	@()
@_desc
	`shunit_m` outputs assert alias, support functions and assert count vars.
	core api is alias.
	@(list)
	  ` _eq`: takes 2 args and test [ ag1 = ag2 ]
	  ` _neq`: takes 2 args and test [ ag1 != ag2 ]
	  ` _suc`: takes no arg. same as` _eq $? 0`
	  ` _fail`: takes no arg. same as` _neq $? 0`
	  ` _res`: this isnt assert. output the result and return. if 5 assert
			failed, $? == 5.
	@()--
	upper 5 takes additional 1 arg for memo/msg. eg) _suc  this_is_f1_test --
	other functions and vars doesnt use directly. option -v,F,A will edit the 
	output code and alias. be careful when using it in copy and paste style.
	@(code)--@
	  ~$ shunit_m	#>> shunit_eq(){ abc...}
	  ~$ shunit_m -F	#>> shunit_eq(){ xyz...}
	@()--
	--- --
	dont eval in compound commands, (), {}, func()(), func(){} etc.
		@(code)--
	--good             |  ---NG1               |  ---NG2
	#!/bin/sh          |  #!/bin/sh            |  #!/bin/sh         
	eval "$(shunit_m)" |  (                    |  testf(){
	(                  |    eval "$(shunit_m)" |    eval "$(shunit_m)"
	    _eq 1 1        |    _eq 1 1            |    _eq 1 1
	    _res           |    _res               |    _res
	)                  |  )                    |  }
	                   |                       |  testf
		@()--
	alias substitution should work as C-lang macros and the scope must 
	be the same as local vars in posix-shell. --
	(https://pubs.opengroup.org/onlinepubs/9699919799/utilities/alias.html)--
	--
	all sample(good, NG1, NG2) should work in posix-shell but any major shells
	(posix mode or not) doesnt compliant the standard. only bash is mentioned
	about this problem in the alias section of its man. --
	bash/dash/ksh/busybox raises error to the below code:
		@(code)--
	sh -c '(alias abc='ls'; alias ; abc)'
	# sh == bash --posix / dash / ksh / busybox sh 
		@()--
	allmost all shells works fine if eval shunit_m in base process, treat as
	C-lang #define delective.

@EXIT_STATUS
	asserts returns 0/!0, _fail 1 2 >> $? == 0.
	` _res` returns failed assert count.
@_eg
	@(raw).SS basic sample:@()
		@(code)@
	#!/bin/sh
	src_main(){
	  f1 "$@"
	  return 0
	}
	f1(){
	  [ $# -eq 2 ] || return 1
	  num=$(($1+$2))
	}
	
	## run main
	# src_main "$@"
	# exit $?
	
	## run testcode
	cmd="$(shunit_m)"
	eval "$cmd"		#>> you can check with 'echo "$cmd"'

	f1	;_fail
	f1 1 2	;_eq 3 $num
	_res
	echo "--test suc"

	--test_run 
	~$ dash src.sh
		@()--
	--

	@(raw).SS more general, separate style:@()
		@(code)@
	---src.sh
	#!/bin/sh
	src_main(){
	  f1 "$@"
	  return 0
	}
	f1(){
	  [ $# -eq 2 ] || return 1
	  num=$(($1+$2))
	}
	src_main "$@"		# @marker@

	--tests.sh
	#!/bin/sh
	eval "$(shunit_m -F)"
	test_1(){ f1 10 20; _fail ;}
	test_2(){ _eq 1 1 ;}
	eval "$(cat src.sh|grep -v '@marker@')"
	suite=$(sed -ne '/^test_[0-9]*[(]/{s/[(].*//p}'<$0)   #grep test_**
	for fc in $suite;do $fc ;done
	_res
	
	--test_run
	~$ bash --posix tests.sh
		@()--

	@(raw).SS make portable/static test style:@()
		@(code)@
	~$ shunit_m -F	#>> output code

	--tests.sh
	#!/bin/sh
	#---
	# copy&paste code
	#---
	test_1(){ _eq 1 1 ;}
	eval "$(cat src.sh|grep -v '@marker@')"
	suite=$(sed -ne '/^test_[0-9]*[(]/{s/[(].*//p}'<$0)
	for fc in $suite;do $fc ;done
	_res

	--test_run--
	~$ sh tests.sh
		@()

@notes
		@(code)@
	--concept
	 - respect posix
	 - small is beautiful
	 - avoid original syntax
		@()
@conforming_to	posix-shell
@copyright
	Copyright (C) 2021 Momi-g --
	License GPLv3+ <https://gnu.org/licenses/gpl.html>
@_ver 2021-12-01 v1.0.4
@_see `shunit2(1), shellspec`
#SH_docE
EEE
usage(){
cat << 'EEE'
HowTo (shunit_m - posix & portable shell xunit)
opt: -v(erbose) -F(all) -A(lias), -hHV(hHelp,version)
---
eg) ~$ shunit_m -v2 > funcs.sh

	#!/bin/sh
	. ./funcs.sh
	_eq 1 1	#>> assert $1 = $2
	_neq 1 1 msg	#>> assert $1 != $2 
	ls
	_fail	#>> assert $? != 0
	_res	# >> output result + return rc. rc is failed assert cnt.
	
 ..shunit_m drops assert alias, funcs and test count vars.
 assert: _eq, _neq, _suc, _fail, _res(report)
 others: shunit_XXX(), shunit_var... etc. name has pfix 'shunit_'
   -v: verbose lv. 0/1/2 = silent/normal/verbose. output to stderr.
   -F: fallthrough. dfl stops running if detect assert failed.
   -A: change alias name. sep with blank. dfl: -A '_eq _neq _suc _fail _res'
EEE
exit 0
}

usage_H(){
 cat << 'E E'|sed -e'1d;$d'
/*--copyfrom shunit_m.1.txt*/
SHUNIT_M(1)                 General Commands Manual                SHUNIT_M(1)



NAME
       shunit_m - posix & portable xunit for shell script

SYNOPSIS
       shunit_m [-hHVvF] [-A names]

TL;DR
       ~$ shunit_m > funcs.sh

       --src.sh
       #!/bin/sh
       . ./funcs.sh
       _eq 1 1
       _neq 1 1 'write anymsg at last arg'
       ls
       _fail     #>> assert $? != 0
       _res      #>> output result + return rc

       --run
       ~$ sh src.sh   #>> ok,NG fail/all=10/20 etc. (bash needs --posix opt)
       ~$ echo $?     #>> output fail num (all suc==0)


OPTIONS
       -h, -H, -V
              usage, version

       -v level
              verbose level. 0/1/2 == quite/normal/verbose. output to stderr

       -F     fallthrough. stops running if detect assert error in default

       -A names
              change assert alias names. set 5 fields
               eg) ~$ shunit_m -A '_eq _neq _suc _fail _res' #same as default
               eg) ~$ shunit_m -A 'aa bb cc dd zz' # _eq 1 2 changes to aa 1 2

DESCRIPTION
       shunit_m outputs assert alias, support functions and assert count vars.
       core api is alias.

         _eq  takes 2 args and test [ ag1 = ag2 ]

         _neq takes 2 args and test [ ag1 != ag2 ]

         _suc takes no arg. same as _eq $? 0

         _fail
              takes no arg. same as _neq $? 0

         _res this isnt assert. output the result  and  return.  if  5  assert
              failed, $? == 5.

       upper 5 takes additional 1 arg for memo/msg. eg) _suc this_is_f1_test
       other  functions  and vars doesnt use directly. option -v,F,A will edit
       the  output code and alias. be careful when using it in copy and  paste
       style.

         ~$ shunit_m  #>> shunit_eq(){ abc...}
         ~$ shunit_m -F    #>> shunit_eq(){ xyz...}

       ---
       dont eval in compound commands, (), {}, func()(), func(){} etc.

            --good             |  ---NG1               |  ---NG2
            #!/bin/sh          |  #!/bin/sh            |  #!/bin/sh
            eval "$(shunit_m)" |  (                    |  testf(){
            (                  |    eval "$(shunit_m)" |    eval "$(shunit_m)"
                _eq 1 1        |    _eq 1 1            |    _eq 1 1
                _res           |    _res               |    _res
            )                  |  )                    |  }
                               |                       |  testf

       alias  substitution should work as C-lang macros and the scope must  be
       the same as local vars in posix-shell.
       (https://pubs.opengroup.org/onlinepubs/9699919799/utilities/alias.html)

       all sample(good, NG1, NG2) should work in  posix-shell  but  any  major
       shells  (posix mode or not) doesnt compliant the standard. only bash is
       mentioned about this problem in the alias section of its man.
       bash/dash/ksh/busybox raises error to the below code:

            sh -c '(alias abc='ls'; alias ; abc)'
            # sh == bash --posix / dash / ksh / busybox sh

       allmost all shells works fine if eval shunit_m in base  process,  tread
       as C-lang #define delective.

EXIT_STATUS
       asserts  returns 0/!0, _fail 1 2 >> $? == 0. ' _res' returns failed as‐
       sert count.

EXSAMPLE
   basic sample:
       #!/bin/sh
       src_main(){
         f1 "$@"
         return 0
       }
       f1(){
         [ $# -eq 2 ] || return 1
         num=$(($1+$2))
       }

       ## run main
       # src_main "$@"
       # exit $?

       ## run testcode
       cmd="$(shunit_m)"
       eval "$cmd"         #>> you can check with 'echo "$cmd"'

       f1   ;_fail
       f1 1 2    ;_eq 3 $num
       _res
       echo "--test suc"

       --test_run
       ~$ dash src.sh



   more general, separate style:
       ---src.sh
       #!/bin/sh
       src_main(){
         f1 "$@"
         return 0
       }
       f1(){
         [ $# -eq 2 ] || return 1
         num=$(($1+$2))
       }
       src_main "$@"       # @marker@

       --tests.sh
       #!/bin/sh
       eval "$(shunit_m -F)"
       test_1(){ f1 10 20; _fail ;}
       test_2(){ _eq 1 1 ;}
       eval "$(cat src.sh|grep -v '@marker@')"
       suite=$(sed -ne '/^test_[0-9]*[(]/{s/[(].*//p}'<$0)   #grep test_**
       for fc in $suite;do $fc ;done
       _res

       --test_run
       ~$ bash --posix tests.sh


   make portable/static test style:
       ~$ shunit_m -F #>> output code

       --tests.sh
       #!/bin/sh
       #---
       # copy&paste code
       #---
       test_1(){ _eq 1 1 ;}
       eval "$(cat src.sh|grep -v '@marker@')"
       suite=$(sed -ne '/^test_[0-9]*[(]/{s/[(].*//p}'<$0)
       for fc in $suite;do $fc ;done
       _res

       --test_run--
       ~$ sh tests.sh


NOTES
       --concept
        - respect posix
        - small is beautiful
        - avoid original syntax


CONFORMING_TO
       posix-shell

COPYRIGHT
       Copyright (C) 2021 Momi-g
       License GPLv3+ <https://gnu.org/licenses/gpl.html>

VERSION
       2021-10-12 v1.0.3

SEE_ALSO
       shunit2(1), shellspec



                                                                   SHUNIT_M(1)
/*--copyend shunit_m.1.txt*/
E E
exit 0
}

version_info(){
cat << 'EEE'
shunit_m v1.0.4
Copyright (C) 2021 Momi-g
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
EEE
exit 0
}

shunit_m_main(){
	## cmd=$(cat << 'END'   # 'END'.. not expand $foo etc. 
	## # opt dfl  type	( +add command you want to test input opt)	
	## 	-h  0		bool	'usage'
	## 	-H  0		bool	'usage_H'
	## 	-V  0		bool	'version_info'
	## 	-v  1		int		'[ 0 -le $opt_v ]&&[ $opt_v -le 2 ]'
	## 	-F  'exit $shunit_fcnt'	bool	'opt_F=":"'
	## 	-A  "_eq _neq _suc _fail _res"	str
	## END
	## )
	## buf=$($exdir/ckopt "$cmd")  #..or.. "$cmd"  (colon, quiet err mode as getopts )
	## eval "$buf"
# GPLv3 or upper, Copyright (C) 2018 Momi-g
# ---this code is generated by ckopt
# ---optsetting
## opt dfl  type( +add command you want to test input opt)
#-h  0 bool 'usage'
#-H  0 bool 'usage_H'
#-V  0 bool 'version_info'
#-v  1 int '[ 0 -le $opt_v ]&&[ $opt_v -le 2 ]'
#-F  'exit $shunit_fcnt' bool 'opt_F=":"'
#-A  "_eq _neq _suc _fail _res" str

ckopt_func () { 
if [ "$1" = "-d" ] ; then
	eval "opt_${2#?}"'="$3"'		# opt_?="$3"
	return 0
fi

ckopt_opt="$2"
if [ "$1" = "-e" ] ; then
shift 4
while [ $# -gt 0 ]
do
if [ "$1" != "${1#[#]e}" ] || [ "$1" != "${1#[#]E}" ] ; then
	eval "${1#[#]?}"
fi
shift
done
return 0
fi

if [ "$1" = "-c" ] ; then
shift 3
printf '%s\n' "$1" | tr '[:upper:]' '[:lower:]' | awk '$1 ~ /int/ {exit 1}'
	if [ "$?" = "1" ] ; then
		shift
		set -- "dummy" 'printf "%d" "$OPTARG" >/dev/null 2>&1;test $? = 0' "$@"
	fi
shift
while [ $# -gt 0 ]
do
	echo "$1"|awk '$1~/^#[eE]$/{exit 1}'
	test $? = 1 && shift && continue
	eval "$1"
	if [ "$?" = "1" ] ; then
		OPTARG="errmsg $ckopt_opt $1"
		return 1
	fi
shift
done
return 0
fi

echo "$0: fatal bug. sleep" >/dev/stderr
while :
do
	sleep 1000
done
exit 1
}

# ---dflset

OPTIND=1	# fixed value. posix rule.
OPTERR=0
OPTARG=
ckopt_buf=''
ckopt_opt=''
 ckopt_func -d -h 0 bool 'usage'
 ckopt_func -d -H 0 bool 'usage_H'
 ckopt_func -d -V 0 bool 'version_info'
 ckopt_func -d -v 1 int '[ 0 -le $opt_v ]&&[ $opt_v -le 2 ]'
 ckopt_func -d -F 'exit $shunit_fcnt' bool 'opt_F=":"'
 ckopt_func -d -A "_eq _neq _suc _fail _res" str
#---getopts loop. break if all args read or detect '--'
while :
do
if [ 0 -eq "$#" ] || [ "${OPTARG%% *}" = "errmsg" ] ; then
	break
fi
# eval展開するので先頭の$と合わせて check_opt=${3-} になる ${OPTIND}ではない
check_opt='check_opt=${'$OPTIND'-}'
eval "$check_opt"
if [ "$check_opt" = "--" ] ; then
	shift $OPTIND; break
fi
OPTARG=''
getopts ":hHVv:FA:" ckopt_opt "$@" || ! :	# ":a:bc:f:" etc...
if [ "$?" = 1 ]&&[ "${OPTARG:=@}" = '@' ]; then	# detect end/normal args. save general args.
	shift $((OPTIND - 1))
	if [ "$#" -eq "0" ] ; then
		break
	fi
	ckopt_buf="$ckopt_buf "`printf '%s' "$1" | sed -e "{        
	s#'#'\"'\"'#g
	s/^/'/g
	s/$/'/g
	}"`
	shift
	OPTIND=1
	continue
fi
# --- your setting

if [ "$ckopt_opt" = "h" ] ; then
	opt_h="1"
	ckopt_func -c -h  0 bool 'usage'
	continue
fi
if [ "$ckopt_opt" = "H" ] ; then
	opt_H="1"
	ckopt_func -c -H  0 bool 'usage_H'
	continue
fi
if [ "$ckopt_opt" = "V" ] ; then
	opt_V="1"
	ckopt_func -c -V  0 bool 'version_info'
	continue
fi
if [ "$ckopt_opt" = "v" ] ; then
	opt_v="$OPTARG"
	ckopt_func -c -v  1 int '[ 0 -le $opt_v ]&&[ $opt_v -le 2 ]'
	continue
fi
if [ "$ckopt_opt" = "F" ] ; then
	opt_F="1"
	ckopt_func -c -F  'exit $shunit_fcnt' bool 'opt_F=":"'
	continue
fi
if [ "$ckopt_opt" = "A" ] ; then
	opt_A="$OPTARG"
	ckopt_func -c -A  "_eq _neq _suc _fail _res" str
	continue
fi
# --- your setting end
# hit err. ckopt chars...
# err detect@silent mode
# $?=1 ... detect optend or '--'
# ':' ... detect option, but dont have subargs (OPTARG="factor").
# '?' ... detect unsupported option char (OPTARG="factor") or args end (OPTARG="blank").
# OPTARG ... "" is optend. "a/b/c..." is invalid option 
# OPTIND ... if err, OPTIND indicates next (new) arg pos. 
OPTARG="errmsg -$OPTARG bad option/no subarg: -hHVv:FA:"
done

# --- post process. set not optional args & clean & #eE last cmd.
for ii in 1	# dummyjump logic
do
	OPTIND=1
	# skip
	test "${OPTARG%% *}" != "errmsg" || break
	ckopt_buf="set -- $ckopt_buf"' "$@"'	# set -- 'cmd' .. "$@"
	eval "$ckopt_buf"

	# run #E cmd.  cmd + test $? ... x n
 ckopt_func -e -h 0 bool 'usage' ||break
 ckopt_func -e -H 0 bool 'usage_H' ||break
 ckopt_func -e -V 0 bool 'version_info' ||break
 ckopt_func -e -v 1 int '[ 0 -le $opt_v ]&&[ $opt_v -le 2 ]' ||break
 ckopt_func -e -F 'exit $shunit_fcnt' bool 'opt_F=":"' ||break
 ckopt_func -e -A "_eq _neq _suc _fail _res" str ||break
done

if [ "${OPTARG%% *}" = "errmsg" ] ; then
	printf "$0: opterr. %s\n" "$OPTARG" >/dev/stderr; exit 1
	test 1 = 0
fi
## ---generate by ckopt end

shunit_lv="$opt_v"
astnames="$opt_A"
stopcmd="$opt_F"

cmd=$(
set -- $astnames
cat<<EEE
###shunit_m drop code: GPLv3+
 shunit_acnt=0
 shunit_fcnt=0
 shunit_lv=$shunit_lv
 shunit_buf=\${LINENO:=no_line}
 
 alias shunit_buf=':'
 shunit_buf 2>/dev/null||{
	 echo "shunit: (bash?) alias doesnt work. add --posix option plz." >/dev/stderr
	 exit 1
 }
 unalias shunit_buf
 
 alias $1='shunit_eq "\$0:\$LINENO" $1'
 alias $2='shunit_neq "\$0:\$LINENO" $2'
 alias $3='shunit_suc "\$0:\$LINENO" $3 \$? _'
 alias $4='shunit_fail "\$0:\$LINENO" $4 \$? _'
 alias $5='shunit_res "\$0:\$LINENO"'
 
 shunit_eq(){ shunit_acf  ;[ "\$3"  = "\$4" ]&&shunit_smg "\$@"|| shunit_fcf "\$@" ;}
 shunit_neq(){ shunit_acf ;[ "\$3" != "\$4" ]&&shunit_smg "\$@"|| shunit_fcf "\$@";}
 shunit_suc(){ shunit_acf ;[ \$3  = 0 ]&&shunit_smg "\$@" ""|| shunit_fcf "\$@" ;}
 shunit_fail(){ shunit_acf;[ \$3 != 0 ]&&shunit_smg "\$@" ""|| shunit_fcf "\$@" ;}
 shunit_acf(){ shunit_acnt=\$((shunit_acnt+1));}
 shunit_smg(){ [ \$shunit_lv -lt 2 ]||printf '%s\n' "\$1: ok: \$2 test: \$3 \$4 \${5-}" >/dev/stderr;}
 shunit_fcf(){
 	shunit_fcnt=\$((shunit_fcnt+1))
 	[ \$shunit_lv -le 0 ]||printf '%s\n' "\$1: NG: \$2 test: \$3 \$4 \${5-}" >/dev/stderr
 	$stopcmd
 	return 1
 }
 shunit_res(){ [ \$shunit_lv -le 0 ]||printf '%s\n' "\$1: fail/all \$shunit_fcnt/\$shunit_acnt \${2-}" >/dev/stderr
	return \$shunit_fcnt
 }
###shunit_m drop code end:
EEE
)
printf '%s\n' "$cmd"
}

shunit_m_main "$@"	#SH_MAIN


cat << EEE >/dev/null
/*
 change log
 --
2021-12-01  Momi-g	<dmy@dmy.dmy>

	* shunit_m (doc): fix doc. v1.0.4

2021-10-12  Momi-g	<dmy@dmy.dmy>

	* shunit_m (doc): improve doc. v1.0.3

2021-09-12  Momi-g	<dmy@dmy.dmy>

	* shunit_m (assert): add ag3 for assert msg. v1.0.2

2021-08-27  Momi-g	<dmy@dmy.dmy>

	* shunit_m (none): publish v1.0.1
	
	* shunit_m (cmd) : replace echo >> printf, save \n
	* (main) : update abspath(printf)

2021-08-19  Momi-g	<dmy@dmy.dmy>

	* shunit_m (none): publish v1.0.0

*/
EEE
