/* * 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. */ #include "bu/unitsuite.h" #include "bu/file.h" #include "bu/sio.h" #include "bu/optparser.h" #include "bu/json.h" #include #include using namespace Bu; #include Bu::UnitSuite::UnitSuite() : iOptions( 0 ), pReport( NULL ) { } Bu::UnitSuite::UnitSuite( int iOptions ) : iOptions( iOptions ) { } Bu::UnitSuite::~UnitSuite() { delete pReport; } int Bu::UnitSuite::run( int argc, char *argv[] ) { bool bCleanup = true; bool bInterop = false; OptParser p; p.addOption( Bu::slot( this, &Bu::UnitSuite::onListCases ), 'l', "list", "List available test cases." ); p.addOption( Bu::slot( this, &Bu::UnitSuite::onPrintName ), "print-name", "Print the internal name of this test suite."); p.addOption( bCleanup, "no-cleanup", "Don't erase temp files."); p.setOverride( "no-cleanup", false ); p.addOption( bInterop, "interop", "Output machine parsable json."); p.setOverride( "interop", true ); p.setNonOption( Bu::slot( this, &Bu::UnitSuite::onAddTest ) ); p.addHelpOption(); p.parse( argc, argv ); if( !hSelTests.isEmpty() ) { TestList lSub; for( TestList::iterator i = lTests.begin(); i != lTests.end(); i++ ) { if( hSelTests.has( (*i).sName ) ) lSub.append( *i ); } lTests = lSub; } if( bInterop ) pReport = new ReportJson(); else pReport = new ReportConsole(); pReport->suiteStarting( *this, lTests ); for( TestList::iterator i = lTests.begin(); i != lTests.end(); i++ ) { try { iStepCount = -1; iProgress = 0; if( pReport ) pReport->testStarting( *i ); (this->*(i->fTest))(); if( pReport ) pReport->testEnded( *i ); } catch( Failed &e ) { if( pReport ) pReport->testEnded( *i, e ); if( (iOptions & optStopOnError) ) { return 0; } } catch( std::exception &e ) { if( pReport ) pReport->testException( *i, e ); if( (iOptions & optStopOnError) ) { return 0; } } catch( ... ) { sio << "fail with external exception." << sio.nl; return -1; if( (iOptions & optStopOnError) ) { return 0; } } } if( pReport ) pReport->suiteEnded(); if( bCleanup ) { for( StrList::iterator i = lFileCleanup.begin(); i; i++ ) { unlink( (*i).getStr() ); } cleanup(); } return 0; } Bu::File Bu::UnitSuite::tempFile( Bu::String &sFileName ) { Bu::File f = Bu::File::tempFile( sFileName ); lFileCleanup.append( sFileName ); return f; } void Bu::UnitSuite::add( Test fTest, const Bu::String &sName, Expect e ) { TestInfo ti; ti.sName = sName; ti.eExpect = e; long index = ti.sName.rfindIdx("::"); if( index != -1 ) { String tmp = sSuiteName; tmp += ti.sName.getStr()+index; ti.sName = tmp; } ti.fTest = fTest; lTests.append( ti ); } void Bu::UnitSuite::setName( const String &sName ) { sSuiteName = sName; } Bu::String Bu::UnitSuite::getName() const { return sSuiteName; } void Bu::UnitSuite::cleanup() { } void Bu::UnitSuite::dispProgress() { if( tLastUpdate == time( NULL ) ) return; if( pReport ) pReport->updateProgress( iProgress, iStepCount ); tLastUpdate = time( NULL ); } void Bu::UnitSuite::setStepCount( int iSteps ) { iStepCount = iSteps; if( iStepCount < 0 ) return; tLastUpdate = 0; dispProgress(); } void Bu::UnitSuite::incProgress( int iAmnt ) { iProgress += iAmnt; if( iProgress < 0 ) iProgress = 0; if( iProgress > iStepCount ) iProgress = iStepCount; dispProgress(); } void Bu::UnitSuite::setProgress( int iAmnt ) { iProgress = iAmnt; if( iProgress < 0 ) iProgress = 0; if( iProgress > iStepCount ) iProgress = iStepCount; dispProgress(); } int Bu::UnitSuite::onListCases( StrArray ) { sio << "Test cases:" << sio.nl; for( TestList::iterator i = lTests.begin(); i; i++ ) { sio << "\t- " << Fmt( iNameWidth, 10, Fmt::Left ) << (*i).sName << " " << (*i).eExpect << sio.nl; } sio << sio.nl; exit( 0 ); return 0; } int Bu::UnitSuite::onPrintName( StrArray ) { Bu::println("%1").arg( sSuiteName ); exit( 0 ); return 0; } int Bu::UnitSuite::onAddTest( StrArray aParam ) { hSelTests.insert( aParam[0], true ); return 0; } Bu::Formatter &Bu::operator<<( Bu::Formatter &f, const Bu::UnitSuite::Expect &e ) { switch( e ) { case Bu::UnitSuite::expectPass: return f << "pass"; case Bu::UnitSuite::expectFail: return f << "fail"; } return f << "**error**"; } ///////// // Bu::UnitSuite::Report //// Bu::UnitSuite::Report::Report() { } Bu::UnitSuite::Report::~Report() { } ///////// // Bu::UnitSuite::ReportConsole //// Bu::UnitSuite::ReportConsole::ReportConsole() : iTestCount( 0 ), iNameWidth( 0 ), iEPass( 0 ), iEFail( 0 ), iUPass( 0 ), iUFail( 0 ) { } Bu::UnitSuite::ReportConsole::~ReportConsole() { } void Bu::UnitSuite::ReportConsole::suiteStarting( const UnitSuite & /*rSuite*/, const TestList &lTests ) { iTestCount = lTests.getSize(); for( TestList::const_iterator i = lTests.begin(); i; i++ ) { if( iNameWidth < i->sName.getSize() ) iNameWidth = i->sName.getSize(); } } void Bu::UnitSuite::ReportConsole::testStarting( const TestInfo &rTest ) { sio << Fmt( iNameWidth+3, Fmt::Left ).fill('.') << rTest.sName << sio.flush; } void Bu::UnitSuite::ReportConsole::updateProgress( int iProgress, int iStepCount ) { sio << Fmt(3) << (iProgress*100/iStepCount) << "%" << "\b\b\b\b" << sio.flush; } void Bu::UnitSuite::ReportConsole::testEnded( const TestInfo &rTest ) { switch( rTest.eExpect ) { case expectPass: sio << "pass." << sio.nl; iEPass++; break; case expectFail: sio << "unexpected pass." << sio.nl; iUPass++; break; } } void Bu::UnitSuite::ReportConsole::testEnded( const TestInfo &rTest, const Bu::UnitSuite::Failed &rFail ) { switch( rTest.eExpect ) { case expectPass: sio << "unexpected "; iUFail++; break; case expectFail: sio << "expected "; iEFail++; break; } if( rFail.bFile ) { sio << "fail in unitTest(" << rFail.str << "). (" << rFail.sFile << ":" << rFail.nLine << ")." << sio.nl; } else { sio << "fail in unitTest(" << rFail.str << ")." << sio.nl; } } void Bu::UnitSuite::ReportConsole::testException( const TestInfo &rTest, std::exception &e ) { switch( rTest.eExpect ) { case expectPass: sio << "unexpected "; iUFail++; break; case expectFail: sio << "expected "; iEFail++; break; } sio << "fail with unknown exception. what: " << e.what() << sio.nl; } void Bu::UnitSuite::ReportConsole::suiteEnded() { sio << sio.nl << "Report:" << sio.nl << "\tTotal tests run: " << iTestCount << sio.nl; if( iEPass > 0 ) sio << "\tExpected passes: " << iEPass << sio.nl; if( iEFail > 0 ) sio << "\tExpected failures: " << iEFail << sio.nl; if( iUPass > 0 ) sio << "\tUnexpected passes: " << iUPass << sio.nl; if( iUFail > 0 ) sio << "\tUnexpected failures: " << iUFail << sio.nl; sio << sio.nl; if( iUPass == 0 && iUFail == 0 ) sio << "\tNothing unexpected." << sio.nl << sio.nl; } ///////// // Bu::UnitSuite::Report //// Bu::UnitSuite::ReportJson::ReportJson() : pReport( new Bu::Json( Bu::Json::Object ) ) { } Bu::UnitSuite::ReportJson::~ReportJson() { delete pReport; } void Bu::UnitSuite::ReportJson::suiteStarting( const UnitSuite &rSuite, const TestList & /*lTests*/ ) { pReport->insert("type", "report"); pReport->insert("suite", rSuite.getName() ); pReport->insertArray("tests"); } void Bu::UnitSuite::ReportJson::testStarting( const TestInfo & ) { } void Bu::UnitSuite::ReportJson::updateProgress( int, int ) { } void Bu::UnitSuite::ReportJson::testEnded( const TestInfo &rTest ) { Bu::Json &rOb = (*pReport)["tests"].appendObject(); rOb.insert("name", rTest.sName ); rOb.insert("expected", Bu::String("%1").arg( rTest.eExpect ) ); rOb.insert("result", "pass"); } void Bu::UnitSuite::ReportJson::testEnded( const TestInfo &rTest, const Bu::UnitSuite::Failed &rFail ) { Bu::Json &rOb = (*pReport)["tests"].appendObject(); rOb.insert("name", rTest.sName ); rOb.insert("expected", Bu::String("%1").arg( rTest.eExpect ) ); rOb.insert("result", "fail"); Bu::Json &rFailOb = rOb.insertObject("fail"); rFailOb.insert("action", rFail.str ); rFailOb.insert("file", rFail.sFile ); rFailOb.insert("line", (double)rFail.nLine ); } void Bu::UnitSuite::ReportJson::testException( const TestInfo &rTest, std::exception &e ) { Bu::Json &rOb = (*pReport)["tests"].appendObject(); rOb.insert("name", rTest.sName ); rOb.insert("expected", Bu::String("%1").arg( rTest.eExpect ) ); rOb.insert("result", "fail"); Bu::Json &rFail = rOb.insertObject("fail"); rFail.insert("what", e.what() ); } void Bu::UnitSuite::ReportJson::suiteEnded() { Bu::println("%1").arg( pReport->toString() ); }