/*M///////////////////////////////////////////////////////////////////////////////////////
//
//  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
//  By downloading, copying, installing or using the software you agree to this license.
//  If you do not agree to this license, do not download, install,
//  copy or use the software.
//
//
//                        Intel License Agreement
//                For Open Source Computer Vision Library
//
// Copyright (C) 2000, Intel Corporation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//   * Redistribution's of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//
//   * Redistribution's in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//
//   * The name of Intel Corporation may not be used to endorse or promote products
//     derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/

#include "_cxts.h"
#include <ctype.h>
#include <stdarg.h>
#include <fcntl.h>
#include <time.h>
#if defined WIN32 || defined _WIN32 || defined WIN64 || defined _WIN64
#include <io.h>
#else
#include <unistd.h>
#endif

CvTest* CvTest::first = 0;
CvTest* CvTest::last = 0;
int CvTest::test_count = 0;

/*****************************************************************************************\
*                                Exception and memory handlers                            *
\*****************************************************************************************/

// a few platform-dependent declarations

#define CV_TS_NORMAL 0
#define CV_TS_BLUE   1
#define CV_TS_GREEN  2
#define CV_TS_RED    4

#if defined WIN32 || defined _WIN32 || defined WIN64 || defined _WIN64
#include <windows.h>

#ifdef _MSC_VER
#include <eh.h>
#endif

#ifdef _MSC_VER
static void cv_seh_translator( unsigned int /*u*/, EXCEPTION_POINTERS* pExp )
{
    int code = CvTS::FAIL_EXCEPTION;
    switch( pExp->ExceptionRecord->ExceptionCode )
    {
    case EXCEPTION_ACCESS_VIOLATION:
    case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
    case EXCEPTION_DATATYPE_MISALIGNMENT:
    case EXCEPTION_FLT_STACK_CHECK:
    case EXCEPTION_STACK_OVERFLOW:
    case EXCEPTION_IN_PAGE_ERROR:
        code = CvTS::FAIL_MEMORY_EXCEPTION;
        break;
    case EXCEPTION_FLT_DENORMAL_OPERAND:
    case EXCEPTION_FLT_DIVIDE_BY_ZERO:
    case EXCEPTION_FLT_INEXACT_RESULT:
    case EXCEPTION_FLT_INVALID_OPERATION:
    case EXCEPTION_FLT_OVERFLOW:
    case EXCEPTION_FLT_UNDERFLOW:
    case EXCEPTION_INT_DIVIDE_BY_ZERO:
    case EXCEPTION_INT_OVERFLOW:
        code = CvTS::FAIL_ARITHM_EXCEPTION;
        break;
    case EXCEPTION_BREAKPOINT:
    case EXCEPTION_ILLEGAL_INSTRUCTION:
    case EXCEPTION_INVALID_DISPOSITION:
    case EXCEPTION_NONCONTINUABLE_EXCEPTION:
    case EXCEPTION_PRIV_INSTRUCTION:
    case EXCEPTION_SINGLE_STEP:
        code = CvTS::FAIL_EXCEPTION;
    }
    throw code;
}
#endif

static void change_color( int color )
{
    static int normal_attributes = -1;
    HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE);
    fflush(stdout);

    if( normal_attributes < 0 )
    {
        CONSOLE_SCREEN_BUFFER_INFO info;
        GetConsoleScreenBufferInfo( hstdout, &info );
        normal_attributes = info.wAttributes;
    }

    SetConsoleTextAttribute( hstdout,
        (WORD)(color == CV_TS_NORMAL ? normal_attributes :
        ((color & CV_TS_BLUE ? FOREGROUND_BLUE : 0)|
        (color & CV_TS_GREEN ? FOREGROUND_GREEN : 0)|
        (color & CV_TS_RED ? FOREGROUND_RED : 0)|FOREGROUND_INTENSITY)) );
}

#else

#include <signal.h>

static const int cv_ts_sig_id[] = { SIGSEGV, SIGBUS, SIGFPE, SIGILL, SIGABRT, -1 };

static jmp_buf cv_ts_jmp_mark;

void cv_signal_handler( int sig_code )
{
    int code = CvTS::FAIL_EXCEPTION;
    switch( sig_code )
    {
    case SIGFPE:
        code = CvTS::FAIL_ARITHM_EXCEPTION;
        break;
    case SIGSEGV:
    case SIGBUS:
        code = CvTS::FAIL_ARITHM_EXCEPTION;
        break;
    case SIGILL:
        code = CvTS::FAIL_EXCEPTION;
    }

    longjmp( cv_ts_jmp_mark, code );
}

static void change_color( int color )
{
    static const uchar ansi_tab[] = { 30, 34, 32, 36, 31, 35, 33, 37 };
    char buf[16];
    int code = 0;
    fflush( stdout );
    if( color != CV_TS_NORMAL )
        code = ansi_tab[color & (CV_TS_BLUE|CV_TS_GREEN|CV_TS_RED)];
    sprintf( buf, "\x1b[%dm", code );
    fputs( buf, stdout );
}

#endif


// reads 16-digit hexadecimal number (i.e. 64-bit integer)
static int64 read_seed( const char* str )
{
    int64 val = 0;
    if( str && strlen(str) == 16 )
    {
        for( int i = 0; str[i]; i++ )
        {
            int c = tolower(str[i]);
            if( !isxdigit(c) )
                return 0;
            val = val * 16 +
            (str[i] < 'a' ? str[i] - '0' : str[i] - 'a' + 10);
        }
    }
    return val;
}


/***************************** memory manager *****************************/

typedef struct CvTestAllocBlock
{
    struct CvTestAllocBlock* prev;
    struct CvTestAllocBlock* next;
    char* origin;
    char* data;
    size_t size;
    int index;
}
CvTestAllocBlock;


class CvTestMemoryManager
{
public:
    CvTestMemoryManager( CvTS* ts );
    virtual ~CvTestMemoryManager();

    virtual void clear_and_check( int min_index = -1 );
    virtual void start_tracking( int index_to_stop_at=-1 );
    virtual void stop_tracking_and_check();
    int get_alloc_index() { return index; }

    static void* alloc_proxy( size_t size, void* userdata );
    static int free_proxy( void* ptr, void* userdata );

protected:
    virtual void* alloc( size_t size );
    virtual int free( void* ptr );
    virtual int free_block( CvTestAllocBlock* block );

    int index;
    int track_blocks;
    int show_msg_box;
    int index_to_stop_at;
    const char* guard_pattern;
    int guard_size;
    int block_align;
    enum { MAX_MARKS = 1024 };
    int marks[MAX_MARKS];
    int marks_top;
    CvTS* ts;
    CvTestAllocBlock* first;
    CvTestAllocBlock* last;
};


void* CvTestMemoryManager::alloc_proxy( size_t size, void* userdata )
{
    return ((CvTestMemoryManager*)userdata)->alloc( size );
}


int CvTestMemoryManager::free_proxy( void* ptr, void* userdata )
{
    return ((CvTestMemoryManager*)userdata)->free( ptr );
}


CvTestMemoryManager::CvTestMemoryManager( CvTS* _test_system )
{
    ts = _test_system;
    guard_pattern = "THIS IS A GUARD PATTERN!";
    guard_size = (int)strlen(guard_pattern);
    block_align = CV_MALLOC_ALIGN;
    track_blocks = 0;
    marks_top = 0;
    first = last = 0;
    index = 0;
    index_to_stop_at = -1;
    show_msg_box = 1;
}


CvTestMemoryManager::~CvTestMemoryManager()
{
    clear_and_check();
}


