aboutsummaryrefslogtreecommitdiff
path: root/src/stable
diff options
context:
space:
mode:
authorMike Buland <eichlan@xagasoft.com>2023-08-08 16:33:38 -0700
committerMike Buland <eichlan@xagasoft.com>2023-08-08 16:33:38 -0700
commitc7e1277ecaf40c6d8ee945418a306f5b15189b97 (patch)
tree05a04473ffe90a76a4e7dd170c221141fea87b7e /src/stable
parent7c36f58654f1b238d1b416927c9485a151216b1b (diff)
downloadlibbu++-c7e1277ecaf40c6d8ee945418a306f5b15189b97.tar.gz
libbu++-c7e1277ecaf40c6d8ee945418a306f5b15189b97.tar.bz2
libbu++-c7e1277ecaf40c6d8ee945418a306f5b15189b97.tar.xz
libbu++-c7e1277ecaf40c6d8ee945418a306f5b15189b97.zip
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!
Diffstat (limited to '')
-rw-r--r--src/stable/unitsuite.cpp337
-rw-r--r--src/stable/unitsuite.h73
2 files changed, 328 insertions, 82 deletions
diff --git a/src/stable/unitsuite.cpp b/src/stable/unitsuite.cpp
index a38c77a..b2544f2 100644
--- a/src/stable/unitsuite.cpp
+++ b/src/stable/unitsuite.cpp
@@ -9,6 +9,7 @@
9#include "bu/file.h" 9#include "bu/file.h"
10#include "bu/sio.h" 10#include "bu/sio.h"
11#include "bu/optparser.h" 11#include "bu/optparser.h"
12#include "bu/json.h"
12#include <stdlib.h> 13#include <stdlib.h>
13#include <time.h> 14#include <time.h>
14 15
@@ -18,28 +19,33 @@ using namespace Bu;
18 19
19Bu::UnitSuite::UnitSuite() : 20Bu::UnitSuite::UnitSuite() :
20 iOptions( 0 ), 21 iOptions( 0 ),
21 iNameWidth( 0 ) 22 pReport( NULL )
22{ 23{
23} 24}
24 25
25Bu::UnitSuite::UnitSuite( int iOptions ) : 26Bu::UnitSuite::UnitSuite( int iOptions ) :
26 iOptions( iOptions ), 27 iOptions( iOptions )
27 iNameWidth( 0 )
28{ 28{
29} 29}
30 30
31Bu::UnitSuite::~UnitSuite() 31Bu::UnitSuite::~UnitSuite()
32{ 32{
33 delete pReport;
33} 34}
34 35
35int Bu::UnitSuite::run( int argc, char *argv[] ) 36int Bu::UnitSuite::run( int argc, char *argv[] )
36{ 37{
37 bool bCleanup = true; 38 bool bCleanup = true;
39 bool bInterop = false;
38 OptParser p; 40 OptParser p;
39 p.addOption( Bu::slot( this, &Bu::UnitSuite::onListCases ), 'l', "list", 41 p.addOption( Bu::slot( this, &Bu::UnitSuite::onListCases ), 'l', "list",
40 "List available test cases." ); 42 "List available test cases." );
43 p.addOption( Bu::slot( this, &Bu::UnitSuite::onPrintName ), "print-name",
44 "Print the internal name of this test suite.");
41 p.addOption( bCleanup, "no-cleanup", "Don't erase temp files."); 45 p.addOption( bCleanup, "no-cleanup", "Don't erase temp files.");
42 p.setOverride( "no-cleanup", false ); 46 p.setOverride( "no-cleanup", false );
47 p.addOption( bInterop, "interop", "Output machine parsable json.");
48 p.setOverride( "interop", true );
43 p.setNonOption( Bu::slot( this, &Bu::UnitSuite::onAddTest ) ); 49 p.setNonOption( Bu::slot( this, &Bu::UnitSuite::onAddTest ) );
44 p.addHelpOption(); 50 p.addHelpOption();
45 p.parse( argc, argv ); 51 p.parse( argc, argv );
@@ -56,108 +62,58 @@ int Bu::UnitSuite::run( int argc, char *argv[] )
56 lTests = lSub; 62 lTests = lSub;
57 } 63 }
58 64
59 int iEPass = 0; 65 if( bInterop )
60 int iEFail = 0; 66 pReport = new ReportJson();
61 int iUPass = 0; 67 else
62 int iUFail = 0; 68 pReport = new ReportConsole();
69 pReport->suiteStarting( *this, lTests );
70
63 for( TestList::iterator i = lTests.begin(); i != lTests.end(); i++ ) 71 for( TestList::iterator i = lTests.begin(); i != lTests.end(); i++ )
64 { 72 {
65 sio << Fmt( iNameWidth+3, Fmt::Left ).fill('.') << i->sName
66 << sio.flush;
67 try 73 try
68 { 74 {
69 iStepCount = -1; 75 iStepCount = -1;
70 iProgress = 0; 76 iProgress = 0;
77 if( pReport )
78 pReport->testStarting( *i );
71 (this->*(i->fTest))(); 79 (this->*(i->fTest))();
72 switch( i->eExpect ) 80 if( pReport )
73 { 81 pReport->testEnded( *i );
74 case expectPass:
75 sio << "pass." << sio.nl;
76 iEPass++;
77 break;
78
79 case expectFail:
80 sio << "unexpected pass." << sio.nl;
81 iUPass++;
82 break;
83 }
84 } 82 }
85 catch( Failed &e ) 83 catch( Failed &e )
86 { 84 {
87 switch( i->eExpect ) 85 if( pReport )
88 { 86 pReport->testEnded( *i, e );
89 case expectPass:
90 sio << "unexpected ";
91 iUFail++;
92 break;
93
94 case expectFail:
95 sio << "expected ";
96 iEFail++;
97 break;
98 }
99 if( e.bFile )
100 {
101 sio << "fail in unitTest(" << e.str << "). (" << e.sFile
102 << ":" << e.nLine << ")." << sio.nl;
103 }
104 else
105 {
106 sio << "fail in unitTest(" << e.str << ")." << sio.nl;
107 }
108 87
109 if( (iOptions & optStopOnError) ) 88 if( (iOptions & optStopOnError) )
89 {
110 return 0; 90 return 0;
91 }
111 } 92 }
112 catch( std::exception &e ) 93 catch( std::exception &e )
113 { 94 {
114 switch( i->eExpect ) 95 if( pReport )
115 { 96 pReport->testException( *i, e );
116 case expectPass:
117 sio << "unexpected ";
118 iUFail++;
119 break;
120
121 case expectFail:
122 sio << "expected ";
123 iEFail++;
124 break;
125 }
126 sio << "fail with unknown exception. what: " << e.what() << sio.nl;
127 97
128 if( (iOptions & optStopOnError) ) 98 if( (iOptions & optStopOnError) )
99 {
129 return 0; 100 return 0;
101 }
130 } 102 }
131 catch( ... ) 103 catch( ... )
132 { 104 {
133 switch( i->eExpect )
134 {
135 case expectPass:
136 sio << "unexpected ";
137 iUFail++;
138 break;
139
140 case expectFail:
141 sio << "expected ";
142 iEFail++;
143 break;
144 }
145 sio << "fail with external exception." << sio.nl; 105 sio << "fail with external exception." << sio.nl;
106 return -1;
146 107
147 if( (iOptions & optStopOnError) ) 108 if( (iOptions & optStopOnError) )
109 {
148 return 0; 110 return 0;
111 }
149 } 112 }
150 } 113 }
151 114
152 sio << sio.nl 115 if( pReport )
153 << "Report:" << sio.nl 116 pReport->suiteEnded();
154 << "\tTotal tests run: " << lTests.getSize() << sio.nl
155 << "\tExpected passes: " << iEPass << sio.nl
156 << "\tExpected failures: " << iEFail << sio.nl
157 << "\tUnexpected passes: " << iUPass << sio.nl
158 << "\tUnexpected failures: " << iUFail << sio.nl << sio.nl;
159 if( iUPass == 0 && iUFail == 0 )
160 sio << "\tNothing unexpected." << sio.nl << sio.nl;
161 117
162 if( bCleanup ) 118 if( bCleanup )
163 { 119 {
@@ -165,6 +121,8 @@ int Bu::UnitSuite::run( int argc, char *argv[] )
165 { 121 {
166 unlink( (*i).getStr() ); 122 unlink( (*i).getStr() );
167 } 123 }
124
125 cleanup();
168 } 126 }
169 127
170 return 0; 128 return 0;
@@ -191,8 +149,6 @@ void Bu::UnitSuite::add( Test fTest, const Bu::String &sName, Expect e )
191 } 149 }
192 ti.fTest = fTest; 150 ti.fTest = fTest;
193 lTests.append( ti ); 151 lTests.append( ti );
194 if( iNameWidth < ti.sName.getSize() )
195 iNameWidth = ti.sName.getSize();
196} 152}
197 153
198void Bu::UnitSuite::setName( const String &sName ) 154void Bu::UnitSuite::setName( const String &sName )
@@ -200,12 +156,21 @@ void Bu::UnitSuite::setName( const String &sName )
200 sSuiteName = sName; 156 sSuiteName = sName;
201} 157}
202 158
159Bu::String Bu::UnitSuite::getName() const
160{
161 return sSuiteName;
162}
163
164void Bu::UnitSuite::cleanup()
165{
166}
167
203void Bu::UnitSuite::dispProgress() 168void Bu::UnitSuite::dispProgress()
204{ 169{
205 if( tLastUpdate == time( NULL ) ) 170 if( tLastUpdate == time( NULL ) )
206 return; 171 return;
207 sio << Fmt(3) << (iProgress*100/iStepCount) << "%" << "\b\b\b\b" 172 if( pReport )
208 << sio.flush; 173 pReport->updateProgress( iProgress, iStepCount );
209 tLastUpdate = time( NULL ); 174 tLastUpdate = time( NULL );
210} 175}
211 176
@@ -251,6 +216,13 @@ int Bu::UnitSuite::onListCases( StrArray )
251 return 0; 216 return 0;
252} 217}
253 218
219int Bu::UnitSuite::onPrintName( StrArray )
220{
221 Bu::println("%1").arg( sSuiteName );
222 exit( 0 );
223 return 0;
224}
225
254int Bu::UnitSuite::onAddTest( StrArray aParam ) 226int Bu::UnitSuite::onAddTest( StrArray aParam )
255{ 227{
256 hSelTests.insert( aParam[0], true ); 228 hSelTests.insert( aParam[0], true );
@@ -262,12 +234,213 @@ Bu::Formatter &Bu::operator<<( Bu::Formatter &f, const Bu::UnitSuite::Expect &e
262 switch( e ) 234 switch( e )
263 { 235 {
264 case Bu::UnitSuite::expectPass: 236 case Bu::UnitSuite::expectPass:
265 return f << "expect pass"; 237 return f << "pass";
266 238
267 case Bu::UnitSuite::expectFail: 239 case Bu::UnitSuite::expectFail:
268 return f << "expect fail"; 240 return f << "fail";
269 } 241 }
270 242
271 return f << "**error**"; 243 return f << "**error**";
272} 244}
273 245
246/////////
247// Bu::UnitSuite::Report
248////
249
250Bu::UnitSuite::Report::Report()
251{
252}
253
254Bu::UnitSuite::Report::~Report()
255{
256}
257
258/////////
259// Bu::UnitSuite::ReportConsole
260////
261
262Bu::UnitSuite::ReportConsole::ReportConsole() :
263 iTestCount( 0 ),
264 iNameWidth( 0 ),
265 iEPass( 0 ),
266 iEFail( 0 ),
267 iUPass( 0 ),
268 iUFail( 0 )
269{
270}
271
272Bu::UnitSuite::ReportConsole::~ReportConsole()
273{
274}
275
276void Bu::UnitSuite::ReportConsole::suiteStarting( const UnitSuite & /*rSuite*/,
277 const TestList &lTests )
278{
279 iTestCount = lTests.getSize();
280 for( TestList::const_iterator i = lTests.begin(); i; i++ )
281 {
282 if( iNameWidth < i->sName.getSize() )
283 iNameWidth = i->sName.getSize();
284 }
285}
286
287void Bu::UnitSuite::ReportConsole::testStarting( const TestInfo &rTest )
288{
289 sio << Fmt( iNameWidth+3, Fmt::Left ).fill('.') << rTest.sName
290 << sio.flush;
291}
292
293void Bu::UnitSuite::ReportConsole::updateProgress( int iProgress,
294 int iStepCount )
295{
296 sio << Fmt(3) << (iProgress*100/iStepCount) << "%" << "\b\b\b\b"
297 << sio.flush;
298}
299
300void Bu::UnitSuite::ReportConsole::testEnded( const TestInfo &rTest )
301{
302 switch( rTest.eExpect )
303 {
304 case expectPass:
305 sio << "pass." << sio.nl;
306 iEPass++;
307 break;
308
309 case expectFail:
310 sio << "unexpected pass." << sio.nl;
311 iUPass++;
312 break;
313 }
314}
315
316void Bu::UnitSuite::ReportConsole::testEnded( const TestInfo &rTest,
317 const Bu::UnitSuite::Failed &rFail )
318{
319 switch( rTest.eExpect )
320 {
321 case expectPass:
322 sio << "unexpected ";
323 iUFail++;
324 break;
325
326 case expectFail:
327 sio << "expected ";
328 iEFail++;
329 break;
330 }
331 if( rFail.bFile )
332 {
333 sio << "fail in unitTest(" << rFail.str << "). (" << rFail.sFile
334 << ":" << rFail.nLine << ")." << sio.nl;
335 }
336 else
337 {
338 sio << "fail in unitTest(" << rFail.str << ")." << sio.nl;
339 }
340}
341
342void Bu::UnitSuite::ReportConsole::testException( const TestInfo &rTest,
343 std::exception &e )
344{
345 switch( rTest.eExpect )
346 {
347 case expectPass:
348 sio << "unexpected ";
349 iUFail++;
350 break;
351
352 case expectFail:
353 sio << "expected ";
354 iEFail++;
355 break;
356 }
357 sio << "fail with unknown exception. what: " << e.what() << sio.nl;
358}
359
360void Bu::UnitSuite::ReportConsole::suiteEnded()
361{
362 sio << sio.nl
363 << "Report:" << sio.nl
364 << "\tTotal tests run: " << iTestCount << sio.nl;
365
366 if( iEPass > 0 )
367 sio << "\tExpected passes: " << iEPass << sio.nl;
368 if( iEFail > 0 )
369 sio << "\tExpected failures: " << iEFail << sio.nl;
370 if( iUPass > 0 )
371 sio << "\tUnexpected passes: " << iUPass << sio.nl;
372 if( iUFail > 0 )
373 sio << "\tUnexpected failures: " << iUFail << sio.nl;
374 sio << sio.nl;
375 if( iUPass == 0 && iUFail == 0 )
376 sio << "\tNothing unexpected." << sio.nl << sio.nl;
377}
378
379
380/////////
381// Bu::UnitSuite::Report
382////
383
384Bu::UnitSuite::ReportJson::ReportJson() :
385 pReport( new Bu::Json( Bu::Json::Object ) )
386{
387}
388
389Bu::UnitSuite::ReportJson::~ReportJson()
390{
391 delete pReport;
392}
393
394void Bu::UnitSuite::ReportJson::suiteStarting( const UnitSuite &rSuite,
395 const TestList & /*lTests*/ )
396{
397 pReport->insert("type", "report");
398 pReport->insert("suite", rSuite.getName() );
399 pReport->insertArray("tests");
400}
401
402void Bu::UnitSuite::ReportJson::testStarting( const TestInfo & )
403{
404}
405
406void Bu::UnitSuite::ReportJson::updateProgress( int, int )
407{
408}
409
410void Bu::UnitSuite::ReportJson::testEnded( const TestInfo &rTest )
411{
412 Bu::Json &rOb = (*pReport)["tests"].appendObject();
413 rOb.insert("name", rTest.sName );
414 rOb.insert("expected", Bu::String("%1").arg( rTest.eExpect ) );
415 rOb.insert("result", "pass");
416}
417
418void Bu::UnitSuite::ReportJson::testEnded( const TestInfo &rTest,
419 const Bu::UnitSuite::Failed &rFail )
420{
421 Bu::Json &rOb = (*pReport)["tests"].appendObject();
422 rOb.insert("name", rTest.sName );
423 rOb.insert("expected", Bu::String("%1").arg( rTest.eExpect ) );
424 rOb.insert("result", "fail");
425 Bu::Json &rFailOb = rOb.insertObject("fail");
426 rFailOb.insert("action", rFail.str );
427 rFailOb.insert("file", rFail.sFile );
428 rFailOb.insert("line", (double)rFail.nLine );
429}
430
431void Bu::UnitSuite::ReportJson::testException( const TestInfo &rTest,
432 std::exception &e )
433{
434 Bu::Json &rOb = (*pReport)["tests"].appendObject();
435 rOb.insert("name", rTest.sName );
436 rOb.insert("expected", Bu::String("%1").arg( rTest.eExpect ) );
437 rOb.insert("result", "fail");
438 Bu::Json &rFail = rOb.insertObject("fail");
439 rFail.insert("what", e.what() );
440}
441
442void Bu::UnitSuite::ReportJson::suiteEnded()
443{
444 Bu::println("%1").arg( pReport->toString() );
445}
446
diff --git a/src/stable/unitsuite.h b/src/stable/unitsuite.h
index ea5e389..b1e2953 100644
--- a/src/stable/unitsuite.h
+++ b/src/stable/unitsuite.h
@@ -17,6 +17,7 @@
17 17
18namespace Bu 18namespace Bu
19{ 19{
20 class Json;
20 /** 21 /**
21 * Provides a unit testing framework. This is pretty easy to use, probably 22 * Provides a unit testing framework. This is pretty easy to use, probably
22 * the best way to get started is to use ch to generate a template, or use 23 * the best way to get started is to use ch to generate a template, or use
@@ -96,6 +97,8 @@ namespace Bu
96 protected: 97 protected:
97 void add( Test fTest, const Bu::String &sName, Expect e=expectPass ); 98 void add( Test fTest, const Bu::String &sName, Expect e=expectPass );
98 void setName( const String &sName ); 99 void setName( const String &sName );
100 String getName() const;
101 virtual void cleanup();
99 102
100 void dispProgress(); 103 void dispProgress();
101 void setStepCount( int iSteps ); 104 void setStepCount( int iSteps );
@@ -104,6 +107,7 @@ namespace Bu
104 107
105 private: 108 private:
106 int onListCases( Bu::Array<Bu::String> aParam ); 109 int onListCases( Bu::Array<Bu::String> aParam );
110 int onPrintName( Bu::Array<Bu::String> aParam );
107 int onAddTest( Bu::Array<Bu::String> aParam ); 111 int onAddTest( Bu::Array<Bu::String> aParam );
108 112
109 private: 113 private:
@@ -128,6 +132,75 @@ namespace Bu
128 time_t tLastUpdate; 132 time_t tLastUpdate;
129 133
130 Bu::Hash<Bu::String, bool> hSelTests; 134 Bu::Hash<Bu::String, bool> hSelTests;
135
136 public:
137 class Report
138 {
139 public:
140 Report();
141 virtual ~Report();
142
143 virtual void suiteStarting( const UnitSuite &rSuite,
144 const TestList &lTests )=0;
145 virtual void testStarting( const TestInfo &rTest )=0;
146 virtual void updateProgress( int iProgress, int iStepCount )=0;
147 virtual void testEnded( const TestInfo &rTest )=0;
148 virtual void testEnded( const TestInfo &rTest,
149 const Failed &rFail )=0;
150 virtual void testException( const TestInfo &rTest,
151 std::exception &e )=0;
152 virtual void suiteEnded()=0;
153 };
154
155 class ReportConsole : public Report
156 {
157 public:
158 ReportConsole();
159 virtual ~ReportConsole();
160
161 virtual void suiteStarting( const UnitSuite &rSuite,
162 const TestList &lTests );
163 virtual void testStarting( const TestInfo &rTest );
164 virtual void updateProgress( int iProgress, int iStepCount );
165 virtual void testEnded( const TestInfo &rTest );
166 virtual void testEnded( const TestInfo &rTest,
167 const Failed &rFail );
168 virtual void testException( const TestInfo &rTest,
169 std::exception &e );
170 virtual void suiteEnded();
171
172 private:
173 int iTestCount;
174 int iNameWidth;
175 int iEPass;
176 int iEFail;
177 int iUPass;
178 int iUFail;
179 };
180
181 class ReportJson : public Report
182 {
183 public:
184 ReportJson();
185 virtual ~ReportJson();
186
187 virtual void suiteStarting( const UnitSuite &rSuite,
188 const TestList &lTests );
189 virtual void testStarting( const TestInfo &rTest );
190 virtual void updateProgress( int iProgress, int iStepCount );
191 virtual void testEnded( const TestInfo &rTest );
192 virtual void testEnded( const TestInfo &rTest,
193 const Failed &rFail );
194 virtual void testException( const TestInfo &rTest,
195 std::exception &e );
196 virtual void suiteEnded();
197
198 private:
199 class Bu::Json *pReport;
200 };
201
202 private:
203 Report *pReport;
131 }; 204 };
132 205
133Bu::Formatter &operator<<( Bu::Formatter &f, const Bu::UnitSuite::Expect &e ); 206Bu::Formatter &operator<<( Bu::Formatter &f, const Bu::UnitSuite::Expect &e );