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