void CvTestMemoryManager::clear_and_check( int min_index )
{
    int alloc_index = -1;
    CvTestAllocBlock* block;
    int leak_size = 0, leak_block_count = 0, mem_size = 0;
    void* mem_addr = 0;

    while( marks_top > 0 && marks[marks_top - 1] >= min_index )
        marks_top--;

    for( block = last; block != 0; )
    {
        CvTestAllocBlock* prev = block->prev;
        if( block->index < min_index )
            break;
        leak_size += (int)block->size;
        leak_block_count++;
        alloc_index = block->index;
        mem_addr = block->data;
        mem_size = (int)block->size;
        free_block( block );
        block = prev;
    }
    track_blocks--;
    if( leak_block_count > 0 )
    {
        ts->set_failed_test_info( CvTS::FAIL_MEMORY_LEAK, alloc_index );
        ts->printf( CvTS::LOG, "Memory leaks: %u blocks, %u bytes total\n"
                    "%s leaked block: %p, %u bytes\n",
                    leak_block_count, leak_size, leak_block_count > 1 ? "The first" : "The",
                    mem_addr, mem_size );
    }

    index = block ? block->index + 1 : 0;
}


void CvTestMemoryManager::start_tracking( int _index_to_stop_at )
{
    track_blocks--;
    marks[marks_top++] = index;
    assert( marks_top <= MAX_MARKS );
    track_blocks+=2;
    index_to_stop_at = _index_to_stop_at >= index ? _index_to_stop_at : -1;
}


void CvTestMemoryManager::stop_tracking_and_check()
{
    if( marks_top > 0 )
    {
        int min_index = marks[--marks_top];
        clear_and_check( min_index );
    }
}


int CvTestMemoryManager::free_block( CvTestAllocBlock* block )
{
    int code = 0;
    char* data = block->data;

    if( block->origin == 0 || ((size_t)block->origin & (sizeof(double)-1)) != 0 )
        code = CvTS::FAIL_MEMORY_CORRUPTION_BEGIN;

    if( memcmp( data - guard_size, guard_pattern, guard_size ) != 0 )
        code = CvTS::FAIL_MEMORY_CORRUPTION_BEGIN;
    else if( memcmp( data + block->size, guard_pattern, guard_size ) != 0 )
        code = CvTS::FAIL_MEMORY_CORRUPTION_END;

    if( code >= 0 )
    {
        if( block->prev )
            block->prev->next = block->next;
        else if( first == block )
            first = block->next;

        if( block->next )
            block->next->prev = block->prev;
        else if( last == block )
            last = block->prev;

        free( block->origin );
    }
    else
    {
        ts->set_failed_test_info( code, block->index );
        ts->printf( CvTS::LOG, "Corrupted block (%s): %p, %u bytes\n",
                    code == CvTS::FAIL_MEMORY_CORRUPTION_BEGIN ? "beginning" : "end",
                    block->data, block->size );
    }

    return code;
}


void* CvTestMemoryManager::alloc( size_t size )
{
    char* data;
    CvTestAllocBlock* block;
    size_t new_size = sizeof(*block) + size + guard_size*2 + block_align + sizeof(size_t)*2;
    char* ptr = (char*)malloc( new_size );

    if( !ptr )
        return 0;

    data = (char*)cvAlignPtr( ptr + sizeof(size_t) + sizeof(*block) + guard_size, block_align );
    block = (CvTestAllocBlock*)cvAlignPtr( data - guard_size -
            sizeof(size_t) - sizeof(*block), sizeof(size_t) );
    block->origin = ptr;
    block->data = data;
    block->size = 0;
    block->index = -1;
    block->next = block->prev = 0;
    memcpy( data - guard_size, guard_pattern, guard_size );
    memcpy( data + size, guard_pattern, guard_size );

    if( track_blocks > 0 )
    {
        track_blocks--;
        block->size = size;

        if( index == index_to_stop_at )
        {
            if( show_msg_box )
            {
        #if defined WIN32 || defined _WIN32
                MessageBox( NULL, "The block that is corrupted and/or not deallocated has been just allocated\n"
                            "Press Ok to start debugging", "Memory Manager", MB_ICONERROR|MB_OK|MB_SYSTEMMODAL );
        #endif
            }
            CV_DBG_BREAK();
        }

        block->index = index++;

        block->prev = last;
        block->next = 0;
        if( last )
            last = last->next = block;
        else
            first = last = block;

        track_blocks++;
    }

    return data;
}


int CvTestMemoryManager::free( void* ptr )
{
    char* data = (char*)ptr;
    CvTestAllocBlock* block = (CvTestAllocBlock*)
        cvAlignPtr( data - guard_size - sizeof(size_t) - sizeof(*block), sizeof(size_t) );

    int code = free_block( block );
    if( code < 0 && ts->is_debug_mode() )
        CV_DBG_BREAK();
    return 0;
}


/***************************** error handler *****************************/

#if 0
static int cvTestErrorCallback( int status, const char* func_name, const char* err_msg,
                         const char* file_name, int line, void* userdata )
{
    if( status < 0 && status != CV_StsBackTrace && status != CV_StsAutoTrace )
        ((CvTS*)userdata)->set_failed_test_info( CvTS::FAIL_ERROR_IN_CALLED_FUNC );

    // print error message
    return cvStdErrReport( status, func_name, err_msg, file_name, line, 0 );
}
#endif

/*****************************************************************************************\
*                                    Base Class for Tests                                 *
\*****************************************************************************************/

CvTest::CvTest( const char* _test_name, const char* _test_funcs, const char* _test_descr ) :
    name(_test_name ? _test_name : ""), tested_functions(_test_funcs ? _test_funcs : ""),
    description(_test_descr ? _test_descr : ""), ts(0)
{
    if( last )
        last->next = this;
    else
        first = this;
    last = this;
    test_count++;
    ts = 0;
    hdr_state = 0;

    timing_param_names = 0;
    timing_param_current = 0;
    timing_param_seqs = 0;
    timing_param_idxs = 0;
    timing_param_count = -1;

    test_case_count = -1;
    support_testing_modes = CvTS::CORRECTNESS_CHECK_MODE;
}

CvTest::~CvTest()
{
    clear();
}

CvTest* CvTest::get_first_test()
{
    return first;
}

void CvTest::clear()
{
    if( timing_param_current )
        free( timing_param_current );
    if( timing_param_seqs )
        free( timing_param_seqs );
    if( timing_param_idxs )
        free( timing_param_idxs );

    timing_param_current = 0;
    timing_param_seqs = 0;
    timing_param_idxs = 0;
    timing_param_count = -1;
}


int CvTest::init( CvTS* _test_system )
{
    clear();
    ts = _test_system;
    return read_params( ts->get_file_storage() );
}


const char* CvTest::get_parent_name( const char* name, char* buffer )
{
    const char* dash_pos = strrchr( name ? name : "", '-' );
    if( !dash_pos )
        return 0;

    if( name != (const char*)buffer )
        strncpy( buffer, name, dash_pos - name );
    buffer[dash_pos - name] = '\0';
    return buffer;
}


const CvFileNode* CvTest::find_param( CvFileStorage* fs, const char* param_name )
{
    char buffer[256];
    const char* name = get_name();
    CvFileNode* node = 0;

    for(;;)
    {
        if( !name )
            break;
        node = cvGetFileNodeByName( fs, 0, name );
        if( node )
        {
            node = cvGetFileNodeByName( fs, node, param_name );
            if( node )
                break;
        }
        name = get_parent_name( name, buffer );
    }

    return node;
}


void CvTest::start_write_param( CvFileStorage* fs )
{
    if( hdr_state == 0 )
    {
        cvStartWriteStruct( fs, get_name(), CV_NODE_MAP );
        hdr_state = 1;
    }
}


void CvTest::write_param( CvFileStorage* fs, const char* paramname, int val )
{
    if( !ts->find_written_param( this, paramname, CV_NODE_INT, &val) )
    {
        start_write_param( fs );
        cvWriteInt( fs, paramname, val );
    }
}


