finish grouping classes, basic assertions,
authorKonrad Rosenbaum <konrad@silmor.de>
Thu, 19 Jun 2014 19:07:39 +0000 (21:07 +0200)
committerKonrad Rosenbaum <konrad@silmor.de>
Thu, 19 Jun 2014 19:07:39 +0000 (21:07 +0200)
source docu for runner lib, start of user docu for runner lib

23 files changed:
DoxyCommon
configure
doc/index.html
doc/runner-cpp.html
examples/qt-simple/qt-simple.pro
examples/qt-simple/simplefunc.cpp [new file with mode: 0644]
examples/qt-simple/simplemeta.cpp
examples/qt-simple/simplemeta.h
runnerlib/c/README [deleted file]
runnerlib/cpp/README [deleted file]
runnerlib/qt5/Doxyfile
runnerlib/qt5/include/skid.h
runnerlib/qt5/include/skid_assert.h
runnerlib/qt5/include/skid_group.h
runnerlib/qt5/include/skid_pointer.h
runnerlib/qt5/runnerqt5.pro
runnerlib/qt5/src/rqtmain.cpp
runnerlib/qt5/src/rqtmain.h
runnerlib/qt5/src/skid_assert.cpp [new file with mode: 0644]
runnerlib/qt5/src/skid_group.cpp
runnerlib/qt5/src/skid_group_p.cpp
runnerlib/qt5/src/skid_group_p.h
runnerlib/qt5/src/skid_pointer.cpp

index 32c7577..6c28ea6 100644 (file)
@@ -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
index 33a5bdb..eea6842 100755 (executable)
--- 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
 
index acfc8ae..a7cf76a 100644 (file)
@@ -24,8 +24,7 @@ development.</p>
 <li>TODO: Using the Command Line Client</li>
 <li>TODO: Remote Execution</li>
 <li>Runner-Lib Reference:<ol>
-  <li><a href="runner-cpp.html">Qt and plain C++ Runner Library Reference</a></li>
-  <li>TODO: ANSI C</li>
+  <li><a href="runner-cpp.html">Qt based Runner Library Reference</a></li>
   <li>TODO: Tcl</li>
   </ol></li>
 <li>SKID Internals:<ol>
index 862a548..c916c32 100644 (file)
@@ -1,9 +1,14 @@
 <html>
-<title>SKID - Qt and C++ based Runner Library</title>
+<title>SKID - Qt based Runner Library</title>
+<link rel="stylesheet" type="text/css" href="style.css"/>
 <body>
-<h1>Qt and C++ based Runner Library</h1>
+<h1>Qt based Runner Library</h1>
 
-<h2>Quick Start</h2>
+<p>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.</p>
+
+<p>This tutorial will use the <tt>qt-simple</tt> example as a template.</p>
+
+<h2>Project File</h2>
 
 <p>If you are using the Qt5 based version in conjunction with QMake, you can use the following template for your project file:</p>
 
 TEMPATE = app
 TARGET = mytest
 
-#it is usually designed to be non-graphical
+#activate SKID:
+CONFIG += skid
+
+#our actual sources:
+SOURCES += mytest.cpp
+</pre>
+
+<p>Normally SKID should be installed so that your local Qt installation can make use of it, otherwise the line <tt>CONFIG+=skid</tt> can be replaced by the following:</p>
+
+<pre>
+#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
+</pre>
 
-#our actual sources:
-SOURCES += 
+<p>To get a good start, copy the <tt>qt-simple</tt> example to a new directory and clean up its project file (instructions are inside) to match your own setup.</p>
+
+<h2>Main Function</h2>
+
+<p>Other than for normal applications you do not write your own <tt>main</tt> function or Application object. SKID does this for you already, you simply include it:</p>
+
+<pre>
+#include &lt;skid.h>
+SKID_MAIN
 </pre>
 
