#include <errmsg: see ... '~$ sh aaa.sh.c -h'   (other opt:no/-m/-w/)>	/*
C='^[/][/*]SH_';O=${0##*[/]};R=`dirname $0`;R=${R%/}/;R0=$R$O;Rb=$R${O%%.*};O="
";R=${0##*.};Rs=$Rb.$R;Rm=$Rb.tmp.$R;Rh=$Rb.h;R=$Rs$Rh$Rm;Rp='printf %s\n ';Rc=:
[ "${R##*$0*}" = '' ]&&$Rp"$0:NG suffix"&&exit 1;R='sed -ne ';Cm=$R'"/[E]ND/!d
:l;n;p;bl"<$R0>$Rm;$Rp"$Rm"';RB=$($R"s/${C}OP//p"<$R0|(F=mw;while read -r a b;do
B=${a%:};F=`$Rp"$F"|$R"s#$B:*##1;p"`${a%_};$Rp"C$B=\$(cat<<'E'$O$b${O}E$O)";done
$Rp"R1=$F"));Rw=$R'"/$C$R/!d;:l;n;/${C}ED/q;p;bl"<$R0';Cw="(R=LS;$Rw;$Rw>&3;R=HD
$Rw;R=SC;$Rw>&3)"'>$Rh 3>$Rs;$Rp"$Rh $Rs"';Re=eval\ ;$Re"$RB";while getopts $R1\
 R;do case $R in \?)exit 1;;*)$Re"O$R=\$OPTARG";Rc=$Rc$O`$Re'$Rp"$C'$R\"`;;esac
done;[ "$Rc" = : ]&&Rc=$Cm;shift $((OPTIND-1));$Re"$C_$O$Rc";exit   #END	*/

/*SH_LS*/
/* Copyright (C) 2019 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/>.
 */

/* add license cmt from cutest-1.5 README.txt */
/*
 NOTE
 
 The license is based on the zlib/libpng license. For more details see
 http://www.opensource.org/licenses/zlib-license.html. The intent of the
 license is to:
 
 - keep the license as simple as possible
 - encourage the use of CuTest in both free and commercial applications
   and libraries
 - keep the source code together
 - give credit to the CuTest contributors for their work
 
 If you ship CuTest in source form with your source distribution, the
 following license document must be included with it in unaltered form.
 If you find CuTest useful we would like to hear about it.
 
 LICENSE
 
 Copyright (c) 2003 Asim Jalis
 
 This software is provided 'as-is', without any express or implied
 warranty. In no event will the authors be held liable for any damages
 arising from the use of this software.
 
 Permission is granted to anyone to use this software for any purpose,
 including commercial applications, and to alter it and redistribute it
 freely, subject to the following restrictions:
 
 1. The origin of this software must not be misrepresented; you must not
 claim that you wrote the original software. If you use this software in
 a product, an acknowledgment in the product documentation would be
 appreciated but is not required.
 
 2. Altered source versions must be plainly marked as such, and must not
 be misrepresented as being the original software.
 
 3. This notice may not be removed or altered from any source
 distribution.
*/
/*SH_ED*/