void CvTest::write_param( CvFileStorage* fs, const char* paramname, double val )
{
    if( !ts->find_written_param( this, paramname, CV_NODE_REAL, &val) )
    {
        start_write_param( fs );
        cvWriteReal( fs, paramname, val );
    }
}


void CvTest::write_param( CvFileStorage* fs, const char* paramname, const char* val )
{
    if( !ts->find_written_param( this, paramname, CV_NODE_STRING, &val) )
    {
        start_write_param( fs );
        cvWriteString( fs, paramname, val );
    }
}


void CvTest::write_string_list( CvFileStorage* fs, const char* paramname, const char** val, int count )
{
    if( val )
    {
        start_write_param( fs );
        int i;
        if( count < 0 )
            count = INT_MAX;

        cvStartWriteStruct( fs, paramname, CV_NODE_SEQ + CV_NODE_FLOW );
        for( i = 0; i < count && val[i] != 0; i++ )
            cvWriteString( fs, 0, val[i] );
        cvEndWriteStruct( fs );
    }
}


void CvTest::write_int_list( CvFileStorage* fs, const char* paramname,
                             const int* val, int count, int stop_value )
{
    if( val )
    {
        start_write_param( fs );
        int i;
        if( count < 0 )
            count = INT_MAX;

        cvStartWriteStruct( fs, paramname, CV_NODE_SEQ + CV_NODE_FLOW );
        for( i = 0; i < count && val[i] != stop_value; i++ )
            cvWriteInt( fs, 0, val[i] );
        cvEndWriteStruct( fs );
    }
}


void CvTest::write_real_list( CvFileStorage* fs, const char* paramname,
                              const double* val, int count, double stop_value )
{
    if( val )
    {
        start_write_param( fs );
        int i;
        if( count < 0 )
            count = INT_MAX;

        cvStartWriteStruct( fs, paramname, CV_NODE_SEQ + CV_NODE_FLOW );
        for( i = 0; i < count && val[i] != stop_value; i++ )
            cvWriteReal( fs, 0, val[i] );
        cvEndWriteStruct( fs );
    }
}


int CvTest::read_params( CvFileStorage* fs )
{
    int code = 0;
    
    if(fs == NULL) return code; 

    if( ts->get_testing_mode() == CvTS::TIMING_MODE )
    {
        timing_param_names = find_param( fs, "timing_params" );
        if( CV_NODE_IS_SEQ(timing_param_names->tag) )
        {
            CvSeq* seq = timing_param_names->data.seq;
            CvSeqReader reader;
            cvStartReadSeq( seq, &reader );
            int i;

            timing_param_count = seq->total;
            timing_param_seqs = (const CvFileNode**)malloc( timing_param_count*sizeof(timing_param_seqs[0]));
            timing_param_idxs = (int*)malloc( timing_param_count*sizeof(timing_param_idxs[0]));
            timing_param_current = (const CvFileNode**)malloc( timing_param_count*sizeof(timing_param_current[0]));
            test_case_count = 1;

            for( i = 0; i < timing_param_count; i++ )
            {
                CvFileNode* param_name = (CvFileNode*)(reader.ptr);

                if( !CV_NODE_IS_STRING(param_name->tag) )
                {
                    ts->printf( CvTS::LOG, "ERROR: name of timing parameter #%d is not a string\n", i );
                    code = -1;
                    break;
                }

                timing_param_idxs[i] = 0;
                timing_param_current[i] = 0;
                timing_param_seqs[i] = find_param( fs, param_name->data.str.ptr );
                if( !timing_param_seqs[i] )
                {
                    ts->printf( CvTS::LOG, "ERROR: timing parameter %s is not found\n", param_name->data.str.ptr );
                    code = -1;
                    break;
                }

                if( CV_NODE_IS_SEQ(timing_param_seqs[i]->tag) )
                    test_case_count *= timing_param_seqs[i]->data.seq->total;

                CV_NEXT_SEQ_ELEM( seq->elem_size, reader );
            }

            if( i < timing_param_count )
                timing_param_count = 0;
        }
        else
        {
            ts->printf( CvTS::LOG, "ERROR: \"timing_params\" is not found" );
            code = -1;
        }
    }

    return code;
}


int CvTest::get_next_timing_param_tuple()
{
    bool increment;
    int i;

    if( timing_param_count <= 0 || !timing_param_names || !timing_param_seqs )
        return -1;

    increment = timing_param_current[0] != 0; // if already have some valid test tuple, move to the next
    for( i = 0; i < timing_param_count; i++ )
    {
        const CvFileNode* node = timing_param_seqs[i];
        int total = CV_NODE_IS_SEQ(node->tag) ? node->data.seq->total : 1;
        int new_idx = timing_param_idxs[i];

        if( !timing_param_current[i] )
            timing_param_idxs[i] = new_idx = 0;
        else if( increment )
        {
            new_idx++;
            if( new_idx >= total )
                new_idx = 0;
            else if( total > 1 )
                increment = false;
        }

        if( !timing_param_current[i] || new_idx != timing_param_idxs[i] )
        {
            if( CV_NODE_IS_SEQ(node->tag) )
                timing_param_current[i] = (CvFileNode*)cvGetSeqElem( node->data.seq, new_idx );
            else
                timing_param_current[i] = node;
            timing_param_idxs[i] = new_idx;
        }
    }

    return !increment; // return 0 in case of overflow (i.e. if there is no more test cases)
}


const CvFileNode* CvTest::find_timing_param( const char* paramname )
{
    if( timing_param_names )
    {
        int i;
        CvSeqReader reader;
        cvStartReadSeq( timing_param_names->data.seq, &reader, 0 );

        for( i = 0; i < timing_param_count; i++ )
        {
            const char* ptr = ((const CvFileNode*)(reader.ptr))->data.str.ptr;
            if( ptr[0] == paramname[0] && strcmp(ptr, paramname) == 0 )
                return timing_param_current[i];
            CV_NEXT_SEQ_ELEM( reader.seq->elem_size, reader );
        }
    }
    return 0;
}


int CvTest::write_defaults(CvTS* _ts)
{
    ts = _ts;
    hdr_state = 0;
    write_default_params( ts->get_file_storage() );
    if( hdr_state )
        cvEndWriteStruct( ts->get_file_storage() );
    return 0;
}


int CvTest::write_default_params( CvFileStorage* fs )
{
    if( ts->get_testing_mode() == CvTS::TIMING_MODE )
        write_string_list( fs, "timing_params", default_timing_param_names, timing_param_count );
    return 0;
}


bool CvTest::can_do_fast_forward()
{
    return true;
}


int CvTest::get_support_testing_modes()
{
    return support_testing_modes;
}

void CvTest::safe_run( int start_from )
{
    if(ts->is_debug_mode())
        run( start_from );
    else
    {
        try
        {
        #if !defined WIN32 && !defined _WIN32
        int _code = setjmp( cv_ts_jmp_mark );
        if( !_code )
            run( start_from );
        else
            throw _code;
        #else
            run( start_from );
        #endif
        }
        catch (const cv::Exception& exc)
        {
            const char* errorStr = cvErrorStr(exc.code);
            char buf[1 << 16];
            
            sprintf( buf, "OpenCV Error: %s (%s) in %s, file %s, line %d",
                    errorStr, exc.err.c_str(), exc.func.size() > 0 ?
                    exc.func.c_str() : "unknown function", exc.file.c_str(), exc.line );
            ts->printf(CvTS::LOG, "%s\n", buf);
            ts->set_failed_test_info( CvTS::FAIL_ERROR_IN_CALLED_FUNC );
        }
        catch (...)
        {
            ts->set_failed_test_info( CvTS::FAIL_EXCEPTION );
        }
    }
}


void CvTest::run( int start_from )
{
    int i, test_case_idx, count = get_test_case_count();
    int64 t_start = cvGetTickCount();
    double freq = cv::getTickFrequency();
    bool ff = can_do_fast_forward();
    int progress = 0, code;
    std::vector<double> v_cpe, v_time;
    int64 t1 = t_start;

    for( test_case_idx = ff && start_from >= 0 ? start_from : 0;
         count < 0 || test_case_idx < count; test_case_idx++ )
    {
        ts->update_context( this, test_case_idx, ff );
        progress = update_progress( progress, test_case_idx, count, (double)(t1 - t_start)/(freq*1000) );
        
        int64 t00 = 0, t0 = 0, t2 = 0, t3 = 0;
        double t_acc = 0, t_cpu_acc = 0;
        
        if( ts->get_testing_mode() == CvTS::TIMING_MODE )
        {
            const int iterations = 20;
            code = prepare_test_case( test_case_idx );

            if( code < 0 || ts->get_err_code() < 0 )
                return;

            if( code == 0 )
                continue;
                
            v_cpe.resize(0);
            v_time.resize(0);

            for( i = 0; i < iterations; i++ )
            {
                for(;;)
                {
					t0 = cv::getTickCount();
					t2 = cv::getCPUTickCount();
					run_func();
					t3 = cv::getCPUTickCount();
					t1 = cv::getTickCount();
					if( ts->get_err_code() < 0 )
						return;

					if( t3 - t2 > 0 && t1 - t0 > 1 )
						break;
				}

				if( i == 0 )
					t00 = t0;
				v_cpe.push_back((double)(t3 - t2));
				v_time.push_back((double)(t1 - t0));
                if( i >= 5 && t1 - t00 > freq*5 )
                    break;
            }

			sort(v_cpe.begin(), v_cpe.end());
			sort(v_time.begin(), v_time.end());
			
            t_cpu_acc = v_cpe[i/2];
            t_acc = v_time[i/2];
            print_time( test_case_idx, t_acc, t_cpu_acc );
        }
        else
        {
            code = prepare_test_case( test_case_idx );
            if( code < 0 || ts->get_err_code() < 0 )
                return;

            if( code == 0 )
                continue;

            run_func();
            if( ts->get_err_code() < 0 )
                return;

            if( validate_test_results( test_case_idx ) < 0 || ts->get_err_code() < 0 )
                return;
        }
    }
}


void CvTest::run_func()
{
    assert(0);
}


int CvTest::get_test_case_count()
{
    return test_case_count;
}


int CvTest::prepare_test_case( int )
{
    return 0;
}


int CvTest::validate_test_results( int )
{
    return 0;
}


void CvTest::print_time( int /*test_case_idx*/, double /*time_usecs*/, double /*time_cpu_clocks*/ )
{
}


int CvTest::update_progress( int progress, int test_case_idx, int count, double dt )
{
    int width = 60 - (int)strlen(get_name());
    if( count > 0 )
    {
        int t = cvRound( ((double)test_case_idx * width)/count );
        if( t > progress )
        {
            ts->printf( CvTS::CONSOLE, "." );
            progress = t;
        }
    }
    else if( cvRound(dt) > progress )
    {
        ts->printf( CvTS::CONSOLE, "." );
        progress = cvRound(dt);
    }

    return progress;
}


CvBadArgTest::CvBadArgTest( const char* _test_name, const char* _test_funcs, const char* _test_descr )
  : CvTest( _test_name, _test_funcs, _test_descr )
{
    progress = -1;
    test_case_idx = -1;
    freq = cv::getTickFrequency();
}

CvBadArgTest::~CvBadArgTest()
{
}

int CvBadArgTest::run_test_case( int expected_code, const char* descr )
{
    double new_t = (double)cv::getTickCount(), dt;
    if( test_case_idx < 0 )
    {
        test_case_idx = 0;
        progress = 0;
        dt = 0;
    }
    else
    {
        dt = (new_t - t)/(freq*1000);
        t = new_t;
    }
    progress = update_progress(progress, test_case_idx, 0, dt);
    
    int errcount = 0;
    bool thrown = false;
    if(!descr)
        descr = "";
    
    try
    {
        run_func();
    }
    catch(const cv::Exception& e)
    {
        thrown = true;
        if( e.code != expected_code )
        {
            ts->printf(CvTS::LOG, "%s (test case #%d): the error code %d is different from the expected %d\n",
                descr, test_case_idx, e.code, expected_code);
            errcount = 1;
        }
    }
    catch(...)
    {
        thrown = true;
        ts->printf(CvTS::LOG, "%s  (test case #%d): unknown exception was thrown (the function has likely crashed)\n",
                   descr, test_case_idx);
        errcount = 1;
    }
    if(!thrown)
    {
        ts->printf(CvTS::LOG, "%s  (test case #%d): no expected exception was thrown\n",
                   descr, test_case_idx);
        errcount = 1;
    }
    test_case_idx++;
    
    return errcount;
}

/*****************************************************************************************\
*                                 Base Class for Test System                              *
\*****************************************************************************************/

/******************************** Constructors/Destructors ******************************/

CvTS::CvTS()
{
    start_time = 0;
    version = CV_TS_VERSION;
    memory_manager = 0;
    /*
    memory_manager = new CvTestMemoryManager(this);
    cvSetMemoryManager( CvTestMemoryManager::alloc_proxy,
                        CvTestMemoryManager::free_proxy,
                        memory_manager );*/
    ostrm_suffixes[SUMMARY_IDX] = ".sum";
    ostrm_suffixes[LOG_IDX] = ".log";
    ostrm_suffixes[CSV_IDX] = ".csv";
    ostrm_suffixes[CONSOLE_IDX] = 0;
    ostrm_base_name = 0;
    memset( output_streams, 0, sizeof(output_streams) );
    memset( &params, 0, sizeof(params) );
    selected_tests = new CvTestPtrVec();
    failed_tests = new CvTestInfoVec();
    written_params = new CvTestPtrVec();

    clear();
}


void CvTS::clear()
{
    int i;
    CvTest* test;

    for( test = get_first_test(); test != 0; test = test->get_next() )
        test->clear();

    for( i = 0; i <= CONSOLE_IDX; i++ )
    {
        if( i == LOG_IDX )
            fflush( stderr );
        else if( i == CONSOLE_IDX )
            fflush( stdout );

        if( i < CONSOLE_IDX && output_streams[i].f )
        {
            fclose( output_streams[i].f );
            output_streams[i].f = 0;
        }

        if( i == LOG_IDX && output_streams[i].default_handle > 0 )
        {
            dup2( output_streams[i].default_handle, 2 );
            output_streams[i].default_handle = 0;
        }
        output_streams[i].enable = 1;
    }
    cvReleaseFileStorage( &fs );
    selected_tests->clear();
    failed_tests->clear();
    if( ostrm_base_name )
    {
        free( ostrm_base_name );
        ostrm_base_name = 0;
    }
    params.rng_seed = 0;
    params.debug_mode = -1;
    params.print_only_failed = 0;
    params.skip_header = 0;
    params.test_mode = CORRECTNESS_CHECK_MODE;
    params.timing_mode = MIN_TIME;
    params.use_optimized = -1;
    params.color_terminal = 1;

    if( memory_manager )
        memory_manager->clear_and_check();
}


CvTS::~CvTS()
{
    clear();
    set_data_path(0);

    if( written_params )
    {
        for( int i = 0; i < written_params->size(); i++ )
            free( written_params->at(i) );
        delete written_params;
    }

    delete selected_tests;
    delete failed_tests;
}


