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