exception format and error reporting much improved

master 201811-before-redesign
Marc Wäckerlin 6 years ago
parent a1c721f45d
commit 74910f249a
  1. 32
      src/commands.hxx
  2. 112
      src/exceptions.hxx
  3. 32
      src/testgui.hxx

@ -100,7 +100,6 @@ class Logger {
~Logger(); ~Logger();
private: private:
Command* _command; Command* _command;
Command* _previous;
Script* _script; Script* _script;
}; };
@ -488,7 +487,7 @@ class Script: public QObject {
public: public:
Script(): Script():
_step(0), _clicktype(JAVASCRIPT_CLICK), _step(0), _clicktype(JAVASCRIPT_CLICK),
_screenshots(true), _command(0), _defaultTimeout(20) { _screenshots(true), _defaultTimeout(20) {
initPrototypes(); initPrototypes();
} }
Script(const Script& o): Script(const Script& o):
@ -496,8 +495,7 @@ class Script: public QObject {
_prototypes(o._prototypes), _prototypes(o._prototypes),
_step(0), _step(0),
_script(o._script), _script(o._script),
_screenshots(true), _screenshots(true) {
_command(0) {
set(o); set(o);
} }
QString syntax() const { QString syntax() const {
@ -597,8 +595,7 @@ class Script: public QObject {
command->indent(indent); command->indent(indent);
return command; return command;
} catch (Exception& e) { } catch (Exception& e) {
e.line(linenr); e.prependFileLine(filename, linenr);
e.file(filename);
throw; throw;
} }
void parse(QStringList in, QString filename, int line = 1, int indent = 0) { 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 if (!_ignores.size() || (*cmd)->tag()=="label") { // not ignored
_timer.start(_timeout*1000); _timer.start(_timeout*1000);
try { try {
command(*cmd);
if (!(res=(*cmd)->execute(this, frame))) { if (!(res=(*cmd)->execute(this, frame))) {
_timer.stop(); _timer.stop();
if (!back) retries = 0; else --back; if (!back) retries = 0; else --back;
@ -755,8 +753,7 @@ class Script: public QObject {
if ((*cmd)->isTestcase()) if ((*cmd)->isTestcase())
_testsuites->last()<<testcase; _testsuites->last()<<testcase;
removeSignals(frame); removeSignals(frame);
e.line((*cmd)->line()); e.prependFileLine((*cmd)->file(), (*cmd)->line());
e.file((*cmd)->file());
if (screenshots) if (screenshots)
try { // write html source and take a last screenshot on error try { // write html source and take a last screenshot on error
{ {
@ -783,11 +780,15 @@ class Script: public QObject {
progress("success", 0, 0); progress("success", 0, 0);
return res; return res;
} }
Command* command() { std::shared_ptr<Command> command() {
return _command; return _command;
} }
void command(Command* cmd) { void command(std::shared_ptr<Command> cmd) { // maintained by Logger
_command = cmd; _command = cmd;
if (_parent) _parent->command(cmd);
}
void parent(Script* p) {
_parent = p;
} }
QString& cout() { QString& cout() {
return _cout; return _cout;
@ -1033,7 +1034,7 @@ class Script: public QObject {
void log(QString text, Command* command = 0) { void log(QString text, Command* command = 0) {
QString prefix QString prefix
(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss ")); (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='?'; for (QChar& c: text) if (c<32&&c!='\n') c='?';
if (cmd) if (cmd)
prefix += QString("%2:%3%1 ") prefix += QString("%2:%3%1 ")
@ -1149,7 +1150,7 @@ class Script: public QObject {
QStringList(url.toString()))); QStringList(url.toString())));
} }
void timedout() { void timedout() {
error(TimeOut()); error(TimeOut(_command->command()));
} }
private: private:
struct AuthRealm { struct AuthRealm {
@ -1179,10 +1180,11 @@ class Script: public QObject {
QString _targetdir; QString _targetdir;
std::shared_ptr<xml::Node> _testsuites; ///< only valid within run std::shared_ptr<xml::Node> _testsuites; ///< only valid within run
QString _testclass; QString _testclass;
Command* _command; std::shared_ptr<Command> _command;
QString _path; QString _path;
QString _filename; QString _filename;
QMap<QString, AuthRealm> _auth; QMap<QString, AuthRealm> _auth;
Script* _parent = nullptr;
}; };
class CommandContainer: public Command { 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): inline Logger::Logger(Command* command, Script* script, bool showLines):
_command(command), _script(script) { _command(command), _script(script) {
if (command) { if (command) {
_previous = _script->command();
_script->command(command);
if (_command->log()) { if (_command->log()) {
if (showLines) if (showLines)
_script->log("\\ "+_command->command(), _command); _script->log("\\ "+_command->command(), _command);
@ -3172,7 +3172,6 @@ inline void Logger::operator[](QString txt) {
inline Logger::~Logger() { inline Logger::~Logger() {
if (_command) { if (_command) {
if (_command->log()) _script->log("/ "+_command->tag(), _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) { QStringList args) {
Script scriptCopy(*script); // only work with a copy of script Script scriptCopy(*script); // only work with a copy of script
scriptCopy.set(*parent); scriptCopy.set(*parent);
scriptCopy.parent(parent);
if (args.size()!=vars.size()) if (args.size()!=vars.size())
error(log, WrongNumberOfArguments(vars, args)); error(log, WrongNumberOfArguments(vars, args));
for (QStringList::iterator var(vars.begin()), arg(args.begin()); for (QStringList::iterator var(vars.begin()), arg(args.begin());

@ -20,11 +20,27 @@ class Exception: public std::exception {
bytes = _what.toUtf8(); bytes = _what.toUtf8();
return bytes.data(); return bytes.data();
} }
void line(int linenr) { void prependFileLine(QString filename, int linenr) {
if (linenr>0) _what=QString::number(linenr)+" "+_what; 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) { protected:
if (filename.size()) _what=filename+":"+_what; 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: protected:
QString _what; QString _what;
@ -32,23 +48,25 @@ class Exception: public std::exception {
class ParseError: public Exception { class ParseError: public Exception {
public: public:
ParseError(QString w): Exception("parse error: "+w) {} ParseError(QString w): Exception("parse error:"+p(w)) {}
}; };
class UnknownCommand: public ParseError { class UnknownCommand: public ParseError {
public: public:
UnknownCommand(QString line): ParseError("unknown command: \""+line+"\"") {} UnknownCommand(QString line): ParseError("unknown command:"+p(q(line))) {}
}; };
class BadArgument: public ParseError { class BadArgument: public ParseError {
public: public:
BadArgument(QString arg): ParseError("bad argument: "+arg) {} BadArgument(QString arg): ParseError("bad argument:"+p(arg)) {}
}; };
class MissingArguments: public ParseError { class MissingArguments: public ParseError {
public: public:
MissingArguments(QString args, QString req): 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 { class MissingLine: public ParseError {
@ -59,7 +77,7 @@ class MissingLine: public ParseError {
class TestFailed: public Exception { class TestFailed: public Exception {
public: public:
TestFailed(QString why): Exception("Test Failed: "+why) {} TestFailed(QString why): Exception("test failed:"+p(why)) {}
}; };
class PossibleRetryLoad: public TestFailed { class PossibleRetryLoad: public TestFailed {
@ -72,10 +90,10 @@ class WrongSignal: public PossibleRetryLoad {
WrongSignal(QString signal, QStringList args, WrongSignal(QString signal, QStringList args,
std::pair<QString, QStringList> received, std::pair<QString, QStringList> received,
QStringList sigs): QStringList sigs):
PossibleRetryLoad PossibleRetryLoad("wrong signal received:"
("expected: \""+signal+" "+args.join(' ')+"\"; " +p("expected: "+q(signal+" "+args.join(' ')))
"received: \""+received.first+" "+received.second.join(' ') +p("received: "+q(received.first+" "+received.second.join(' ')))
+"\"; queue: "+sigs.join(", ")) { +p("queue:"+pq(sigs))) {
} }
}; };
@ -86,7 +104,7 @@ class UnhandledSignals: public TestFailed {
TestFailed("unhandled signals:") { TestFailed("unhandled signals:") {
while (!sigs.empty()) { while (!sigs.empty()) {
Signal res(sigs.front()); Signal res(sigs.front());
_what += "\n"+res.first+" "+res.second.join(' '); _what += q(res.first+" "+res.second.join(' '));
sigs.pop(); sigs.pop();
} }
} }
@ -94,49 +112,49 @@ class UnhandledSignals: public TestFailed {
class TimeOut: public PossibleRetryLoad { class TimeOut: public PossibleRetryLoad {
public: public:
TimeOut(): PossibleRetryLoad("command timeout") {} TimeOut(QString cmd): PossibleRetryLoad("command timeout:"+p(cmd)) {}
}; };
class ElementNotFound: public TestFailed { class ElementNotFound: public TestFailed {
public: public:
ElementNotFound(QString selector): ElementNotFound(QString selector):
TestFailed("element not found: "+selector) {} TestFailed("element not found:"+pq(selector)) {}
}; };
class DirectoryCannotBeCreated: public TestFailed { class DirectoryCannotBeCreated: public TestFailed {
public: public:
DirectoryCannotBeCreated(QString name): DirectoryCannotBeCreated(QString name):
TestFailed("cannot create directory: "+name) {} TestFailed("cannot create directory:"+pq(name)) {}
}; };
class CannotWriteScreenshot: public TestFailed { class CannotWriteScreenshot: public TestFailed {
public: public:
CannotWriteScreenshot(QString name): CannotWriteScreenshot(QString name):
TestFailed("cannot write screenshot: "+name) {} TestFailed("cannot write screenshot:"+pq(name)) {}
}; };
class CannotWriteSouceHTML: public TestFailed { class CannotWriteSouceHTML: public TestFailed {
public: public:
CannotWriteSouceHTML(QString name): CannotWriteSouceHTML(QString name):
TestFailed("cannot write html source code: "+name) {} TestFailed("cannot write html source code:"+pq(name)) {}
}; };
class FileNotFound: public TestFailed { class FileNotFound: public TestFailed {
public: public:
FileNotFound(QString arg): TestFailed("file not found: "+arg) {} FileNotFound(QString arg): TestFailed("file not found:"+pq(arg)) {}
}; };
class NotACertificate: public TestFailed { class NotACertificate: public TestFailed {
public: public:
NotACertificate(QString arg): NotACertificate(QString arg):
TestFailed("file is not a certificate: "+arg) { TestFailed("file is not a certificate:"+pq(arg)) {
} }
}; };
class KeyNotReadable: public TestFailed { class KeyNotReadable: public TestFailed {
public: public:
KeyNotReadable(QString arg): 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 { class LastFileNotUploaded: public TestFailed {
public: public:
LastFileNotUploaded(QString arg): 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 { class SetFileUploadFailed: public TestFailed {
public: public:
SetFileUploadFailed(QString selector, QString filename): SetFileUploadFailed(QString selector, QString filename):
TestFailed("set file upload failed for selector "+selector TestFailed("set file upload failed for:"
+" and file "+filename) { +p("selector: "+q(selector))
+p("file: "+q(filename))) {
} }
}; };
class AssertionFailed: public TestFailed { class AssertionFailed: public TestFailed {
public: public:
AssertionFailed(QString text): AssertionFailed(QString text):
TestFailed("assertion failed: "+text) { TestFailed("assertion failed:"+pq(text)) {
} }
}; };
class DownloadFailed: public TestFailed { class DownloadFailed: public TestFailed {
public: public:
DownloadFailed(QString file): DownloadFailed(QString file):
TestFailed("download failed of file \""+file+"\"") { TestFailed("download failed of file:"+pq(file)) {
} }
}; };
class WriteFileFailed: public TestFailed { class WriteFileFailed: public TestFailed {
public: public:
WriteFileFailed(QString file): WriteFileFailed(QString file):
TestFailed("write failed of file \""+QDir(file).absolutePath()+"\"") { TestFailed("write failed of file:"+pq(QDir(file).absolutePath())) {
} }
}; };
class ScriptFailed: public TestFailed { class ScriptFailed: public TestFailed {
public: public:
ScriptFailed(QString dsc, QString cmd, QStringList args, QString script): ScriptFailed(QString dsc, QString cmd, QStringList args, QString script):
TestFailed(dsc+"; command: "+cmd TestFailed(dsc
+(args.size()?" "+args.join(' '):QString()) +p("command: "
+(script.size()?"\n"+script:QString())) { +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, ScriptExecutionFailed(QString command, QStringList args, QString script,
int code, QString sout, QString serr): int code, QString sout, QString serr):
ScriptFailed("failed with exit code "+QString::number(code) ScriptFailed("failed with exit code "+QString::number(code)
+(sout.size()?"; sout=\""+sout+"\"":"") +(sout.size()?p("stdout: "+q(sout)):"")
+(serr.size()?"; serr=\""+serr+"\"":""), +(serr.size()?p("stderr: "+q(serr)):""),
command, args, script) { command, args, script) {
} }
}; };
@ -244,53 +264,53 @@ class FunctionCallFailed: public TestFailed {
public: public:
FunctionCallFailed(QString name, QStringList vars, QStringList args, FunctionCallFailed(QString name, QStringList vars, QStringList args,
const std::exception& x): const std::exception& x):
TestFailed("function call failed: "+name) { TestFailed("function call failed:"+p(name)) {
for (QStringList::iterator var(vars.begin()), arg(args.begin()); for (QStringList::iterator var(vars.begin()), arg(args.begin());
var<vars.end() && arg<args.end(); ++var, ++arg) var<vars.end() && arg<args.end(); ++var, ++arg)
_what += " "+*var+"="+*arg; _what += " "+*var+"="+*arg;
_what += QString("; reason: ")+x.what(); _what += p("reason:"+p(x.what()));
} }
}; };
class FunctionNotFound: public TestFailed { class FunctionNotFound: public TestFailed {
public: public:
FunctionNotFound(QString name): TestFailed("function not found: "+name) { FunctionNotFound(QString name): TestFailed("function not found:"+pq(name)) {}
}
}; };
class VariableNotFound: public TestFailed { class VariableNotFound: public TestFailed {
public: public:
VariableNotFound(QString name): VariableNotFound(QString name):
TestFailed("variable not found: "+name) { TestFailed("variable not found:"+pq(name)) {
} }
}; };
class CheckFailed: public TestFailed { class CheckFailed: public TestFailed {
public: public:
CheckFailed(QString value1, char cmp, QString value2): CheckFailed(QString value1, char cmp, QString value2):
TestFailed(QString("check failed: %1 %2 %3") TestFailed("check failed:"
.arg(value1).arg(cmp).arg(value2)) { +p(QString("%1 %2 %3")
.arg(value1).arg(cmp).arg(value2))) {
} }
}; };
class OpenIncludeFailed: public TestFailed { class OpenIncludeFailed: public TestFailed {
public: public:
OpenIncludeFailed(QString file): 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 { class ParseIncludeFailed: public TestFailed {
public: public:
ParseIncludeFailed(QString file, QString msg): 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 { class ExecuteIncludeFailed: public TestFailed {
public: public:
ExecuteIncludeFailed(QString file, QString msg): 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->setChecked(false);
_record->setEnabled(false); _record->setEnabled(false);
_run->setEnabled(false); _run->setEnabled(false);
Script script;
try { try {
Script script;
connect(&script, SIGNAL(logging(QString)), SLOT(logging(QString))); connect(&script, SIGNAL(logging(QString)), SLOT(logging(QString)));
connect(&script, SIGNAL(progress(QString, int, int)), SLOT(progress(QString, int, int))); connect(&script, SIGNAL(progress(QString, int, int)), SLOT(progress(QString, int, int)));
std::shared_ptr<xml::Node> testsuites(new xml::Node("testsuite")); 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); _status->setCurrentIndex(STATUS_SUCCESS);
} catch (std::exception &x) { } catch (std::exception &x) {
_status->setCurrentIndex(STATUS_ERROR); _status->setCurrentIndex(STATUS_ERROR);
QMessageBox::critical(this, tr("Test Failed"), tr("Error [%1]: %2") std::shared_ptr<Command> cmd(script.command());
.arg(demangle(typeid(x).name())) if (cmd)
.arg(x.what())); 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); _run->setEnabled(true);
_record->setEnabled(true); _record->setEnabled(true);

Loading…
Cancel
Save