X-Git-Url: https://git.teslayout.com/public/public/public/?a=blobdiff_plain;f=source%2Ftesting%2FTestManager.cpp;fp=source%2Ftesting%2FTestManager.cpp;h=8f1a72812a1c8c8a5e344ea80fffebba7469d14a;hb=d723d5bf571eb48c641b092058eaa38bb6c4fcc8;hp=0000000000000000000000000000000000000000;hpb=61f1340da3cae5159d2e3bc14fc47c6d4bf9453e;p=anna.git diff --git a/source/testing/TestManager.cpp b/source/testing/TestManager.cpp new file mode 100644 index 0000000..8f1a728 --- /dev/null +++ b/source/testing/TestManager.cpp @@ -0,0 +1,537 @@ +// ANNA - Anna is Not Nothingness Anymore // +// // +// (c) Copyright 2005-2015 Eduardo Ramos Testillano & Francisco Ruiz Rayo // +// // +// See project site at http://redmine.teslayout.com/projects/anna-suite // +// See accompanying file LICENSE or copy at http://www.teslayout.com/projects/public/anna.LICENSE // + +// Standard +#include +#include + +// Project +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +//class TestTimer; + + +using namespace anna::testing; + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +void TestManager::StatSummary::newTCState(const TestCase::State::_v beginState, const TestCase::State::_v endState) throw() { + + if ((beginState == TestCase::State::Initialized)&&(endState == TestCase::State::Initialized)) { // special case (new test case provisioning) + a_initializedTcs++; + return; + } + + switch (beginState) { + case TestCase::State::Initialized: a_initializedTcs--; break; + case TestCase::State::InProgress: a_inprogressTcs--; break; + case TestCase::State::Failed: a_failedTcs--; break; + case TestCase::State::Success: a_sucessTcs--; break; + default: break; + } + switch (endState) { + case TestCase::State::Initialized: a_initializedTcs++; break; + case TestCase::State::InProgress: a_inprogressTcs++; break; + case TestCase::State::Failed: a_failedTcs++; break; + case TestCase::State::Success: a_sucessTcs++; break; + default: break; + } +} + +void TestManager::StatSummary::clear() throw() { + a_initializedTcs = 0; + a_inprogressTcs = 0; + a_failedTcs = 0; + a_sucessTcs = 0; +} + +anna::xml::Node *TestManager::StatSummary::asXML(anna::xml::Node* parent) const throw() { + anna::xml::Node* result = parent->createChild("StatSummary"); + + anna::xml::Node* tcs = result->createChild("TestCasesCounts"); + tcs->createAttribute("Total", a_initializedTcs + a_inprogressTcs + a_failedTcs + a_sucessTcs); + tcs->createAttribute("Initialized", a_initializedTcs); + tcs->createAttribute("InProgress", a_inprogressTcs); + tcs->createAttribute("Failed", a_failedTcs); + tcs->createAttribute("Success", a_sucessTcs); + + return result; +} +/////////////////////////////////////////////////////////////////////////////////////////////////// + + + +TestManager::TestManager() : + anna::timex::TimeEventObserver("TestManager") { + a_timeController = NULL; + a_reportsDirectory = "./"; + + a_dumpInProgressReports = false; + a_dumpInitializedReports = false; + a_dumpFailedReports = false; + a_dumpSuccessReports = false; + + a_dumpHexMessages = false; + a_synchronousAmount = 1; + a_poolRepeats = 0; // repeat disabled by default + a_poolCycle = 1; + a_inProgressLimit = UINT_MAX; // no limit + a_clock = NULL; + //a_testPool.clear(); + //a_statSummary.clear(); + a_currentTestIt = a_testPool.end(); +} + +void TestManager::registerSessionId(const std::string &sessionId, const TestCase *testCase) throw(anna::RuntimeException) { + + std::map::const_iterator it = a_sessionIdTestCaseMap.find(sessionId); + if (it != a_sessionIdTestCaseMap.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); + } + } + else { + a_sessionIdTestCaseMap[sessionId] = const_cast(testCase); + LOGDEBUG(anna::Logger::debug(anna::functions::asString("TestManager::registerSessionId for test case (id = %llu): %s)", testCase->getId(), sessionId.c_str()), ANNA_FILE_LOCATION)); + } +} + +void TestManager::registerSubscriberId(const std::string &subscriberId, const TestCase *testCase) throw(anna::RuntimeException) { + + std::map::const_iterator it = a_subscriberIdTestCaseMap.find(subscriberId); + if (it != a_subscriberIdTestCaseMap.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); + } + } + else { + a_subscriberIdTestCaseMap[subscriberId] = const_cast(testCase); + LOGDEBUG(anna::Logger::debug(anna::functions::asString("TestManager::registerSubscriberId for test case (id = %llu): %s)", testCase->getId(), subscriberId.c_str()), ANNA_FILE_LOCATION)); + } +} + +TestTimer* TestManager::createTimer(TestCaseStep* testCaseStep, const anna::Millisecond &timeout, const TestTimer::Type::_v type) +throw(anna::RuntimeException) { + TestTimer* result(NULL); + + if(a_timeController == NULL) + throw anna::RuntimeException("You must invoke 'setTimerController' with a not NULL timex engine", ANNA_FILE_LOCATION); + + anna::Guard guard(a_timeController, "TestManager::createTimer"); // avoid interblocking + result = a_timers.create(); + result->setType(type); + result->setId((anna::timex::TimeEvent::Id) testCaseStep); + result->setObserver(this); + result->setContext(testCaseStep); + result->setTimeout(timeout); + + LOGDEBUG( + std::string msg("TestManager::createTimer | "); + msg += result->asString(); + anna::Logger::debug(msg, ANNA_FILE_LOCATION); + ); + + a_timeController->activate(result); + return result; +} + +void TestManager::cancelTimer(TestTimer* timer) +throw() { + if(timer == NULL) + return; + + LOGDEBUG( + std::string msg("TestManager::cancel | "); + msg += timer->asString(); + anna::Logger::debug(msg, ANNA_FILE_LOCATION); + ); + + try { + if(a_timeController == NULL) + a_timeController = anna::app::functions::component (ANNA_FILE_LOCATION); + + a_timeController->cancel(timer); + } catch(anna::RuntimeException& ex) { + ex.trace(); + } +} + +//------------------------------------------------------------------------------------------ +// Se invoca automaticamente desde anna::timex::Engine +//------------------------------------------------------------------------------------------ +void TestManager::release(anna::timex::TimeEvent* timeEvent) +throw() { + TestTimer* timer = static_cast (timeEvent); + timer->setContext(NULL); + a_timers.release(timer); +} + +bool TestManager::configureTTPS(int testTicksPerSecond) throw() { + + if (testTicksPerSecond == 0) { + if (a_clock) { + a_timeController->cancel(a_clock); + LOGDEBUG(anna::Logger::debug("Testing timer clock stopped !", ANNA_FILE_LOCATION)); + } + else { + LOGDEBUG(anna::Logger::debug("No testing timer started yet !", ANNA_FILE_LOCATION)); + } + return true; + } + else if (testTicksPerSecond < 0) { + LOGWARNING(anna::Logger::warning("Invalid 'ttps' provided", ANNA_FILE_LOCATION)); + return false; + } + + anna::Millisecond applicationTimeInterval = anna::Millisecond(1000 / testTicksPerSecond); + a_synchronousAmount = 1; + + if (applicationTimeInterval < anna::Millisecond(1)) { + LOGWARNING(anna::Logger::warning("Not allowed to configure more than 1000 events per second for for triggering testing system", ANNA_FILE_LOCATION)); + return false; + } + + if (applicationTimeInterval < a_timeController->getResolution()) { + int maximumObtained = 1000 / (int)(a_timeController->getResolution()); + a_synchronousAmount = ceil((double)testTicksPerSecond/maximumObtained); + // calculate again: + applicationTimeInterval = anna::Millisecond(a_synchronousAmount * 1000 / testTicksPerSecond); + } + + if (a_synchronousAmount > 1) { + LOGWARNING( + 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", + testTicksPerSecond, + a_synchronousAmount, + applicationTimeInterval.getValue(), + a_synchronousAmount, + 1000/applicationTimeInterval, + a_synchronousAmount); + + anna::Logger::warning(msg, ANNA_FILE_LOCATION); + ); + } + + if (a_clock) { + a_clock->setTimeout(applicationTimeInterval); + } + else { + a_clock = new TestClock("Testing clock", applicationTimeInterval, this); // clock + } + + if (!a_clock->isActive()) a_timeController->activate(a_clock); + + return true; +} + +bool TestManager::gotoTestCase(unsigned int id) throw() { + test_pool_it it = a_testPool.find(id); + if (it != a_testPool.end()) { + a_currentTestIt = it; + return true; + } + + return false; +} + +TestCase *TestManager::findTestCase(unsigned int id) const throw() { // id = -1 provides current test case triggered + + if (!tests()) return NULL; + test_pool_it it = ((id != -1) ? a_testPool.find(id) : a_currentTestIt); + if (it != a_testPool.end()) return const_cast(it->second); + return NULL; +} + +TestCase *TestManager::getTestCase(unsigned int id) throw() { + + test_pool_nc_it it = a_testPool.find(id); + if (it != a_testPool.end()) return it->second; + + TestCase *result = new TestCase(id); + a_testPool[id] = result; + return result; +} + +bool TestManager::clearPool() throw() { + if (!tests()) return false; + for (test_pool_it it = a_testPool.begin(); it != a_testPool.end(); it++) delete it->second; + // TODO: stop the possible command threads or there will be a core dump + + a_testPool.clear(); + a_sessionIdTestCaseMap.clear(); + a_subscriberIdTestCaseMap.clear(); + a_currentTestIt = a_testPool.end(); + a_poolCycle = 1; + configureTTPS(0); // stop + a_statSummary.clear(); + return true; +} + +bool TestManager::resetPool(bool hard) throw() { + bool result = false; // any reset + + if (!tests()) return result; + for (test_pool_nc_it it = a_testPool.begin(); it != a_testPool.end(); it++) { + if (it->second->reset(hard)) + result = true; + } + //a_sessionIdTestCaseMap.clear(); + //a_subscriberIdTestCaseMap.clear(); + return result; +} + +bool TestManager::tick() throw() { + LOGDEBUG(anna::Logger::debug("New test clock tick !", ANNA_FILE_LOCATION)); + return execTestCases(a_synchronousAmount); +} + +bool TestManager::execTestCases(int sync_amount) throw() { + + if (!tests()) { + LOGWARNING(anna::Logger::warning("Testing pool is empty. You need programming", ANNA_FILE_LOCATION)); + return false; + } + + // Synchronous sendings per tick: + int count = sync_amount; + while (count > 0) { + if (!nextTestCase()) return false; // stop the clock + count--; + } + + return true; +} + +bool TestManager::nextTestCase() throw() { + + while (true) { + + // Limit for in-progress test cases: + if (getInProgressCount() >= a_inProgressLimit) { + LOGDEBUG(anna::Logger::debug(anna::functions::asString("TestManager next case ignored (over in-progress count limit: %llu)", a_inProgressLimit), ANNA_FILE_LOCATION)); + return true; // wait next tick to release OTA test cases + } + + // Next test case: + if (a_currentTestIt == a_testPool.end()) + a_currentTestIt = a_testPool.begin(); + else + a_currentTestIt++; + + // Completed: + if (a_currentTestIt == a_testPool.end()) { + if ((a_poolCycle > a_poolRepeats) && (a_poolRepeats != -1)) { + LOGWARNING(anna::Logger::warning("Testing pool cycle completed. No remaining repeat cycles left. Suspending", ANNA_FILE_LOCATION)); + a_poolCycle = 1; + return false; + } + else { + LOGWARNING( + std::string nolimit = (a_poolRepeats != -1) ? "":" [no limit]"; + 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); + ); + a_poolCycle++; + //a_currentTestIt = a_testPool.begin(); + return true; // avoids infinite loop: if the cycle takes less time than test cases completion, below reset never will turns state + // into Initialized and this while will be infinite. It is preferable to wait one tick when the cycle is completed. + } + } + + // Soft reset to initialize already finished (in previous cycle) test cases: + a_currentTestIt->second->reset(false); + + // 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)); + if (a_currentTestIt->second->getState() != TestCase::State::InProgress) { + a_currentTestIt->second->process(); + return true; // is not probably to reach still In-Progress test cases from previous cycles due to the whole + // time for complete the test cases pool regarding the single test case lifetime. You shouldn't + // forget to programm a test case timeout with a reasonable value + } + } +} + +TestCase *TestManager::getTestCaseFromSessionId(const anna::DataBlock &message, std::string &sessionId) throw() { + try { + sessionId = anna::diameter::helpers::base::functions::getSessionId(message); + } + catch (anna::RuntimeException &ex) { + //ex.trace(); + 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::const_iterator sessionIdIt = a_sessionIdTestCaseMap.find(sessionId); + if (sessionIdIt != a_sessionIdTestCaseMap.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() { + 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 + subscriberId = anna::diameter::helpers::dcca::functions::getSubscriptionIdData(message, anna::diameter::helpers::dcca::AVPVALUES__Subscription_Id_Type::END_USER_IMSI); + } + catch (anna::RuntimeException &ex) { + //ex.trace(); + 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::const_iterator subscriberIdIt = a_subscriberIdTestCaseMap.find(subscriberId); + if (subscriberIdIt != a_subscriberIdTestCaseMap.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) { + + // Testing disabled: + if (!tests()) return; + + // Identify the test case: + std::string sessionId, subscriberId; + TestCase *tc; + tc = getTestCaseFromSessionId(message, sessionId); + if (!tc) + tc = getTestCaseFromSubscriberId(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 */); + if (!tsw) { // store as 'uncovered' + 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(); + } + catch (anna::RuntimeException &ex) { + ex.trace(); + hint += "\n"; hint += ex.asString(); + } + hint += "\n"; hint += clientSession->asString(); + + tc->addDebugSummaryHint(hint); + } + else { + tsw->setClientSession(const_cast(clientSession)); + tc->process(); + } +} + +void TestManager::receiveMessage(const anna::DataBlock &message, const anna::diameter::comm::ServerSession *serverSession) throw(anna::RuntimeException) { + + // Testing disabled: + if (!tests()) return; + + // Identify the test case: + std::string sessionId, subscriberId; + TestCase *tc; + tc = getTestCaseFromSessionId(message, sessionId); + if (!tc) + tc = getTestCaseFromSubscriberId(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 */); + if (!tsw) { // store as 'uncovered' + 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(); + } + catch (anna::RuntimeException &ex) { + ex.trace(); + hint += "\n"; hint += ex.asString(); + } + hint += "\n"; hint += serverSession->asString(); + + tc->addDebugSummaryHint(hint); + } + else { + tsw->setServerSession(const_cast(serverSession)); + tc->process(); + } +} + +anna::xml::Node* TestManager::asXML(anna::xml::Node* parent) const +throw() { + anna::xml::Node* result = parent->createChild("TestManager"); + + int poolSize = a_testPool.size(); + result->createAttribute("NumberOfTestCases", poolSize); + if (a_poolRepeats) result->createAttribute("PoolRepeats", a_poolRepeats); + else result->createAttribute("PoolRepeats", "disabled"); + result->createAttribute("PoolCycle", a_poolCycle); + a_statSummary.asXML(result); + if (a_inProgressLimit == UINT_MAX) + result->createAttribute("InProgressLimit", ""); + else + result->createAttribute("InProgressLimit", a_inProgressLimit); + result->createAttribute("DumpInitializedReports", (a_dumpInitializedReports ? "yes":"no")); + result->createAttribute("DumpInProgressReports", (a_dumpInProgressReports ? "yes":"no")); + result->createAttribute("DumpFailedReports", (a_dumpFailedReports ? "yes":"no")); + result->createAttribute("DumpSuccessReports", (a_dumpSuccessReports ? "yes":"no")); + result->createAttribute("DumpHexMessages", (a_dumpHexMessages ? "yes":"no")); + result->createAttribute("ReportsDirectory", a_reportsDirectory); + if (a_clock) { + result->createAttribute("AsynchronousSendings", a_synchronousAmount); + int ticksPerSecond = (a_synchronousAmount * 1000) / a_clock->getTimeout(); + result->createAttribute("TicksPerSecond", ticksPerSecond); + } + if (a_currentTestIt != a_testPool.end()) { + result->createAttribute("CurrentTestCaseId", (*a_currentTestIt).first); + } + if (poolSize != 0) { + anna::xml::Node* testCases = result->createChild("TestCases"); + for (test_pool_it it = a_testPool.begin(); it != a_testPool.end(); it++) { + if (((*it).second->getState() == TestCase::State::Initialized) && (!getDumpInitializedReports())) continue; + if (((*it).second->getState() == TestCase::State::InProgress) && (!getDumpInProgressReports())) continue; + if (((*it).second->getState() == TestCase::State::Failed) && (!getDumpFailedReports())) continue; + if (((*it).second->getState() == TestCase::State::Success) && (!getDumpSuccessReports())) continue; + (*it).second->asXML(testCases); + } + } + + return result; +} + +std::string TestManager::asXMLString() const throw() { + anna::xml::Node root("root"); + return anna::xml::Compiler().apply(asXML(&root)); +} +