#include #include #include #include #include #include #include #include #include typedef Bu::List BlobList; Bu::Blob getSuiteName( const Bu::Blob &bSuiteExec ) { Bu::Process proc( Bu::Process::StdOut, bSuiteExec.getData(), bSuiteExec.getData(), "--print-name", NULL ); Bu::String sResult = proc.readAll().trimWhitespace(); Bu::Blob bRet( sResult.getStr(), sResult.getSize() ); return bRet; } Bu::Json *runSuite( const Bu::Blob &bSuiteExec, Bu::Json *pSuiteStatus=NULL ) { BlobList lCmd; lCmd.append( bSuiteExec ); lCmd.append( "--interop" ); if( pSuiteStatus != NULL ) { for( Bu::Json::iterator i = (*pSuiteStatus)["tests"].begin(); i; i++ ) { lCmd.append( (**i)["name"].getString().get() ); } } char **argv = new char*[lCmd.getSize()+1]; int j = 0; for( BlobList::iterator i = lCmd.begin(); i; j++, i++ ) { argv[j] = (*i).getData(); } argv[lCmd.getSize()] = NULL; Bu::Process proc( Bu::Process::StdOut, bSuiteExec.getData(), argv ); Bu::BlobBuilder bbReport; while( proc.isRunning() ) { int iRead = 0; char dRead[4096]; iRead = proc.read( dRead, 4096 ); if( iRead > 0 ) { bbReport.append( dRead, iRead ); } } delete[] argv; Bu::Json *pReport = new Bu::Json(); pReport->parse( bbReport.getBlob() ); return pReport; } BlobList getSuitePaths( const Bu::Blob &bDir ) { BlobList lPaths; DIR *dir = opendir( bDir.getData() ); if( dir == NULL ) { Bu::println("Could not open directory: %1").arg( bDir ); return lPaths; } struct dirent *de = NULL; while( (de = readdir( dir )) != NULL ) { if( de->d_type != DT_REG ) continue; Bu::BlobBuilder bbPath; bbPath.append( bDir ); bbPath.append("/"); bbPath.append( de->d_name ); Bu::Blob bPath = bbPath.getBlob(); if( access( bPath.getData(), X_OK ) != 0 ) continue; lPaths.append( bPath ); } closedir( dir ); return lPaths; } int main( int argc, char *argv[] ) { Bu::Blob bDir("unit"); bool bAll = false; bool bHasStatus = false; Bu::Json jsLastStatus; Bu::OptParser options; options.addOption( bDir, 'p', "path", "Directory to look for unit tests."); options.addOption( bAll, 'a', "all", "Execute all tests, ignoring state."); options.setOverride("all", "true"); options.addHelpOption(); options.parse( argc, argv ); if( !bAll ) { try { Bu::String sData = Bu::File("rununits.status.json", Bu::File::Read ) .readAll(); jsLastStatus.parse( sData ); bHasStatus = true; Bu::println("Current status loaded, created: %1") .arg( jsLastStatus["date"].getString().get() ); } catch(...) { } } BlobList lPaths = getSuitePaths( bDir ); Bu::Hash hStatusSuitesByPath; if( bHasStatus ) { BlobList lNewPaths; for( Bu::Json::iterator i = jsLastStatus["suites"].begin(); i; i++ ) { hStatusSuitesByPath.insert( (**i)["path"].getString().get(), (*i) ); } for( BlobList::iterator i = lPaths.begin(); i; i++ ) { if( hStatusSuitesByPath.has( *i ) ) lNewPaths.append( *i ); } lPaths = lNewPaths; } int iNumLen = Bu::String("%1").arg( lPaths.getSize() ).end().getSize(); int iMaxSuiteName = 0; Bu::Hash hName; for( BlobList::iterator i = lPaths.begin(); i; i++ ) { Bu::Blob bSuiteName = getSuiteName( *i ); if( iMaxSuiteName < bSuiteName.getSize() ) iMaxSuiteName = bSuiteName.getSize(); hName.insert( *i, bSuiteName ); } Bu::List lReport; int iTest = 0; for( BlobList::iterator i = lPaths.begin(); i; i++ ) { iTest++; Bu::Blob bSuiteName = hName.get( *i ); Bu::print("\rRunning test suite [%1/%2]: %3") .arg( iTest, Bu::Fmt().width( iNumLen ).right() ) .arg( lPaths.getSize(), Bu::Fmt().width( iNumLen ) ) .arg( bSuiteName, Bu::Fmt().width( iMaxSuiteName ).fill(' ').left() ); Bu::sio << Bu::sio.flush; Bu::Json *pSuiteStatus = NULL; if( bHasStatus ) { pSuiteStatus = hStatusSuitesByPath.get( *i ); } Bu::Json *pReport = runSuite( (*i), pSuiteStatus ); pReport->insert("path", *i ); int iUnexpected = 0; int iTotal = 0; iTotal = (*pReport)["tests"].getSize(); for( int j = 0; j < iTotal; j++ ) { if( !((*pReport)["tests"][j]["expected"].getString() == (*pReport)["tests"][j]["result"].getString()) ) { iUnexpected++; } } if( iUnexpected == 0 ) { delete pReport; } else { lReport.append( pReport ); } } Bu::println("\rCompleted %1 unit test suites.%2") .arg( lPaths.getSize(), Bu::Fmt().width( iNumLen ) ) .arg( Bu::Blob(""), Bu::Fmt().width( iMaxSuiteName ).fill(' ').left() ); if( lReport.getSize() == 0 ) { Bu::println("\nNothing unexpected in unit tests."); unlink("rununits.status.json"); } else { Bu::Json jStatus( Bu::Json::Object ); time_t tNow = time( NULL ); jStatus.insert("date", Bu::String(ctime( &tNow )).trimWhitespace() ); jStatus.insertArray("suites"); for( Bu::List::iterator i = lReport.begin(); i; i++ ) { Bu::println("\nUnexpected results in: %1") .arg( (**i)["suite"].getString().get() ); Bu::Json &jStSuite = jStatus["suites"].appendObject(); jStSuite.insert("name", (**i)["suite"].getString() ); jStSuite.insert("path", (**i)["path"].getString() ); Bu::Json &jStTests = jStSuite.insertArray("tests"); for( Bu::Json::iterator iTest = (**i)["tests"].begin(); iTest; iTest++ ) { if( (**iTest)["expected"].getString() == (**iTest)["result"].getString() ) { continue; } Bu::Json &jsTest = jStTests.appendObject(); jsTest.insert("name", (**iTest)["name"].getString() ); jsTest.insert("result", (**iTest)["result"].getString() ); jsTest.insert("expected", (**iTest)["expected"].getString() ); Bu::println(" %1: unexpected %2") .arg( (**iTest)["name"].getString().get() ) .arg( (**iTest)["result"].getString().get() ); if( (**iTest).has("fail") ) { if( (**iTest)["fail"].has("action") ) { Bu::println(" unitTest( %1 );") .arg( (**iTest)["fail"]["action"].getString().get() ); Bu::println(" at %1:%2") .arg( (**iTest)["fail"]["file"].getString().get() ) .arg( (int)(**iTest)["fail"]["line"].getNumber() ); } else if( (**iTest)["fail"].has("what") ) { Bu::println(" Unexpected exception: %1") .arg( (**iTest)["fail"]["what"].getString().get() ); } } else { Bu::println(" No further details."); } } delete *i; } Bu::File("rununits.status.json", Bu::File::WriteNew ) .write( jStatus.toString() ); } return 0; }