exception format and error reporting much improved

master 201811-before-redesign
Marc Wäckerlin 5 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();
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);

Loading…
Cancel
Save