mirror of
				https://github.com/coolaj86/fizzbuzz.git
				synced 2024-11-16 17:29:04 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1181 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			C++
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			1181 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			C++
		
	
	
		
			Executable File
		
	
	
	
	
| #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
 | |
| 
 | |
| /**
 | |
|  * 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.
 | |
|  */
 | |
| 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.
 | |
|  */
 | |
| 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.
 | |
|  */
 | |
| struct beyond_last_test : public no_such_test
 | |
| {
 | |
|     beyond_last_test()
 | |
|     {
 | |
|     }
 | |
|     
 | |
|     ~beyond_last_test() throw()
 | |
|     {
 | |
|     }
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Group not found exception.
 | |
|  */
 | |
| 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.
 | |
|  */
 | |
| struct no_more_tests
 | |
| {
 | |
|     no_more_tests()
 | |
|     {
 | |
|     }
 | |
|     
 | |
|     ~no_more_tests() throw()
 | |
|     {
 | |
|     }
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Internal exception to be throwed when 
 | |
|  * test constructor has failed.
 | |
|  */
 | |
| 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.
 | |
|  */
 | |
| 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.
 | |
|  */
 | |
| struct warning : public tut_error
 | |
| {
 | |
|     warning(const std::string& msg) 
 | |
|         : tut_error(msg)
 | |
|     {
 | |
|     }
 | |
|     
 | |
|     ~warning() throw()
 | |
|     {
 | |
|     }
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Exception to be throwed when test issued SEH (Win32)
 | |
|  */
 | |
| 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.
 | |
|  */
 | |
| struct test_result
 | |
| {
 | |
|     /**
 | |
|      * Test group name.
 | |
|      */
 | |
|     std::string group;
 | |
| 
 | |
|     /**
 | |
|      * Test number in group.
 | |
|      */
 | |
|     int test;
 | |
| 
 | |
|     /**
 | |
|      * Test name (optional)
 | |
|      */
 | |
|     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
 | |
|      */
 | |
|     enum result_type
 | |
|     { 
 | |
|         ok, 
 | |
|         fail, 
 | |
|         ex, 
 | |
|         warn, 
 | |
|         term, 
 | |
|         ex_ctor 
 | |
|     };
 | |
|     
 | |
|     result_type result;
 | |
| 
 | |
|     /**
 | |
|      * Exception message for failed test.
 | |
|      */
 | |
|     std::string message;
 | |
|     std::string exception_typeid;
 | |
| 
 | |
|     /**
 | |
|      * Default constructor.
 | |
|      */
 | |
|     test_result()
 | |
|         : test(0),
 | |
|           result(ok)
 | |
|     {
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Constructor.
 | |
|      */
 | |
|     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.
 | |
|      */
 | |
|     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.
 | |
|  */
 | |
| 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.
 | |
|  */
 | |
| struct callback
 | |
| {
 | |
|     /**
 | |
|      * Virtual destructor is a must for subclassed types.
 | |
|      */
 | |
|     virtual ~callback()
 | |
|     {
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Called when new test run started.
 | |
|      */
 | |
|     virtual void run_started()
 | |
|     {
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Called when a group started
 | |
|      * @param name Name of the group
 | |
|      */
 | |
|     virtual void group_started(const std::string& /*name*/)
 | |
|     {
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Called when a test finished.
 | |
|      * @param tr Test results.
 | |
|      */
 | |
|     virtual void test_completed(const test_result& /*tr*/)
 | |
|     {
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Called when a group is completed
 | |
|      * @param name Name of the group
 | |
|      */
 | |
|     virtual void group_completed(const std::string& /*name*/)
 | |
|     {
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Called when all tests in run completed.
 | |
|      */
 | |
|     virtual void run_completed()
 | |
|     {
 | |
|     }
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Typedef for runner::list_groups()
 | |
|  */
 | |
| typedef std::vector<std::string> groupnames;
 | |
| 
 | |
| /**
 | |
|  * Test runner.
 | |
|  */
 | |
| class test_runner
 | |
| {
 | |
| 
 | |
| public:
 | |
|     
 | |
|     /**
 | |
|      * Constructor
 | |
|      */
 | |
|     test_runner() 
 | |
|         : callback_(&default_callback_)
 | |
|     {
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Stores another group for getting by name.
 | |
|      */
 | |
|     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.
 | |
|      */
 | |
|     void set_callback(callback* cb)
 | |
|     {
 | |
|         callback_ = cb == 0 ? &default_callback_ : cb;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns callback object.
 | |
|      */
 | |
|     callback& get_callback() const
 | |
|     {
 | |
|         return *callback_;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns list of known test groups.
 | |
|      */
 | |
|     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
 | |
|      */
 | |
|     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.
 | |
|      */
 | |
|     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.
 | |
|      */
 | |
|     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.
 | |
|  */
 | |
| 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>
 | |
| class test_object : public Data
 | |
| {
 | |
| public:
 | |
| 
 | |
|     /**
 | |
|      * Default constructor
 | |
|      */
 | |
|     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>
 | |
|     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.
 | |
|      */
 | |
|     bool called_method_was_a_dummy_test_;
 | |
| 
 | |
| private:
 | |
| 
 | |
|     std::string current_test_name_;
 | |
| };
 | |
| 
 | |
| namespace
 | |
| {
 | |
| 
 | |
| /**
 | |
|  * Tests provided condition.
 | |
|  * Throws if false.
 | |
|  */
 | |
| void ensure(bool cond)
 | |
| {
 | |
|     if (!cond)
 | |
|     {
 | |
|         // TODO: default ctor?
 | |
|         throw failure("");
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Tests provided condition.
 | |
|  * Throws if true.
 | |
|  */
 | |
| void ensure_not(bool cond)
 | |
| {
 | |
|     ensure(!cond);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Tests provided condition.
 | |
|  * Throws if false.
 | |
|  */
 | |
| template <typename T>
 | |
| void ensure(const T msg, bool cond)
 | |
| {
 | |
|     if (!cond)
 | |
|     {
 | |
|         throw failure(msg);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Tests provided condition.
 | |
|  * Throws if true.
 | |
|  */
 | |
| template <typename T>
 | |
| 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>
 | |
| 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>
 | |
| 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.
 | |
|  */
 | |
| 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>
 | |
| 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>
 | |
| 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>
 | |
|     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.
 | |
|          */
 | |
|         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.
 | |
|          */
 | |
|         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.
 | |
|          */
 | |
|         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.
 | |
|      */
 | |
|     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.
 | |
|      */
 | |
|     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.
 | |
|      */
 | |
|     void reg(int n, testmethod tm)
 | |
|     {
 | |
|         tests_[n] = tm;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Reset test position before first test.
 | |
|      */
 | |
|     void rewind()
 | |
|     {
 | |
|         current_test_ = tests_.begin();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Runs next test.
 | |
|      */
 | |
|     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.
 | |
|      */
 | |
|     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!
 | |
|      */
 | |
|     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.
 | |
|      */
 | |
|     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
 | |
| 
 |