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