Logo Search packages:      
Sourcecode: passenger version File versions  Download package

tut.h

/**
 * TUT unit testing framework.
 * http://tut-framework.sourceforge.net/
 *
 * Copyright 2002-2006 Vladimir Dyuzhev.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 * Redistributions 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.
 *
 * THIS SOFTWARE IS PROVIDED ``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
 * AUTHOR 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.
 */

// Slightly modified for use in this software package.

#ifndef TUT_H_GUARD
#define TUT_H_GUARD

#include <iostream>
#include <map>
#include <vector>
#include <string>
#include <sstream>
#include <typeinfo>

#if defined(TUT_USE_SEH)
#include <windows.h>
#include <winbase.h>
#endif

#define DEFINE_TEST_GROUP(name) \
      using namespace tut; \
      typedef test_group<name> factory; \
      typedef factory::object object; \
      factory name## _group(#name)

#define TEST_METHOD(i) \
      template<> template<> \
      void object::test<i>()

/**
 * Template Unit Tests Framework for C++.
 * http://tut.dozen.ru
 *
 * @author Vladimir Dyuzhev, Vladimir.Dyuzhev@gmail.com
 */
namespace tut
{

/**
 * The base for all TUT exceptions.
 */
00067 struct tut_error : public std::exception
{
    tut_error(const std::string& msg)
        : err_msg(msg)
    {
    }
    
    ~tut_error() throw()
    {
    }
    
    const char* what() const throw()
    {
        return err_msg.c_str();
    }
    
private:

    std::string err_msg;
};

/**
 * Exception to be throwed when attempted to execute 
 * missed test by number.
 */
00092 struct no_such_test : public tut_error
{
    no_such_test() 
        : tut_error("no such test")
    {
    }
    
    ~no_such_test() throw()
    {
    }
};

/**
 * No such test and passed test number is higher than
 * any test number in current group. Used in one-by-one
 * test running when upper bound is not known.
 */
00109 struct beyond_last_test : public no_such_test
{
    beyond_last_test()
    {
    }
    
    ~beyond_last_test() throw()
    {
    }
};

/**
 * Group not found exception.
 */
00123 struct no_such_group : public tut_error
{
    no_such_group(const std::string& grp) 
        : tut_error(grp)
    {
    }
    
    ~no_such_group() throw()
    {
    }
};

/**
 * Internal exception to be throwed when 
 * no more tests left in group or journal.
 */
00139 struct no_more_tests
{
    no_more_tests()
    {
    }
    
    ~no_more_tests() throw()
    {
    }
};

/**
 * Internal exception to be throwed when 
 * test constructor has failed.
 */
00154 struct bad_ctor : public tut_error
{
    bad_ctor(const std::string& msg) 
        : tut_error(msg)
    {
    }
    
    ~bad_ctor() throw()
    {
    }
};

/**
 * Exception to be throwed when ensure() fails or fail() called.
 */
00169 struct failure : public tut_error
{
    failure(const std::string& msg) 
        : tut_error(msg)
    {
    }
    
    ~failure() throw()
    {
    }
};

/**
 * Exception to be throwed when test desctructor throwed an exception.
 */
00184 struct warning : public tut_error
{
    warning(const std::string& msg) 
        : tut_error(msg)
    {
    }
    
    ~warning() throw()
    {
    }
};

/**
 * Exception to be throwed when test issued SEH (Win32)
 */
00199 struct seh : public tut_error
{
    seh(const std::string& msg) 
        : tut_error(msg)
    {
    }
    
    ~seh() throw()
    {
    }
};

/**
 * Return type of runned test/test group.
 *
 * For test: contains result of test and, possible, message
 * for failure or exception.
 */
00217 struct test_result
{
    /**
     * Test group name.
     */
00222     std::string group;

    /**
     * Test number in group.
     */
00227     int test;

    /**
     * Test name (optional)
     */
00232     std::string name;

    /**
     * ok - test finished successfully
     * fail - test failed with ensure() or fail() methods
     * ex - test throwed an exceptions
     * warn - test finished successfully, but test destructor throwed
     * term - test forced test application to terminate abnormally
     */
00241     enum result_type
    { 
        ok, 
        fail, 
        ex, 
        warn, 
        term, 
        ex_ctor 
    };
    
    result_type result;

    /**
     * Exception message for failed test.
     */
00256     std::string message;
    std::string exception_typeid;

    /**
     * Default constructor.
     */
00262     test_result()
        : test(0),
          result(ok)
    {
    }

    /**
     * Constructor.
     */
00271     test_result(const std::string& grp, int pos,
                const std::string& test_name, result_type res)
        : group(grp), 
          test(pos), 
          name(test_name), 
          result(res)
    {
    }

    /**
     * Constructor with exception.
     */
00283     test_result(const std::string& grp,int pos,
                const std::string& test_name, result_type res,
                const std::exception& ex)
        : group(grp), 
          test(pos), 
          name(test_name), 
          result(res),
          message(ex.what()),
          exception_typeid(typeid(ex).name())
    {
    }
};

/**
 * Interface.
 * Test group operations.
 */
00300 struct group_base
{
    virtual ~group_base()
    {
    }

    // execute tests iteratively
    virtual void rewind() = 0;
    virtual test_result run_next() = 0;

    // execute one test
    virtual test_result run_test(int n) = 0;
};

/**
 * Test runner callback interface.
 * Can be implemented by caller to update
 * tests results in real-time. User can implement 
 * any of callback methods, and leave unused 
 * in default implementation.
 */
00321 struct callback
{
    /**
     * Virtual destructor is a must for subclassed types.
     */
00326     virtual ~callback()
    {
    }

    /**
     * Called when new test run started.
     */
00333     virtual void run_started()
    {
    }

    /**
     * Called when a group started
     * @param name Name of the group
     */
00341     virtual void group_started(const std::string& /*name*/)
    {
    }

    /**
     * Called when a test finished.
     * @param tr Test results.
     */
00349     virtual void test_completed(const test_result& /*tr*/)
    {
    }

    /**
     * Called when a group is completed
     * @param name Name of the group
     */
00357     virtual void group_completed(const std::string& /*name*/)
    {
    }

    /**
     * Called when all tests in run completed.
     */
00364     virtual void run_completed()
    {
    }
};

/**
 * Typedef for runner::list_groups()
 */
00372 typedef std::vector<std::string> groupnames;

/**
 * Test runner.
 */
00377 class test_runner
{

public:
    
    /**
     * Constructor
     */
00385     test_runner() 
        : callback_(&default_callback_)
    {
    }

    /**
     * Stores another group for getting by name.
     */
00393     void register_group(const std::string& name, group_base* gr)
    {
        if (gr == 0)
        {
            throw tut_error("group shall be non-null");
        }

        // TODO: inline variable
        groups::iterator found = groups_.find(name);
        if (found != groups_.end())
        {
            std::string msg("attempt to add already existent group " + name);
            // this exception terminates application so we use cerr also
            // TODO: should this message appear in stream?
            std::cerr << msg << std::endl;
            throw tut_error(msg);
        }

        groups_[name] = gr;
    }

    /**
     * Stores callback object.
     */
00417     void set_callback(callback* cb)
    {
        callback_ = cb == 0 ? &default_callback_ : cb;
    }

    /**
     * Returns callback object.
     */
00425     callback& get_callback() const
    {
        return *callback_;
    }

    /**
     * Returns list of known test groups.
     */
00433     const groupnames list_groups() const
    {
        groupnames ret;
        const_iterator i = groups_.begin();
        const_iterator e = groups_.end();
        while (i != e)
        {
            ret.push_back(i->first);
            ++i;
        }
        return ret;
    }

    /**
     * Runs all tests in all groups.
     * @param callback Callback object if exists; null otherwise
     */
00450     void run_tests() const
    {
        callback_->run_started();

        const_iterator i = groups_.begin();
        const_iterator e = groups_.end();
        while (i != e)
        {
            callback_->group_started(i->first);
            try
            {
                run_all_tests_in_group_(i);
            }
            catch (const no_more_tests&)
            {
                callback_->group_completed(i->first);
            }

            ++i;
        }

        callback_->run_completed();
    }

    /**
     * Runs all tests in specified group.
     */
00477     void run_tests(const std::string& group_name) const
    {
        callback_->run_started();

        const_iterator i = groups_.find(group_name);
        if (i == groups_.end())
        {
            callback_->run_completed();
            throw no_such_group(group_name);
        }

        callback_->group_started(group_name);
        try
        {
            run_all_tests_in_group_(i);
        }
        catch (const no_more_tests&)
        {
            // ok
        }

        callback_->group_completed(group_name);
        callback_->run_completed();
    }

    /**
     * Runs one test in specified group.
     */
00505     test_result run_test(const std::string& group_name, int n) const
    {
        callback_->run_started();

        const_iterator i = groups_.find(group_name);
        if (i == groups_.end())
        {
            callback_->run_completed();
            throw no_such_group(group_name);
        }

        callback_->group_started(group_name);
        try
        {
            test_result tr = i->second->run_test(n);
            callback_->test_completed(tr);
            callback_->group_completed(group_name);
            callback_->run_completed();
            return tr;
        }
        catch (const beyond_last_test&)
        {
            callback_->group_completed(group_name);
            callback_->run_completed();
            throw;
        }
        catch (const no_such_test&)
        {
            callback_->group_completed(group_name);
            callback_->run_completed();
            throw;
        }
    }

protected:
    
    typedef std::map<std::string, group_base*> groups;
    typedef groups::iterator iterator;
    typedef groups::const_iterator const_iterator;
    groups groups_;

    callback  default_callback_;
    callback* callback_;


private:

    void run_all_tests_in_group_(const_iterator i) const
    {
        i->second->rewind();
        for ( ;; )
        {
            test_result tr = i->second->run_next();
            callback_->test_completed(tr);

            if (tr.result == test_result::ex_ctor)
            {
                throw no_more_tests();
            }
        }
    }
};

/**
 * Singleton for test_runner implementation.
 * Instance with name runner_singleton shall be implemented
 * by user.
 */
00573 class test_runner_singleton
{
public:

    static test_runner& get()
    {
        static test_runner tr;
        return tr;
    }
};

extern test_runner_singleton runner;

/**
 * Test object. Contains data test run upon and default test method 
 * implementation. Inherited from Data to allow tests to  
 * access test data as members.
 */
template <class Data>
00592 class test_object : public Data
{
public:

    /**
     * Default constructor
     */
00599     test_object()
    {
    }

    void set_test_name(const std::string& current_test_name)
    {
        current_test_name_ = current_test_name;
    }

    const std::string& get_test_name() const
    {
        return current_test_name_;
    }

    /**
     * Default do-nothing test.
     */
    template <int n>
00617     void test()
    {
        called_method_was_a_dummy_test_ = true;
    }

    /**
     * The flag is set to true by default (dummy) test.
     * Used to detect usused test numbers and avoid unnecessary
     * test object creation which may be time-consuming depending
     * on operations described in Data::Data() and Data::~Data().
     * TODO: replace with throwing special exception from default test.
     */
00629     bool called_method_was_a_dummy_test_;

private:

    std::string current_test_name_;
};

namespace
{

/**
 * Tests provided condition.
 * Throws if false.
 */
00643 void ensure(bool cond)
{
    if (!cond)
    {
        // TODO: default ctor?
        throw failure("");
    }
}

/**
 * Tests provided condition.
 * Throws if true.
 */
00656 void ensure_not(bool cond)
{
    ensure(!cond);
}

/**
 * Tests provided condition.
 * Throws if false.
 */
template <typename T>
00666 void ensure(const T msg, bool cond)
{
    if (!cond)
    {
        throw failure(msg);
    }
}

/**
 * Tests provided condition.
 * Throws if true.
 */
template <typename T>
00679 void ensure_not(const T msg, bool cond)
{
    ensure(msg, !cond);
}

/**
 * Tests two objects for being equal.
 * Throws if false.
 *
 * NB: both T and Q must have operator << defined somewhere, or
 * client code will not compile at all!
 */
template <class T, class Q>
00692 void ensure_equals(const char* msg, const Q& actual, const T& expected)
{
    if (expected != actual)
    {
        std::stringstream ss;
        ss << (msg ? msg : "") 
            << (msg ? ":" : "") 
            << " expected '" 
            << expected 
            << "' actual '" 
            << actual
            << '\'';
        throw failure(ss.str().c_str());
    }
}

template <class T, class Q>
void ensure_equals(const Q& actual, const T& expected)
{
    ensure_equals<>(0, actual, expected);
}

/**
 * Tests two objects for being at most in given distance one from another.
 * Borders are excluded.
 * Throws if false.
 *
 * NB: T must have operator << defined somewhere, or
 * client code will not compile at all! Also, T shall have
 * operators + and -, and be comparable.
 */
template <class T>
00724 void ensure_distance(const char* msg, const T& actual, const T& expected,
    const T& distance)
{
    if (expected-distance >= actual || expected+distance <= actual)
    {
        std::stringstream ss;
        ss << (msg ? msg : "") 
            << (msg? ":" : "") 
            << " expected (" 
            << expected-distance 
            << " - "
            << expected+distance 
            << ") actual '" 
            << actual
            << '\'';
        throw failure(ss.str().c_str());
    }
}

template <class T>
void ensure_distance(const T& actual, const T& expected, const T& distance)
{
    ensure_distance<>(0, actual, expected, distance);
}

/**
 * Unconditionally fails with message.
 */
00752 void fail(const char* msg = "")
{
    throw failure(msg);
}

} // end of namespace

/**
 * Walks through test tree and stores address of each
 * test method in group. Instantiation stops at 0.
 */
template <class Test, class Group, int n>
00764 struct tests_registerer
{
    static void reg(Group& group)
    {
        group.reg(n, &Test::template test<n>);
        tests_registerer<Test, Group, n - 1>::reg(group);
    }
};

template <class Test, class Group>
struct tests_registerer<Test, Group, 0>
{
    static void reg(Group&)
    {
    }
};

/**
 * Test group; used to recreate test object instance for
 * each new test since we have to have reinitialized 
 * Data base class.
 */
template <class Data, int MaxTestsInGroup = 50>
00787 class test_group : public group_base
{
    const char* name_;

    typedef void (test_object<Data>::*testmethod)();
    typedef std::map<int, testmethod> tests;
    typedef typename tests::iterator tests_iterator;
    typedef typename tests::const_iterator tests_const_iterator;
    typedef typename tests::const_reverse_iterator
    tests_const_reverse_iterator;
    typedef typename tests::size_type size_type;

    tests tests_;
    tests_iterator current_test_;

    /**
     * Exception-in-destructor-safe smart-pointer class.
     */
    template <class T>
00806     class safe_holder
    {
        T* p_;
        bool permit_throw_in_dtor;

        safe_holder(const safe_holder&);
        safe_holder& operator=(const safe_holder&);

    public:
        safe_holder() 
            : p_(0),
              permit_throw_in_dtor(false)
        {
        }

        ~safe_holder()
        {
            release();
        }

        T* operator->() const
        {
            return p_;
        }
        
        T* get() const
        {
            return p_;
        }

        /**
         * Tell ptr it can throw from destructor. Right way is to
         * use std::uncaught_exception(), but some compilers lack
         * correct implementation of the function.
         */
00841         void permit_throw()
        {
            permit_throw_in_dtor = true;
        }

        /**
         * Specially treats exceptions in test object destructor; 
         * if test itself failed, exceptions in destructor
         * are ignored; if test was successful and destructor failed,
         * warning exception throwed.
         */
00852         void release()
        {
            try
            {
                if (delete_obj() == false)
                {
                    throw warning("destructor of test object raised"
                        " an SEH exception");
                }
            }
            catch (const std::exception& ex)
            {
                if (permit_throw_in_dtor)
                {
                    std::string msg = "destructor of test object raised"
                        " exception: ";
                    msg += ex.what();
                    throw warning(msg);
                }
            }
            catch( ... )
            {
                if (permit_throw_in_dtor)
                {
                    throw warning("destructor of test object raised an"
                        " exception");
                }
            }
        }

        /**
         * Re-init holder to get brand new object.
         */
00885         void reset()
        {
            release();
            permit_throw_in_dtor = false;
            p_ = new T();
        }

        bool delete_obj()
        {
#if defined(TUT_USE_SEH)
            __try
            {
#endif
                T* p = p_;
                p_ = 0;
                delete p;
#if defined(TUT_USE_SEH)
            }
            __except(handle_seh_(::GetExceptionCode()))
            {
                if (permit_throw_in_dtor)
                {
                    return false;
                }
            }
#endif
            return true;
        }
    };

public:

    typedef test_object<Data> object;

    /**
     * Creates and registers test group with specified name.
     */
00922     test_group(const char* name)
        : name_(name)
    {
        // register itself
        runner.get().register_group(name_,this);

        // register all tests
        tests_registerer<object, test_group, MaxTestsInGroup>::reg(*this);
    }

    /**
     * This constructor is used in self-test run only.
     */
00935     test_group(const char* name, test_runner& another_runner)
        : name_(name)
    {
        // register itself
        another_runner.register_group(name_, this);

        // register all tests
        tests_registerer<test_object<Data>, test_group, 
            MaxTestsInGroup>::reg(*this);
    };

    /**
     * Registers test method under given number.
     */
00949     void reg(int n, testmethod tm)
    {
        tests_[n] = tm;
    }

    /**
     * Reset test position before first test.
     */
00957     void rewind()
    {
        current_test_ = tests_.begin();
    }

    /**
     * Runs next test.
     */
00965     test_result run_next()
    {
        if (current_test_ == tests_.end())
        {
            throw no_more_tests();
        }

        // find next user-specialized test
        safe_holder<object> obj;
        while (current_test_ != tests_.end())
        {
            try
            {
                return run_test_(current_test_++, obj);
            }
            catch (const no_such_test&)
            {
                continue;
            }
        }

        throw no_more_tests();
    }

    /**
     * Runs one test by position.
     */
00992     test_result run_test(int n)
    {
        // beyond tests is special case to discover upper limit
        if (tests_.rbegin() == tests_.rend())
        {
            throw beyond_last_test();
        }
        
        if (tests_.rbegin()->first < n)
        {
            throw beyond_last_test();
        }

        // withing scope; check if given test exists
        tests_iterator ti = tests_.find(n);
        if (ti == tests_.end())
        {
            throw no_such_test();
        }

        safe_holder<object> obj;
        return run_test_(ti, obj);
    }

private:

    /**
     * VC allows only one exception handling type per function,
     * so I have to split the method.
     * 
     * TODO: refactoring needed!
     */
01024     test_result run_test_(const tests_iterator& ti, safe_holder<object>& obj)
    {
        std::string current_test_name;
        try
        {
            if (run_test_seh_(ti->second,obj, current_test_name) == false)
            {
                throw seh("seh");
            }
        }
        catch (const no_such_test&)
        {
            throw;
        }
        catch (const warning& ex)
        {
            // test ok, but destructor failed
            if (obj.get())
            {
                current_test_name = obj->get_test_name();
            }
            test_result tr(name_,ti->first, current_test_name, 
                test_result::warn, ex);
            return tr;
        }
        catch (const failure& ex)
        {
            // test failed because of ensure() or similar method
            if (obj.get())
            {
                current_test_name = obj->get_test_name();
            }
            test_result tr(name_,ti->first, current_test_name, 
                test_result::fail, ex);
            return tr;
        }
        catch (const seh& ex)
        {
            // test failed with sigsegv, divide by zero, etc
            if (obj.get())
            {
                current_test_name = obj->get_test_name();
            }
            test_result tr(name_, ti->first, current_test_name, 
                test_result::term, ex);
            return tr;
        }
        catch (const bad_ctor& ex)
        {
            // test failed because test ctor failed; stop the whole group
            if (obj.get())
            {
                current_test_name = obj->get_test_name();
            }
            test_result tr(name_, ti->first, current_test_name, 
                test_result::ex_ctor, ex);
            return tr;
        }
        catch (const std::exception& ex)
        {
            // test failed with std::exception
            if (obj.get())
            {
                current_test_name = obj->get_test_name();
            }
            test_result tr(name_, ti->first, current_test_name, 
                test_result::ex, ex);
            return tr;
        }
        catch (...)
        {
            // test failed with unknown exception
            if (obj.get())
            {
                current_test_name = obj->get_test_name();
            }
            test_result tr(name_, ti->first, current_test_name, 
                test_result::ex);
            return tr;
        }

        // test passed
        test_result tr(name_,ti->first, current_test_name, test_result::ok);
        return tr;
    }

    /**
     * Runs one under SEH if platform supports it.
     */
01113     bool run_test_seh_(testmethod tm, safe_holder<object>& obj, 
        std::string& current_test_name)
    {
#if defined(TUT_USE_SEH)
        __try
        {
#endif
        if (obj.get() == 0)
        {
            reset_holder_(obj);
        }
            
        obj->called_method_was_a_dummy_test_ = false;

#if defined(TUT_USE_SEH)

            __try
            {
#endif
                (obj.get()->*tm)();
#if defined(TUT_USE_SEH)
            }
            __except(handle_seh_(::GetExceptionCode()))
            {
                // throw seh("SEH");
                current_test_name = obj->get_test_name();
                return false;
            }
#endif

        if (obj->called_method_was_a_dummy_test_)
        {
            // do not call obj.release(); reuse object
            throw no_such_test();
        }

        current_test_name = obj->get_test_name();
        obj.permit_throw();
        obj.release();
#if defined(TUT_USE_SEH)
        }
        __except(handle_seh_(::GetExceptionCode()))
        {
            return false;
        }
#endif
        return true;
    }

    void reset_holder_(safe_holder<object>& obj)
    {
        try
        {
            obj.reset();
        }
        catch (const std::exception& ex)
        {
            throw bad_ctor(ex.what());
        }
        catch (...)
        {
            throw bad_ctor("test constructor has generated an exception;"
                " group execution is terminated");
        }
    }
};

#if defined(TUT_USE_SEH)
/**
 * Decides should we execute handler or ignore SE.
 */
inline int handle_seh_(DWORD excode)
{
    switch(excode)
    {
    case EXCEPTION_ACCESS_VIOLATION:
    case EXCEPTION_DATATYPE_MISALIGNMENT:
    case EXCEPTION_BREAKPOINT:
    case EXCEPTION_SINGLE_STEP:
    case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
    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_STACK_CHECK:
    case EXCEPTION_FLT_UNDERFLOW:
    case EXCEPTION_INT_DIVIDE_BY_ZERO:
    case EXCEPTION_INT_OVERFLOW:
    case EXCEPTION_PRIV_INSTRUCTION:
    case EXCEPTION_IN_PAGE_ERROR:
    case EXCEPTION_ILLEGAL_INSTRUCTION:
    case EXCEPTION_NONCONTINUABLE_EXCEPTION:
    case EXCEPTION_STACK_OVERFLOW:
    case EXCEPTION_INVALID_DISPOSITION:
    case EXCEPTION_GUARD_PAGE:
    case EXCEPTION_INVALID_HANDLE:
        return EXCEPTION_EXECUTE_HANDLER;
    };

    return EXCEPTION_CONTINUE_SEARCH;
}
#endif
}

#endif


Generated by  Doxygen 1.6.0   Back to index