const char* CvTS::str_from_code( int code )
{
    switch( code )
    {
    case OK: return "Ok";
    case FAIL_GENERIC: return "Generic/Unknown";
    case FAIL_MISSING_TEST_DATA: return "No test data";
    case FAIL_INVALID_TEST_DATA: return "Invalid test data";
    case FAIL_ERROR_IN_CALLED_FUNC: return "cvError invoked";
    case FAIL_EXCEPTION: return "Hardware/OS exception";
    case FAIL_MEMORY_EXCEPTION: return "Invalid memory access";
    case FAIL_ARITHM_EXCEPTION: return "Arithmetic exception";
    case FAIL_MEMORY_CORRUPTION_BEGIN: return "Corrupted memblock (beginning)";
    case FAIL_MEMORY_CORRUPTION_END: return "Corrupted memblock (end)";
    case FAIL_MEMORY_LEAK: return "Memory leak";
    case FAIL_INVALID_OUTPUT: return "Invalid function output";
    case FAIL_MISMATCH: return "Unexpected output";
    case FAIL_BAD_ACCURACY: return "Bad accuracy";
    case FAIL_HANG: return "Infinite loop(?)";
    case FAIL_BAD_ARG_CHECK: return "Incorrect handling of bad arguments";
    default: return "Generic/Unknown";
    }
}

/************************************** Running tests **********************************/

void CvTS::make_output_stream_base_name( const char* config_name )
{
    int k, len = (int)strlen( config_name );

    if( ostrm_base_name )
        free( ostrm_base_name );

    for( k = len-1; k >= 0; k-- )
    {
        char c = config_name[k];
        if( c == '.' || c == '/' || c == '\\' || c == ':' )
            break;
    }

    if( k > 0 && config_name[k] == '.' )
        len = k;

    ostrm_base_name = (char*)malloc( len + 1 );
    memcpy( ostrm_base_name, config_name, len );
    ostrm_base_name[len] = '\0';
}


void CvTS::set_handlers( bool on )
{
    if( on )
    {
        cvSetErrMode( CV_ErrModeParent );
        cvRedirectError( cvStdErrReport );
    #if defined WIN32 || defined _WIN32
        #ifdef _MSC_VER
        _set_se_translator( cv_seh_translator );
        #endif
    #else
        for( int i = 0; cv_ts_sig_id[i] >= 0; i++ )
            signal( cv_ts_sig_id[i], cv_signal_handler );
    #endif
    }
    else
    {
        cvSetErrMode( CV_ErrModeLeaf );
        cvRedirectError( cvGuiBoxReport );
    #if defined WIN32 || defined _WIN32
        #ifdef _MSC_VER
        _set_se_translator( 0 );
        #endif
    #else
        for( int i = 0; cv_ts_sig_id[i] >= 0; i++ )
            signal( cv_ts_sig_id[i], SIG_DFL );
    #endif
    }
}


void CvTS::set_data_path( const char* data_path )
{
    if( data_path == params.data_path )
        return;

    if( params.data_path )
        delete[] params.data_path;
    if( data_path )
    {
        int size = (int)strlen(data_path)+1;
        bool append_slash = data_path[size-1] != '/' && data_path[size-1] != '\\';
        params.data_path = new char[size+1];
        memcpy( params.data_path, data_path, size );
        if( append_slash )
            strcat( params.data_path, "/" );
    }
}


typedef struct CvTsParamVal
{
    const char* fullname;
    const void* val;
}
CvTsParamVal;

int CvTS::find_written_param( CvTest* test, const char* paramname, int valtype, const void* val )
{
    const char* testname = test->get_name();
    bool add_to_list = test->get_func_list()[0] == '\0';
    char buffer[256];
    int paramname_len = (int)strlen(paramname);
    int paramval_len = valtype == CV_NODE_INT ? (int)sizeof(int) :
        valtype == CV_NODE_REAL ? (int)sizeof(double) : -1;
    const char* name = CvTest::get_parent_name( testname, buffer );

    if( !fs )
        return -1;

    if( paramval_len < 0 )
    {
        assert(0); // unsupported parameter type
        return -1;
    }

    while( name )
    {
        int i, len = (int)strlen(buffer);
        buffer[len] = '.';
        memcpy( buffer + len + 1, paramname, paramname_len + 1 );
        for( i = 0; i < written_params->size(); i++ )
        {
            CvTsParamVal* param = (CvTsParamVal*)written_params->at(i);
            if( strcmp( param->fullname, buffer ) == 0 )
            {
                if( (paramval_len > 0 && memcmp( param->val, val, paramval_len ) == 0) ||
                    (paramval_len < 0 && strcmp( (const char*)param->val, (const char*)val ) == 0) )
                    return 1;
                break;
            }
        }
        if( i < written_params->size() )
            break;
        buffer[len] = '\0';
        name = CvTest::get_parent_name( buffer, buffer );
    }

    if( add_to_list )
    {
        int bufsize, fullname_len = (int)strlen(testname) + paramname_len + 2;
        CvTsParamVal* param;
        if( paramval_len < 0 )
            paramval_len = (int)strlen((const char*)val) + 1;
        bufsize = sizeof(*param) + fullname_len + paramval_len;
        param = (CvTsParamVal*)malloc(bufsize);
        param->fullname = (const char*)(param + 1);
        param->val = param->fullname + fullname_len;
        sprintf( (char*)param->fullname, "%s.%s", testname, paramname );
        memcpy( (void*)param->val, val, paramval_len );
        written_params->push( param );
    }

    return 0;
}


#ifndef MAX_PATH
#define MAX_PATH 1024
#endif

static int CV_CDECL cmp_test_names( const void* a, const void* b )
{
    return strcmp( (*(const CvTest**)a)->get_name(), (*(const CvTest**)b)->get_name() );
}

int CvTS::run( int argc, char** argv )
{
    time( &start_time );

    int i, write_params = 0;
    int list_tests = 0;
    CvTestPtrVec all_tests;
    CvTest* test;

    // 0. reset all the parameters, reorder tests
    clear();

/*#if defined WIN32 || defined _WIN32
	cv::setBreakOnError(true);
#endif*/

    for( test = get_first_test(), i = 0; test != 0; test = test->get_next(), i++ )
        all_tests.push(test);

    if( all_tests.size() > 0 && all_tests.data() )
        qsort( all_tests.data(), all_tests.size(), sizeof(CvTest*), cmp_test_names );

    // 1. parse command line options
    for( i = 1; i < argc; i++ )
    {
        if( strcmp( argv[i], "-h" ) == 0 || strcmp( argv[i], "--help" ) == 0 )
        {
            print_help();
            return 0;
        }
        else if( strcmp( argv[i], "-f" ) == 0 )
            config_name = argv[++i];
        else if( strcmp( argv[i], "-w" ) == 0 )
            write_params = 1;
        else if( strcmp( argv[i], "-t" ) == 0 )
            params.test_mode = TIMING_MODE;
        else if( strcmp( argv[i], "-O0" ) == 0 || strcmp( argv[i], "-O1" ) == 0 )
            params.use_optimized = argv[i][2] - '0';
        else if( strcmp( argv[i], "-l" ) == 0 )
            list_tests = 1;
        else if( strcmp( argv[i], "-d" ) == 0 )
            set_data_path(argv[++i]);
        else if( strcmp( argv[i], "-nc" ) == 0 )
            params.color_terminal = 0;
        else if( strcmp( argv[i], "-r" ) == 0 )
            params.debug_mode = 0;
        else if( strcmp( argv[i], "-tn" ) == 0 )
        {
            params.test_filter_pattern = argv[++i];
            params.test_filter_mode = CHOOSE_TESTS;
        }
        else if( strcmp( argv[i], "-seed" ) == 0 )
        {
            params.rng_seed = read_seed(argv[++i]);
            if( params.rng_seed == 0 )
                fprintf(stderr, "Invalid or zero RNG seed. Will use the seed from the config file or default one\n");
        }
    }

#if 0
//#if !defined WIN32 && !defined _WIN32
    if (! config_name )
    {    
      char * confname = getenv("configname");
      if (confname)
        config_name = confname;
    }
    
    if( !params.data_path || !params.data_path[0] )
    {
        char* datapath = getenv("datapath");
        if( datapath )
            set_data_path(datapath);
    }
    
    // this is the fallback for the current OpenCV autotools setup
    if( !params.data_path || !params.data_path[0] )
    {
        char* srcdir = getenv("srcdir");
        char buf[1024];
        if( srcdir )
        {
            sprintf( buf, "%s/../../opencv_extra/testdata/", srcdir );
            set_data_path(buf);
        }
    }
#endif

    if( write_params )
    {
        if( !config_name )
        {
            printf( LOG, "ERROR: output config name is not specified\n" );
            return -1;
        }
        fs = cvOpenFileStorage( config_name, 0, CV_STORAGE_WRITE );
        if( !fs )
        {
            printf( LOG, "ERROR: could not open config file %s\n", config_name );
            return -1;
        }
        cvWriteComment( fs, CV_TS_VERSION " config file", 0 );
        cvStartWriteStruct( fs, "common", CV_NODE_MAP );
        write_default_params( fs );
        cvEndWriteStruct( fs );

        for( i = 0; i < all_tests.size(); i++ )
        {
            test = (CvTest*)all_tests[i];
            if( !(test->get_support_testing_modes() & get_testing_mode()) )
                continue;
            test->write_defaults( this );
            test->clear();
        }
        cvReleaseFileStorage( &fs );
        return 0;
    }

    if( !config_name )
        printf( LOG, "WARNING: config name is not specified, using default parameters\n" );
    else
    {
        // 2. read common parameters of test system
        fs = cvOpenFileStorage( config_name, 0, CV_STORAGE_READ );
        if( !fs )
        {
            printf( LOG, "ERROR: could not open config file %s", config_name );
            return -1;
        }
    }

    if( params.test_mode == CORRECTNESS_CHECK_MODE || fs )
    {
        // in the case of algorithmic tests we always run read_params,
        // even if there is no config file
        if( read_params(fs) < 0 )
            return -1;
    }

    if( !ostrm_base_name )
        make_output_stream_base_name( config_name ? config_name : argv[0] );

    ostream_testname_mask = -1; // disable printing test names at initial stage

    // 3. open file streams
    for( i = 0; i < CONSOLE_IDX; i++ )
    {
        char filename[MAX_PATH];
        sprintf( filename, "%s%s", ostrm_base_name, ostrm_suffixes[i] );
        output_streams[i].f = fopen( filename, "wt" );
        if( !output_streams[i].f )
        {
            printf( LOG, "ERROR: could not open %s\n", filename );
            return -1;
        }

        if( i == LOG_IDX )
        {
            // redirect stderr to log file
            fflush( stderr );
            output_streams[i].default_handle = dup(2);
            dup2( fileno(output_streams[i].f), 2 );
        }
    }

    // 4. traverse through the list of all registered tests.
    // Initialize the selected tests and put them into the separate sequence
    for( i = 0; i < all_tests.size(); i++ )
    {
        test = (CvTest*)all_tests[i];
        if( !(test->get_support_testing_modes() & get_testing_mode()) )
            continue;

        if( strcmp( test->get_func_list(), "" ) != 0 && filter(test) )
        {
            if( test->init(this) >= 0 )
            {
                selected_tests->push( test );
                if( list_tests )
                    ::printf( "%s\n", test->get_name() );
            }
            else
                printf( LOG, "WARNING: an error occured during test %s initialization\n", test->get_name() );
        }
    }

    if( list_tests )
    {
        clear();
        return 0;
    }

    // 5. setup all the neccessary handlers and print header
    set_handlers( !params.debug_mode );

    if( params.use_optimized == 0 )
        cvUseOptimized(0);

    if( !params.skip_header )
        print_summary_header( SUMMARY + LOG + CONSOLE + CSV );
    rng = params.rng_seed;
    update_context( 0, -1, true );

    // 6. run all the tests
    for( i = 0; i < selected_tests->size(); i++ )
    {
        CvTest* test = (CvTest*)selected_tests->at(i);
        int code;
        CvTestInfo temp;

        if( memory_manager )
            memory_manager->start_tracking();
        update_context( test, -1, true );
        current_test_info.rng_seed0 = current_test_info.rng_seed;
        
        ostream_testname_mask = 0; // reset "test name was printed" flags
        logbuf = std::string();
        if( output_streams[LOG_IDX].f )
            fflush( output_streams[LOG_IDX].f );

        temp = current_test_info;
        test->safe_run(0);
        if( get_err_code() >= 0 )
        {
            update_context( test, -1, false );
            current_test_info.rng_seed = temp.rng_seed;
            current_test_info.base_alloc_index = temp.base_alloc_index;
        }
        test->clear();
        if( memory_manager )
            memory_manager->stop_tracking_and_check();

        code = get_err_code();
        if( code >= 0 )
        {
            if( !params.print_only_failed )
            {
                printf( SUMMARY + CONSOLE, "\t" );
                set_color( CV_TS_GREEN );
                printf( SUMMARY + CONSOLE, "Ok\n" );
                set_color( CV_TS_NORMAL );
            }
        }
        else
        {
            printf( SUMMARY + CONSOLE, "\t" );
            set_color( CV_TS_RED );
            printf( SUMMARY + CONSOLE, "FAIL(%s)\n", str_from_code(code) );
            set_color( CV_TS_NORMAL );
            printf( LOG, "context: test case = %d, seed = %08x%08x\n",
                    current_test_info.test_case_idx,
                    (unsigned)(current_test_info.rng_seed>>32),
                    (unsigned)(current_test_info.rng_seed));
            if(logbuf.size() > 0)
            {
                printf( SUMMARY + CONSOLE, ">>>\n%s\n", logbuf.c_str());
            }
            failed_tests->push(current_test_info);
            if( params.rerun_immediately )
                break;
        }
    }

    ostream_testname_mask = -1;
    print_summary_tailer( SUMMARY + CONSOLE + LOG );

    if( !params.debug_mode && (params.rerun_failed || params.rerun_immediately) )
    {
        set_handlers(0);
        update_context( 0, -1, true );
        for( i = 0; i < failed_tests->size(); i++ )
        {
            CvTestInfo info = failed_tests->at(i);
            if( (info.code == FAIL_MEMORY_CORRUPTION_BEGIN ||
                info.code == FAIL_MEMORY_CORRUPTION_END ||
                info.code == FAIL_MEMORY_LEAK) && memory_manager )
                memory_manager->start_tracking( info.alloc_index - info.base_alloc_index
                                                + memory_manager->get_alloc_index() );
            rng = info.rng_seed;
            test->safe_run( info.test_case_idx );
        }
    }
    int nfailed = failed_tests ? (int)failed_tests->size() : 0;
    clear();

    return nfailed;
}


void CvTS::print_help()
{
    ::printf(
        "Usage: <test_executable> [{-h|--help}][-l] [-r] [-w] [-t] [-f <config_name>] [-d <data_path>] [-O{0|1}] [-tn <test_name>]\n\n"
        "-d - specify the test data path\n"
        "-f - use parameters from the provided XML/YAML config file\n"
        "     instead of the default parameters\n"
        "-h or --help - print this help information\n"
        "-l - list all the registered tests or subset of the tests,\n"
        "     selected in the config file, and exit\n"
        "-tn - only run a specific test\n"
        "-nc - do not use colors in the console output\n"     
        "-O{0|1} - disable/enable on-fly detection of IPP and other\n"
        "          supported optimized libs. It's enabled by default\n"
        "-r - continue running tests after OS/Hardware exception occured\n"
        "-t - switch to the performance testing mode instead of\n"
        "     the default algorithmic/correctness testing mode\n"
        "-w - write default parameters of the algorithmic or\n"
        "     performance (when -t is passed) tests to the specifed\n"
        "     config file (see -f) and exit\n\n"
        //"Test data path and config file can also be specified by the environment variables 'config' and 'datapath'.\n\n"
        );
}


