/* 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.
*/
/*-*
@_name	HCUT_ADD, HCUT_RUN, eq, eq_s/i/d/p
@auther momi-g
@brief	C Unit Test, portable unittest implemented in 1 header file
@_synopsys
	void HCUT_ADD(fcname)
	int HCUT_RUN(const char* logfile, int msglv [, fcnames ...] )
	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)
	... all eq funcs can take lastarg as cmt, eq_i(1,2, "failtest_cmt") etc. 
@_eg
	#include <stdio.h>
	#include "hcut.h"		//header holds all implements 
	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 );	//HCUT_RUN() has 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.h"
	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. use as funcptr. test all suite you set here
@return		HCUT_RUN() returns failed assert cnt. rtn -1 if err.
@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.
 https://sourceforge.net/projects/cutest/files/cutest/1.5/
 https://en.wikipedia.org/wiki/List_of_unit_testing_frameworks
@_conforming c99+
@version 2021-07-10 v1.2.7
-*/

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

#include <stdio.h>
#if (__STDC_VERSION__ +0 < 199901L) 
	# include "needs compiler c99 or upper"
#endif
#if (199901L <= __STDC_VERSION__ +0)	/* nealy 200112L, _POSIX_C_SOURCE	c99*/
	#include <sys/types.h>
	#include <unistd.h>
	#define HCUT_Eactag	__func__, getpid()
#else
	#define HCUT_Eactag	"func:c99+", 0
#endif
#include <string.h>
#include <errno.h>
#define HCUT_Eact(xpr, msg, act)	if(xpr){ fprintf(stderr, \
	"ERR: %s %d %s() pid:%d %s msg:%s sys:%s\n",__FILE__,__LINE__, HCUT_Eactag \
	, "hit(" #xpr ")", msg, strerror(errno) ); act; }

//api_macro...see this srcend sample
#define HCUT_ADD(a)		HCUT_9611a28d_ADD(a)
#define HCUT_RUN(...)	HCUT_9611a28d_RUN(__VA_ARGS__)	
#define eq(...)		HCUT_9611a28d_assert_eq_sub(__VA_ARGS__, 0)
#define HCUT_9611a28d_assert_eq_sub(a, ...)	\
	HCUT_9611a28d_assert_eq(__FILE__, __LINE__, #a, !!(a), 1, __VA_ARGS__)

#define eq_s(...)	HCUT_9611a28d_assert_eqs_sub(__VA_ARGS__, 0)
#define HCUT_9611a28d_assert_eqs_sub(a, b, ...)	\
	HCUT_9611a28d_assert_eqs(__FILE__, __LINE__, #a ", " #b, a, b, __VA_ARGS__)

#define eq_i(...)	HCUT_9611a28d_assert_eqi_sub(__VA_ARGS__, 0)
#define HCUT_9611a28d_assert_eqi_sub(a, b, ...)	\
	HCUT_9611a28d_assert_eqi(__FILE__, __LINE__, #a ", " #b, (int)a, (int)b, __VA_ARGS__)

#define eq_d(...)	HCUT_9611a28d_assert_eqd_sub(__VA_ARGS__, 0)
#define HCUT_9611a28d_assert_eqd_sub(a, b, ...)	\
	HCUT_9611a28d_assert_eqd(__FILE__, __LINE__, #a ", " #b, (double)a, (double)b, __VA_ARGS__)

#define eq_p(...)	HCUT_9611a28d_assert_eqp_sub(__VA_ARGS__, 0)
#define HCUT_9611a28d_assert_eqp_sub(a, b, ...)	\
	HCUT_9611a28d_assert_eqp(__FILE__, __LINE__, #a ", " #b, (char*)a, (char*)b, __VA_ARGS__)


//--coresrc
#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)

// *p=NULL ... kill valgrind reachable mem warning
#define HCUT_9611a28d_RUN(out, lv, ...)		int main(int argc, char** argv){	\
	HCUT_9611a28d_tf_t tfarr[]={ __VA_ARGS__, NULL};	\
	int rc=HCUT_9611a28d_run(out, lv, tfarr, argc, argv);	\
	char** p = (char**)&tfarr;	\
	*p=NULL;	\
	return rc;	\
}

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) {
	FILE* fp=NULL;
	; HCUT_Eact(lv<0||2<lv, "verboseLv allows only 0,1,2" , exit(-1) );
	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){
		fp = fopen(out, "w+");
		; HCUT_Eact(fp==NULL, "fopen() failed: ", fputs(out, stderr);exit(-1); );
		HCUT_9611a28d_cmn.fp = fp;
	}
	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);
	if(fp){fclose(fp);}
	return HCUT_9611a28d_cmn.fail;
}

#define HCUT_9611a28d_assert_eq_SAME(fmt, lnum, str, a1, a2)	\
	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, lnum, HCUT_9611a28d_cmn.fcname, ckstr, a1, a2, msg); \
	}	\
	else if(1){	\
		HCUT_9611a28d_cmn.fail++;	\
		HCUT_9611a28d_pf(1, fmt, "FAIL", flname, lnum, HCUT_9611a28d_cmn.fcname, ckstr, a1, a2, msg); \
	}	\
	va_end(va);	\
/* mc_end */

void HCUT_9611a28d_assert_eq(const char* flname, int lnum
	, const char* ckstr, int a1, int a2, ...){
	int rc = (a1==a2);
	const char* fmt = "%s: %s:%d:%s(): (%s): %d(suc:%d)	:%s\n";
	HCUT_9611a28d_assert_eq_SAME(fmt, lnum, str, a1, a2);
}

void HCUT_9611a28d_assert_eqs(const char* flname, int lnum
	, 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(fmt, lnum, str, a1, a2);
}

void HCUT_9611a28d_assert_eqi(const char* flname, int lnum
	, 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(fmt, lnum, str, a1, a2);
}

void HCUT_9611a28d_assert_eqd(const char* flname, int lnum
	, 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(fmt, lnum, str, a1, a2);
}

void HCUT_9611a28d_assert_eqp(const char* flname, int lnum
	, 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(fmt, lnum, str, a1, a2);
}
#endif /* HCUT_9611a28d */
