From 78e429a5e2f29bb7db07ebdf90bd3410e83f2237 Mon Sep 17 00:00:00 2001 From: Konrad Rosenbaum Date: Thu, 19 Jun 2014 21:07:39 +0200 Subject: [PATCH] finish grouping classes, basic assertions, source docu for runner lib, start of user docu for runner lib --- DoxyCommon | 6 +- configure | 1 + doc/index.html | 3 +- doc/runner-cpp.html | 53 ++++- examples/qt-simple/qt-simple.pro | 31 ++- examples/qt-simple/simplefunc.cpp | 82 ++++++++ examples/qt-simple/simplemeta.cpp | 28 +++- examples/qt-simple/simplemeta.h | 32 +++- runnerlib/c/README | 22 -- runnerlib/cpp/README | 22 -- runnerlib/qt5/Doxyfile | 13 ++ runnerlib/qt5/include/skid.h | 11 + runnerlib/qt5/include/skid_assert.h | 369 +++++++++++++++++++++++++++++++--- runnerlib/qt5/include/skid_group.h | 247 ++++++++++++++++++----- runnerlib/qt5/include/skid_pointer.h | 120 ++++++++++- runnerlib/qt5/runnerqt5.pro | 3 +- runnerlib/qt5/src/rqtmain.cpp | 86 +++++++-- runnerlib/qt5/src/rqtmain.h | 91 +++++++-- runnerlib/qt5/src/skid_assert.cpp | 52 +++++ runnerlib/qt5/src/skid_group.cpp | 271 ++++++++++++++++++++------ runnerlib/qt5/src/skid_group_p.cpp | 58 +++++- runnerlib/qt5/src/skid_group_p.h | 126 ++++++++++-- runnerlib/qt5/src/skid_pointer.cpp | 10 +- 23 files changed, 1454 insertions(+), 283 deletions(-) create mode 100644 examples/qt-simple/simplefunc.cpp delete mode 100644 runnerlib/c/README delete mode 100644 runnerlib/cpp/README create mode 100644 runnerlib/qt5/src/skid_assert.cpp diff --git a/DoxyCommon b/DoxyCommon index 32c7577..6c28ea6 100644 --- a/DoxyCommon +++ b/DoxyCommon @@ -441,7 +441,7 @@ EXTRACT_LOCAL_METHODS = NO # are hidden. # The default value is: NO. -EXTRACT_ANON_NSPACES = NO +EXTRACT_ANON_NSPACES = YES # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these @@ -546,7 +546,7 @@ SORT_BRIEF_DOCS = NO # detailed member documentation. # The default value is: NO. -SORT_MEMBERS_CTORS_1ST = NO +SORT_MEMBERS_CTORS_1ST = YES # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy # of group names into alphabetical order. If set to NO the group names will @@ -715,7 +715,7 @@ WARN_IF_DOC_ERROR = YES # documentation, but not about the absence of documentation. # The default value is: NO. -WARN_NO_PARAMDOC = NO +WARN_NO_PARAMDOC = YES # The WARN_FORMAT tag determines the format of the warning messages that doxygen # can produce. The string should contain the $file, $line, and $text tags, which diff --git a/configure b/configure index 33a5bdb..eea6842 100755 --- a/configure +++ b/configure @@ -155,6 +155,7 @@ rm -f $PRF echo SKID_LIBDIR = $PREFIX/lib >>$PRF echo SKID_INCDIR = $PREFIX/include/skid >>$PRF echo 'CONFIG += c++11 exceptions' >>$PRF +echo 'QT -= gui' >>$PRF echo 'INCLUDEPATH += $$SKID_INCDIR' >>$PRF echo 'LIBS += -L$$SKID_LIBDIR -lrunnerqt5' >>$PRF diff --git a/doc/index.html b/doc/index.html index acfc8ae..a7cf76a 100644 --- a/doc/index.html +++ b/doc/index.html @@ -24,8 +24,7 @@ development.

  • TODO: Using the Command Line Client
  • TODO: Remote Execution
  • Runner-Lib Reference:
      -
    1. Qt and plain C++ Runner Library Reference
    2. -
    3. TODO: ANSI C
    4. +
    5. Qt based Runner Library Reference
    6. TODO: Tcl
  • SKID Internals:
      diff --git a/doc/runner-cpp.html b/doc/runner-cpp.html index 862a548..c916c32 100644 --- a/doc/runner-cpp.html +++ b/doc/runner-cpp.html @@ -1,9 +1,14 @@ -SKID - Qt and C++ based Runner Library +SKID - Qt based Runner Library + -

      Qt and C++ based Runner Library

      +

      Qt based Runner Library

      -

      Quick Start

      +

      Qt based SKID test projects are simple applications, which are build on top of the SKID runner library. You can find several examples of tests in the examples directory.

      + +

      This tutorial will use the qt-simple example as a template.

      + +

      Project File

      If you are using the Qt5 based version in conjunction with QMake, you can use the following template for your project file:

      @@ -12,7 +17,17 @@ TEMPATE = app TARGET = mytest -#it is usually designed to be non-graphical +#activate SKID: +CONFIG += skid + +#our actual sources: +SOURCES += mytest.cpp + + +

      Normally SKID should be installed so that your local Qt installation can make use of it, otherwise the line CONFIG+=skid can be replaced by the following:

      + +
      +#it is designed to be non-graphical
       QT -= gui
       
       #we will make use of modern features
      @@ -21,16 +36,36 @@ CONFIG += c++11
       CONFIG += link_prl
       
       #where to find the library (replace this with the real path):
      -TESTLIBDIR = path/to/runnerlib/qt5
      +TESTLIBDIR = path/to/skid
       
       #where to find runner headers and library:
      -INCLUDEPATH += $$TESTLIBDIR/include
      -LIBS += -lrunnerqt5 -L$$TESTLIBDIR
      +INCLUDEPATH += $$TESTLIBDIR/include/skid
      +LIBS += -L$$TESTLIBDIR/lib -lrunnerqt5
      +
      -#our actual sources: -SOURCES += +

      To get a good start, copy the qt-simple example to a new directory and clean up its project file (instructions are inside) to match your own setup.

      + +

      Main Function

      + +

      Other than for normal applications you do not write your own main function or Application object. SKID does this for you already, you simply include it:

      + +
      +#include <skid.h>
      +SKID_MAIN
       
      +

      Simple Tests

      + +

      TODO: explain qt-simple - make steps from functions, static methods, lamdas

      + +

      Dynamic Tests

      + +

      TODO: (apart from better headline) - make steps from method pointers, using bind to add parameters, QMetaObject. testCaseFromClass, ...

      + + +

      Distributed Tests

      + +

      TODO: explain qt-distrib

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