diff options
author | Mike Buland <eichlan@xagasoft.com> | 2023-08-08 16:33:38 -0700 |
---|---|---|
committer | Mike Buland <eichlan@xagasoft.com> | 2023-08-08 16:33:38 -0700 |
commit | c7e1277ecaf40c6d8ee945418a306f5b15189b97 (patch) | |
tree | 05a04473ffe90a76a4e7dd170c221141fea87b7e /src/stable/unitsuite.cpp | |
parent | 7c36f58654f1b238d1b416927c9485a151216b1b (diff) | |
download | libbu++-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 'src/stable/unitsuite.cpp')
-rw-r--r-- | src/stable/unitsuite.cpp | 337 |
1 files changed, 255 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 | ||
19 | Bu::UnitSuite::UnitSuite() : | 20 | Bu::UnitSuite::UnitSuite() : |
20 | iOptions( 0 ), | 21 | iOptions( 0 ), |
21 | iNameWidth( 0 ) | 22 | pReport( NULL ) |
22 | { | 23 | { |
23 | } | 24 | } |
24 | 25 | ||
25 | Bu::UnitSuite::UnitSuite( int iOptions ) : | 26 | Bu::UnitSuite::UnitSuite( int iOptions ) : |
26 | iOptions( iOptions ), | 27 | iOptions( iOptions ) |
27 | iNameWidth( 0 ) | ||
28 | { | 28 | { |
29 | } | 29 | } |
30 | 30 | ||
31 | Bu::UnitSuite::~UnitSuite() | 31 | Bu::UnitSuite::~UnitSuite() |
32 | { | 32 | { |
33 | delete pReport; | ||
33 | } | 34 | } |
34 | 35 | ||
35 | int Bu::UnitSuite::run( int argc, char *argv[] ) | 36 | int 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 | ||
198 | void Bu::UnitSuite::setName( const String &sName ) | 154 | void 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 | ||
159 | Bu::String Bu::UnitSuite::getName() const | ||
160 | { | ||
161 | return sSuiteName; | ||
162 | } | ||
163 | |||
164 | void Bu::UnitSuite::cleanup() | ||
165 | { | ||
166 | } | ||
167 | |||
203 | void Bu::UnitSuite::dispProgress() | 168 | void 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 | ||
219 | int Bu::UnitSuite::onPrintName( StrArray ) | ||
220 | { | ||
221 | Bu::println("%1").arg( sSuiteName ); | ||
222 | exit( 0 ); | ||
223 | return 0; | ||
224 | } | ||
225 | |||
254 | int Bu::UnitSuite::onAddTest( StrArray aParam ) | 226 | int 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 | |||
250 | Bu::UnitSuite::Report::Report() | ||
251 | { | ||
252 | } | ||
253 | |||
254 | Bu::UnitSuite::Report::~Report() | ||
255 | { | ||
256 | } | ||
257 | |||
258 | ///////// | ||
259 | // Bu::UnitSuite::ReportConsole | ||
260 | //// | ||
261 | |||
262 | Bu::UnitSuite::ReportConsole::ReportConsole() : | ||
263 | iTestCount( 0 ), | ||
264 | iNameWidth( 0 ), | ||
265 | iEPass( 0 ), | ||
266 | iEFail( 0 ), | ||
267 | iUPass( 0 ), | ||
268 | iUFail( 0 ) | ||
269 | { | ||
270 | } | ||
271 | |||
272 | Bu::UnitSuite::ReportConsole::~ReportConsole() | ||
273 | { | ||
274 | } | ||
275 | |||
276 | void 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 | |||
287 | void Bu::UnitSuite::ReportConsole::testStarting( const TestInfo &rTest ) | ||
288 | { | ||
289 | sio << Fmt( iNameWidth+3, Fmt::Left ).fill('.') << rTest.sName | ||
290 | << sio.flush; | ||
291 | } | ||
292 | |||
293 | void 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 | |||
300 | void 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 | |||
316 | void 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 | |||
342 | void 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 | |||
360 | void 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 | |||
384 | Bu::UnitSuite::ReportJson::ReportJson() : | ||
385 | pReport( new Bu::Json( Bu::Json::Object ) ) | ||
386 | { | ||
387 | } | ||
388 | |||
389 | Bu::UnitSuite::ReportJson::~ReportJson() | ||
390 | { | ||
391 | delete pReport; | ||
392 | } | ||
393 | |||
394 | void 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 | |||
402 | void Bu::UnitSuite::ReportJson::testStarting( const TestInfo & ) | ||
403 | { | ||
404 | } | ||
405 | |||
406 | void Bu::UnitSuite::ReportJson::updateProgress( int, int ) | ||
407 | { | ||
408 | } | ||
409 | |||
410 | void 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 | |||
418 | void 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 | |||
431 | void 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 | |||
442 | void Bu::UnitSuite::ReportJson::suiteEnded() | ||
443 | { | ||
444 | Bu::println("%1").arg( pReport->toString() ); | ||
445 | } | ||
446 | |||