+<h2>Simple Tests</h2>
+
+<p>TODO: explain qt-simple - make steps from functions, static methods, lamdas</p>
+
+<h3>Dynamic Tests</h3>
+
+<p>TODO: (apart from better headline) - make steps from method pointers, using bind to add parameters, QMetaObject. testCaseFromClass, ...</p>
+
+
+<h2>Distributed Tests</h2>
+
+<p>TODO: explain qt-distrib</p>
 
 </body>
 </html>
index 81f3c80..a7d0d5f 100644 (file)
@@ -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 (file)
index 0000000..d2e69ea
--- /dev/null
@@ -0,0 +1,82 @@
+//
+// example of a simple test sequence
+//
+//
+// Author: Konrad Rosenbaum <konrad@silmor.de>, (C) 2014
+//
+// Copyright: See README/COPYING files that come with this distribution
+//
+//
+
+#include <skid.h>
+#include <QDebug>
+
+
+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;
+        Assert().isEqual<QString>(arg,"fortytwo");
+}
+
+class MyTestClass
+{
+        QString mname;
+public:
+        MyTestClass(QString name):mname(name){}
+        
+        void firstMethod()
+        {
+                qDebug()<<"first method call of"<<mname;
+                Assert().isEQ(1,1);
+        }
+        
+        void secondMethod()
+        {
+                qDebug()<<"second method call of"<<mname;
+                Assert().setFailMessage("Gotcha!").isEqual(1,42);
+        }
+        
+        void thirdMethodWithArg(int arg)
+        {
+                qDebug()<<"third method call of"<<mname<<"with argument"<<arg;
+        }
+        
+        static void staticMethod()
+        {
+                qDebug()<<"static method call";
+        }
+};
+
+static MyTestClass myTestObject("my test object");
+
+static TestGroup tg=TestGroup::getGroup("function_group")
+        .add(TestCase("Static-Function-Pointers")
+                .add(TestStep("first-function",&myTestFunction1))
+                .add(TestStep("second-function",&myTestFunction2))
+                .add(TestStep("static-method",&MyTestClass::staticMethod))
+                .add(TestStep("function-with-argument",
+                              std::bind(&myTestFunction3,"Hello Argument")
+                     ))
+        )
+        .add(TestCase("Lambda")
+                .add(TestStep("first-lambda",[]{qDebug()<<"This is the first lambda";}))
+                .add(TestStep("second-lambda",[]{qDebug()<<"This is the second lambda";}))
+        )
+        .add(TestCase("Object-Method-Pointers")
+                .add(TestStep("first-method",std::bind(&MyTestClass::firstMethod,&myTestObject)))
+                .add(TestStep("second-method",std::bind(&MyTestClass::secondMethod,&myTestObject)))
+                .add(TestStep("third-method-with-args",
+                              std::bind(&MyTestClass::thirdMethodWithArg,&myTestObject,42))
+                    )
+        )
+;
\ No newline at end of file
index 98174ea..f5fda43 100644 (file)
@@ -32,8 +32,28 @@ void SimpleTestSequence::testStep2()
         qDebug()<<"SimpleTestSequence: executing step 2";
 }
 
