c8c65e3bf8f041db6aed7a7da5d1a017070d5d45
[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   result = "all the programmed test cases have been dropped";
340   return true;
341 }
342
343 bool TestManager::resetPool(bool hard) throw() {
344   bool result = false; // any reset
345
346   if (!tests()) return result;
347   for (test_pool_nc_it it = a_testPool.begin(); it != a_testPool.end(); it++) {
348     if (it->second->reset(hard))
349       result = true;
350   }
351   //a_key1TestCaseMap.clear();
352   //a_key2TestCaseMap.clear();
353   return result;
354 }
355
356 bool TestManager::tick() throw() {
357   LOGDEBUG(anna::Logger::debug("New test clock tick !", ANNA_FILE_LOCATION));
358   return execTestCases(a_synchronousAmount);
359 }
360
361 bool TestManager::execTestCases(int sync_amount) throw() {
362
363   if (!tests()) {
364     LOGWARNING(anna::Logger::warning("Testing pool is empty. You need programming", ANNA_FILE_LOCATION));
365     return false;
366   }
367
368   // Synchronous sendings per tick:
369   int count = sync_amount;
370   while (count > 0) {
371     if (!nextTestCase()) return false; // stop the clock
372     count--;
373   }
374
375   return true;
376 }
377
378 bool TestManager::nextTestCase() throw() {
379
380   while (true) {
381
382     // Limit for in-progress test cases:
383     if (getInProgressCount() >= a_inProgressLimit) {
384       LOGDEBUG(anna::Logger::debug(anna::functions::asString("TestManager next case ignored (over in-progress count limit: %llu)", a_inProgressLimit), ANNA_FILE_LOCATION));
385       return true; // wait next tick to release OTA test cases
386     }
387
388     // Next test case:
389     if (a_currentTestIt == a_testPool.end())
390       a_currentTestIt = a_testPool.begin();
391     else
392       a_currentTestIt++;
393
394     // Completed:
395     if (a_currentTestIt == a_testPool.end()) {
396       if ((a_poolCycle > a_poolRepeats) && (a_poolRepeats != -1)) {
397         LOGWARNING(anna::Logger::warning("Testing pool cycle completed. No remaining repeat cycles left. Suspending", ANNA_FILE_LOCATION));
398         a_poolCycle = 1;
399         return false;
400       }
401       else {
402         LOGWARNING(
403             std::string nolimit = (a_poolRepeats != -1) ? "":" [no limit]";
404         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);
405         );
406         a_poolCycle++;
407         //a_currentTestIt = a_testPool.begin();
408         return true; // avoids infinite loop: if the cycle takes less time than test cases completion, below reset never will turns state
409         // into Initialized and this while will be infinite. It is preferable to wait one tick when the cycle is completed.
410       }
411     }
412
413     // Hard reset or soft reset to initialize already finished (in previous cycle) test cases:
414     a_currentTestIt->second->reset(a_autoResetHard);
415
416     // Process test case:
417     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));
418     if (a_currentTestIt->second->getState() != TestCase::State::InProgress) {
419       a_currentTestIt->second->process();
420       return true; // is not probably to reach still In-Progress test cases from previous cycles due to the whole
421       //  time for complete the test cases pool regarding the single test case lifetime. You shouldn't
422       //  forget to programm a test case timeout with a reasonable value
423     }
424   }
425 }
426
427 TestCase *TestManager::getDiameterTestCaseFromSessionId(const anna::DataBlock &message, std::string &sessionId) throw() {
428   try {
429     sessionId = anna::diameter::helpers::base::functions::getSessionId(message);
430   }
431   catch (anna::RuntimeException &ex) {
432     //ex.trace();
433     LOGDEBUG(anna::Logger::debug("Cannot get the Session-Id from received DataBlock in order to identify the Test Case", ANNA_FILE_LOCATION));
434     return NULL;
435   }
436   auto sessionIdIt = a_key1TestCaseMap.find(sessionId);
437   if (sessionIdIt != a_key1TestCaseMap.end())
438     return sessionIdIt->second;
439
440   LOGDEBUG(anna::Logger::debug(anna::functions::asString("Cannot identify the Test Case for received Session-Id: %s", sessionId.c_str()), ANNA_FILE_LOCATION));
441   return NULL;
442 }
443
444 TestCase *TestManager::getDiameterTestCaseFromSubscriberId(const anna::DataBlock &message, std::string &subscriberId) throw() {
445   try {
446     subscriberId = anna::diameter::helpers::dcca::functions::getSubscriptionIdData(message, anna::diameter::helpers::dcca::AVPVALUES__Subscription_Id_Type::END_USER_E164);
447     if (subscriberId == "") // try with IMSI
448       subscriberId = anna::diameter::helpers::dcca::functions::getSubscriptionIdData(message, anna::diameter::helpers::dcca::AVPVALUES__Subscription_Id_Type::END_USER_IMSI);
449   }
450   catch (anna::RuntimeException &ex) {
451     //ex.trace();
452     LOGDEBUG(anna::Logger::debug("Cannot get the Subscriber-Id from received DataBlock in order to identify the Test Case", ANNA_FILE_LOCATION));
453     return NULL;
454   }
455   auto subscriberIdIt = a_key2TestCaseMap.find(subscriberId);
456   if (subscriberIdIt != a_key2TestCaseMap.end())
457     return subscriberIdIt->second;
458
459   LOGDEBUG(anna::Logger::debug(anna::functions::asString("Cannot identify the Test Case for received Subscriber-Id: %s", subscriberId.c_str()), ANNA_FILE_LOCATION));
460   return NULL;
461 }
462
463 void TestManager::receiveDiameterMessage(const anna::DataBlock &message, const anna::diameter::comm::ClientSession *clientSession) throw(anna::RuntimeException) {
464
465   // Testing disabled:
466   if (!tests()) return;
467
468   // Identify the test case:
469   std::string sessionId, subscriberId;
470   TestCase *tc;
471   tc = getDiameterTestCaseFromSessionId(message, sessionId);
472   if (!tc)
473     tc = getDiameterTestCaseFromSubscriberId(message, subscriberId);
474   if (!tc) {
475     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
476     return;
477   }
478
479   // Work with Test case:
480   TestStepWaitDiameter *tsw = tc->searchNextWaitConditionFulfilled(message, true /* comes from entity */);
481   if (!tsw) { // store as 'uncovered'
482     std::string hint = "Uncovered condition for received message from entity over Session-Id '"; hint += sessionId; hint += "'; ";
483
484     try {
485       static anna::diameter::codec::Message codecMsg;
486       codecMsg.decode(message);
487       hint += "HEX Message: '"; hint += anna::functions::asHexString(message);
488       hint += "'; XML Message:\n"; hint += codecMsg.asXMLString();
489     }
490     catch (anna::RuntimeException &ex) {
491       ex.trace();
492       hint += ex.asString();
493     }
494     hint += "\nClient Session: "; hint += clientSession->asString();
495
496     tc->addDebugSummaryHint(hint);
497   }
498   else {
499     tsw->setClientSession(const_cast<anna::diameter::comm::ClientSession*>(clientSession));
500     tc->process();
501   }
502 }
503
504 void TestManager::receiveDiameterMessage(const anna::DataBlock &message, const anna::diameter::comm::ServerSession *serverSession) throw(anna::RuntimeException) {
505
506   // Testing disabled:
507   if (!tests()) return;
508
509   // Identify the test case:
510   std::string sessionId, subscriberId;
511   TestCase *tc;
512   tc = getDiameterTestCaseFromSessionId(message, sessionId);
513   if (!tc)
514     tc = getDiameterTestCaseFromSubscriberId(message, subscriberId);
515   if (!tc) {
516     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
517     return;
518   }
519
520   // Work with Test case:
521   TestStepWaitDiameter *tsw = tc->searchNextWaitConditionFulfilled(message, false /* comes from client */);
522   if (!tsw) { // store as 'uncovered'
523     std::string hint = "Uncovered condition for received message from client over Session-Id '"; hint += sessionId; hint += "'; ";
524
525     try {
526       static anna::diameter::codec::Message codecMsg;
527       codecMsg.decode(message);
528       hint += "HEX Message: '"; hint += anna::functions::asHexString(message);
529       hint += "'; XML Message:\n"; hint += codecMsg.asXMLString();
530     }
531     catch (anna::RuntimeException &ex) {
532       ex.trace();
533       hint += ex.asString();
534     }
535     hint += "\nServer Session: "; hint += serverSession->asString();
536
537     tc->addDebugSummaryHint(hint);
538   }
539   else {
540     tsw->setServerSession(const_cast<anna::diameter::comm::ServerSession*>(serverSession));
541     tc->process();
542   }
543 }
544
545 anna::xml::Node* TestManager::asXML(anna::xml::Node* parent) const
546 throw() {
547   anna::xml::Node* result = parent->createChild("TestManager");
548
549   int poolSize = a_testPool.size();
550   result->createAttribute("NumberOfTestCases", poolSize);
551   if (a_poolRepeats) result->createAttribute("PoolRepeats", a_poolRepeats);
552   else result->createAttribute("PoolRepeats", "disabled");
553   result->createAttribute("PoolCycle", a_poolCycle);
554   a_statSummary.asXML(result);
555   if (a_inProgressLimit == UINT_MAX)
556     result->createAttribute("InProgressLimit", "<no limit>");
557   else
558     result->createAttribute("InProgressLimit", a_inProgressLimit);
559   result->createAttribute("DumpInitializedReports", (a_dumpInitializedReports ? "yes":"no"));
560   result->createAttribute("DumpInProgressReports", (a_dumpInProgressReports ? "yes":"no"));
561   result->createAttribute("DumpFailedReports", (a_dumpFailedReports ? "yes":"no"));
562   result->createAttribute("DumpSuccessReports", (a_dumpSuccessReports ? "yes":"no"));
563   result->createAttribute("DumpHexMessages", (a_dumpHexMessages ? "yes":"no"));
564   result->createAttribute("DumpStdout", (a_dumpStdout ? "yes":"no"));
565   result->createAttribute("AutoResetHard", (a_autoResetHard ? "yes":"no"));
566   result->createAttribute("ReportsDirectory", a_reportsDirectory);
567   if (a_clock) {
568     result->createAttribute("AsynchronousSendings", a_synchronousAmount);
569     int ticksPerSecond = (a_synchronousAmount * 1000) / a_clock->getTimeout();
570     result->createAttribute("TicksPerSecond", a_clock->isActive() ? ticksPerSecond : 0);
571   }
572   if (a_currentTestIt != a_testPool.end()) {
573     result->createAttribute("CurrentTestCaseId", (*a_currentTestIt).first);
574   }
575   if (poolSize != 0) {
576     anna::xml::Node* testCases = result->createChild("TestCases");
577     for (test_pool_it it = a_testPool.begin(); it != a_testPool.end(); it++) {
578       if (((*it).second->getState() == TestCase::State::Initialized) && (!getDumpInitializedReports())) continue;
579       if (((*it).second->getState() == TestCase::State::InProgress) && (!getDumpInProgressReports())) continue;
580       if (((*it).second->getState() == TestCase::State::Failed) && (!getDumpFailedReports())) continue;
581       if (((*it).second->getState() == TestCase::State::Success) && (!getDumpSuccessReports())) continue;
582       (*it).second->asXML(testCases);
583     }
584   }
585
586   return result;
587 }
588
589 anna::xml::Node* TestManager::junitAsXML(anna::xml::Node* parent) const
590 throw() {
591   // if only a single testsuite element is present, the testsuites element can be omitted
592   //anna::xml::Node* result = parent->createChild("testsuites");
593   anna::xml::Node* result = parent->createChild("testsuite");
594
595 /*
596 https://stackoverflow.com/questions/4922867/what-is-the-junit-xml-format-specification-that-hudson-supports
597
598 JUnit XSD file
599 ==============
600 https://windyroad.com.au/dl/Open%20Source/JUnit.xsd
601
602 EXAMPLES
603 ========
604 <?xml version="1.0" encoding="UTF-8"?>
605 <testsuites disabled="" errors="" failures="" name="" tests="" time="">
606     <testsuite disabled="" errors="" failures="" hostname="" id=""
607                name="" package="" skipped="" tests="" time="" timestamp="">
608         <properties>
609             <property name="" value=""/>
610         </properties>
611         <testcase assertions="" classname="" name="" status="" time="">
612             <skipped/>
613             <error message="" type=""/>
614             <failure message="" type=""/>
615             <system-out/>
616             <system-err/>
617         </testcase>
618         <system-out/>
619         <system-err/>
620     </testsuite>
621 </testsuites>
622
623 Some of these items can occur multiple times:
624
625 There can only be one testsuites element, since that's how XML works, but there can be multiple testsuite elements within the testsuites element.
626 Each properties element can have multiple property children.
627 Each testsuite element can have multiple testcase children.
628 Each testcase element can have multiple error, failure, system-out, or system-err children:
629
630 <testsuite tests="3">
631     <testcase classname="foo1" name="ASuccessfulTest"/>
632     <testcase classname="foo2" name="AnotherSuccessfulTest"/>
633     <testcase classname="foo3" name="AFailingTest">
634         <failure type="NotEnoughFoo"> details about failure </failure>
635     </testcase>
636 </testsuite>
637
638 */
639
640 /*
641   <testsuite name=""      <!-- Full (class) name of the test for non-aggregated testsuite documents.
642                                Class name without the package for aggregated testsuites documents. Required -->
643          tests=""     <!-- The total number of tests in the suite, required. -->
644
645          disabled=""  <!-- the total number of disabled tests in the suite. optional -->
646              errors=""    <!-- The total number of tests in the suite that errored. An errored test is one that had an unanticipated problem,
647                                for example an unchecked throwable; or a problem with the implementation of the test. optional -->
648              failures=""  <!-- The total number of tests in the suite that failed. A failure is a test which the code has explicitly failed
649                                by using the mechanisms for that purpose. e.g., via an assertEquals. optional -->
650              hostname=""  <!-- Host on which the tests were executed. 'localhost' should be used if the hostname cannot be determined. optional -->
651          id=""        <!-- Starts at 0 for the first testsuite and is incremented by 1 for each following testsuite -->
652          package=""   <!-- Derived from testsuite/@name in the non-aggregated documents. optional -->
653          skipped=""   <!-- The total number of skipped tests. optional -->
654          time=""      <!-- Time taken (in seconds) to execute the tests in the suite. optional -->
655          timestamp="" <!-- when the test was executed in ISO 8601 format (2014-01-21T16:17:18). Timezone may not be specified. optional -->
656          >
657 */
658   result->createAttribute("name", "ADML Testing Main Testsuite");
659   int poolSize = a_testPool.size();
660   result->createAttribute("tests", poolSize);
661
662   result->createAttribute("errors", 0);
663   result->createAttribute("failures", a_statSummary.getFailedCount());
664   result->createAttribute("hostname", anna::functions::getHostname());
665   result->createAttribute("skipped", a_statSummary.getInitializedCount());
666
667   // Testcases:
668
669 /*
670     <!-- testcase can appear multiple times, see /testsuites/testsuite@tests -->
671     <testcase name=""       <!-- Name of the test method, required. -->
672           assertions="" <!-- number of assertions in the test case. optional -->
673           classname=""  <!-- Full class name for the class the test method is in. required -->
674           status=""
675           time=""       <!-- Time taken (in seconds) to execute the test. optional -->
676           >
677
678 */
679   int testcasesLapseMs = 0;
680   int startTimestampMs = 0;
681   double secs;
682   if (poolSize != 0) {
683     test_pool_it it_min = a_testPool.begin();
684     test_pool_it it_max = a_testPool.end();
685     startTimestampMs = (int)((*it_min).second->getStartTimestamp());
686     std::string debugSummary;
687
688     for (test_pool_it it = it_min; it != it_max; it++) {
689       anna::xml::Node* testcase = result->createChild("testcase");
690       auto tc = (*it).second;
691
692       testcasesLapseMs = (int)(tc->getLapseMs());
693       testcase->createAttribute("classname", "adml_testcase");
694       testcase->createAttribute("description", tc->getDescription());
695       testcase->createAttribute("status", TestCase::asText(tc->getState()));
696
697       secs = testcasesLapseMs / 1000.0;
698       testcase->createAttribute("time", anna::functions::asString(secs, "%.2f"));
699
700       if (tc->isFailed()) {
701         anna::xml::Node* failure = testcase->createChild("failure");
702         std::string text = "";
703         debugSummary = tc->getDebugSummary().asString();
704         if (debugSummary != "") {
705           text += "[Testcase debug summary]";
706           text += "<!--";
707           text += debugSummary;
708           text += "-->";
709         }
710         text += "[Testcase information (xml dump)]";
711         text += "<!--";
712         text += tc->asXMLString();
713         text += "-->";
714         failure->createText(text);
715       }
716     }
717   }
718
719   time_t startTimestamp = startTimestampMs/1000;
720   time(&startTimestamp);
721   char buf[sizeof "2011-10-08T07:07:09Z"];
722   strftime(buf, sizeof buf, "%FT%TZ", gmtime(&startTimestamp));
723   // this will work too, if your compiler doesn't support %F or %T:
724   //     //strftime(buf, sizeof buf, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now));
725   //         std::cout << buf << "\n";
726   result->createAttribute("timestamp", buf);
727
728
729   return result;
730 }
731
732 std::string TestManager::asXMLString() const throw() {
733   anna::xml::Node root("root");
734   return anna::xml::Compiler().apply(asXML(&root));
735 }
736
737 std::string TestManager::junitAsXMLString() const throw() {
738   anna::xml::Node root("root");
739   return anna::xml::Compiler().apply(junitAsXML(&root));
740 }
741
742 std::string TestManager::summaryCounts() const throw() {
743
744   std::string result= "\nSummary Counts:\n";
745   unsigned int total = a_statSummary.getTotal();
746   result += "\nTotal:       " + anna::functions::asString(total);
747   result += "\nInitialized: " + anna::functions::asString(a_statSummary.getInitializedCount());
748   result += "\nIn Progress: " + anna::functions::asString(a_statSummary.getInProgressCount());
749   unsigned int failed = a_statSummary.getFailedCount();
750   unsigned int success = a_statSummary.getSuccessCount();
751   result += "\nFailed:      " + anna::functions::asString(failed);
752   result += "\nSuccess:     " + anna::functions::asString(success);
753   std::string verdict = ((failed == 0) && (success > 0) && (total == success)) ? "PASS":"FAIL";
754   if (total != 0) {
755     result += std::string("\n\nVERDICT:     ") + verdict;
756   }
757
758   return result;
759 }
760
761 std::string TestManager::summaryStates() const throw() {
762
763   std::string result = "\nSummary States:\n";
764   const char *literal = "\n[%s] %s";
765   const char *secsLiteral = " (%.2f secs)";
766
767   int testcasesLapseMs = 0;
768   int startTimestampMs = 0;
769   double secs;
770   int poolSize = a_testPool.size();
771   if (poolSize != 0) {
772     test_pool_it it_min = a_testPool.begin();
773     test_pool_it it_max = a_testPool.end();
774     startTimestampMs = (int)((*it_min).second->getStartTimestamp());
775
776     for (test_pool_it it = it_min; it != it_max; it++) {
777       auto tc = (*it).second;
778
779       testcasesLapseMs = (int)(tc->getLapseMs());
780
781       result += anna::functions::asString(literal, TestCase::asText(tc->getState()), tc->getDescription().c_str());
782
783       if (testcasesLapseMs >= 0) {
784         secs = testcasesLapseMs / 1000.0;
785         result += anna::functions::asString(secsLiteral, secs);
786       }
787     }
788   }
789   else {
790     result +="\nNo test cases programmed !";
791   }
792
793   return result;
794 }
795