new commands testsuite and testcase

master
Marc Wäckerlin 9 years ago
parent ee1ebd8ca9
commit 9335d5f6bb
  1. 7
      ChangeLog
  2. 33
      ax_check_qt.m4
  3. 46
      ax_init_standard_project.m4
  4. 96
      bootstrap.sh
  5. 9
      doc/makefile.amto
  6. 151
      src/commands.hxx
  7. 21
      src/testgui.hxx
  8. 71
      src/webrunner.cxx

@ -1,3 +1,10 @@
2015-05-08 15:34 marc
* ChangeLog, ax_init_standard_project.m4, bootstrap.sh,
docker/Dockerfile, src/commands.hxx, src/exceptions.hxx,
src/testgui.hxx: new feature: if tatement for conditions in
functions
2015-05-06 23:09 marc
* ChangeLog, bootstrap.sh, doc/doxyfile.in,

@ -104,12 +104,24 @@ AC_DEFUN([AX_CHECK_QT], [
MOC_FLAGS+=" -DHAVE_$1=1 ${[$1]5_CFLAGS}"
AM_CXXFLAGS+=" ${[$1]5_CFLAGS}"
LIBS+=" ${[$1]5_LIBS}"
modules=${qt_modules//Qt/Qt5}
if test -z "$PKG_REQUIREMENTS"; then
PKG_REQUIREMENTS="${modules// /, }"
else
PKG_REQUIREMENTS="${PKG_REQUIREMENTS}, ${modules// /, }"
fi
if test -n "${qt_modules_optional}"; then
PKG_CHECK_MODULES([$1]5_OPTIONAL, [${qt_modules_optional//Qt/Qt5}], [
AM_CPPFLAGS+=" ${[$1]5_OPTIONAL_CFLAGS}"
MOC_FLAGS+=" ${[$1]5_OPTIONAL_CFLAGS}"
AM_CXXFLAGS+=" ${[$1]5_OPTIONAL_CFLAGS}"
LIBS+=" ${[$1]5_OPTIONAL_LIBS}"
modules=${qt_modules_optional//Qt/Qt5}
if test -z "$PKG_REQUIREMENTS"; then
PKG_REQUIREMENTS="${modules// /, }"
else
PKG_REQUIREMENTS="${PKG_REQUIREMENTS}, ${modules// /, }"
fi
], [
AC_MSG_NOTICE([Not found: ${qt_modules_optional//Qt/Qt5}])
])
@ -122,12 +134,24 @@ AC_DEFUN([AX_CHECK_QT], [
MOC_FLAGS+=" -DHAVE_$1=1 ${$1_CFLAGS}"
AM_CXXFLAGS+=" ${$1_CFLAGS}"
LIBS+=" ${$1_LIBS}"
modules=${qt_modules}
if test -z "$PKG_REQUIREMENTS"; then
PKG_REQUIREMENTS="${modules// /, }"
else
PKG_REQUIREMENTS="${PKG_REQUIREMENTS}, ${modules// /, }"
fi
if test -n "$3"; then
PKG_CHECK_MODULES($1_OPTIONAL, [${qt_modules_optional}], [
AM_CPPFLAGS+=" ${$1_OPTIONAL_CFLAGS}"
MOC_FLAGS+=" ${$1_OPTIONAL_CFLAGS}"
AM_CXXFLAGS+=" ${$1_OPTIONAL_CFLAGS}"
LIBS+=" ${$1_OPTIONAL_LIBS}"
modules=${qt_modules_optional}
if test -z "$PKG_REQUIREMENTS"; then
PKG_REQUIREMENTS="${modules// /, }"
else
PKG_REQUIREMENTS="${PKG_REQUIREMENTS}, ${modules// /, }"
fi
], [
AC_MSG_NOTICE([Not found: ${qt_modules_optional}])
])
@ -139,6 +163,7 @@ AC_DEFUN([AX_CHECK_QT], [
AC_SUBST(AM_CPPFLAGS)
AC_SUBST(MOC_FLAGS)
AC_SUBST(AM_CXXFLAGS)
AC_SUBST(PKG_REQUIREMENTS)
AX_ADDITIONAL_QT_RULES_HACK='
ui_%.hxx: %.ui
$(UIC) -o [$][@] $<
@ -191,3 +216,11 @@ AC_DEFUN([AX_REQUIRE_QT], [
AC_MSG_ERROR([Required Qt modules not found: $2])
fi
])
# Omit Qt Keywords
# AX_QT_NO_KEYWORDS
AC_DEFUN([AX_QT_NO_KEYWORDS], [
AM_CPPFLAGS+=" -DQT_NO_KEYWORDS"
])

@ -5,7 +5,7 @@
# define least version number from subversion's revision number:
# it is taken modulo 256 due to a bug on Apple's MacOSX
m4_define(x_version, m4_esyscmd_s(
m4_define(x_least, m4_esyscmd_s(
SVN_REVISION="ERROR-UNDEFINED-REVISION-to-be-built-in-subdirectory-of-svn-checkout"
for path in . .. ../..; do
if svn info $path 2>&1 > /dev/null; then
@ -15,7 +15,26 @@ m4_define(x_version, m4_esyscmd_s(
fi
done
# Mac does not support LEAST > 255
echo $ECHO_N x_major.$((x_minor+$SVN_REVISION/256)).$(($SVN_REVISION%256))
echo $ECHO_N $(($SVN_REVISION%256))
))
# define version number from subversion's revision number:
# it is taken modulo 256 due to a bug on Apple's MacOSX
# add to x_minor if revision number is > 256
m4_define(x_minor_fixed, m4_esyscmd_s(
SVN_REVISION="ERROR-UNDEFINED-REVISION-to-be-built-in-subdirectory-of-svn-checkout"
for path in . .. ../..; do
if svn info $path 2>&1 > /dev/null; then
SVN_REVISION=$(LANG= svn info $path | sed -n 's/Last Changed Rev: //p')
(cd $path && svn2cl)
break;
fi
done
# Mac does not support LEAST > 255
echo $ECHO_N $((x_minor+$SVN_REVISION/256))
))
# setup version number
m4_define(x_version, m4_esyscmd_s(
echo $ECHO_N x_major.x_minor_fixed.x_least
))
## bugreport mail address is taken from <user@host> in first line of AUTHORS
@ -81,7 +100,7 @@ AC_DEFUN([AX_SUBST], [
# m4_define(x_major, MAJOR_NUMBER) # project's major version
# m4_define(x_minor, MINOR_NUMBER) # project's minor version
# m4_include(ax_init_standard_project.m4)
# AC_INIT(x_package_name, x_major.x_minor.x_least, x_bugreport, x_package_name)
# AC_INIT(x_package_name, x_version, x_bugreport, x_package_name)
# AM_INIT_AUTOMAKE([1.9 tar-pax])
# AX_INIT_STANDARD_PROJECT
#
@ -105,7 +124,7 @@ AC_DEFUN([AX_INIT_STANDARD_PROJECT], [
AX_SUBST(DISTRO)
BUILD_NUMBER=${BUILD_NUMBER:-1}
AX_SUBST(BUILD_NUMBER)
BUILD_DATE=$(date +"%Y-%m-%d %H:%M %Z")
BUILD_DATE=$(LANG= date +"%a, %d %B %Y %H:%M:%S %z")
AX_SUBST(BUILD_DATE)
if test -f "${PROJECT_NAME}-logo.png"; then
PROJECT_LOGO="${PROJECT_NAME}-logo.png"
@ -225,7 +244,7 @@ AC_DEFUN([AX_USE_LIBTOOL], [
# libtool versioning
LIB_MAJOR=m4_eval(x_major+x_minor)
LIB_MINOR=x_least
LIB_LEAST=x_minor
LIB_LEAST=x_minor_fixed
LIB_VERSION="${LIB_MAJOR}:${LIB_MINOR}:${LIB_LEAST}"
AM_LDFLAGS="-version-info ${LIB_VERSION}"
AC_SUBST(AM_LDFLAGS)
@ -380,6 +399,11 @@ AC_DEFUN([AX_PKG_REQUIRE], [
AM_CPPFLAGS+=" ${$1_CFLAGS}"
AM_CXXFLAGS+=" ${$1_CFLAGS}"
LIBS+=" ${$1_LIBS}"
if test -z "$PKG_REQUIREMENTS"; then
PKG_REQUIREMENTS="$2"
else
PKG_REQUIREMENTS="${PKG_REQUIREMENTS}, $2"
fi
], [
AC_MSG_WARN([Recommended package $2 for feature $1 not installed])
if test -n "$4"; then
@ -411,6 +435,11 @@ AC_DEFUN([AX_PKG_REQUIRE], [
AM_CPPFLAGS+=" ${$1_CFLAGS}"
AM_CXXFLAGS+=" ${$1_CFLAGS}"
LIBS+=" ${$1_LIBS}"
if test -z "$PKG_REQUIREMENTS"; then
PKG_REQUIREMENTS="$pkg"
else
PKG_REQUIREMENTS="${PKG_REQUIREMENTS}, $pkg"
fi
], [
AC_MSG_WARN([Recommended package $pkg for feature $1 not installed])
])
@ -419,6 +448,7 @@ AC_DEFUN([AX_PKG_REQUIRE], [
])
AC_SUBST(AM_CPPFLAGS)
AC_SUBST(AM_CXXFLAGS)
AC_SUBST(PKG_REQUIREMENTS)
if test -n "$3"; then
old_CPPFLAGS=${CPPFLAGS}
CPPFLAGS=" ${$1_CFLAGS} ${CPPFLAGS}"
@ -445,6 +475,11 @@ AC_DEFUN([AX_PKG_CHECK], [
AM_CPPFLAGS+=" ${$1_CFLAGS}"
AM_CXXFLAGS+=" ${$1_CFLAGS}"
LIBS+=" ${$1_LIBS}"
if test -z "$PKG_REQUIREMENTS"; then
PKG_REQUIREMENTS="m4_default([$2], [$1])"
else
PKG_REQUIREMENTS="${PKG_REQUIREMENTS}, m4_default([$2], [$1])"
fi
], [
HAVE_$1=0
])
@ -452,4 +487,5 @@ AC_DEFUN([AX_PKG_CHECK], [
AC_SUBST(HAVE_$1)
AC_SUBST(AM_CPPFLAGS)
AC_SUBST(AM_CXXFLAGS)
AC_SUBST(PKG_REQUIREMENTS)
])

@ -22,28 +22,31 @@ configure=0
build=0
overwrite=0
rebuild=0
rebuildfiles=()
while test $# -gt 0; do
case "$1" in
(--configure|-c) configure=1;;
(--build|-b) configure=1; build=1;;
(--overwrite|-o) overwrite=1;;
(--rebuild|-r) rebuild=1;;
(--rebuild-file|-f) shift; rebuildfiles+=("$1");;
(--version|-v)
echo "$Id$";
exit;;
(--help|-h) less <<EOF
SYNOPSIS
${MY_NAME} [--help|-h] [--configure|-c]
${MY_NAME} [--help|-h] [OPTIOS]
OPTIONS
--configure, -c call ./configure after initialization
--build, -c build, call ./configure && make after initialization
--overwrite, -o overwrite all basic files (bootstrap.sh, m4-macros)
--rebuild -r force rebuild of generated files, even if modified
--help, -h show this help
--version, -v show version and date of this file
--configure, -c call ./configure after initialization
--build, -c build, also call ./configure && make
--overwrite, -o overwrite all basic files (bootstrap.sh, m4-macros)
--rebuild, -r force rebuild of generated files, even if modified
--rebuild-file, -f <file> rebild specific file (can be added multiple times)
--help, -h show this help
--version, -v show version and date of this file
DESCRIPTION
@ -122,9 +125,15 @@ GENERATED FILES
REBUILDING FILES
To rebuild all these files, just run "${MY_NAME} -o". You can also
remove and rebuild a single file by removing it from subversion,
just call "svn rm <file>" and "${MY_NAME}" to rebuild file "<file>".
To rebuild all these files, just run "${MY_NAME} -r".
To copy only the files provided by this package, that means those
files you must never change, that means to update the build system
to the latest release, run "${MY_NAME} -o"
You can also rebuild a list of singleany list of specific file files
by adding option "${MY_NAME} -f <file>" to rebuild file
"<file>". You can add option "-f" more than once.
FILES TO EDIT
@ -216,7 +225,7 @@ done
HEADER='## @id '"\$Id\$"'
#
# This file has been added by '${MY_NAME}' on '$(date +"%Y-%m-%d %H:%M %Z")'
# This file has been added by '${MY_NAME}' on '$(LANG= date +"%a, %d %B %Y %H:%M:%S %z")'
# Feel free to change it or even remove and rebuild it, up to your needs
#
## 1 2 3 4 5 6 7 8
@ -224,6 +233,10 @@ HEADER='## @id '"\$Id\$"'
'
notice() {
echo -e "\e[1;33m$*\e[0m"
}
run() {
check=1
while test $# -gt 0; do
@ -256,6 +269,12 @@ testtag() {
egrep -q '^ *'"$1" configure.ac
}
contains() {
local e
for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done
return 1
}
checkdir() {
if ! test -d "$1"; then # create path
run mkdir -p "$1"
@ -285,8 +304,10 @@ to() {
esac
shift;
done
if checkfile "$1" && test $rebuild -eq 0; then # file already exists
return
if checkfile "$1" && test $rebuild -eq 0 \
&& ! contains "$1" "${rebuildfiles[@]}"; then
# file already exists and must not be rebuilt
return 1
fi
checkdir "$(dirname ${1})"
echo -en "\e[1m-> generating:\e[0m $1 ..."
@ -307,10 +328,13 @@ to() {
run svn add "$1"
run svn propset svn:keywords "Id" "$1"
fi
return 0
}
copy() {
if checkfile "$1" && test $overwrite -eq 0; then
if checkfile "$1" && test $overwrite -eq 0 \
&& ! contains "$1" "${rebuildfiles[@]}"; then
# file already exists and must not be rebuilt
return
fi
run cp "${0%/*}/$1" "$1"
@ -347,14 +371,18 @@ copy ${MY_NAME}
copy ax_init_standard_project.m4
copy ax_cxx_compile_stdcxx_11.m4
copy ax_check_qt.m4
copy AUTHORS
if ! test -f configure.ac; then
to configure.ac <<EOF
AUTHOR=$(gpg -K | sed -n 's,uid *,,p' | sort | head -1)
if test -z "${AUTHOR}"; then
AUTHOR="FIRSTNAME LASTNAME (URL) <EMAIL>"
fi
echo "$AUTHOR" | to AUTHORS && notice "please edit AUTHORS"
to configure.ac <<EOF && \
( notice "please edit configure.ac, then rerun $0"; exit 0 )
${HEADER}m4_define(x_package_name, ${DEFAULT_PROJECT_NAME}) # project's name
m4_define(x_major, 0) # project's major version
m4_define(x_minor, 0) # project's minor version
m4_include(ax_init_standard_project.m4)
AC_INIT(x_package_name, x_major.x_minor.x_least, x_bugreport, x_package_name)
AC_INIT(x_package_name, x_version, x_package_name)
AM_INIT_AUTOMAKE([1.9 tar-pax])
AX_INIT_STANDARD_PROJECT
@ -370,14 +398,11 @@ AX_INIT_STANDARD_PROJECT
# qt features, uncomment, what you need:
#AX_CHECK_QT([QT], [QtCore QtGui QtNetwork], [QtWidgets])
#AM_CPPFLAGS="${AM_CPPFLAGS} -DQT_NO_KEYWORDS"
#AX_QT_NO_KEYWORDS
# create output
AC_OUTPUT
EOF
echo "please edit configure.ac, then rerun $0"
exit 0
fi
PACKAGE_NAME=$(sed -n 's/.*m4_define *( *x_package_name *, *\([^ ]*\) *).*/\1/p' configure.ac)
echo "${HEADER}MAINTAINERCLEANFILES = makefile.in" | to --condition AX_USE_CXX src/makefile.am
to --condition AX_USE_CXX src/version.hxx <<EOF
@ -473,10 +498,14 @@ LDADD = -l${PACKAGE_NAME}
MAINTAINERCLEANFILES = makefile.in
EOF
if testtag AX_USE_DOXYGEN && ! test -f doc/doxyfile.in; then
if testtag AX_USE_DOXYGEN && \
( ! checkfile doc/doxyfile.in || \
contains doc/doxyfile.in "${rebuildfiles[@]}" ); then
run doxygen -g doc/doxyfile.in
run svn add doc/doxyfile.in
run svn propset svn:keywords "Id" doc/doxyfile.in
if test $exists -eq 0; then
run svn add doc/doxyfile.in
run svn propset svn:keywords "Id" doc/doxyfile.in
fi
doxyreplace PROJECT_NAME "@PACKAGE_NAME@"
doxyreplace PROJECT_NUMBER "@PACKAGE_VERSION@"
doxyreplace PROJECT_BRIEF "@DESCRIPTION@"
@ -595,13 +624,15 @@ ${HEADER}%:
EOF
echo 7 | to debian/compat
fi
if ! test -f makefile.am; then
SUBDIRS=""
for d in src test scripts doc examples; do
test -d $d && SUBDIRS="${SUBDIRS} $d"
done
echo "${HEADER}SUBDIRS =${SUBDIRS}\n\nMAINTAINERCLEANFILES = makefile.in" | to makefile.am
fi
SUBDIRS=""
for d in src test scripts doc examples; do
test -d $d && SUBDIRS="${SUBDIRS} $d"
done
to makefile.am<<EOF
${HEADER}SUBDIRS =${SUBDIRS}
MAINTAINERCLEANFILES = makefile.in
EOF
to --condition AX_USE_LIBTOOL src/${PACKAGE_NAME}.pc.in <<EOF
${HEADER}prefix=@prefix@
exec_prefix=@exec_prefix@
@ -613,6 +644,7 @@ Description: @DESCRIPTION@
Version: @VERSION@
Libs: -L\${libdir} -l@PACKAGE_NAME@ @LDFLAGS@
Cflags: -I\${includedir} @CPPFLAGS@
Requires: @PKG_REQUIREMENTS@
EOF
#### Cleanup If Makefile Exists ####

@ -0,0 +1,9 @@
## @id $Id$
#
# This file has been added by bootstrap.sh on Sat, 09 May 2015 14:15:27 +0200
# Feel free to change it or even remove and rebuild it, up to your needs
#
## 1 2 3 4 5 6 7 8
## 45678901234567890123456789012345678901234567890123456789012345678901234567890
MAINTAINERCLEANFILES = makefile.in

@ -462,35 +462,45 @@ class Script: public QObject {
linenr+=oldsize-in.size())
_script.push_back(parse(in, linenr));
}
void run(QWebFrame* frame, QString td = QString(), bool screenshots = true,
int maxretries = 0) {
assert(_internalTestsuiteNode);
run(frame, *_internalTestsuiteNode, td,
screenshots, maxretries); //( @todo extract from parent
QStringList print() {
QStringList result;
for (auto cmd(_script.begin()); cmd!=_script.end(); ++cmd) {
result += (*cmd)->command();
}
return result;
}
void run(QWebFrame* frame) {
run(frame, _testsuites, targetdir(), _screenshots, _maxretries);
}
void run(QWebFrame* frame, xml::Node& testsuiteNode,
void run(QWebFrame* frame, std::shared_ptr<xml::Node> testsuites,
QString td = QString(), bool screenshots = true,
int maxretries = 0) {
_internalTestsuiteNode = &testsuiteNode;
assert(_internalTestsuiteNode);
_testsuites = testsuites;
_timeout = 20; // defaults to 20s
_ignoreSignalsUntil.clear();
addSignals(frame);
_screenshots = screenshots;
_maxretries = maxretries;
_timer.setSingleShot(true);
targetdir(!td.isEmpty()
? td
: _testsuites->children()
? xmlstr(_testsuites->last().attr("name"))
: "attachments");
if (!_testsuites->children()) {
xml::Node testsuite("testsuite");
testsuite.attr("name") = "Unnamed Test Suite";
(*_testsuites)<<testsuite;
}
int retries(0), back(0);
for (auto cmd(_script.begin()); cmd!=_script.end(); ++cmd) {
xml::Node testcase("testcase");
try {
testcase.attr("classname") =
testsuiteNode.attr("name");
//xmlattr((*cmd)->command(), true).toStdString();
testcase.attr("classname") = xmlattr(_testclass, true).toStdString();
testcase.attr("name") =
xmlattr((*cmd)->tag(), true).toStdString();
xmlattr((*cmd)->command(), true).toStdString();
if (!_ignores.size() || (*cmd)->tag()=="label") { // not ignored
_timer.start(_timeout*1000);
testsuite(xmlstr(testsuiteNode.attr("name")));
targetdir(!td.isEmpty() ? td : xmlstr(testsuiteNode.attr("name")));
try {
if (!(*cmd)->execute(this, frame)) {
_timer.stop();
@ -501,7 +511,7 @@ class Script: public QObject {
xmlattr(_cerr).toStdString());
_cout.clear();
_cerr.clear();
testsuiteNode<<testcase;
_testsuites->last()<<testcase;
break; // test is successfully finished
}
} catch (PossibleRetryLoad& e) {
@ -511,7 +521,7 @@ class Script: public QObject {
try { // take a screenshot on error
QString filename(Screenshot::screenshot
((*cmd)->line(), targetdir(),
QFileInfo(testsuite()).baseName(),
_testclass,
QString("retry-%1")
.arg((ulong)retries, 2, 10,
QLatin1Char('0')),
@ -563,7 +573,7 @@ class Script: public QObject {
xmlattr(_cerr).toStdString());
_cout.clear();
_cerr.clear();
testsuiteNode<<testcase;
_testsuites->last()<<testcase;
}
} catch (Exception& e) {
_timer.stop();
@ -574,7 +584,7 @@ class Script: public QObject {
xmlattr(_cerr).toStdString());
_cout.clear();
_cerr.clear();
testsuiteNode<<testcase;
_testsuites->last()<<testcase;
removeSignals(frame);
e.line((*cmd)->line());
if (screenshots)
@ -582,22 +592,20 @@ class Script: public QObject {
{
QString filename(Screenshot::sourceHtml
((*cmd)->line(), targetdir(),
QFileInfo(testsuite()).baseName(),
_testclass,
"error", frame));
plainlog("[[ATTACHMENT|"+filename+"]]");
} {
QString filename(Screenshot::screenshot
((*cmd)->line(), targetdir(),
QFileInfo(testsuite()).baseName(),
_testclass,
"error", frame));
plainlog("[[ATTACHMENT|"+filename+"]]");
}
} catch (... ) {} // ignore exception in screenshot
_internalTestsuiteNode = 0;
throw;
}
}
_internalTestsuiteNode = 0;
removeSignals(frame);
if (!_signals.empty()) throw UnhandledSignals(_signals);
}
@ -613,18 +621,26 @@ class Script: public QObject {
bool screenshots() {
return _screenshots;
}
void testsuite(QString name) {
_testsuite = name;
}
QString testsuite() {
return _testsuite;
}
void targetdir(QString name) {
_targetdir = name;
}
QString targetdir() {
return _targetdir;
}
void testclass(QString tc) {
_testclass = tc;
}
QString testclass() {
return _testclass;
}
void testsuite(QString name) {
xml::Node testsuite("testsuite");
testsuite.attr("name") = xmlattr(name, true).toStdString();
testsuite.attr("timestamp") =
QDateTime::currentDateTime().toString(Qt::ISODate).toStdString();
_testsuites->last().attr("failures") = "0";
(*_testsuites)<<testsuite;
}
Signal getSignal() {
while (!_signals.size()) QCoreApplication::processEvents();
Signal res(_signals.front());
@ -655,10 +671,11 @@ class Script: public QObject {
_rvariables = o._rvariables;
_timeout = o._timeout;
_clicktype = o._clicktype;
_testsuite = o._testsuite;
_internalTestsuiteNode = o._internalTestsuiteNode;
assert(_internalTestsuiteNode);
_testsuites = o._testsuites;
_testclass = o._testclass;
_targetdir = o._targetdir;
_maxretries = o._maxretries;
_screenshots = o._screenshots;
_cout.clear();
_cerr.clear();
_ignoreSignalsUntil.clear();
@ -835,15 +852,16 @@ class Script: public QObject {
QString _cout;
QString _cerr;
bool _screenshots;
int _maxretries;
QString _ignoreSignalsUntil;
QMap<QString, QString> _variables; ///< variable mapping
QMap<LenString, LenString> _rvariables; ///< reverse variable mapping
QMap<QString, std::shared_ptr<Function> > _functions;
int _timeout;
ClickType _clicktype;
QString _testsuite;
QString _targetdir;
xml::Node* _internalTestsuiteNode; ///< only valid within run
std::shared_ptr<xml::Node> _testsuites; ///< only valid within run
QString _testclass;
};
class Do: public Command {
@ -1892,7 +1910,8 @@ class If: public Command {
"Match allows a regular expression.";
}
QString command() const {
return tag();
return tag()+" "+_variable+" "+QString(_cmp)+" "+_value
+(_script.get()?"\n"+_script->print().join("\n "):"");
}
std::shared_ptr<Command> parse(Script*, QString args,
QStringList& in, int) {
@ -1934,6 +1953,64 @@ class If: public Command {
std::shared_ptr<Script> _script;
};
class TestSuite: public Command {
public:
QString tag() const {
return "testsuite";
}
QString description() const {
return
tag()+" <name>"
"\n\n"
"Start a testsuite and give it a name.";
}
QString command() const {
return tag()+" "+_name;
}
std::shared_ptr<Command> parse(Script*, QString args,
QStringList&, int) {
std::shared_ptr<TestSuite> cmd(new TestSuite());
cmd->_name = args;
return cmd;
}
bool execute(Script* script, QWebFrame* frame) {
Logger log(this, script);
script->testsuite(script->replacevars(_name));
return true;
}
private:
QString _name;
};
class TestCase: public Command {
public:
QString tag() const {
return "testcase";
}
QString description() const {
return
tag()+" <name>"
"\n\n"
"Start a testcase and give it a name.";
}
QString command() const {
return tag()+" "+_name;
}
std::shared_ptr<Command> parse(Script*, QString args,
QStringList&, int) {
std::shared_ptr<TestCase> cmd(new TestCase());
cmd->_name = args;
return cmd;
}
bool execute(Script* script, QWebFrame* frame) {
Logger log(this, script);
script->testclass(script->replacevars(_name));
return true;
}
private:
QString _name;
};
/* Template:
class : public Command {
public:
@ -1965,7 +2042,7 @@ inline bool Screenshot::execute(Script* script, QWebFrame* frame) {
if (!script->screenshots()) return true;
Logger log(this, script);
QString filename(screenshot(line(), script->targetdir(),
QFileInfo(script->testsuite()).baseName(),
script->testclass(),
script->replacevars(_filename), frame));
log["[[ATTACHMENT|"+filename+"]]"];
return true;
@ -2004,7 +2081,7 @@ inline void Command::subScript(std::shared_ptr<Script> script,
connect(script.get(), SIGNAL(logging(QString)),
parent, SLOT(log(QString)));
parent->removeSignals(frame);
script->run(frame, parent->targetdir());
script->run(frame);
parent->addSignals(frame);
disconnect(script.get(), SIGNAL(logging(QString)),
parent, SLOT(log(QString)));
@ -2042,6 +2119,8 @@ inline void Script::initPrototypes() {
add(new Function);
add(new Call);
add(new If);
add(new TestSuite);
add(new TestCase);
}
#endif

@ -117,23 +117,17 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
try {
Script script;
connect(&script, SIGNAL(logging(QString)), SLOT(logging(QString)));
xml::Node testsuite("testsuite");
std::shared_ptr<xml::Node> testsuites(new xml::Node("testsuite"));
if (_setupscriptactive->isEnabled()
&& _setupscriptactive->isChecked()) {
script.parse(_setupscript->toPlainText().split('\n'));
testsuite.attr("name") = "setup-script";
testsuite.attr("timestamp") =
QDateTime::currentDateTime().toString(Qt::ISODate).toStdString();
script.run(_web->page()->mainFrame(), testsuite, QString(), false);
script.run(_web->page()->mainFrame(), testsuites, QString(), false);
script.reset();
}
QString text(_testscript->textCursor().selection().toPlainText());
if (text.isEmpty()) text = _testscript->toPlainText();
script.parse(text.split('\n'));
testsuite.attr("name") = "setup-script";
testsuite.attr("timestamp") =
QDateTime::currentDateTime().toString(Qt::ISODate).toStdString();
script.run(_web->page()->mainFrame(), testsuite, QString(), false);
script.run(_web->page()->mainFrame(), testsuites, QString(), false);
} catch (std::exception &x) {
QMessageBox::critical(this, tr("Script Failed"),
tr("Script failed with message:\n%1")
@ -222,17 +216,14 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
_setupscriptactive->setEnabled(false);
try {
_setupscriptstatus->setText(trUtf8("?"));
xml::Node testsuite("testsuite");
testsuite.attr("name") = "setup-script";
testsuite.attr("timestamp") =
QDateTime::currentDateTime().toString(Qt::ISODate).toStdString();
std::shared_ptr<xml::Node> testsuites(new xml::Node("testsuite"));
Script script;
TestWebPage page(0, true);
script.parse(_setupscript->toPlainText().split('\n'));
script.run(page.mainFrame(), testsuite, QString(), false);
script.run(page.mainFrame(), testsuites, QString(), false);
_setupScript.cleanup();
_setupScript.parse(_setupscript->toPlainText().split('\n'));
_setupScript.run(page.mainFrame(), testsuite, QString(), false);;
_setupScript.run(page.mainFrame(), testsuites, QString(), false);;
_setupscriptstatus->setText(trUtf8(""));
_setupscriptactive->setEnabled(true);
} catch (std::exception &x) {

@ -16,6 +16,42 @@
#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 = 60) {
QStringList res;
QStringList lines(txt.split('\n'));
@ -102,7 +138,7 @@ int main(int argc, char *argv[]) try {
int height(parser.value("height").toInt());
QString target(parser.value("target-path"));
p.resize(width, height);
xml::Node testsuites("testsuites");
std::shared_ptr<xml::Node> testsuites(new xml::Node("testsuites"));
Q_FOREACH(QString file, parser.positionalArguments()) {
int expectedtestcases(-1);
xml::Node testsuite("testsuite");
@ -142,47 +178,48 @@ int main(int argc, char *argv[]) try {
testsuite<<testcase;
}
} else {
script.run(p.page()->mainFrame(), testsuite,
testsuite.attr("failures") = "0";
(*testsuites)<<testsuite;
script.run(p.page()->mainFrame(), testsuites,
target+QDir::separator()+QFileInfo(file).baseName(),
true, retries);
testsuite.attr("failures") = "0";
script.log("SUCCESS: "+file);
}
} catch (std::exception& e) {
script.log("FAILED: "+file+" with "+e.what());
xml::Node failure("failure");
failure.attr("message") = Script::xmlattr(e.what()).toStdString();
testsuite[testsuite.children()-1]<<failure;
testsuites->last().last()<<failure;
if (expectedtestcases==-1)
testsuite.attr("failures") = "1";
testsuites->last().attr("failures") = "1";
else
testsuite.attr("failures") =
mrw::string(expectedtestcases+1-testsuite.children());
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(testsuite.children()); i<expectedtestcases; ++i)
testsuite<<testcase;
for (int i(testsuites->last().children()); i<expectedtestcases; ++i)
testsuites->last()<<testcase;
failed = true;
}
}
if (expectedtestcases==-1) {
testsuite.attr("tests") = mrw::string(testsuite.children());
testsuites->last().attr("tests") =
mrw::string(testsuites->last().children());
} else {
testsuite.attr("tests") = mrw::string(expectedtestcases);
testsuites->last().attr("tests") = mrw::string(expectedtestcases);
}
if (script.cout().size())
testsuite<<(xml::String("system-out") =
script.xmlattr(script.cout()).toStdString());
testsuites->last()<<(xml::String("system-out") =
script.xmlattr(script.cout()).toStdString());
if (script.cerr().size())
testsuite<<(xml::String("system-err") =
script.xmlattr(script.cerr()).toStdString());
testsuites<<testsuite;
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;
xmlfile<<(*testsuites)<<std::endl;
}
return failed ? 1 : 0;
} catch (std::exception& e) {

Loading…
Cancel
Save