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";
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";
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";
// 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.
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)
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);
}
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;
}
#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;
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
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) {
a_steps.push_back(step);
}
+void TestCase::addCmd(const std::string &script, const std::string ¶meters) 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;
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 ®exp) throw(anna::RuntimeException);
+ void addCmd(const std::string &script, const std::string ¶meters) throw(anna::RuntimeException);
+
// Process:
void nextStep() throw() { a_stepsIt++; }
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;
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;
}
a_testPool.clear();
a_sessionIdTestCaseMap.clear();
a_currentTestIt = a_testPool.end();
+ a_poolCycle = 1;
configureTTPS(0); // stop
return true;
}
// 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:
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>");
// 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
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; }
// 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
////////////////////////////////////////////////////////////////////////////////////////////////////////
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];
}
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() {
}
if (msg != "") result->createAttribute("MatchedMessage", msg);
- if (xmlmsg != "") result->createAttribute("XMLMessage", xmlmsg);
+ if (xmlmsg != "") result->createAttribute("MatchedXMLMessage", xmlmsg);
return result;
}
}
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() {
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 = "";
+}
+
// Standard
#include <string>
#include <vector>
+#include <thread>
// Project
#include <anna/core/DataBlock.hpp>
#include <TestCondition.hpp>
+
+
namespace anna {
class Millisecond;
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); }
};
+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 ¶ms) 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