Ignore ECHILD during system call on thread
[anna.git] / example / diameter / launcher / testing / TestStep.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 <iostream>
12 #include <stdio.h>
13 #include <errno.h>
14
15 // Project
16 #include <anna/xml/Compiler.hpp>
17 #include <anna/core/util/Millisecond.hpp>
18 #include <anna/diameter.comm/Message.hpp>
19 #include <anna/core/tracing/Logger.hpp>
20 #include <anna/diameter/codec/functions.hpp>
21 #include <anna/diameter/helpers/base/functions.hpp>
22
23 // Process
24 #include <RealmNode.hpp>
25 #include <MyDiameterEntity.hpp>
26 #include <MyLocalServer.hpp>
27 #include <TestStep.hpp>
28 #include <Launcher.hpp>
29 #include <TestCase.hpp>
30 #include <TestManager.hpp>
31 #include <TestTimer.hpp>
32
33
34 namespace {
35   void cmdRunOnThread (TestStepCmd *step, const std::string &cmd) {
36     step->setThreadRunning(true);
37     int rc = system(cmd.c_str());
38     if (rc < 0 && errno == ECHILD) rc = 0; // ignore, it could happens
39     // I know one reason for this is that SICCHLD is set to SIG_IGN but this
40     // should not be the case here. SIGCHLD is explicity set to SIG_DFL
41     // using a sigaction before the call to system(). (Although it is
42     // normally set to SIG_IGN). There should not be any other threads
43     // messing about with SIGCHLD.
44
45     if (rc < 0) {
46       step->setErrorMsg(anna::functions::asString("errno = %d", errno));
47       //std::terminate;
48     }
49     else {
50       rc >>= 8; // divide by 256
51     }
52
53     step->setResultCode(rc);
54     step->complete();
55     // TODO: timeout the system call
56   }
57 }
58
59
60 ////////////////////////////////////////////////////////////////////////////////////////////////////////
61 // TestStep
62 ////////////////////////////////////////////////////////////////////////////////////////////////////////
63 void TestStep::initialize(TestCase *testCase) {
64   a_testCase = testCase;
65   a_completed = false;
66   a_type = Type::Unconfigured;
67   a_beginTimestamp = 0;
68   a_endTimestamp = 0;
69   a_number = testCase->steps() + 1; // testCase is not NULL
70 }
71
72 const char* TestStep::asText(const Type::_v type)
73 throw() {
74   static const char* text [] = { "Unconfigured", "Timeout", "Sendxml2e", "Sendxml2c", "Delay", "Wait", "Cmd" };
75   return text [type];
76 }
77
78 anna::xml::Node* TestStep::asXML(anna::xml::Node* parent) const
79 throw() {
80   anna::xml::Node* result = parent->createChild("TestStep");
81
82   result->createAttribute("Number", a_number);
83   result->createAttribute("Type", asText(a_type));
84   result->createAttribute("Completed", (a_completed ? "yes":"no"));
85
86   // Begin
87   std::string s_aux = a_beginTimestamp.asString();
88 //  int deltaMs = (int)(a_beginTimestamp - a_testCase->getStartTimestamp());
89 //  if (a_beginTimestamp != 0 && deltaMs > 0) s_aux += anna::functions::asString(" [%.3f]", deltaMs/1000.0);
90   result->createAttribute("BeginTimestamp", s_aux);
91
92   // End
93   s_aux = a_endTimestamp.asString();
94 //  deltaMs = (int)(a_endTimestamp - a_testCase->getStartTimestamp());
95 //  if (a_endTimestamp != 0 && deltaMs > 0) s_aux += anna::functions::asString(" [%.3f]", deltaMs/1000.0);
96   result->createAttribute("EndTimestamp", s_aux);
97
98   return result;
99 }
100
101 std::string TestStep::asXMLString() const throw() {
102   anna::xml::Node root("root");
103   return anna::xml::Compiler().apply(asXML(&root));
104 }
105
106 bool TestStep::execute() throw() {
107   LOGDEBUG(anna::Logger::debug(anna::functions::asString("EXECUTING %s (step number %d) for Test Case %llu (%p) (%p)", asText(a_type), a_number, a_testCase->getId(), (TestCaseStep*)this, this), ANNA_FILE_LOCATION));
108   setBeginTimestamp(anna::functions::millisecond());
109   return do_execute();
110 }
111
112 void TestStep::complete() throw() {
113   LOGDEBUG(anna::Logger::debug(anna::functions::asString("COMPLETE %s (step number %d) for Test Case %llu (%p) (%p)", asText(a_type), a_number, a_testCase->getId(), (TestCaseStep*)this, this), ANNA_FILE_LOCATION));
114   a_completed = true;
115   setEndTimestamp(anna::functions::millisecond());
116   do_complete();
117 }
118
119 void TestStep::reset() throw() {
120   LOGDEBUG(anna::Logger::debug(anna::functions::asString("RESET %s (step number %d) for Test Case %llu (%p) (%p)", asText(a_type), a_number, a_testCase->getId(), (TestCaseStep*)this, this), ANNA_FILE_LOCATION));
121   // type and testCase kept
122   a_completed = false;
123   a_beginTimestamp = 0;
124   a_endTimestamp = 0;
125   do_reset();
126 }
127
128 void TestStep::next() throw() {
129   a_testCase->nextStep();
130   a_testCase->process();
131 }
132
133
134 ////////////////////////////////////////////////////////////////////////////////////////////////////////
135 // TestStepTimeout
136 ////////////////////////////////////////////////////////////////////////////////////////////////////////
137 anna::xml::Node* TestStepTimeout::asXML(anna::xml::Node* parent) const
138 throw() {
139   anna::xml::Node* result = TestStep::asXML(parent); // end timestamp will be 0 if test finished OK
140   //parent->createChild("TestStepTimeout");
141   result->createAttribute("Timeout", a_timeout.asString());
142
143   return result;
144 }
145
146 bool TestStepTimeout::do_execute() throw() {
147   try {
148     a_timer = TestManager::instantiate().createTimer((TestCaseStep*)this, a_timeout, TestTimer::Type::TimeLeft);
149   }
150   catch (anna::RuntimeException &ex) {
151     ex.trace();
152     a_testCase->addDebugSummaryHint(ex.asString()); // before report (when set Failed state)
153     a_testCase->setState(TestCase::State::Failed);
154   }
155
156   return true; // go next
157 }
158
159 void TestStepTimeout::do_complete() throw() {
160   int stepNumber = getNumber();
161   if (stepNumber == a_testCase->steps()) {
162     a_testCase->addDebugSummaryHint(anna::functions::asString("Timeout expired (step number %d) but it was the last test case step", stepNumber)); // before report (when set Failed state)
163     a_testCase->setState(TestCase::State::Success);
164   }
165   else if (a_testCase->getState() == TestCase::State::InProgress) { // sure
166     a_testCase->addDebugSummaryHint(anna::functions::asString("Timeout expired (step number %d) before test case finished", stepNumber)); // before report (when set Failed state)
167     a_testCase->setState(TestCase::State::Failed);
168   }
169
170   a_timer = NULL;
171 }
172
173 void TestStepTimeout::do_reset() throw() {
174   LOGDEBUG(anna::Logger::debug(anna::functions::asString("REXXXXXET %s for Test Case %llu (%p) (%p) (a_timer %p)", asText(a_type), a_testCase->getId(), (TestCaseStep*)this, this, a_timer), ANNA_FILE_LOCATION));
175
176   try {
177     TestManager::instantiate().cancelTimer(a_timer);
178   }
179   catch (anna::RuntimeException &ex) {
180     ex.trace();
181   }
182   a_timer = NULL;
183   //a_timeout = 0; THIS IS CONFIGURATION INFO
184 }
185
186 ////////////////////////////////////////////////////////////////////////////////////////////////////////
187 // TestStepSendxml
188 ////////////////////////////////////////////////////////////////////////////////////////////////////////
189 anna::xml::Node* TestStepSendxml::asXML(anna::xml::Node* parent) const
190 throw() {
191   anna::xml::Node* result = TestStep::asXML(parent);
192   //parent->createChild("TestStepSendxml");
193   std::string msg = "", xmlmsg = "";
194
195   // Message
196   if (TestManager::instantiate().getDumpHex()) {
197     if (a_message.isEmpty()) {
198       msg = "<empty>";
199     }
200     else {
201       msg = "\n"; msg += a_message.asString(); msg += "\n";
202     }
203   }
204
205   if (!a_message.isEmpty()) {
206     try {
207       Launcher& my_app = static_cast <Launcher&>(anna::app::functions::getApp());
208       static anna::diameter::codec::Message codecMsg(my_app.getCodecEngine());
209       codecMsg.decode(a_message);
210       xmlmsg = "\n"; xmlmsg += codecMsg.asXMLString(); xmlmsg += "\n";
211     }
212     catch (anna::RuntimeException &ex) {
213       ex.trace();
214     }
215   }
216
217   if (msg != "") result->createAttribute("Message", msg);
218   if (xmlmsg != "") result->createAttribute("XMLMessage", xmlmsg);
219   result->createAttribute("Expired", (a_expired ? "yes":"no"));
220   if (a_waitForRequestStepNumber != -1)
221     result->createAttribute("WaitForRequestStepNumber", a_waitForRequestStepNumber);
222
223   return result;
224 }
225
226 bool TestStepSendxml::do_execute() throw() {
227   anna::diameter::comm::Message *msg = a_realmNode->createCommMessage();
228   bool success = false;
229   std::string failReason, s_warn;
230
231   try {
232     // Update sequence for answers:
233     if (a_waitForRequestStepNumber != -1) { // is an answer: try to copy sequence information; alert about Session-Id discrepance
234       // Request which was received:
235       const TestStepWait *tsw = (const TestStepWait*)(a_testCase->getStep(a_waitForRequestStepNumber));
236       const anna::DataBlock &request = tsw->getMsgDataBlock();
237       anna::diameter::HopByHop hbh = anna::diameter::codec::functions::getHopByHop(request);
238       anna::diameter::EndToEnd ete = anna::diameter::codec::functions::getEndToEnd(request);
239       // Update sequence:
240       anna::diameter::codec::functions::setHopByHop(a_message, hbh);
241       anna::diameter::codec::functions::setEndToEnd(a_message, ete);
242
243       // Check Session-Id for warning ...
244       std::string sessionIdAnswer = anna::diameter::helpers::base::functions::getSessionId(a_message);
245       std::string sessionIdRequest = anna::diameter::helpers::base::functions::getSessionId(request);
246       if (sessionIdRequest != sessionIdAnswer) {
247         s_warn = anna::functions::asString("Sending an answer which Session-Id (%s) is different than supposed corresponding request (%s)", sessionIdAnswer.c_str(), sessionIdRequest.c_str());
248         LOGWARNING(anna::Logger::warning(s_warn, ANNA_FILE_LOCATION));
249         a_testCase->addDebugSummaryHint(s_warn);
250       }
251     }
252
253     if (getType() == Type::Sendxml2e) {
254       MyDiameterEntity *entity = a_realmNode->getEntity();
255       if (entity) {
256         //msg->clearBody();
257         msg->setBody(a_message);
258         /* response = NULL =*/entity->send(msg);
259         success = true;
260       }
261       else {
262         failReason = "There is no diameter entity currently configured. Unable to send the message";
263         LOGWARNING(anna::Logger::warning(failReason, ANNA_FILE_LOCATION));
264       }
265     }
266     else if (getType() == Type::Sendxml2c) {
267       MyLocalServer *localServer = a_realmNode->getDiameterServer();
268       if (localServer) {
269         //msg->clearBody();
270         msg->setBody(a_message);
271         /* response = NULL =*/localServer->send(msg);
272         success = true;
273       }
274       else {
275         failReason = "There is no diameter local server currently configured. Unable to send the message";
276         LOGWARNING(anna::Logger::warning(failReason, ANNA_FILE_LOCATION));
277       }
278     }
279   } catch(anna::RuntimeException &ex) {
280     failReason = ex.asString();
281   }
282
283   // release msg
284   a_realmNode->releaseCommMessage(msg);
285
286   if (!success) {
287     a_testCase->addDebugSummaryHint(failReason); // before report (when set Failed state);
288     a_testCase->setState(TestCase::State::Failed);
289   }
290   else {
291     complete();
292   }
293
294   return success; // go next if sent was OK
295 }
296
297 void TestStepSendxml::do_reset() throw() {
298   a_expired = false;
299   //a_message.clear();
300 }
301
302 ////////////////////////////////////////////////////////////////////////////////////////////////////////
303 // TestStepDelay
304 ////////////////////////////////////////////////////////////////////////////////////////////////////////
305 anna::xml::Node* TestStepDelay::asXML(anna::xml::Node* parent) const
306 throw() {
307   anna::xml::Node* result = TestStep::asXML(parent);
308   //parent->createChild("TestStepDelay");
309
310   result->createAttribute("Delay", a_delay.asString());
311
312   return result;
313 }
314
315 bool TestStepDelay::do_execute() throw() {
316   try {
317     a_timer = TestManager::instantiate().createTimer((TestCaseStep*)this, a_delay, TestTimer::Type::Delay);
318   }
319   catch (anna::RuntimeException &ex) {
320     ex.trace();
321     a_testCase->addDebugSummaryHint(ex.asString()); // before report (when set Failed state)
322     a_testCase->setState(TestCase::State::Failed);
323   }
324
325   return false; // don't go next (wait complete)
326 }
327
328 void TestStepDelay::do_complete() throw() {
329   a_timer = NULL;
330   next(); // next() invoked here because execute() is always false for delay and never advance the iterator
331 }
332
333 void TestStepDelay::do_reset() throw() {
334   try {
335     TestManager::instantiate().cancelTimer(a_timer);
336   }
337   catch (anna::RuntimeException &ex) {
338     ex.trace();
339   }
340   a_timer = NULL;
341   //a_delay = 0; THIS IS CONFIGURATION INFO
342 }
343
344 ////////////////////////////////////////////////////////////////////////////////////////////////////////
345 // TestStepWait
346 ////////////////////////////////////////////////////////////////////////////////////////////////////////
347 void TestStepWait::setCondition(bool fromEntity,
348                                   const std::string &code, const std::string &bitR, const std::string &resultCode, const std::string &sessionId,
349                                   const std::string &hopByHop, const std::string &msisdn, const std::string &imsi, const std::string &serviceContextId) throw() {
350
351   a_condition.setReceivedFromEntity(fromEntity);
352   a_condition.setCode(code);
353   a_condition.setBitR(bitR);
354   a_condition.setResultCode(resultCode);
355   a_condition.setSessionId(sessionId);
356   a_condition.setHopByHop(hopByHop);
357   a_condition.setMsisdn(msisdn);
358   a_condition.setImsi(imsi);
359   a_condition.setServiceContextId(serviceContextId);
360 }
361
362 void TestStepWait::setCondition(bool fromEntity, const std::string &regexp) throw() {
363
364   a_condition.setReceivedFromEntity(fromEntity);
365   a_condition.setRegexp(regexp);
366 }
367
368 anna::xml::Node* TestStepWait::asXML(anna::xml::Node* parent) const
369 throw() {
370   anna::xml::Node* result = TestStep::asXML(parent);
371   //parent->createChild("TestStepWait");
372   std::string msg = "", xmlmsg = "";
373
374   // Condition
375   a_condition.asXML(result);
376
377   // Message
378   if (TestManager::instantiate().getDumpHex()) {
379     if (a_message.isEmpty()) {
380       msg = "<empty>";
381     }
382     else {
383       msg = "\n"; msg += a_message.asString(); msg += "\n";
384     }
385   }
386
387   if (!a_message.isEmpty()) {
388     try {
389       Launcher& my_app = static_cast <Launcher&>(anna::app::functions::getApp());
390       static anna::diameter::codec::Message codecMsg(my_app.getCodecEngine());
391       codecMsg.decode(a_message);
392       xmlmsg = "\n"; xmlmsg += codecMsg.asXMLString(); xmlmsg += "\n";
393     }
394     catch (anna::RuntimeException &ex) {
395       ex.trace();
396     }
397   }
398
399   if (msg != "") result->createAttribute("MatchedMessage", msg);
400   if (xmlmsg != "") result->createAttribute("MatchedXMLMessage", xmlmsg);
401
402   return result;
403 }
404
405 bool TestStepWait::do_execute() throw() {
406   return a_completed;
407 }
408
409 void TestStepWait::do_complete() throw() {
410   a_testCase->process(); // next() not invoked; we only want to reactivate the test case
411 }
412
413 bool TestStepWait::fulfilled(const anna::DataBlock &db/*, bool matchSessionId*/) throw() {
414   if (a_condition.comply(db/*, matchSessionId*/)) {
415     a_message = db; // store matched
416     complete();
417     return true;
418   }
419
420   return false;
421 }
422
423 void TestStepWait::do_reset() throw() {
424   a_message.clear();
425   a_clientSession = NULL;
426   a_serverSession = NULL;
427 }
428
429 ////////////////////////////////////////////////////////////////////////////////////////////////////////
430 // TestStepCmd
431 ////////////////////////////////////////////////////////////////////////////////////////////////////////
432 anna::xml::Node* TestStepCmd::asXML(anna::xml::Node* parent) const
433 throw() {
434   anna::xml::Node* result = TestStep::asXML(parent);
435   //parent->createChild("TestStepCmd");
436
437   result->createAttribute("Script", (a_script != "") ? a_script:"<no script>");
438   result->createAttribute("Parameters", (a_parameters != "") ? a_parameters:"<no parameters>");
439   result->createAttribute("CommandInProgress", a_threadRunning ? "yes":"no");
440   if (a_errorMsg != "") result->createAttribute("ErrorMessage", a_errorMsg);
441   if (!a_threadRunning && a_resultCode != -2) {
442     result->createAttribute("ResultCode", a_resultCode);
443     //if (a_output != "") result->createAttribute("Output", a_output);
444   }
445
446   return result;
447 }
448
449 bool TestStepCmd::do_execute() throw() {
450   if (!a_threadRunning) {
451     // Special tags to replace:
452     std::string cmd = getScript();
453     cmd += " ";
454     cmd += getParameters();
455     size_t index;
456     while ((index = cmd.find(SH_COMMAND_TAG_FOR_REPLACE__CYCLE_ID)) != std::string::npos)
457       cmd.replace(index, strlen(SH_COMMAND_TAG_FOR_REPLACE__CYCLE_ID), anna::functions::asString(TestManager::instantiate().getPoolCycle()));
458     while ((index = cmd.find(SH_COMMAND_TAG_FOR_REPLACE__TESTCASE_ID)) != std::string::npos)
459       cmd.replace(index, strlen(SH_COMMAND_TAG_FOR_REPLACE__TESTCASE_ID), anna::functions::asString(a_testCase->getId()));
460     while ((index = cmd.find(SH_COMMAND_TAG_FOR_REPLACE__TESTSTEP_ID)) != std::string::npos)
461       cmd.replace(index, strlen(SH_COMMAND_TAG_FOR_REPLACE__TESTSTEP_ID), anna::functions::asString(getNumber()));
462
463     a_thread = std::thread(cmdRunOnThread, this, cmd);
464     a_thread.detach();
465   }
466
467   return false; // don't go next (wait complete)
468 }
469
470 void TestStepCmd::do_complete() throw() {
471
472   a_threadRunning = false;
473   if (a_threadDeprecated) {
474     a_threadDeprecated = false;
475     return; // ignore TODO: interrupt the thread to avoid execution of the script
476   }
477
478   if (getResultCode() != 0)
479     a_testCase->setState(TestCase::State::Failed);
480   else
481     next(); // next() invoked here because execute() is always false for delay and never advance the iterator
482 }
483
484 void TestStepCmd::do_reset() throw() {
485
486   if (a_threadRunning) {
487     std::string s_warn = anna::functions::asString("Thread still in progress: deprecating step %d for Test Case %llu", getNumber(), a_testCase->getId());
488     LOGWARNING(anna::Logger::warning(s_warn, ANNA_FILE_LOCATION));
489     a_threadDeprecated = true;
490   }
491
492   a_resultCode = -2;
493   //a_output = "";
494 }
495