Improvements from anna fork
[anna.git] / source / testing / TestManager.cpp
index 8f1a728..20a29a2 100644 (file)
@@ -20,6 +20,7 @@
 #include <anna/diameter/helpers/base/functions.hpp>
 #include <anna/diameter/helpers/dcca/functions.hpp>
 #include <anna/comm/functions.hpp>
+#include <anna/core/functions.hpp>
 #include <anna/diameter.comm/ClientSession.hpp>
 #include <anna/diameter.comm/ServerSession.hpp>
 #include <anna/testing/TestStep.hpp>
@@ -91,6 +92,7 @@ TestManager::TestManager() :
   a_dumpSuccessReports = false;
 
   a_dumpHexMessages = false;
+  a_dumpStdout = false;
   a_synchronousAmount = 1;
   a_poolRepeats = 0; // repeat disabled by default
   a_poolCycle = 1;
@@ -98,36 +100,39 @@ TestManager::TestManager() :
   a_clock = NULL;
   //a_testPool.clear();
   //a_statSummary.clear();
+
+  a_autoResetHard = false;
+
   a_currentTestIt = a_testPool.end();
 }
 
-void TestManager::registerSessionId(const std::string &sessionId, const TestCase *testCase)  throw(anna::RuntimeException) {
+void TestManager::registerKey1(const std::string &key, const TestCase *testCase)  throw(anna::RuntimeException) {
 
-  std::map<std::string /* session id's */, TestCase*>::const_iterator it = a_sessionIdTestCaseMap.find(sessionId);
-  if (it != a_sessionIdTestCaseMap.end()) { // found
+  auto it = a_key1TestCaseMap.find(key);
+  if (it != a_key1TestCaseMap.end()) { // found
     unsigned int id = it->second->getId();
     if (id != testCase->getId()) {
-      throw anna::RuntimeException(anna::functions::asString("There is another test case (id = %llu) which registered such sessionId: %s", id, sessionId.c_str()), ANNA_FILE_LOCATION);
+      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);
     }
   }
   else {
-    a_sessionIdTestCaseMap[sessionId] = const_cast<TestCase*>(testCase);
-    LOGDEBUG(anna::Logger::debug(anna::functions::asString("TestManager::registerSessionId for test case (id = %llu): %s)", testCase->getId(), sessionId.c_str()), ANNA_FILE_LOCATION));
+    a_key1TestCaseMap[key] = const_cast<TestCase*>(testCase);
+    LOGDEBUG(anna::Logger::debug(anna::functions::asString("TestManager::registerKey1 for test case (id = %llu): %s)", testCase->getId(), key.c_str()), ANNA_FILE_LOCATION));
   }
 }
 
-void TestManager::registerSubscriberId(const std::string &subscriberId, const TestCase *testCase)  throw(anna::RuntimeException) {
+void TestManager::registerKey2(const std::string &key, const TestCase *testCase)  throw(anna::RuntimeException) {
 
-  std::map<std::string /* subscriber id's */, TestCase*>::const_iterator it = a_subscriberIdTestCaseMap.find(subscriberId);
-  if (it != a_subscriberIdTestCaseMap.end()) { // found
+  auto it = a_key2TestCaseMap.find(key);
+  if (it != a_key2TestCaseMap.end()) { // found
     unsigned int id = it->second->getId();
     if (id != testCase->getId()) {
-      throw anna::RuntimeException(anna::functions::asString("There is another test case (id = %llu) which registered such subscriberId: %s", id, subscriberId.c_str()), ANNA_FILE_LOCATION);
+      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);
     }
   }
   else {
-    a_subscriberIdTestCaseMap[subscriberId] = const_cast<TestCase*>(testCase);
-    LOGDEBUG(anna::Logger::debug(anna::functions::asString("TestManager::registerSubscriberId for test case (id = %llu): %s)", testCase->getId(), subscriberId.c_str()), ANNA_FILE_LOCATION));
+    a_key2TestCaseMap[key] = const_cast<TestCase*>(testCase);
+    LOGDEBUG(anna::Logger::debug(anna::functions::asString("TestManager::registerKey2 for test case (id = %llu): %s)", testCase->getId(), key.c_str()), ANNA_FILE_LOCATION));
   }
 }
 
@@ -263,12 +268,12 @@ TestCase *TestManager::findTestCase(unsigned int id) const throw() { // id = -1
   return NULL;
 }
 
-TestCase *TestManager::getTestCase(unsigned int id) throw() {
+TestCase *TestManager::getTestCase(unsigned int id, const std::string &description) throw() {
 
   test_pool_nc_it it = a_testPool.find(id);
   if (it != a_testPool.end()) return it->second;
 
-  TestCase *result = new TestCase(id);
+  TestCase *result = new TestCase(id, description);
   a_testPool[id] = result;
   return result;
 }
@@ -279,8 +284,8 @@ bool TestManager::clearPool() throw() {
   // TODO: stop the possible command threads or there will be a core dump
 
   a_testPool.clear();
-  a_sessionIdTestCaseMap.clear();
-  a_subscriberIdTestCaseMap.clear();
+  a_key1TestCaseMap.clear();
+  a_key2TestCaseMap.clear();
   a_currentTestIt = a_testPool.end();
   a_poolCycle = 1;
   configureTTPS(0); // stop
@@ -296,8 +301,8 @@ bool TestManager::resetPool(bool hard) throw() {
     if (it->second->reset(hard))
       result = true;
   }
-  //a_sessionIdTestCaseMap.clear();
-  //a_subscriberIdTestCaseMap.clear();
+  //a_key1TestCaseMap.clear();
+  //a_key2TestCaseMap.clear();
   return result;
 }
 
@@ -358,8 +363,8 @@ bool TestManager::nextTestCase() throw() {
       }
     }
 
-    // Soft reset to initialize already finished (in previous cycle) test cases:
-    a_currentTestIt->second->reset(false);
+    // Hard reset or soft reset to initialize already finished (in previous cycle) test cases:
+    a_currentTestIt->second->reset(a_autoResetHard);
 
     // Process test case:
     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));