#if defined WIN32 || defined _WIN32
const char* default_data_path = "../tests/cv/testdata/";
#else
const char* default_data_path = "../../../../tests/cv/testdata/";
#endif


int CvTS::read_params( CvFileStorage* fs )
{
    CvFileNode* node = fs ? cvGetFileNodeByName( fs, 0, "common" ) : 0;
    if(params.debug_mode < 0)
        params.debug_mode = cvReadIntByName( fs, node, "debug_mode", 1 ) != 0;
    params.skip_header = cvReadIntByName( fs, node, "skip_header", 0 ) != 0;
    params.print_only_failed = cvReadIntByName( fs, node, "print_only_failed", 0 ) != 0;
    params.rerun_failed = cvReadIntByName( fs, node, "rerun_failed", 0 ) != 0;
    params.rerun_immediately = cvReadIntByName( fs, node, "rerun_immediately", 0 ) != 0;
    const char* str = cvReadStringByName( fs, node, "filter_mode", "tests" );
    params.test_filter_mode = strcmp( str, "functions" ) == 0 ? CHOOSE_FUNCTIONS : CHOOSE_TESTS;
    str = cvReadStringByName( fs, node, "test_mode", params.test_mode == TIMING_MODE ? "timing" : "correctness" );
    params.test_mode = strcmp( str, "timing" ) == 0 || strcmp( str, "performance" ) == 0 ?
                        TIMING_MODE : CORRECTNESS_CHECK_MODE;
    str = cvReadStringByName( fs, node, "timing_mode", params.timing_mode == AVG_TIME ? "avg" : "min" );
    params.timing_mode = strcmp( str, "average" ) == 0 || strcmp( str, "avg" ) == 0 ? AVG_TIME : MIN_TIME;
    params.test_filter_pattern = params.test_filter_pattern != 0 &&
		strlen(params.test_filter_pattern) > 0 ? params.test_filter_pattern :
		cvReadStringByName( fs, node, params.test_filter_mode == CHOOSE_FUNCTIONS ?
                                                     "functions" : "tests", "" );
    params.resource_path = cvReadStringByName( fs, node, "." );
    if( params.use_optimized < 0 )
        params.use_optimized = cvReadIntByName( fs, node, "use_optimized", -1 );
    if( !params.data_path || !params.data_path[0] )
    {
        const char* data_path =
            cvReadStringByName( fs, node, "data_path", default_data_path );
        set_data_path(data_path);
    }
    params.test_case_count_scale = cvReadRealByName( fs, node, "test_case_count_scale", 1. );
    if( params.test_case_count_scale <= 0 )
        params.test_case_count_scale = 1.;
    str = cvReadStringByName( fs, node, "seed", 0 );
    if( str && params.rng_seed == 0 )
        params.rng_seed = read_seed(str);

    if( params.rng_seed == 0 )
        params.rng_seed = cvGetTickCount();

    str = cvReadStringByName( fs, node, "output_file_base_name", 0 );
    if( str )
        make_output_stream_base_name( str );

    return 0;
}


void CvTS::write_default_params( CvFileStorage* fs )
{
    read_params(0); // fill parameters with default values

    cvWriteInt( fs, "debug_mode", params.debug_mode );
    cvWriteInt( fs, "skip_header", params.skip_header );
    cvWriteInt( fs, "print_only_failed", params.print_only_failed );
    cvWriteInt( fs, "rerun_failed", params.rerun_failed );
    cvWriteInt( fs, "rerun_immediately", params.rerun_immediately );
    cvWriteString( fs, "filter_mode", params.test_filter_mode == CHOOSE_FUNCTIONS ? "functions" : "tests" );
    cvWriteString( fs, "test_mode", params.test_mode == TIMING_MODE ? "timing" : "correctness" );
    cvWriteString( fs, "data_path", params.data_path ? params.data_path : default_data_path, 1 );
    if( params.test_mode == TIMING_MODE )
        cvWriteString( fs, "timing_mode", params.timing_mode == AVG_TIME ? "avg" : "min" );
    // test_filter, seed & output_file_base_name are not written
}


void CvTS::enable_output_streams( int stream_mask, int value )
{
    for( int i = 0; i < MAX_IDX; i++ )
        if( stream_mask & (1 << i) )
            output_streams[i].enable = value != 0;
}


void CvTS::update_context( CvTest* test, int test_case_idx, bool update_ts_context )
{
    current_test_info.test = test;
    current_test_info.test_case_idx = test_case_idx;
    current_test_info.alloc_index = 0;
    current_test_info.code = 0;
    cvSetErrStatus( CV_StsOk );
    if( update_ts_context )
    {
        current_test_info.rng_seed = rng;
        current_test_info.base_alloc_index = memory_manager ?
            memory_manager->get_alloc_index() : 0;
    }
}


void CvTS::set_failed_test_info( int fail_code, int alloc_index )
{
    if( fail_code == FAIL_MEMORY_CORRUPTION_BEGIN ||
        fail_code == FAIL_MEMORY_CORRUPTION_END ||
        current_test_info.code >= 0 )
    {
        current_test_info.code = fail_code;
        current_test_info.alloc_index = alloc_index;
    }
}


const char* CvTS::get_libs_info( const char** addon_modules )
{
    const char* all_info = 0;
    cvGetModuleInfo( 0, &all_info, addon_modules );
    return all_info;
}


void CvTS::print_summary_header( int streams )
{
    char csv_header[256], *ptr = csv_header;
    int i;

    printf( streams, "Engine: %s\n", version );
    time_t t1;
    time( &t1 );
    struct tm *t2 = localtime( &t1 );
    char buf[1024];
    strftime( buf, sizeof(buf)-1, "%c", t2 );
    printf( streams, "Execution Date & Time: %s\n", buf );
    printf( streams, "Config File: %s\n", config_name );
    const char* plugins = 0;
    const char* lib_verinfo = get_libs_info( &plugins );
    printf( streams, "Tested Libraries: %s\n", lib_verinfo );
    printf( streams, "Optimized Low-level Plugin\'s: %s\n", plugins );
    printf( streams, "=================================================\n");

    sprintf( ptr, "funcName,dataType,channels,size," );
    ptr += strlen(ptr);

    for( i = 0; i < CvTest::TIMING_EXTRA_PARAMS; i++ )
    {
        sprintf( ptr, "param%d,", i );
        ptr += strlen(ptr);
    }

    sprintf( ptr, "CPE,Time(uSecs)" );
    printf( CSV, "%s\n", csv_header );
}


void CvTS::print_summary_tailer( int streams )
{
    printf( streams, "=================================================\n");
    if( selected_tests && failed_tests )
    {
        time_t end_time;
        time( &end_time );
        double total_time = difftime( end_time, start_time );
        printf( streams, "Summary: %d out of %d tests failed\n",
            failed_tests->size(), selected_tests->size() );
        int minutes = cvFloor(total_time/60.);
        int seconds = cvRound(total_time - minutes*60);
        int hours = minutes / 60;
        minutes %= 60;
        printf( streams, "Running time: %02d:%02d:%02d\n", hours, minutes, seconds );
    }
}

