mirror of
				https://github.com/coolaj86/fizzbuzz.git
				synced 2024-11-16 17:29:04 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			395 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			C++
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			395 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			C++
		
	
	
		
			Executable File
		
	
	
	
	
| #ifndef TUT_RESTARTABLE_H_GUARD
 | |
| #define TUT_RESTARTABLE_H_GUARD
 | |
| 
 | |
| #include "tut.hpp"
 | |
| #include <fstream>
 | |
| #include <iostream>
 | |
| #include <stdexcept>
 | |
| 
 | |
| /**
 | |
|  * Template Unit Tests Framework for C++.
 | |
|  * http://tut.dozen.ru
 | |
|  *
 | |
|  * Optional restartable wrapper for test_runner. Allows to restart test runs
 | |
|  * finished due to abnormal test application termination (such as segmentation
 | |
|  * fault or math error).
 | |
|  *
 | |
|  * @author Vladimir Dyuzhev, Vladimir.Dyuzhev@gmail.com
 | |
|  */
 | |
| 
 | |
| namespace tut
 | |
| {
 | |
|     
 | |
| namespace util
 | |
| {
 | |
|     
 | |
| /**
 | |
|  * Escapes non-alphabetical characters in string.
 | |
|  */
 | |
| std::string escape(const std::string& orig)
 | |
| {
 | |
|     std::string rc;
 | |
|     std::string::const_iterator i,e;
 | |
|     i = orig.begin();
 | |
|     e = orig.end();
 | |
| 
 | |
|     while (i != e)
 | |
|     {
 | |
|         if ((*i >= 'a' && *i <= 'z') ||
 | |
|                 (*i >= 'A' && *i <= 'Z') ||
 | |
|                 (*i >= '0' && *i <= '9') )
 | |
|         {
 | |
|             rc += *i;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             rc += '\\';
 | |
|             rc += ('a'+(((unsigned int)*i) >> 4));
 | |
|             rc += ('a'+(((unsigned int)*i) & 0xF));
 | |
|         }
 | |
| 
 | |
|         ++i;
 | |
|     }
 | |
|     return rc;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Un-escapes string.
 | |
|  */
 | |
| std::string unescape(const std::string& orig)
 | |
| {
 | |
|     std::string rc;
 | |
|     std::string::const_iterator i,e;
 | |
|     i = orig.begin();
 | |
|     e = orig.end();
 | |
| 
 | |
|     while (i != e)
 | |
|     {
 | |
|         if (*i != '\\')
 | |
|         {
 | |
|             rc += *i;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             ++i;
 | |
|             if (i == e)
 | |
|             {
 | |
|                 throw std::invalid_argument("unexpected end of string");
 | |
|             }
 | |
|             unsigned int c1 = *i;
 | |
|             ++i;
 | |
|             if (i == e)
 | |
|             {
 | |
|                 throw std::invalid_argument("unexpected end of string");
 | |
|             }
 | |
|             unsigned int c2 = *i;
 | |
|             rc += (((c1 - 'a') << 4) + (c2 - 'a'));
 | |
|         }
 | |
| 
 | |
|         ++i;
 | |
|     }
 | |
|     return rc;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Serialize test_result avoiding interfering with operator <<.
 | |
|  */
 | |
| void serialize(std::ostream& os, const tut::test_result& tr)
 | |
| {
 | |
|     os << escape(tr.group) << std::endl;
 | |
|     os << tr.test << ' ';
 | |
|     switch(tr.result)
 | |
|     {
 | |
|     case test_result::ok:
 | |
|         os << 0;
 | |
|         break;
 | |
|     case test_result::fail:
 | |
|         os << 1;
 | |
|         break;
 | |
|     case test_result::ex:
 | |
|         os << 2;
 | |
|         break;
 | |
|     case test_result::warn:
 | |
|         os << 3;
 | |
|         break;
 | |
|     case test_result::term:
 | |
|         os << 4;
 | |
|         break;
 | |
|     default:
 | |
|         throw std::logic_error("operator << : bad result_type");
 | |
|     }
 | |
|     os << ' ' << escape(tr.message) << std::endl;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * deserialization for test_result
 | |
|  */
 | |
| void deserialize(std::istream& is, tut::test_result& tr)
 | |
| {
 | |
|     std::getline(is,tr.group);
 | |
|     if (is.eof())
 | |
|     {
 | |
|         throw tut::no_more_tests();
 | |
|     }
 | |
|     tr.group = unescape(tr.group);
 | |
| 
 | |
|     tr.test = -1;
 | |
|     is >> tr.test;
 | |
|     if (tr.test < 0)
 | |
|     {
 | |
|         throw std::logic_error("operator >> : bad test number");
 | |
|     }
 | |
| 
 | |
|     int n = -1;
 | |
|     is >> n;
 | |
|     switch(n)
 | |
|     {
 | |
|     case 0:
 | |
|         tr.result = test_result::ok;
 | |
|         break;
 | |
|     case 1:
 | |
|         tr.result = test_result::fail;
 | |
|         break;
 | |
|     case 2:
 | |
|         tr.result = test_result::ex;
 | |
|         break;
 | |
|     case 3:
 | |
|         tr.result = test_result::warn;
 | |
|         break;
 | |
|     case 4:
 | |
|         tr.result = test_result::term;
 | |
|         break;
 | |
|     default:
 | |
|         throw std::logic_error("operator >> : bad result_type");
 | |
|     }
 | |
| 
 | |
|     is.ignore(1); // space
 | |
|     std::getline(is,tr.message);
 | |
|     tr.message = unescape(tr.message);
 | |
|     if (!is.good())
 | |
|     {
 | |
|         throw std::logic_error("malformed test result");
 | |
|     }
 | |
| }
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Restartable test runner wrapper.
 | |
|  */
 | |
| class restartable_wrapper
 | |
| {
 | |
|     test_runner& runner_;
 | |
|     callback* callback_;
 | |
| 
 | |
|     std::string dir_;
 | |
|     std::string log_; // log file: last test being executed
 | |
|     std::string jrn_; // journal file: results of all executed tests
 | |
| 
 | |
| public:
 | |
|     /**
 | |
|      * Default constructor.
 | |
|      * @param dir Directory where to search/put log and journal files
 | |
|      */
 | |
|     restartable_wrapper(const std::string& dir = ".")
 | |
|         : runner_(runner.get()), 
 | |
|           callback_(0), 
 | |
|           dir_(dir)
 | |
|     {
 | |
|         // dozen: it works, but it would be better to use system path separator
 | |
|         jrn_ = dir_ + '/' + "journal.tut";
 | |
|         log_ = dir_ + '/' + "log.tut";
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Stores another group for getting by name.
 | |
|      */
 | |
|     void register_group(const std::string& name, group_base* gr)
 | |
|     {
 | |
|         runner_.register_group(name,gr);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Stores callback object.
 | |
|      */
 | |
|     void set_callback(callback* cb)
 | |
|     {
 | |
|         callback_ = cb;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns callback object.
 | |
|      */
 | |
|     callback& get_callback() const
 | |
|     {
 | |
|         return runner_.get_callback();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns list of known test groups.
 | |
|      */
 | |
|     groupnames list_groups() const
 | |
|     {
 | |
|         return runner_.list_groups();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Runs all tests in all groups.
 | |
|      */
 | |
|     void run_tests() const
 | |
|     {
 | |
|         // where last run was failed
 | |
|         std::string fail_group;
 | |
|         int fail_test;
 | |
|         read_log_(fail_group,fail_test);
 | |
|         bool fail_group_reached = (fail_group == "");
 | |
| 
 | |
|         // iterate over groups
 | |
|         tut::groupnames gn = list_groups();
 | |
|         tut::groupnames::const_iterator gni,gne;
 | |
|         gni = gn.begin();
 | |
|         gne = gn.end();
 | |
|         while (gni != gne)
 | |
|         {
 | |
|             // skip all groups before one that failed
 | |
|             if (!fail_group_reached)
 | |
|             {
 | |
|                 if (*gni != fail_group)
 | |
|                 {
 | |
|                     ++gni;
 | |
|                     continue;
 | |
|                 }
 | |
|                 fail_group_reached = true;
 | |
|             }
 | |
| 
 | |
|             // first or restarted run
 | |
|             int test = (*gni == fail_group && fail_test >= 0) ? fail_test + 1 : 1;
 | |
|             while(true)
 | |
|             {
 | |
|                 // last executed test pos
 | |
|                 register_execution_(*gni,test);
 | |
| 
 | |
|                 try
 | |
|                 {
 | |
|                     tut::test_result tr = runner_.run_test(*gni,test);
 | |
|                     register_test_(tr);
 | |
|                 }
 | |
|                 catch (const tut::beyond_last_test&)
 | |
|                 {
 | |
|                     break;
 | |
|                 }
 | |
|                 catch(const tut::no_such_test&)
 | |
|                 {
 | |
|                     // it's ok
 | |
|                 }
 | |
| 
 | |
|                 ++test;
 | |
|             }
 | |
| 
 | |
|             ++gni;
 | |
|         }
 | |
| 
 | |
|         // show final results to user
 | |
|         invoke_callback_();
 | |
| 
 | |
|         // truncate files as mark of successful finish
 | |
|         truncate_();
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     /**
 | |
|      * Shows results from journal file.
 | |
|      */
 | |
|     void invoke_callback_() const
 | |
|     {
 | |
|         runner_.set_callback(callback_);
 | |
|         runner_.get_callback().run_started();
 | |
| 
 | |
|         std::string current_group;
 | |
|         std::ifstream ijournal(jrn_.c_str());
 | |
|         while (ijournal.good())
 | |
|         {
 | |
|             // read next test result
 | |
|             try
 | |
|             {
 | |
|                 tut::test_result tr;
 | |
|                 util::deserialize(ijournal,tr);
 | |
|                 runner_.get_callback().test_completed(tr);
 | |
|             }
 | |
|             catch (const no_more_tests&)
 | |
|             {
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         runner_.get_callback().run_completed();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Register test into journal.
 | |
|      */
 | |
|     void register_test_(const test_result& tr) const
 | |
|     {
 | |
|         std::ofstream ojournal(jrn_.c_str(), std::ios::app);
 | |
|         util::serialize(ojournal, tr);
 | |
|         ojournal << std::flush;
 | |
|         if (!ojournal.good())
 | |
|         {
 | |
|             throw std::runtime_error("unable to register test result in file "
 | |
|                 + jrn_);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Mark the fact test going to be executed
 | |
|      */
 | |
|     void register_execution_(const std::string& grp, int test) const
 | |
|     {
 | |
|         // last executed test pos
 | |
|         std::ofstream olog(log_.c_str());
 | |
|         olog << util::escape(grp) << std::endl << test << std::endl << std::flush;
 | |
|         if (!olog.good())
 | |
|         {
 | |
|             throw std::runtime_error("unable to register execution in file "
 | |
|                 + log_);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Truncate tests.
 | |
|      */
 | |
|     void truncate_() const
 | |
|     {
 | |
|         std::ofstream olog(log_.c_str());
 | |
|         std::ofstream ojournal(jrn_.c_str());
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Read log file
 | |
|      */
 | |
|     void read_log_(std::string& fail_group, int& fail_test) const
 | |
|     {
 | |
|         // read failure point, if any
 | |
|         std::ifstream ilog(log_.c_str());
 | |
|         std::getline(ilog,fail_group);
 | |
|         fail_group = util::unescape(fail_group);
 | |
|         ilog >> fail_test;
 | |
|         if (!ilog.good())
 | |
|         {
 | |
|             fail_group = "";
 | |
|             fail_test = -1;
 | |
|             truncate_();
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             // test was terminated...
 | |
|             tut::test_result tr(fail_group, fail_test, "", tut::test_result::term);
 | |
|             register_test_(tr);
 | |
|         }
 | |
|     }
 | |
| };
 | |
| 
 | |
| }
 | |
| 
 | |
| #endif
 | |
| 
 |