From 78e429a5e2f29bb7db07ebdf90bd3410e83f2237 Mon Sep 17 00:00:00 2001
From: Konrad Rosenbaum
Date: Thu, 19 Jun 2014 21:07:39 +0200
Subject: [PATCH] finish grouping classes, basic assertions,
source docu for runner lib, start of user docu for runner lib
---
DoxyCommon | 6 +-
configure | 1 +
doc/index.html | 3 +-
doc/runner-cpp.html | 53 ++++-
examples/qt-simple/qt-simple.pro | 31 ++-
examples/qt-simple/simplefunc.cpp | 82 ++++++++
examples/qt-simple/simplemeta.cpp | 28 +++-
examples/qt-simple/simplemeta.h | 32 +++-
runnerlib/c/README | 22 --
runnerlib/cpp/README | 22 --
runnerlib/qt5/Doxyfile | 13 ++
runnerlib/qt5/include/skid.h | 11 +
runnerlib/qt5/include/skid_assert.h | 369 +++++++++++++++++++++++++++++++---
runnerlib/qt5/include/skid_group.h | 247 ++++++++++++++++++-----
runnerlib/qt5/include/skid_pointer.h | 120 ++++++++++-
runnerlib/qt5/runnerqt5.pro | 3 +-
runnerlib/qt5/src/rqtmain.cpp | 86 +++++++--
runnerlib/qt5/src/rqtmain.h | 91 +++++++--
runnerlib/qt5/src/skid_assert.cpp | 52 +++++
runnerlib/qt5/src/skid_group.cpp | 271 ++++++++++++++++++++------
runnerlib/qt5/src/skid_group_p.cpp | 58 +++++-
runnerlib/qt5/src/skid_group_p.h | 126 ++++++++++--
runnerlib/qt5/src/skid_pointer.cpp | 10 +-
23 files changed, 1454 insertions(+), 283 deletions(-)
create mode 100644 examples/qt-simple/simplefunc.cpp
delete mode 100644 runnerlib/c/README
delete mode 100644 runnerlib/cpp/README
create mode 100644 runnerlib/qt5/src/skid_assert.cpp
diff --git a/DoxyCommon b/DoxyCommon
index 32c7577..6c28ea6 100644
--- a/DoxyCommon
+++ b/DoxyCommon
@@ -441,7 +441,7 @@ EXTRACT_LOCAL_METHODS = NO
# are hidden.
# The default value is: NO.
-EXTRACT_ANON_NSPACES = NO
+EXTRACT_ANON_NSPACES = YES
# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
# undocumented members inside documented classes or files. If set to NO these
@@ -546,7 +546,7 @@ SORT_BRIEF_DOCS = NO
# detailed member documentation.
# The default value is: NO.
-SORT_MEMBERS_CTORS_1ST = NO
+SORT_MEMBERS_CTORS_1ST = YES
# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
# of group names into alphabetical order. If set to NO the group names will
@@ -715,7 +715,7 @@ WARN_IF_DOC_ERROR = YES
# documentation, but not about the absence of documentation.
# The default value is: NO.
-WARN_NO_PARAMDOC = NO
+WARN_NO_PARAMDOC = YES
# The WARN_FORMAT tag determines the format of the warning messages that doxygen
# can produce. The string should contain the $file, $line, and $text tags, which
diff --git a/configure b/configure
index 33a5bdb..eea6842 100755
--- a/configure
+++ b/configure
@@ -155,6 +155,7 @@ rm -f $PRF
echo SKID_LIBDIR = $PREFIX/lib >>$PRF
echo SKID_INCDIR = $PREFIX/include/skid >>$PRF
echo 'CONFIG += c++11 exceptions' >>$PRF
+echo 'QT -= gui' >>$PRF
echo 'INCLUDEPATH += $$SKID_INCDIR' >>$PRF
echo 'LIBS += -L$$SKID_LIBDIR -lrunnerqt5' >>$PRF
diff --git a/doc/index.html b/doc/index.html
index acfc8ae..a7cf76a 100644
--- a/doc/index.html
+++ b/doc/index.html
@@ -24,8 +24,7 @@ development.
SKID Internals:
diff --git a/doc/runner-cpp.html b/doc/runner-cpp.html
index 862a548..c916c32 100644
--- a/doc/runner-cpp.html
+++ b/doc/runner-cpp.html
@@ -1,9 +1,14 @@
-SKID - Qt and C++ based Runner Library
+SKID - Qt based Runner Library
+
-
Qt and C++ based Runner Library
+
Qt based Runner Library
-
Quick Start
+
Qt based SKID test projects are simple applications, which are build on top of the SKID runner library. You can find several examples of tests in the examples directory.
+
+
This tutorial will use the qt-simple example as a template.
+
+
Project File
If you are using the Qt5 based version in conjunction with QMake, you can use the following template for your project file:
@@ -12,7 +17,17 @@
TEMPATE = app
TARGET = mytest
-#it is usually designed to be non-graphical
+#activate SKID:
+CONFIG += skid
+
+#our actual sources:
+SOURCES += mytest.cpp
+
+
+
Normally SKID should be installed so that your local Qt installation can make use of it, otherwise the line CONFIG+=skid can be replaced by the following:
+
+
+#it is designed to be non-graphical
QT -= gui
#we will make use of modern features
@@ -21,16 +36,36 @@ CONFIG += c++11
CONFIG += link_prl
#where to find the library (replace this with the real path):
-TESTLIBDIR = path/to/runnerlib/qt5
+TESTLIBDIR = path/to/skid
#where to find runner headers and library:
-INCLUDEPATH += $$TESTLIBDIR/include
-LIBS += -lrunnerqt5 -L$$TESTLIBDIR
+INCLUDEPATH += $$TESTLIBDIR/include/skid
+LIBS += -L$$TESTLIBDIR/lib -lrunnerqt5
+
-#our actual sources:
-SOURCES +=
+
To get a good start, copy the qt-simple example to a new directory and clean up its project file (instructions are inside) to match your own setup.
+
+
Main Function
+
+
Other than for normal applications you do not write your own main function or Application object. SKID does this for you already, you simply include it:
+
+
+#include <skid.h>
+SKID_MAIN
+
Simple Tests
+
+
TODO: explain qt-simple - make steps from functions, static methods, lamdas
+
+
Dynamic Tests
+
+
TODO: (apart from better headline) - make steps from method pointers, using bind to add parameters, QMetaObject. testCaseFromClass, ...
+
+
+
Distributed Tests
+
+
TODO: explain qt-distrib
diff --git a/examples/qt-simple/qt-simple.pro b/examples/qt-simple/qt-simple.pro
index 81f3c80..a7d0d5f 100644
--- a/examples/qt-simple/qt-simple.pro
+++ b/examples/qt-simple/qt-simple.pro
@@ -1,19 +1,34 @@
#project file for simple local test example
+#tests are applications
TEMPLATE = app
TARGET = qt-simple
-QT -= gui
-include(../../common.pri)
-DESTDIR = $$BINDIR
-
-#where to find runner headers and library:
-INCLUDEPATH += $$PWD/../../runnerlib/qt5/include
-LIBS += -L$$LIBDIR -lrunnerqt5
+#if you want to follow the tutorial:
+#uncomment the following line and delete the lines after ##... at the end
+# CONFIG += skid
#our actual sources:
SOURCES += simplemain.cpp \
- simplemeta.cpp
+ simplemeta.cpp \
+ simplefunc.cpp
HEADERS += \
simplemeta.h
+
+
+#####################
+# this is needed for the internal build process,
+# delete it if you want to follow the tutorial!
+
+#no GUI
+QT -= gui
+
+#where to find runner headers and library:
+LIBS += -L$$LIBDIR -lrunnerqt5
+INCLUDEPATH += $$PWD/../../runnerlib/qt5/include
+
+#basics and target dir
+include(../../common.pri)
+DESTDIR = $$BINDIR
+
diff --git a/examples/qt-simple/simplefunc.cpp b/examples/qt-simple/simplefunc.cpp
new file mode 100644
index 0000000..d2e69ea
--- /dev/null
+++ b/examples/qt-simple/simplefunc.cpp
@@ -0,0 +1,82 @@
+//
+// example of a simple test sequence
+//
+//
+// Author: Konrad Rosenbaum , (C) 2014
+//
+// Copyright: See README/COPYING files that come with this distribution
+//
+//
+
+#include
+#include
+
+
+static void myTestFunction1()
+{
+ qDebug()<<"first test function";
+}
+
+static void myTestFunction2()
+{
+ qDebug()<<"second test function";
+}
+
+static void myTestFunction3(QString arg)
+{
+ qDebug()<<"test function with argument"<(arg,"fortytwo");
+}
+
+class MyTestClass
+{
+ QString mname;
+public:
+ MyTestClass(QString name):mname(name){}
+
+ void firstMethod()
+ {
+ qDebug()<<"first method call of"<("testCase_from_Class"))
- .add(tc.add(TestSequence(&SimpleTestSequence::staticMetaObject,"metaObject-Sequence")))
+SimpleTestMetaSequence::SimpleTestMetaSequence ( QObject *parent ) : QObject ( parent )
+{
+ qDebug()<<"creating meta object based SimpleTestMetaSequence";
+}
+
+SimpleTestMetaSequence::~SimpleTestMetaSequence()
+{
+ qDebug()<<"deleting meta object based SimpleTestMetaSequence";
+}
+
+void SimpleTestMetaSequence::testStep1()
+{
+ qDebug()<<"SimpleTestMetaSequence: executing step 1";
+}
+
+void SimpleTestMetaSequence::testStep2()
+{
+ qDebug()<<"SimpleTestMetaSequence: executing step 2";
+}
+
+
+static TestGroup tg=TestGroup::getGroup("class_group")
+ .add(testCaseByClass("testCase_from_Class"))
+ .add(TestCase(&SimpleTestMetaSequence::staticMetaObject,"metaObject-test-case"))
;
\ No newline at end of file
diff --git a/examples/qt-simple/simplemeta.h b/examples/qt-simple/simplemeta.h
index a7e7eff..fbbfafd 100644
--- a/examples/qt-simple/simplemeta.h
+++ b/examples/qt-simple/simplemeta.h
@@ -13,21 +13,49 @@
#include
-/** This class shows how to feed a sequence using a QObject based class or QMetaObject.
+/** This class shows how to create a sequence using a QObject based class.
*
* When a class is used to initialize a test sequence or test case the steps of that
- * sequence or case are set to the call-able methods (public slots and public QINVOKABLE
+ * sequence or case are set to the call-able methods (public slots and public Q_INVOKABLE
* methods without parameters). The steps are sorted in ASCII order of the method name.
*/
class SimpleTestSequence:public QObject
{
Q_OBJECT
public:
+ /// constructs the object, this will be done automatically by the TestCase
explicit SimpleTestSequence ( QObject *parent = 0 );
virtual ~SimpleTestSequence();
+
public slots:
+ ///first test step
void testStep1();
+ ///second test step
void testStep2();
};
+
+
+
+/** This class shows how to create a sequence using QMetaObject.
+ *
+ * When a class is used to initialize a test sequence or test case the steps of that
+ * sequence or case are set to the call-able methods (public slots and public Q_INVOKABLE
+ * methods without parameters). The steps are sorted in ASCII order of the method name.
+ */
+class SimpleTestMetaSequence:public QObject
+{
+ Q_OBJECT
+public:
+ /// if QMetaObject is used directly, the default constructor must be Q_INVOKABLE
+ Q_INVOKABLE explicit SimpleTestMetaSequence ( QObject *parent = 0 );
+ virtual ~SimpleTestMetaSequence();
+
+public slots:
+ ///second test step
+ void testStep2();
+ ///first test step
+ void testStep1();
+};
+
#endif
diff --git a/runnerlib/c/README b/runnerlib/c/README
deleted file mode 100644
index 2d5cac7..0000000
--- a/runnerlib/c/README
+++ /dev/null
@@ -1,22 +0,0 @@
-README for ANSI-C SKID runnerlib
-=================================
-
-AS YOU CAN SEE THERE IS NO SOURCE HERE YET. THE BELOW DESCRIPTION IS A DRAFT
-FOR FUTURE DEVELOPMENT.
-
-Building
-----------
-
-This runnerlib is currently designed for Unix/Linux only.
-
-Edit the first few lines of the Makefile - see the comments in the file for
-details.
-
-Then call: make
-
-You should end up with a libskidrunnerc.so and/or libskidrunnerc.a
-
-Using It
----------
-
-Follow the documentation for C++: doc/runner-c.html
diff --git a/runnerlib/cpp/README b/runnerlib/cpp/README
deleted file mode 100644
index cfd497c..0000000
--- a/runnerlib/cpp/README
+++ /dev/null
@@ -1,22 +0,0 @@
-README for plain C++ SKID runnerlib
-====================================
-
-AS YOU CAN SEE THERE IS NO SOURCE HERE YET. THE BELOW DESCRIPTION IS A DRAFT
-FOR FUTURE DEVELOPMENT.
-
-Building
-----------
-
-This runnerlib is currently designed for Unix/Linux only.
-
-Edit the first few lines of the Makefile - see the comments in the file for
-details.
-
-Then call: make
-
-You should end up with a libskidrunnercpp.so and/or libskidrunnercpp.a
-
-Using It
----------
-
-Follow the documentation for C++: doc/runner-cpp.html
diff --git a/runnerlib/qt5/Doxyfile b/runnerlib/qt5/Doxyfile
index faa71f8..604945c 100644
--- a/runnerlib/qt5/Doxyfile
+++ b/runnerlib/qt5/Doxyfile
@@ -12,3 +12,16 @@ PROJECT_NAME = "SKID Qt Runner"
PROJECT_BRIEF = "SKID Runner Library for Qt5, Class Documentation"
+# friends are strictly internal
+HIDE_FRIEND_COMPOUNDS = YES
+
+# we need to show the files, since some docu depends on it
+SHOW_FILES = YES
+
+# hide the export macro
+MACRO_EXPANSION = YES
+EXPAND_ONLY_PREDEF = YES
+PREDEFINED = SKID_RUNNERLIB_EXPORT
+
+
+INTERNAL_DOCS = YES
diff --git a/runnerlib/qt5/include/skid.h b/runnerlib/qt5/include/skid.h
index 08bb1e0..c79c317 100644
--- a/runnerlib/qt5/include/skid.h
+++ b/runnerlib/qt5/include/skid.h
@@ -8,15 +8,26 @@
//
//
+///\file skid.h
+///Include this file to gain access to all SKID runner facilities.
+
#ifndef SKID_RUNNERLIB_QT_SKID_H
#define SKID_RUNNERLIB_QT_SKID_H
#include "skid_export.h"
+///This is the main namespace in which all SKID specific functions and classes exist.
namespace Skid {
+/// Main function for SKID based test runners.
+/// Normally you do not call this function, but instead include the SKID_MAIN macro in your sources.
+/// \see SKID_MAIN
SKID_RUNNERLIB_EXPORT int skid_main(int,char**);
};
+///\def SKID_MAIN
+///Include this macro in your SKID based runner source, it will automatically create
+///everything necessary to make the runner behave correctly.
+///\hideinitializer
#define SKID_MAIN \
int main(int ac,char**av){return skid_main(ac,av);}
diff --git a/runnerlib/qt5/include/skid_assert.h b/runnerlib/qt5/include/skid_assert.h
index 6a7136c..6983202 100644
--- a/runnerlib/qt5/include/skid_assert.h
+++ b/runnerlib/qt5/include/skid_assert.h
@@ -8,100 +8,404 @@
//
//
+///\file skid_assert.h
+///Defines Assertion handling.
+
#ifndef SKID_RUNNERLIB_QT_ASSERT_SKID_H
#define SKID_RUNNERLIB_QT_ASSERT_SKID_H
#include "skid_export.h"
+#include "skid_group.h"
#include
#include
namespace Skid {
+///represents results of an assertion
+///\relates Assert_
enum class Result{
- Pass=0,
- Fail=1,
- Abort=2,
- Skip=3,
- Inconclusive=4
+ ///the assertion passed
+ Pass=TestItem::Success,
+ ///the assertion failed
+ Fail=TestItem::Failed,
+ ///the test was aborted deliberately
+ Abort=TestItem::Aborted,
+ ///the test was skipped
+ Skip=TestItem::Skipped,
+ ///the test was inconclusive
+ Inconclusive=TestItem::Inconclusive
};
+///specifies the action taken by an assertion
+///\relates Assert_
enum class Action{
+ ///no action is taken, the specific result is ignored
None=0,
+ ///aborts the test (step, sequence, case, ...)
Abort=1,
+ ///just sets the result and continues executing the test
SetAndContinue=2,
};
+///if the action is to abort, the frame specifies how far to go
+///\relates Assert_
enum class Frame{
+ ///abort just the one test step
Step=0,
- TestCase=1,
- Group=2,
- Job=3,
- AllJobs=4
+ ///abort the sequence that contains the test step
+ Sequence=1,
+ ///abort the entire test case
+ TestCase=2,
+ ///abort the group containing the test case
+ Group=3,
+ ///abort the entire job (not recommended)
+ Job=0x80,
+ ///abort the entire job and prevent all queued jobs from running (highly discouraged)
+ AllJobs=0xff
};
-
-class Assert_
+///This class is used internally to signal that a test has been aborted.
+class SKID_RUNNERLIB_EXPORT AssertException
{
+ public:
+ AssertException()=delete;
+ ///Creates a new AssertException.
+ ///\param line should hold the source line in which the assertion failed
+ ///\param file should hold the source file name in which the assertion failed
+ ///\param msg the message that is being emitted during failure
+ ///\param frame the frame to be aborted by this exception (see Frame)
+ ///\param result the result of the abortion (see Result)
+ AssertException(int line,QString file,QString msg,Frame frame,Result result)
+ :mline(line),mfile(file),mmsg(msg),mframe(frame),mresult(result)
+ {}
+ ///Creates a new AssertException without a reference to sources.
+ ///\param msg the message that is being emitted during failure
+ ///\param frame the frame to be aborted by this exception (see Frame)
+ ///\param result the result of the abortion (see Result)
+ AssertException(QString msg,Frame frame,Result result)
+ :mmsg(msg),mframe(frame),mresult(result)
+ {}
+ ///Creates a copy of the exception.
+ AssertException(const AssertException&)=default;
+ ///Creates a copy of the exception.
+ AssertException(AssertException&&)=default;
+
+ ///Returns the frame that should abort upon receiving this exception.
+ ///The sub-classes of TestItem will handle this field to decide whether to rethrow the exception.
+ Frame frame()const{return mframe;}
+ ///Returns the result that should be set once the exception was caught.
+ Result result()const{return mresult;}
+ ///Returns the result as an equivalent TestItem::State value.
+ TestItem::State resultAsState()const;
+ ///Returns the result as a string representation.
+ QString resultAsString()const;
+ ///Returns the message of this exception.
+ QString message()const{return mmsg;}
+
+ ///Returns the line in the source file where the assertion failed.
+ int line()const{return mline;}
+ ///Returns the source file name in which the assertion failed.
+ QString file()const{return mfile;}
+
private:
int mline=-1;
QString mfile,mmsg;
- Result mresult=Result::Skip;
+ Frame mframe=Frame::Step;
+ Result mresult=Result::Fail;
+};
+
+///This class encapsulates and represents assertions in a test.
+///Usually assertions are instantiated using the Assert macros.
+///
+///Assertions are normally called through the Assert() macro and are
+///only created as temporary objects:
+///\code
+/// //compare two integers
+/// Assert().isEqual(myint,1);
+///
+/// //compare two strings
+/// Assert().isNotEqual(somestring,"please not this value");
+///
+/// //abort test case if this fails
+/// Assert().isEqual(badvalue,666).ifFailedFailTestCase("something bad happend");
+///\endcode
+///
+/// At the end of each of those assertion lines the result is stored, or (mostly on failure) and
+/// AssertException is thrown to end the corresponding test case, step or group.
+///
+///\see Assert()
+///\throws AssertException to abort test cases in case of failure
+class SKID_RUNNERLIB_EXPORT Assert_
+{
+ private:
+ int mline=-1;
+ QString mfile,mmsgiftrue,mmsgiffalse;
+ Result miftrue=Result::Pass;
+ Result miffalse=Result::Fail;
+ Action mactiftrue=Action::SetAndContinue;
+ Action mactiffalse=Action::Abort;
+ Frame mfrmiftrue=Frame::Step;
+ Frame mfrmiffalse=Frame::TestCase;
- Assert_& handleCompare(bool r);
+ enum TriState{TSNone,TSTrue,TSFalse};
+ TriState mresult=TSNone;
+ inline Assert_& handleCompare(bool r){if(r)mresult=TSTrue;else mresult=TSFalse;return *this;}
+
+ templatevoid setFMsg(QString msg,T v1,T v2)
+ {
+ if(!mmsgiffalse.isEmpty())return;
+ mmsgiffalse=msg.arg(v1).arg(v2);
+ }
public:
+ ///creates a new assertion with a reference to source file and line
Assert_(int line,QString file):mline(line),mfile(file){}
- ~Assert_();
+ ///deletes the assertion
+ ~Assert_()noexcept(false);
+ ///@cond HIDE_DELETED
+ Assert_(const Assert_&)=delete;
+ Assert_(Assert_&&)=delete;
+ void*operator new(std::size_t)=delete;
+ void*operator new[](std::size_t)=delete;
+ void*operator new(std::size_t, const std::nothrow_t&)=delete;
+ void*operator new[](std::size_t, const std::nothrow_t&)=delete;
+ ///@endcond
+
+ ///returns the message that will be emitted when the assertion passes,
+ ///the default is no message
+ QString passMessage()const{return mmsgiftrue;}
+ ///returns the message that will be emitted when the assertion fails,
+ ///the default is assertion type specific
+ QString failMessage()const{return mmsgiffalse;}
+ ///returns the result that will be set if the assertion succeeds,
+ ///the default is Result::Pass
+ Result passResult()const{return miftrue;}
+ ///returns the result that will be set if the assertion fails,
+ ///the default is Result::Fail
+ Result failResult()const{return miffalse;}
+ ///returns the action that will be taken if the assertion succeeds,
+ ///the default is Action::SetAndContinue
+ Action passAction()const{return mactiftrue;}
+ ///returns the action that will be taken if the assertion fails,
+ ///the default is Action::Abort
+ Action failAction()const{return mactiffalse;}
+ ///returns how far the test will be aborted in case of success if passAction() was set to Abort,
+ ///the default is Frame::Step
+ Frame passFrame()const{return mfrmiftrue;}
+ ///returns how far the test will be aborted in case of failure, if failAction() is still Abort,
+ ///the default is Frame::TestCase
+ Frame failFrame()const{return mfrmiffalse;}
+
+ ///changes the message for a successfull test
+ Assert_& setPassMessage(QString msg){mmsgiftrue=msg;return *this;}
+ ///changes the message for a failed test
+ Assert_& setFailMessage(QString msg){mmsgiffalse=msg;return *this;}
+ ///changes the result of a successfull test
+ Assert_& setPassResult(Result r){miftrue=r;return *this;}
+ ///changes the result of a failed test
+ Assert_& setFailResult(Result r){miffalse=r;return *this;}
+ ///changes the action for a successfull test
+ Assert_& setPassAction(Action a){mactiftrue=a;return *this;}
+ ///changes the action for a failed test
+ Assert_& setFailAction(Action a){mactiffalse=a;return *this;}
+ ///changes how far the test is aborted in case of success if passAction() is Abort
+ Assert_& setPassFrame(Frame f){mfrmiftrue=f;return *this;}
+ ///changes how far the test is aborted in case of failure if failAction() is Abort
+ Assert_& setFailFrame(Frame f){mfrmiffalse=f;return *this;}
+
+ ///tells SKID to abort the test step if this assertion fails,
+ ///the test case/sequence will continue with the next step
+ Assert_& ifFailedAbortTestStep(QString msg=QString())
+ {
+ mactiffalse=Action::Abort;
+ mfrmiffalse=Frame::Step;
+ miftrue=Result::Abort;
+ if(!msg.isEmpty())
+ mmsgiffalse=msg;
+ return *this;
+ }
+ ///tells SKID to abort the test case if this assertion fails,
+ ///the test group continues with the next test case
+ Assert_& ifFailedAbortTestCase(QString msg=QString())
+ {
+ mactiffalse=Action::Abort;
+ mfrmiffalse=Frame::TestCase;
+ miftrue=Result::Abort;
+ if(!msg.isEmpty())
+ mmsgiffalse=msg;
+ return *this;
+ }
+ ///tells SKID to abort the entire test group if this assertion fails,
+ ///the next test group will be executed next
+ Assert_& ifFailedAbortGroup(QString msg=QString())
+ {
+ mactiffalse=Action::Abort;
+ mfrmiffalse=Frame::Group;
+ miftrue=Result::Abort;
+ if(!msg.isEmpty())
+ mmsgiffalse=msg;
+ return *this;
+ }
+ ///tells SKID to abort the entire test job if this assertion fails
+ Assert_& ifFailedAbortJob(QString msg=QString())
+ {
+ mactiffalse=Action::Abort;
+ mfrmiffalse=Frame::Job;
+ miftrue=Result::Abort;
+ if(!msg.isEmpty())
+ mmsgiffalse=msg;
+ return *this;
+ }
+
+ ///tells SKID to fail the test step if this assertion fails,
+ ///the test case/sequence will continue with the next step
+ Assert_& ifFailedFailTestStep(QString msg=QString())
+ {
+ mactiffalse=Action::Abort;
+ mfrmiffalse=Frame::Step;
+ miftrue=Result::Abort;
+ if(!msg.isEmpty())
+ mmsgiffalse=msg;
+ return *this;
+ }
+ ///tells SKID to fail the test case if this assertion fails,
+ ///the test group will continue with the next test case
+ Assert_& ifFailedFailTestCase(QString msg=QString())
+ {
+ mactiffalse=Action::Abort;
+ mfrmiffalse=Frame::TestCase;
+ miftrue=Result::Abort;
+ if(!msg.isEmpty())
+ mmsgiffalse=msg;
+ return *this;
+ }
+ ///tells SKID to fail the entire test group if this assertion fails,
+ ///the next test group will be chosen next
+ Assert_& ifFailedFailGroup(QString msg=QString())
+ {
+ mactiffalse=Action::Abort;
+ mfrmiffalse=Frame::Group;
+ miftrue=Result::Abort;
+ if(!msg.isEmpty())
+ mmsgiffalse=msg;
+ return *this;
+ }
+
+ ///tells SKID to completely ignore a failure of the assertion
+ Assert_& ignoreFailure()
+ {
+ mactiffalse=Action::None;
+ return *this;
+ }
+
+ ///tells SKID to invert the logic of the assertion, e.g. isEqual will behave like isNotEqual
+ Assert_& invertResult()
+ {
+ Action tmpa=mactiffalse;
+ mactiffalse=mactiftrue;
+ mactiftrue=tmpa;
+ Result tmpr=miffalse;
+ miffalse=miftrue;
+ miftrue=tmpr;
+ QString tmpm=mmsgiffalse;
+ mmsgiffalse=mmsgiftrue;
+ mmsgiftrue=tmpm;
+ Frame tmpf=mfrmiffalse;
+ mfrmiffalse=mfrmiftrue;
+ mfrmiftrue=tmpf;
+ return *this;
+ }
+
+ ///compares the two values, succeeds if they are identical
template inline Assert_& isEqual(T v1,T v2)
- {return handleCompare(v1==v2);}
+ {
+ setFMsg("values do not match\nexpected: %1\n but got: %2",v1,v2);
+ return handleCompare(v1==v2);
+ }
+
+ ///compares two values, succeeds if they are different
template inline Assert_& isNotEqual(T v1,T v2)
{return handleCompare(v1!=v2);}
+
+ ///compares two values, succeeds if the first one is less than the second one,
+ ///requires a < operator for the type
template inline Assert_& isLighter(T v1,T v2)
{return handleCompare(v1 operator for the type
template inline Assert_& isGreater(T v1,T v2)
{return handleCompare(v1>v2);}
+
+ ///compares two values, succeeds if the first one is equal or lighter than the second one,
+ ///requires a <= operator for the type
template inline Assert_& isLighterOrEqual(T v1,T v2)
{return handleCompare(v1<=v2);}
+
+ ///compares two values, succeeds if the first one is equal or greater than the second one,
+ ///requires a <= operator for the type
template inline Assert_& isGreaterOrEqual(T v1,T v2)
{return handleCompare(v1>=v2);}
+ ///alias for isEquial
template inline Assert_& isEQ(T v1,T v2)
- {return handleCompare(v1==v2);}
+ {return isEqual(v1,v2);}
+ ///alias for isNotEqual
template inline Assert_& isNE(T v1,T v2)
- {return handleCompare(v1!=v2);}
+ {return isNotEqual(v1,v2);}
+ ///alias for isLighter
template inline Assert_& isLT(T v1,T v2)
- {return handleCompare(v1(v1,v2);}
+ ///alias for isGreater
template inline Assert_& isGT(T v1,T v2)
- {return handleCompare(v1>v2);}
+ {return isGreater(v1,v2);}
+ ///alias for isLighterOrEqual
template inline Assert_& isLE(T v1,T v2)
- {return handleCompare(v1<=v2);}
+ {return isLighterOrEqual(v1,v2);}
+ ///alias for isGreaterOrEqual
template inline Assert_& isGE(T v1,T v2)
- {return handleCompare(v1>=v2);}
+ {return isGreaterOrEqual(v1,v2);}
+ ///succeeds if the value is true
inline Assert_& isTrue(bool b)
{return handleCompare(b);}
+ ///succeeds if the value is false
inline Assert_& isFalse(bool b)
{return handleCompare(!b);}
+ ///succeeds if the callback function throws exception type Ex,
+ ///fails if no exception is thrown;
+ ///any other exception is a failure;
+ ///if you call doesThrow(callback), all exceptions are counted as success
template inline
Assert_& doesThrow(std::functioncallback)
{
- bool dt=false;
- try{callback();}
- catch(Ex){dt=true;}
- return handleCompare(dt);
+ try{
+ callback();
+ handleCompare(false);
+ }
+ catch(Ex){handleCompare(true);}
+ catch(...){handleCompare(false);}
+ return *this;
}
+ ///succeeds if the callback does not throw an exception of type Ex;
+ ///no exception and any other exceptions are handled as success;
+ ///if you call doesNotThrow(callback), all exceptions are caught and counted as failure
template inline
Assert_& doesNotThrow(std::functioncallback)
{
- bool dt=false;
- try{callback();}
- catch(Ex){dt=true;}
- return handleCompare(!dt);
+ try{
+ callback();
+ handleCompare(true);
+ }
+ catch(Ex){handleCompare(false);}
+ catch(...){handleCompare(true);}
+ return *this;
}
};
+///@cond HIDE_SPECIALIZATION
template <> inline
Assert_& Assert_::doesThrow(std::functioncallback)
{
@@ -118,8 +422,13 @@ Assert_& Assert_::doesNotThrow(std::functioncallback)
catch(...){dt=true;}
return handleCompare(!dt);
}
+///@endcond
+
+///\def Assert()
+///Creates a temporary Assertion instance to be used in an actual test.
+///\hideinitializer
+#define Assert() ::Skid::Assert_(__LINE__,__FILE__)
-#define Assert SkidAssert_(__LINE__,__FILE__)
}
#endif
diff --git a/runnerlib/qt5/include/skid_group.h b/runnerlib/qt5/include/skid_group.h
index a6220b6..62bd13e 100644
--- a/runnerlib/qt5/include/skid_group.h
+++ b/runnerlib/qt5/include/skid_group.h
@@ -18,36 +18,48 @@
#include "skid_pointer.h"
+#include
+
class QXmlStreamWriter;
namespace Skid {
-class TestItemPrivate;
-class TestGroupingItemPrivate;
-class TestGroupPrivate;
-class TestCasePrivate;
-class TestStepItemPrivate;
-class TestStepPrivate;
-class TestSequencePrivate;
+namespace Internal {
+ class TestItemPrivate;
+ class TestGroupingItemPrivate;
+ class TestGroupPrivate;
+ class TestCasePrivate;
+ class TestStepItemPrivate;
+ class TestStepPrivate;
+ class TestSequencePrivate;
+ class Executing;
+}
+
+class AssertException;
class TestItem;
+class Assert_;
/// Abstract base class for all test grouping items.
class SKID_RUNNERLIB_EXPORT TestItem
{
protected:
- ReferencePtr d;
+ Internal::ReferencePtr d;
TestItem()=delete;
- class Executing;
- static QThreadStorage>currentItem;
+ static QThreadStorage>currentItem;
///used to instantiate item from private d-ptr,
///both as a fresh item and as a copy of a child of a group
- explicit TestItem(TestItemPrivate*);
+ explicit TestItem(Internal::TestItemPrivate*);
//need this to access d of other sub-classes
friend class TestGroup;
friend class TestSequence;
+ friend class Internal::Executing;
+ friend class Assert_;
+
+ void emitMessage(const AssertException&);
+ void emitMessage(const QString&);
public:
///move the item, the target refers to the same logical item
TestItem(TestItem&&);
@@ -77,13 +89,28 @@ class SKID_RUNNERLIB_EXPORT TestItem
///returns the name of the item
QString name()const;
+ ///returns the full name as a dot-separated string, starting at the root-group
+ QString fullName()const;
///returns the human readable description of the item
QString description()const;
+ ///returns the message generated by the last run, if any
+ QString message()const;
///executes the test item
virtual void execute();
+
+ enum EnumerationOption {
+ MinimalListing=0,
+ ListDescription=1,
+ ListChildren=2,
+ ListState=0x10,
+ ListMessage=0x20,
+ DefaultListing=MinimalListing|ListDescription|ListChildren,
+ StatusListing=MinimalListing|ListState|ListMessage|ListChildren,
+ };
+ Q_DECLARE_FLAGS(EnumerationOptions,EnumerationOption);
///streams the content of this item as XML
- virtual void enumerate(QXmlStreamWriter&)const;
+ virtual void enumerate(QXmlStreamWriter&,EnumerationOptions opt=DefaultListing)const;
///delete this reference to the logical item
virtual ~TestItem();
@@ -114,13 +141,22 @@ class SKID_RUNNERLIB_EXPORT TestItem
TestItem findItem(QStringList base)const;
///tries to find a child with the given name (non-recursive)
TestItem getChild(const QString&)const;
+
+ ///returns the parent item (or an invalid item if there is no parent)
+ TestItem parent()const;
+
+ protected:
+ static void setCurrentResult(TestItem::State,QString);
};
+Q_DECLARE_OPERATORS_FOR_FLAGS(TestItem::EnumerationOptions)
+
+/// Base class for TestGroup and TestCase, cannot be instantiated directly
class SKID_RUNNERLIB_EXPORT TestGroupingItem:public TestItem
{
protected:
TestGroupingItem()=delete;
- explicit TestGroupingItem ( TestItemPrivate * b):TestItem(b){}
+ explicit TestGroupingItem ( Internal::TestItemPrivate * b):TestItem(b){}
public:
TestGroupingItem ( TestGroupingItem && )=default;
TestGroupingItem ( const TestGroupingItem & )=default;
@@ -132,11 +168,12 @@ class SKID_RUNNERLIB_EXPORT TestGroupingItem:public TestItem
};
+/// Base class for TestSequence and TestStep, cannot be instantiated directly
class SKID_RUNNERLIB_EXPORT TestStepItem:public TestItem
{
protected:
TestStepItem()=delete;
- explicit TestStepItem ( TestItemPrivate * b):TestItem(b){}
+ explicit TestStepItem ( Internal::TestItemPrivate * b):TestItem(b){}
TestStepItem ( TestStepItem && )=default;
TestStepItem ( const TestStepItem & )=default;
TestStepItem& operator=(const TestStepItem&)=default;
@@ -150,15 +187,24 @@ class SKID_RUNNERLIB_EXPORT TestStepItem:public TestItem
class SKID_RUNNERLIB_EXPORT TestGroup:public TestGroupingItem
{
public:
+ ///creates an invalid group
TestGroup();
+ ///creates a new group with the given name
explicit TestGroup(QString name,QString description=QString());
+ ///moves the group, creating a new reference to it
TestGroup(TestGroup&&)=default;
+ ///creates a new reference to a group
TestGroup(const TestGroup&)=default;
+ ///deletes the reference to the group
~TestGroup()=default;
+ ///forgets the current group status and makes this group a fresh reference of the argument
TestGroup& operator=(const TestGroup&)=default;
+ ///forgets the current group status and makes this group a fresh reference of the argument
TestGroup& operator=(TestGroup&&)=default;
+ ///forgets the current group status and makes this group a fresh reference of the argument
TestGroup& operator=(const TestItem&it){TestItem::operator=(it);return *this;}
+ ///forgets the current group status and makes this group a fresh reference of the argument
TestGroup& operator=(TestItem&&it){TestItem::operator=(it);return *this;}
///adds an item to this group
@@ -166,8 +212,10 @@ class SKID_RUNNERLIB_EXPORT TestGroup:public TestGroupingItem
///removes an item from this group
TestGroup& remove(const TestGroupingItem&);
+ ///executes all sub-groups and test-cases inside this group
void execute()override;
- virtual void enumerate(QXmlStreamWriter&)const override;
+ ///lists this group, including all sub-groups and test-cases
+ virtual void enumerate(QXmlStreamWriter&,EnumerationOptions opt=DefaultListing)const override;
///returns the root group of all tests
static TestGroup root();
@@ -176,91 +224,180 @@ class SKID_RUNNERLIB_EXPORT TestGroup:public TestGroupingItem
static TestGroup getGroup(const QString&);
private:
- explicit TestGroup ( TestItemPrivate *b ):TestGroupingItem(b){}
- friend class TestItem;
-};
-
-/// Represents a Test Case.
-/// A Test Case is a self-contained sequence of steps to test
-class SKID_RUNNERLIB_EXPORT TestCase:public TestGroupingItem
-{
- public:
- TestCase();
- explicit TestCase ( QString name );
- TestCase ( const QMetaObject*,QString name);
- TestCase ( const TestCase & )=default;
- TestCase ( TestCase && )=default;
- virtual ~TestCase()=default;
- TestCase& operator=(const TestCase&)=default;
- TestCase& operator=(TestCase&&)=default;
- TestCase& operator=(const TestItem&it){TestItem::operator=(it);return *this;}
- TestCase& operator=(TestItem&&it){TestItem::operator=(it);return *this;}
-
- TestCase&add(const TestStepItem&);
- TestCase&remove(const TestStepItem&);
-
- void execute()override;
- virtual void enumerate(QXmlStreamWriter&)const override;
-
- private:
- explicit TestCase ( TestItemPrivate *b ):TestGroupingItem(b){}
- friend class TestGroup;
+ explicit TestGroup ( Internal::TestItemPrivate *b ):TestGroupingItem(b){}
+ /// \internal
friend class TestItem;
};
+///Test steps are atomic actions taken inside a test case, each step has its own
+///result state and contributes to the result of the test case.
+///
+///Test steps are always executed in the exact sequence in which they are set up.
class SKID_RUNNERLIB_EXPORT TestStep:public TestStepItem
{
public:
+ ///creates an invalid test step
TestStep();
+ ///creates a new test step with a name, but no function (always fails)
explicit TestStep ( QString name );
+ ///creates a new test step with a name and function to call
+ TestStep ( QString name, std::function );
+ ///creates a new reference to the same test step
TestStep ( TestStep && )=default;
+ ///creates a new reference to the same test step
TestStep ( const TestStep & )=default;
+ ///creates a new reference to the same test step
TestStep& operator=(const TestStep&)=default;
+ ///creates a new reference to the same test step
TestStep& operator=(TestStep&&)=default;
+ ///creates a new reference to the same test step
TestStep& operator=(const TestItem&it){TestItem::operator=(it);return *this;}
+ ///creates a new reference to the same test step
TestStep& operator=(TestItem&&it){TestItem::operator=(it);return *this;}
-
+
+ ///executes the test step
void execute()override;
- virtual void enumerate(QXmlStreamWriter&)const override;
+ ///lists the test step
+ virtual void enumerate(QXmlStreamWriter&,EnumerationOptions opt=DefaultListing)const override;
private:
- explicit TestStep(TestItemPrivate*b):TestStepItem(b){}
+ explicit TestStep(Internal::TestItemPrivate*b):TestStepItem(b){}
friend class TestItem;
};
+///Test sequences are groups of test steps (and/or other sequences) inside a test case.
+///
+///Sequences are always executed in the order they have been added to the test case
+///and always execute the steps/sequences they contain in order.
class SKID_RUNNERLIB_EXPORT TestSequence:public TestStepItem
{
public:
+ ///creates an invalid sequence
TestSequence();
+ ///creates a new (empty) sequence with a name,
+ ///you can add steps and sequences using the add method
explicit TestSequence ( QString name );
- TestSequence (const QMetaObject*,QString name);
+ ///Creates a new sequence based on a meta object and name.
+ ///These kinds of sequences cannot be altered after creation.
+ ///\param metaobject the meta object to use as a template, all public slots without parameters will be used as steps for this sequence, steps will be sorted by method name in ascending ASCII order
+ ///\param name the name of the sequence, if empty, the class name from the metaobject will be used
+ ///\see Skid::testSequenceByClass
+ explicit TestSequence (const QMetaObject*metaobject,QString name=QString());
+ ///Creates a new reference to the same test sequence.
TestSequence ( TestSequence && )=default;
+ ///Creates a new reference to the same test sequence.
TestSequence ( const TestSequence & )=default;
+ ///Creates a new reference to the same test sequence.
TestSequence& operator=(const TestSequence&)=default;
+ ///Creates a new reference to the same test sequence.
TestSequence& operator=(TestSequence&&)=default;
+ ///Creates a new reference to the same test sequence.
TestSequence& operator=(const TestItem&it){TestItem::operator=(it);return *this;}
+ ///Creates a new reference to the same test sequence.
TestSequence& operator=(TestItem&&it){TestItem::operator=(it);return *this;}
+ ///executes the test sequence in order
void execute()override;
- virtual void enumerate(QXmlStreamWriter&)const override;
+ ///lists the test sequence
+ virtual void enumerate(QXmlStreamWriter&,EnumerationOptions opt=DefaultListing)const override;
+ ///adds a new step or sequence to the end of this sequence
void add(const TestStepItem&);
+ ///removes a step or sequence from this sequence
void remove(const TestStepItem&);
+
+ ///helper type: instantiates a new QObject-based instance
+ ///\see testSequenceByClass
+ typedef std::functionFactoryType;
+ ///if this is a QObject/QMetaObject based sequence, sets a factory to create a new instance
+ void setFactory(FactoryType);
private:
- explicit TestSequence(TestItemPrivate*b):TestStepItem(b){}
+ ///creates a new sequence
+ explicit TestSequence(Internal::TestItemPrivate*b):TestStepItem(b){}
friend class TestItem;
friend class TestCase;
- void subenumerate(QXmlStreamWriter&)const;
+ ///\internal helper to list children of the sequence (also used by TestCase)
+ void subenumerate(QXmlStreamWriter&,EnumerationOptions)const;
+};
+
+/// Represents a Test Case.
+/// A Test Case is a self-contained sequence of steps to test
+class SKID_RUNNERLIB_EXPORT TestCase:public TestGroupingItem
+{
+ public:
+ ///creates a new invalid test case
+ TestCase();
+ ///creates a new test case by name
+ explicit TestCase ( QString name );
+ ///Creates a new test case based on a meta object and name.
+ ///These kinds of test cases cannot be altered after creation.
+ ///\param metaobject the meta object to use as a template, all public slots without parameters will be used as steps for this test case, steps will be sorted by method name in ascending ASCII order
+ ///\param name the name of the test case, if empty, the class name from the metaobject will be used
+ ///\see Skid::testCaseByClass
+ TestCase ( const QMetaObject*metaobject,QString name=QString());
+ ///creates a new reference to the same test case
+ TestCase ( const TestCase & )=default;
+ ///creates a new reference to the same test case
+ TestCase ( TestCase && )=default;
+ ///deletes the reference to the test case
+ virtual ~TestCase()=default;
+ ///creates a new reference to the same test case
+ TestCase& operator=(const TestCase&)=default;
+ ///creates a new reference to the same test case
+ TestCase& operator=(TestCase&&)=default;
+ ///creates a new reference to the same test case
+ TestCase& operator=(const TestItem&it){TestItem::operator=(it);return *this;}
+ ///creates a new reference to the same test case
+ TestCase& operator=(TestItem&&it){TestItem::operator=(it);return *this;}
+
+ ///adds a step or sequence to the end of this test case
+ TestCase&add(const TestStepItem&);
+ ///removes a step or sequence from this test case
+ TestCase&remove(const TestStepItem&);
+
+ ///executes the test case in order
+ void execute()override;
+ ///lists the test case and its children
+ virtual void enumerate(QXmlStreamWriter&,EnumerationOptions opt=DefaultListing)const override;
+
+ ///if this is a QObject/QMetaObject based test case, sets a factory to create a new instance
+ void setFactory(TestSequence::FactoryType);
+
+ private:
+ ///creates a new test case
+ explicit TestCase ( Internal::TestItemPrivate *b ):TestGroupingItem(b){}
+ friend class TestGroup;
+ friend class TestItem;
};
+///Creates a new test case from a QObject based class.
+///\param T the class to use in this test case, the test case will use all public slots without parameters (in ASCII order of method names), a new instance will be created every time the test case is executed and deleted when done
+///\param name the name of the test case, if empty the class name is used
+///\relatesalso TestCase
+template
+TestCase testCaseByClass(QString name=QString())
+{
+ TestCase tc(&T::staticMetaObject,name);
+ auto fac=[]()->QObject*{return new T();};
+ tc.setFactory(fac);
+ return tc;
+}
+
+///Creates a new test sequence from a QObject based class.
+///\param T the class to use in this sequence, the sequence will use all public slots without parameters (in ASCII order of method names), a new instance will be created every time the sequence is executed and deleted when done
+///\param name the name of the sequence, if empty the class name is used
+///\relatesalso TestSequence
template
-TestCase testCaseByMetaObject(QString name)
+TestSequence testSequenceByClass(QString name=QString())
{
- return TestCase(&T::staticMetaObject,name);
+ TestSequence tc(&T::staticMetaObject,name);
+ auto fac=[]()->QObject*{return new T();};
+ tc.setFactory(fac);
+ return tc;
}
-//Goal
-//TestGroup::root().add(myGroup,"description");
+
+//end of namespace
}
#endif
\ No newline at end of file
diff --git a/runnerlib/qt5/include/skid_pointer.h b/runnerlib/qt5/include/skid_pointer.h
index 3ab9c02..2c50cbe 100644
--- a/runnerlib/qt5/include/skid_pointer.h
+++ b/runnerlib/qt5/include/skid_pointer.h
@@ -15,23 +15,40 @@
#include
namespace Skid {
+
+/// Internal classes and functions of SKID runner, never used directly.
+namespace Internal {
class ReferencePtr;
class WeakPtr;
+/// Base class of classes that can be garbage collected.
+/// Instances of this class are automatically deleted when the last reference is deleted.
+/// \see ReferencePtr
+/// \see WeakPtr
class Collectible
{
public:
+ ///Creates a Collectible
Collectible();
+ ///Deletes a Collectible
virtual ~Collectible();
- /// returns the number of references to this item
- inline int numrefs()const{return pointers.size();}
+ /// returns the number of references to this item which keep it from being deleted,
+ /// when this number goes to zero the instance is deleted
+ inline int numReferences()const{return pointers.size();}
+ /// returns the number of references, including weak reference
+ inline int numAllReferences()const{return pointers.size()+weakptrs.size();}
private:
+ ///\internal shows that the instance is still alive
static const int CanaryAlive=0xba1100;
+ ///\internal used when the instance is killed
static const int CanaryDead=0xdeadbeef;
+ ///\internal used as a canary to detect access after the instance has already been free'd
int mcanary=CanaryAlive;
+ ///\internal pointers back to the references, so they can be reset if necessary
QListpointers;
+ ///\internal pointers back to the references, so they can be reset if necessary
QListweakptrs;
friend class ReferencePtr;
@@ -42,78 +59,159 @@ private:
void attach(ReferencePtr*p);
};
+///Smart pointer to track references to Collectible objects.
+///Automatically reverts to nullptr if the object if deleted.
+///Does not keep the Collectible from being deleted.
class WeakPtr
{
public:
+ ///creates a weak pointer to nullptr
WeakPtr()=default;
+ ///creates a weak pointer as a copy of the given pointer
WeakPtr(const WeakPtr&p){operator=(p);}
+ ///creates a weak pointer as a copy of the given pointer
WeakPtr(WeakPtr&&p){operator=(p);}
+ ///creates a weak pointer that points to the given Collectible
WeakPtr(Collectible*p){operator=(p);}
+ ///deletes the weak pointer, but not the object it points to
~WeakPtr(){forget();}
+ ///returns the Collectible referenced by this WeakPtr
Collectible*operator->(){return ptr;}
+ ///returns the Collectible referenced by this WeakPtr
const Collectible*operator->()const{return ptr;}
+ ///returns true if this WeakPtr points to a null object
bool isNull()const{return ptr!=nullptr;}
- void forget();
+ ///makes this WeakPtr a copy of other, forgets about its current content
WeakPtr& operator=(const WeakPtr&p);
+ ///makes this WeakPtr a copy of other, forgets about its current content
WeakPtr& operator=(WeakPtr&&p);
+ ///makes this a reference to the given Collectible
WeakPtr& operator=(Collectible*p);
+ ///returns true if the two weak pointers point to the same object
bool operator==(const WeakPtr&p)const{return ptr==p.ptr;}
+ ///returns true if the weak pointer points to this Collectible
bool operator==(const Collectible*p)const{return ptr==p;}
+ ///returns true if the two weak pointers point to different objects
bool operator!=(const WeakPtr&p)const{return ptr!=p.ptr;}
+ ///returns true if the weak pointer points to a different Collectible
bool operator!=(const Collectible*p)const{return ptr!=p;}
+ ///returns the Collectible referenced by this ReferencePtr,
+ ///casts the pointer to the given class,
+ ///returns nullptr if the classes are not compatible or the reference points to null
templateT*data()const{return dynamic_cast(ptr);}
+ ///returns the Collectible referenced by this WeakPtr
Collectible*rawdata()const{return ptr;}
+ ///returns true if the WeakPtr contains a pointer compatible with T
+ template bool isa()const{return dynamic_cast(ptr)!=(T*)nullptr;}
private:
+ ///\internal forgets the current pointer, decreases the reference count of the Collectible this ReferencePtr points to
+ void forget();
friend class Collectible;
+ ///\internal pointer to Collectible
Collectible*ptr=nullptr;
};
-/// \internal used to properly track and delete pointers to d in TestItem and its sub-classes
+/// Used to properly track and delete pointers to d in Collectible and its sub-classes.
+/// ReferencePtr instances behave like pointers, except that when the last ReferencePtr is deleted or
+/// re-assigned the Collectible instance is deleted.
class ReferencePtr
{
public:
+ ///creates a reference to null object
ReferencePtr(){cref();}
+ ///creates a copy of the reference
ReferencePtr(const ReferencePtr&p){cref();operator=(p);}
+ ///creates a copy of the reference
ReferencePtr(ReferencePtr&&p){cref();operator=(p);}
+ ///creates a reference to the given Collectible
ReferencePtr(Collectible*p){cref();operator=(p);}
+ ///removes the reference
~ReferencePtr(){dref();forget();}
+ ///returns the Collectible referenced by this ReferencePtr
Collectible*operator->()const{return ptr;}
+ ///returns the Collectible referenced by this ReferencePtr
Collectible& operator*()const{return *ptr;}
+ ///returns true if this ReferencePtr points to a null object
bool isNull()const{return ptr==(Collectible*)nullptr;}
+ ///returns true if this ReferencePtr points to a valid Collectible
bool isValid()const{return ptr!=(Collectible*)nullptr;}
- void forget();
- ReferencePtr& operator=(const ReferencePtr&);
+ ///makes this ReferencePtr a copy of other, forgets about its current content
+ ReferencePtr& operator=(const ReferencePtr&other);
+ ///makes this ReferencePtr a copy of other, forgets about its current content
ReferencePtr& operator=(ReferencePtr&&);
+ ///makes this a reference to the given Collectible
ReferencePtr& operator=(Collectible*);
operator bool()const{return ptr!=(Collectible*)nullptr;}
+ ///returns the Collectible referenced by this ReferencePtr,
+ ///casts the pointer to the given class,
+ ///returns nullptr if the classes are not compatible or the reference points to null
templateT*data()const{return dynamic_cast(ptr);}
+ ///returns the Collectible referenced by this ReferencePtr
Collectible*rawdata()const{return ptr;}
+ ///returns the Collectible referenced by this ReferencePtr
operator Collectible*()const{return ptr;}
+ ///returns the Collectible referenced by this ReferencePtr,
+ ///casts the pointer to the given class,
+ ///returns nullptr if the classes are not compatible or the reference points to null
template operator T*()const{return dynamic_cast(ptr);}
+ ///returns true if the ReferencePtr contains a pointer compatible with T
+ template bool isa()const{return dynamic_cast(ptr)!=(T*)nullptr;}
private:
- friend class Skid::Collectible;
+ ///\internal forgets the current pointer, decreases the reference count of the Collectible this ReferencePtr points to
+ void forget();
+ friend class Skid::Internal::Collectible;
+ ///\internal pointer to Collectible
Collectible*ptr=nullptr;
- void cref();//{}
- void dref();//{}
+ ///\internal helper for debugging
+ inline void cref(){}
+ ///\internal helper for debugging
+ inline void dref(){}
};
-
+///returns true if the two references point to the same object
+///\relates ReferencePtr
inline bool operator==(const ReferencePtr&p1,const ReferencePtr&p2){return p1.rawdata()==p2.rawdata();}
+///returns true if the reference points to the given Collectible object
+///\relates ReferencePtr
inline bool operator==(const Collectible*d,const ReferencePtr&p){return p.rawdata()==d;}
+///returns true if the reference points to the given Collectible object
+///\relates ReferencePtr
inline bool operator==(const ReferencePtr&p,const Collectible*d){return p.rawdata()==d;}
+///returns true if the reference points to the given Collectible object
+///\relates ReferencePtr
inline bool operator==(Collectible*d,const ReferencePtr&p){return p.rawdata()==d;}
+///returns true if the reference points to the given Collectible object
+///\relates ReferencePtr
inline bool operator==(const ReferencePtr&p,Collectible*d){return p.rawdata()==d;}
+///returns true if the reference points to the null object
+///\relates ReferencePtr
inline bool operator==(const std::nullptr_t,const ReferencePtr&p){return p.isNull();}
+///returns true if the reference points to the null object
+///\relates ReferencePtr
inline bool operator==(const ReferencePtr&p,const std::nullptr_t){return p.isNull();}
+///returns true if the two references point to different objects
+///\relates ReferencePtr
inline bool operator!=(const ReferencePtr&p1,const ReferencePtr&p2){return p1.rawdata()!=p2.rawdata();}
+///returns true if the reference points to an object different from d
+///\relates ReferencePtr
inline bool operator!=(const Collectible*d,const ReferencePtr&p){return p.rawdata()!=d;}
+///returns true if the reference points to an object different from d
+///\relates ReferencePtr
inline bool operator!=(const ReferencePtr&p,const Collectible*d){return p.rawdata()!=d;}
+///returns true if the reference points to an object different from d
+///\relates ReferencePtr
inline bool operator!=(Collectible*d,const ReferencePtr&p){return p.rawdata()!=d;}
+///returns true if the reference points to an object different from d
+///\relates ReferencePtr
inline bool operator!=(const ReferencePtr&p,Collectible*d){return p.rawdata()!=d;}
+///returns true if the reference is not null
+///\relates ReferencePtr
inline bool operator!=(const std::nullptr_t,const ReferencePtr&p){return p.operator bool();}
+///returns true if the reference is not null
+///\relates ReferencePtr
inline bool operator!=(const ReferencePtr&p,const std::nullptr_t){return p.operator bool();}
-}
+}}
#endif
\ No newline at end of file
diff --git a/runnerlib/qt5/runnerqt5.pro b/runnerlib/qt5/runnerqt5.pro
index 47dd2f1..77d36bd 100644
--- a/runnerlib/qt5/runnerqt5.pro
+++ b/runnerlib/qt5/runnerqt5.pro
@@ -50,7 +50,8 @@ SOURCES += \
src/rqtmain.cpp \
src/skid_group.cpp \
src/skid_group_p.cpp \
- src/skid_pointer.cpp
+ src/skid_pointer.cpp \
+ src/skid_assert.cpp
HEADERS += \
src/rqtmain.h
\ No newline at end of file
diff --git a/runnerlib/qt5/src/rqtmain.cpp b/runnerlib/qt5/src/rqtmain.cpp
index af4db7a..90dbdae 100644
--- a/runnerlib/qt5/src/rqtmain.cpp
+++ b/runnerlib/qt5/src/rqtmain.cpp
@@ -20,9 +20,10 @@
#include
#include
-#define sApp (qobject_cast(qApp))
+using namespace Skid;
+using namespace Skid::Internal;
-Skid::Application::Application ( int &argc, char **argv ) : QCoreApplication ( argc, argv )
+Skid::Internal::Application::Application ( int &argc, char **argv ) : QCoreApplication ( argc, argv )
{
//make sure the root group exists
TestGroup::root();
@@ -30,7 +31,7 @@ Skid::Application::Application ( int &argc, char **argv ) : QCoreApplication ( a
QTimer::singleShot(0,this,SLOT(scanArguments()));
}
-void Skid::Application::scanArguments()
+void Skid::Internal::Application::scanArguments()
{
for(const QString&argument:arguments().mid(1)){
if(argument=="-run"){
@@ -40,10 +41,16 @@ void Skid::Application::scanArguments()
if(Job::testExecution(argument.mid(5))==nullptr)
qFatal("Unable to execute tests on prefix %s",argument.mid(5).toLocal8Bit().data());
}else if(argument=="-list"){
- if(Job::testEnumeration(QString())==nullptr)
+ if(Job::testEnumeration(QString(),false)==nullptr)
qFatal("Unable to enumerate tests.");
}else if(argument.startsWith("-list=")){
- if(Job::testEnumeration(argument.mid(6))==nullptr)
+ if(Job::testEnumeration(argument.mid(6),false)==nullptr)
+ qFatal("Unable to enumerate tests on prefix %s",argument.mid(6).toLocal8Bit().data());
+ }else if(argument=="-state"){
+ if(Job::testEnumeration(QString(),true)==nullptr)
+ qFatal("Unable to enumerate tests.");
+ }else if(argument.startsWith("-state=")){
+ if(Job::testEnumeration(argument.mid(7),true)==nullptr)
qFatal("Unable to enumerate tests on prefix %s",argument.mid(6).toLocal8Bit().data());
}else if(argument.startsWith("-connect=")){
Connection::netConnection(argument.mid(9));
@@ -87,7 +94,7 @@ void Application::addJob ( Job * j)
if(j!=nullptr && !mjobs.contains(j)){
mjobs<state()==Job::Pending)
+ if(first!=nullptr && first->state()==Job::Pending)
first->execute();
}
+void Application::postMessage ( const AssertException &ex, const QString &fromitem )
+{
+ Job*first=mjobs[0];
+ if(first!=nullptr)
+ first->postMessage(ex,fromitem);
+}
+
+void Application::postMessage ( const QString &message, const QString &fromitem )
+{
+ Job*first=mjobs[0];
+ if(first!=nullptr)
+ first->postMessage(message,fromitem);
+}
+
+void Application::clearJobs()
+{
+ QListjobs=mjobs;
+ mjobs.clear();
+ for(Job*j:jobs)j->deleteLater();
+}
+
void Application::newTestData ( Job::JobType type, const QByteArray & data)
{
QString typestr;
@@ -194,8 +222,8 @@ Connection *Connection::netConnection ( QString tohost)
return new Connection(sock);
}
-Job::Job ( Job::JobType t, QString b)
-: QObject(), mtype(t),mbase(b),mitem(TestGroup::root().findItem(b))
+Job::Job ( Job::JobType t, QString b,TestItem::EnumerationOptions enumopt)
+: QObject(), mtype(t),menumopt(enumopt),mbase(b),mitem(TestGroup::root().findItem(b))
{
if(!mitem.isValid())deleteLater();
else sApp->addJob(this);
@@ -206,16 +234,17 @@ Job::~Job()
sApp->removeJob(this);
}
-Job *Job::testEnumeration ( QString base)
+Job *Job::testEnumeration ( QString base, bool isstate)
{
if(TestGroup::root().findItem(base).isValid())
- return new Job(Job::EnumerateTest,base);
+ return new Job(Job::EnumerateTest,base, isstate?TestItem::StatusListing:TestItem::DefaultListing);
else
return nullptr;
}
Job *Job::testExecution ( QString base)
{
+// qDebug()<<"Scheduling execution of"<clearJobs();
+ postMessage(ex,".");
+ }catch(...){
+ qDebug()<<"Ooops. Uncaught exception.";
+ }
+ }else if(mtype==EnumerateTest){
QByteArray result;
QXmlStreamWriter writer(&result);
writer.setAutoFormatting(true);
writer.writeStartDocument();
writer.writeStartElement("TestListing");
writer.writeAttribute("base",mbase);
- mitem.enumerate(writer);
+ mitem.enumerate(writer,menumopt);
writer.writeEndDocument();
emit newTestData(mtype,result);
}
@@ -242,9 +280,25 @@ void Job::execute()
deleteLater();
}
+void Job::postMessage ( const AssertException &ex, const QString &fromitem )
+{
+ //TODO: generate a message and relay to connections
+ qDebug()<<"Assertion failed. Exception caught in item"<mconnections;
- QListmjobs;
+ QListmconnections;
+ QListmjobs;
};
-//end of namespace
+//end of namespace Internal
+}
+//end of namespace Skid
}
+
+#define sApp (qobject_cast(qApp))
+
+
+
#endif
diff --git a/runnerlib/qt5/src/skid_assert.cpp b/runnerlib/qt5/src/skid_assert.cpp
new file mode 100644
index 0000000..69b27b5
--- /dev/null
+++ b/runnerlib/qt5/src/skid_assert.cpp
@@ -0,0 +1,52 @@
+//
+// assertions for SKID
+//
+//
+// Author: Konrad Rosenbaum , (C) 2014
+//
+// Copyright: See README/COPYING files that come with this distribution
+//
+//
+
+#include "skid.h"
+
+
+static inline TestItem::State result2state(Result r)
+{
+ return (TestItem::State)r;
+}
+
+Assert_::~Assert_()noexcept(false)
+{
+ if(mresult==TSNone){
+ qDebug()<<"Warning: Assertion in file"<
#include "skid_group_p.h"
+#include "rqtmain.h"
#include
#include
@@ -20,6 +21,8 @@
#include
#include
+using namespace Skid::Internal;
+
namespace Skid {
//----------------------------
@@ -58,6 +61,15 @@ TestItem::~TestItem()
d=nullptr;
}
+void TestItem::setCurrentResult(State s,QString m)
+{
+ TestItemPrivate*prv=currentItem.localData().last();
+ if(prv!=nullptr){
+ prv->setState(s);
+ if(!m.isEmpty())prv->message=m;
+ }
+}
+
bool TestItem::isGroup()const
{
return d!=nullptr && d.data()->type()==TestItemPrivate::Group;
@@ -81,12 +93,42 @@ QString TestItem::name()const
else return QString();
}
+QString TestItem::fullName()const
+{
+ if(!d)return QString();
+ if(d.data()->parent()==nullptr)return name();
+ if(name().isEmpty()||name()==".")return parent().fullName();
+ else return parent().fullName()+"."+name();
+}
+
QString TestItem::description()const
{
if(d)return d.data()->description;
return QString();
}
+void TestItem::emitMessage(const AssertException&ex)
+{
+ if(d)
+ d.data()->message=ex.message();
+ sApp->postMessage(ex,fullName());
+}
+
+void TestItem::emitMessage(const QString&msg)
+{
+ if(d)
+ d.data()->message=msg;
+ sApp->postMessage(msg,fullName());
+}
+
+QString TestItem::message()const
+{
+ if(d)
+ return d.data()->message;
+ else
+ return QString();
+}
+
TestGroup TestGroup::root()
{
static TestGroup rootgroup(".","root group");
@@ -189,6 +231,12 @@ TestItem TestItem::getChild(const QString&name)const
return TestItem(nullptr);
}
+TestItem TestItem::parent()const
+{
+ if(d.isa())return TestItem(d.data()->parent());
+ return TestItem(nullptr);
+}
+
TestCase::TestCase()
:TestGroupingItem(nullptr)
{
@@ -197,25 +245,44 @@ TestCase::TestCase()
TestCase::TestCase(QString name)
:TestGroupingItem(new TestCasePrivate(name))
{
+ TestSequencePrivate*p=d.data()->sequence.d.data();
+ d.data()->setSequence(p);
}
TestCase::TestCase(const QMetaObject*mobj,QString name)
- :TestGroupingItem(new TestCasePrivate(name,mobj))
+ :TestGroupingItem(new TestCasePrivate(name.isEmpty()?QString(mobj?mobj->className():""):name, mobj))
{
}
-TestCase& TestCase::add(const TestStepItem&)
+TestCase& TestCase::add(const TestStepItem&item)
{
- //TODO
+ if(!d.isa()){
+ qDebug()<<"Warning: trying to add items to a non-valid test case.";
+ return *this;
+ }
+ d.data()->sequence.add(item);
return *this;
}
-TestCase& TestCase::remove(const TestStepItem&)
+TestCase& TestCase::remove(const TestStepItem&item)
{
- //TODO
+ if(!d.isa()){
+ qDebug()<<"Warning: trying to remove items from a non-valid test case.";
+ return *this;
+ }
+ d.data()->sequence.remove(item);
return *this;
}
+void TestCase::setFactory(TestSequence::FactoryType fac)
+{
+ if(!d.isa()){
+ qDebug()<<"Warning: cannot set factory on a non-valid test case.";
+ return;
+ }
+ d.data()->sequence.setFactory(fac);
+}
+
TestStep::TestStep()
:TestStepItem(nullptr)
{
@@ -226,6 +293,12 @@ TestStep::TestStep(QString name)
{
}
+TestStep::TestStep ( QString name, std::function func)
+ :TestStepItem(new TestStepPrivate(name))
+{
+ d.data()->xfunction=func;
+}
+
TestSequence::TestSequence()
:TestStepItem(nullptr)
{
@@ -237,9 +310,10 @@ TestSequence::TestSequence(QString name)
}
TestSequence::TestSequence(const QMetaObject*mobj,QString name)
- :TestStepItem(new TestSequencePrivate(name))
+ :TestStepItem(new TestSequencePrivate(name.isEmpty()?QString(mobj?mobj->className():""):name))
{
if(mobj==nullptr)return;
+ d.data()->xmetaobject=mobj;
//find useable methods
QListmethods;
for(int i=0;imethodCount();i++){
@@ -256,7 +330,7 @@ TestSequence::TestSequence(const QMetaObject*mobj,QString name)
if(m.name()=="deleteLater")
continue;
//remember
- methods.append(m.methodSignature());
+ methods.append(m.name());
}
//sort them
qSort(methods);
@@ -269,10 +343,19 @@ TestSequence::TestSequence(const QMetaObject*mobj,QString name)
}
}
+void TestSequence::setFactory(FactoryType fac)
+{
+ if(!d.isa()){
+ qDebug()<<"Warning: cannot set factory on a non-valid sequence.";
+ return;
+ }
+ d.data()->xfactory=fac;
+}
+
void TestSequence::add(const TestStepItem&item)
{
if(d.isNull())return;
- d.data()->add(item.d.data());
+ d.data()->add(item.d.data());
}
void TestSequence::remove(const TestStepItem&item)
@@ -288,28 +371,22 @@ void TestSequence::remove(const TestStepItem&item)
void TestItem::execute()
{
- if(isNull())return;
+ if(isNull()){
+ qDebug()<<"Warning: trying to execute null-item!";
+ return;
+ }
if(isGroup())TestGroup(d.data()).execute();
else if(isTestCase())TestCase(d.data()).execute();
else if(isSequence())TestSequence(d.data()).execute();
else if(isStep())TestStep(d.data()).execute();
+ else qDebug()<<"Ooops. Executing impossible item type. Giving up.";
}
-TestItem::Executing::Executing ( TestItemPrivate * prv)
-{
- currentItem.localData().append(prv);
-}
-
-TestItem::Executing::~Executing()
-{
- currentItem.localData().removeLast();
-}
-
void TestGroup::execute()
{
if(!d){
- //TODO throw exception that makes the parent fail
+ throw AssertException("Invalid Test Group",Frame::Group,Result::Abort);
return;
}
Executing ex(d.data());
@@ -321,9 +398,18 @@ void TestGroup::execute()
}
if(item->type()==TestItemPrivate::Group)
TestGroup(item).execute();
- else if(item->type()==TestItemPrivate::Case)
- TestCase(item).execute();
- else {
+ //currently no try-catch at this level: the Abort is caught on the first group
+ else if(item->type()==TestItemPrivate::Case){
+ try{
+ TestCase(item).execute();
+ }catch(AssertException ex){
+ if(ex.frame()==Frame::Group){
+ d.data()->setState(ex.resultAsState());
+ emitMessage(ex);
+ }else throw ex;
+ }
+ } else {
+ qDebug()<<"Unexpected child type"<strtype()<<"in group"<setState(Failed);
//TODO set an explanation
return;
@@ -334,35 +420,59 @@ void TestGroup::execute()
void TestCase::execute()
{
if(!d){
- //TODO throw exception that makes the parent fail
+ throw AssertException("Invalid Test Case",Frame::Group,Result::Abort);
return;
}
Executing ex(d.data());
- TestSequence(d.data()->sequence).execute();
+ try{
+ TestSequence(d.data()->sequence).execute();
+ }catch(AssertException ex){
+ if(ex.frame()==Frame::TestCase || ex.frame()==Frame::Sequence){
+ d.data()->setState(ex.resultAsState());
+ emitMessage(ex);
+ }else throw ex;
+ }
}
void TestSequence::execute()
{
if(!d){
- //TODO throw exception that makes the parent fail
+ throw AssertException("Invalid Test Sequence",Frame::Sequence,Result::Abort);
return;
}
Executing ex(d.data());
TestSequencePrivate*p=d.data();
if(p->xmetaobject!=nullptr){
+// qDebug()<<"sequence has meta-object, creating object";
if(!p->xobject.isNull())delete p->xobject;
- p->xobject=p->xmetaobject->newInstance();
+ //try factory first, then meta object
+ if(p->xfactory!=nullptr)
+ p->xobject=p->xfactory();
+ else
+ p->xobject=p->xmetaobject->newInstance();
+ if(p->xobject.isNull())
+ qDebug()<<"Oops. Cannot create the object for this sequence!";
+ }
+ for(TestItem it:children()){
+ try {
+ it.execute();
+ }catch(AssertException ex){
+ if(ex.frame()==Frame::Sequence){
+ d.data()->setState(ex.resultAsState());
+ emitMessage(ex);
+ }else throw ex;
+ }
}
- for(TestItem it:children())
- it.execute();
- if(p->xmetaobject!=nullptr && !p->xobject.isNull())
+ if(p->xmetaobject!=nullptr && !p->xobject.isNull()){
+// qDebug()<<"sequence cleanup object";
delete p->xobject;
+ }
}
void TestStep::execute()
{
if(!d){
- //TODO throw exception that makes the parent fail
+ throw AssertException("Invalid Test Step",Frame::Sequence,Result::Abort);
return;
}
Executing ex(d.data());
@@ -370,39 +480,61 @@ void TestStep::execute()
//is it a method call?
if(!p->xmethod.isEmpty()){
QObject*obj=nullptr;
+// qDebug()<<"step has method";
//check whether we know the object or need to retrieve it from the sequence
if(!p->xobject.isNull())obj=p->xobject;
else {
- if(p->parent()==nullptr || p->parent()->type()!=Skid::TestItemPrivate::Sequence){
+ if(p->parent() == nullptr || p->parent()->type() != Skid::Internal::TestItemPrivate::Sequence){
d.data()->setState(Failed);
+ qDebug()<<"error step has no object and parent can't have one either";
//TODO set an explanation
return;
}
+// qDebug()<<"getting object";
obj=p->parent()->asSequence()->xobject;
}
if(obj==nullptr){
d.data()->setState(Failed);
+ qDebug()<<"error no object to execute method on";
//TODO set an explanation
return;
}
const QMetaObject *meta=obj->metaObject();
if(!meta){
d.data()->setState(Failed);
+ qDebug()<<"error have object, but no meta object";
//TODO set an explanation
return;
}
- if(!meta->invokeMethod(obj,p->xmethod.data(),Qt::DirectConnection)){
- d.data()->setState(Failed);
- //TODO set an explanation
- return;
+ try{
+ if(!meta->invokeMethod(obj,p->xmethod.data(),Qt::DirectConnection)){
+ d.data()->setState(Failed);
+ qDebug()<<"error cannot invoke method";
+ //TODO set an explanation
+ return;
+ }
+ }catch(AssertException ex){
+ if(ex.frame()==Frame::Step){
+ d.data()->setState(ex.resultAsState());
+ emitMessage(ex);
+ return;
+ }else throw ex;
}
}else
//lambda expression or function pointer
- if(p->xfunction!=nullptr)
- p->xfunction();
+ if(p->xfunction!=nullptr){
+ try{
+ p->xfunction();
+ }catch(AssertException ex){
+ if(ex.frame()==Frame::Step){
+ d.data()->setState(ex.resultAsState());
+ emitMessage(ex);
+ }else throw ex;
+ }
//not a valid step
- else {
+ } else {
+ qDebug()<<"error no functioning lambda";
d.data()->setState(Failed);
//TODO set an explanation
}
@@ -412,59 +544,82 @@ void TestStep::execute()
//-------------------------------
// TODO Enumeration
-void TestItem::enumerate(QXmlStreamWriter&writer)const
+void TestItem::enumerate(QXmlStreamWriter&writer,EnumerationOptions opt)const
{
if(isNull())
writer.writeTextElement("Null","");
else if(isGroup())
- TestGroup(d.data()).enumerate(writer);
+ TestGroup(d.data()).enumerate(writer,opt);
else if(isTestCase())
- TestCase(d.data()).enumerate(writer);
+ TestCase(d.data()).enumerate(writer,opt);
else if(isSequence())
- TestSequence(d.data()).enumerate(writer);
+ TestSequence(d.data()).enumerate(writer,opt);
else if(isStep())
- TestStep(d.data()).enumerate(writer);
+ TestStep(d.data()).enumerate(writer,opt);
}
-void TestGroup::enumerate(QXmlStreamWriter&writer)const
+void TestGroup::enumerate(QXmlStreamWriter&writer,EnumerationOptions opt)const
{
writer.writeStartElement("Group");
writer.writeAttribute("id",d.data()->name());
- writer.writeAttribute("description",d.data()->description);
- for(TestGroupingItemPrivate*itm:d.data()->children())
- TestItem(itm).enumerate(writer);
+ if(opt&ListDescription)
+ writer.writeAttribute("description",d.data()->description);
+ if(opt&ListState)
+ writer.writeAttribute("state",d.data()->stateStr());
+ if(opt&ListMessage)
+ writer.writeAttribute("message",d.data()->message);
+ if(opt&ListChildren)
+ for(TestGroupingItemPrivate*itm:d.data()->children())
+ TestItem(itm).enumerate(writer,opt);
writer.writeEndElement();
}
-void TestCase::enumerate(QXmlStreamWriter&writer)const
+void TestCase::enumerate(QXmlStreamWriter&writer,EnumerationOptions opt)const
{
writer.writeStartElement("TestCase");
writer.writeAttribute("id",d.data()->name());
- writer.writeAttribute("description",d.data()->description);
- d.data()->sequence.subenumerate(writer);
+ if(opt&ListDescription)
+ writer.writeAttribute("description",d.data()->description);
+ if(opt&ListState)
+ writer.writeAttribute("state",d.data()->stateStr());
+ if(opt&ListMessage)
+ writer.writeAttribute("message",d.data()->message);
+ if(opt&ListChildren)
+ d.data()->sequence.subenumerate(writer,opt);
writer.writeEndElement();
}
-void TestSequence::enumerate(QXmlStreamWriter&writer)const
+void TestSequence::enumerate(QXmlStreamWriter&writer,EnumerationOptions opt)const
{
writer.writeStartElement("Sequence");
writer.writeAttribute("id",d.data()->name());
- writer.writeAttribute("description",d.data()->description);
- subenumerate(writer);
+ if(opt&ListDescription)
+ writer.writeAttribute("description",d.data()->description);
+ if(opt&ListState)
+ writer.writeAttribute("state",d.data()->stateStr());
+ if(opt&ListMessage)
+ writer.writeAttribute("message",d.data()->message);
+ if(opt&ListChildren)
+ subenumerate(writer,opt);
writer.writeEndElement();
}
-void TestSequence::subenumerate(QXmlStreamWriter&writer)const
+void TestSequence::subenumerate(QXmlStreamWriter&writer,EnumerationOptions opt)const
{
for(const TestItem&it:children())
- it.enumerate(writer);
+ it.enumerate(writer,opt);
}
-void TestStep::enumerate(QXmlStreamWriter&writer)const
+void TestStep::enumerate(QXmlStreamWriter&writer,EnumerationOptions opt)const
{
writer.writeStartElement("Step");
writer.writeAttribute("id",d.data()->name());
- writer.writeAttribute("description",d.data()->description);
+ if(opt&ListDescription)
+ writer.writeAttribute("description",d.data()->description);
+ if(opt&ListState)
+ writer.writeAttribute("state",d.data()->stateStr());
+ if(opt&ListMessage)
+ writer.writeAttribute("message",d.data()->message);
writer.writeEndElement();
}
diff --git a/runnerlib/qt5/src/skid_group_p.cpp b/runnerlib/qt5/src/skid_group_p.cpp
index d0e6f03..dc9a629 100644
--- a/runnerlib/qt5/src/skid_group_p.cpp
+++ b/runnerlib/qt5/src/skid_group_p.cpp
@@ -1,5 +1,5 @@
//
-// groups, cases, steps for SKID
+// groups, cases, steps for SKID, private data classes
//
//
// Author: Konrad Rosenbaum , (C) 2014
@@ -20,14 +20,18 @@
#include
#include
-namespace Skid {
+using namespace Skid;
+using namespace Skid::Internal;
+
+namespace Skid {
-//-----------------------
-// Privates
QThreadStorage> TestItem::currentItem;
+namespace Internal {
+
+
TestGroupingItemPrivate* TestItemPrivate::asGroupingItem()
{
if(type()==Case || type()==Group)
@@ -72,11 +76,22 @@ void TestItemPrivate::removeChild(TestItemPrivate*p)
p->setParent(nullptr);
mchildren.removeAll(r);
}
+
void TestItemPrivate::addChild(TestItemPrivate*p)
{
+ //ignore nulls
+ if(p==nullptr){
+ qDebug()<<"Warning: cannot add null child!";
+ return;
+ }
ReferencePtr r(p);
+ //do not move a parent into the child list
+ if(equalsOrIsChildOf(p))
+ return;
+ //ignore if already a child
if(mchildren.contains(r))return;
mchildren.append(r);
+ //make sure the child knows who daddy is
p->setParent(this);
}
@@ -91,6 +106,39 @@ void TestSequencePrivate::remove ( TestStepItemPrivate * item)
removeChild(item);
}
+QString TestItemPrivate::strtype() const
+{
+ switch(type()){
+ case Group:return "TestGroup";
+ case Case:return "TestCase";
+ case Sequence:return "TestStepSequence";
+ case Step:return "TestStep";
+ default: return "Unknown";
+ }
+}
+
+void TestCasePrivate::setSequence ( TestSequencePrivate *p )
+{
+ if(children().size()==0 && p!=nullptr)
+ TestItemPrivate::addChild(p);
+}
+
+
+Executing::Executing ( TestItemPrivate * prv)
+{
+// qDebug()<<"start exec"<name();
+ TestItem::currentItem.localData().append(prv);
+ prv->resetState();
+}
+
+Executing::~Executing()
+{
+ TestItemPrivate*prv=TestItem::currentItem.localData().takeLast();
+ if(prv->state()==TestItem::NotRun)
+ prv->setState(TestItem::Inconclusive);
+// qDebug()<<"finished exec"<name();
+}
+
//end of namespace Skid
-};
+}}
diff --git a/runnerlib/qt5/src/skid_group_p.h b/runnerlib/qt5/src/skid_group_p.h
index e28cfaa..82c07af 100644
--- a/runnerlib/qt5/src/skid_group_p.h
+++ b/runnerlib/qt5/src/skid_group_p.h
@@ -1,5 +1,5 @@
//
-// groups, cases, steps for SKID
+// groups, cases, steps for SKID, private data classes
//
//
// Author: Konrad Rosenbaum , (C) 2014
@@ -18,16 +18,17 @@
#include
#include
-namespace Skid {
+namespace Skid { namespace Internal {
//-----------------------
// Privates
-/// \internal common data fields and some logic for all test items
+/// common data fields and some logic for all test items
struct TestItemPrivate:public Collectible
{
///create item from a name
TestItemPrivate(QString name):mname(name){}
+ ///delete the item
virtual ~TestItemPrivate()
{
// qDebug()<();}
+ ///sets a new parent for this item
bool setParent(TestItemPrivate*p)
{
+ //ignore if already done
if(mparent==p)return true;
- if(this==p)return false;
+ //reset to no-parent
+ if(p==nullptr){
+ if(mparent!=nullptr)
+ mparent.data()->removeChild(this);
+ mparent=nullptr;
+ return true;
+ }
+ //do not invert hierarchy
+ if(p->equalsOrIsChildOf(this))return false;
+ //remove from old parent
if(mparent!=nullptr)
mparent.data()->removeChild(this);
+ //set parent
mparent=p;
+ //make sure the parent pays alimony
+ if(mparent!=nullptr)
+ mparent.data()->addChild(this);
return true;
}
+ ///returns the state of this item
inline TestItem::State state()const{return mstate;}
+ ///sets the state of this item, does not set the state to a state less severe than the current state
inline void setState(const TestItem::State nstate)
{
if(parent())parent()->setState(nstate);
@@ -69,28 +92,56 @@ struct TestItemPrivate:public Collectible
if((int)nstate>(int)mstate)
mstate=nstate;
}
- inline void resetState(){mstate=TestItem::NotRun;}
+ ///resets this item to the not-yet-run state
+ inline void resetState(){mstate=TestItem::NotRun;message.clear();}
+ ///returns the state as human readable string
+ inline QString stateStr()const
+ {
+ switch(mstate){
+ case TestItem::NotRun:return "notrun";
+ case TestItem::Inconclusive:return "inconclusive";
+ case TestItem::Success:return "success";
+ case TestItem::Failed:return "failed";
+ case TestItem::Aborted:return "aborted";
+ case TestItem::Skipped:return "skipped";
+ default:return "unknown";
+ }
+ }
+ ///returns true if this item has a parent
inline bool hasParent()const{return mparent!=nullptr;}
+ ///returns true if this item is a child of the given item
inline bool isChildOf(TestItemPrivate*b)
{
if(b==nullptr || mparent==nullptr)return false;
if(mparent==b)return true;
return mparent.data()->isChildOf(b);
}
+ ///returns true if this item equals b or is a child of b
inline bool equalsOrIsChildOf(TestItemPrivate*b){return b==this || isChildOf(b);}
+ ///returns this item if it is a Group or TestCase
TestGroupingItemPrivate*asGroupingItem();
+ ///returns this item if it is a Group
TestGroupPrivate*asGroup();
+ ///returns this item if it is a TestCase
TestCasePrivate*asCase();
+ ///returns this item if it is a Step or Sequence
TestStepItemPrivate*asStepItem();
+ ///returns this item if it is a Sequence
TestSequencePrivate*asSequence();
+ ///returns this item if it is a Step
TestStepPrivate*asStep();
+ ///returns all children of this item
QListchildren()const{return mchildren;}
+ ///returns the number of children
int numChildren()const{return mchildren.size();}
+ ///removes a child
void removeChild(TestItemPrivate*);
protected:
+ ///adds a child, this should not be used directly
+ ///item types that have children should create a public add method which calls this helper
void addChild(TestItemPrivate*);
private:
TestItem::State mstate=TestItem::NotRun;
@@ -99,72 +150,113 @@ private:
WeakPtr mparent;
};
-struct TestItem::Executing
-{
- Executing()=delete;
- Executing(const Executing&)=delete;
- Executing(Executing&&)=delete;
-
- Executing(TestItemPrivate*);
- ~Executing();
-};
-
+///common base for Group and TestCase data
struct TestGroupingItemPrivate:public TestItemPrivate
{
+ ///create a new group or test case (called indirectly only)
TestGroupingItemPrivate(QString n):TestItemPrivate(n){}
};
+///test group data
struct TestGroupPrivate:public TestGroupingItemPrivate
{
TestGroupPrivate()=delete;
+ ///creates a new group
TestGroupPrivate(QString n=QString()):TestGroupingItemPrivate(n){}
+ ///add a group or test case to this group
void add(TestGroupingItemPrivate*);
+ ///remove a group or test case from this group
void remove(TestGroupingItemPrivate*);
virtual Type type()const override{return Group;}
};
+///test case data
struct TestCasePrivate:public TestGroupingItemPrivate
{
TestCasePrivate()=delete;
+ ///creates a new test case
TestCasePrivate(QString n):TestGroupingItemPrivate(n){}
+ ///creates a new test case as steps derived from a QObject based class
+ ///\see TestSequencePrivate::TestSequencePrivate
+ ///\see testCaseByClass
TestCasePrivate(QString n,const QMetaObject*m):TestGroupingItemPrivate(n),sequence(m,QString()){}
virtual Type type()const override{return Case;}
+ ///test cases contain a single hidden anonymous sequence which contains all the
+ ///sequences and steps of this test case
TestSequence sequence=TestSequence(QString());
+
+ ///\internal add the sequence to children
+ void setSequence(TestSequencePrivate*p);
};
+///common base of test step and sequence data
struct TestStepItemPrivate:public TestItemPrivate
{
+ ///creates a new step or sequence (called indirectly only)
TestStepItemPrivate(QString n):TestItemPrivate(n){}
};
+///test sequence data
struct TestSequencePrivate:public TestStepItemPrivate
{
TestSequencePrivate()=delete;
+ ///creates a new sequence
TestSequencePrivate(QString n):TestStepItemPrivate(n){}
virtual Type type()const override{return Sequence;}
+ ///if not null: the sequence consists of slots of a QObject based class
const QMetaObject*xmetaobject=nullptr;
+ ///if not null: the sequence consists of slots of a QObject based object
+ ///if xmetaobject is also not null this pointer is temporary only
QPointerxobject;
+ ///if not null: used to create the object in xobject, only valid if xmetaobject is also not null
+ Skid::TestSequence::FactoryType xfactory=nullptr;
+
+ ///adds a step or sequence to the end of this sequence
void add(TestStepItemPrivate*);
+ ///removes a step or sequence from this sequence
void remove(TestStepItemPrivate*);
};
+///test step data
struct TestStepPrivate:public TestStepItemPrivate
{
TestStepPrivate()=delete;
+ ///creates a new step
TestStepPrivate(QString n):TestStepItemPrivate(n){}
virtual Type type()const override{return Step;}
+ ///if not null and xmethod is not empty: reference to the QObject to execute the slot
QPointerxobject;
+ ///if not null: this step calls a slot of a QObject;
+ ///if xobject is also not null, the object in xobject is called, otherwise the
+ ///owning sequence of this step is asked for an object
QByteArray xmethod;
+ ///if not null and xmethod is not set: a pointer to a function (or lambda, method, etc.) to call for this step
std::functionxfunction;
};
+///helper to track execution of test items
+struct Executing
+{
+ Executing()=delete;
+ Executing(const Executing&)=delete;
+ Executing(Executing&&)=delete;
+
+ ///registers the item as currently running,
+ ///puts it on top of the running-tests stack of the current thread,
+ ///resets the run-state of the item
+ Executing(TestItemPrivate*);
+ ///finalizes the item, removes it from the stack of running tests,
+ ///if it has no run-state yet, sets it to Inconclusive
+ ~Executing();
+};
//end of namespace Skid
-};
+}}
+
#endif
diff --git a/runnerlib/qt5/src/skid_pointer.cpp b/runnerlib/qt5/src/skid_pointer.cpp
index da17c1a..9a37a11 100644
--- a/runnerlib/qt5/src/skid_pointer.cpp
+++ b/runnerlib/qt5/src/skid_pointer.cpp
@@ -13,9 +13,9 @@
#include
// #define xDebug() qDebug()<