Logo Search packages:      
Sourcecode: dballe version File versions

tut.h

#ifndef TUT_H_GUARD
#define TUT_H_GUARD

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

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

/**
 * Template Unit Tests Framework for C++.
 * http://tut.dozen.ru
 *
 * @author dozen, tut@dozen.ru
 */
namespace tut
{
  /**
   * Exception to be throwed when attempted to execute 
   * missed test by number.
   */
00029   struct no_such_test : public std::logic_error
  {
    no_such_test() : std::logic_error("no such test"){};
  };

  /**
   * 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.
   */
00039   struct beyond_last_test : public no_such_test
  {
    beyond_last_test(){};
  };

  /**
   * Group not found exception.
   */
00047   struct no_such_group : public std::logic_error
  {
    no_such_group(const std::string& grp) : 
      std::logic_error(grp){};
  };

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

  /**
   * Exception to be throwed when ensure() fails or fail() called.
   */
00065   class failure : public std::logic_error
  {
    public:
      failure(const std::string& msg) : std::logic_error(msg){};
  };

  /**
   * Exception to be throwed when test desctructor throwed an exception.
   */
00074   class warning : public std::logic_error
  {
    public:
      warning(const std::string& msg) : std::logic_error(msg){};
  };

  /**
   * Exception to be throwed when test issued SEH (Win32)
   */
00083   class seh : public std::logic_error
  {
    public:
      seh(const std::string& msg) : std::logic_error(msg){};
  };

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

    /**
     * Test number in group.
     */
00105     int test;
    
    /**
     * 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
     */
00114     typedef enum { ok, fail, ex, warn, term } result_type;
    result_type result;

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

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

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

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

  /**
   * Interface.
   * Test group operations.
   */
00155   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.
   */
00174   struct callback
  {
    /**
     * Virtual destructor is a must for subclassed types.
     */
00179     virtual ~callback(){};

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

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

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

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

  /**
   * Test runner.
   */
00206   class test_runner
  {
    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_;

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

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

      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
        std::cerr << msg << std::endl;
        throw std::logic_error(msg);
      }

      groups_[name] = gr;
    }

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

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

    /**
     * Returns list of known test groups.
     */
00266     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
     */
00283     void run_tests() const
    {
      callback_->run_started();

      const_iterator i = groups_.begin();
      const_iterator e = groups_.end();
      while( i != e )
      {
        try
        {
          // iterate all tests
          i->second->rewind();
          for( ;; )
          {
            test_result tr = i->second->run_next();
            callback_->test_completed(tr);
          }
        }
        catch( const no_more_tests& )
        {
          // ok
        }

        ++i;
      }

      callback_->run_completed();
    }

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

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

      try
      {
        // iterate all tests
        i->second->rewind();
        for(;;)
        {
          test_result tr = i->second->run_next();
          callback_->test_completed(tr);
        }
      }
      catch( const no_more_tests& )
      {
        // ok
      }

      callback_->run_completed();
    }

    /**
     * Runs one test in specified group.
     */
00346     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() )
      {
        throw no_such_group(group_name);
      }

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

  /**
   * Singleton for test_runner implementation.
   * Instance with name runner_singleton shall be implemented
   * by user.
   */
00381   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>
00398   class test_object : public Data
  {
    public:
    /**
     * Default constructor
     */
00404     test_object(){};

    /**
     * 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.
     */
    bool called_method_was_a_dummy_test_;

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

  namespace 
  {
    /**
     * Tests provided condition.
     * Throws if false.
     */
00431     void ensure(bool cond)
    {
       if( !cond ) throw failure("");
    }

    /**
     * Tests provided condition.
     * Throws if false.
     */
00440     void ensure(const char* msg,bool cond)
    {
       if( !cond ) throw failure(msg);
    }

    /**
     * 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>
00453     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>
00479     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.
     */
00499     void fail(const char* msg="")
    {
      throw failure(msg);
    }
  }

  /**
   * Walks through test tree and stores address of each
   * test method in group. Instantiation stops at 0.
   */
  template <class Test,class Group,int n>
00510   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>
00531   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>
00550     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.
       */
00576       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.
       */
00584       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.
       */
00614       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.
     */
00650     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.
     */
00663     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.
     */
00677     void reg(int n,testmethod tm)
    {
      tests_[n] = tm;
    }

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

    /**
     * Runs next test.
     */
00693     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.
     */
00720     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
     */
00739     test_result run_test_(const tests_iterator& ti,safe_holder<object>& obj)
    {
      try
      {
        if( run_test_seh_(ti->second,obj) == false )
          throw seh("seh");
      }
      catch(const no_such_test&)
      {
        throw;
      }
      catch(const warning& ex)
      {
        // test ok, but destructor failed
        test_result tr(name_,ti->first,test_result::warn,ex);
        return tr;
      }
      catch(const failure& ex)
      {
        // test failed because of ensure() or similar method
        test_result tr(name_,ti->first,test_result::fail,ex);
        return tr;
      }
      catch(const seh& ex)
      {
        // test failed with sigsegv, divide by zero, etc
        test_result tr(name_,ti->first,test_result::term,ex);
        return tr;
      }
      catch(const std::exception& ex)
      {
        // test failed with std::exception
        test_result tr(name_,ti->first,test_result::ex,ex);
        return tr;
      }
      catch(...)
      {
        // test failed with unknown exception
        test_result tr(name_,ti->first,test_result::ex);
        return tr;
      }

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

    /**
     * Runs one under SEH if platform supports it.
     */
00789     bool run_test_seh_(testmethod tm,safe_holder<object>& obj)
    {
#if defined(TUT_USE_SEH)
      __try
      {
#endif
        if( obj.get() == 0 ) obj.reset();
        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");
          return false;
        }
#endif

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

        obj.permit_throw();
        obj.release();
#if defined(TUT_USE_SEH)
      }
      __except(handle_seh_(::GetExceptionCode()))
      {
        // throw seh("SEH");
        return false;
      }
#endif
      return true;
    }
  };


#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