Hard refactoring. CodecEngine is associated to a unique stack.
[anna.git] / example / diameter / launcher / 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
15 #include <iostream>
16
17 // Project
18 #include <anna/xml/Compiler.hpp>
19 #include <anna/diameter/defines.hpp>
20 #include <anna/diameter/helpers/dcca/defines.hpp>
21 #include <anna/diameter/codec/functions.hpp>
22 #include <anna/diameter/helpers/base/functions.hpp>
23 #include <anna/diameter/helpers/dcca/functions.hpp>
24 #include <anna/core/util/Millisecond.hpp>
25 #include <anna/core/tracing/Logger.hpp>
26
27 // Process
28 #include <TestCase.hpp>
29 #include <TestManager.hpp>
30
31
32 void TestCase::DebugSummary::addHint(const std::string &hint) throw() {
33   event_t event;
34   event.Timestamp = anna::functions::millisecond();
35   event.Hint = hint;
36   a_events.push_back(event);
37 }
38
39 void TestCase::DebugSummary::clear() throw() {
40   a_events.clear();
41 }
42
43 anna::xml::Node* TestCase::DebugSummary::asXML(anna::xml::Node* parent) const throw() {
44   anna::xml::Node* result = parent->createChild("DebugSummary");
45
46   std::vector<event_t>::const_iterator it;
47   for (it = a_events.begin(); it != a_events.end(); it++) {
48     anna::xml::Node* event = result->createChild("Event");
49     event->createAttribute("Timestamp", (*it).Timestamp.asString());
50     event->createAttribute("Hint", (*it).Hint);
51   }
52
53   return result;
54 };
55
56
57 TestCase::~TestCase() {
58   reset(true); // hard reset
59   std::vector<TestStep*>::const_iterator it;
60   for (it = a_steps.begin(); it != a_steps.end(); it++) delete (*it);
61 }
62
63 const char* TestCase::asText(const State::_v state)
64 throw() {
65   static const char* text [] = { "Initialized", "InProgress", "Failed", "Success" };
66   return text [state];
67 }
68
69 anna::xml::Node* TestCase::asXML(anna::xml::Node* parent) const
70 throw() {
71   anna::xml::Node* result = parent->createChild("TestCase");
72
73   result->createAttribute("Id", a_id);
74   result->createAttribute("State", asText(a_state));
75   result->createAttribute("StartTimestamp", a_startTime.asString());
76   int steps = a_steps.size();
77   if (steps != 0) {
78     result->createAttribute("NumberOfTestSteps", steps);
79     std::vector<TestStep*>::const_iterator it;
80     for (it = a_steps.begin(); it != a_steps.end(); it++) {
81       (*it)->asXML(result);
82     }
83   }
84
85   if (a_debugSummary.events()) {
86     a_debugSummary.asXML(result);
87   }
88
89   return result;
90 }
91
92 std::string TestCase::asXMLString() const throw() {
93   anna::xml::Node root("root");
94   return anna::xml::Compiler().apply(asXML(&root));
95 }
96
97 bool TestCase::hasSameCondition(const TestCondition &condition) const throw() {
98   std::vector<TestStep*>::const_iterator it;
99   TestStepWait *step;
100   for (it = a_steps.begin(); it != a_steps.end(); it++) {
101     if ((*it)->getType() != TestStep::Type::Wait) continue;
102     step = (TestStepWait *)(*it);
103     if (step->getCondition() == condition) return true;
104   }
105   return false;
106 }
107
108
109 void TestCase::setState(const State::_v &state) throw() {
110
111   State::_v previousState = a_state;
112   if (state == previousState) return;
113   a_state = state;
114   TestManager &testManager = TestManager::instantiate();
115   if (isFinished()) {
116     if (!testManager.getDumpReports()) return;
117     // report file name: cycle-<cycle id>.testcase-<test case id>.xml
118
119     // FORMAT: We tabulate the cycle and test case in order to ease ordering of files by mean ls:
120     int cycles = testManager.getPoolRepeats();
121     int tests = testManager.tests();
122     int cyclesWidth = (cycles<=0) ? 3 /* 1000 cycles !! */: ((int) log10 ((double) cycles) + 1);
123     int testsWidth = (tests<=0) ? 9 /* subscribers */: ((int) log10 ((double) tests) + 1);
124     std::stringstream format;
125     format << "/cycle-%0" << cyclesWidth << "d.testcase-%0" << testsWidth << "llu.xml";
126
127     // FILE NAME:
128     std::string file = testManager.getReportsDirectory() + anna::functions::asString(format.str().c_str(), testManager.getPoolCycle(), a_id);
129     std::ofstream out;
130     out.open(file.c_str(), std::ofstream::out | std::ofstream::app);
131     if(out.is_open() == false) {
132       std::string msg("Error opening '");
133       msg += file;
134       msg += "' for writting";
135       anna::Logger::error(msg, ANNA_FILE_LOCATION);
136     }
137     else {
138       out << asXMLString() << std::endl;
139       out.close();
140     }
141   }
142
143   // Count in-progress test cases:
144   if (inProgress()) {
145     testManager.setInProgressCountDelta(1);
146   }
147   else if (previousState == State::InProgress){
148     testManager.setInProgressCountDelta(-1);
149   }
150 }
151
152 bool TestCase::done() throw() {
153   if (a_stepsIt == a_steps.end()) {
154     setState(State::Success);
155     return true;
156   }
157
158   return false;
159 }
160
161 bool TestCase::process() throw() {
162   if (a_steps.size() == 0) {
163     LOGWARNING(anna::Logger::warning(anna::functions::asString("Test case %llu is empty, nothing to execute", a_id), ANNA_FILE_LOCATION));
164     return false;
165   }
166   if (isFinished()) {
167     LOGDEBUG(anna::Logger::debug(anna::functions::asString("Test case %llu is finished, nothing done until soft-reset", a_id), ANNA_FILE_LOCATION));
168     return false;
169   }
170
171   if (a_state == State::Initialized) {
172     a_stepsIt = a_steps.begin();
173     setState(State::InProgress);
174
175     // For 'wait' steps (not really useful, but better than nothing: begin timestamp on test case start timestamp...):
176     a_startTime = anna::functions::millisecond();
177   }
178
179   // Check end of the test case:
180   if (done()) return false;
181
182   bool somethingDone = false;
183   while ((*a_stepsIt)->execute()) { // executes returns 'true' if the next step must be also executed (execute until can't stand no more)
184     nextStep();
185     // Check end of the test case:
186     if (done()) return false;
187     somethingDone = true;
188   }
189
190   return somethingDone;
191 }
192
193 bool TestCase::reset(bool hard) throw() {
194
195   // Soft reset if finished:
196   if (!hard /* is soft reset */  && !isFinished()) return false;
197
198   // Clean stage ////////////////////////////
199   // id is kept
200   std::vector<TestStep*>::iterator it;
201   for (it = a_steps.begin(); it != a_steps.end(); it++)
202     (*it)->reset();
203
204   a_debugSummary.clear();
205   a_startTime = 0;
206
207   setState(State::Initialized);
208
209   return true;
210 }
211
212 void TestCase::assertInitialized() const throw(anna::RuntimeException) {
213   if (a_state != State::Initialized)
214     throw anna::RuntimeException(anna::functions::asString("Cannot program anymore. The test case %llu was started. You must reset it to append new steps.", a_id), ANNA_FILE_LOCATION);
215 }
216
217 void TestCase::assertMessage(const anna::DataBlock &db, bool toEntity) throw(anna::RuntimeException) {
218
219   bool isRequest = anna::diameter::codec::functions::isRequest(db);
220   bool registerSessionId = ((isRequest && toEntity) || (!isRequest && !toEntity) /* (*) */);
221   // (*) we register answers Session-Id assuming that we will know the Session-Id values created by the client (OCS)
222   // This is another solution for TODO(***) regarding diameter server testing. No tsure about the final implementation.
223
224   // Check hop-by-hop:
225   if (isRequest) {
226     anna::diameter::HopByHop hbh = anna::diameter::codec::functions::getHopByHop(db);
227     if (a_hopByHops.find(hbh) != a_hopByHops.end())
228       throw anna::RuntimeException(anna::functions::asString("Another request has been programmed with the same hop-by-hop (%llu) in this test case (%llu)", hbh, a_id), ANNA_FILE_LOCATION);
229     a_hopByHops[hbh] = NULL; // may be assigned to a wait condition
230   }
231
232   if (registerSessionId)
233     TestManager::instantiate().registerSessionId(anna::diameter::helpers::base::functions::getSessionId(db), this);
234 }
235
236 void TestCase::addTimeout(const anna::Millisecond &timeout) throw(anna::RuntimeException) {
237   assertInitialized();
238   TestStepTimeout *step = new TestStepTimeout(this);
239   step->setTimeout(timeout);
240   a_steps.push_back(step);
241 }
242
243 void TestCase::addSendxml2e(const anna::DataBlock &db, RealmNode *realm, int stepNumber) throw(anna::RuntimeException) {
244   assertInitialized();
245   assertMessage(db, true /* to entity */);
246
247   if (stepNumber != -1) {
248     int steps = a_steps.size();
249     int stepIndx = stepNumber - 1;
250     if ((stepIndx < 0) || (stepIndx > (a_steps.size()-1)))
251       throw anna::RuntimeException(anna::functions::asString("Step number (%d) out of range (test case %llu)", stepNumber, a_id), ANNA_FILE_LOCATION);
252
253     TestStep *stepReferred = a_steps[stepIndx];
254     if (stepReferred->getType() != TestStep::Type::Wait)
255       throw anna::RuntimeException(anna::functions::asString("Step number (%d) must refer to a 'wait' step (test case %llu)", stepNumber, a_id), ANNA_FILE_LOCATION);
256
257     const TestCondition &tc = (static_cast<TestStepWait*>(stepReferred))->getCondition();
258     if (tc.getCode() == "0") { // if regexp used, is not possible to detect this kind of errors
259       throw anna::RuntimeException(anna::functions::asString("Step number (%d) must refer to a 'wait for request' step (test case %llu)", stepNumber, a_id), ANNA_FILE_LOCATION);
260     }
261   }
262
263   TestStepSendxml2e *step = new TestStepSendxml2e(this);
264   step->setMsgDataBlock(db);
265   step->setRealmNode(realm);
266   step->setWaitForRequestStepNumber(stepNumber); // -1 means, no reference
267   a_steps.push_back(step);
268 }
269
270 void TestCase::addSendxml2c(const anna::DataBlock &db, RealmNode *realm, int stepNumber) throw(anna::RuntimeException) {
271   assertInitialized();
272   assertMessage(db, false /* to client */);
273
274   TestStepSendxml2c *step = new TestStepSendxml2c(this);
275   step->setMsgDataBlock(db);
276   step->setRealmNode(realm);
277   a_steps.push_back(step);
278 }
279
280 void TestCase::addDelay(const anna::Millisecond &delay) throw(anna::RuntimeException) {
281   assertInitialized();
282   TestStepDelay *step = new TestStepDelay(this);
283   step->setDelay(delay);
284   a_steps.push_back(step);
285 }
286
287 void TestCase::addWait(bool fromEntity,
288               const std::string &code, const std::string &bitR, const std::string &hopByHop, const std::string &applicationId,
289               const std::string &sessionId, const std::string &resultCode,
290               const std::string &msisdn, const std::string &imsi, const std::string &serviceContextId) throw(anna::RuntimeException) {
291   assertInitialized();
292   std::string usedHopByHop = hopByHop;
293   TestStepWait *step = NULL;
294
295   // Check basic conditions:
296   if (bitR == "1") {
297     if (resultCode != "")
298       throw anna::RuntimeException(anna::functions::asString("You cannot specify Result-Code (%s) for a wait condition of a diameter request message (test case %llu)", resultCode.c_str(), a_id), ANNA_FILE_LOCATION);
299     if (hopByHop != "")
300       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)", hopByHop.c_str(), a_id), ANNA_FILE_LOCATION);
301   }
302   else {
303     if (hopByHop != "") {
304       if (hopByHop[0] == '#') {
305         int steps = a_steps.size();
306         if (steps == 0)
307           throw anna::RuntimeException(anna::functions::asString("No steps has been programmed, step reference is nonsense (test case %llu)", a_id), ANNA_FILE_LOCATION);
308
309         int stepNumber = atoi(hopByHop.substr(1).c_str());
310         int stepIndx = stepNumber - 1;
311         if ((stepIndx < 0) || (stepIndx > (steps-1)))
312           throw anna::RuntimeException(anna::functions::asString("Step reference number %d out of range [1-%d]", stepNumber, steps), ANNA_FILE_LOCATION);
313
314         TestStep *stepReferred = a_steps[stepIndx];
315         if (stepReferred->getType() != TestStep::Type::Sendxml2e && stepReferred->getType() != TestStep::Type::Sendxml2c)
316           throw anna::RuntimeException(anna::functions::asString("Step number must refer to a 'sendxml2e' or 'sendxml2c' step (test case %llu)", a_id), ANNA_FILE_LOCATION);
317
318         const anna::DataBlock &db = (static_cast<TestStepSendxml*>(stepReferred))->getMsgDataBlock();
319         bool isAnswer = anna::diameter::codec::functions::isAnswer(db);
320         if (isAnswer)
321           throw anna::RuntimeException(anna::functions::asString("Step number must refer to a request message (test case %llu)", a_id), ANNA_FILE_LOCATION);
322
323         // Hop-by-hop:
324         anna::diameter::HopByHop hbh = anna::diameter::codec::functions::getHopByHop(db);
325         usedHopByHop = anna::functions::asString(hbh);
326         step = new TestStepWait(this);
327         a_hopByHops[hbh /* always exists: is the info we calculated above */] = step;
328       }
329     }
330   }
331
332   if (!step) step = new TestStepWait(this);
333   step->setCondition(fromEntity, code, bitR, usedHopByHop, applicationId, sessionId, resultCode, msisdn, imsi, serviceContextId);
334
335   LOGWARNING(
336     if (hasSameCondition(step->getCondition()))
337       anna::Logger::warning(anna::functions::asString("The same wait condition has already been programmed in this test case (%llu). Are you sure ?", a_id), ANNA_FILE_LOCATION);
338   );
339
340   a_steps.push_back(step);
341 }
342
343 void TestCase::addWaitRegexp(bool fromEntity, const std::string &regexp) throw(anna::RuntimeException) {
344   assertInitialized();
345
346   TestStepWait *step = new TestStepWait(this);
347   step->setCondition(fromEntity, regexp);
348
349   LOGWARNING(
350     if (hasSameCondition(step->getCondition()))
351       anna::Logger::warning(anna::functions::asString("The same wait condition has already been programmed in this test case (%llu). Are you sure ?", a_id), ANNA_FILE_LOCATION);
352   );
353
354   a_steps.push_back(step);
355 }
356
357 void TestCase::addCmd(const std::string &script, const std::string &parameters) throw(anna::RuntimeException) {
358   assertInitialized();
359
360   TestStepCmd *step = new TestStepCmd(this);
361   step->setScript(script);
362   step->setParameters(parameters);
363
364   a_steps.push_back(step);
365 }
366
367 TestStepWait *TestCase::searchNextWaitConditionFulfilled(const anna::DataBlock &message, bool waitFromEntity) throw() {
368
369   TestStepWait *result;
370   for (std::vector<TestStep*>::const_iterator it = a_stepsIt /* current */; it != a_steps.end(); it++) {
371     if ((*it)->getType() != TestStep::Type::Wait) continue;
372     if ((*it)->isCompleted()) continue;
373     result = (TestStepWait*)(*it);
374     if ((result->getCondition().receivedFromEntity() == waitFromEntity) && (result->fulfilled(message)))
375       return result;
376   }
377
378   return NULL;
379 }
380
381 const TestStep *TestCase::getStep(int stepNumber) const throw() {
382   int stepIndx = stepNumber - 1;
383   if ((stepIndx < 0) || (stepIndx > (a_steps.size()-1))) return NULL;
384   std::vector<TestStep*>::const_iterator it = (a_steps.begin() + stepNumber);
385   return (*it);
386 }