Command execution for system test cases
[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 out of range (test case %llu)", 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 must refer to a 'wait' step (test case %llu)", 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 must refer to a 'wait for request' step (test case %llu)", 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 &resultCode, const std::string &sessionId,
289                         const std::string &hopByHop, const std::string &msisdn, const std::string &imsi, const std::string &serviceContextId) throw(anna::RuntimeException) {
290   assertInitialized();
291   std::string usedHopByHop = hopByHop;
292   TestStepWait *step = NULL;
293
294   // Check basic conditions:
295   if (bitR == "1") {
296     if (resultCode != "")
297       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);
298     if (hopByHop != "")
299       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);
300   }
301   else {
302     if (hopByHop != "") {
303       if (hopByHop[0] == '#') {
304         int steps = a_steps.size();
305         if (steps == 0)
306           throw anna::RuntimeException(anna::functions::asString("No steps has been programmed, step reference is nonsense (test case %llu)", a_id), ANNA_FILE_LOCATION);
307
308         int stepNumber = atoi(hopByHop.substr(1).c_str());
309         int stepIndx = stepNumber - 1;
310         if ((stepIndx < 0) || (stepIndx > (steps-1)))
311           throw anna::RuntimeException(anna::functions::asString("Step reference number %d out of range [1-%d]", stepNumber, steps), ANNA_FILE_LOCATION);
312
313         TestStep *stepReferred = a_steps[stepIndx];
314         if (stepReferred->getType() != TestStep::Type::Sendxml2e && stepReferred->getType() != TestStep::Type::Sendxml2c)
315           throw anna::RuntimeException(anna::functions::asString("Step number must refer to a 'sendxml2e' or 'sendxml2c' step (test case %llu)", a_id), ANNA_FILE_LOCATION);
316
317         const anna::DataBlock &db = (static_cast<TestStepSendxml*>(stepReferred))->getMsgDataBlock();
318         bool isAnswer = anna::diameter::codec::functions::isAnswer(db);
319         if (isAnswer)
320           throw anna::RuntimeException(anna::functions::asString("Step number must refer to a request message (test case %llu)", a_id), ANNA_FILE_LOCATION);
321
322         // Hop-by-hop:
323         anna::diameter::HopByHop hbh = anna::diameter::codec::functions::getHopByHop(db);
324         usedHopByHop = anna::functions::asString(hbh);
325         step = new TestStepWait(this);
326         a_hopByHops[hbh /* always exists: is the info we calculated above */] = step;
327       }
328     }
329   }
330
331   if (!step) step = new TestStepWait(this);
332   step->setCondition(fromEntity, code, bitR, resultCode, sessionId, usedHopByHop, msisdn, imsi, serviceContextId);
333
334   LOGWARNING(
335     if (hasSameCondition(step->getCondition()))
336       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);
337   );
338
339   a_steps.push_back(step);
340 }
341
342 void TestCase::addWaitRegexp(bool fromEntity, const std::string &regexp) throw(anna::RuntimeException) {
343   assertInitialized();
344
345   TestStepWait *step = new TestStepWait(this);
346   step->setCondition(fromEntity, regexp);
347
348   LOGWARNING(
349     if (hasSameCondition(step->getCondition()))
350       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);
351   );
352
353   a_steps.push_back(step);
354 }
355
356 void TestCase::addCmd(const std::string &script, const std::string &parameters) throw(anna::RuntimeException) {
357   assertInitialized();
358
359   TestStepCmd *step = new TestStepCmd(this);
360   step->setScript(script);
361   step->setParameters(parameters);
362
363   a_steps.push_back(step);
364 }
365
366 TestStepWait *TestCase::searchNextWaitConditionFulfilled(const anna::DataBlock &message, bool waitFromEntity) throw() {
367
368   TestStepWait *result;
369   for (std::vector<TestStep*>::const_iterator it = a_stepsIt /* current */; it != a_steps.end(); it++) {
370     if ((*it)->getType() != TestStep::Type::Wait) continue;
371     if ((*it)->isCompleted()) continue;
372     result = (TestStepWait*)(*it);
373     if ((result->getCondition().receivedFromEntity() == waitFromEntity) && (result->fulfilled(message)))
374       return result;
375   }
376
377   return NULL;
378 }
379
380 const TestStep *TestCase::getStep(int stepNumber) const throw() {
381   int stepIndx = stepNumber - 1;
382   if ((stepIndx < 0) || (stepIndx > (a_steps.size()-1))) return NULL;
383   std::vector<TestStep*>::const_iterator it = (a_steps.begin() + stepNumber);
384   return (*it);
385 }