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 | |
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 '')
-rw-r--r-- | src/experimental/xmlreader.cpp | 2 | ||||
-rw-r--r-- | src/stable/unitsuite.cpp | 337 | ||||
-rw-r--r-- | src/stable/unitsuite.h | 73 | ||||
-rw-r--r-- | src/tools/mkunit.cpp | 34 | ||||
-rw-r--r-- | src/tools/rununits.cpp | 186 | ||||
-rw-r--r-- | src/unit/file.unit | 5 | ||||
-rw-r--r-- | src/unit/xml.unit | 6 | ||||
-rw-r--r-- | src/unstable/blob.cpp | 2 |
8 files changed, 552 insertions, 93 deletions
diff --git a/src/experimental/xmlreader.cpp b/src/experimental/xmlreader.cpp index e1d9ca8..107b76d 100644 --- a/src/experimental/xmlreader.cpp +++ b/src/experimental/xmlreader.cpp | |||
@@ -48,7 +48,7 @@ void Bu::XmlReader::cleanupBuffer( int iUsed ) | |||
48 | } | 48 | } |
49 | } | 49 | } |
50 | 50 | ||
51 | printf("--Deleting %d bytes from front of buffer.\n", iUsed ); | 51 | //printf("--Deleting %d bytes from front of buffer.\n", iUsed ); |
52 | sBuf.trimFront( iUsed ); | 52 | sBuf.trimFront( iUsed ); |
53 | } | 53 | } |
54 | 54 | ||
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 | |||
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 | ||
18 | namespace Bu | 18 | namespace 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 | ||
133 | Bu::Formatter &operator<<( Bu::Formatter &f, const Bu::UnitSuite::Expect &e ); | 206 | Bu::Formatter &operator<<( Bu::Formatter &f, const Bu::UnitSuite::Expect &e ); |
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 | |||
42 | tokTest, | 42 | tokTest, |
43 | tokChar, | 43 | tokChar, |
44 | tokBlock, | 44 | tokBlock, |
45 | tokCleanup, | ||
45 | tokEof | 46 | tokEof |
46 | }; | 47 | }; |
47 | 48 | ||
@@ -54,6 +55,7 @@ Bu::Formatter &operator<<( Bu::Formatter &f, TokType t ) | |||
54 | case tokTest: return f << "tokTest"; | 55 | case tokTest: return f << "tokTest"; |
55 | case tokChar: return f << "tokChar"; | 56 | case tokChar: return f << "tokChar"; |
56 | case tokBlock: return f << "tokBlock"; | 57 | case tokBlock: return f << "tokBlock"; |
58 | case tokCleanup: return f << "tokCleanup"; | ||
57 | case tokEof: return f << "tokEof"; | 59 | case tokEof: return f << "tokEof"; |
58 | } | 60 | } |
59 | 61 | ||
@@ -193,6 +195,8 @@ public: | |||
193 | { | 195 | { |
194 | if( sTok == "test" ) | 196 | if( sTok == "test" ) |
195 | return tokTest; | 197 | return tokTest; |
198 | else if( sTok == "cleanup" ) | ||
199 | return tokCleanup; | ||
196 | else | 200 | else |
197 | { | 201 | { |
198 | v = sTok; | 202 | v = sTok; |
@@ -361,6 +365,15 @@ public: | |||
361 | s.lTest.append( t ); | 365 | s.lTest.append( t ); |
362 | } | 366 | } |
363 | break; | 367 | break; |
368 | |||
369 | case tokCleanup: | ||
370 | { | ||
371 | if( nextToken( v, sWs, iL, iC ) != tokBlock ) | ||
372 | throw Bu::ExceptionBase("%d:%d: Expected " | ||
373 | "{...} block.", | ||
374 | iL, iC ); | ||
375 | } | ||
376 | break; | ||
364 | 377 | ||
365 | case tokChar: | 378 | case tokChar: |
366 | if( v.get<char>() == '}' ) | 379 | if( v.get<char>() == '}' ) |
@@ -438,7 +451,7 @@ public: | |||
438 | for( TestList::iterator i = s.lTest.begin(); i; i++ ) | 451 | for( TestList::iterator i = s.lTest.begin(); i; i++ ) |
439 | { | 452 | { |
440 | f << "\t\tadd( static_cast<Bu::UnitSuite::Test>(" | 453 | f << "\t\tadd( static_cast<Bu::UnitSuite::Test>(" |
441 | "&" << sClass << "::" << (*i).sName << "), \"" | 454 | "&" << sClass << "::_test_" << (*i).sName << "), \"" |
442 | << (*i).sName << "\", Bu::UnitSuite::" | 455 | << (*i).sName << "\", Bu::UnitSuite::" |
443 | "expectPass );" << f.nl; | 456 | "expectPass );" << f.nl; |
444 | } | 457 | } |
@@ -481,9 +494,24 @@ public: | |||
481 | "{...} block.", | 494 | "{...} block.", |
482 | iL, iC ); | 495 | iL, iC ); |
483 | 496 | ||
484 | f << "\tvoid " << t.sName << "()" | 497 | f << "void _test_" << t.sName << "()" |
498 | << f.nl << "#line " << iL | ||
499 | << " \"" << sIn << "\"" << f.nl << " " | ||
500 | << v << f.nl; | ||
501 | } | ||
502 | break; | ||
503 | |||
504 | case tokCleanup: | ||
505 | { | ||
506 | fOut.write( sWs ); | ||
507 | if( nextToken( v, sWs, iL, iC ) != tokBlock ) | ||
508 | throw Bu::ExceptionBase("%d:%d: Expected " | ||
509 | "{...} block.", | ||
510 | iL, iC ); | ||
511 | |||
512 | f << "void cleanup()" | ||
485 | << f.nl << "#line " << iL | 513 | << f.nl << "#line " << iL |
486 | << " \"" << sIn << "\"" << f.nl | 514 | << " \"" << sIn << "\"" << f.nl << " " |
487 | << v << f.nl; | 515 | << v << f.nl; |
488 | } | 516 | } |
489 | break; | 517 | 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 @@ | |||
1 | #include <bu/sio.h> | ||
2 | #include <bu/json.h> | ||
3 | #include <bu/process.h> | ||
4 | #include <bu/optparser.h> | ||
5 | #include <bu/blob.h> | ||
6 | #include <bu/blobbuilder.h> | ||
7 | |||
8 | #include <sys/types.h> | ||
9 | #include <dirent.h> | ||
10 | |||
11 | Bu::Blob getSuiteName( const Bu::Blob &bSuiteExec ) | ||
12 | { | ||
13 | Bu::Process proc( | ||
14 | Bu::Process::StdOut, | ||
15 | bSuiteExec.getData(), | ||
16 | bSuiteExec.getData(), | ||
17 | "--print-name", | ||
18 | NULL | ||
19 | ); | ||
20 | |||
21 | Bu::String sResult = proc.readAll().trimWhitespace(); | ||
22 | Bu::Blob bRet( sResult.getStr(), sResult.getSize() ); | ||
23 | return bRet; | ||
24 | } | ||
25 | |||
26 | Bu::Json *runSuite( const Bu::Blob &bSuiteExec ) | ||
27 | { | ||
28 | Bu::Process proc( | ||
29 | Bu::Process::StdOut, | ||
30 | bSuiteExec.getData(), | ||
31 | bSuiteExec.getData(), | ||
32 | "--interop", | ||
33 | NULL | ||
34 | ); | ||
35 | Bu::BlobBuilder bbReport; | ||
36 | while( proc.isRunning() ) | ||
37 | { | ||
38 | int iRead = 0; | ||
39 | char dRead[4096]; | ||
40 | iRead = proc.read( dRead, 4096 ); | ||
41 | if( iRead > 0 ) | ||
42 | { | ||
43 | bbReport.append( dRead, iRead ); | ||
44 | } | ||
45 | } | ||
46 | Bu::Json *pReport = new Bu::Json(); | ||
47 | pReport->parse( bbReport.getBlob() ); | ||
48 | return pReport; | ||
49 | } | ||
50 | |||
51 | typedef Bu::List<Bu::Blob> BlobList; | ||
52 | |||
53 | BlobList getSuitePaths( const Bu::Blob &bDir ) | ||
54 | { | ||
55 | BlobList lPaths; | ||
56 | DIR *dir = opendir( bDir.getData() ); | ||
57 | if( dir == NULL ) | ||
58 | { | ||
59 | Bu::println("Could not open directory: %1").arg( bDir ); | ||
60 | return lPaths; | ||
61 | } | ||
62 | struct dirent *de = NULL; | ||
63 | while( (de = readdir( dir )) != NULL ) | ||
64 | { | ||
65 | if( de->d_type != DT_REG ) | ||
66 | continue; | ||
67 | |||
68 | Bu::BlobBuilder bbPath; | ||
69 | bbPath.append( bDir ); | ||
70 | bbPath.append("/"); | ||
71 | bbPath.append( de->d_name ); | ||
72 | Bu::Blob bPath = bbPath.getBlob(); | ||
73 | if( access( bPath.getData(), X_OK ) != 0 ) | ||
74 | continue; | ||
75 | |||
76 | lPaths.append( bPath ); | ||
77 | } | ||
78 | closedir( dir ); | ||
79 | |||
80 | return lPaths; | ||
81 | } | ||
82 | |||
83 | int main( int /*argc*/, char * /*argv*/[] ) | ||
84 | { | ||
85 | Bu::Blob bDir("unit"); | ||
86 | BlobList lPaths = getSuitePaths( bDir ); | ||
87 | |||
88 | int iNumLen = Bu::String("%1").arg( lPaths.getSize() ).end().getSize(); | ||
89 | int iMaxSuiteName = 0; | ||
90 | Bu::Hash<Bu::Blob, Bu::Blob> hName; | ||
91 | for( BlobList::iterator i = lPaths.begin(); i; i++ ) | ||
92 | { | ||
93 | Bu::Blob bSuiteName = getSuiteName( *i ); | ||
94 | if( iMaxSuiteName < bSuiteName.getSize() ) | ||
95 | iMaxSuiteName = bSuiteName.getSize(); | ||
96 | hName.insert( *i, bSuiteName ); | ||
97 | } | ||
98 | |||
99 | Bu::List<Bu::Json *> lReport; | ||
100 | |||
101 | int iTest = 0; | ||
102 | for( BlobList::iterator i = lPaths.begin(); i; i++ ) | ||
103 | { | ||
104 | iTest++; | ||
105 | Bu::Blob bSuiteName = hName.get( *i ); | ||
106 | Bu::print("\rRunning test suite [%1/%2]: %3") | ||
107 | .arg( iTest, Bu::Fmt().width( iNumLen ).right() ) | ||
108 | .arg( lPaths.getSize(), Bu::Fmt().width( iNumLen ) ) | ||
109 | .arg( bSuiteName, Bu::Fmt().width( iMaxSuiteName ).fill(' ').left() ); | ||
110 | Bu::sio << Bu::sio.flush; | ||
111 | |||
112 | Bu::Json *pReport = runSuite( (*i) ); | ||
113 | int iUnexpected = 0; | ||
114 | int iTotal = 0; | ||
115 | iTotal = (*pReport)["tests"].getSize(); | ||
116 | for( int j = 0; j < iTotal; j++ ) | ||
117 | { | ||
118 | if( !((*pReport)["tests"][j]["expected"].getString() == | ||
119 | (*pReport)["tests"][j]["result"].getString()) ) | ||
120 | { | ||
121 | iUnexpected++; | ||
122 | } | ||
123 | } | ||
124 | if( iUnexpected == 0 ) | ||
125 | { | ||
126 | delete pReport; | ||
127 | } | ||
128 | else | ||
129 | { | ||
130 | lReport.append( pReport ); | ||
131 | } | ||
132 | } | ||
133 | Bu::println("\rCompleted %1 unit test suites.%2") | ||
134 | .arg( lPaths.getSize(), Bu::Fmt().width( iNumLen ) ) | ||
135 | .arg( Bu::Blob(""), Bu::Fmt().width( iMaxSuiteName ).fill(' ').left() ); | ||
136 | |||
137 | if( lReport.getSize() == 0 ) | ||
138 | { | ||
139 | Bu::println("\nNothing unexpected in unit tests."); | ||
140 | } | ||
141 | else | ||
142 | { | ||
143 | for( Bu::List<Bu::Json *>::iterator i = lReport.begin(); i; i++ ) | ||
144 | { | ||
145 | Bu::println("\nUnexpected results in: %1") | ||
146 | .arg( (**i)["suite"].getString().get() ); | ||
147 | |||
148 | for( Bu::Json::iterator iTest = (**i)["tests"].begin(); | ||
149 | iTest; iTest++ ) | ||
150 | { | ||
151 | if( (**iTest)["expected"].getString() == | ||
152 | (**iTest)["result"].getString() ) | ||
153 | { | ||
154 | continue; | ||
155 | } | ||
156 | |||
157 | Bu::println(" %1: unexpected %2") | ||
158 | .arg( (**iTest)["name"].getString().get() ) | ||
159 | .arg( (**iTest)["result"].getString().get() ); | ||
160 | if( (**iTest).has("fail") ) | ||
161 | { | ||
162 | if( (**iTest)["fail"].has("action") ) | ||
163 | { | ||
164 | Bu::println(" unitTest( %1 );") | ||
165 | .arg( (**iTest)["fail"]["action"].getString().get() ); | ||
166 | Bu::println(" at %1:%2") | ||
167 | .arg( (**iTest)["fail"]["file"].getString().get() ) | ||
168 | .arg( (int)(**iTest)["fail"]["line"].getNumber() ); | ||
169 | } | ||
170 | else if( (**iTest)["fail"].has("what") ) | ||
171 | { | ||
172 | Bu::println(" Unexpected exception: %1") | ||
173 | .arg( (**iTest)["fail"]["what"].getString().get() ); | ||
174 | } | ||
175 | } | ||
176 | else | ||
177 | { | ||
178 | Bu::println(" No further details."); | ||
179 | } | ||
180 | } | ||
181 | delete *i; | ||
182 | } | ||
183 | } | ||
184 | |||
185 | return 0; | ||
186 | } | ||
diff --git a/src/unit/file.unit b/src/unit/file.unit index ee69995..b8cb73f 100644 --- a/src/unit/file.unit +++ b/src/unit/file.unit | |||
@@ -14,6 +14,11 @@ | |||
14 | 14 | ||
15 | suite File | 15 | suite File |
16 | { | 16 | { |
17 | cleanup | ||
18 | { | ||
19 | unlink("testfile1"); | ||
20 | } | ||
21 | |||
17 | test writeFull | 22 | test writeFull |
18 | { | 23 | { |
19 | Bu::File sf("testfile1", Bu::File::WriteNew ); | 24 | Bu::File sf("testfile1", Bu::File::WriteNew ); |
diff --git a/src/unit/xml.unit b/src/unit/xml.unit index 0d62b8b..21757cb 100644 --- a/src/unit/xml.unit +++ b/src/unit/xml.unit | |||
@@ -12,10 +12,4 @@ | |||
12 | 12 | ||
13 | suite Xml | 13 | suite Xml |
14 | { | 14 | { |
15 | test declaration | ||
16 | { | ||
17 | Bu::String sXml("<?xml ?> <hi />"); | ||
18 | Bu::MemBuf buf( sXml ); | ||
19 | Bu::XmlReader xr( buf ); | ||
20 | } | ||
21 | } | 15 | } |
diff --git a/src/unstable/blob.cpp b/src/unstable/blob.cpp index a9cb99d..d343963 100644 --- a/src/unstable/blob.cpp +++ b/src/unstable/blob.cpp | |||
@@ -813,7 +813,7 @@ template<> void Bu::__tracer_format<Bu::Blob>( const Bu::Blob &v ) | |||
813 | #include "bu/formatter.h" | 813 | #include "bu/formatter.h" |
814 | Bu::Formatter &Bu::operator<<( Bu::Formatter &rOut, const Bu::Blob &b ) | 814 | Bu::Formatter &Bu::operator<<( Bu::Formatter &rOut, const Bu::Blob &b ) |
815 | { | 815 | { |
816 | rOut.write( b.getData(), b.getSize() ); | 816 | rOut.writeAligned( b.getData(), b.getSize() ); |
817 | return rOut; | 817 | return rOut; |
818 | } | 818 | } |
819 | 819 | ||