@@ -372,7 +377,7 @@ bool TestManager::nextTestCase() throw() {
   }
 }
 
-TestCase *TestManager::getTestCaseFromSessionId(const anna::DataBlock &message, std::string &sessionId) throw() {
+TestCase *TestManager::getDiameterTestCaseFromSessionId(const anna::DataBlock &message, std::string &sessionId) throw() {
   try {
     sessionId = anna::diameter::helpers::base::functions::getSessionId(message);
   }
@@ -381,15 +386,15 @@ TestCase *TestManager::getTestCaseFromSessionId(const anna::DataBlock &message,
     LOGDEBUG(anna::Logger::debug("Cannot get the Session-Id from received DataBlock in order to identify the Test Case", ANNA_FILE_LOCATION));
     return NULL;
   }
-  std::map<std::string /* session id's */, TestCase*>::const_iterator sessionIdIt = a_sessionIdTestCaseMap.find(sessionId);
-  if (sessionIdIt != a_sessionIdTestCaseMap.end())
+  auto sessionIdIt = a_key1TestCaseMap.find(sessionId);
+  if (sessionIdIt != a_key1TestCaseMap.end())
     return sessionIdIt->second;
 
   LOGDEBUG(anna::Logger::debug(anna::functions::asString("Cannot identify the Test Case for received Session-Id: %s", sessionId.c_str()), ANNA_FILE_LOCATION));
   return NULL;
 }
 
-TestCase *TestManager::getTestCaseFromSubscriberId(const anna::DataBlock &message, std::string &subscriberId) throw() {
+TestCase *TestManager::getDiameterTestCaseFromSubscriberId(const anna::DataBlock &message, std::string &subscriberId) throw() {
   try {
     subscriberId = anna::diameter::helpers::dcca::functions::getSubscriptionIdData(message, anna::diameter::helpers::dcca::AVPVALUES__Subscription_Id_Type::END_USER_E164);
     if (subscriberId == "") // try with IMSI
@@ -400,15 +405,15 @@ TestCase *TestManager::getTestCaseFromSubscriberId(const anna::DataBlock &messag
     LOGDEBUG(anna::Logger::debug("Cannot get the Subscriber-Id from received DataBlock in order to identify the Test Case", ANNA_FILE_LOCATION));
     return NULL;
   }
-  std::map<std::string /* subscriber id's */, TestCase*>::const_iterator subscriberIdIt = a_subscriberIdTestCaseMap.find(subscriberId);
-  if (subscriberIdIt != a_subscriberIdTestCaseMap.end())
+  auto subscriberIdIt = a_key2TestCaseMap.find(subscriberId);
+  if (subscriberIdIt != a_key2TestCaseMap.end())
     return subscriberIdIt->second;
 
   LOGDEBUG(anna::Logger::debug(anna::functions::asString("Cannot identify the Test Case for received Subscriber-Id: %s", subscriberId.c_str()), ANNA_FILE_LOCATION));
   return NULL;
 }
 
-void TestManager::receiveMessage(const anna::DataBlock &message, const anna::diameter::comm::ClientSession *clientSession) throw(anna::RuntimeException) {
+void TestManager::receiveDiameterMessage(const anna::DataBlock &message, const anna::diameter::comm::ClientSession *clientSession) throw(anna::RuntimeException) {
 
   // Testing disabled:
   if (!tests()) return;
@@ -416,29 +421,30 @@ void TestManager::receiveMessage(const anna::DataBlock &message, const anna::dia
   // Identify the test case:
   std::string sessionId, subscriberId;
   TestCase *tc;
-  tc = getTestCaseFromSessionId(message, sessionId);
+  tc = getDiameterTestCaseFromSessionId(message, sessionId);
   if (!tc)
-    tc = getTestCaseFromSubscriberId(message, subscriberId);
+    tc = getDiameterTestCaseFromSubscriberId(message, subscriberId);
   if (!tc) {
     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
     return;
   }
 
   // Work with Test case:
-  TestStepWait *tsw = tc->searchNextWaitConditionFulfilled(message, true /* comes from entity */);
+  TestStepWaitDiameter *tsw = tc->searchNextWaitConditionFulfilled(message, true /* comes from entity */);
   if (!tsw) { // store as 'uncovered'
-    std::string hint = "Uncovered condition for received message from entity over Session-Id '"; hint += sessionId; hint += "':";
+    std::string hint = "Uncovered condition for received message from entity over Session-Id '"; hint += sessionId; hint += "'";
 
     try {
       static anna::diameter::codec::Message codecMsg;
       codecMsg.decode(message);
-      hint += "\n"; hint += codecMsg.asXMLString();
+      hint += "HEX Message: '"; hint += anna::functions::asHexString(message);
+      hint += "'; XML Message:\n"; hint += codecMsg.asXMLString();
     }
     catch (anna::RuntimeException &ex) {
       ex.trace();
-      hint += "\n"; hint += ex.asString();
+      hint += ex.asString();
     }
-    hint += "\n"; hint += clientSession->asString();
+    hint += "\nClient Session: "; hint += clientSession->asString();
 
     tc->addDebugSummaryHint(hint);
   }
@@ -448,7 +454,7 @@ void TestManager::receiveMessage(const anna::DataBlock &message, const anna::dia
   }
 }
 
-void TestManager::receiveMessage(const anna::DataBlock &message, const anna::diameter::comm::ServerSession *serverSession) throw(anna::RuntimeException) {
+void TestManager::receiveDiameterMessage(const anna::DataBlock &message, const anna::diameter::comm::ServerSession *serverSession) throw(anna::RuntimeException) {
 
   // Testing disabled:
   if (!tests()) return;
@@ -456,29 +462,30 @@ void TestManager::receiveMessage(const anna::DataBlock &message, const anna::dia
   // Identify the test case:
   std::string sessionId, subscriberId;
   TestCase *tc;
-  tc = getTestCaseFromSessionId(message, sessionId);
+  tc = getDiameterTestCaseFromSessionId(message, sessionId);
   if (!tc)
-    tc = getTestCaseFromSubscriberId(message, subscriberId);
+    tc = getDiameterTestCaseFromSubscriberId(message, subscriberId);
   if (!tc) {
     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
     return;
   }
 
   // Work with Test case:
-  TestStepWait *tsw = tc->searchNextWaitConditionFulfilled(message, false /* comes from client */);
+  TestStepWaitDiameter *tsw = tc->searchNextWaitConditionFulfilled(message, false /* comes from client */);
   if (!tsw) { // store as 'uncovered'
-    std::string hint = "Uncovered condition for received message from client over Session-Id '"; hint += sessionId; hint += "':";
+    std::string hint = "Uncovered condition for received message from client over Session-Id '"; hint += sessionId; hint += "'";
 
     try {
       static anna::diameter::codec::Message codecMsg;
       codecMsg.decode(message);
-      hint += "\n"; hint += codecMsg.asXMLString();
+      hint += "HEX Message: '"; hint += anna::functions::asHexString(message);
+      hint += "'; XML Message:\n"; hint += codecMsg.asXMLString();
     }
     catch (anna::RuntimeException &ex) {
       ex.trace();
-      hint += "\n"; hint += ex.asString();
+      hint += ex.asString();
     }
-    hint += "\n"; hint += serverSession->asString();
+    hint += "\nServer Session: "; hint += serverSession->asString();
 
     tc->addDebugSummaryHint(hint);
   }
@@ -507,6 +514,8 @@ throw() {
   result->createAttribute("DumpFailedReports", (a_dumpFailedReports ? "yes":"no"));
   result->createAttribute("DumpSuccessReports", (a_dumpSuccessReports ? "yes":"no"));
   result->createAttribute("DumpHexMessages", (a_dumpHexMessages ? "yes":"no"));
+  result->createAttribute("DumpStdout", (a_dumpStdout ? "yes":"no"));
+  result->createAttribute("AutoResetHard", (a_autoResetHard ? "yes":"no"));
   result->createAttribute("ReportsDirectory", a_reportsDirectory);
   if (a_clock) {
     result->createAttribute("AsynchronousSendings", a_synchronousAmount);
@@ -530,8 +539,210 @@ throw() {
   return result;
 }
 
+anna::xml::Node* TestManager::junitAsXML(anna::xml::Node* parent) const
+throw() {
+  // if only a single testsuite element is present, the testsuites element can be omitted
+  //anna::xml::Node* result = parent->createChild("testsuites");
+  anna::xml::Node* result = parent->createChild("testsuite");
+
+/*
+https://stackoverflow.com/questions/4922867/what-is-the-junit-xml-format-specification-that-hudson-supports
+
+JUnit XSD file
+==============
+https://windyroad.com.au/dl/Open%20Source/JUnit.xsd
+
+EXAMPLES
+========
+<?xml version="1.0" encoding="UTF-8"?>
+<testsuites disabled="" errors="" failures="" name="" tests="" time="">
+    <testsuite disabled="" errors="" failures="" hostname="" id=""
+               name="" package="" skipped="" tests="" time="" timestamp="">
+        <properties>
+            <property name="" value=""/>
+        </properties>
+        <testcase assertions="" classname="" name="" status="" time="">
+            <skipped/>
+            <error message="" type=""/>
+            <failure message="" type=""/>
+            <system-out/>
+            <system-err/>
+        </testcase>
+        <system-out/>
+        <system-err/>
+    </testsuite>
+</testsuites>
+
+Some of these items can occur multiple times:
+
+There can only be one testsuites element, since that's how XML works, but there can be multiple testsuite elements within the testsuites element.
+Each properties element can have multiple property children.
+Each testsuite element can have multiple testcase children.
+Each testcase element can have multiple error, failure, system-out, or system-err children:
+
+<testsuite tests="3">
+    <testcase classname="foo1" name="ASuccessfulTest"/>
+    <testcase classname="foo2" name="AnotherSuccessfulTest"/>
+    <testcase classname="foo3" name="AFailingTest">
+        <failure type="NotEnoughFoo"> details about failure </failure>
+    </testcase>
+</testsuite>
+
+*/
+
+/*
+  <testsuite name=""      <!-- Full (class) name of the test for non-aggregated testsuite documents.
+                               Class name without the package for aggregated testsuites documents. Required -->
+         tests=""     <!-- The total number of tests in the suite, required. -->
+
+         disabled=""  <!-- the total number of disabled tests in the suite. optional -->
+             errors=""    <!-- The total number of tests in the suite that errored. An errored test is one that had an unanticipated problem,
+                               for example an unchecked throwable; or a problem with the implementation of the test. optional -->
+             failures=""  <!-- The total number of tests in the suite that failed. A failure is a test which the code has explicitly failed
+                               by using the mechanisms for that purpose. e.g., via an assertEquals. optional -->
+             hostname=""  <!-- Host on which the tests were executed. 'localhost' should be used if the hostname cannot be determined. optional -->
+         id=""        <!-- Starts at 0 for the first testsuite and is incremented by 1 for each following testsuite -->
+         package=""   <!-- Derived from testsuite/@name in the non-aggregated documents. optional -->
+         skipped=""   <!-- The total number of skipped tests. optional -->
+         time=""      <!-- Time taken (in seconds) to execute the tests in the suite. optional -->
+         timestamp="" <!-- when the test was executed in ISO 8601 format (2014-01-21T16:17:18). Timezone may not be specified. optional -->
+         >
+*/
+  result->createAttribute("name", "ADML Testing Main Testsuite");
+  int poolSize = a_testPool.size();
+  result->createAttribute("tests", poolSize);
+
+  result->createAttribute("errors", 0);
+  result->createAttribute("failures", a_statSummary.getFailedCount());
+  result->createAttribute("hostname", anna::functions::getHostname());
+  result->createAttribute("skipped", a_statSummary.getInitializedCount());
+
+  // Testcases:
+
+/*
+    <!-- testcase can appear multiple times, see /testsuites/testsuite@tests -->
+    <testcase name=""       <!-- Name of the test method, required. -->
+          assertions="" <!-- number of assertions in the test case. optional -->
+          classname=""  <!-- Full class name for the class the test method is in. required -->
+          status=""
+          time=""       <!-- Time taken (in seconds) to execute the test. optional -->
+          >
+
+*/
+  int testcasesLapseMs = 0;
+  int startTimestampMs = 0;
+  double secs;
+  if (poolSize != 0) {
+    test_pool_it it_min = a_testPool.begin();
+    test_pool_it it_max = a_testPool.end();
+    startTimestampMs = (int)((*it_min).second->getStartTimestamp());
+    std::string debugSummary;
+
+    for (test_pool_it it = it_min; it != it_max; it++) {
+      anna::xml::Node* testcase = result->createChild("testcase");
+      auto tc = (*it).second;
+
+      testcasesLapseMs = (int)(tc->getLapseMs());
+      testcase->createAttribute("classname", "adml_testcase");
+      testcase->createAttribute("description", tc->getDescription());
+      testcase->createAttribute("status", TestCase::asText(tc->getState()));
+
+      secs = testcasesLapseMs / 1000.0;
+      testcase->createAttribute("time", anna::functions::asString(secs, "%.2f"));
+
+      if (tc->isFailed()) {
+        anna::xml::Node* failure = testcase->createChild("failure");
+        std::string text = "";
+        debugSummary = tc->getDebugSummary().asString();
+        if (debugSummary != "") {
+          text += "[Testcase debug summary]";
+          text += "<!--";
+          text += debugSummary;
+          text += "-->";
+        }
+        text += "[Testcase information (xml dump)]";
+        text += "<!--";
+        text += tc->asXMLString();
+        text += "-->";
+        failure->createText(text);
+      }
+    }
+  }
+
+  time_t startTimestamp = startTimestampMs/1000;
+  time(&startTimestamp);
+  char buf[sizeof "2011-10-08T07:07:09Z"];
+  strftime(buf, sizeof buf, "%FT%TZ", gmtime(&startTimestamp));
+  // this will work too, if your compiler doesn't support %F or %T:
+  //     //strftime(buf, sizeof buf, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now));
+  //         std::cout << buf << "\n";
+  result->createAttribute("timestamp", buf);
+
+
+  return result;
+}
+
 std::string TestManager::asXMLString() const throw() {
   anna::xml::Node root("root");
   return anna::xml::Compiler().apply(asXML(&root));
 }
 
+std::string TestManager::junitAsXMLString() const throw() {
+  anna::xml::Node root("root");
+  return anna::xml::Compiler().apply(junitAsXML(&root));
+}
+
+std::string TestManager::summaryCounts() const throw() {
+
+  std::string result= "\nSummary Counts:\n";
+  unsigned int total = a_statSummary.getTotal();
+  result += "\nTotal:       " + anna::functions::asString(total);
+  result += "\nInitialized: " + anna::functions::asString(a_statSummary.getInitializedCount());
+  result += "\nIn Progress: " + anna::functions::asString(a_statSummary.getInProgressCount());
+  unsigned int failed = a_statSummary.getFailedCount();
+  unsigned int success = a_statSummary.getSuccessCount();
+  result += "\nFailed:      " + anna::functions::asString(failed);
+  result += "\nSuccess:     " + anna::functions::asString(success);
+  std::string verdict = ((failed == 0) && (success > 0) && (total == success)) ? "PASS":"FAIL";
+  if (total != 0) {
+    result += std::string("\n\nVERDICT:     ") + verdict;
+  }
+
+  return result;
+}
+
+std::string TestManager::summaryStates() const throw() {
+
+  std::string result = "\nSummary States:\n";
+  const char *literal = "\n[%s] %s";
+  const char *secsLiteral = " (%.2f secs)";
+
+  int testcasesLapseMs = 0;
+  int startTimestampMs = 0;
+  double secs;
+  int poolSize = a_testPool.size();
+  if (poolSize != 0) {
+    test_pool_it it_min = a_testPool.begin();
+    test_pool_it it_max = a_testPool.end();
+    startTimestampMs = (int)((*it_min).second->getStartTimestamp());
+
+    for (test_pool_it it = it_min; it != it_max; it++) {
+      auto tc = (*it).second;
+
+      testcasesLapseMs = (int)(tc->getLapseMs());
+
+      result += anna::functions::asString(literal, TestCase::asText(tc->getState()), tc->getDescription().c_str());
+
+      if (testcasesLapseMs >= 0) {
+        secs = testcasesLapseMs / 1000.0;
+        result += anna::functions::asString(secsLiteral, secs);
+      }
+    }
+  }
+  else {
+    result +="\nNo test cases programmed !";
+  }
+
+  return result;
+}
+