From c7e1277ecaf40c6d8ee945418a306f5b15189b97 Mon Sep 17 00:00:00 2001 From: Mike Buland Date: Tue, 8 Aug 2023 16:33:38 -0700 Subject: Unit test augmentations and harness. Added some features to the mkunit program, including cleanup routine support. Added reporting modes for the UnitSuite class, and it can now generate machine readable reports. Added a new program, rununits that runs all unit tests and generates a synopsis of what you really care about at the end, issues! --- src/tools/mkunit.cpp | 34 ++++++++- src/tools/rununits.cpp | 186 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+), 3 deletions(-) create mode 100644 src/tools/rununits.cpp (limited to 'src/tools') diff --git a/src/tools/mkunit.cpp b/src/tools/mkunit.cpp index f6ea669..5711c42 100644 --- a/src/tools/mkunit.cpp +++ b/src/tools/mkunit.cpp @@ -42,6 +42,7 @@ enum TokType tokTest, tokChar, tokBlock, + tokCleanup, tokEof }; @@ -54,6 +55,7 @@ Bu::Formatter &operator<<( Bu::Formatter &f, TokType t ) case tokTest: return f << "tokTest"; case tokChar: return f << "tokChar"; case tokBlock: return f << "tokBlock"; + case tokCleanup: return f << "tokCleanup"; case tokEof: return f << "tokEof"; } @@ -193,6 +195,8 @@ public: { if( sTok == "test" ) return tokTest; + else if( sTok == "cleanup" ) + return tokCleanup; else { v = sTok; @@ -361,6 +365,15 @@ public: s.lTest.append( t ); } break; + + case tokCleanup: + { + if( nextToken( v, sWs, iL, iC ) != tokBlock ) + throw Bu::ExceptionBase("%d:%d: Expected " + "{...} block.", + iL, iC ); + } + break; case tokChar: if( v.get() == '}' ) @@ -438,7 +451,7 @@ public: for( TestList::iterator i = s.lTest.begin(); i; i++ ) { f << "\t\tadd( static_cast(" - "&" << sClass << "::" << (*i).sName << "), \"" + "&" << sClass << "::_test_" << (*i).sName << "), \"" << (*i).sName << "\", Bu::UnitSuite::" "expectPass );" << f.nl; } @@ -481,9 +494,24 @@ public: "{...} block.", iL, iC ); - f << "\tvoid " << t.sName << "()" + f << "void _test_" << t.sName << "()" + << f.nl << "#line " << iL + << " \"" << sIn << "\"" << f.nl << " " + << v << f.nl; + } + break; + + case tokCleanup: + { + fOut.write( sWs ); + if( nextToken( v, sWs, iL, iC ) != tokBlock ) + throw Bu::ExceptionBase("%d:%d: Expected " + "{...} block.", + iL, iC ); + + f << "void cleanup()" << f.nl << "#line " << iL - << " \"" << sIn << "\"" << f.nl + << " \"" << sIn << "\"" << f.nl << " " << v << f.nl; } break; diff --git a/src/tools/rununits.cpp b/src/tools/rununits.cpp new file mode 100644 index 0000000..769d3ab --- /dev/null +++ b/src/tools/rununits.cpp @@ -0,0 +1,186 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +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::Process proc( + Bu::Process::StdOut, + bSuiteExec.getData(), + bSuiteExec.getData(), + "--interop", + NULL + ); + Bu::BlobBuilder bbReport; + while( proc.isRunning() ) + { + int iRead = 0; + char dRead[4096]; + iRead = proc.read( dRead, 4096 ); + if( iRead > 0 ) + { + bbReport.append( dRead, iRead ); + } + } + Bu::Json *pReport = new Bu::Json(); + pReport->parse( bbReport.getBlob() ); + return pReport; +} + +typedef Bu::List BlobList; + +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"); + BlobList lPaths = getSuitePaths( bDir ); + + 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 *pReport = runSuite( (*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."); + } + else + { + for( Bu::List::iterator i = lReport.begin(); i; i++ ) + { + Bu::println("\nUnexpected results in: %1") + .arg( (**i)["suite"].getString().get() ); + + for( Bu::Json::iterator iTest = (**i)["tests"].begin(); + iTest; iTest++ ) + { + if( (**iTest)["expected"].getString() == + (**iTest)["result"].getString() ) + { + continue; + } + + 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; + } + } + + return 0; +} -- cgit v1.2.3