2024-12-01 19:35:24 +00:00
//| Copyright: (C) 2020-2024 Kevin Larke <contact AT larke DOT org>
//| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
2024-05-29 16:32:33 +00:00
# include "cwCommon.h"
# include "cwLog.h"
# include "cwCommonImpl.h"
# include "cwTest.h"
# include "cwMem.h"
# include "cwText.h"
# include "cwNumericConvert.h"
# include "cwObject.h"
# include "cwLex.h"
# include "cwTime.h"
# include "cwFile.h"
# include "cwFileSys.h"
# include "cwVectOps.h"
2024-05-30 13:20:41 +00:00
# include "cwTextBuf.h"
# include "cwAudioDevice.h"
# include "cwAudioBufDecls.h"
# include "cwAudioBuf.h"
# include "cwMtx.h"
2024-07-31 21:18:42 +00:00
# include "cwAudioFile.h"
2024-07-13 15:58:14 +00:00
# include "cwDspTypes.h"
2024-07-31 21:18:42 +00:00
# include "cwMath.h"
# include "cwDsp.h"
# include "cwAudioTransforms.h"
2024-07-13 15:58:14 +00:00
# include "cwWaveTableBank.h"
2024-07-31 21:18:42 +00:00
# include "cwWaveTableNotes.h"
2024-07-13 15:58:14 +00:00
2024-05-29 16:32:33 +00:00
# include "cwFlowTest.h"
2024-09-15 18:54:25 +00:00
# include "cwThread.h"
# include "cwThreadMach.h"
2024-05-29 16:32:33 +00:00
namespace cw
{
namespace test
{
typedef struct test_map_str
{
const char * module_name ;
test_func_t test_func ;
} test_map_t ;
test_map_t _test_map [ ] = {
{ " /lex " , lex : : test } ,
{ " /filesys " , filesys : : test } ,
{ " /object " , object_test } ,
{ " /vop " , vop : : test } ,
2024-12-02 19:48:13 +00:00
{ " /dsp " , dsp : : test_dsp } ,
2024-05-29 16:32:33 +00:00
{ " /time " , time : : test } ,
{ " /flow " , flow : : test } ,
2024-05-30 13:20:41 +00:00
{ " /textBuf " , textBuf : : test } ,
{ " /audioBuf " , audio : : buf : : test } ,
{ " /mtx " , mtx : : test } ,
2024-07-13 15:58:14 +00:00
{ " /wt_bank " , wt_bank : : test } ,
2024-07-31 21:18:42 +00:00
{ " /audio_transform " , dsp : : test } ,
{ " /wt_note " , wt_note : : test } ,
2024-09-15 18:54:25 +00:00
{ " /thread_tasks " , thread_tasks : : test } ,
2024-11-29 18:15:44 +00:00
{ " /numeric_convert " , numericConvertTest } ,
2024-05-29 16:32:33 +00:00
{ nullptr , nullptr } ,
} ;
typedef struct test_str
{
int argc ; // extra cmd line arguments to be passes to the test cases
const char * * argv ;
const char * base_dir ; // base test dictionary
const object_t * test_cfg ; // top level test cfg.
const char * rsrc_folder ; // name of the 'rsrc' folder in the base dir
const char * out_folder ; // name of the output folder in the base dir
const char * ref_folder ; // name of the test reference folder in the base dir
const char * sel_module_label ; // selected module label
const char * sel_test_label ; // selected test label
bool all_module_fl ; // true if all modules should be run
bool all_test_fl ; // true if all tests in the selected module should be run
bool compare_fl ; // true if compare operation should be run
bool echo_fl ; // echo test output to the console (false=write all output to log file only)
bool gen_report_fl ; // print module/test names as the gen phase is executes
void * logCbArg ; // original log callback args
log : : logOutputCbFunc_t logCbFunc ;
const char * cur_test_label ; // current test label
file : : handle_t cur_log_fileH ; // current log file handle
unsigned gen_cnt ; // count of tests which generated output
unsigned compare_ok_cnt ; // count of tests which passed the compare pass
unsigned compare_fail_cnt ; // count of tests which failed the compare test
} test_t ;
rc_t _parse_args ( test_t & test , const object_t * cfg , int argc , const char * * argv )
{
rc_t rc = kOkRC ;
char * out_dir = nullptr ;
int argi = 1 ;
test . all_module_fl = true ;
test . all_test_fl = true ;
if ( argc > 1 )
{
test . sel_module_label = argv [ 1 ] ;
test . all_module_fl = textIsEqual ( test . sel_module_label , " all " ) ;
argi + = 1 ;
}
if ( argc > 2 )
{
test . sel_test_label = argv [ 2 ] ;
test . all_test_fl = textIsEqual ( test . sel_test_label , " all " ) ;
argi + = 1 ;
}
for ( int i = argi ; i < argc ; + + i )
{
if ( textIsEqual ( argv [ i ] , " compare " ) )
test . compare_fl = true ;
if ( textIsEqual ( argv [ i ] , " echo " ) )
test . echo_fl = true ;
if ( textIsEqual ( argv [ i ] , " gen_report " ) )
test . gen_report_fl = true ;
// test specific args follow the 'args' keyword
if ( textIsEqual ( argv [ i ] , " args " ) )
{
test . argc = argc - ( i + 1 ) ;
test . argv = argv + ( i + 1 ) ;
}
}
if ( ( rc = cfg - > readv ( " base_dir " , 0 , test . base_dir ,
" test " , kDictTId , test . test_cfg ,
" resource_dir " , 0 , test . rsrc_folder ,
" output_dir " , 0 , test . out_folder ,
" ref_dir " , 0 , test . ref_folder ) ) ! = kOkRC )
{
goto errLabel ;
}
if ( ( out_dir = filesys : : makeFn ( test . base_dir , nullptr , nullptr , test . out_folder , nullptr ) ) = = nullptr )
{
rc = cwLogError ( kOpFailRC , " Output directory name formation failed. " ) ;
goto errLabel ;
}
if ( ! filesys : : isDir ( out_dir ) )
{
if ( ( rc = filesys : : makeDir ( out_dir ) ) ! = kOkRC )
goto errLabel ;
}
errLabel :
mem : : release ( out_dir ) ;
return rc ;
}
void _exec_test_log_cb ( void * cbArg , unsigned level , const char * text )
{
rc_t rc ;
test_t * r = ( test_t * ) cbArg ;
if ( ( rc = file : : print ( r - > cur_log_fileH , text ) ) ! = kOkRC )
cwLogError ( rc , " Log file write failed for '%s'. " , text ) ;
if ( r - > logCbFunc ! = nullptr & & r - > echo_fl )
r - > logCbFunc ( r - > logCbArg , level , text ) ;
}
rc_t _exec_dispatch ( test_t & test , test_args_t & args )
{
rc_t rc = kOkRC ;
unsigned i = 0 ;
// find the requested test function ....
for ( ; _test_map [ i ] . module_name ! = nullptr ; + + i )
if ( textIsEqual ( _test_map [ i ] . module_name , args . module_label ) )
{
rc = _test_map [ i ] . test_func ( args ) ; //.... and call it
break ;
}
if ( _test_map [ i ] . module_name = = nullptr )
{
rc = cwLogError ( kEleNotFoundRC , " The test function for module %s was not found. " , cwStringNullGuard ( args . module_label ) ) ;
goto errLabel ;
}
errLabel :
return rc ;
}
rc_t _compare_one_test ( test_t & test , const char * ref_dir , const char * test_dir )
{
rc_t rc = kOkRC ;
unsigned testRefDirN = 0 ;
filesys : : dirEntry_t * testRefDirA = nullptr ;
bool ok_fl = true ;
// get the list of files in the test directory
if ( ( testRefDirA = filesys : : dirEntries ( ref_dir , filesys : : kFileFsFl , & testRefDirN ) ) = = nullptr )
{
rc = cwLogError ( kOpFailRC , " An error occurred while attempting to read the directory file names from '%s'. " , cwStringNullGuard ( ref_dir ) ) ;
goto errLabel ;
}
// for each file
for ( unsigned j = 0 ; rc = = kOkRC & & j < testRefDirN ; + + j )
if ( testRefDirA [ j ] . name ! = nullptr )
{
char * testRefFn = filesys : : makeFn ( ref_dir , testRefDirA [ j ] . name , nullptr , nullptr ) ;
char * testCmpFn = filesys : : makeFn ( test_dir , testRefDirA [ j ] . name , nullptr , nullptr ) ;
bool isEqualFl = false ;
// compare two files
if ( ( rc = file : : compare ( testRefFn , testCmpFn , isEqualFl ) ) ! = kOkRC )
rc = cwLogError ( rc , " The comparison failed on '%s' == '%s'. " , cwStringNullGuard ( testRefFn ) , cwStringNullGuard ( testCmpFn ) ) ;
else
{
// check compare status
if ( ! isEqualFl )
{
ok_fl = false ;
2024-06-01 12:51:36 +00:00
cwLogInfo ( " Compare failed on: '%s' " , testRefFn ) ;
2024-05-29 16:32:33 +00:00
}
}
mem : : release ( testRefFn ) ;
mem : : release ( testCmpFn ) ;
}
mem : : release ( testRefDirA ) ;
errLabel :
if ( ok_fl )
test . compare_ok_cnt + = 1 ;
else
test . compare_fail_cnt + = 1 ;
return rc ;
}
rc_t _exec_one_test ( test_t & test , const char * module_label , const object_t * module_args , const char * test_label , const object_t * test_args )
{
rc_t rc = kOkRC ;
char * rsrc_dir = nullptr ;
char * out_dir = nullptr ;
char * ref_dir = nullptr ;
char * log_fname = nullptr ;
test_args_t args = { } ;
if ( ( rsrc_dir = filesys : : makeFn ( test . base_dir , nullptr , nullptr , test . rsrc_folder , module_label , test_label , nullptr ) ) = = nullptr )
{
rc = cwLogError ( kOpFailRC , " Resource directory '%s' formation failed. " , cwStringNullGuard ( rsrc_dir ) ) ;
goto errLabel ;
}
if ( ( out_dir = filesys : : makeFn ( test . base_dir , nullptr , nullptr , test . out_folder , module_label , test_label , nullptr ) ) = = nullptr )
{
rc = cwLogError ( kOpFailRC , " Output directory '%s' formation failed. " , cwStringNullGuard ( out_dir ) ) ;
goto errLabel ;
}
if ( ( ref_dir = filesys : : makeFn ( test . base_dir , nullptr , nullptr , test . ref_folder , module_label , test_label , nullptr ) ) = = nullptr )
{
rc = cwLogError ( kOpFailRC , " Reference directory '%s' formation failed. " , cwStringNullGuard ( ref_dir ) ) ;
goto errLabel ;
}
if ( ( log_fname = filesys : : makeFn ( out_dir , " log " , " txt " , nullptr , nullptr ) ) = = nullptr )
{
rc = cwLogError ( kOpFailRC , " Log filename '%s' formation failed. " , cwStringNullGuard ( log_fname ) ) ;
goto errLabel ;
}
if ( ! filesys : : isDir ( out_dir ) )
{
if ( ( rc = filesys : : makeDir ( out_dir ) ) ! = kOkRC )
{
rc = cwLogError ( kOpFailRC , " Output directory '%s' create failed. " , cwStringNullGuard ( out_dir ) ) ;
goto errLabel ;
}
}
// open the log file
if ( ( rc = file : : open ( test . cur_log_fileH , log_fname , file : : kWriteFl ) ) ! = kOkRC )
{
rc = cwLogError ( rc , " The log file '%s' could not be created. " , cwStringNullGuard ( log_fname ) ) ;
goto errLabel ;
}
if ( test . gen_report_fl )
cwLogInfo ( " %s %s " , module_label , test_label ) ;
// save the log to a
test . logCbArg = log : : outputCbArg ( log : : globalHandle ( ) ) ;
test . logCbFunc = log : : outputCb ( log : : globalHandle ( ) ) ;
log : : setOutputCb ( log : : globalHandle ( ) , _exec_test_log_cb , & test ) ;
args . module_label = module_label ;
args . test_label = test_label ;
args . module_args = module_args ;
args . test_args = test_args ;
args . rsrc_dir = rsrc_dir ;
args . out_dir = out_dir ;
args . argc = test . argc ;
args . argv = test . argv ;
if ( ( rc = _exec_dispatch ( test , args ) ) ! = kOkRC )
goto errLabel ;
test . gen_cnt + = 1 ;
errLabel :
file : : close ( test . cur_log_fileH ) ;
log : : setOutputCb ( log : : globalHandle ( ) , test . logCbFunc , test . logCbArg ) ;
// if compare is enabled
if ( rc = = kOkRC & & test . compare_fl )
rc = _compare_one_test ( test , ref_dir , out_dir ) ;
if ( rc ! = kOkRC )
rc = cwLogError ( rc , " Test process failed on module:%s test:%s. " , module_label , test_label ) ;
mem : : release ( log_fname ) ;
mem : : release ( rsrc_dir ) ;
mem : : release ( out_dir ) ;
mem : : release ( ref_dir ) ;
return rc ;
}
rc_t _exec_cases ( test_t & test , const char * module_label , const object_t * module_args , const object_t * cases_cfg )
{
rc_t rc = kOkRC ;
// get count of test cases
unsigned testN = cases_cfg - > child_count ( ) ;
// for each test case
for ( unsigned i = 0 ; i < testN ; + + i )
{
const object_t * test_pair = cases_cfg - > child_ele ( i ) ;
const char * test_label = test_pair - > pair_label ( ) ;
// apply filter/test label filter
if ( ( test . all_module_fl | | textIsEqual ( module_label , test . sel_module_label ) ) & &
( test . all_test_fl | | textIsEqual ( test_label , test . sel_test_label ) ) )
{
if ( ( rc = _exec_one_test ( test , module_label , module_args , test_label , test_pair - > pair_value ( ) ) ) ! = kOkRC )
{
goto errLabel ;
}
}
}
errLabel :
return rc ;
}
rc_t _exec_module ( test_t & test , const char * module_label , const object_t * module_args , const object_t * module_cfg )
{
rc_t rc = kOkRC ;
const object_t * cases_cfg = nullptr ;
const object_t * mod_args_cfg = nullptr ;
if ( ( rc = module_cfg - > getv_opt ( " cases " , cases_cfg ,
" module_args " , mod_args_cfg ) ) ! = kOkRC )
{
rc = cwLogError ( rc , " Parse failed on module fields in '%s'. " , cwStringNullGuard ( module_label ) ) ;
goto errLabel ;
}
if ( cases_cfg = = nullptr )
cases_cfg = module_cfg ;
if ( mod_args_cfg = = nullptr )
mod_args_cfg = module_args ;
if ( ( rc = _exec_cases ( test , module_label , mod_args_cfg , cases_cfg ) ) ! = kOkRC )
goto errLabel ;
errLabel :
return rc ;
}
rc_t _proc_test_cfg ( test_t & test , const char * module_label , const object_t * test_cfg ) ;
rc_t _proc_test_from_file ( test_t & test , const char * module_label , const char * fname )
{
rc_t rc = kOkRC ;
char * cfg_fname ;
object_t * test_cases_cfg = nullptr ;
2024-07-03 18:44:05 +00:00
//const char* orig_base_dir = test.base_dir;
2024-05-29 16:32:33 +00:00
char * new_base_dir = nullptr ;
if ( ( cfg_fname = filesys : : makeFn ( test . base_dir , fname , nullptr , nullptr ) ) = = nullptr )
{
rc = cwLogError ( kOpFailRC , " The test cases file name for the module '%s' in '%s' / '%s' could not be formed. " , cwStringNullGuard ( module_label ) , cwStringNullGuard ( test . base_dir ) , cwStringNullGuard ( fname ) ) ;
goto errLabel ;
}
if ( ( rc = objectFromFile ( cfg_fname , test_cases_cfg ) ) ! = kOkRC )
{
rc = cwLogError ( kOpFailRC , " Parse failed on the test case module file '%s'. " , cwStringNullGuard ( cfg_fname ) ) ;
goto errLabel ;
}
if ( ( new_base_dir = filesys : : makeFn ( test . base_dir , nullptr , nullptr , test . out_folder , module_label , nullptr ) ) = = nullptr )
{
rc = cwLogError ( kOpFailRC , " The base test directory name for the module '%s' could not be formed in '%s'. " , cwStringNullGuard ( test . base_dir ) , cwStringNullGuard ( module_label ) ) ;
goto errLabel ;
}
if ( ! filesys : : isDir ( new_base_dir ) )
{
rc = cwLogError ( kOpFailRC , " The base test directory '%s' for the module '%s' does not exist. " , cwStringNullGuard ( test . base_dir ) , cwStringNullGuard ( module_label ) ) ;
goto errLabel ;
}
rc = _proc_test_cfg ( test , module_label , test_cases_cfg ) ;
errLabel :
mem : : release ( cfg_fname ) ;
mem : : release ( new_base_dir ) ;
if ( test_cases_cfg ! = nullptr )
test_cases_cfg - > free ( ) ;
return rc ;
}
rc_t _proc_module ( test_t & test , const char * base_module_label , const char * module_label , const object_t * module_cfg )
{
rc_t rc = kOkRC ;
char * new_module_label = filesys : : makeFn ( base_module_label , nullptr , nullptr , module_label , nullptr ) ;
2024-05-30 13:20:41 +00:00
// form the module output directory
char * out_dir = filesys : : makeFn ( test . base_dir , nullptr , nullptr , test . out_folder , new_module_label , nullptr ) ;
// verify that the the module output directory exists
if ( ! filesys : : isDir ( out_dir ) )
{
if ( ( rc = filesys : : makeDir ( out_dir ) ) ! = kOkRC )
{
rc = cwLogError ( rc , " The module output directory '%s' create failed. " , cwStringNullGuard ( out_dir ) ) ;
goto errLabel ;
}
}
2024-05-29 16:32:33 +00:00
switch ( module_cfg - > type_id ( ) )
{
2024-05-30 13:20:41 +00:00
case kStringTId : // an external module file was given
2024-05-29 16:32:33 +00:00
{
const char * s = nullptr ;
if ( ( rc = module_cfg - > value ( s ) ) ! = kOkRC )
{
rc = cwLogError ( rc , " Parse failed on module filename in '%s'. " , cwStringNullGuard ( module_label ) ) ;
goto errLabel ;
}
rc = _proc_test_from_file ( test , new_module_label , s ) ;
}
break ;
2024-05-30 13:20:41 +00:00
case kDictTId : // a nested module dict or case dict was given
2024-05-29 16:32:33 +00:00
rc = _proc_test_cfg ( test , new_module_label , module_cfg ) ;
break ;
default :
break ;
}
errLabel :
2024-05-30 13:20:41 +00:00
mem : : release ( out_dir ) ;
2024-05-29 16:32:33 +00:00
mem : : release ( new_module_label ) ;
return rc ;
}
rc_t _proc_test_cfg ( test_t & test , const char * module_label , const object_t * test_cfg )
{
rc_t rc = kOkRC ;
const object_t * module_args = nullptr ;
const object_t * modules_cfg = nullptr ;
const object_t * cases_cfg = nullptr ;
if ( ( rc = test_cfg - > getv_opt ( " module_args " , module_args ,
" modules " , modules_cfg ,
" cases " , cases_cfg ) ) ! = kOkRC )
{
rc = cwLogError ( rc , " The 'module_args' parse failed on module '%s'. " , cwStringNullGuard ( module_label ) ) ;
goto errLabel ;
}
// if a list of modules was given
if ( modules_cfg ! = nullptr )
{
unsigned modulesN = modules_cfg - > child_count ( ) ;
for ( unsigned i = 0 ; i < modulesN ; + + i )
{
const object_t * mod_pair = modules_cfg - > child_ele ( i ) ;
if ( ( rc = _proc_module ( test , module_label , mod_pair - > pair_label ( ) , mod_pair - > pair_value ( ) ) ) ! = kOkRC )
goto errLabel ;
}
}
2024-05-30 13:20:41 +00:00
// if no keywords were found then the dictionary must be a list of cases
2024-05-29 16:32:33 +00:00
if ( module_args = = nullptr & & modules_cfg = = nullptr & & cases_cfg = = nullptr )
{
cases_cfg = test_cfg ;
}
// if a list of cases was given
if ( cases_cfg ! = nullptr )
{
if ( ( rc = _exec_cases ( test , module_label , module_args , cases_cfg ) ) ! = kOkRC )
goto errLabel ;
}
errLabel :
return rc ;
}
}
}
cw : : rc_t cw : : test : : test ( const struct object_str * cfg , int argc , const char * * argv )
{
2024-07-03 18:44:05 +00:00
test_t test { } ;
2024-05-29 16:32:33 +00:00
rc_t rc = kOkRC ;
if ( ( rc = _parse_args ( test , cfg , argc , argv ) ) ! = kOkRC )
{
rc = cwLogError ( rc , " Test arguments parse failed. " ) ;
goto errLabel ;
}
if ( ( rc = _proc_test_cfg ( test , " / " , test . test_cfg ) ) ! = kOkRC )
{
goto errLabel ;
}
errLabel :
cwLogInfo ( " Test Gen Count:%i. " , test . gen_cnt ) ;
if ( test . compare_fl )
cwLogInfo ( " Test Compare - ok:%i fail:%i. " , test . compare_ok_cnt , test . compare_fail_cnt ) ;
if ( rc ! = kOkRC )
rc = cwLogError ( rc , " Testing process failed. " ) ;
return rc ;
}
2024-07-03 18:44:05 +00:00
cw : : rc_t cw : : test : : test ( const char * fname , int argc , const char * * argv )
{
rc_t rc = kOkRC ;
object_t * cfg = nullptr ;
const object_t * test_dict = nullptr ;
const object_t * test_test_dict = nullptr ;
if ( ( rc = objectFromFile ( fname , cfg ) ) ! = kOkRC )
{
rc = cwLogError ( rc , " Parsing failed on the test cfg file '%s'. " , cwStringNullGuard ( fname ) ) ;
goto errLabel ;
}
if ( ( rc = cfg - > get ( " test " , test_dict ) ) ! = kOkRC )
{
goto errLabel ;
}
if ( ( rc = test_dict - > get ( " test " , test_test_dict ) ) ! = kOkRC )
{
goto errLabel ;
}
rc = test ( test_test_dict , argc , argv ) ;
errLabel :
if ( cfg ! = nullptr )
cfg - > free ( ) ;
return rc ;
}
2024-05-29 16:32:33 +00:00