Improvements from anna fork
[anna.git] / source / testing / TestCase.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
9 // Standard
10 #include <string>
11 #include <fstream>
12 #include <sstream>
13 #include <cmath>
14 #include <iostream>
15
16 // Project
17 #include <anna/testing/TestCase.hpp>
18 #include <anna/testing/TestStep.hpp>
19
20 #include <anna/xml/Compiler.hpp>
21 #include <anna/diameter/defines.hpp>
22 #include <anna/diameter/helpers/dcca/defines.hpp>
23 #include <anna/diameter/codec/functions.hpp>
24 #include <anna/diameter.comm/OriginHost.hpp>
25 #include <anna/diameter/helpers/base/functions.hpp>
26 #include <anna/diameter/helpers/dcca/functions.hpp>
27 #include <anna/core/util/Millisecond.hpp>
28 #include <anna/core/tracing/Logger.hpp>
29 #include <anna/testing/TestManager.hpp>
30
31
32 using namespace anna::testing;
33
34
35 ///////////////////////////////////////////////////////////////////////////////////////////////////
36 void TestCase::DebugSummary::addHint(const std::string &hint) throw() {
37   event_t event;
38   event.Timestamp = anna::functions::millisecond();
39   event.Hint = hint;
40   a_events.push_back(event);
41 }
42
43 void TestCase::DebugSummary::clear() throw() {
44   a_events.clear();
45 }
46
47 anna::xml::Node* TestCase::DebugSummary::asXML(anna::xml::Node* parent) const throw() {
48   anna::xml::Node* result = parent->createChild("DebugSummary");
49
50   std::vector<event_t>::const_iterator it;
51   for (it = a_events.begin(); it != a_events.end(); it++) {
52     anna::xml::Node* event = result->createChild("Event");
53     event->createAttribute("Timestamp", (*it).Timestamp.asString());
54     event->createAttribute("Hint", (*it).Hint);
55   }
56
57   return result;
58 };
59
60 std::string TestCase::DebugSummary::asString() const throw() {
61   std::string result = "";
62
63   std::vector<event_t>::const_iterator it;
64   for (it = a_events.begin(); it != a_events.end(); it++) {
65     result += anna::functions::asString("[Timestamp: %s] %s", (*it).Timestamp.asString().c_str(), (*it).Hint.c_str());
66   }
67
68   return result;
69 };
70
71 ///////////////////////////////////////////////////////////////////////////////////////////////////
72
73
74 TestCase::TestCase(unsigned int id, const std::string &description) :
75     a_id(id),
76     a_description((description != "") ? description : (anna::functions::asString("Testcase_%d", id))),
77     a_state(State::Initialized),
78     a_startTimestamp(0),
79     a_finishTimestamp(0),
80     a_interactiveAmount(-1) {
81
82   /*a_stepsIt = a_steps.end()*/;
83   TestManager &testManager = TestManager::instantiate();
84   testManager.tcsStateStats(State::Initialized, State::Initialized);
85 }
86
87 TestCase::~TestCase() {
88   reset(true); // hard reset
89   std::vector<TestStep*>::const_iterator it;
90   for (it = a_steps.begin(); it != a_steps.end(); it++) delete (*it);
91 }
92
93 const char* TestCase::asText(const State::_v state)
94 throw() {
95   static const char* text [] = { "Initialized", "InProgress", "Failed", "Success" };
96   return text [state];
97 }
98
99 anna::Millisecond TestCase::getLapseMs() const throw() {
100   return ((a_finishTimestamp >= a_startTimestamp) ? (a_finishTimestamp - a_startTimestamp) : (anna::Millisecond)0);
101 }
102
103 anna::xml::Node* TestCase::asXML(anna::xml::Node* parent) const
104 throw() {
105   anna::xml::Node* result = parent->createChild("TestCase");
106
107   result->createAttribute("Id", a_id);
108   result->createAttribute("Description", a_description);
109   result->createAttribute("State", asText(a_state));
110   result->createAttribute("StartTimestamp", a_startTimestamp.asString());
111
112   if (a_finishTimestamp != 0) {
113     result->createAttribute("FinishTimestamp", a_finishTimestamp.asString());
114     result->createAttribute("LapseMs", getLapseMs());
115   }
116
117   int steps = a_steps.size();
118   if (steps != 0) {
119     result->createAttribute("NumberOfTestSteps", steps);
120     std::vector<TestStep*>::const_iterator it;
121     for (it = a_steps.begin(); it != a_steps.end(); it++) {
122       (*it)->asXML(result);
123     }
124   }
125
126   if (a_debugSummary.events()) {
127     a_debugSummary.asXML(result);
128   }
129
130   result->createAttribute("Interactive", (a_interactiveAmount != -1) ? "yes":"no");
131
132   return result;
133 }
134
135 std::string TestCase::asXMLString() const throw() {
136   anna::xml::Node root("root");
137   return anna::xml::Compiler().apply(asXML(&root));
138 }
139
140 bool TestCase::hasSameCondition(const TestDiameterCondition &condition) const throw() {
141   std::vector<TestStep*>::const_iterator it;
142   TestStepWaitDiameter *step;
143   for (it = a_steps.begin(); it != a_steps.end(); it++) {
144     if ((*it)->getType() != TestStep::Type::Wait) continue;
145     step = (TestStepWaitDiameter *)(*it);
146     if (step->getCondition() == condition) return true;
147   }
148   return false;
149 }
150
151
152 void TestCase::setState(const State::_v &state) throw() {
153
154   State::_v previousState = a_state;
155   if (state == previousState) return;
156   a_state = state;
157   TestManager &testManager = TestManager::instantiate();
158
159   // stats:
160   testManager.tcsStateStats(previousState, state);
161
162
163   if (isFinished()) {
164     const char *literal = "FINISHED Test Case %llu/%llu [%s] => %s";
165     TestManager& testManager (TestManager::instantiate ());
166     LOGDEBUG(anna::Logger::debug(anna::functions::asString(literal, getId(), testManager.tests(), getDescription().c_str(), asText(a_state)), ANNA_FILE_LOCATION));
167
168     if (testManager.getDumpStdout()) {
169       std::cout << std::endl << anna::functions::asString(literal, getId(), testManager.tests(), getDescription().c_str(), asText(a_state)) << std::endl;
170     }
171
172     a_finishTimestamp = anna::functions::millisecond();
173
174     // Cancel existing timers:
175     std::vector<TestStep*>::iterator it;
176     for (it = a_steps.begin(); it != a_steps.end(); it++) {
177       if ((*it)->getType() == TestStep::Type::Timeout) {
178         TestStepTimeout *step = (TestStepTimeout *)(*it);
179         step->cancelTimer();
180       }
181       else if ((*it)->getType() == TestStep::Type::Delay) {
182         TestStepDelay *step = (TestStepDelay *)(*it);
183         step->cancelTimer();
184       }
185     }
186
187     if ((getState() == State::Failed) && (!testManager.getDumpFailedReports())) return;
188     if ((getState() == State::Success) && (!testManager.getDumpSuccessReports())) return;
189     // report file name: cycle-<cycle id>.testcase-<test case id>.xml
190
191     // FORMAT: We tabulate the cycle and test case in order to ease ordering of files by mean ls:
192     int cycles = testManager.getPoolRepeats();
193     int tests = testManager.tests();
194     int cyclesWidth = (cycles<=0) ? 3 /* 1000 cycles !! */: ((int) log10 ((double) cycles) + 1);
195     int testsWidth = (tests<=0) ? 9 /* subscribers */: ((int) log10 ((double) tests) + 1);
196     std::stringstream format;
197     format << "/cycle-%0" << cyclesWidth << "d.testcase-%0" << testsWidth << "llu.xml";
198
199     // FILE NAME:
200     std::string file = testManager.getReportsDirectory() + anna::functions::asString(format.str().c_str(), testManager.getPoolCycle(), a_id);
201     std::ofstream out;
202     out.open(file.c_str(), std::ofstream::out | std::ofstream::app);
203     if(out.is_open() == false) {
204       std::string msg("Error opening '");
205       msg += file;
206       msg += "' for writting";
207       anna::Logger::error(msg, ANNA_FILE_LOCATION);
208     }
209     else {
210       out << asXMLString() << std::endl;
211       out.close();
212     }
213   }
214 }
215
216 bool TestCase::done() throw() {
217   if (a_stepsIt == a_steps.end()) {
218     setState(State::Success);
219     return true;
220   }
221
222   return false;
223 }
224
225 bool TestCase::process() throw() {
226   if (steps() == 0) {
227     LOGWARNING(anna::Logger::warning(anna::functions::asString("Test case %llu (%s) is empty, nothing to execute", a_id, a_description.c_str()), ANNA_FILE_LOCATION));
228     return false;
229   }
230   if (isFinished()) {
231     LOGDEBUG(anna::Logger::debug(anna::functions::asString("Test case %llu (%s) is finished, nothing done until soft-reset", a_id, a_description.c_str()), ANNA_FILE_LOCATION));
232     return false;
233   }
234
235   const char *literal = "PROCESS Test Case %llu/%llu [%s]";
236   TestManager& testManager (TestManager::instantiate ());
237   LOGDEBUG(anna::Logger::debug(anna::functions::asString(literal, getId(), testManager.tests(), getDescription().c_str()), ANNA_FILE_LOCATION));
238
239   if (a_state == State::Initialized) {
240     if (testManager.getDumpStdout()) {
241       std::cout << std::endl << std::endl << anna::functions::asString(literal, getId(), testManager.tests(), getDescription().c_str()) << std::endl;
242     }
243
244     a_stepsIt = a_steps.begin();
245     setState(State::InProgress);
246
247     // For 'wait' steps (not really useful, but better than nothing: begin timestamp on test case start timestamp...):
248     a_startTimestamp = anna::functions::millisecond();
249   }
250
251   // Check end of the test case:
252   if (done()) return false;
253
254   bool somethingDone = false;
255   while ((*a_stepsIt)->execute()) { // executes returns 'true' if the next step must be also executed (execute until can't stand no more)
256     nextStep();
257     // Check end of the test case:
258     if (done()) return false;
259     somethingDone = true;
260   }
261
262   return somethingDone;
263 }
264
265 bool TestCase::reset(bool hard) throw() {
266
267   // Soft reset if finished:
268   if (!hard /* is soft reset */  && !isFinished()) return false;
269
270   // Dump as failed if still in progress (hard reset):
271   if (getState() == State::InProgress) {
272     addDebugSummaryHint("Testcase hard reset while in progress");
273     setState(State::Failed);
274   }
275
276   // Clean stage ////////////////////////////
277   // id is kept
278   std::vector<TestStep*>::iterator it;
279   for (it = a_steps.begin(); it != a_steps.end(); it++)
280     (*it)->reset();
281
282   a_debugSummary.clear();
283   a_startTimestamp = 0;
284   a_finishTimestamp = 0;
285   a_interactiveAmount = -1;
286
287   setState(State::Initialized);
288
289   return true;
290 }
291
292 void TestCase::assertInitialized() const throw(anna::RuntimeException) {
293   if (isFinished())
294     throw anna::RuntimeException(anna::functions::asString("Cannot program anymore. The test case %llu (%s) has finished. You must reset it to append new steps (or do it during execution, which is also allowed).", a_id, a_description.c_str()), ANNA_FILE_LOCATION);
295 }
296
297 void TestCase::assertMessage(const anna::DataBlock &db, bool toEntity) throw(anna::RuntimeException) {
298
299   bool isRequest = anna::diameter::codec::functions::isRequest(db);
300   bool registerKeys = ((isRequest && toEntity) || (!isRequest && !toEntity) /* (*) */);
301   // (*) we register answers Session-Id "assuming" that we will know the Session-Id values created by the client.
302   // This is another solution regarding diameter server testing. No sure about the final implementation.
303   // We will help registering also subscriber data, because certain messages (i.e. SLR) coming from clients could
304   //  have specific Session-Id value (unknown at test programming), and normally are identified by subscriber.
305
306   // Check hop-by-hop:
307   if (isRequest) {
308     anna::diameter::HopByHop hbh = anna::diameter::codec::functions::getHopByHop(db);
309     if (a_hopByHops.find(hbh) != a_hopByHops.end())
310       throw anna::RuntimeException(anna::functions::asString("Another request has been programmed with the same hop-by-hop (%llu) in this test case (%llu, '%s')", hbh, a_id, a_description.c_str()), ANNA_FILE_LOCATION);
311     a_hopByHops[hbh] = NULL; // may be assigned to a wait condition
312   }
313
314   if (registerKeys) {
315     TestManager &testManager = TestManager::instantiate();
316     testManager.registerKey1(anna::diameter::helpers::base::functions::getSessionId(db), this);
317
318
319     std::string subscriberId = anna::diameter::helpers::dcca::functions::getSubscriptionIdData(db, anna::diameter::helpers::dcca::AVPVALUES__Subscription_Id_Type::END_USER_E164);
320     if (subscriberId == "") // try with IMSI
321       subscriberId = anna::diameter::helpers::dcca::functions::getSubscriptionIdData(db, anna::diameter::helpers::dcca::AVPVALUES__Subscription_Id_Type::END_USER_IMSI);
322
323     if (subscriberId != "")
324       testManager.registerKey2(subscriberId, this);
325   }
326 }
327
328 void TestCase::addTimeout(const anna::Millisecond &timeout) throw(anna::RuntimeException) {
329   assertInitialized();
330   TestStepTimeout *step = new TestStepTimeout(this);
331   step->setTimeout(timeout);
332   addStep(step);
333 }
334
335 void TestCase::addSendDiameterXml2e(const anna::DataBlock &db, anna::diameter::comm::OriginHost *host, int stepNumber) throw(anna::RuntimeException) {
336   assertInitialized();
337   assertMessage(db, true /* to entity */);
338
339   if (stepNumber != -1) {
340     const TestStep *stepReferred = getStep(stepNumber);
341     if (!stepReferred)
342       throw anna::RuntimeException(anna::functions::asString("Step number (%d) do not exists (test case %llu, '%s')", stepNumber, a_id, a_description.c_str()), ANNA_FILE_LOCATION);
343
344     if (stepReferred->getType() != TestStep::Type::Wait)
345       throw anna::RuntimeException(anna::functions::asString("Step number (%d) must refer to a 'wait' step (test case %llu, '%s')", stepNumber, a_id, a_description.c_str()), ANNA_FILE_LOCATION);
346
347     const TestDiameterCondition &tc = (static_cast<const TestStepWaitDiameter*>(stepReferred))->getCondition();
348     if (tc.getCode() == "0") { // if regexp used, is not possible to detect this kind of errors
349       throw anna::RuntimeException(anna::functions::asString("Step number (%d) must refer to a 'wait for request' step (test case %llu, '%s')", stepNumber, a_id, a_description.c_str()), ANNA_FILE_LOCATION);
350     }
351   }
352
353   TestStepSendDiameterXml2e *step = new TestStepSendDiameterXml2e(this);
354   step->setMsgDataBlock(db);
355   step->setOriginHost(host);
356   step->setWaitForRequestStepNumber(stepNumber); // -1 means, no reference
357   addStep(step);
358 }
359
360 void TestCase::addSendDiameterXml2c(const anna::DataBlock &db, anna::diameter::comm::OriginHost *host, int stepNumber) throw(anna::RuntimeException) {
361   assertInitialized();
362   assertMessage(db, false /* to client */);
363
364   if (stepNumber != -1) {
365     const TestStep *stepReferred = getStep(stepNumber);
366     if (!stepReferred)
367       throw anna::RuntimeException(anna::functions::asString("Step number (%d) do not exists (test case %llu, '%s')", stepNumber, a_id, a_description.c_str()), ANNA_FILE_LOCATION);
368
369     if (stepReferred->getType() != TestStep::Type::Wait)
370       throw anna::RuntimeException(anna::functions::asString("Step number (%d) must refer to a 'wait' step (test case %llu, '%s')", stepNumber, a_id, a_description.c_str()), ANNA_FILE_LOCATION);
371
372     const TestDiameterCondition &tc = (static_cast<const TestStepWaitDiameter*>(stepReferred))->getCondition();
373     if (tc.getCode() == "0") { // if regexp used, is not possible to detect this kind of errors
374       throw anna::RuntimeException(anna::functions::asString("Step number (%d) must refer to a 'wait for request' step (test case %llu, '%s')", stepNumber, a_id, a_description.c_str()), ANNA_FILE_LOCATION);
375     }
376   }
377
378   TestStepSendDiameterXml2c *step = new TestStepSendDiameterXml2c(this);
379   step->setMsgDataBlock(db);
380   step->setOriginHost(host);
381   step->setWaitForRequestStepNumber(stepNumber); // -1 means, no reference
382   addStep(step);
383 }
384
385 void TestCase::addDelay(const anna::Millisecond &delay) throw(anna::RuntimeException) {
386   assertInitialized();
387   TestStepDelay *step = new TestStepDelay(this);
388   step->setDelay(delay);
389   addStep(step);
390 }
391
392 void TestCase::addWaitDiameter(bool fromEntity,
393               const std::string &code, const std::string &bitR, const std::string &hopByHop, const std::string &applicationId,
394               const std::string &sessionId, const std::string &resultCode,
395               const std::string &msisdn, const std::string &imsi, const std::string &serviceContextId) throw(anna::RuntimeException) {
396   assertInitialized();
397   std::string usedHopByHop = hopByHop;
398   TestStepWaitDiameter *step = NULL;
399
400   // Check basic conditions:
401   if (bitR == "1") {
402     if (resultCode != "")
403       throw anna::RuntimeException(anna::functions::asString("You cannot specify Result-Code (%s) for a wait condition of a diameter request message (test case %llu, '%s')", resultCode.c_str(), a_id, a_description.c_str()), ANNA_FILE_LOCATION);
404     if (hopByHop != "")
405       throw anna::RuntimeException(anna::functions::asString("You cannot specify Hop-by-hop (%s) for a wait condition of a diameter request message (test case %llu, '%s')", hopByHop.c_str(), a_id, a_description.c_str()), ANNA_FILE_LOCATION);
406   }
407   else {
408     if (hopByHop != "") {
409       if (hopByHop[0] == '#') {
410         if (steps() == 0)
411           throw anna::RuntimeException(anna::functions::asString("No steps has been programmed, step reference is nonsense (test case %llu, '%s')", a_id, a_description.c_str()), ANNA_FILE_LOCATION);
412
413         int stepNumber = atoi(hopByHop.substr(1).c_str());
414
415         const TestStep *stepReferred = getStep(stepNumber);
416         if (!stepReferred)
417           throw anna::RuntimeException(anna::functions::asString("Step reference number (%d) do not exists (test case %llu, '%s')", stepNumber, a_id, a_description.c_str()), ANNA_FILE_LOCATION);
418
419         if (stepReferred->getType() != TestStep::Type::Sendxml2e && stepReferred->getType() != TestStep::Type::Sendxml2c)
420           throw anna::RuntimeException(anna::functions::asString("Step number must refer to a 'sendxml2e' or 'sendxml2c' step (test case %llu, '%s')", a_id, a_description.c_str()), ANNA_FILE_LOCATION);
421
422         const anna::DataBlock &db = (static_cast<const TestStepSendDiameterXml*>(stepReferred))->getMsgDataBlock();
423         bool isAnswer = anna::diameter::codec::functions::isAnswer(db);
424         if (isAnswer)
425           throw anna::RuntimeException(anna::functions::asString("Step number must refer to a request message (test case %llu, '%s')", a_id, a_description.c_str()), ANNA_FILE_LOCATION);
426
427         // Hop-by-hop:
428         anna::diameter::HopByHop hbh = anna::diameter::codec::functions::getHopByHop(db);
429         usedHopByHop = anna::functions::asString(hbh);
430         step = new TestStepWaitDiameter(this);
431         a_hopByHops[hbh /* always exists: is the info we calculated above */] = step;
432       }
433     }
434   }
435
436   if (!step) step = new TestStepWaitDiameter(this);
437   step->setCondition(fromEntity, code, bitR, usedHopByHop, applicationId, sessionId, resultCode, msisdn, imsi, serviceContextId);
438
439   LOGINFORMATION(
440     if (hasSameCondition(step->getCondition()))
441       anna::Logger::information(anna::functions::asString("The same wait condition has already been programmed in this test case (%llu, '%s'). Are you sure ?", a_id, a_description.c_str()), ANNA_FILE_LOCATION);
442   );
443
444   addStep(step);
445 }
446
447 void TestCase::addWaitDiameterRegexpHex(bool fromEntity, const std::string &regexp) throw(anna::RuntimeException) {
448   assertInitialized();
449
450   TestStepWaitDiameter *step = new TestStepWaitDiameter(this);
451   step->setConditionRegexpHex(fromEntity, regexp);
452
453   LOGINFORMATION(
454     if (hasSameCondition(step->getCondition()))
455       anna::Logger::information(anna::functions::asString("The same wait condition has already been programmed in this test case (%llu, '%s'). Are you sure ?", a_id, a_description.c_str()), ANNA_FILE_LOCATION);
456   );
457
458   addStep(step);
459 }
460
461 void TestCase::addWaitDiameterRegexpXml(bool fromEntity, const std::string &regexp) throw(anna::RuntimeException) {
462   assertInitialized();
463
464   TestStepWaitDiameter *step = new TestStepWaitDiameter(this);
465   step->setConditionRegexpXml(fromEntity, regexp);
466
467   LOGINFORMATION(
468     if (hasSameCondition(step->getCondition()))
469       anna::Logger::information(anna::functions::asString("The same wait condition has already been programmed in this test case (%llu, '%s'). Are you sure ?", a_id, a_description.c_str()), ANNA_FILE_LOCATION);
470   );
471
472   addStep(step);
473 }
474
475 void TestCase::addCommand(const std::string &cmd) throw(anna::RuntimeException) {
476   assertInitialized();
477
478   TestStepCmd *step = new TestStepCmd(this);
479   step->setScript(cmd);
480
481   addStep(step);
482 }
483
484 void TestCase::addIpLimit(unsigned int ipLimit) throw(anna::RuntimeException) {
485   assertInitialized();
486
487   TestStepIpLimit *step = new TestStepIpLimit(this);
488   step->setIpLimit(ipLimit);
489
490   addStep(step);
491 }
492
493 TestStepWaitDiameter *TestCase::searchNextWaitConditionFulfilled(const anna::DataBlock &message, bool waitFromEntity) throw() {
494
495   TestStepWaitDiameter *result;
496   for (std::vector<TestStep*>::const_iterator it = a_stepsIt /* current */; it != a_steps.end(); it++) {
497     if ((*it)->getType() != TestStep::Type::Wait) continue;
498     if ((*it)->isCompleted()) continue;
499     result = (TestStepWaitDiameter*)(*it);
500     if ((result->getCondition().receivedFromEntity() == waitFromEntity) && (result->fulfilled(message)))
501       return result;
502   }
503
504   return NULL;
505 }
506
507 const TestStep *TestCase::getStep(int stepNumber) const throw() {
508   if (stepNumber < 1 || stepNumber > steps()) return NULL;
509 //  return a_steps.at(stepNumber-1);  // http://stackoverflow.com/questions/3269809/stdvectorat-vs-operator-surprising-results-5-to-10-times-slower-f
510   return a_steps[stepNumber-1];
511 }