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