better progress indicator

master
Marc Wäckerlin 7 years ago
parent e527c94918
commit 32f090c2f0
  1. 193
      src/commands.hxx
  2. 2
      src/webrunner.cxx

@ -117,6 +117,9 @@ class Command: public QObject {
log(QString(" FAILED[")+demangle(typeid(e).name())+"]: "+e.what());
throw e;
}
virtual int steps(Script* parent) {
return 1;
}
void line(int linenr) {
_line = linenr;
}
@ -187,6 +190,8 @@ class Command: public QObject {
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
}
protected:
std::shared_ptr<Script> subParser(Script* parent, const QStringList& in,
const QString& file, int line, int indent);
bool runScript(Logger& log, Command* parentCommand,
std::shared_ptr<Script> script,
Script* parent, QWebFrame* frame,
@ -204,7 +209,7 @@ class Command: public QObject {
}
QStringList quotedStrings(QString value,
QString delimiter = " ",
bool keepDelimiters = false) {
bool keepDelimiters = false) const {
QStringList res;
QString quot("'\"");
while (value=value.trimmed(), value.size()) {
@ -222,17 +227,10 @@ class Command: public QObject {
res += value.mid(start, m.capturedStart()-start);
value.remove(0, m.capturedEnd());
if (keepDelimiters && m.capturedLength()) res+=m.captured().mid(start).trimmed();
// std::cout<<"REMOVE: \""<<m.captured()<<"\" 0 - "<<(m.capturedEnd()+start)
// <<" start="<<start<<" pos="<<pos<<std::endl
// <<"REMAINING: \""<<value<<"\""<<std::endl;
}
// std::cout<<"FOUND"<<std::endl;
// Q_FOREACH(QString tag, res) {
// std::cout<<" - \""<<tag<<"\""<<std::endl;
// }
return res;
}
QStringList commaSeparatedList(QString value) {
QStringList commaSeparatedList(QString value) const {
return quotedStrings(value, ",");
}
static QWebElement find(QWebFrame* frame, QString selector,
@ -487,12 +485,14 @@ class Script: public QObject {
.replace("&nbsp;", " ").replace("&amp;", "&");
}
public:
Script(): _clicktype(JAVASCRIPT_CLICK), _command(0), _screenshots(true),
_defaultTimeout(20) {
Script():
_step(0), _clicktype(JAVASCRIPT_CLICK), _command(0),
_screenshots(true), _defaultTimeout(20) {
initPrototypes();
}
Script(const Script& o):
QObject(),
_step(0),
_prototypes(o._prototypes),
_script(o._script),
_command(0),
@ -568,8 +568,8 @@ class Script: public QObject {
reset();
_variables.clear();
_rvariables.clear();
_functions.clear();
_timeout = _defaultTimeout;
_step = 0;
_clicktype = JAVASCRIPT_CLICK;
}
std::shared_ptr<Command> parseLine(QStringList& in,
@ -600,6 +600,7 @@ class Script: public QObject {
throw;
}
void parse(QStringList in, QString filename, int line = 1, int indent = 0) {
_filename = filename;
for (int linenr(0), oldsize(0);
oldsize=in.size(), in.size();
linenr+=oldsize-in.size())
@ -619,6 +620,7 @@ class Script: public QObject {
QString td = QString(), bool screenshots = true,
int maxretries = 0) {
bool res(true);
_step = 0;
_testsuites = testsuites;
_timeout = _defaultTimeout; // defaults to 20s
_ignoreSignalsUntil.clear();
@ -636,10 +638,11 @@ class Script: public QObject {
testsuite.attr("name") = "Unnamed Test Suite";
(*_testsuites)<<testsuite;
}
int retries(0), back(0), step(0);
for (auto cmd(_script.begin()); cmd!=_script.end(); ++cmd, ++step) {
int retries(0), back(0);
for (auto cmd(_script.begin()); cmd!=_script.end();
_step+=(*cmd)->steps(this), ++cmd) {
progress(QString("%1:%2").arg((*cmd)->file()).arg((*cmd)->line()),
step, steps());
_step, countSteps());
xml::Node testcase("testcase");
try {
testcase.attr("classname") =
@ -670,8 +673,7 @@ class Script: public QObject {
break; // test is successfully finished
}
progress(QString("%1:%2").arg((*cmd)->file()).arg((*cmd)->line()),
step, steps());
_step, countSteps());
} catch (PossibleRetryLoad& e) {
_timer.stop();
// timeout may happen during load due to bad internet connection
@ -691,13 +693,13 @@ class Script: public QObject {
QUrl url(frame->url());
if ((*cmd)->command()=="expect loadFinished true") {
------cmd;
------step;
------_step;
back += 3;
_ignoreSignalsUntil = "loadStarted";
frame->load(url);
} else if ((*cmd)->command()=="expect loadStarted") {
----cmd;
----step;
----_step;
back += 2;
_ignoreSignalsUntil = "loadStarted";
frame->page()->triggerAction(QWebPage::Stop);
@ -706,7 +708,7 @@ class Script: public QObject {
url2.remove("expect urlChanged");
if (url2.size()) url=url2.trimmed();
----cmd;
----step;
----_step;
back += 2;
_ignoreSignalsUntil = "loadStarted";
frame->load(url);
@ -715,7 +717,7 @@ class Script: public QObject {
url2.remove("expect load");
if (url2.size()) url=url2.trimmed();
----cmd;
----step;
----_step;
back += 2;
_ignoreSignalsUntil = "loadStarted";
frame->load(url);
@ -791,8 +793,11 @@ class Script: public QObject {
QString& cerr() {
return _cerr;
}
int steps() {
return _script.size();
int countSteps() {
int res(0);
for (auto cmd(_script.begin()); cmd!=_script.end(); ++cmd)
res += (*cmd)->steps(this);
return res;
}
bool screenshots() {
return _screenshots;
@ -839,7 +844,7 @@ class Script: public QObject {
QStringList variables() {
return _variables.keys();
}
QString variable(Logger& log, QString name) {
QString variable(QString name) {
QMap<QString, QString>::iterator it(_variables.find(name));
if (it==_variables.end()) error(VariableNotFound(name));
return *it;
@ -871,7 +876,7 @@ class Script: public QObject {
void function(QString name, std::shared_ptr<Function> f) {
_functions[name] = f;
}
std::shared_ptr<Function> function(Logger& log, QString name) {
std::shared_ptr<Function> function(QString name) {
QMap<QString, std::shared_ptr<Function> >::iterator
it(_functions.find(name));
if (it==_functions.end()) error(FunctionNotFound(name));
@ -1017,6 +1022,9 @@ class Script: public QObject {
_prototypes[c->tag()] = std::shared_ptr<Command>(c);
}
private Q_SLOTS:
void innerProgress(QString txt, int delta) {
progress(txt, _step+delta, countSteps());
}
void authenticationRequired(QNetworkReply*, QAuthenticator* a) {
if (_auth.contains(a->realm())) {
log("network: login to "+a->realm());
@ -1083,6 +1091,7 @@ class Script: public QObject {
Commands _script;
std::queue<Signal> _signals;
QTimer _timer;
int _step;
QSet<QString> _ignores;
QString _cout;
QString _cerr;
@ -1100,9 +1109,22 @@ class Script: public QObject {
QString _testclass;
Command* _command;
QString _path;
QString _filename;
QMap<QString, AuthRealm> _auth;
};
class CommandContainer: public Command {
public:
int steps(Script* parent) {
return countSteps(parent);
}
protected:
virtual int countSteps(Script* parent) const {
return _script->countSteps()+1;
}
std::shared_ptr<Script> _script;
};
class Do: public Command {
public:
QString tag() const {
@ -2083,7 +2105,7 @@ class SetValue: public Command {
QString _value;
};
class Function: public Command {
class Function: public CommandContainer {
public:
QString tag() const {
return "function";
@ -2115,8 +2137,7 @@ class Function: public Command {
cmd->_name = allargs.takeFirst().trimmed();
cmd->_vars = commaSeparatedList(allargs.join(' '));
script->function(cmd->_name, cmd);
cmd->_script = std::shared_ptr<Script>(new Script);
cmd->_script->parse(subCommandBlock(in), file, line+1, indent+1);
cmd->_script = cmd->subParser(script, subCommandBlock(in), file, line, indent);
return cmd;
}
bool execute(Script* script, QWebFrame*) {
@ -2134,7 +2155,6 @@ class Function: public Command {
private:
QString _name;
QStringList _vars;
std::shared_ptr<Script> _script;
};
class Call: public Command {
@ -2165,15 +2185,18 @@ class Call: public Command {
}
bool execute(Script* script, QWebFrame* frame) {
Logger log(this, script);
return script->function(log, _name)->call(log, this, script->replacevars(_args),
return script->function(_name)->call(log, this, script->replacevars(_args),
script, frame);
}
int steps(Script* parent) {
return parent->function(_name)->steps(parent);
}
public:
QString _name;
QStringList _args;
};
class If: public Command {
class If: public CommandContainer {
public:
QString tag() const {
return "if";
@ -2213,7 +2236,7 @@ class If: public Command {
return tag()+" "+_variable+" "+_cmp+" "+_value
+(_script.get()?"\n "+_script->print().join("\n "):"");
}
std::shared_ptr<Command> parse(Script*, QString args,
std::shared_ptr<Command> parse(Script* script, QString args,
QStringList& in, QString file, int line,
int indent) {
std::shared_ptr<If> cmd(new If());
@ -2229,12 +2252,10 @@ class If: public Command {
}
cmd->_variable = args.left(pos).trimmed();
cmd->_value = args.mid(pos+len).trimmed();
cmd->_script = std::shared_ptr<Script>(new Script);
cmd->_script->parse(subCommandBlock(in), file, line+1, indent+1);
cmd->_script = cmd->subParser(script, subCommandBlock(in), file, line, indent);
if (in.size() && in.first().contains(QRegularExpression("^ *else *$"))) {
in.removeFirst();
cmd->_else = std::shared_ptr<Script>(new Script);
cmd->_else->parse(subCommandBlock(in), file, line+1, indent+1);
cmd->_else = cmd->subParser(script, subCommandBlock(in), file, line, indent);
}
return cmd;
}
@ -2256,35 +2277,39 @@ class If: public Command {
+selector+" "+_cmp+" "+value);
} else {
switch (_cmp[0].toLatin1()) {
case '=': check = script->variable(log, _variable)==value;
case '=': check = script->variable(_variable)==value;
break;
case '!': check = script->variable(log, _variable)!=value;
case '!': check = script->variable(_variable)!=value;
break;
case '.': check = script->variable(log, _variable).contains(value);
case '.': check = script->variable(_variable).contains(value);
break;
case '^': check = !script->variable(log, _variable).contains(value);
case '^': check = !script->variable(_variable).contains(value);
break;
case '~': check =
script->variable(log, _variable).contains(QRegularExpression(value));
script->variable(_variable).contains(QRegularExpression(value));
break;
case '<': check = script->variable(log, _variable).toInt()<value.toInt();
case '<': check = script->variable(_variable).toInt()<value.toInt();
break;
case '>': check = script->variable(log, _variable).toInt()>value.toInt();
case '>': check = script->variable(_variable).toInt()>value.toInt();
break;
default:;
}
log(QString("evaluated expression to ")+(check?"true":"false")+": "
+script->variable(log, _variable)+" "+_cmp+" "+value);
+script->variable(_variable)+" "+_cmp+" "+value);
}
if (check) return runScript(log, this, _script, script, frame);
else if (_else) return runScript(log, this, _else, script, frame);
return true;
}
protected:
int countSteps(Script* parent) {
int res1(CommandContainer::countSteps(parent)), res2(_else->countSteps()+1);
return res1 > res2 ? res1 : res2;
}
private:
QString _variable;
QString _cmp;
QString _value;
std::shared_ptr<Script> _script;
std::shared_ptr<Script> _else;
};
@ -2428,7 +2453,7 @@ class Check: public Command {
std::shared_ptr<Command> _next;
};
class For: public Command {
class For: public CommandContainer {
public:
QString tag() const {
return "for";
@ -2452,29 +2477,35 @@ class For: public Command {
QString command() const {
return tag()+" "+_variable+" "+_vals.join(" ");
}
std::shared_ptr<Command> parse(Script*, QString args,
std::shared_ptr<Command> parse(Script* script, QString args,
QStringList& in, QString file, int line, int indent) {
std::shared_ptr<For> cmd(new For());
if (!args.size()) throw BadArgument(tag()+" requires a <variable>");
QStringList allargs(args.split("->"));
cmd->_variable = allargs.takeFirst().trimmed();
cmd->_vals = commaSeparatedList(allargs.join("->"));
cmd->_script = std::shared_ptr<Script>(new Script);
cmd->_script->parse(subCommandBlock(in), file, line+1, indent+1);
cmd->_script = cmd->subParser(script, subCommandBlock(in), file, line, indent);
return cmd;
}
bool execute(Script* script, QWebFrame* frame) {
Logger log(this, script);
Q_FOREACH(QString i, _vals.size()?_vals:commaSeparatedList(script->variable(log, _variable))) {
Q_FOREACH(QString i, _vals.size()?_vals:commaSeparatedList(script->variable(_variable))) {
if (!runScript(log, this, _script, script, frame, QStringList()<<_variable, QStringList()<<i))
return false;
}
return true;
}
int countSteps(Script* parent) const {
int sz(1);
if (_vals.size())
sz = _vals.size();
else if (parent->variables().contains(_variable))
sz = commaSeparatedList(parent->variable(_variable)).size();
return (sz?sz:1)*CommandContainer::countSteps(parent);
}
private:
QString _variable;
QStringList _vals;
std::shared_ptr<Script> _script;
};
class Echo: public Command {
@ -2575,7 +2606,7 @@ class ClearCookies: public Command {
QString _url;
};
class Include: public Command {
class Include: public CommandContainer {
public:
QString tag() const {
return "include";
@ -2597,11 +2628,12 @@ class Include: public Command {
if (!f.open(QIODevice::ReadOnly)) throw OpenIncludeFailed(cmd->_filename);
QString txt(QString::fromUtf8(f.readAll()));
try {
cmd->_script = std::shared_ptr<Script>(new Script);
cmd->_script->parse(txt.split('\n'), cmd->_filename, 1, indent+1);
cmd->_script = cmd->subParser(script, txt.split('\n'), cmd->_filename, 0, indent);
} catch (Exception& e) {
throw ParseIncludeFailed(cmd->_filename, e.what());
}
Q_FOREACH(QString key, cmd->_script->functions()) // copy new functions to parent
script->function(key, cmd->_script->function(key));
return cmd;
}
bool execute(Script* script, QWebFrame* frame) {
@ -2614,7 +2646,6 @@ class Include: public Command {
}
private:
QString _filename;
std::shared_ptr<Script> _script;
};
class Case: public Command {
@ -2670,7 +2701,7 @@ class Case: public Command {
}
return tag()+" "+_variable+body;
}
std::shared_ptr<Command> parse(Script*, QString args,
std::shared_ptr<Command> parse(Script* script, QString args,
QStringList& in, QString file, int line,
int indent) {
std::shared_ptr<Case> cmd(new Case());
@ -2684,9 +2715,9 @@ class Case: public Command {
QString value(parts.join(' '));
if (!cmp.contains(QRegularExpression("^[=!.^~<>]|->|default$")))
throw BadArgument(tag()+" needs a comparision, not: "+cmp);
std::shared_ptr<Script> script(std::shared_ptr<Script>(new Script));
script->parse(subCommandBlock(body), file, line+1, indent+2);
cmd->_conditions.append(Condition(cmp, value, script));
std::shared_ptr<Script> sub
(cmd->subParser(script, subCommandBlock(body), file, line, indent+1));
cmd->_conditions.append(Condition(cmp, value, sub));
}
return cmd;
}
@ -2712,31 +2743,39 @@ class Case: public Command {
+selector+" "+condition.cmp+" "+value);
} else {
switch (condition.cmp[0].toLatin1()) {
case '=': check = script->variable(log, _variable)==value;
case '=': check = script->variable(_variable)==value;
break;
case '!': check = script->variable(log, _variable)!=value;
case '!': check = script->variable(_variable)!=value;
break;
case '.': check = script->variable(log, _variable).contains(value);
case '.': check = script->variable(_variable).contains(value);
break;
case '^': check = !script->variable(log, _variable).contains(value);
case '^': check = !script->variable(_variable).contains(value);
break;
case '~': check =
script->variable(log, _variable).contains(QRegularExpression(value));
script->variable(_variable).contains(QRegularExpression(value));
break;
case '<': check = script->variable(log, _variable).toInt()<value.toInt();
case '<': check = script->variable(_variable).toInt()<value.toInt();
break;
case '>': check = script->variable(log, _variable).toInt()>value.toInt();
case '>': check = script->variable(_variable).toInt()>value.toInt();
break;
default:;
}
log(QString("evaluated expression to ")+(check?"true":"false")+": "
+script->variable(log, _variable)+" "+condition.cmp+" "+value);
+script->variable(_variable)+" "+condition.cmp+" "+value);
}
if (check) return runScript(log, this, condition.script, script, frame);
}
return true;
}
private:
int countSteps(Script* parent) {
int res1(0);
for (auto condition: _conditions) {
int res2(condition.script->countSteps()+1);
if (res2>res1) res1=res2;
}
return res1;
}
struct Condition {
Condition(QString c, QString v, std::shared_ptr<Script> s):
cmp(c), value(v), script(s) {
@ -2888,6 +2927,16 @@ inline Logger::~Logger() {
}
}
inline std::shared_ptr<Script> Command::subParser(Script* parent, const QStringList& in,
const QString& file,
int line, int indent) {
std::shared_ptr<Script> res(new Script);
Q_FOREACH(QString key, parent->functions()) // copy functions from parent
res->function(key, parent->function(key));
res->parse(in, file, line+1, indent+1);
return res;
}
inline bool Command::runScript(Logger& log, Command* parentCommand,
std::shared_ptr<Script> script,
Script* parent, QWebFrame* frame,
@ -2906,21 +2955,25 @@ inline bool Command::runScript(Logger& log, Command* parentCommand,
try {
assert(connect(&scriptCopy, SIGNAL(logging(QString)),
parent, SLOT(parentlog(QString))));
assert(connect(&scriptCopy, SIGNAL(progress(QString, int, int)),
parent, SLOT(innerProgress(QString, int))));
parent->removeSignals(frame);
bool res(scriptCopy.run(frame));
parent->addSignals(frame);
disconnect(&scriptCopy, SIGNAL(progress(QString, int, int)),
parent, SLOT(innerProgress(QString, int)));
disconnect(&scriptCopy, SIGNAL(logging(QString)),
parent, SLOT(parentlog(QString)));
parentCommand->_result = scriptCopy.result();
Q_FOREACH(QString key, scriptCopy.variables()) // copy new variables to parent
if (!vars.contains(key)) parent->set(key, scriptCopy.variable(log, key));
Q_FOREACH(QString key, scriptCopy.functions()) // copy new functions to parent
parent->function(key, scriptCopy.function(log, key));
if (!vars.contains(key)) parent->set(key, scriptCopy.variable(key));
if (parentCommand->_result.size())
parent->log("result: "+parentCommand->_result);
return res;
} catch (const Exception& x) {
parent->addSignals(frame);
disconnect(&scriptCopy, SIGNAL(progress(QString, int, int)),
parent, SLOT(innerProgress(QString, int)));
disconnect(&scriptCopy, SIGNAL(logging(QString)),
parent, SLOT(parentlog(QString)));
throw;

@ -174,7 +174,7 @@ int main(int argc, char *argv[]) try {
testcase.attr("name") = "parse test suite file";
testsuite<<testcase;
script.parse(txt.split('\n'), file);
expectedtestcases = script.steps()+2;
expectedtestcases = script.countSteps()+2;
if (failed) {
script.log("FAILED: "+file+" skipped due to previous error");
testcase.attr("name") = "testsuite";

Loading…
Cancel
Save