살군의 보조기억 장치

Another memory device…

BaseTest – ts.hpp

with 2 comments

뭐가 좀 되나 싶으니 또 다른게 나오는구나. 아니 사실 무시하고 넘어갈 수 있을꺼라 생각했는데 결국 다 이유가 있어서 사용하는 것으로 보이는 놈을 분석하기 위해 다시 ts.hpp 파일로 돌아왔다. 실제 test 를 구현한 코드를 살펴보면 BaseTest 를 상속받아서 정의한 클래스들이 있다. 그러니 이걸 살펴볼 수 밖에… 언제 테스트코드를 작성하나.

class CV_EXPORTS BaseTest
{
public:
    // ...
    virtual void run( int start_from );            // the main procedure of the test
    virtual void safe_run( int start_from=0 );     // the wrapper for run that cares of exceptions
    virtual bool can_do_fast_forward();            // returns true if and only if the different test cases do not depend on each other
                                                   // (so that test system could get right to a problematic test case)
    virtual void clear();                  // deallocates all the memory.
                                           // called by init() (before initialization) and by the destructor
protected:
    // ...
    virtual int read_params( CvFileStorage* fs );              // read test params
    virtual int get_test_case_count();                         // returns the number of tests or -1 if it is unknown a-priori
    virtual int prepare_test_case( int test_case_idx );        // prepares data for the next test case. rng seed is updated by the function
    virtual int validate_test_results( int test_case_idx );    // checks if the test output is valid and accurate
    virtual void run_func();                                   // calls the tested function. the method is called from run_test_case() runs tested func(s)
    virtual int update_progress( int progress, int test_case_idx, int count, double dt );      // updates progress bar
    const CvFileNode* find_param( CvFileStorage* fs, const char* param_name );                 // finds test parameter

    string name;       // name of the test (it is possible to locate a test by its name)
    TS* ts;            // pointer to the system that includes the test
};

막상 적어놓고 보니, 이게 뭘 하는지는 전혀 감이오지 않는다. 설정하는 부분만 잔뜩 있고 실제 뭔가 어떤 일을 하는 부분은 대부분 virtual 키워드로 되어 있으므로, 실제 코드를 사용하는 테스트케이스를 살펴봐야 자세한 내용을 알 수 있겠다. 그전에 BaseTest가 정의된 ts.cpp 의 내용을 먼저 살펴보자.

const CvFileNode* BaseTest::find_param( CvFileStorage* fs, const char* param_name )
{
    CvFileNode* node = cvGetFileNodeByName(fs, 0, get_name().c_str());
    return node ? cvGetFileNodeByName( fs, node, param_name ) : 0;
}

// ...

void BaseTest::safe_run( int start_from )
{
    read_params( ts->get_file_storage() );        // 실제로는 아무 일도 하지 않는다.
    ts->update_context( 0, -1, true );            // 중복사용? 왜 이렇게 쓴는거지?
    ts->update_context( this, -1, true );         // this 를 test case 로 사용

    if( !::testing::GTEST_FLAG(catch_exceptions) )    // test 시 옵션을 주는 것으로, 사용되지 않는다.
        run( start_from );                            // 개념상으로 보면 gtest 에서 catch_exceptions 를
    else                                              // 정의했다면 바로 run() 을 수행하고,
    {                                                 // 아닐 경우 try {} catch {} 로 exception 핸들링
        try
        {
        #if !defined WIN32 && !defined _WIN32
        int _code = setjmp( tsJmpMark );
        if( !_code )
            run( start_from );
        else
            throw _code;
        #else
            run( start_from );                // 실재 windows7 기반으로 수행될 경우, 실행되는 부분
        #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(TS::LOG, "%s\n", buf);
            ts->set_failed_test_info( TS::FAIL_ERROR_IN_CALLED_FUNC );
        }
        catch (...)
        {
            ts->set_failed_test_info( TS::FAIL_EXCEPTION );
        }
    }

    ts->set_gtest_status();
}

void BaseTest::run( int start_from )                     // 내가 타겟으로 잡은 test_eigen.cpp 에서는
{                                                        // 클래스에서 새로 run() 을 선언했으므로, 
    int 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;
    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) );

        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;
    }
}

// ...

코드를 분석하고 보니, 여기서 많은 일들이 처리된다. line:195 의 GTEST_FLAG() 내용을 살펴보면,

// Macro for referencing flags.
#define GTEST_FLAG(name) FLAGS_gtest_##name

라고 정의되어 있다. 여기서 ## 은 두가지 내용을 공백없이 이어붙이는 역할을 한다. 여기서 사용예를 살펴볼 수 있다. 코드에서 처럼 GTEST_FLAG(catch_exceptions) 로 호출할 경우, 결과값은 FLAGS_gtest_catch_exceptions 이된다.

나머지 분석은 다음 포스트에서…
line:221 의 catch(…) 구문도 평소에 보기 힘든 것이다. c++의 exception 부분을 살펴보면,

If an ellipsis (…) is used as the parameter of catch, that handler will catch any exception no matter what the type of the exception thrown. This can be used as a default handler that catches all exceptions not caught by other handlers:

try {
  // code here
}
catch (int param) { cout << "int exception"; }
catch (char param) { cout << "char exception"; }
catch (...) { cout << "default exception"; }

In this case, the last handler would catch any exception thrown of a type that is neither int nor char.

After an exception has been handled the program, execution resumes after the try-catch block, not after the throw statement!.

즉, 미리 정의하지 않은 exception 이 발생할 경우 default exception handler 로 동작하는 것이다. 아직 정확하게 어떻게 동작하는지는 잘 모르겠지만 대충 어떻게 되는 건지는 감이 온다.

참고
Advertisements

Written by gomiski

2014/02/17 at 6:34 am

2 Responses

Subscribe to comments with RSS.

  1. […] 본 ## 은 매크로에서 사용하는 것으로 지난번 포스팅에서도 한번 언급을 했는데, 빈 칸을 붙여주는 것이다. 자… 이제 대충 […]

  2. […] BaseTest 와  ts.hpp 를 거쳐서 다시 돌어온 부분이 바로 TS class 다. 이제 unit test 와 관련된 기본 구조는 이걸로 정리가 대충 다된 것 이라고 기대하며 같다. 이번에 살펴볼 클래스는 TS 다. BaseTest 안에 있으면서 test 와 관련된 가장 기본적인 작업을 하는 클래스다. […]


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: