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