/*SH_HD*/
/*-*
@_name	HCUT_ADD, HCUT_RUN, eq, eq_s/i/d/p
@auther momi-g
@brief	Header inc C Unit Test, portable unittest macros.
@_synopsys
	void HCUT_ADD(fcname)
	int HCUT_RUN(const char* logfile, int msglv, fcname1, fcname2, ...)
	void eq(int cmp)
	void eq_s(void* s1, void* s2)
	void eq_i(int i1, int i2)
	void eq_d(double d1, double d2)
	void eq_p(void* p1, void* p2)
	... eq asserts can take lastarg as cmt, eq_i(1,2, "failtest_cmt") etc. 
@_eg
	#include <stdio.h>
	#include "hcut.hpp"		//direct include. header+src in one-file. 
	int hw() {
		puts("hw");
		return 0;
	}
	HCUT_ADD(anyname_hw) {
		int ck = 123;
		eq( hw()==10 );
		eq_i( hw(), 100, "mycmt" );
		printf("bin:%s\n", argv[0]);
	}
	HCUT_RUN("./mytest.log", 1, anyname_hw );	//holding main() func
	// ~$ cc hw.c && ./a.out && echo $?	>>	drop ./mytest.log

@_eg
	-- hw.c
	#include <stdio.h>
	int hw() { puts("hw"); return 0; }
	-- test_hw.c
	#include "hcut.hpp"
	HCUT_ADD(t_hw1) { eq_i( hw(), 0, "mymsg" ); }
	HCUT_ADD(t_hw2) { printf("ag1:%s\n", argv[1]); eq( hw()==1 ); }
	HCUT_RUN("stderr", 2, t_hw1, t_hw2 );		// disp to stderr
	// ~$ cc hw.c test_hw.c && ./a.out xyz && echo $?

@param fcname	testfunc(test suite) name.  
@param logfile msgout fname. "stdout","stderr","NULL" are used as FILE* / noout.
@param lv	verbose lv. 0/1/2 ... quiet/normal/verbose.
@param fcname1,2-	test tgt. test all suite you set here.
@return		HCUT_RUN() returns failed assert cnt.
@details	HCUT_RUN() holds "int main(int argc, char** argv){...}" func.
 you can use cmdin data 'argc/argv' in HCUR_ADD() block.
@_note	other sample src is at srcend.
	ref:https://sourceforge.net/projects/cutest/files/cutest/1.5/
	ref:https://en.wikipedia.org/wiki/List_of_unit_testing_frameworks
@_conforming c99+ (200112L+)
@version 1.2.2, 2020-10-28
-*/

// change... sed -i 'HCUT_9611a28d' 'some_new_prefix_str'
#ifndef HCUT_9611a28d
#define HCUT_9611a28d

//api_macro...see this srcend sample
#define HCUT_ADD(a)		HCUT_9611a28d_ADD(a)
#define HCUT_RUN(...)		HCUT_9611a28d_RUN(__VA_ARGS__)	
#define eq(a, ...)		HCUT_9611a28d_assert_eq(__FILE__, __LINE__, #a, a, __VA_ARGS__ +0)
#define eq_s(a,b, ...)	HCUT_9611a28d_assert_eqs(__FILE__, __LINE__, #a "," #b, a, b, __VA_ARGS__ +0)
#define eq_i(a,b, ...)	HCUT_9611a28d_assert_eqi(__FILE__, __LINE__, #a "," #b, a, b, __VA_ARGS__ +0)
#define eq_d(a,b, ...)	HCUT_9611a28d_assert_eqd(__FILE__, __LINE__, #a "," #b, a, b, __VA_ARGS__ +0)
#define eq_p(a,b, ...)	HCUT_9611a28d_assert_eqp(__FILE__, __LINE__, #a "," #b, a, b, __VA_ARGS__ +0)

//mainsrc
#include <features.h>
#if ( _POSIX_C_SOURCE +0 < 200112L )
	#error	"needs compiler posix-2001 or upper(c99+)"
	#include "...dmy inc, preprocess stoper"
#endif

#include <errno.h>
#include <string.h>
#if  (199901L <= __STDC_VERSION__ +0)  /* nealy 200112L, _POSIX_C_SOURCE	c99*/
#define ERRact(xpr, msg, act)	do{if(xpr){ fprintf(stderr, \
	"ERR: %s %d: %s():%s msg:%s sys:%s\n",__FILE__,__LINE__, __func__,\
	"hit(" #xpr ")", msg, strerror(errno) );act ;} }while(0)
#else
#define ERRact(xpr, msg, act)	do{if(xpr){ fprintf(stderr, \
	"ERR: %s %d: %s():%s msg:%s sys:%s\n",__FILE__,__LINE__, "func:c99+",\
	"hit(" #xpr ")", msg, strerror(errno) );act ;} }while(0)
#endif

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>

typedef struct HCUT_9611a28d_cmn_tag{
	FILE* fp;
	int outlv;
	const char* fcname;
	int fail;
	int suc;
} HCUT_9611a28d_cmn_t;
static HCUT_9611a28d_cmn_t HCUT_9611a28d_cmn;

