exception format and error reporting much improved
This commit is contained in:
@@ -100,7 +100,6 @@ class Logger {
|
||||
~Logger();
|
||||
private:
|
||||
Command* _command;
|
||||
Command* _previous;
|
||||
Script* _script;
|
||||
};
|
||||
|
||||
@@ -488,7 +487,7 @@ class Script: public QObject {
|
||||
public:
|
||||
Script():
|
||||
_step(0), _clicktype(JAVASCRIPT_CLICK),
|
||||
_screenshots(true), _command(0), _defaultTimeout(20) {
|
||||
_screenshots(true), _defaultTimeout(20) {
|
||||
initPrototypes();
|
||||
}
|
||||
Script(const Script& o):
|
||||
@@ -496,8 +495,7 @@ class Script: public QObject {
|
||||
_prototypes(o._prototypes),
|
||||
_step(0),
|
||||
_script(o._script),
|
||||
_screenshots(true),
|
||||
_command(0) {
|
||||
_screenshots(true) {
|
||||
set(o);
|
||||
}
|
||||
QString syntax() const {
|
||||
@@ -597,8 +595,7 @@ class Script: public QObject {
|
||||
command->indent(indent);
|
||||
return command;
|
||||
} catch (Exception& e) {
|
||||
e.line(linenr);
|
||||
e.file(filename);
|
||||
e.prependFileLine(filename, linenr);
|
||||
throw;
|
||||
}
|
||||
void parse(QStringList in, QString filename, int line = 1, int indent = 0) {
|
||||
@@ -662,6 +659,7 @@ class Script: public QObject {
|
||||
if (!_ignores.size() || (*cmd)->tag()=="label") { // not ignored
|
||||
_timer.start(_timeout*1000);
|
||||
try {
|
||||
command(*cmd);
|
||||
if (!(res=(*cmd)->execute(this, frame))) {
|
||||
_timer.stop();
|
||||
if (!back) retries = 0; else --back;
|
||||
@@ -755,8 +753,7 @@ class Script: public QObject {
|
||||
if ((*cmd)->isTestcase())
|
||||
_testsuites->last()<<testcase;
|
||||
removeSignals(frame);
|
||||
e.line((*cmd)->line());
|
||||
e.file((*cmd)->file());
|
||||
e.prependFileLine((*cmd)->file(), (*cmd)->line());
|
||||
if (screenshots)
|
||||
try { // write html source and take a last screenshot on error
|
||||
{
|
||||
@@ -783,11 +780,15 @@ class Script: public QObject {
|
||||
progress("success", 0, 0);
|
||||
return res;
|
||||
}
|
||||
Command* command() {
|
||||
std::shared_ptr<Command> command() {
|
||||
return _command;
|
||||
}
|
||||
void command(Command* cmd) {
|
||||
void command(std::shared_ptr<Command> cmd) { // maintained by Logger
|
||||
_command = cmd;
|
||||
if (_parent) _parent->command(cmd);
|
||||
}
|
||||
void parent(Script* p) {
|
||||
_parent = p;
|
||||
}
|
||||
QString& cout() {
|
||||
return _cout;
|
||||
@@ -1033,7 +1034,7 @@ class Script: public QObject {
|
||||
void log(QString text, Command* command = 0) {
|
||||
QString prefix
|
||||
(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss "));
|
||||
Command* cmd(command?command:_command);
|
||||
Command* cmd(command?command:_command.get());
|
||||
for (QChar& c: text) if (c<32&&c!='\n') c='?';
|
||||
if (cmd)
|
||||
prefix += QString("%2:%3%1 ")
|
||||
@@ -1149,7 +1150,7 @@ class Script: public QObject {
|
||||
QStringList(url.toString())));
|
||||
}
|
||||
void timedout() {
|
||||
error(TimeOut());
|
||||
error(TimeOut(_command->command()));
|
||||
}
|
||||
private:
|
||||
struct AuthRealm {
|
||||
@@ -1179,10 +1180,11 @@ class Script: public QObject {
|
||||
QString _targetdir;
|
||||
std::shared_ptr<xml::Node> _testsuites; ///< only valid within run
|
||||
QString _testclass;
|
||||
Command* _command;
|
||||
std::shared_ptr<Command> _command;
|
||||
QString _path;
|
||||
QString _filename;
|
||||
QMap<QString, AuthRealm> _auth;
|
||||
Script* _parent = nullptr;
|
||||
};
|
||||
|
||||
class CommandContainer: public Command {
|
||||
@@ -3153,8 +3155,6 @@ inline bool Screenshot::execute(Script* script, QWebFrame* frame) {
|
||||
inline Logger::Logger(Command* command, Script* script, bool showLines):
|
||||
_command(command), _script(script) {
|
||||
if (command) {
|
||||
_previous = _script->command();
|
||||
_script->command(command);
|
||||
if (_command->log()) {
|
||||
if (showLines)
|
||||
_script->log("\\ "+_command->command(), _command);
|
||||
@@ -3172,7 +3172,6 @@ inline void Logger::operator[](QString txt) {
|
||||
inline Logger::~Logger() {
|
||||
if (_command) {
|
||||
if (_command->log()) _script->log("/ "+_command->tag(), _command);
|
||||
_script->command(_previous);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3200,6 +3199,7 @@ inline bool Command::runScript(Logger& log, Command* parentCommand,
|
||||
QStringList args) {
|
||||
Script scriptCopy(*script); // only work with a copy of script
|
||||
scriptCopy.set(*parent);
|
||||
scriptCopy.parent(parent);
|
||||
if (args.size()!=vars.size())
|
||||
error(log, WrongNumberOfArguments(vars, args));
|
||||
for (QStringList::iterator var(vars.begin()), arg(args.begin());
|
||||
|
@@ -20,11 +20,27 @@ class Exception: public std::exception {
|
||||
bytes = _what.toUtf8();
|
||||
return bytes.data();
|
||||
}
|
||||
void line(int linenr) {
|
||||
if (linenr>0) _what=QString::number(linenr)+" "+_what;
|
||||
void prependFileLine(QString filename, int linenr) {
|
||||
if (filename.size()) {
|
||||
if (linenr>0)
|
||||
_what = "→ in file "+filename+" at line "+QString::number(linenr)+":\n→ "+_what;
|
||||
else
|
||||
_what = "→ in file "+filename+":\n→ "+_what;
|
||||
} else if (linenr>0)
|
||||
_what = "→ at line "+QString::number(linenr)+":\n→ "+_what;
|
||||
}
|
||||
void file(QString filename) {
|
||||
if (filename.size()) _what=filename+":"+_what;
|
||||
protected:
|
||||
QString q(const QString& text) const {
|
||||
return '"'+text+'"';
|
||||
}
|
||||
QString p(const QString& text) const {
|
||||
return QString("\n"+text).replace("\n", "\n ");
|
||||
}
|
||||
QString pq(const QString& text) const {
|
||||
return p(q(text));
|
||||
}
|
||||
QString pq(const QStringList& texts) const {
|
||||
return pq(texts.join("\"\n\""));
|
||||
}
|
||||
protected:
|
||||
QString _what;
|
||||
@@ -32,23 +48,25 @@ class Exception: public std::exception {
|
||||
|
||||
class ParseError: public Exception {
|
||||
public:
|
||||
ParseError(QString w): Exception("parse error: "+w) {}
|
||||
ParseError(QString w): Exception("parse error:"+p(w)) {}
|
||||
};
|
||||
|
||||
class UnknownCommand: public ParseError {
|
||||
public:
|
||||
UnknownCommand(QString line): ParseError("unknown command: \""+line+"\"") {}
|
||||
UnknownCommand(QString line): ParseError("unknown command:"+p(q(line))) {}
|
||||
};
|
||||
|
||||
class BadArgument: public ParseError {
|
||||
public:
|
||||
BadArgument(QString arg): ParseError("bad argument: "+arg) {}
|
||||
BadArgument(QString arg): ParseError("bad argument:"+p(arg)) {}
|
||||
};
|
||||
|
||||
class MissingArguments: public ParseError {
|
||||
public:
|
||||
MissingArguments(QString args, QString req):
|
||||
ParseError("missing arguments, requires "+req+", got: "+args) {}
|
||||
ParseError("missing arguments:"
|
||||
+p("requires: "+req)
|
||||
+p("got: "+q(args))) {}
|
||||
};
|
||||
|
||||
class MissingLine: public ParseError {
|
||||
@@ -59,7 +77,7 @@ class MissingLine: public ParseError {
|
||||
|
||||
class TestFailed: public Exception {
|
||||
public:
|
||||
TestFailed(QString why): Exception("Test Failed: "+why) {}
|
||||
TestFailed(QString why): Exception("test failed:"+p(why)) {}
|
||||
};
|
||||
|
||||
class PossibleRetryLoad: public TestFailed {
|
||||
@@ -72,10 +90,10 @@ class WrongSignal: public PossibleRetryLoad {
|
||||
WrongSignal(QString signal, QStringList args,
|
||||
std::pair<QString, QStringList> received,
|
||||
QStringList sigs):
|
||||
PossibleRetryLoad
|
||||
("expected: \""+signal+" "+args.join(' ')+"\"; "
|
||||
"received: \""+received.first+" "+received.second.join(' ')
|
||||
+"\"; queue: "+sigs.join(", ")) {
|
||||
PossibleRetryLoad("wrong signal received:"
|
||||
+p("expected: "+q(signal+" "+args.join(' ')))
|
||||
+p("received: "+q(received.first+" "+received.second.join(' ')))
|
||||
+p("queue:"+pq(sigs))) {
|
||||
}
|
||||
};
|
||||
|
||||
@@ -86,7 +104,7 @@ class UnhandledSignals: public TestFailed {
|
||||
TestFailed("unhandled signals:") {
|
||||
while (!sigs.empty()) {
|
||||
Signal res(sigs.front());
|
||||
_what += "\n"+res.first+" "+res.second.join(' ');
|
||||
_what += q(res.first+" "+res.second.join(' '));
|
||||
sigs.pop();
|
||||
}
|
||||
}
|
||||
@@ -94,49 +112,49 @@ class UnhandledSignals: public TestFailed {
|
||||
|
||||
class TimeOut: public PossibleRetryLoad {
|
||||
public:
|
||||
TimeOut(): PossibleRetryLoad("command timeout") {}
|
||||
TimeOut(QString cmd): PossibleRetryLoad("command timeout:"+p(cmd)) {}
|
||||
};
|
||||
|
||||
class ElementNotFound: public TestFailed {
|
||||
public:
|
||||
ElementNotFound(QString selector):
|
||||
TestFailed("element not found: "+selector) {}
|
||||
TestFailed("element not found:"+pq(selector)) {}
|
||||
};
|
||||
|
||||
class DirectoryCannotBeCreated: public TestFailed {
|
||||
public:
|
||||
DirectoryCannotBeCreated(QString name):
|
||||
TestFailed("cannot create directory: "+name) {}
|
||||
TestFailed("cannot create directory:"+pq(name)) {}
|
||||
};
|
||||
|
||||
class CannotWriteScreenshot: public TestFailed {
|
||||
public:
|
||||
CannotWriteScreenshot(QString name):
|
||||
TestFailed("cannot write screenshot: "+name) {}
|
||||
TestFailed("cannot write screenshot:"+pq(name)) {}
|
||||
};
|
||||
|
||||
class CannotWriteSouceHTML: public TestFailed {
|
||||
public:
|
||||
CannotWriteSouceHTML(QString name):
|
||||
TestFailed("cannot write html source code: "+name) {}
|
||||
TestFailed("cannot write html source code:"+pq(name)) {}
|
||||
};
|
||||
|
||||
class FileNotFound: public TestFailed {
|
||||
public:
|
||||
FileNotFound(QString arg): TestFailed("file not found: "+arg) {}
|
||||
FileNotFound(QString arg): TestFailed("file not found:"+pq(arg)) {}
|
||||
};
|
||||
|
||||
class NotACertificate: public TestFailed {
|
||||
public:
|
||||
NotACertificate(QString arg):
|
||||
TestFailed("file is not a certificate: "+arg) {
|
||||
TestFailed("file is not a certificate:"+pq(arg)) {
|
||||
}
|
||||
};
|
||||
|
||||
class KeyNotReadable: public TestFailed {
|
||||
public:
|
||||
KeyNotReadable(QString arg):
|
||||
TestFailed("key file is not readable (password?): "+arg) {
|
||||
TestFailed("key file is not readable (password?):"+pq(arg)) {
|
||||
}
|
||||
};
|
||||
|
||||
@@ -148,7 +166,7 @@ class NotUnattended: public TestFailed {
|
||||
class LastFileNotUploaded: public TestFailed {
|
||||
public:
|
||||
LastFileNotUploaded(QString arg):
|
||||
TestFailed("last specified upload file has not been uploaded: "+arg) {
|
||||
TestFailed("last specified upload file has not been uploaded:"+pq(arg)) {
|
||||
}
|
||||
};
|
||||
|
||||
@@ -165,38 +183,40 @@ class NoUploadFile: public TestFailed {
|
||||
class SetFileUploadFailed: public TestFailed {
|
||||
public:
|
||||
SetFileUploadFailed(QString selector, QString filename):
|
||||
TestFailed("set file upload failed for selector "+selector
|
||||
+" and file "+filename) {
|
||||
TestFailed("set file upload failed for:"
|
||||
+p("selector: "+q(selector))
|
||||
+p("file: "+q(filename))) {
|
||||
}
|
||||
};
|
||||
|
||||
class AssertionFailed: public TestFailed {
|
||||
public:
|
||||
AssertionFailed(QString text):
|
||||
TestFailed("assertion failed: "+text) {
|
||||
TestFailed("assertion failed:"+pq(text)) {
|
||||
}
|
||||
};
|
||||
|
||||
class DownloadFailed: public TestFailed {
|
||||
public:
|
||||
DownloadFailed(QString file):
|
||||
TestFailed("download failed of file \""+file+"\"") {
|
||||
TestFailed("download failed of file:"+pq(file)) {
|
||||
}
|
||||
};
|
||||
|
||||
class WriteFileFailed: public TestFailed {
|
||||
public:
|
||||
WriteFileFailed(QString file):
|
||||
TestFailed("write failed of file \""+QDir(file).absolutePath()+"\"") {
|
||||
TestFailed("write failed of file:"+pq(QDir(file).absolutePath())) {
|
||||
}
|
||||
};
|
||||
|
||||
class ScriptFailed: public TestFailed {
|
||||
public:
|
||||
ScriptFailed(QString dsc, QString cmd, QStringList args, QString script):
|
||||
TestFailed(dsc+"; command: "+cmd
|
||||
+(args.size()?" "+args.join(' '):QString())
|
||||
+(script.size()?"\n"+script:QString())) {
|
||||
TestFailed(dsc
|
||||
+p("command: "
|
||||
+pq(cmd+(args.size()?" "+args.join(' '):QString())))
|
||||
+(script.size()?p("script:"+p(script)):QString())) {
|
||||
}
|
||||
};
|
||||
|
||||
@@ -226,8 +246,8 @@ class ScriptExecutionFailed: public ScriptFailed {
|
||||
ScriptExecutionFailed(QString command, QStringList args, QString script,
|
||||
int code, QString sout, QString serr):
|
||||
ScriptFailed("failed with exit code "+QString::number(code)
|
||||
+(sout.size()?"; sout=\""+sout+"\"":"")
|
||||
+(serr.size()?"; serr=\""+serr+"\"":""),
|
||||
+(sout.size()?p("stdout: "+q(sout)):"")
|
||||
+(serr.size()?p("stderr: "+q(serr)):""),
|
||||
command, args, script) {
|
||||
}
|
||||
};
|
||||
@@ -244,53 +264,53 @@ 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();
|
||||
TestFailed("function call failed:"+p(name)) {
|
||||
for (QStringList::iterator var(vars.begin()), arg(args.begin());
|
||||
var<vars.end() && arg<args.end(); ++var, ++arg)
|
||||
_what += " "+*var+"="+*arg;
|
||||
_what += p("reason:"+p(x.what()));
|
||||
}
|
||||
};
|
||||
|
||||
class FunctionNotFound: public TestFailed {
|
||||
public:
|
||||
FunctionNotFound(QString name): TestFailed("function not found: "+name) {
|
||||
}
|
||||
FunctionNotFound(QString name): TestFailed("function not found:"+pq(name)) {}
|
||||
};
|
||||
|
||||
class VariableNotFound: public TestFailed {
|
||||
public:
|
||||
VariableNotFound(QString name):
|
||||
TestFailed("variable not found: "+name) {
|
||||
TestFailed("variable not found:"+pq(name)) {
|
||||
}
|
||||
};
|
||||
|
||||
class CheckFailed: public TestFailed {
|
||||
public:
|
||||
CheckFailed(QString value1, char cmp, QString value2):
|
||||
TestFailed(QString("check failed: %1 %2 %3")
|
||||
.arg(value1).arg(cmp).arg(value2)) {
|
||||
TestFailed("check failed:"
|
||||
+p(QString("%1 %2 %3")
|
||||
.arg(value1).arg(cmp).arg(value2))) {
|
||||
}
|
||||
};
|
||||
|
||||
class OpenIncludeFailed: public TestFailed {
|
||||
public:
|
||||
OpenIncludeFailed(QString file):
|
||||
TestFailed(QString("open include file %1 failed").arg(file)) {
|
||||
TestFailed(QString("open include file %1 failed").arg(q(file))) {
|
||||
}
|
||||
};
|
||||
|
||||
class ParseIncludeFailed: public TestFailed {
|
||||
public:
|
||||
ParseIncludeFailed(QString file, QString msg):
|
||||
TestFailed(QString("parse include file %1 failed with: %2").arg(file).arg(msg)) {
|
||||
TestFailed(QString("parse include file %1 failed with:%2").arg(q(file)).arg(p(msg))) {
|
||||
}
|
||||
};
|
||||
|
||||
class ExecuteIncludeFailed: public TestFailed {
|
||||
public:
|
||||
ExecuteIncludeFailed(QString file, QString msg):
|
||||
TestFailed(QString("error in included file %1: %2").arg(file).arg(msg)) {
|
||||
TestFailed(QString("error in included file %1:%2").arg(file).arg(p(msg))) {
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -156,8 +156,8 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
|
||||
_record->setChecked(false);
|
||||
_record->setEnabled(false);
|
||||
_run->setEnabled(false);
|
||||
Script script;
|
||||
try {
|
||||
Script script;
|
||||
connect(&script, SIGNAL(logging(QString)), SLOT(logging(QString)));
|
||||
connect(&script, SIGNAL(progress(QString, int, int)), SLOT(progress(QString, int, int)));
|
||||
std::shared_ptr<xml::Node> testsuites(new xml::Node("testsuite"));
|
||||
@@ -176,9 +176,33 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
|
||||
_status->setCurrentIndex(STATUS_SUCCESS);
|
||||
} catch (std::exception &x) {
|
||||
_status->setCurrentIndex(STATUS_ERROR);
|
||||
QMessageBox::critical(this, tr("Test Failed"), tr("Error [%1]: %2")
|
||||
.arg(demangle(typeid(x).name()))
|
||||
.arg(x.what()));
|
||||
std::shared_ptr<Command> cmd(script.command());
|
||||
if (cmd)
|
||||
QMessageBox::critical(this,
|
||||
tr("Test Failed"),
|
||||
tr("<html>"
|
||||
" <h1>Error [%1]</h1>"
|
||||
" <dl>"
|
||||
" <dt>Command:</dt><dd><code>%3</code></dd>"
|
||||
" <dt>File:</dt><dd>%4</dd>"
|
||||
" <dt>Line:</dt><dd>%5</dd>"
|
||||
" <dt>Error Message:</dt><dd><pre>%2</pre></dd>"
|
||||
" </dl>"
|
||||
"</html>")
|
||||
.arg(demangle(typeid(x).name()))
|
||||
.arg(x.what())
|
||||
.arg(cmd->command())
|
||||
.arg(cmd->file())
|
||||
.arg(cmd->line()));
|
||||
else
|
||||
QMessageBox::critical(this,
|
||||
tr("Test Failed"),
|
||||
tr("<html>"
|
||||
" <h1>Error [%1]</h1>"
|
||||
" <p><code>%2</code></p>"
|
||||
"</html>")
|
||||
.arg(demangle(typeid(x).name()))
|
||||
.arg(QString(x.what()).replace("\n", "<br/>")));
|
||||
}
|
||||
_run->setEnabled(true);
|
||||
_record->setEnabled(true);
|
||||
|
Reference in New Issue
Block a user