280a1988ebe17b2f17b8165fc47b6fe739932865
[anna.git] / source / testing / TestManager.cpp
1 // ANNA - Anna is Not Nothingness Anymore                                                         //
2 //                                                                                                //
3 // (c) Copyright 2005-2015 Eduardo Ramos Testillano & Francisco Ruiz Rayo                         //
4 //                                                                                                //
5 // See project site at http://redmine.teslayout.com/projects/anna-suite                           //
6 // See accompanying file LICENSE or copy at http://www.teslayout.com/projects/public/anna.LICENSE //
7
8 // Standard
9 #include <climits>
10 #include <cmath>
11
12 // Project
13 #include <anna/testing/TestManager.hpp>
14
15 #include <anna/xml/Compiler.hpp>
16 #include <anna/xml/Node.hpp>
17 #include <anna/core/tracing/Logger.hpp>
18 #include <anna/app/functions.hpp>
19 #include <anna/timex/Engine.hpp>
20 #include <anna/diameter/helpers/base/functions.hpp>
21 #include <anna/diameter/helpers/dcca/functions.hpp>
22 #include <anna/comm/functions.hpp>
23 #include <anna/core/functions.hpp>
24 #include <anna/diameter.comm/ClientSession.hpp>
25 #include <anna/diameter.comm/ServerSession.hpp>
26 #include <anna/testing/TestStep.hpp>
27 #include <anna/testing/TestClock.hpp>
28 #include <anna/diameter/codec/Message.hpp>
29
30
31 //class TestTimer;
32
33
34 using namespace anna::testing;
35
36
37 ///////////////////////////////////////////////////////////////////////////////////////////////////
38 void TestManager::StatSummary::newTCState(const TestCase::State::_v beginState, const TestCase::State::_v endState) throw() {
39
40   if ((beginState == TestCase::State::Initialized)&&(endState == TestCase::State::Initialized)) { // special case (new test case provisioning)
41     a_initializedTcs++;
42     return;
43   }
44
45   switch (beginState) {
46   case TestCase::State::Initialized: a_initializedTcs--; break;
47   case TestCase::State::InProgress: a_inprogressTcs--; break;
48   case TestCase::State::Failed: a_failedTcs--; break;
49   case TestCase::State::Success: a_sucessTcs--; break;
50   default: break;
51   }
52   switch (endState) {
53   case TestCase::State::Initialized: a_initializedTcs++; break;
54   case TestCase::State::InProgress: a_inprogressTcs++; break;
55   case TestCase::State::Failed: a_failedTcs++; break;
56   case TestCase::State::Success: a_sucessTcs++; break;
57   default: break;
58   }
59 }
60
61 void TestManager::StatSummary::clear() throw() {
62   a_initializedTcs = 0;
63   a_inprogressTcs = 0;
64   a_failedTcs = 0;
65   a_sucessTcs = 0;
66 }
67
68 anna::xml::Node *TestManager::StatSummary::asXML(anna::xml::Node* parent) const throw() {
69   anna::xml::Node* result = parent->createChild("StatSummary");
70
71   anna::xml::Node* tcs = result->createChild("TestCasesCounts");
72   tcs->createAttribute("Total", a_initializedTcs + a_inprogressTcs + a_failedTcs + a_sucessTcs);
73   tcs->createAttribute("Initialized", a_initializedTcs);
74   tcs->createAttribute("InProgress", a_inprogressTcs);
75   tcs->createAttribute("Failed", a_failedTcs);
76   tcs->createAttribute("Success", a_sucessTcs);
77
78   return result;
79 }
80 ///////////////////////////////////////////////////////////////////////////////////////////////////
81
82
83
84 TestManager::TestManager() :
85       anna::timex::TimeEventObserver("TestManager") {
86   a_timeController = NULL;
87   a_reportsDirectory = "./";
88
89   a_dumpInProgressReports = false;
90   a_dumpInitializedReports = false;
91   a_dumpFailedReports = false;
92   a_dumpSuccessReports = false;
93
94   a_dumpHexMessages = false;
95   a_dumpStdout = false;
96   a_synchronousAmount = 1;
97   a_poolRepeats = 0; // repeat disabled by default
98   a_poolCycle = 1;
99   a_inProgressLimit = UINT_MAX; // no limit
100   a_clock = NULL;
101   //a_testPool.clear();
102   //a_statSummary.clear();
103
104   a_autoResetHard = false;
105
106   a_currentTestIt = a_testPool.end();
107 }
108
109 void TestManager::registerKey1(const std::string &key, const TestCase *testCase)  throw(anna::RuntimeException) {
110
111   auto it = a_key1TestCaseMap.find(key);
112   if (it != a_key1TestCaseMap.end()) { // found
113     unsigned int id = it->second->getId();
114     if (id != testCase->getId()) {
115       throw anna::RuntimeException(anna::functions::asString("There is another test case (id = %llu) which registered such key1: %s", id, key.c_str()), ANNA_FILE_LOCATION);
116     }
117   }
118   else {
119     a_key1TestCaseMap[key] = const_cast<TestCase*>(testCase);
120     LOGDEBUG(anna::Logger::debug(anna::functions::asString("TestManager::registerKey1 for test case (id = %llu): %s)", testCase->getId(), key.c_str()), ANNA_FILE_LOCATION));
121   }
122 }
123
124 void TestManager::registerKey2(const std::string &key, const TestCase *testCase)  throw(anna::RuntimeException) {
125
126   auto it = a_key2TestCaseMap.find(key);
127   if (it != a_key2TestCaseMap.end()) { // found
128     unsigned int id = it->second->getId();
129     if (id != testCase->getId()) {
130       throw anna::RuntimeException(anna::functions::asString("There is another test case (id = %llu) which registered such key2: %s", id, key.c_str()), ANNA_FILE_LOCATION);
131     }
132   }
133   else {
134     a_key2TestCaseMap[key] = const_cast<TestCase*>(testCase);
135     LOGDEBUG(anna::Logger::debug(anna::functions::asString("TestManager::registerKey2 for test case (id = %llu): %s)", testCase->getId(), key.c_str()), ANNA_FILE_LOCATION));
136   }
137 }
138
139 TestTimer* TestManager::createTimer(TestCaseStep* testCaseStep, const anna::Millisecond &timeout, const TestTimer::Type::_v type)
140 throw(anna::RuntimeException) {
141   TestTimer* result(NULL);
142
143   if(a_timeController == NULL)
144     throw anna::RuntimeException("You must invoke 'setTimerController' with a not NULL timex engine", ANNA_FILE_LOCATION);
145
146   anna::Guard guard(a_timeController, "TestManager::createTimer");              // avoid interblocking
147   result = a_timers.create();
148   result->setType(type);
149   result->setId((anna::timex::TimeEvent::Id) testCaseStep);
150   result->setObserver(this);
151   result->setContext(testCaseStep);
152   result->setTimeout(timeout);
153
154   LOGDEBUG(
155       std::string msg("TestManager::createTimer | ");
156   msg += result->asString();
157   anna::Logger::debug(msg, ANNA_FILE_LOCATION);
158   );
159
160   a_timeController->activate(result);
161   return result;
162 }
163
164 void TestManager::cancelTimer(TestTimer* timer)
165 throw() {
166   if(timer == NULL)
167     return;
168
169   LOGDEBUG(
170       std::string msg("TestManager::cancel | ");
171   msg += timer->asString();
172   anna::Logger::debug(msg, ANNA_FILE_LOCATION);
173   );
174
175   try {
176     if(a_timeController == NULL)
177       a_timeController = anna::app::functions::component <anna::timex::Engine> (ANNA_FILE_LOCATION);
178
179     a_timeController->cancel(timer);
180   } catch(anna::RuntimeException& ex) {
181     ex.trace();
182   }
183 }
184
185 //------------------------------------------------------------------------------------------
186 // Se invoca automaticamente desde anna::timex::Engine
187 //------------------------------------------------------------------------------------------
188 void TestManager::release(anna::timex::TimeEvent* timeEvent)
189 throw() {
190   TestTimer* timer = static_cast <TestTimer*>(timeEvent);
191   timer->setContext(NULL);
192   a_timers.release(timer);
193 }
194
195 bool TestManager::configureTTPS(int testTicksPerSecond) throw() {
196
197   if (testTicksPerSecond == 0) {
198     if (a_clock) {
199       a_timeController->cancel(a_clock);
200       LOGDEBUG(anna::Logger::debug("Testing timer clock stopped !", ANNA_FILE_LOCATION));
201     }
202     else {
203       LOGDEBUG(anna::Logger::debug("No testing timer started yet !", ANNA_FILE_LOCATION));
204     }
205     return true;
206   }
207   else if (testTicksPerSecond  < 0) {
208     LOGWARNING(anna::Logger::warning("Invalid 'ttps' provided", ANNA_FILE_LOCATION));
209     return false;
210   }
211
212   anna::Millisecond applicationTimeInterval = anna::Millisecond(1000 / testTicksPerSecond);
213   a_synchronousAmount = 1;
214
215   if (applicationTimeInterval < anna::Millisecond(1)) {
216     LOGWARNING(anna::Logger::warning("Not allowed to configure more than 1000 events per second for for triggering testing system", ANNA_FILE_LOCATION));
217     return false;
218   }
219
220   if (applicationTimeInterval < a_timeController->getResolution()) {
221     int maximumObtained = 1000 / (int)(a_timeController->getResolution());
222     a_synchronousAmount = ceil((double)testTicksPerSecond/maximumObtained);
223     // calculate again:
224     applicationTimeInterval = anna::Millisecond(a_synchronousAmount * 1000 / testTicksPerSecond);
225   }
226
227   if (a_synchronousAmount > 1) {
228     LOGWARNING(
229         std::string msg = anna::functions::asString("Desired testing time trigger rate (%d events per second) requires more than one sending per event (%d every %lld milliseconds). Consider launch more instances with lower rate (for example %d ADML processes with %d ttps), or configure %d or more sockets to the remote endpoints to avoid burst sendings",
230             testTicksPerSecond,
231             a_synchronousAmount,
232             applicationTimeInterval.getValue(),
233             a_synchronousAmount,
234             1000/applicationTimeInterval,
235             a_synchronousAmount);
236
237     anna::Logger::warning(msg, ANNA_FILE_LOCATION);
238     );
239   }
240
241   if (a_clock) {
242     a_clock->setTimeout(applicationTimeInterval);
243   }
244   else {
245     a_clock = new TestClock("Testing clock", applicationTimeInterval, this); // clock
246   }
247
248   if (!a_clock->isActive()) a_timeController->activate(a_clock);
249
250   return true;
251 }
252
253 bool TestManager::gotoTestCase(unsigned int id) throw() {
254   test_pool_it it = a_testPool.find(id);
255   if (it != a_testPool.end()) {
256     a_currentTestIt = it;
257     return true;
258   }
259
260   return false;
261 }
262
263 bool TestManager::runTestCase(unsigned int id) throw() {
264   test_pool_it it = a_testPool.find(id);
265   if (it != a_testPool.end()) {
266     a_currentTestIt = it;
267
268     // execTestCases will get the next one, we must return 1 position:
269     if (a_currentTestIt == a_testPool.begin())
270       a_currentTestIt = a_testPool.end();
271     else
272       a_currentTestIt--;
273
274     return execTestCases(1);
275   }
276
277   return false;
278 }
279
280 TestCase *TestManager::findTestCase(unsigned int id) const throw() { // id = -1 provides current test case triggered
281
282   if (!tests()) return NULL;
283   test_pool_it it = ((id != -1) ? a_testPool.find(id) : a_currentTestIt);
284   if (it != a_testPool.end()) return const_cast<TestCase*>(it->second);
285   return NULL;
286 }
287
288 TestCase *TestManager::getTestCase(unsigned int id, const std::string &description) throw() {
289
290   test_pool_nc_it it = a_testPool.find(id);
291   if (it != a_testPool.end()) return it->second;
292
293   TestCase *result = new TestCase(id, description);
294   a_testPool[id] = result;
295   return result;
296 }
297
298 bool TestManager::clearPool(std::string &result) throw() {
299
300   result = "";
301
302   if (!tests()) {
303     result = "there are not programmed test cases to be removed";
304     return false;
305   }
306
307   int total = a_testPool.size();
308   int unsafe = 0;
309
310   test_pool_it it;
311   for (it = a_testPool.begin(); it != a_testPool.end(); it++) {
312     if (!it->second->safeToClear()) { // Check that non pending threads are running (command steps):
313       unsafe++;
314     }
315   }
316
317   if (unsafe > 0) {
318     result = "some test cases cannot be removed (";
319     result += anna::functions::asString(unsafe);
320     result += "/";
321     result += anna::functions::asString(total);
322     result += "), mainly those having running-thread steps. Check for stuck external procedures or try later.";
323     return false;
324   }
325
326   // Here is safe to clear:
327   for (it = a_testPool.begin(); it != a_testPool.end(); it++) {
328     delete it->second;
329   }
330
331   a_testPool.clear();
332   a_key1TestCaseMap.clear();
333   a_key2TestCaseMap.clear();
334   a_currentTestIt = a_testPool.end();
335   a_poolCycle = 1;
336   configureTTPS(0); // stop
337   a_statSummary.clear();
338
339   return true;
340 }
341
342 bool TestManager::resetPool(bool hard) throw() {
343   bool result = false; // any reset
344
345   if (!tests()) return result;
346   for (test_pool_nc_it it = a_testPool.begin(); it != a_testPool.end(); it++) {
347     if (it->second->reset(hard))
348       result = true;
349   }
350   //a_key1TestCaseMap.clear();
351   //a_key2TestCaseMap.clear();
352   return result;
353 }
354
355 bool TestManager::tick() throw() {
356   LOGDEBUG(anna::Logger::debug("New test clock tick !", ANNA_FILE_LOCATION));
357   return execTestCases(a_synchronousAmount);
358 }
359
360 bool TestManager::execTestCases(int sync_amount) throw() {
361
362   if (!tests()) {
363     LOGWARNING(anna::Logger::warning("Testing pool is empty. You need programming", ANNA_FILE_LOCATION));
364     return false;
365   }
366
367   // Synchronous sendings per tick:
368   int count = sync_amount;
369   while (count > 0) {
370     if (!nextTestCase()) return false; // stop the clock
371     count--;
372   }
373
374   return true;
375 }
376
377 bool TestManager::nextTestCase() throw() {
378
379   while (true) {
380
381     // Limit for in-progress test cases:
382     if (getInProgressCount() >= a_inProgressLimit) {
383       LOGDEBUG(anna::Logger::debug(anna::functions::asString("TestManager next case ignored (over in-progress count limit: %llu)", a_inProgressLimit), ANNA_FILE_LOCATION));
384       return true; // wait next tick to release OTA test cases
385     }
386
387     // Next test case:
388     if (a_currentTestIt == a_testPool.end())
389       a_currentTestIt = a_testPool.begin();
390     else
391       a_currentTestIt++;
392
393     // Completed:
394     if (a_currentTestIt == a_testPool.end()) {
395       if ((a_poolCycle > a_poolRepeats) && (a_poolRepeats != -1)) {
396         LOGWARNING(anna::Logger::warning("Testing pool cycle completed. No remaining repeat cycles left. Suspending", ANNA_FILE_LOCATION));
397         a_poolCycle = 1;
398         return false;
399       }
400       else {
401         LOGWARNING(
402             std::string nolimit = (a_poolRepeats != -1) ? "":" [no limit]";
403         anna::Logger::warning(anna::functions::asString("Testing pool cycle %d completed (repeats configured: %d%s). Restarting for the %s cycle", a_poolCycle, a_poolRepeats, nolimit.c_str(), (a_poolRepeats == a_poolCycle) ? "last":"next"), ANNA_FILE_LOCATION);
404         );
405         a_poolCycle++;
406         //a_currentTestIt = a_testPool.begin();
407         return true; // avoids infinite loop: if the cycle takes less time than test cases completion, below reset never will turns state
408         // into Initialized and this while will be infinite. It is preferable to wait one tick when the cycle is completed.
409       }
410     }
411
412     // Hard reset or soft reset to initialize already finished (in previous cycle) test cases:
413     a_currentTestIt->second->reset(a_autoResetHard);
414
415     // Process test case:
416     LOGDEBUG(anna::Logger::debug(anna::functions::asString("Processing test case id = %llu, currently '%s' state", a_currentTestIt->first, TestCase::asText(a_currentTestIt->second->getState())), ANNA_FILE_LOCATION));
417     if (a_currentTestIt->second->getState() != TestCase::State::InProgress) {
418       a_currentTestIt->second->process();
419       return true; // is not probably to reach still In-Progress test cases from previous cycles due to the whole
420       //  time for complete the test cases pool regarding the single test case lifetime. You shouldn't
421       //  forget to programm a test case timeout with a reasonable value
422     }
423   }
424 }
425
426 TestCase *TestManager::getDiameterTestCaseFromSessionId(const anna::DataBlock &message, std::string &sessionId) throw() {
427   try {
428     sessionId = anna::diameter::helpers::base::functions::getSessionId(message);
429   }
430   catch (anna::RuntimeException &ex) {
431     //ex.trace();
432     LOGDEBUG(anna::Logger::debug("Cannot get the Session-Id from received DataBlock in order to identify the Test Case", ANNA_FILE_LOCATION));
433     return NULL;
434   }
435   auto sessionIdIt = a_key1TestCaseMap.find(sessionId);
436   if (sessionIdIt != a_key1TestCaseMap.end())
437     return sessionIdIt->second;
438
439   LOGDEBUG(anna::Logger::debug(anna::functions::asString("Cannot identify the Test Case for received Session-Id: %s", sessionId.c_str()), ANNA_FILE_LOCATION));
440   return NULL;
441 }
442
443 TestCase *TestManager::getDiameterTestCaseFromSubscriberId(const anna::DataBlock &message, std::string &subscriberId) throw() {
444   try {
445     subscriberId = anna::diameter::helpers::dcca::functions::getSubscriptionIdData(message, anna::diameter::helpers::dcca::AVPVALUES__Subscription_Id_Type::END_USER_E164);
446     if (subscriberId == "") // try with IMSI
447       subscriberId = anna::diameter::helpers::dcca::functions::getSubscriptionIdData(message, anna::diameter::helpers::dcca::AVPVALUES__Subscription_Id_Type::END_USER_IMSI);
448   }
449   catch (anna::RuntimeException &ex) {
450     //ex.trace();
451     LOGDEBUG(anna::Logger::debug("Cannot get the Subscriber-Id from received DataBlock in order to identify the Test Case", ANNA_FILE_LOCATION));
452     return NULL;
453   }
454   auto subscriberIdIt = a_key2TestCaseMap.find(subscriberId);
455   if (subscriberIdIt != a_key2TestCaseMap.end())
456     return subscriberIdIt->second;
457
458   LOGDEBUG(anna::Logger::debug(anna::functions::asString("Cannot identify the Test Case for received Subscriber-Id: %s", subscriberId.c_str()), ANNA_FILE_LOCATION));
459   return NULL;
460 }
461
462 void TestManager::receiveDiameterMessage(const anna::DataBlock &message, const anna::diameter::comm::ClientSession *clientSession) throw(anna::RuntimeException) {
463
464   // Testing disabled:
465   if (!tests()) return;
466
467   // Identify the test case:
468   std::string sessionId, subscriberId;
469   TestCase *tc;
470   tc = getDiameterTestCaseFromSessionId(message, sessionId);
471   if (!tc)
472     tc = getDiameterTestCaseFromSubscriberId(message, subscriberId);
473   if (!tc) {
474     LOGWARNING(anna::Logger::warning(anna::comm::functions::asText("Cannot identify the Test Case for the message received from server: ", message), ANNA_FILE_LOCATION)); // this should not appear
475     return;
476   }
477
478   // Work with Test case:
479   TestStepWaitDiameter *tsw = tc->searchNextWaitConditionFulfilled(message, true /* comes from entity */);
480   if (!tsw) { // store as 'uncovered'
481     std::string hint = "Uncovered condition for received message from entity over Session-Id '"; hint += sessionId; hint += "'; ";
482
483     try {
484       static anna::diameter::codec::Message codecMsg;
485       codecMsg.decode(message);
486       hint += "HEX Message: '"; hint += anna::functions::asHexString(message);
487       hint += "'; XML Message:\n"; hint += codecMsg.asXMLString();
488     }
489     catch (anna::RuntimeException &ex) {
490       ex.trace();
491       hint += ex.asString();
492     }
493     hint += "\nClient Session: "; hint += clientSession->asString();
494
495     tc->addDebugSummaryHint(hint);
496   }
497   else {
498     tsw->setClientSession(const_cast<anna::diameter::comm::ClientSession*>(clientSession));
499     tc->process();
500   }
501 }
502
503 void TestManager::receiveDiameterMessage(const anna::DataBlock &message, const anna::diameter::comm::ServerSession *serverSession) throw(anna::RuntimeException) {
504
505   // Testing disabled:
506   if (!tests()) return;
507
508   // Identify the test case:
509   std::string sessionId, subscriberId;
510   TestCase *tc;
511   tc = getDiameterTestCaseFromSessionId(message, sessionId);
512   if (!tc)
513     tc = getDiameterTestCaseFromSubscriberId(message, subscriberId);
514   if (!tc) {
515     LOGWARNING(anna::Logger::warning(anna::comm::functions::asText("Cannot identify the Test Case for the message received from client: ", message), ANNA_FILE_LOCATION)); // this should not appear
516     return;
517   }
518
519   // Work with Test case:
520   TestStepWaitDiameter *tsw = tc->searchNextWaitConditionFulfilled(message, false /* comes from client */);
521   if (!tsw) { // store as 'uncovered'
522     std::string hint = "Uncovered condition for received message from client over Session-Id '"; hint += sessionId; hint += "'; ";
523
524     try {
525       static anna::diameter::codec::Message codecMsg;
526       codecMsg.decode(message);
527       hint += "HEX Message: '"; hint += anna::functions::asHexString(message);
528       hint += "'; XML Message:\n"; hint += codecMsg.asXMLString();
529     }
530     catch (anna::RuntimeException &ex) {
531       ex.trace();
532       hint += ex.asString();
533     }
534     hint += "\nServer Session: "; hint += serverSession->asString();
535
536     tc->addDebugSummaryHint(hint);
537   }
538   else {
539     tsw->setServerSession(const_cast<anna::diameter::comm::ServerSession*>(serverSession));
540     tc->process();
541   }
542 }
543
544 anna::xml::Node* TestManager::asXML(anna::xml::Node* parent) const
545 throw() {
546   anna::xml::Node* result = parent->createChild("TestManager");
547
548   int poolSize = a_testPool.size();
549   result->createAttribute("NumberOfTestCases", poolSize);
550   if (a_poolRepeats) result->createAttribute("PoolRepeats", a_poolRepeats);
551   else result->createAttribute("PoolRepeats", "disabled");
552   result->createAttribute("PoolCycle", a_poolCycle);
553   a_statSummary.asXML(result);
554   if (a_inProgressLimit == UINT_MAX)
555     result->createAttribute("InProgressLimit", "<no limit>");
556   else
557     result->createAttribute("InProgressLimit", a_inProgressLimit);
558   result->createAttribute("DumpInitializedReports", (a_dumpInitializedReports ? "yes":"no"));
559   result->createAttribute("DumpInProgressReports", (a_dumpInProgressReports ? "yes":"no"));
560   result->createAttribute("DumpFailedReports", (a_dumpFailedReports ? "yes":"no"));
561   result->createAttribute("DumpSuccessReports", (a_dumpSuccessReports ? "yes":"no"));
562   result->createAttribute("DumpHexMessages", (a_dumpHexMessages ? "yes":"no"));
563   result->createAttribute("DumpStdout", (a_dumpStdout ? "yes":"no"));
564   result->createAttribute("AutoResetHard", (a_autoResetHard ? "yes":"no"));
565   result->createAttribute("ReportsDirectory", a_reportsDirectory);
566   if (a_clock) {
567     result->createAttribute("AsynchronousSendings", a_synchronousAmount);
568     int ticksPerSecond = (a_synchronousAmount * 1000) / a_clock->getTimeout();
569     result->createAttribute("TicksPerSecond", a_clock->isActive() ? ticksPerSecond : 0);
570   }
571   if (a_currentTestIt != a_testPool.end()) {
572     result->createAttribute("CurrentTestCaseId", (*a_currentTestIt).first);
573   }
574   if (poolSize != 0) {
575     anna::xml::Node* testCases = result->createChild("TestCases");
576     for (test_pool_it it = a_testPool.begin(); it != a_testPool.end(); it++) {
577       if (((*it).second->getState() == TestCase::State::Initialized) && (!getDumpInitializedReports())) continue;
578       if (((*it).second->getState() == TestCase::State::InProgress) && (!getDumpInProgressReports())) continue;
579       if (((*it).second->getState() == TestCase::State::Failed) && (!getDumpFailedReports())) continue;
580       if (((*it).second->getState() == TestCase::State::Success) && (!getDumpSuccessReports())) continue;
581       (*it).second->asXML(testCases);
582     }
583   }
584
585   return result;
586 }
587
588 anna::xml::Node* TestManager::junitAsXML(anna::xml::Node* parent) const
589 throw() {
590   // if only a single testsuite element is present, the testsuites element can be omitted
591   //anna::xml::Node* result = parent->createChild("testsuites");
592   anna::xml::Node* result = parent->createChild("testsuite");
593
594 /*
595 https://stackoverflow.com/questions/4922867/what-is-the-junit-xml-format-specification-that-hudson-supports
596
597 JUnit XSD file
598 ==============
599 https://windyroad.com.au/dl/Open%20Source/JUnit.xsd
600
601 EXAMPLES
602 ========
603 <?xml version="1.0" encoding="UTF-8"?>
604 <testsuites disabled="" errors="" failures="" name="" tests="" time="">
605     <testsuite disabled="" errors="" failures="" hostname="" id=""
606                name="" package="" skipped="" tests="" time="" timestamp="">
607         <properties>
608             <property name="" value=""/>
609         </properties>
610         <testcase assertions="" classname="" name="" status="" time="">
611             <skipped/>
612             <error message="" type=""/>
613             <failure message="" type=""/>
614             <system-out/>
615             <system-err/>
616         </testcase>
617         <system-out/>
618         <system-err/>
619     </testsuite>
620 </testsuites>
621
622 Some of these items can occur multiple times:
623
624 There can only be one testsuites element, since that's how XML works, but there can be multiple testsuite elements within the testsuites element.
625 Each properties element can have multiple property children.
626 Each testsuite element can have multiple testcase children.
627 Each testcase element can have multiple error, failure, system-out, or system-err children:
628
629 <testsuite tests="3">
630     <testcase classname="foo1" name="ASuccessfulTest"/>
631     <testcase classname="foo2" name="AnotherSuccessfulTest"/>
632     <testcase classname="foo3" name="AFailingTest">
633         <failure type="NotEnoughFoo"> details about failure </failure>
634     </testcase>
635 </testsuite>
636
637 */
638
639 /*
640   <testsuite name=""      <!-- Full (class) name of the test for non-aggregated testsuite documents.
641                                Class name without the package for aggregated testsuites documents. Required -->
642          tests=""     <!-- The total number of tests in the suite, required. -->
643
644          disabled=""  <!-- the total number of disabled tests in the suite. optional -->
645              errors=""    <!-- The total number of tests in the suite that errored. An errored test is one that had an unanticipated problem,
646                                for example an unchecked throwable; or a problem with the implementation of the test. optional -->
647              failures=""  <!-- The total number of tests in the suite that failed. A failure is a test which the code has explicitly failed
648                                by using the mechanisms for that purpose. e.g., via an assertEquals. optional -->
649              hostname=""  <!-- Host on which the tests were executed. 'localhost' should be used if the hostname cannot be determined. optional -->
650          id=""        <!-- Starts at 0 for the first testsuite and is incremented by 1 for each following testsuite -->
651          package=""   <!-- Derived from testsuite/@name in the non-aggregated documents. optional -->
652          skipped=""   <!-- The total number of skipped tests. optional -->
653          time=""      <!-- Time taken (in seconds) to execute the tests in the suite. optional -->
654          timestamp="" <!-- when the test was executed in ISO 8601 format (2014-01-21T16:17:18). Timezone may not be specified. optional -->
655          >
656 */
657   result->createAttribute("name", "ADML Testing Main Testsuite");
658   int poolSize = a_testPool.size();
659   result->createAttribute("tests", poolSize);
660
661   result->createAttribute("errors", 0);
662   result->createAttribute("failures", a_statSummary.getFailedCount());
663   result->createAttribute("hostname", anna::functions::getHostname());
664   result->createAttribute("skipped", a_statSummary.getInitializedCount());
665
666   // Testcases:
667
668 /*
669     <!-- testcase can appear multiple times, see /testsuites/testsuite@tests -->
670     <testcase name=""       <!-- Name of the test method, required. -->
671           assertions="" <!-- number of assertions in the test case. optional -->
672           classname=""  <!-- Full class name for the class the test method is in. required -->
673           status=""
674           time=""       <!-- Time taken (in seconds) to execute the test. optional -->
675           >
676
677 */
678   int testcasesLapseMs = 0;
679   int startTimestampMs = 0;
680   double secs;
681   if (poolSize != 0) {
682     test_pool_it it_min = a_testPool.begin();
683     test_pool_it it_max = a_testPool.end();
684     startTimestampMs = (int)((*it_min).second->getStartTimestamp());
685     std::string debugSummary;
686
687     for (test_pool_it it = it_min; it != it_max; it++) {
688       anna::xml::Node* testcase = result->createChild("testcase");
689       auto tc = (*it).second;
690
691       testcasesLapseMs = (int)(tc->getLapseMs());
692       testcase->createAttribute("classname", "adml_testcase");
693       testcase->createAttribute("description", tc->getDescription());
694       testcase->createAttribute("status", TestCase::asText(tc->getState()));
695
696       secs = testcasesLapseMs / 1000.0;
697       testcase->createAttribute("time", anna::functions::asString(secs, "%.2f"));
698
699       if (tc->isFailed()) {
700         anna::xml::Node* failure = testcase->createChild("failure");
701         std::string text = "";
702         debugSummary = tc->getDebugSummary().asString();
703         if (debugSummary != "") {
704           text += "[Testcase debug summary]";
705           text += "<!--";
706           text += debugSummary;
707           text += "-->";
708         }
709         text += "[Testcase information (xml dump)]";
710         text += "<!--";
711         text += tc->asXMLString();
712         text += "-->";
713         failure->createText(text);
714       }
715     }
716   }
717
718   time_t startTimestamp = startTimestampMs/1000;
719   time(&startTimestamp);
720   char buf[sizeof "2011-10-08T07:07:09Z"];
721   strftime(buf, sizeof buf, "%FT%TZ", gmtime(&startTimestamp));
722   // this will work too, if your compiler doesn't support %F or %T:
723   //     //strftime(buf, sizeof buf, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now));
724   //         std::cout << buf << "\n";
725   result->createAttribute("timestamp", buf);
726
727
728   return result;
729 }
730
731 std::string TestManager::asXMLString() const throw() {
732   anna::xml::Node root("root");
733   return anna::xml::Compiler().apply(asXML(&root));
734 }
735
736 std::string TestManager::junitAsXMLString() const throw() {
737   anna::xml::Node root("root");
738   return anna::xml::Compiler().apply(junitAsXML(&root));
739 }
740
741 std::string TestManager::summaryCounts() const throw() {
742
743   std::string result= "\nSummary Counts:\n";
744   unsigned int total = a_statSummary.getTotal();
745   result += "\nTotal:       " + anna::functions::asString(total);
746   result += "\nInitialized: " + anna::functions::asString(a_statSummary.getInitializedCount());
747   result += "\nIn Progress: " + anna::functions::asString(a_statSummary.getInProgressCount());
748   unsigned int failed = a_statSummary.getFailedCount();
749   unsigned int success = a_statSummary.getSuccessCount();
750   result += "\nFailed:      " + anna::functions::asString(failed);
751   result += "\nSuccess:     " + anna::functions::asString(success);
752   std::string verdict = ((failed == 0) && (success > 0) && (total == success)) ? "PASS":"FAIL";
753   if (total != 0) {
754     result += std::string("\n\nVERDICT:     ") + verdict;
755   }
756
757   return result;
758 }
759
760 std::string TestManager::summaryStates() const throw() {
761
762   std::string result = "\nSummary States:\n";
763   const char *literal = "\n[%s] %s";
764   const char *secsLiteral = " (%.2f secs)";
765
766   int testcasesLapseMs = 0;
767   int startTimestampMs = 0;
768   double secs;
769   int poolSize = a_testPool.size();
770   if (poolSize != 0) {
771     test_pool_it it_min = a_testPool.begin();
772     test_pool_it it_max = a_testPool.end();
773     startTimestampMs = (int)((*it_min).second->getStartTimestamp());
774
775     for (test_pool_it it = it_min; it != it_max; it++) {
776       auto tc = (*it).second;
777
778       testcasesLapseMs = (int)(tc->getLapseMs());
779
780       result += anna::functions::asString(literal, TestCase::asText(tc->getState()), tc->getDescription().c_str());
781
782       if (testcasesLapseMs >= 0) {
783         secs = testcasesLapseMs / 1000.0;
784         result += anna::functions::asString(secsLiteral, secs);
785       }
786     }
787   }
788   else {
789     result +="\nNo test cases programmed !";
790   }
791
792   return result;
793 }
794