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