int HCUT_9611a28d_pf(int lv, const char* fmt, ...){
	if( HCUT_9611a28d_cmn.outlv < lv ){ return 0;}
	if( HCUT_9611a28d_cmn.fp == NULL ){ return 0;}
	va_list va;
	va_start(va, fmt);
	int rc = vfprintf(HCUT_9611a28d_cmn.fp, fmt, va);
	va_end(va);
	return rc;
}

#define HCUT_9611a28d_ADD(tfname)	void tfname ## _sub(int argc, char** argv);	\
	void tfname(int argc, char** argv){	HCUT_9611a28d_cmn.fcname = #tfname; tfname ## _sub(argc, argv);} \
	void tfname ## _sub(int argc, char** argv)

#define HCUT_9611a28d_RUN(out, lv, ...)		int main(int argc, char** argv){	\
	HCUT_9611a28d_tf_t tfarr[]={ __VA_ARGS__, NULL};	\
	return HCUT_9611a28d_run(out, lv, tfarr, argc, argv);	\
}

typedef void (*HCUT_9611a28d_tf_t)(int argc, char** argv);
int HCUT_9611a28d_run(const char* out, int lv, HCUT_9611a28d_tf_t* tfarr, int argc, char** argv) {
	if(strcmp(out, "stdout")==0 ){ HCUT_9611a28d_cmn.fp = stdout;}
	else if(strcmp(out, "stderr")==0 ){ HCUT_9611a28d_cmn.fp = stderr;}
	else if(strcmp(out, "NULL")==0 ){ HCUT_9611a28d_cmn.fp = NULL;}
	else if(1){
		FILE* fp = fopen(out, "w+");
		ERRact(fp==NULL, "fopen() failed: ", fputs(out, stderr);exit(1); );
		HCUT_9611a28d_cmn.fp = fp;
	}
	if( lv<0 || 2<lv){ ERRact(1, "verboseLv allows only 0,1,2" , exit(1) ); }
	HCUT_9611a28d_cmn.outlv = lv;
	int i;
	for(i=0; tfarr[i]!=NULL; i++){ tfarr[i](argc, argv); }
	HCUT_9611a28d_pf(1, "fail/all: %d/%d\n", HCUT_9611a28d_cmn.fail
		, HCUT_9611a28d_cmn.fail + HCUT_9611a28d_cmn.suc);
	return HCUT_9611a28d_cmn.fail;
}

void HCUT_9611a28d_assert_eq(const char* flname, int ln
	, const char* ckstr, int expr, ...){
	va_list va;
	va_start(va, expr);
	const char* msg = va_arg(va, char*);
	if(msg==NULL){msg="-"; }
	const char* fmt = "%s: %s %d %s(): (%s): %d (0:fail)	:%s\n";
	if(expr){
		HCUT_9611a28d_cmn.suc++;
		HCUT_9611a28d_pf(2, fmt, "suc", flname, ln, HCUT_9611a28d_cmn.fcname, ckstr, expr, msg); 
	}
	else if(1){
		HCUT_9611a28d_cmn.fail++;
		HCUT_9611a28d_pf(1, fmt, "FAIL", flname, ln, HCUT_9611a28d_cmn.fcname, ckstr, expr, msg); 
	}
	va_end(va);
}

#define HCUT_9611a28d_assert_eq_SAME	\
	va_list va;	\
	va_start(va, a2);	\
	const char* msg = va_arg(va, char*);	\
	if(msg==NULL){msg="-";}	\
	if(rc==1){	\
		HCUT_9611a28d_cmn.suc++;	\
		HCUT_9611a28d_pf(2, fmt, "suc", flname, ln, HCUT_9611a28d_cmn.fcname, ckstr, a1, a2, msg); \
	}	\
	else if(1){	\
		HCUT_9611a28d_cmn.fail++;	\
		HCUT_9611a28d_pf(1, fmt, "FAIL", flname, ln, HCUT_9611a28d_cmn.fcname, ckstr, a1, a2, msg); \
	}