-TestCase tc("testCase_with_MetaObject");
-TestGroup tg=TestGroup::getGroup("class_group")
-        .add(testCaseByMetaObject<SimpleTestSequence>("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<SimpleTestSequence>("testCase_from_Class"))
+        .add(TestCase(&SimpleTestMetaSequence::staticMetaObject,"metaObject-test-case"))
 ;
\ No newline at end of file
index a7e7eff..fbbfafd 100644 (file)
 
 #include <QObject>
 
-/** 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 (file)
index 2d5cac7..0000000
+++ /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 (file)
index cfd497c..0000000
+++ /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
index faa71f8..604945c 100644 (file)
@@ -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
index 08bb1e0..c79c317 100644 (file)
@@ -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);}
 
index 6a7136c..6983202 100644 (file)
 //
 //
 
+///\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 <QString>
 #include <functional>
 
 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;}
+                
+                template<class T>void 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 <typename T> inline Assert_& isEqual(T v1,T v2)
-                {return handleCompare(v1==v2);}
+                {
+                        setFMsg<T>("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 <typename T> 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 <typename T> inline Assert_& isLighter(T v1,T v2)
                 {return handleCompare(v1<v2);}
+                
+                ///compares two values, succeeds if the first one is greater than the second one,
+                ///requires a > operator for the type
                 template <typename T> 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 <typename T> 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 <typename T> inline Assert_& isGreaterOrEqual(T v1,T v2)
                 {return handleCompare(v1>=v2);}
 
+                ///alias for isEquial
                 template <typename T> inline Assert_& isEQ(T v1,T v2)
-                {return handleCompare(v1==v2);}
+                {return isEqual<T>(v1,v2);}
+                ///alias for isNotEqual
                 template <typename T> inline Assert_& isNE(T v1,T v2)
-                {return handleCompare(v1!=v2);}
+                {return isNotEqual<T>(v1,v2);}
+                ///alias for isLighter
                 template <typename T> inline Assert_& isLT(T v1,T v2)
-                {return handleCompare(v1<v2);}
+                {return isLighter<T>(v1,v2);}
+                ///alias for isGreater
                 template <typename T> inline Assert_& isGT(T v1,T v2)
-                {return handleCompare(v1>v2);}
+                {return isGreater<T>(v1,v2);}
+                ///alias for isLighterOrEqual
                 template <typename T> inline Assert_& isLE(T v1,T v2)
-                {return handleCompare(v1<=v2);}
+                {return isLighterOrEqual<T>(v1,v2);}
+                ///alias for isGreaterOrEqual
                 template <typename T> inline Assert_& isGE(T v1,T v2)
-                {return handleCompare(v1>=v2);}
+                {return isGreaterOrEqual<T>(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<void>(callback), all exceptions are counted as success
                 template <typename Ex> inline 
                 Assert_& doesThrow(std::function<void()>callback)
                 {
-                        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<void>(callback), all exceptions are caught and counted as failure
                 template <typename Ex> inline 
                 Assert_& doesNotThrow(std::function<void()>callback)
                 {
-                        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<void>(std::function<void()>callback)
 {
@@ -118,8 +422,13 @@ Assert_& Assert_::doesNotThrow<void>(std::function<void()>callback)
         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
index a6220b6..62bd13e 100644 (file)
 
 #include "skid_pointer.h"
 
+#include <functional>
+
 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<QList<TestItemPrivate*>>currentItem;
+                static QThreadStorage<QList<Internal::TestItemPrivate*>>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<void()> );
+                ///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::function<QObject*()>FactoryType;
+                ///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 <class T> 
+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 <class T> 
-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
index 3ab9c02..2c50cbe 100644 (file)
 #include <QDebug>
 
 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
         QList<ReferencePtr*>pointers;
+        ///\internal pointers back to the references, so they can be reset if necessary
         QList<WeakPtr*>weakptrs;
 
         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
                 template<class T>T*data()const{return dynamic_cast<T*>(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<class T> bool isa()const{return dynamic_cast<T*>(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
                 template<class T>T*data()const{return dynamic_cast<T*>(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<class T> operator T*()const{return dynamic_cast<T*>(ptr);}
+                ///returns true if the ReferencePtr contains a pointer compatible with T
+                template<class T> bool isa()const{return dynamic_cast<T*>(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
index 47dd2f1..77d36bd 100644 (file)
@@ -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
index af4db7a..90dbdae 100644 (file)
 #include <QUrl>
 #include <QXmlStreamWriter>
 
-#define sApp (qobject_cast<Skid::Application*>(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<<j;
                 connect(j,SIGNAL(destroyed(QObject*)),this,SLOT(removeJob(QObject*)));
-                connect(j,SIGNAL(newTestData(Job::JobType,QByteArray)),this,SLOT(newTestData(Job::JobType,QByteArray)));
+                connect(j,SIGNAL(newTestData(Internal::Job::JobType,QByteArray)),this,SLOT(newTestData(Internal::Job::JobType,QByteArray)));
         }
         QTimer::singleShot(0,this,SLOT(schedule()));
 }
@@ -108,10 +115,31 @@ void Application::schedule()
                 return;
         }
         Job*first=mjobs[0];
-        if(first->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()
+{
+        QList<Job*>jobs=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"<<base;
         if(TestGroup::root().findItem(base).isValid())
                 return new Job(Job::ExecuteTest,base);
         else
@@ -224,17 +253,26 @@ Job *Job::testExecution ( QString base)
 
 void Job::execute()
 {
+//         qDebug()<<"Running Job"<<(int)mtype<<mbase;
         mstate=Running;
-        if(mtype==ExecuteTest)
-                mitem.execute();
-        else if(mtype==EnumerateTest){
+        if(mtype==ExecuteTest){
+                try{
+                        mitem.execute();
+                }catch(AssertException ex){
+                        if(ex.frame()==Frame::AllJobs)
+                                sApp->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"<<fromitem;
+        qDebug()<<"  Origin: file"<<ex.file()<<"line"<<ex.line();
+        qDebug()<<"  Message:"<<ex.message();
+        qDebug()<<"  Result:"<<ex.resultAsString();
+}
+
+void Job::postMessage ( const QString &message, const QString &fromitem )
+{
+        //TODO: generate a message and relay to connections
+        qDebug()<<"Message from item"<<fromitem;
+        qDebug()<<"  Message:"<<message;
+}
+
 
 int Skid::skid_main(int ac,char**av)
 {
-        Skid::Application app(ac,av);
+        Skid::Internal::Application app(ac,av);
         return app.exec();
 }
index 4e82079..7278662 100644 (file)
 class QTcpSocket;
 class QLocalSocket;
 
-namespace Skid {
+namespace Skid { namespace Internal {
 
+///Used internally to represent jobs.
+///A job can be the task to execute or enumerate tests.
 class Job:public QObject
 {
         Q_OBJECT
         public:
+                ///deletes the job
                 virtual ~Job();
+                ///the kind of job represented by this instance
                 enum JobType{
+                        ///not a real job
                         Invalid,
+                        ///supposed to execute a test
                         ExecuteTest,
+                        ///supposed to enumerate the test
                         EnumerateTest
                 };
+                ///returns the job type of this job
                 JobType type()const{return mtype;}
+                ///returns the name of the test to execute/enumerate
                 QString base()const{return mbase;}
                 
-                static Job* testExecution(QString);
-                static Job* testEnumeration(QString);
+                ///returns a job that executes a test, if the test does not exist a nullptr is returned
+                ///\param base the item at which to start execution
+                static Job* testExecution(QString base);
+                ///returns a job that enumerates a test, if the test does not exist a nullptr is returned
+                ///\param base the item at which to start enumeration
+                ///\param isstate if true: list item state, if false: list meta data
+                static Job* testEnumeration(QString base,bool isstate);
                 
-                enum JobState{Pending,Running,Done};
+                ///state of the job
+                enum JobState{
+                        ///the job is scheduled, but has not begun yet
+                        Pending,
+                        ///the job is currently running
+                        Running,
+                        ///the job has completed
+                        Done
+                };
+                ///returns the current state of the job
                 JobState state()const{return mstate;}
         public slots:
+                ///runs the job
                 void execute();
+                
+                ///posts an exception as message to all connections
+                void postMessage(const AssertException&,const QString&fromitem);
+                ///posts a message to all connections
+                void postMessage(const QString&message,const QString&fromitem);
         signals:
-                void newTestData(Job::JobType,const QByteArray&);
+                ///emitted when an update about the job is available
+                void newTestData(Internal::Job::JobType,const QByteArray&);
         private:
                 JobType mtype=Invalid;
                 JobState mstate=Pending;
+                TestItem::EnumerationOptions menumopt;
                 QString mbase;
                 TestItem mitem;
-                Job(JobType,QString);
+                ///creates a new job
+                Job(JobType,QString,TestItem::EnumerationOptions enumopt=TestItem::DefaultListing);
 };
 
+///Represents a connection to an agent or controller.
 class Connection:public QObject
 {
         Q_OBJECT
         public:
+                ///tries to connect to a local server socket and returns a Connection object
+                ///if the connection succeeds
                 static Connection*localConnection(QString);
+                ///tries to connect to a remote TCP server socket and returns a Connection object
+                ///if the connection succeeds, the argument should have the format "host:port"
                 static Connection*netConnection(QString);
                 
+                ///closes the connection
                 virtual ~Connection();
         public slots:
+                /// \internal used to receive commands from the controller
                 void readData();
+                /// \internal triggers when the connection closes or needs to be terminated
                 void close();
                 
-                void newTestData(Job::JobType,const QByteArray&);
+                ///sends new data upstream
+                void newTestData(Internal::Job::JobType,const QByteArray&);
         private:
                 Connection(QLocalSocket*);
                 Connection(QTcpSocket*);
@@ -69,31 +110,55 @@ class Connection:public QObject
                 QTcpSocket*tcp=nullptr;
 };
 
+
+///Central application object of the runner.
 class Application:public QCoreApplication
 {
         Q_OBJECT
         public:
+                ///instantiates the Application object
                 Application ( int &argc, char **argv);
 
         public slots:
-                void addConnection(Connection*);
+                ///adds another connection to the runner
+                void addConnection(Internal::Connection*);
+                ///removes a connection (usually after it has been closed)
                 void removeConnection(QObject*);
                 
-                void addJob(Job*);
+                ///adds a job to the scheduler
+                void addJob(Internal::Job*);
+                ///removes a job from the scheduler (usually after completion)
                 void removeJob(QObject*);
+                ///removes all jobs
+                void clearJobs();
                 
-                void newTestData(Job::JobType,const QByteArray&);
+                ///relays test updates to all connections
+                void newTestData(Internal::Job::JobType,const QByteArray&);
+                
+                ///relays messages to current job
+                void postMessage(const AssertException&,const QString&fromitem);
+                ///relays messages to current job
+                void postMessage(const QString&message,const QString&fromitem);
                 
         private slots:
+                ///\internal scans command line arguments to create new connections or add jobs
                 void scanArguments();
                 
+                ///\internal scheduler call
                 void schedule();
         private:
-                QList<Connection*>mconnections;
-                QList<Job*>mjobs;
+                QList<Internal::Connection*>mconnections;
+                QList<Internal::Job*>mjobs;
                 
 };
 
-//end of namespace
+//end of namespace Internal
+}
+//end of namespace Skid
 }
+
+#define sApp (qobject_cast<Skid::Internal::Application*>(qApp))
+
+
+
 #endif
diff --git a/runnerlib/qt5/src/skid_assert.cpp b/runnerlib/qt5/src/skid_assert.cpp
new file mode 100644 (file)
index 0000000..69b27b5
--- /dev/null
@@ -0,0 +1,52 @@
+//
+// assertions for SKID
+//
+//
+// Author: Konrad Rosenbaum <konrad@silmor.de>, (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"<<mfile<<"line"<<mline<<"did not execute a test.";
+        }
+        if(mresult==TSTrue){
+                if(mactiftrue==Action::None)return;
+                TestItem::setCurrentResult(result2state(miftrue),mmsgiftrue);
+                if(mactiftrue==Action::Abort)
+                        throw AssertException(mline,mfile,mmsgiftrue,mfrmiftrue,miftrue);
+        }else{
+                if(mactiffalse==Action::None)return;
+                TestItem::setCurrentResult(result2state(miffalse),mmsgiffalse);
+                if(mactiffalse==Action::Abort)
+                        throw AssertException(mline,mfile,mmsgiffalse,mfrmiffalse,miffalse);
+        }
+}
+
+TestItem::State AssertException::resultAsState()const
+{
+        return result2state(mresult);
+}
+
+QString AssertException::resultAsString()const
+{
+        switch(mresult){
+                case Result::Abort:return "abort";
+                case Result::Pass:return "pass";
+                case Result::Fail:return "fail";
+                case Result::Skip:return "skip";
+                case Result::Inconclusive:return "inconclusive";
+                default: return "unknown";
+        }
+}
index 183aa28..d4c9398 100644 (file)
@@ -10,6 +10,7 @@
 
 #include <skid.h>
 #include "skid_group_p.h"
+#include "rqtmain.h"
 
 #include <QList>
 #include <QPointer>
@@ -20,6 +21,8 @@
 #include <QMetaMethod>
 #include <QDebug>
 
+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<TestItemPrivate>()->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<TestItemPrivate>()->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<TestItemPrivate>()->description;
         return QString();
 }
 
+void TestItem::emitMessage(const AssertException&ex)
+{
+        if(d)
+                d.data<TestItemPrivate>()->message=ex.message();
+        sApp->postMessage(ex,fullName());
+}
+
+void TestItem::emitMessage(const QString&msg)
+{
+        if(d)
+                d.data<TestItemPrivate>()->message=msg;
+        sApp->postMessage(msg,fullName());
+}
+
+QString TestItem::message()const
+{
+        if(d)
+                return d.data<TestItemPrivate>()->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<TestItemPrivate>())return TestItem(d.data<TestItemPrivate>()->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<TestCasePrivate>()->sequence.d.data<TestSequencePrivate>();
+        d.data<TestCasePrivate>()->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<TestCasePrivate>()){
+                qDebug()<<"Warning: trying to add items to a non-valid test case.";
+                return *this;
+        }
+        d.data<TestCasePrivate>()->sequence.add(item);
         return *this;
 }
 
-TestCase& TestCase::remove(const TestStepItem&)
+TestCase& TestCase::remove(const TestStepItem&item)
 {
-        //TODO
+        if(!d.isa<TestCasePrivate>()){
+                qDebug()<<"Warning: trying to remove items from a non-valid test case.";
+                return *this;
+        }
+        d.data<TestCasePrivate>()->sequence.remove(item);
         return *this;
 }
 
+void TestCase::setFactory(TestSequence::FactoryType fac)
+{
+        if(!d.isa<TestCasePrivate>()){
+                qDebug()<<"Warning: cannot set factory on a non-valid test case.";
+                return;
+        }
+        d.data<TestCasePrivate>()->sequence.setFactory(fac);
+}
+
 TestStep::TestStep()
         :TestStepItem(nullptr)
 {
@@ -226,6 +293,12 @@ TestStep::TestStep(QString name)
 {
 }
 
+TestStep::TestStep ( QString name, std::function<void()> func)
+        :TestStepItem(new TestStepPrivate(name))
+{
+        d.data<TestStepPrivate>()->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<TestSequencePrivate>()->xmetaobject=mobj;
         //find useable methods
         QList<QByteArray>methods;
         for(int i=0;i<mobj->methodCount();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<TestSequencePrivate>()){
+                qDebug()<<"Warning: cannot set factory on a non-valid sequence.";
+                return;
+        }
+        d.data<TestSequencePrivate>()->xfactory=fac;
+}
+
 void TestSequence::add(const TestStepItem&item)
 {
         if(d.isNull())return;
-        d.data<TestSequencePrivate>()->add(item.d.data<TestSequencePrivate>());
+        d.data<TestSequencePrivate>()->add(item.d.data<TestStepItemPrivate>());
 }
 
 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<TestGroupPrivate>()).execute();
         else if(isTestCase())TestCase(d.data<TestCasePrivate>()).execute();
         else if(isSequence())TestSequence(d.data<TestSequencePrivate>()).execute();
         else if(isStep())TestStep(d.data<TestStepPrivate>()).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<TestItemPrivate>());
@@ -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<TestGroupPrivate>()->setState(ex.resultAsState());
+                                        emitMessage(ex);
+                                }else throw ex;
+                        }
+                } else {
+                        qDebug()<<"Unexpected child type"<<item->strtype()<<"in group"<<fullName();
                         item->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<TestItemPrivate>());
-        TestSequence(d.data<TestCasePrivate>()->sequence).execute();
+        try{
+                TestSequence(d.data<TestCasePrivate>()->sequence).execute();
+        }catch(AssertException ex){
+                if(ex.frame()==Frame::TestCase || ex.frame()==Frame::Sequence){
+                        d.data<TestCasePrivate>()->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<TestItemPrivate>());
         TestSequencePrivate*p=d.data<TestSequencePrivate>();
         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<TestSequencePrivate>()->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<TestItemPrivate>());
@@ -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<TestItemPrivate>()->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<TestItemPrivate>()->setState(Failed);
+                        qDebug()<<"error no object to execute method on";
                         //TODO set an explanation
                         return;
                 }
                 const QMetaObject *meta=obj->metaObject();
                 if(!meta){
                         d.data<TestItemPrivate>()->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<TestItemPrivate>()->setState(Failed);
-                        //TODO set an explanation
-                        return;
+                try{
+                        if(!meta->invokeMethod(obj,p->xmethod.data(),Qt::DirectConnection)){
+                                d.data<TestItemPrivate>()->setState(Failed);
+                                qDebug()<<"error cannot invoke method";
+                                //TODO set an explanation
+                                return;
+                        }
+                }catch(AssertException ex){
+                        if(ex.frame()==Frame::Step){
+                                d.data<TestStepPrivate>()->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<TestStepPrivate>()->setState(ex.resultAsState());
+                                emitMessage(ex);
+                        }else throw ex;
+                }
         //not a valid step
-        else {
+        } else {
+                qDebug()<<"error no functioning lambda";
                 d.data<TestItemPrivate>()->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<TestGroupPrivate>()).enumerate(writer);
+                TestGroup(d.data<TestGroupPrivate>()).enumerate(writer,opt);
         else if(isTestCase())
-                TestCase(d.data<TestCasePrivate>()).enumerate(writer);
+                TestCase(d.data<TestCasePrivate>()).enumerate(writer,opt);
         else if(isSequence())
-                TestSequence(d.data<TestSequencePrivate>()).enumerate(writer);
+                TestSequence(d.data<TestSequencePrivate>()).enumerate(writer,opt);
         else if(isStep())
-                TestStep(d.data<TestStepItemPrivate>()).enumerate(writer);
+                TestStep(d.data<TestStepItemPrivate>()).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<TestItemPrivate>()->name());
-        writer.writeAttribute("description",d.data<TestItemPrivate>()->description);
-        for(TestGroupingItemPrivate*itm:d.data<TestItemPrivate>()->children())
-                TestItem(itm).enumerate(writer);
+        if(opt&ListDescription)
+                writer.writeAttribute("description",d.data<TestItemPrivate>()->description);
+        if(opt&ListState)
+                writer.writeAttribute("state",d.data<TestItemPrivate>()->stateStr());
+        if(opt&ListMessage)
+                writer.writeAttribute("message",d.data<TestItemPrivate>()->message);
+        if(opt&ListChildren)
+                for(TestGroupingItemPrivate*itm:d.data<TestItemPrivate>()->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<TestItemPrivate>()->name());
-        writer.writeAttribute("description",d.data<TestItemPrivate>()->description);
-        d.data<TestCasePrivate>()->sequence.subenumerate(writer);
+        if(opt&ListDescription)
+                writer.writeAttribute("description",d.data<TestItemPrivate>()->description);
+        if(opt&ListState)
+                writer.writeAttribute("state",d.data<TestItemPrivate>()->stateStr());
+        if(opt&ListMessage)
+                writer.writeAttribute("message",d.data<TestItemPrivate>()->message);
+        if(opt&ListChildren)
+                d.data<TestCasePrivate>()->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<TestItemPrivate>()->name());
-        writer.writeAttribute("description",d.data<TestItemPrivate>()->description);
-        subenumerate(writer);
+        if(opt&ListDescription)
+                writer.writeAttribute("description",d.data<TestItemPrivate>()->description);
+        if(opt&ListState)
+                writer.writeAttribute("state",d.data<TestItemPrivate>()->stateStr());
+        if(opt&ListMessage)
+                writer.writeAttribute("message",d.data<TestItemPrivate>()->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<TestItemPrivate>()->name());
-        writer.writeAttribute("description",d.data<TestItemPrivate>()->description);
+        if(opt&ListDescription)
+                writer.writeAttribute("description",d.data<TestItemPrivate>()->description);
+        if(opt&ListState)
+                writer.writeAttribute("state",d.data<TestItemPrivate>()->stateStr());
+        if(opt&ListMessage)
+                writer.writeAttribute("message",d.data<TestItemPrivate>()->message);
         writer.writeEndElement();        
 }
 
index d0e6f03..dc9a629 100644 (file)
@@ -1,5 +1,5 @@
 //
-// groups, cases, steps for SKID
+// groups, cases, steps for SKID, private data classes
 //
 //
 // Author: Konrad Rosenbaum <konrad@silmor.de>, (C) 2014
 #include <QMetaMethod>
 #include <QDebug>
 
-namespace Skid {
+using namespace Skid;
+using namespace Skid::Internal;
+
+namespace Skid { 
 
-//-----------------------
-// Privates
 
 QThreadStorage<QList<TestItemPrivate*>> 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"<<prv->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"<<prv->name();
+}
+
 
 //end of namespace Skid
-};
+}}
index e28cfaa..82c07af 100644 (file)
@@ -1,5 +1,5 @@
 //
-// groups, cases, steps for SKID
+// groups, cases, steps for SKID, private data classes
 //
 //
 // Author: Konrad Rosenbaum <konrad@silmor.de>, (C) 2014
 #include <QDebug>
 #include <QPointer>
 
-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()<<hex<<"deleting item"<<(qintptr)this;
@@ -38,25 +39,47 @@ struct TestItemPrivate:public Collectible
         enum Type {Group,Case,Sequence,Step};
         ///returns the item type
         virtual Type type()const=0;
+        ///returns a human readable type
+        QString strtype()const;
         
         ///returns the name of the item, empty string for root or anonymous items
         inline QString name()const{return mname;}
         
         ///human readable description
         QString description;
-        
+        ///failure message
+        QString message;
+    
+        ///returns the parent item of this item
         TestItemPrivate*parent()const{return mparent.data<TestItemPrivate>();}
+        ///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<TestItemPrivate>()->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<TestItemPrivate>()->removeChild(this);
+                //set parent
                 mparent=p;
+                //make sure the parent pays alimony
+                if(mparent!=nullptr)
+                        mparent.data<TestItemPrivate>()->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<TestItemPrivate>()->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
         QList<ReferencePtr>children()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
         QPointer<QObject>xobject;
+        ///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
         QPointer<QObject>xobject;
+        ///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::function<void()>xfunction;
 };
 
+///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
index da17c1a..9a37a11 100644 (file)
@@ -13,9 +13,9 @@
 #include <QDebug>
 
 // #define xDebug() qDebug()<<hex
-#define xDebug() QNoDebug()
+#define xDebug() while(false)QNoDebug()
 
-namespace Skid {
+namespace Skid {namespace Internal {
 
 Collectible::Collectible()
 {
@@ -72,8 +72,8 @@ void Collectible::attach(ReferencePtr*p)
                 pointers.append(p);
 }
 
-void ReferencePtr::cref(){xDebug()<<"create ref"<<(qintptr)this;}
-void ReferencePtr::dref(){xDebug()<<"delete ref"<<(qintptr)this;}
+// void ReferencePtr::dcref(){xDebug()<<"create ref"<<(qintptr)this;}
+// void ReferencePtr::dref(){xDebug()<<"delete ref"<<(qintptr)this;}
 
 void ReferencePtr::forget()
 {
@@ -151,4 +151,4 @@ WeakPtr& WeakPtr::operator=(Collectible*p)
 
 
 //end of namespace Skid
-};
+}}