241 lines
8.7 KiB
C++
241 lines
8.7 KiB
C++
#include <commands.hxx>
|
|
#include <webpage.hxx>
|
|
#include <QApplication>
|
|
#include <QFile>
|
|
#include <QWebView>
|
|
#include <QStringList>
|
|
#include <QCommandLineParser>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <fstream>
|
|
#include <QDateTime>
|
|
|
|
#include <xml-cxx/xml.hxx>
|
|
#include <mrw/string.hxx>
|
|
|
|
#include <version.hxx>
|
|
using namespace NAMESPACE;
|
|
|
|
/** @page junitXml Test Output XML Format
|
|
|
|
The test output XML format emulates JUnit's format, so that JUnit
|
|
tools can be used to evaluate ist, namely on tools like jenkins.
|
|
|
|
@code{.xml}
|
|
<testsuites>
|
|
<testsuite name="..." timestamp="..." failures="...">
|
|
<testcase classname="..." name="...">
|
|
<failure message="..." />
|
|
<system-out>...</system-out>
|
|
<system-err>...</system-err>
|
|
</testcase>
|
|
<testcase ... />
|
|
</testsuite>
|
|
<testsuite ...>
|
|
...
|
|
</testsuite>
|
|
<testsuites>
|
|
@endcode
|
|
|
|
The default name for a testsuite is the name of the test script
|
|
file. it can be overwritten using the @c testsuite statement. The
|
|
attribute @c failures contains the number of failed test cases.
|
|
|
|
The default classname for a testcase is @c testsuite-preparation
|
|
before the script runs, then in the script it is the name of the
|
|
current command and the classname equals to the testsuite's name.
|
|
|
|
If you specify testsuites, testcases in your script file, then
|
|
this overwrites the defaults. Here @c testsuite maps to the
|
|
testsuite's name, @c testcase maps to the next testcase's
|
|
classname.
|
|
|
|
*/
|
|
|
|
QString format(QString txt, int indent = 2, int cpl = 80) {
|
|
QStringList res;
|
|
QStringList lines(txt.split('\n'));
|
|
QString ind(indent, ' ');
|
|
Q_FOREACH(QString line, lines) {
|
|
line.insert(0, ind);
|
|
for (int pos(indent); line.size()-pos>cpl; ++pos) {
|
|
int pos2=line.lastIndexOf(' ', pos+cpl);
|
|
if (pos2>pos) pos=pos2;
|
|
else pos=line.indexOf(' ', pos+1);
|
|
if (pos<0) break;
|
|
line.remove(pos, 1);
|
|
line.insert(pos, "\n"+ind);
|
|
}
|
|
res+=line;
|
|
}
|
|
return res.join('\n');
|
|
}
|
|
QString format(std::string txt, int indent = 2, int cpl = 60) {
|
|
return format(QString::fromStdString(txt), indent, cpl);
|
|
}
|
|
|
|
QString help(const Script& s) {
|
|
std::ostringstream ss;
|
|
ss<<"Synopsis"<<std::endl
|
|
<<std::endl
|
|
<<" webrunner [<OPTIONS>] [<file1> [<file2> [...]]]"<<std::endl
|
|
<<std::endl
|
|
<<"DESCRIPTION"<<std::endl
|
|
<<std::endl
|
|
<<format(description()).toStdString()<<std::endl
|
|
<<std::endl
|
|
<<format(readme()).toStdString()<<std::endl
|
|
<<std::endl
|
|
<<"SCRIPT SYNTAX"<<std::endl
|
|
<<std::endl
|
|
<<format(s.syntax()).toStdString()<<std::endl
|
|
<<std::endl
|
|
<<"SCRIPT COMMANDS"<<std::endl
|
|
<<std::endl
|
|
<<format(s.commands()).toStdString()<<std::endl;
|
|
return QString::fromStdString(ss.str());
|
|
}
|
|
|
|
int main(int argc, char *argv[]) try {
|
|
bool failed(false);
|
|
QApplication a(argc, argv);
|
|
QWebView p;
|
|
p.setPage(new TestWebPage(&p, true));
|
|
QCommandLineParser parser;
|
|
Script script;
|
|
parser.setApplicationDescription(help(script));
|
|
parser.addHelpOption();
|
|
parser.addOption(QCommandLineOption
|
|
(QStringList()<<"p"<<"path",
|
|
"search <path> within the test scripts", "path"));
|
|
parser.addOption(QCommandLineOption
|
|
(QStringList()<<"x"<<"xml",
|
|
"store XML output in <xmlfile>", "xmlfile"));
|
|
parser.addOption(QCommandLineOption
|
|
(QStringList()<<"r"<<"retries",
|
|
"on error retry up to <maxretries> times",
|
|
"maxretries", "0"));
|
|
parser.addOption(QCommandLineOption
|
|
(QStringList()<<"timeout",
|
|
"set default timeout in seconds",
|
|
"timeout", "40"));
|
|
parser.addOption(QCommandLineOption
|
|
(QStringList()<<"W"<<"width",
|
|
"set screenshot size to <width> pixel", "width", "2048"));
|
|
parser.addOption(QCommandLineOption
|
|
(QStringList()<<"H"<<"height",
|
|
"set screenshot size to <height> pixel", "height", "2048"));
|
|
parser.addOption(QCommandLineOption
|
|
(QStringList()<<"v"<<"version",
|
|
"show version information"));
|
|
parser.addOption(QCommandLineOption
|
|
(QStringList()<<"s"<<"skipped",
|
|
"treat skipped test cases as failure in XML output file"));
|
|
parser.addOption(QCommandLineOption
|
|
(QStringList()<<"t"<<"target-path",
|
|
"set screenshot target path to <path>", "path",
|
|
QDir().absolutePath()+QDir::separator()+"attachments"));
|
|
parser.process(a);
|
|
if (parser.isSet("version")) {
|
|
std::cout<<*argv<<std::endl
|
|
<<" from package "<<package_string()<<std::endl
|
|
<<" by "<<author()<<std::endl
|
|
<<" built on "<<build_date()<<std::endl;
|
|
return 0;
|
|
}
|
|
if (parser.isSet("path")) script.path(parser.value("path"));
|
|
int retries(parser.value("retries").toInt());
|
|
int timeout(parser.value("timeout").toInt());
|
|
int width(parser.value("width").toInt());
|
|
int height(parser.value("height").toInt());
|
|
script.defaultTimeout(parser.value("timeout").toInt());
|
|
QString target(parser.value("target-path"));
|
|
p.resize(width, height);
|
|
std::shared_ptr<xml::Node> testsuites(new xml::Node("testsuites"));
|
|
Q_FOREACH(QString file, parser.positionalArguments()) {
|
|
int expectedtestcases(-1);
|
|
xml::Node testsuite("testsuite");
|
|
testsuite.attr("name") = QFileInfo(file).baseName().toStdString();
|
|
testsuite.attr("timestamp") =
|
|
QDateTime::currentDateTime().toString(Qt::ISODate).toStdString();
|
|
xml::Node testcase("testcase");
|
|
testcase.attr("classname") = "testsuite-preparation";
|
|
try {
|
|
script.reset();
|
|
script.log("=====================");
|
|
script.log("TEST: "+file);
|
|
script.log("---------------------");
|
|
testcase.attr("name") = "open test suite file";
|
|
testsuite<<testcase;
|
|
QFile f(file);
|
|
if (!f.open(QIODevice::ReadOnly))
|
|
throw std::runtime_error("cannot open file "+file.toStdString());
|
|
QString txt(QString::fromUtf8(f.readAll()));
|
|
testcase.attr("name") = "parse test suite file";
|
|
testsuite<<testcase;
|
|
script.parse(txt.split('\n'), file);
|
|
expectedtestcases = script.countSteps()+2;
|
|
if (failed) {
|
|
script.log("FAILED: "+file+" skipped due to previous error");
|
|
testcase.attr("name") = "testsuite";
|
|
xml::Node failure("failure");
|
|
failure.attr("message") = "ignored due to previous failure";
|
|
testsuite<<(testcase<<failure);
|
|
testsuite.attr("failures") =
|
|
mrw::string(expectedtestcases+1-testsuite.children());
|
|
if (parser.isSet("skipped")) {
|
|
testcase.attr("name") = "skipped test case";
|
|
failure.attr("message") = "skipped due to previous failure";
|
|
testcase<<failure;
|
|
for (int i(testsuite.children()); i<expectedtestcases; ++i)
|
|
testsuite<<testcase;
|
|
}
|
|
} else {
|
|
testsuite.attr("failures") = "0";
|
|
(*testsuites)<<testsuite;
|
|
script.run(p.page()->mainFrame(), testsuites,
|
|
target+QDir::separator()+QFileInfo(file).baseName(),
|
|
true, retries);
|
|
script.log("SUCCESS: "+file);
|
|
}
|
|
} catch (std::exception& e) {
|
|
failed = true;
|
|
script.log("FAILED: "+file+" with "+e.what());
|
|
xml::Node failure("failure");
|
|
failure.attr("message") = Script::xmlattr(e.what()).toStdString();
|
|
testsuites->last().last()<<failure;
|
|
if (expectedtestcases==-1)
|
|
testsuites->last().attr("failures") = "1";
|
|
else
|
|
testsuites->last().attr("failures") =
|
|
mrw::string(expectedtestcases+1-testsuites->last().children());
|
|
if (parser.isSet("skipped")) {
|
|
testcase.attr("name") = "skipped test case";
|
|
failure.attr("message") = "skipped due to previous failure";
|
|
testcase<<failure;
|
|
for (int i(testsuites->last().children()); i<expectedtestcases; ++i)
|
|
testsuites->last()<<testcase;
|
|
}
|
|
}
|
|
if (expectedtestcases==-1) {
|
|
testsuites->last().attr("tests") =
|
|
mrw::string(testsuites->last().children());
|
|
} else {
|
|
testsuites->last().attr("tests") = mrw::string(expectedtestcases);
|
|
}
|
|
if (script.cout().size())
|
|
testsuites->last()<<(xml::String("system-out") =
|
|
script.xmlattr(script.cout()).toStdString());
|
|
if (script.cerr().size())
|
|
testsuites->last()<<(xml::String("system-err") =
|
|
script.xmlattr(script.cerr()).toStdString());
|
|
}
|
|
if (parser.isSet("xml")) { // todo: write xml file
|
|
std::ofstream xmlfile(parser.value("xml").toStdString());
|
|
xmlfile<<(*testsuites)<<std::endl;
|
|
}
|
|
return failed ? 1 : 0;
|
|
} catch (std::exception& e) {
|
|
return 1;
|
|
}
|