void HCUT_9611a28d_assert_eqs(const char* flname, int ln
	, const char* ckstr, const char* a1, const char* a2, ...){
	int rc = (0==strcmp(a1, a2));
	const char* fmt = "%s: %s %d %s(): (%s): %s =s= %s	:%s\n";
	HCUT_9611a28d_assert_eq_SAME
}

void HCUT_9611a28d_assert_eqi(const char* flname, int ln
	, const char* ckstr, int a1, int a2, ...){
	int rc = (a1==a2);
	const char* fmt = "%s: %s %d %s(): (%s): %d =i= %d	:%s\n";
	HCUT_9611a28d_assert_eq_SAME
}

void HCUT_9611a28d_assert_eqd(const char* flname, int ln
	, const char* ckstr, double a1, double a2, ...){
	int rc = (a1==a2);
	const char* fmt = "%s: %s %d %s(): (%s): %f =d= %f	:%s\n";
	HCUT_9611a28d_assert_eq_SAME
}

void HCUT_9611a28d_assert_eqp(const char* flname, int ln
	, const char* ckstr, void* a1, void* a2, ...){
	int rc = (a1==a2);
	const char* fmt = "%s: %s %d %s(): (%s): %p =p= %p	:%s\n";
	HCUT_9611a28d_assert_eq_SAME
}
#endif /* HCUT_9611a28d */
/*SH_ED*/

/*SH_SC*/
// hpp, header only code
/*SH_SMP
#include <stdio.h>
#include "hcut.hpp"

int hw() {
	puts("hw");
	return 0;
}

// expand to "void t_hw(void){...}"
HCUT_ADD(t_hw) {
	int ck = 123;
	eq(hw() == 0 );
	eq(hw() == ck, "cmt if last arg(void*) is exist" );
}

int gw() {
	puts("gw");
	return 0;
}
HCUT_ADD(test_gw12345) {
	//assert type: cmp, int, dbl, str, ptr 
	eq(1==2, "all asserts support lastarg cmt");
	eq_i( gw(), 3);
	eq_d(1.01, (double)666);
	eq_s("aaa", "bbb");
	eq_p("aaa", NULL, "my_cmt");
	puts("my_test_end?");
}

HCUT_RUN("stderr", 1, t_hw, test_gw12345 );

// HCUT_RUN() holds "int main(int argc, char** argv){..}" and returns fail cnt.
// arg1 : msgout filename. "stdout","stderr","NULL" are used as FILE* or noout. 
// arg2 : verbose lv. 0/1/2 ... quiet/normal/verbose 
// arg3-: select test tgt. test all suite you set here. 
// rtn: cnt failed assert. rtn 0 if all asserts are success.
/*SH_SMPE*/

/*
 change log
 --
2020-10-28	Momi-g	<dmy@dmy.dmy>
	
	* hcut.sh.c: v1.2.2 release
	* hcut.sh.c(*SH_bn*): rename srcfile name, header suffix .cc -> .hpp
	* (brp.sh): add Rh="$bn.hpp", change .cc to .hpp

2020-10-25	Momi-g	<dmy@dmy.dmy>

	* hcut.sh.cc(assert_eqs): debug rc strcmp() >> (strcmp==0)
	change eqs args type void* >> const char*

2020-10-19	Momi-g	<dmy@dmy.dmy>

	* hcut.sh.cc: support cmdline data (argc, argv) at HCUT_ADD() codeblock
	* (doc): fix doc.

2020-06-30	Momi-g	<dmy@dmy.dmy>

	* hcut.sh.cc: v1.1.0 rewrite.

2020-02-02	Momi-g	<dmy@dmy.dmy>

	* hcut.sh.cc: v1.0.2 releases.

	* hcut.sh.cc (#define ast): change API stirng, ast->eq.
	* hcut.sh.cc (@sh-opt): fix Doc.

2020-1-11	Momi-g	<dmy@dmy.dmy>

	* hcut.sh.cc (@sh-opt): fix Doc.
	
2019-11-3	Momi-g	<dmy@dmy.dmy>
	
	* hcut.sh.cc: v1.0.1 releases.
	
	* hcut.sh.cc: (@sh-opt): build opt -b add. fix Doc. 

2019-10-15	Momi-g	<dmy@dmy.dmy>

	* hcut.sh.cc: v1.0.0 releases. 

*/
/*SH_ED*/

