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