Command execution for system test cases
authorEduardo Ramos Testillano <eduardo.ramos.testillano@ericsson.com>
Thu, 17 Sep 2015 11:44:07 +0000 (13:44 +0200)
committerEduardo Ramos Testillano <eduardo.ramos.testillano@ericsson.com>
Thu, 17 Sep 2015 11:44:07 +0000 (13:44 +0200)
example/diameter/launcher/Launcher.cpp
example/diameter/launcher/Launcher.hpp
example/diameter/launcher/resources/scripts/operation_signal.sh
example/diameter/launcher/testing/TestCase.cpp
example/diameter/launcher/testing/TestCase.hpp
example/diameter/launcher/testing/TestManager.cpp
example/diameter/launcher/testing/TestManager.hpp
example/diameter/launcher/testing/TestStep.cpp
example/diameter/launcher/testing/TestStep.hpp

index 041e7a9..4452ee4 100644 (file)
@@ -1078,6 +1078,24 @@ std::string Launcher::help() const throw() {
   result += "\n                                                          buffer search. The main advantage is the great flexibility to identify";
   result += "\n                                                          any content with a regular expression.";
   result += "\n";
+  result += "\n                              sh-command|<script>[|parameters]";
+  result += "\n                                                          External execution for script/executable via shell through an";
+  result += "\n                                                          independent thread, providing the script name and the parameters.";
+  result += "\n                                                          You could use dynamic variables ##<tag> to have more flexibility:";
+  result += "\n                                                             Test pool cycle id: "; result += SH_COMMAND_TAG_FOR_REPLACE__CYCLE_ID;
+  result += "\n                                                             Test case id:       "; result += SH_COMMAND_TAG_FOR_REPLACE__TESTCASE_ID;
+  result += "\n                                                             Test step id:       "; result += SH_COMMAND_TAG_FOR_REPLACE__TESTSTEP_ID;
+  result += "\n";
+  result += "\n                                                          For example, your command could be something like this:";
+  result += "\n                                                             script:     insert_sql.sh";
+  result += "\n                                                             parameters: -db dbname --verbose > /tmp/cycle-"; result += SH_COMMAND_TAG_FOR_REPLACE__CYCLE_ID; result += ".";
+  result += "\n                                                                            testcase-"; result += SH_COMMAND_TAG_FOR_REPLACE__TESTCASE_ID; result += ".teststep-"; result += SH_COMMAND_TAG_FOR_REPLACE__TESTSTEP_ID; result += ".out";
+  result += "\n";
+  result += "\n                                                          You could also make substitutions on script name: insert_sql_"; result += SH_COMMAND_TAG_FOR_REPLACE__TESTCASE_ID; result += ".sh";
+  result += "\n";
+  result += "\n                                                          Don't try to redirect stdout and stderr to avoid ADML output contamination";
+  result += "\n                                                          with the possible outputs from the scripts.";
+  result += "\n";
   result += "\n                           <condition>: Optional parameters which must be fulfilled to continue through the next step.";
   result += "\n                                        Any received message over diameter interfaces will be evaluated against the";
   result += "\n                                         corresponding test case starting from the current step until the first one";
@@ -1215,17 +1233,18 @@ std::string Launcher::help() const throw() {
   result += "\n";
   result += "\n   test|goto|<id>                Updates current test pointer position.";
   result += "\n";
-  result += "\n   test|look[|id]                Show programmed test case for id provided, current when missing. Test cases programmed";
-  result += "\n                                 are not dumped on process context (too many information in general). When the test case";
-  result += "\n                                 has been completed or initiated, it will contain context information acting as a report.";
+  result += "\n   test|look[|id]                Show programmed test case for id provided, current 'in-process' test case when missing.";
+  result += "\n                                 Test cases reports are not dumped on process context (too many information in general).";
+  result += "\n                                 The report contains context information in every moment: this operation acts as a snapshot.";
   result += "\n";
   result += "\n   test|report[|[yes]|no]        Every time a test case is finished, its xml representation will be dump on a file under";
   result += "\n                                 the execution directory (or the one configured in process command-line 'tmDir') with";
-  result += "\n                                 the name 'testcase.<id>.xml'. If repeat mode is active, new test case executions will";
-  result += "\n                                 append its reports into the same file. This option is disabled by default to improve";
-  result += "\n                                 performance (reducing IO). ADML process context will show test manager whole information";
-  result += "\n                                 and test case reports will be written depending on this report option. Anyway, you could";
-  result += "\n                                  use the 'look' operation to see the report.";
+  result += "\n                                 the name 'cycle-<cycle id>.testcase-<test case id>.xml'. This option is disabled by";
+  result += "\n                                 default reducing IO. ADML process context won't show test manager whole information";
+  result += "\n                                 because it could be a huge amount of data to write. Anyway, you could use the 'look'";
+  result += "\n                                 operation to see the desired report(s).";
+  result += "\n";
+  result += "\n   test|report-hex[|[yes]|no]    Reports could include the diameter messages in hexadecimal format. Disabled by default.";
   result += "\n";
   result += "\n   test|report-hex[|[yes]|no]    Reports could include the diameter messages in hexadecimal format. Disabled by default.";
   result += "\n";
@@ -1235,12 +1254,11 @@ std::string Launcher::help() const throw() {
   result += "\n                                 - soft: only for finished cases (those with 'Success' or 'Failed' states). It does not";
   result += "\n                                         affect to test cases with 'InProgress' state.";
   result += "\n";
-  result += "\n   test|repeat[|[yes]|no]        Restarts the whole programmed test list when finished, disabled by default (testing";
-  result += "\n                                 time trigger system will enter suspended state until new ttps operation is received).";
-  result += "\n                                 Test cases state & data will be reset (when achieved again), but general statistics";
-  result += "\n                                 and counters will continue measuring until reset with 'collect' operation.";
-  result += "\n                                 When the test cases pool has been processed (and this repeat option is disabled), you";
-  result += "\n                                 could reactivate the testing by mean 'test|reset|soft' and then 'test|ttps|<value>'.";
+  result += "\n   test|repeats|<amount>         Restarts the whole programmed test list when finished the amount number of times (repeats";
+  result += "\n                                 forever if value -1 is provided). This is disabled by default (amount = 0): testing trigger";
+  result += "\n                                 system will enter suspended state until new ttps operation is received and a soft reset has";
+  result += "\n                                 been done before. Test cases state & data will be reset (when achieved again), but general";
+  result += "\n                                 statistics and counters will continue measuring until reset with 'collect' operation.";
   result += "\n";
   result += "\n   test|clear                    Clears all the programmed test cases and stop testing (if in progress).";
   result += "\n";
@@ -1610,7 +1628,7 @@ void Launcher::eventOperation(const std::string &operation, std::string &respons
     // test|<id>|<command>[|parameters]   Add a new step to the test case ...
     // test|ttps|<amount>                 Starts/resume the provided number of time ticks per second (ttps). The ADML starts ...
     // test|ip-limit[|amount]             In-progress limit of test cases. No new test cases will be launched over this value ...
-    // test|repeat[|[yes]|no]             Restarts the programmed test cases when finished. Disabled by default: the testing ...
+    // test|repeats|<amount>              Restarts the whole programmed test list when finished the amount number of times ...
     // test|report[|[yes]|no]             Every time a test case is finished a report file in xml format will be created under ...
     // test|report-hex[|[yes]|no]         Reports could include the diameter messages in hexadecimal format. Disabled by default.
     // test|goto|<id>                     Updates current test pointer position.
@@ -1652,13 +1670,14 @@ void Launcher::eventOperation(const std::string &operation, std::string &respons
         opt_response_content += " test cases running";
       }
     }
-    else if(param1 == "repeat") {
-      if (numParams > 2)
+    else if(param1 == "repeats") {
+      if (numParams > 2 || numParams < 2)
         throw anna::RuntimeException("Wrong body content format on HTTP Request. Use 'help' management command to see more information.", ANNA_FILE_LOCATION);
 
-      if(param2 == "") param2 = "yes";
-      testManager.setPoolRepeat((param2 == "yes"));
-      opt_response_content += (testManager.getPoolRepeat() ? "repeat enabled" : "repeat disabled");
+      testManager.setPoolRepeats(atoi(param2.c_str()));
+      int repeats = testManager.getPoolRepeats();
+      std::string nolimit = (repeats != -1) ? "":" [no limit]";
+      opt_response_content += anna::functions::asString("Pool repeats: %d%s (current cycle: %d)", repeats, nolimit.c_str(), testManager.getPoolCycle());
     }
     else if(param1 == "report") {
       if (numParams > 2)
@@ -1830,6 +1849,12 @@ void Launcher::eventOperation(const std::string &operation, std::string &respons
           throw anna::RuntimeException(anna::functions::asString("Missing condition for '%s' command in test id operation", param2.c_str()), ANNA_FILE_LOCATION);
         }
       }
+      else if (param2 == "sh-command") {
+        if (numParams > 4)
+          throw anna::RuntimeException("Wrong body content format on HTTP Request. Use 'help' management command to see more information.", ANNA_FILE_LOCATION);
+        if(param3 == "") throw anna::RuntimeException("Missing script for 'sh-command' in test id operation", ANNA_FILE_LOCATION);
+        testManager.getTestCase(id)->addCmd(param3, param4); // creates / reuses
+      }
       else {
         throw anna::RuntimeException("Wrong body content format on HTTP Request. Use 'help' management command to see more information.", ANNA_FILE_LOCATION);
       }
@@ -1965,7 +1990,7 @@ throw() {
   anna::statistics::Engine::instantiate().asXML(result);
 
   // Testing: could be heavy if test case reports are enabled
-  TestManager::instantiate().asXML(result);
+  //TestManager::instantiate().asXML(result);
 
   return result;
 }
index 204459a..9ac09af 100644 (file)
 #include <TestManager.hpp>
 
 
+#define SH_COMMAND_TAG_FOR_REPLACE__CYCLE_ID    "##cycleid##"
+#define SH_COMMAND_TAG_FOR_REPLACE__TESTCASE_ID "##testcaseid##"
+#define SH_COMMAND_TAG_FOR_REPLACE__TESTSTEP_ID "##teststepid##"
+
+
 namespace anna {
   namespace timex {
     class Engine;
index d312981..4db01a5 100755 (executable)
@@ -17,11 +17,20 @@ cd `dirname $0`
 PID=`cat .pid`
 
 # Send operation:
-[ "$1" = "" ] && _exit "Usage: $0 <operation string>; i.e.: $0 help"
-echo $1 > sigusr2.in
-kill -s SIGUSR2 $PID
+[ "$1" = "" ] && _exit "Usage: $0 [-f] <content: operation string or file (one operation per line) if '-f' provided>; i.e.: $0 help, $0 -f myOperationsList.txt"
+FILE=
+[ "$1" = "-f" ] && FILE=$2
 
-#echo "You could see results on '`pwd`/sigusr2.out' file."
-cat `pwd`/sigusr2.out 2>/dev/null
-echo
+if [ -z "$FILE" ]
+then
+  echo $1 > sigusr2.in
+else
+  [ ! -f "$FILE" ] && _exit "Can't found provided file '$FILE'."
+  grep -v "^#" $FILE | sed '/^[ \t]*$/d' > sigusr2.in
+fi
+0> sigusr2.out
+kill -s SIGUSR2 $PID
+count=0
+while [ ! -s sigusr2.out -a $count -lt 500 ]; do sleep 0.01; count=$((count+1)); done
+cat `pwd`/sigusr2.out
 
index 1c5beeb..b3d2f2f 100644 (file)
@@ -110,7 +110,8 @@ void TestCase::setState(const State::_v &state) throw() {
   TestManager &testManager = TestManager::instantiate();
   if (isFinished()) {
     if (!testManager.getDumpReports()) return;
-    std::string file = testManager.getReportsDirectory() + anna::functions::asString("/testcase.%llu.xml", a_id);
+    // report file name: cycle-<cycle id>.testcase-<test case id>.xml
+    std::string file = testManager.getReportsDirectory() + anna::functions::asString("/cycle-%d.testcase-%llu.xml", testManager.getPoolCycle(), a_id);
     std::ofstream out;
     out.open(file.c_str(), std::ofstream::out | std::ofstream::app);
     if(out.is_open() == false) {
@@ -338,6 +339,16 @@ void TestCase::addWaitRegexp(bool fromEntity, const std::string &regexp) throw(a
   a_steps.push_back(step);
 }
 
+void TestCase::addCmd(const std::string &script, const std::string &parameters) throw(anna::RuntimeException) {
+  assertInitialized();
+
+  TestStepCmd *step = new TestStepCmd(this);
+  step->setScript(script);
+  step->setParameters(parameters);
+
+  a_steps.push_back(step);
+}
+
 TestStepWait *TestCase::searchNextWaitConditionFulfilled(const anna::DataBlock &message, bool waitFromEntity) throw() {
 
   TestStepWait *result;
index a3c48f0..ea8dd1b 100644 (file)
@@ -79,6 +79,8 @@ public:
                 const std::string &hopByHop, const std::string &msisdn, const std::string &imsi, const std::string &serviceContextId) throw(anna::RuntimeException);
   void addWaitAnswer(bool fromEntity, int stepNumber) throw(anna::RuntimeException);
   void addWaitRegexp(bool fromEntity, const std::string &regexp) throw(anna::RuntimeException);
+  void addCmd(const std::string &script, const std::string &parameters) throw(anna::RuntimeException);
+
 
   // Process:
   void nextStep() throw() { a_stepsIt++; }
index da90107..ea340b9 100644 (file)
@@ -36,7 +36,8 @@ TestManager::TestManager() :
   a_dumpReports = false;
   a_dumpHexMessages = false;
   a_synchronousAmount = 1;
-  a_poolRepeat = false;
+  a_poolRepeats = 0; // repeat disabled by default
+  a_poolCycle = 1;
   a_inProgressCount = 0;
   a_inProgressLimit = UINT_MAX; // no limit
   a_clock = NULL;
@@ -135,7 +136,7 @@ bool TestManager::configureTTPS(int testTicksPerSecond) throw() {
   a_synchronousAmount = 1;
 
   if (admlTimeInterval < anna::Millisecond(1)) {
-    LOGWARNING(anna::Logger::warning("Not allowed to configure more than 1000 events per second for the time trigger testing system", ANNA_FILE_LOCATION));
+    LOGWARNING(anna::Logger::warning("Not allowed to configure more than 1000 events per second for for triggering testing system", ANNA_FILE_LOCATION));
     return false;
   }
 
@@ -209,6 +210,7 @@ bool TestManager::clearPool() throw() {
   a_testPool.clear();
   a_sessionIdTestCaseMap.clear();
   a_currentTestIt = a_testPool.end();
+  a_poolCycle = 1;
   configureTTPS(0); // stop
   return true;
 }
@@ -261,16 +263,21 @@ bool TestManager::nextTestCase() throw() {
 
     // Completed:
     if (a_currentTestIt == a_testPool.end()) {
-      if (a_poolRepeat) {
-        LOGWARNING(anna::Logger::warning("Testing pool cycle completed. Repeat mode on. Restarting", ANNA_FILE_LOCATION));
+      if ((a_poolCycle > a_poolRepeats) && (a_poolRepeats != -1)) {
+        LOGWARNING(anna::Logger::warning("Testing pool cycle completed. No remaining repeat cycles left. Suspending", ANNA_FILE_LOCATION));
+        a_poolCycle = 1;
+        return false;
+      }
+      else {
+        LOGWARNING(
+          std::string nolimit = (a_poolRepeats != -1) ? "":" [no limit]";
+          anna::Logger::warning(anna::functions::asString("Testing pool cycle %d completed (repeats configured: %d%s). Restarting for the %s cycle", a_poolCycle, a_poolRepeats, nolimit.c_str(), (a_poolRepeats == a_poolCycle) ? "last":"next"), ANNA_FILE_LOCATION);
+        );
+        a_poolCycle++;
         //a_currentTestIt = a_testPool.begin();
         return true; // avoids infinite loop: if the cycle takes less time than test cases completion, below reset never will turns state
                      // into Initialized and this while will be infinite. It is preferable to wait one tick when the cycle is completed.
       }
-      else {
-        LOGWARNING(anna::Logger::warning("Testing pool cycle completed. Repeat mode off. Suspending", ANNA_FILE_LOCATION));
-        return false;
-      }
     }
 
     // Soft reset to initialize already finished (in previous cycle) test cases:
@@ -378,7 +385,9 @@ throw() {
 
   int poolSize = a_testPool.size();
   result->createAttribute("NumberOfTestCases", poolSize);
-  result->createAttribute("PoolRepeat", (a_poolRepeat ? "yes":"no"));
+  if (a_poolRepeats) result->createAttribute("PoolRepeats", a_poolRepeats);
+  else result->createAttribute("PoolRepeats", "disabled");
+  result->createAttribute("PoolCycle", a_poolCycle);
   result->createAttribute("InProgressCount", a_inProgressCount);
   if (a_inProgressLimit == UINT_MAX)
     result->createAttribute("InProgressLimit", "<no limit>");
index 1756315..1719faf 100644 (file)
@@ -61,7 +61,8 @@ class TestManager : public anna::timex::TimeEventObserver, public anna::Singleto
   // Pool of test cases
   test_pool_t a_testPool;
   test_pool_it a_currentTestIt;
-  bool a_poolRepeat; // repeat pool when finish
+  int a_poolRepeats; // repeat pool N times
+  int a_poolCycle; // current cycle, from 1 to N
   unsigned int a_inProgressCount;
   unsigned int a_inProgressLimit; // limit load to have this value
 
@@ -108,8 +109,10 @@ class TestManager : public anna::timex::TimeEventObserver, public anna::Singleto
 
     bool clearPool() throw();
     bool resetPool(bool hard /* hard reset includes in-progress test cases */) throw();
-    void setPoolRepeat(bool repeat) throw() { a_poolRepeat = repeat; }
-    bool getPoolRepeat() const throw() { return a_poolRepeat; }
+    void setPoolRepeats(int repeats) throw() { a_poolRepeats = repeats; }
+    int getPoolRepeats() const throw() { return a_poolRepeats; }
+    int getPoolCycle() const throw() { return a_poolCycle; }
+
     unsigned int getInProgressCount() const throw() { return a_inProgressCount; }
     void setInProgressCountDelta(unsigned int delta) throw() { a_inProgressCount += delta; }
     unsigned int getInProgressLimit() const throw() { return a_inProgressLimit; }
index e53ae9a..605407e 100644 (file)
@@ -8,6 +8,8 @@
 
 // Standard
 #include <string>
+#include <iostream>
+#include <stdio.h>
 
 // Project
 #include <anna/xml/Compiler.hpp>
 #include <TestTimer.hpp>
 
 
+namespace {
+  void cmdRunOnThread (TestStepCmd *step, const std::string &cmd) {
+    step->setThreadRunning(true);
+    int rc = system(cmd.c_str());
+    if (rc != -1) rc >>= 8; // divide by 256
+    step->setResultCode(rc);
+    step->complete();
+    // TODO: timeout the system call
+  }
+}
+
+
 ////////////////////////////////////////////////////////////////////////////////////////////////////////
 // TestStep
 ////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -42,7 +56,7 @@ void TestStep::initialize(TestCase *testCase) {
 
 const char* TestStep::asText(const Type::_v type)
 throw() {
-  static const char* text [] = { "Unconfigured", "Timeout", "Sendxml2e", "Sendxml2c", "Delay", "Wait" };
+  static const char* text [] = { "Unconfigured", "Timeout", "Sendxml2e", "Sendxml2c", "Delay", "Wait", "Cmd" };
   return text [type];
 }
 
@@ -298,7 +312,7 @@ bool TestStepDelay::do_execute() throw() {
 
 void TestStepDelay::do_complete() throw() {
   a_timer = NULL;
-  next(); // next() invoked here because execute() is always false for delay and never dvance the iterator
+  next(); // next() invoked here because execute() is always false for delay and never advance the iterator
 }
 
 void TestStepDelay::do_reset() throw() {
@@ -368,7 +382,7 @@ throw() {
   }
 
   if (msg != "") result->createAttribute("MatchedMessage", msg);
-  if (xmlmsg != "") result->createAttribute("XMLMessage", xmlmsg);
+  if (xmlmsg != "") result->createAttribute("MatchedXMLMessage", xmlmsg);
 
   return result;
 }
@@ -378,7 +392,7 @@ bool TestStepWait::do_execute() throw() {
 }
 
 void TestStepWait::do_complete() throw() {
-  next(); // next() invoked here because execute() never do this.
+  a_testCase->process(); // next() not invoked; we only want to reactivate the test case
 }
 
 bool TestStepWait::fulfilled(const anna::DataBlock &db/*, bool matchSessionId*/) throw() {
@@ -397,3 +411,69 @@ void TestStepWait::do_reset() throw() {
   a_serverSession = NULL;
 }
 
+////////////////////////////////////////////////////////////////////////////////////////////////////////
+// TestStepCmd
+////////////////////////////////////////////////////////////////////////////////////////////////////////
+anna::xml::Node* TestStepCmd::asXML(anna::xml::Node* parent) const
+throw() {
+  anna::xml::Node* result = TestStep::asXML(parent);
+  //parent->createChild("TestStepCmd");
+
+  result->createAttribute("Script", (a_script != "") ? a_script:"<no script>");
+  result->createAttribute("Parameters", (a_parameters != "") ? a_parameters:"<no parameters>");
+  result->createAttribute("CommandInProgress", a_threadRunning ? "yes":"no");
+  if (!a_threadRunning && a_resultCode != -2) {
+    result->createAttribute("ResultCode", a_resultCode);
+    //if (a_output != "") result->createAttribute("Output", a_output);
+  }
+
+  return result;
+}
+
+bool TestStepCmd::do_execute() throw() {
+  if (!a_threadRunning) {
+    // Special tags to replace:
+    std::string cmd = getScript();
+    cmd += " ";
+    cmd += getParameters();
+    size_t index;
+    while ((index = cmd.find(SH_COMMAND_TAG_FOR_REPLACE__CYCLE_ID)) != std::string::npos)
+      cmd.replace(index, strlen(SH_COMMAND_TAG_FOR_REPLACE__CYCLE_ID), anna::functions::asString(TestManager::instantiate().getPoolCycle()));
+    while ((index = cmd.find(SH_COMMAND_TAG_FOR_REPLACE__TESTCASE_ID)) != std::string::npos)
+      cmd.replace(index, strlen(SH_COMMAND_TAG_FOR_REPLACE__TESTCASE_ID), anna::functions::asString(a_testCase->getId()));
+    while ((index = cmd.find(SH_COMMAND_TAG_FOR_REPLACE__TESTSTEP_ID)) != std::string::npos)
+      cmd.replace(index, strlen(SH_COMMAND_TAG_FOR_REPLACE__TESTSTEP_ID), anna::functions::asString(getNumber()));
+
+    a_thread = std::thread(cmdRunOnThread, this, cmd);
+    a_thread.detach();
+  }
+
+  return false; // don't go next (wait complete)
+}
+
+void TestStepCmd::do_complete() throw() {
+
+  a_threadRunning = false;
+  if (a_threadDeprecated) {
+    a_threadDeprecated = false;
+    return; // ignore TODO: interrupt the thread to avoid execution of the script
+  }
+
+  if (getResultCode() != 0)
+    a_testCase->setState(TestCase::State::Failed);
+  else
+    next(); // next() invoked here because execute() is always false for delay and never advance the iterator
+}
+
+void TestStepCmd::do_reset() throw() {
+
+  if (a_threadRunning) {
+    std::string s_warn = anna::functions::asString("Thread still in progress: deprecating step %d for Test Case %llu", getNumber(), a_testCase->getId());
+    LOGWARNING(anna::Logger::warning(s_warn, ANNA_FILE_LOCATION));
+    a_threadDeprecated = true;
+  }
+
+  a_resultCode = -2;
+  //a_output = "";
+}
+
index b63c147..aa2f575 100644 (file)
@@ -12,6 +12,7 @@
 // Standard
 #include <string>
 #include <vector>
+#include <thread>
 
 // Project
 #include <anna/core/DataBlock.hpp>
@@ -21,6 +22,8 @@
 #include <TestCondition.hpp>
 
 
+
+
 namespace anna {
   class Millisecond;
 
@@ -53,7 +56,7 @@ class TestStep {
     void initialize(TestCase *testCase);
 
   public:
-    struct Type { enum _v { Unconfigured, Timeout, Sendxml2e, Sendxml2c, Delay, Wait }; };
+    struct Type { enum _v { Unconfigured, Timeout, Sendxml2e, Sendxml2c, Delay, Wait, Cmd }; };
     static const char* asText(const Type::_v type) throw();
 
     TestStep(TestCase *testCase) { initialize(testCase); }
@@ -201,4 +204,41 @@ class TestStepWait : public TestStep {
 };
 
 
+class TestStepCmd : public TestStep {
+
+  std::string a_script;
+  std::string a_parameters;
+  std::thread a_thread;
+  bool a_threadRunning;
+  bool a_threadDeprecated;
+  int a_resultCode;
+  //std::string a_output;
+
+  public:
+    TestStepCmd(TestCase *testCase) : TestStep(testCase), a_threadRunning(false), a_threadDeprecated(false), a_resultCode(-2)/*, a_output("")*/ { a_type = Type::Cmd; }
+
+    // setter & getters
+    void setThreadRunning(bool running) throw() { a_threadRunning = running; }
+    //bool getThreadRunning() const throw() { return a_threadRunning; }
+    //void setThreadDeprecated(bool deprecated) throw() { a_threadDeprecated = deprecated; }
+    //bool getThreadDeprecated() const throw() { return a_threadDeprecated; }
+
+    void setResultCode(int rc) throw() { a_resultCode = rc; }
+    int getResultCode() const throw() { return a_resultCode; }
+    //void setOutput(const std::string &output) throw() { a_output = output; }
+    //const std::string &getOutput() const throw() { return a_output; }
+
+    void setScript(const std::string &script) throw() { a_script = script; }
+    const std::string &getScript() const throw() { return a_script; }
+    void setParameters(const std::string &params) throw() { a_parameters = params; }
+    const std::string &getParameters() const throw() { return a_parameters; }
+
+    // virtuals
+    bool do_execute() throw();
+    void do_complete() throw();
+    void do_reset() throw();
+    anna::xml::Node* asXML(anna::xml::Node* parent) const throw();
+};
+
+
 #endif