/*SH_OP _ a=`sed -ne "/${C}DF/!d;:l;n;/${C}DE/q;p;bl"<$R0`;eval "$a"	#*/
/*SH_OP g cat $*|sed -ne 's/.*\*SH_co\*\([^*]*\).*$/\1/p'|$u	#*/
/*SH_OP d find `dirname $0` -type d|sed -e 's/\(.*\)/ -I\1 -L\1 /g'|$N	#*/
/*SH_OP A (fo "$@";$e"$Cg$O$Cd")|$N	#*/

/*SH_OP	h $p"-t/s/b:test/smpl/bld /T:t+mem /f:functop"	 #*/
/*SH_OP	f sed -ne 's@^HCUT_ADD(\([^)]*\).*@\1,@p'<$R0;sed -e '/^[a-z].*)/!d;/;/d'<$R0 #*/

/*SH_OP t $e"$CW";[ "$1" != "" ]&&$i4;cat $Rs $tf>$Rm;$p'cc -g $Rm `fA $Rm`'|fv #*/
/*SH_OP s $e"$CB";f0 "${C}SMP" "${C}SMPE"<$Rs>eg.c;$p'cc eg.c `fA eg.c`'|fv #*/

/*SH_OP b $e"$CW";$p'cc -c $Rs  -O2 -Wall'|fv;$p"$bn.o" #*/
/*SH_OP B $e"$Cb";$p"ar r lib$bn.a `fo $Rh $Rs` $bn.o"|fv;$p"lib$bn.a"	#*/
/*SH_OP S $e"$CW";$p'cc $Rs `fo $Rh $Rs` `$e"$Cg$O$Cd"` -O2 -Wall -shared -fPIC -o lib'$bn'.so'|fv;$p"lib$bn.so" #*/
/*SH_OP W $e"$Cm$O$Cw";$i0;$i1;$cn<$Rh>$Rm;mv $Rm $Rh;$p"$Rs $tf"	#*/

/*SH_DF

#--name
bn=`basename ${Rs%.*}`
tf=${bn}_ts.${Rs##*.}
Rh="$bn.hpp"

#--cmd
e="eval "
p="$Rp" 
cn=$e'sed -e "s@\*${C##*]}bn\*@$bn@g"'
n=$e"tr ' \t' '\n'|grep -v ^$"
N=$e'(tr "\n" " ";$p)'
u=$e'$n|sort -u|$N'


f0()(sed -ne "/$1/!d;:l;n;/$2/d;p;bl"|$cn)
f1()(sed -e "/$1/!d;:l;/$2/{p;d};n;bl"|$cn)

fo()(a=$#;set `cat $@|(sed -ne 's/^#in.*"\(.*\.h\)".*$/\1/p';$p"$@")|$u`
if [ $a = $# ];then $p"$@"|$n|sed -ne 's/\.h$/.o/p'|grep -Fv "$bn.o";else fo "$@";fi)

fv()(while read -r a;do $e"cat<<E$O# $a${O}E">/dev/stderr;$e"$a";done)
fA()(set -- "$@";(fo "$@";$e"$Cg$O$Cd")|$N)

i0=$e'f0 "^#ifdef TEST" "^#endif"<$Rm|$cn>$tf'
i1=$e'sed -ne "/^#ifdef TEST/bl;p;d;:l;n;/^#endif/d;bl"<$Rs|$cn>$Rm;mv $Rm $Rs'
# i3=$e'cat $a|(sed -ne "s/^#in.*\"\(.*\.h\)\".*$/\1/p";$p"$a")|$u'
i4=$e'sed -ne "p;/_MAIN/bl;d;:l;n;/_AFT/{i\\$O$1);$O p;d};bl"<$tf>$Rm;mv $Rm $tf'
# i5=$e'sed -ne "/${C}SMP/!d;:l;n;/${C}SMPE/q;p;bl"'
# i5=$e'f0 "${C}SMP" "${C}SMPE"'

/*SH_DE*/