#if defined _MSC_VER && _MSC_VER < 1400
#undef vsnprintf
#define vsnprintf _vsnprintf
#endif

void CvTS::vprintf( int streams, const char* fmt, va_list l )
{
    if( streams )
    {
        char str[1 << 14];
        vsnprintf( str, sizeof(str)-1, fmt, l );

        for( int i = 0; i < MAX_IDX; i++ )
        {
            if( (streams & (1 << i)) && output_streams[i].enable )
            {
                FILE* f = i == CONSOLE_IDX ? stdout :
                          i == LOG_IDX ? stderr : output_streams[i].f;
                if( f )
                {
                    if( i != CSV_IDX && !(ostream_testname_mask & (1 << i)) && current_test_info.test )
                    {
                        fprintf( f, "-------------------------------------------------\n" );
                        if( i == CONSOLE_IDX || i == SUMMARY_IDX )
							fprintf( f, "[%08x%08x]\n", (int)(current_test_info.rng_seed0 >> 32),
							    (int)(current_test_info.rng_seed0));
                        fprintf( f, "%s: ", current_test_info.test->get_name() );
                        fflush( f );
                        ostream_testname_mask |= 1 << i;
                        if( i == LOG_IDX )
                            logbuf = std::string();
                    }
                    fputs( str, f );
                    if( i == LOG_IDX )
                        logbuf += std::string(str);
                    if( i == CONSOLE_IDX )
                        fflush(f);
                }
            }
        }
    }
}


void CvTS::printf( int streams, const char* fmt, ... )
{
    if( streams )
    {
        va_list l;
        va_start( l, fmt );
        vprintf( streams, fmt, l );
        va_end( l );
    }
}

void CvTS::set_color(int color)
{
    if( params.color_terminal )
        change_color(color);
}

static char* cv_strnstr( const char* str, int len,
                         const char* pattern,
                         int pattern_len = -1,
                         int whole_word = 1 )
{
    int i;

    if( len < 0 && pattern_len < 0 )
        return (char*)strstr( str, pattern );

    if( len < 0 )
        len = (int)strlen( str );

    if( pattern_len < 0 )
        pattern_len = (int)strlen( pattern );

    for( i = 0; i < len - pattern_len + 1; i++ )
    {
        int j = i + pattern_len;
        if( str[i] == pattern[0] &&
            memcmp( str + i, pattern, pattern_len ) == 0 &&
            (!whole_word ||
            ((i == 0 || (!isalnum(str[i-1]) && str[i-1] != '_')) &&
             (j == len || (!isalnum(str[j]) && str[j] != '_')))))
            return (char*)(str + i);
    }

    return 0;
}


int CvTS::filter( CvTest* test )
{
    const char* pattern = params.test_filter_pattern;
    int inverse = 0;

    if( pattern && pattern[0] == '!' )
    {
        inverse = 1;
        pattern++;
    }

    if( !pattern || strcmp( pattern, "" ) == 0 || strcmp( pattern, "*" ) == 0 )
        return 1 ^ inverse;

    if( params.test_filter_mode == CHOOSE_TESTS )
    {
        int found = 0;

        while( pattern && *pattern )
        {
            char *ptr, *endptr = (char*)strchr( pattern, ',' );
            int len, have_wildcard;
            int t_name_len;

            if( endptr )
                *endptr = '\0';

            ptr = (char*)strchr( pattern, '*' );
            if( ptr )
            {
                len = (int)(ptr - pattern);
                have_wildcard = 1;
            }
            else
            {
                len = (int)strlen( pattern );
                have_wildcard = 0;
            }

            t_name_len = (int)strlen( test->get_name() );
            found = (t_name_len == len || (have_wildcard && t_name_len > len)) &&
                    (len == 0 || memcmp( test->get_name(), pattern, len ) == 0);
            if( endptr )
            {
                *endptr = ',';
                pattern = endptr + 1;
                while( isspace(*pattern) )
                    pattern++;
            }

            if( found || !endptr )
                break;
        }

        return found ^ inverse;
    }
    else
    {
        assert( params.test_filter_mode == CHOOSE_FUNCTIONS );
        int glob_len = (int)strlen( pattern );
        const char* ptr = test->get_func_list();
        const char *tmp_ptr;

        while( ptr && *ptr )
        {
            const char* endptr = ptr - 1;
            const char* name_ptr;
            const char* name_first_match;
            int name_len;
            char c;

            do c = *++endptr;
            while( isspace(c) );

            if( !c )
                break;

            assert( isalpha(c) );
            name_ptr = endptr;

            do c = *++endptr;
            while( isalnum(c) || c == '_' );

            if( c == ':' ) // class
            {
                assert( endptr[1] == ':' );
                endptr = endptr + 2;
                name_len = (int)(endptr - name_ptr);

                // find the first occurence of the class name
                // in pattern
                name_first_match = cv_strnstr( pattern,
                                      glob_len, name_ptr, name_len, 1 );

                if( *endptr == '*' )
                {
                    if( name_first_match )
                        return 1 ^ inverse;
                }
                else
                {
                    assert( *endptr == '{' ); // a list of methods

                    if( !name_first_match )
                    {
                        // skip all the methods, if there is no such a class name
                        // in pattern
                        endptr = strchr( endptr, '}' );
                        assert( endptr != 0 );
                        endptr--;
                    }

                    for( ;; )
                    {
                        const char* method_name_ptr;
                        int method_name_len;

                        do c = *++endptr;
                        while( isspace(c) );

                        if( c == '}' )
                            break;
                        assert( isalpha(c) );

                        method_name_ptr = endptr;

                        do c = *++endptr;
                        while( isalnum(c) || c == '_' );

                        method_name_len = (int)(endptr - method_name_ptr);

                        // search for class_name::* or
                        // class_name::{...method_name...}
                        tmp_ptr = name_first_match;
                        do
                        {
                            const char* tmp_ptr2;
                            tmp_ptr += name_len;
                            if( *tmp_ptr == '*' )
                                return 1;
                            assert( *tmp_ptr == '{' );
                            tmp_ptr2 = strchr( tmp_ptr, '}' );
                            assert( tmp_ptr2 );

                            if( cv_strnstr( tmp_ptr, (int)(tmp_ptr2 - tmp_ptr) + 1,
                                             method_name_ptr, method_name_len, 1 ))
                                return 1 ^ inverse;

                            tmp_ptr = cv_strnstr( tmp_ptr2, glob_len -
                                                   (int)(tmp_ptr2 - pattern),
                                                   name_ptr, name_len, 1 );
                        }
                        while( tmp_ptr );

                        endptr--;
                        do c = *++endptr;
                        while( isspace(c) );

                        if( c != ',' )
                            endptr--;
                    }
                }
            }
            else
            {
                assert( !c || isspace(c) || c == ',' );
                name_len = (int)(endptr - name_ptr);
                tmp_ptr = pattern;

                for(;;)
                {
                    const char *tmp_ptr2, *tmp_ptr3;

                    tmp_ptr = cv_strnstr( tmp_ptr, glob_len -
                        (int)(tmp_ptr - pattern), name_ptr, name_len, 1 );

                    if( !tmp_ptr )
                        break;

                    // make sure it is not a method
                    tmp_ptr2 = strchr( tmp_ptr, '}' );
                    if( !tmp_ptr2 )
                        return 1 ^ inverse;

                    tmp_ptr3 = strchr( tmp_ptr, '{' );
                    if( tmp_ptr3 < tmp_ptr2 )
                        return 1 ^ inverse;

                    tmp_ptr = tmp_ptr2 + 1;
                }

                endptr--;
            }

            do c = *++endptr;
            while( isspace(c) );

            if( c == ',' )
                endptr++;
            ptr = endptr;
        }

        return 0 ^ inverse;
    }
}

/* End of file. */
