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