/* * Copyright (C) 2007-2023 Xagasoft, All rights reserved. * * This file is part of the libbu++ library and is released under the * terms of the license contained in the file LICENSE. */ #ifndef BU_UNIT_SUITE_H #define BU_UNIT_SUITE_H #include #include "bu/list.h" #include "bu/string.h" #include "bu/file.h" #include "bu/array.h" #include "bu/hash.h" namespace Bu { class Json; /** * Provides a unit testing framework. This is pretty easy to use, probably * the best way to get started is to use ch to generate a template, or use * the code below with appropriate tweaks: *@code * #include "unitsuite.h" * * class Unit : public Bu::UnitSuite * { * public: * Unit() * { * setName("Example"); * addTest( Unit::test ); * } * * virtual ~Unit() { } * * // * // Tests go here * // * void test() * { * unitTest( 1 == 1 ); * } * }; * * int main( int argc, char *argv[] ) * { * return Unit().run( argc, argv ); * } * @endcode * The main function can contain other things, but using this one exactly * makes all of the test suites work exactly the same. Using the optional * setName at the top of the constructor replaces the class name with the * chosen name when printing out stats and info. */ class UnitSuite { public: UnitSuite(); UnitSuite( int iOptions ); virtual ~UnitSuite(); int run( int argc=0, char *argv[]=NULL ); Bu::File tempFile( Bu::String &sFileName ); typedef void (UnitSuite::*Test)(); class Failed { public: Failed() : str(""), bFile( false ) { } Failed( const String &s ) : str( s ), bFile( false ) { } Failed( const String &s, const String &sFile, int nLine ) : str( s ), sFile( sFile ), nLine( nLine ), bFile( true ) { } String str; String sFile; int nLine; bool bFile; }; enum { optStopOnError = 0x000001 }; enum Expect { expectPass, expectFail }; protected: void add( Test fTest, const Bu::String &sName, Expect e=expectPass ); void setName( const String &sName ); String getName() const; virtual void cleanup(); void dispProgress(); void setStepCount( int iSteps ); void incProgress( int iAmnt = 1 ); void setProgress( int iAmnt ); private: int onListCases( Bu::Array aParam ); int onPrintName( Bu::Array aParam ); int onAddTest( Bu::Array aParam ); private: typedef struct TestInfo { String sName; Test fTest; Expect eExpect; } TestInfo; typedef Bu::List TestList; TestList lTests; String sSuiteName; int iOptions; typedef Bu::List StrList; StrList lFileCleanup; int iNameWidth; int iStepCount; int iProgress; time_t tLastUpdate; Bu::Hash hSelTests; public: class Report { public: Report(); virtual ~Report(); virtual void suiteStarting( const UnitSuite &rSuite, const TestList &lTests )=0; virtual void testStarting( const TestInfo &rTest )=0; virtual void updateProgress( int iProgress, int iStepCount )=0; virtual void testEnded( const TestInfo &rTest )=0; virtual void testEnded( const TestInfo &rTest, const Failed &rFail )=0; virtual void testException( const TestInfo &rTest, std::exception &e )=0; virtual void suiteEnded()=0; }; class ReportConsole : public Report { public: ReportConsole(); virtual ~ReportConsole(); virtual void suiteStarting( const UnitSuite &rSuite, const TestList &lTests ); virtual void testStarting( const TestInfo &rTest ); virtual void updateProgress( int iProgress, int iStepCount ); virtual void testEnded( const TestInfo &rTest ); virtual void testEnded( const TestInfo &rTest, const Failed &rFail ); virtual void testException( const TestInfo &rTest, std::exception &e ); virtual void suiteEnded(); private: int iTestCount; int iNameWidth; int iEPass; int iEFail; int iUPass; int iUFail; }; class ReportJson : public Report { public: ReportJson(); virtual ~ReportJson(); virtual void suiteStarting( const UnitSuite &rSuite, const TestList &lTests ); virtual void testStarting( const TestInfo &rTest ); virtual void updateProgress( int iProgress, int iStepCount ); virtual void testEnded( const TestInfo &rTest ); virtual void testEnded( const TestInfo &rTest, const Failed &rFail ); virtual void testException( const TestInfo &rTest, std::exception &e ); virtual void suiteEnded(); private: class Bu::Json *pReport; }; private: Report *pReport; }; Bu::Formatter &operator<<( Bu::Formatter &f, const Bu::UnitSuite::Expect &e ); } #define addTest( fn ) add( static_cast(&fn), #fn ) #define unitTest( tst ) if( !(tst) ) \ { \ throw Bu::UnitSuite::Failed( #tst, __FILE__, __LINE__ ); \ } else (void)0 #define unitTestCatch( tst, exception ) try \ { \ tst; \ throw Bu::UnitSuite::Failed( #tst, __FILE__, __LINE__ ); \ } catch( exception & ) { } \ catch(...) { \ throw Bu::UnitSuite::Failed( #tst, __FILE__, __LINE__ ); \ } (void)0 #define unitFailed( msg ) throw Bu::UnitSuite::Failed(msg, __FILE__, __LINE__) #endif