new feature: define and call functions using the two new commands function and call

master
Marc Wäckerlin 9 years ago
parent 332db8b079
commit 2c12481418
  1. 211
      src/commands.hxx
  2. 27
      src/exceptions.hxx
  3. 2
      src/testgui.hxx

@ -32,6 +32,7 @@
class Script;
class Command;
class Function;
class Logger {
public:
@ -121,7 +122,20 @@ class Command: public QObject {
while (QTime::currentTime()<dieTime)
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
}
public:
protected:
QStringList commaSeparatedList(QString value) {
switch (value.size()>1&&value.at(0)==value.at(value.size()-1)
?value.at(0).toLatin1():'\0') {
case '"': case '\'': {
return value.mid(1, value.size()-2)
.split(QRegularExpression(QString(value[0])+", *"
+QString(value[0])));
} break;
default: {
return value.split(QRegularExpression(", *"));
}
}
}
static QWebElement find(QWebFrame* frame, QString selector,
int repeat = 2, int sleepsec = 1) {
QWebElement element;
@ -418,6 +432,7 @@ class Script: public QObject {
reset();
_variables.clear();
_rvariables.clear();
_functions.clear();
_timeout = 20;
_clicktype = JAVASCRIPT_CLICK;
}
@ -609,10 +624,29 @@ class Script: public QObject {
_variables[name] = value;
_rvariables[value] = name;
}
void set(const Script& o) {
_variables = o._variables;
_rvariables = o._rvariables;
_timeout = o._timeout;
_clicktype = o._clicktype;
_cout.clear();
_cerr.clear();
_ignoreSignalsUntil.clear();
_functions.clear();
}
void unset(QString name) {
_rvariables.remove(_variables[name]);
_variables.remove(name);
}
void function(QString name, std::shared_ptr<Function> f) {
_functions[name] = f;
}
std::shared_ptr<Function> function(QString name) {
QMap<QString, std::shared_ptr<Function> >::iterator
it(_functions.find(name));
if (it==_functions.end()) throw FunctionNotFound(name);
return *it;
}
void timeout(int t) {
_timeout = t;
}
@ -637,24 +671,6 @@ class Script: public QObject {
}
return txt;
}
public Q_SLOTS:
void log(QString text) {
text = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss ")+text;
logging(text);
std::cout<<text<<std::endl<<std::flush;
_cout += text + "\n";
}
void plainlog(QString text) {
logging(text);
std::cout<<text<<std::endl<<std::flush;
_cout += text + "\n";
}
private:
std::shared_ptr<Command> unknown(QString line) {
if (!line.size()) return std::shared_ptr<Command>(new Empty());
if (line[0]=='#') return std::shared_ptr<Command>(new Comment(line));
throw UnknownCommand(line); // error
}
void addSignals(QWebFrame* frame) {
connect(dynamic_cast<NetworkAccessManager*>
(frame->page()->networkAccessManager()),
@ -703,6 +719,24 @@ class Script: public QObject {
this, SLOT(urlChanged(const QUrl&)));
disconnect(&_timer, SIGNAL(timeout()), this, SLOT(timeout()));
}
public Q_SLOTS:
void log(QString text) {
text = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss ")+text;
logging(text);
std::cout<<text<<std::endl<<std::flush;
_cout += text + "\n";
}
void plainlog(QString text) {
logging(text);
std::cout<<text<<std::endl<<std::flush;
_cout += text + "\n";
}
private:
std::shared_ptr<Command> unknown(QString line) {
if (!line.size()) return std::shared_ptr<Command>(new Empty());
if (line[0]=='#') return std::shared_ptr<Command>(new Comment(line));
throw UnknownCommand(line); // error
}
void initPrototypes();
void add(Command* c) {
_prototypes[c->tag()] = std::shared_ptr<Command>(c);
@ -774,6 +808,7 @@ class Script: public QObject {
QString _ignoreSignalsUntil;
QMap<QString, QString> _variables; ///< variable mapping
QMap<LenString, LenString> _rvariables; ///< reverse variable mapping
QMap<QString, std::shared_ptr<Function> > _functions;
int _timeout;
ClickType _clicktype;
};
@ -1217,7 +1252,7 @@ class Execute: public Command {
}
QString description() const {
return
tag()+" <command>\n <line1>\n <line2>\n <...>"
tag()+" <command>\n <line1>\n <line2>\n <...>"
"\n\n"
"Execute <command>. The command can have space separated arguments. "
"Following lines that are intended by at least "
@ -1416,7 +1451,8 @@ class Set: public Command {
"Sets the value of a variable either to a constant, or to the output"
" of a command. A command should be a command that produces an"
" output, such as «do», which returns the result of JavaScript or"
" «execute», which returns the output of the executed command.";
" «execute», which returns the output of the executed command."
" All variables are global with regrad to functions.";
}
QString command() const {
if (_next)
@ -1663,7 +1699,7 @@ class SetValue: public Command {
QString description() const {
return
tag()+" <selector> -> '<value>'\n"+
tag()+" <selector> -> '<value1>', '<value2>', ..."
tag()+" <selector> -> '<value1>', '<value2>', <...>"
"\n\n"
"Set the selected element to a given value. It is mostly the same as"
" the following constuct, except that options in a select are evaluated"
@ -1697,18 +1733,7 @@ class SetValue: public Command {
QString value(script->replacevars(_value));
if (element.tagName()=="SELECT") {
// value is a comma seperated list of option values
QStringList values;
switch (value.size()>1&&value.at(0)==value.at(value.size()-1)
?value.at(0).toLatin1():'\0') {
case '"': case '\'': {
values = value.mid(1, value.size()-2)
.split(QRegularExpression(QString(value[0])+", *"
+QString(value[0])));
} break;
default: {
values = value.split(QRegularExpression(", *"));
}
}
QStringList values(commaSeparatedList(value));
Q_FOREACH(QWebElement option, element.findAll("option")) {
QString name(option.evaluateJavaScript("this.value").toString());
option.evaluateJavaScript
@ -1730,6 +1755,120 @@ class SetValue: public Command {
QString _value;
};
class Function: public Command {
public:
QString tag() const {
return "function";
}
QString description() const {
return
tag()+" <name> [<var1>, <var2>, <...>]\n"
" <command1>\n"
" <command2>\n"
" <...>\n"
"\n\n"
"Define a function with arguments. The arguments are treated like"
" local variables. In a sequence of scripts within the same testrun,"
" functions are inherited from all followin scripts, so you can first"
" load a script file that contains all functions. Within the same file,"
" a function can be called before the definition.\n\n"
"If you quote the values, then quote all values with the same"
" quotes. If you need a comma within a value, you must quote.";
}
QString command() const {
return tag()+" "+_name+" "+_vars.join(" ");
}
std::shared_ptr<Command> parse(Script* script, QString args,
QStringList& in, int) {
std::shared_ptr<Function> cmd(new Function());
if (!args.size()) throw BadArgument(tag()+" requires a <name>");
QStringList allargs(args.split(" "));
cmd->_name = allargs.takeFirst().trimmed();
cmd->_vars = commaSeparatedList(allargs.join(' '));
script->function(cmd->_name, cmd);
QStringList commands;
int pos(-1);
while (in.size() && in[0].size() && in[0][0]==' ') {
if (pos<0) pos=in[0].toStdString().find_first_not_of(' ');
commands += in.takeFirst().mid(pos);
}
cmd->_script = std::shared_ptr<Script>(new Script);
cmd->_script->parse(commands);
return cmd;
}
bool execute(Script* script, QWebFrame*) {
Logger log(this, script);
return true;
}
bool call(QStringList args, Script* script, QWebFrame* frame) {
Logger log(this, script);
if (args.size()!=_vars.size())
throw WrongNumberOfArguments(_name, _vars, args);
xml::Node suite("testcase");
suite.attr("classname") = _name.toStdString();
suite.attr("name") = testsuite().toStdString();
_script->set(*script);
for (QStringList::iterator var(_vars.begin()), arg(args.begin());
var<_vars.end() && arg<args.end(); ++var, ++arg)
_script->set(*var, script->replacevars(*arg));
try {
connect(_script.get(), SIGNAL(logging(QString)),
script, SLOT(log(QString)));
script->removeSignals(frame);
_script->run(frame, suite, targetdir());
script->addSignals(frame);
disconnect(_script.get(), SIGNAL(logging(QString)),
script, SLOT(log(QString)));
} catch (const std::exception& x) {
script->addSignals(frame);
disconnect(_script.get(), SIGNAL(logging(QString)),
script, SLOT(log(QString)));
throw FunctionCallFailed(_name, _vars, args, x);
}
return true;
}
private:
QString _name;
QStringList _vars;
std::shared_ptr<Script> _script;
};
class Call: public Command {
public:
QString tag() const {
return "call";
}
QString description() const {
return
tag()+" <name> ['<arg1>', '<arg2>', ...]"
"\n\n"
"Calls a function. The number of arguments must be exactly the same"
" as in the function definition.\n\n"
"If you quote the values, then quote all values with the same"
" quotes. If you need a comma within a value, you must quote.";
}
QString command() const {
return tag()+" "+_name+(_args.size()?" '"+_args.join("', '")+"'":"");
}
std::shared_ptr<Command> parse(Script*, QString args,
QStringList&, int) {
std::shared_ptr<Call> cmd(new Call());
if (!args.size()) throw BadArgument(tag()+" requires a <name>");
QStringList allargs(args.split(" "));
cmd->_name = allargs.takeFirst().trimmed();
cmd->_args = commaSeparatedList(allargs.join(' '));
return cmd;
}
bool execute(Script* script, QWebFrame* frame) {
Logger log(this, script);
script->function(_name)->call(_args, script, frame);
return true;
}
public:
QString _name;
QStringList _args;
};
/* Template:
class : public Command {
public:
@ -1746,7 +1885,7 @@ class : public Command {
return tag();
}
std::shared_ptr<Command> parse(Script*, QString args,
QStringList& in, int) {
QStringList&, int) {
std::shared_ptr<> cmd(new ());
return cmd;
}
@ -1809,6 +1948,8 @@ inline void Script::initPrototypes() {
add(new ClientCertificate);
add(new ::ClickType);
add(new SetValue);
add(new Function);
add(new Call);
}
#endif

@ -219,4 +219,31 @@ class ScriptExecutionFailed: public ScriptFailed {
}
};
class WrongNumberOfArguments: public TestFailed {
public:
WrongNumberOfArguments(QString name, QStringList vars, QStringList args):
TestFailed(QString("%1 has %2 arguments, but %3 were given")
.arg(name).arg(vars.size()).arg(args.size())) {
}
};
class FunctionCallFailed: public TestFailed {
public:
FunctionCallFailed(QString name, QStringList vars, QStringList args,
const std::exception& x):
TestFailed("function call failed: "+name) {
for (QStringList::iterator var(vars.begin()), arg(args.begin());
var<vars.end() && arg<args.end(); ++var, ++arg)
_what += " "+*var+"="+*arg;
_what += QString("; reason: ")+x.what();
}
};
class FunctionNotFound: public TestFailed {
public:
FunctionNotFound(QString name):
TestFailed("function not found: "+name) {
}
};
#endif

@ -269,7 +269,7 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
int pos1(text.lastIndexOf(QRegularExpression("^do ")));
int pos2(text.lastIndexOf(QRegularExpression("^load ")));
int pos3(text.lastIndexOf(QRegularExpression("^click ")));
text.insert(mrw::max(pos1, pos2, pos3), "download "+filename);
text.insert(std::max({pos1, pos2, pos3}), "download "+filename);
_testscript->setPlainText(text);
_testscript->moveCursor(QTextCursor::End);
_testscript->ensureCursorVisible();

Loading…
Cancel
Save