# 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
# 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
# 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
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
<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>
<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
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 <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>
#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
+
--- /dev/null
+//
+// 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
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
#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
+++ /dev/null
-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
+++ /dev/null
-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
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
//
//
+///\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);}
//
//
+///\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)
{
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
#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&&);
///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();
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;
};
+/// 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;
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
///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();
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
#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;
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
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
#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();
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"){
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));
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()));
}
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;
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);
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
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);
}
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();
}
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*);
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
--- /dev/null
+//
+// 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";
+ }
+}
#include <skid.h>
#include "skid_group_p.h"
+#include "rqtmain.h"
#include <QList>
#include <QPointer>
#include <QMetaMethod>
#include <QDebug>
+using namespace Skid::Internal;
+
namespace Skid {
//----------------------------
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;
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");
return TestItem(nullptr);
}
+TestItem TestItem::parent()const
+{
+ if(d.isa<TestItemPrivate>())return TestItem(d.data<TestItemPrivate>()->parent());
+ return TestItem(nullptr);
+}
+
TestCase::TestCase()
:TestGroupingItem(nullptr)
{
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)
{
{
}
+TestStep::TestStep ( QString name, std::function<void()> func)
+ :TestStepItem(new TestStepPrivate(name))
+{
+ d.data<TestStepPrivate>()->xfunction=func;
+}
+
TestSequence::TestSequence()
:TestStepItem(nullptr)
{
}
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++){
if(m.name()=="deleteLater")
continue;
//remember
- methods.append(m.methodSignature());
+ methods.append(m.name());
}
//sort them
qSort(methods);
}
}
+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)
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>());
}
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;
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>());
//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
}
//-------------------------------
// 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();
}
//
-// 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)
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);
}
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
-};
+}}
//
-// 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;
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);
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;
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
#include <QDebug>
// #define xDebug() qDebug()<<hex
-#define xDebug() QNoDebug()
+#define xDebug() while(false)QNoDebug()
-namespace Skid {
+namespace Skid {namespace Internal {
Collectible::Collectible()
{
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()
{
//end of namespace Skid
-};
+}}