summaryrefslogtreecommitdiffstats
path: root/kunittest
diff options
context:
space:
mode:
Diffstat (limited to 'kunittest')
-rw-r--r--kunittest/Makefile.am27
-rw-r--r--kunittest/modrunner.cpp67
-rw-r--r--kunittest/module.h120
-rw-r--r--kunittest/runner.cpp322
-rw-r--r--kunittest/runner.h219
-rw-r--r--kunittest/samplemodule.cpp40
-rw-r--r--kunittest/samplemodule.h38
-rw-r--r--kunittest/tester.cpp99
-rw-r--r--kunittest/tester.h716
9 files changed, 1648 insertions, 0 deletions
diff --git a/kunittest/Makefile.am b/kunittest/Makefile.am
new file mode 100644
index 000000000..4f8ebebc1
--- /dev/null
+++ b/kunittest/Makefile.am
@@ -0,0 +1,27 @@
+INCLUDES = $(all_includes)
+METASOURCES = AUTO
+
+lib_LTLIBRARIES = libkunittest.la
+libkunittest_la_SOURCES = runner.cpp tester.cpp
+libkunittest_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) -version-info 1:0:0
+libkunittest_la_LIBADD = $(LIB_KDECORE)
+
+libkunittestinclude_HEADERS = runner.h tester.h module.h
+libkunittestincludedir = $(includedir)/kunittest
+
+bin_PROGRAMS = kunittestmodrunner
+kunittestmodrunner_LDFLAGS = $(all_libraries) $(KDE_RPATH)
+kunittestmodrunner_LDADD = libkunittest.la $(LIB_KDECORE)
+kunittestmodrunner_SOURCES = modrunner.cpp
+
+# The check_ target makes sure we don't install the modules,
+# $(KDE_CHECK_PLUGIN) assures a shared library is created.
+check_LTLIBRARIES = kunittest_samplemodule.la
+kunittest_samplemodule_la_SOURCES = samplemodule.cpp
+kunittest_samplemodule_la_LIBADD = libkunittest.la
+kunittest_samplemodule_la_LDFLAGS = -module $(KDE_CHECK_PLUGIN) $(all_libraries)
+
+check-local:
+ ./kunittestmodrunner
+
+include ../admin/Doxyfile.am
diff --git a/kunittest/modrunner.cpp b/kunittest/modrunner.cpp
new file mode 100644
index 000000000..cb633189d
--- /dev/null
+++ b/kunittest/modrunner.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2005 Jeroen Wijnhout <Jeroen.Wijnhout@kdemail.net>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <kdebug.h>
+#include <kglobal.h>
+#include <kinstance.h>
+#include <kaboutdata.h>
+#include <kcmdlineargs.h>
+#include <klocale.h>
+
+#include "runner.h"
+
+static const char description[] =
+ I18N_NOOP("A command-line application that can be used to run KUnitTest modules.");
+
+static const char version[] = "0.1";
+
+static KCmdLineOptions options[] =
+{
+ {"query [regexp]", I18N_NOOP("Only run modules whose filenames match the regexp."), "^kunittest_.*\\.la$"},
+ {"folder [folder]", I18N_NOOP("Only run tests modules which are found in the folder. Use the query option to select modules."), "."},
+ { "enable-dbgcap", I18N_NOOP("Disables debug capturing. You typically use this option when you use the GUI."), 0},
+ KCmdLineLastOption
+};
+
+
+int main( int argc, char **argv )
+{
+ KInstance instance("modrunner");
+
+ KAboutData about("KUnitTest Module Runner", I18N_NOOP("KUnitTest ModRunner"), version, description,
+ KAboutData::License_BSD, "(C) 2005 Jeroen Wijnhout", 0, 0,
+ "Jeroen.Wijnhout@kdemail.net");
+
+ KCmdLineArgs::init(argc, argv, &about);
+ KCmdLineArgs::addCmdLineOptions( options );
+ KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
+
+ KUnitTest::Runner::loadModules(args->getOption("folder"), args->getOption("query"));
+ KUnitTest::Runner::setDebugCapturingEnabled(args->isSet("enable-dbgcap"));
+
+ KUnitTest::Runner::self()->runTests();
+
+ return KUnitTest::Runner::self()->numberOfFailedTests() - KUnitTest::Runner::self()->numberOfExpectedFailures();
+}
diff --git a/kunittest/module.h b/kunittest/module.h
new file mode 100644
index 000000000..e33887101
--- /dev/null
+++ b/kunittest/module.h
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2005 Jeroen Wijnhout <Jeroen.Wijnhout@kdemail.net>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*!
+ * @file module.h
+ * Provides macros to ease building unit tests as shared libraries
+ */
+
+#ifndef KUNITTEST_MODULE_H
+#define KUNITTEST_MODULE_H
+
+#include <qstring.h>
+
+#include <klibloader.h>
+#include <kunittest/runner.h>
+
+namespace KUnitTest
+{
+ /*! @def KUNITTEST_MODULE(library,suite)
+ * Use this macro if you are creating a KUnitTest module named library.
+ * This macro creates a module-class named a factory class. The module
+ * will appear under the name suite in the test runner.
+ * There is no need in calling the K_EXPORT_COMPONENT_FACTORY macro,
+ * this is taken care of automatically.
+ *
+ * @code KUNITTEST_MODULE(kunittest_samplemodule,"TestSuite") @endcode
+ */
+ #define KUNITTEST_MODULE(library,suite) \
+ static const QString s_kunittest_suite = QString::fromLatin1(suite); \
+ class library##Module : public QObject \
+ { \
+ public: \
+ library##Module() \
+ { \
+ KUnitTest::RegistryIteratorType it(s_registry); \
+ for( ; it.current(); ++it ) \
+ KUnitTest::Runner::registerTester(it.currentKey(), it.current()); \
+ } \
+ \
+ static KUnitTest::RegistryType s_registry; \
+ }; \
+ \
+ KUnitTest::RegistryType library##Module::s_registry; \
+ \
+ void kunittest_registerModuleTester(const char *name, KUnitTest::Tester *test) \
+ { \
+ library##Module::s_registry.insert(name, test); \
+ } \
+ \
+ class module##Factory : public KLibFactory \
+ { \
+ public: \
+ QObject *createObject (QObject *, const char *, const char *, const QStringList &) \
+ { \
+ return new library##Module(); \
+ }; \
+ }; \
+ \
+ K_EXPORT_COMPONENT_FACTORY( library, module##Factory )
+
+ /*! @def KUNITTEST_MODULE_REGISTER_TESTER(tester)
+ * Use this macro to add a tester class to your module. The name of the tester will
+ * be identical to the class name.
+ *
+ * @code KUNITTEST_MODULE_REGISTER_TESTER(SimpleSampleTester) @endcode
+ */
+ #define KUNITTEST_MODULE_REGISTER_TESTER( tester) \
+ static class tester##ModuleAutoregister \
+ { \
+ public: \
+ tester##ModuleAutoregister() \
+ { \
+ KUnitTest::Tester *test = new tester(); \
+ QString name = s_kunittest_suite + QString::fromLatin1("::") + QString::fromLocal8Bit(#tester); \
+ test->setName(name.local8Bit()); \
+ kunittest_registerModuleTester(name.local8Bit(), test ); \
+ } \
+ } tester##ModuleAutoregisterInstance;
+
+ /*! @def KUNITTEST_MODULE_REGISTER_NAMEDTESTER(name,tester)
+ * Use this macro to add a tester class, with specified name, to your module..
+ *
+ * @code KUNITTEST_MODULE_REGISTER_TESTER("SubSuite::PrettyName",SimpleSampleTester) @endcode
+ */
+ #define KUNITTEST_MODULE_REGISTER_NAMEDTESTER( name , tester) \
+ static class tester##ModuleAutoregister \
+ { \
+ public: \
+ tester##ModuleAutoregister() \
+ { \
+ QString fullName = s_kunittest_suite + QString("::") + QString::fromLocal8Bit(name); \
+ KUnitTest::Tester *test = new tester(fullName.local8Bit()); \
+ kunittest_registerModuleTester(fullName.local8Bit(), test); \
+ } \
+ } tester##ModuleAutoregisterInstance;
+}
+
+#endif
diff --git a/kunittest/runner.cpp b/kunittest/runner.cpp
new file mode 100644
index 000000000..776e01cda
--- /dev/null
+++ b/kunittest/runner.cpp
@@ -0,0 +1,322 @@
+/*
+ *
+ * Copyright (C) 2004 Zack Rusin <zack@kde.org>
+ * Copyright (C) 2005 Jeroen Wijnhout <Jeroen.Wijnhout@kdemail.net>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <iostream>
+using namespace std;
+
+#include <qregexp.h>
+#include <qdir.h>
+#include <qmetaobject.h>
+
+#include <kdebug.h>
+#include <klibloader.h>
+#include <kglobal.h>
+#include <kstandarddirs.h>
+
+#include "runner.h"
+#include "tester.h"
+
+namespace KUnitTest
+{
+ Runner *Runner::s_self = 0L;
+ bool Runner::s_debugCapturingEnabled = false;
+
+ void Runner::registerTester(const char *name, Tester *test)
+ {
+ Runner::self()->m_registry.insert(name, test);
+ }
+
+ void Runner::loadModules(const QString &folder, const QString &query)
+ {
+ QRegExp reQuery(query);
+ QDir dir(folder, "kunittest_*.la");
+
+ // Add the folder to the "module" resource such that the KLibLoader can
+ // find the modules in this folder.
+ KGlobal::dirs()->addResourceDir("module", folder);
+ kdDebug() << "Looking in folder: " << dir.absPath() << endl;
+
+ // Get a list of all modules.
+ QStringList modules = dir.entryList();
+
+ for ( uint i = 0; i < modules.count(); ++i )
+ {
+ QString module = modules[i];
+ kdDebug() << "Module: " << dir.absPath() + "/" + module << endl;
+
+ if ( reQuery.search(module) != -1 )
+ {
+ // strip the .la extension
+ module.truncate(module.length()-3);
+ KLibFactory *factory = KLibLoader::self()->factory(module.local8Bit());
+ if ( factory )
+ factory->create();
+ else {
+ kdWarning() << "\tError loading " << module << " : " << KLibLoader::self()->lastErrorMessage() << endl;
+ ::exit( 1 );
+ }
+ }
+ else
+ kdDebug() << "\tModule doesn't match." << endl;
+ }
+ }
+
+ void Runner::setDebugCapturingEnabled(bool enabled)
+ {
+ s_debugCapturingEnabled = enabled;
+ }
+
+ RegistryType &Runner::registry()
+ {
+ return m_registry;
+ }
+
+ int Runner::numberOfTestCases()
+ {
+ return m_registry.count();
+ }
+
+ Runner *Runner::self()
+ {
+ if ( s_self == 0L ) s_self = new Runner();
+
+ return s_self;
+ }
+
+ Runner::Runner()
+ {
+ reset();
+ }
+
+ int Runner::numberOfTests() const
+ {
+ return globalSteps;
+ }
+
+ int Runner::numberOfPassedTests() const
+ {
+ return globalPasses;
+ }
+
+ int Runner::numberOfFailedTests() const
+ {
+ return globalFails;
+ }
+
+ int Runner::numberOfExpectedFailures() const
+ {
+ return globalXFails;
+ }
+
+ int Runner::numberOfSkippedTests() const
+ {
+ return globalSkipped;
+ }
+
+ void Runner::reset()
+ {
+ globalSteps = 0;
+ globalPasses = 0;
+ globalFails = 0;
+ globalXFails = 0;
+ globalXPasses = 0;
+ globalSkipped = 0;
+ }
+
+ int Runner::runTests()
+ {
+ globalSteps = 0;
+ globalPasses = 0;
+ globalFails = 0;
+ globalXFails = 0;
+ globalXPasses = 0;
+ globalSkipped = 0;
+
+ cout << "# Running normal tests... #" << endl << endl;
+ RegistryIteratorType it(m_registry);
+
+ for( ; it.current(); ++it )
+ runTest(it.currentKey());
+
+#if 0 // very thorough, but not very readable
+ cout << "# Done with normal tests:" << endl;
+ cout << " Total test cases: " << m_registry.count() << endl;
+ cout << " Total test steps : " << globalSteps << endl;
+ cout << " Total passed test steps (including unexpected) : " << globalPasses << endl;
+ cout << " Total unexpected passed test steps : " << globalXPasses << endl;
+ cout << " Total failed test steps (including expected) : " << globalFails << endl;
+ cout << " Total expected failed test steps : " << globalXFails << endl;
+ cout << " Total skipped test steps : " << globalSkipped << endl;
+#else
+ unsigned int numTests = m_registry.count(); // should this be globalSteps instead?
+ QString str;
+ if ( globalFails == 0 )
+ if ( globalXFails == 0 )
+ str = QString( "All %1 tests passed" ).arg( numTests );
+ else
+ str = QString( "All %1 tests behaved as expected (%2 expected failures)" ).arg( numTests ).arg( globalXFails );
+ else
+ if ( globalXPasses == 0 )
+ str = QString( "%1 of %2 tests failed" ).arg( globalFails ).arg( numTests );
+ else
+ str = QString( "%1 of %2 tests did not behave as expected (%1 unexpected passes)" ).arg( globalFails ).arg( numTests ).arg( globalXPasses );
+ if ( globalSkipped )
+ str += QString( " (%1 tests skipped)" ).arg( globalSkipped );
+ cout << str.local8Bit() << endl;
+#endif
+
+ return m_registry.count();
+ }
+
+ void Runner::runMatchingTests(const QString &prefix)
+ {
+ RegistryIteratorType it(m_registry);
+ for( ; it.current(); ++it )
+ if ( QString(it.currentKey()).startsWith(prefix) )
+ runTest(it.currentKey());
+ }
+
+ void Runner::runTest(const char *name)
+ {
+ Tester *test = m_registry.find(name);
+ if ( test == 0L ) return;
+
+ if ( s_debugCapturingEnabled )
+ {
+ cout << "KUnitTest_Debug_Start[" << name << "]" << endl;
+ }
+
+ test->results()->clear();
+ test->allTests();
+
+ if ( s_debugCapturingEnabled )
+ {
+ cout << "KUnitTest_Debug_End[" << name << "]" << endl << endl << flush;
+ }
+
+ int numPass = 0;
+ int numFail = 0;
+ int numXFail = 0;
+ int numXPass = 0;
+ int numSkip = 0;
+ QStringList xpassList;
+ QStringList errorList;
+ QStringList xfailList;
+ QStringList skipList;
+
+ if ( test->inherits("KUnitTest::SlotTester") )
+ {
+ SlotTester *sltest = static_cast<SlotTester*>(test);
+ TestResultsListIteratorType it(sltest->resultsList());
+ for ( ; it.current(); ++it)
+ {
+ numPass += it.current()->passed() + it.current()->xpasses();
+ numFail += it.current()->errors() + it.current()->xfails();
+ numXFail += it.current()->xfails();
+ numXPass += it.current()->xpasses();
+ numSkip += it.current()->skipped();
+ globalSteps += it.current()->testsFinished();
+
+ xpassList += it.current()->xpassList();
+ errorList += it.current()->errorList();
+ xfailList += it.current()->xfailList();
+ skipList += it.current()->skipList();
+ }
+ }
+ else
+ {
+ numPass= test->results()->passed() + test->results()->xpasses();
+ numFail= test->results()->errors() + test->results()->xfails();
+ numXFail = test->results()->xfails();
+ numXPass = test->results()->xpasses();
+ numSkip= test->results()->skipped();
+ globalSteps += test->results()->testsFinished();
+
+ xpassList += test->results()->xpassList();
+ errorList += test->results()->errorList();
+ xfailList += test->results()->xfailList();
+ skipList += test->results()->skipList();
+ }
+
+
+ globalPasses += numPass;
+ globalFails += numFail;
+ globalXFails += numXFail;
+ globalXPasses += numXPass;
+ globalSkipped += numSkip;
+
+ cout << name << " - ";
+ cout << numPass << " test" << ( ( 1 == numPass )?"":"s") << " passed";
+ if ( 0 < xpassList.count() ) {
+ cout << " (" << numXPass << " unexpected pass" << ( ( 1 == numXPass )?"":"es") << ")";
+ }
+ cout << ", " << numFail << " test" << ( ( 1 == numFail )?"":"s") << " failed";
+ if ( 0 < numXFail ) {
+ cout << " (" << numXFail << " expected failure" << ( ( 1 == numXFail )?"":"s") << ")";
+ }
+ if ( 0 < numSkip ) {
+ cout << "; also " << numSkip << " skipped";
+ }
+ cout << endl;
+
+ if ( 0 < numXPass ) {
+ cout << " Unexpected pass" << ( ( 1 == numXPass )?"":"es") << ":" << endl;
+ QStringList list = xpassList;
+ for ( QStringList::Iterator itr = list.begin(); itr != list.end(); ++itr ) {
+ cout << "\t" << (*itr).latin1() << endl;
+ }
+ }
+ if ( 0 < (numFail - numXFail) ) {
+ cout << " Unexpected failure" << ( ( 1 == numFail )?"":"s") << ":" << endl;
+ QStringList list = errorList;
+ for ( QStringList::Iterator itr = list.begin(); itr != list.end(); ++itr ) {
+ cout << "\t" << (*itr).latin1() << endl;
+ }
+ }
+ if ( 0 < numXFail ) {
+ cout << " Expected failure" << ( ( 1 == numXFail)?"":"s") << ":" << endl;
+ QStringList list = xfailList;
+ for ( QStringList::Iterator itr = list.begin(); itr != list.end(); ++itr ) {
+ cout << "\t" << (*itr).latin1() << endl;
+ }
+ }
+ if ( 0 < numSkip ) {
+ cout << " Skipped test" << ( ( 1 == numSkip )?"":"s") << ":" << endl;
+ QStringList list = skipList;
+ for ( QStringList::Iterator itr = list.begin(); itr != list.end(); ++itr ) {
+ cout << "\t" << (*itr).latin1() << endl;
+ }
+ }
+ cout << endl;
+
+ emit finished(name, test);
+ }
+}
+
+#include "runner.moc"
+
diff --git a/kunittest/runner.h b/kunittest/runner.h
new file mode 100644
index 000000000..be1b056c8
--- /dev/null
+++ b/kunittest/runner.h
@@ -0,0 +1,219 @@
+/*
+ * kunittest.h
+ *
+ * Copyright (C) 2004 Zack Rusin <zack@kde.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*!
+ * @file runner.h
+ * Defines a set of macros and classes for running unit tests
+ */
+
+#ifndef KUNITTEST_RUNNER_H
+#define KUNITTEST_RUNNER_H
+
+#include <iostream>
+using namespace std;
+
+#include <qobject.h>
+#include <qasciidict.h>
+#include <qstring.h>
+
+#include <kdelibs_export.h>
+
+#include "tester.h"
+
+class QSocketNotifier;
+
+namespace KUnitTest
+{
+ /*! @def KUNITTEST_SUITE(suite)
+ *
+ * This macro must be used if you are not making a test-module. The macro
+ * defines the name of the test suite.
+ */
+ #define KUNITTEST_SUITE(suite)\
+ static const QString s_kunittest_suite = suite;
+
+ /*! @def KUNITTEST_REGISTER_TESTER( tester )
+ * @brief Automatic registration of Tester classes.
+ *
+ * This macro can be used to register the Tester into the global registry. Use
+ * this macro in the implementation file of your Tester class. If you keep the
+ * Tester classes in a shared or convenience library then you should not use this
+ * macro as this macro relies on the static initialization of a TesterAutoregister class.
+ * You can always use the static Runner::registerTester(const char *name, Tester *test) method.
+ */
+ #define KUNITTEST_REGISTER_TESTER( tester )\
+ static TesterAutoregister tester##Autoregister( QString(s_kunittest_suite + QString("::") + QString::fromLocal8Bit(#tester)).local8Bit() , new tester ())
+
+ #define KUNITTEST_REGISTER_NAMEDTESTER( name, tester )\
+ static TesterAutoregister tester##Autoregister( QString(s_kunittest_suite + QString("::") + QString::fromLocal8Bit(name)).local8Bit() , new tester ())
+
+ /*! The type of the registry. */
+ typedef QAsciiDict<Tester> RegistryType;
+
+ /*! A type that can be used to iterate through the registry. */
+ typedef QAsciiDictIterator<Tester> RegistryIteratorType;
+
+ /*! The Runner class holds a list of registered Tester classes and is able
+ * to run those test cases. The Runner class follows the singleton design
+ * pattern, which means that you can only have one Runner instance. This
+ * instance can be retrieved using the Runner::self() method.
+ *
+ * The registry is an object of type RegistryType, it is able to map the name
+ * of a test to a pointer to a Tester object. The registry is also a singleton
+ * and can be accessed via Runner::registry(). Since there is only one registry,
+ * which can be accessed at all times, test cases can be added without having to
+ * worry if a Runner instance is present or not. This allows for a design in which
+ * the KUnitTest library can be kept separate from the test case sources. Test cases
+ * (classes inheriting from Tester) can be added using the static
+ * registerTester(const char *name, Tester *test) method. Allthough most users
+ * will want to use the KUNITTEST_REGISTER_TESTER macro.
+ *
+ * @see KUNITTEST_REGISTER_TESTER
+ */
+ class KUNITTEST_EXPORT Runner : public QObject
+ {
+ Q_OBJECT
+
+ public:
+ /*! Registers a test case. A registry will be automatically created if necessary.
+ * @param name The name of the test case.
+ * @param test A pointer to a Tester object.
+ */
+ static void registerTester(const char *name, Tester *test);
+
+ /*! @returns The registry holding all the Tester objects.
+ */
+ RegistryType &registry();
+
+ /*! @returns The global Runner instance. If necessary an instance will be created.
+ */
+ static Runner *self();
+
+ /*! @returns The number of registered test cases.
+ */
+ int numberOfTestCases();
+
+ /*! Load all modules found in the folder.
+ * @param folder The folder where to look for modules.
+ * @param query A regular expression. Only modules which match the query will be run.
+ */
+ static void loadModules(const QString &folder, const QString &query);
+
+ /*! The runner can spit out special debug messages needed by the Perl script: kunittest_debughelper.
+ * This script can attach the debug output of each suite to the results in the KUnitTest GUI.
+ * Not very useful for console minded developers, so this static method can be used to disable
+ * those debug messages.
+ * @param enabled If true the debug messages are enabled (default), otherwise they are disabled.
+ */
+ static void setDebugCapturingEnabled(bool enabled);
+
+ private:
+ RegistryType m_registry;
+ static Runner *s_self;
+ static bool s_debugCapturingEnabled;
+
+ protected:
+ Runner();
+
+ public:
+ /*! @returns The number of finished tests. */
+ int numberOfTests() const;
+
+ /*! @returns The number of passed tests. */
+ int numberOfPassedTests() const;
+
+ /*! @returns The number of failed tests, this includes the number of expected failures. */
+ int numberOfFailedTests() const;
+
+ /*! @returns The number of failed tests which were expected. */
+ int numberOfExpectedFailures() const;
+
+ /*! @returns The number of skipped tests. */
+ int numberOfSkippedTests() const;
+
+ public slots:
+ /*! Call this slot to run all the registered tests.
+ * @returns The number of finished tests.
+ */
+ int runTests();
+
+ /*! Call this slot to run a single test.
+ * @param name The name of the test case. This name has to correspond to the name
+ * that was used to register the test. If the KUNITTEST_REGISTER_TESTER macro was
+ * used to register the test case then this name is the class name.
+ */
+ void runTest(const char *name);
+
+ /*! Call this slot to run tests with names starting with prefix.
+ * @param prefix Only run tests starting with the string prefix.
+ */
+ void runMatchingTests(const QString &prefix);
+
+ /*! Reset the Runner in order to prepare it to run one or more tests again.
+ */
+ void reset();
+
+ signals:
+ /*! Emitted after a test is finished.
+ * @param name The name of the test.
+ * @param test A pointer to the Tester object.
+ */
+ void finished(const char *name, Tester *test);
+ void invoke();
+
+ private:
+ void registerTests();
+
+ private:
+ int globalSteps;
+ int globalTests;
+ int globalPasses;
+ int globalFails;
+ int globalXFails;
+ int globalXPasses;
+ int globalSkipped;
+ };
+
+ /*! The TesterAutoregister is a helper class to allow the automatic registration
+ * of Tester classes.
+ */
+ class TesterAutoregister
+ {
+ public:
+ /*! @param name A unique name that identifies the Tester class.
+ * @param test A pointer to a Tester object.
+ */
+ TesterAutoregister(const char *name, Tester *test)
+ {
+ if ( test->name() == 0L ) test->setName(name);
+ Runner::registerTester(name, test);
+ }
+ };
+
+}
+
+#endif
diff --git a/kunittest/samplemodule.cpp b/kunittest/samplemodule.cpp
new file mode 100644
index 000000000..390102c38
--- /dev/null
+++ b/kunittest/samplemodule.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2005 Jeroen Wijnhout <Jeroen.Wijnhout@kdemail.net>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <kunittest/runner.h>
+#include <kunittest/module.h>
+
+#include "samplemodule.h"
+
+using namespace KUnitTest;
+
+KUNITTEST_MODULE( kunittest_samplemodule, "Suite1" );
+KUNITTEST_MODULE_REGISTER_TESTER( SampleTester );
+
+void SampleTester::allTests()
+{
+ int i = 2;
+ CHECK( i + 2, 4 );
+}
diff --git a/kunittest/samplemodule.h b/kunittest/samplemodule.h
new file mode 100644
index 000000000..b9def68fd
--- /dev/null
+++ b/kunittest/samplemodule.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2005 Jeroen Wijnhout <Jeroen.Wijnhout@kdemail.net>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SAMPLETESTMODULE_H
+#define SAMPLETESTMODULE_H
+
+#include "tester.h"
+// Outside of kdelibs you would use: #include <kunittest/tester.h>
+
+class SampleTester : public KUnitTest::Tester
+{
+public:
+ void allTests();
+};
+
+#endif
diff --git a/kunittest/tester.cpp b/kunittest/tester.cpp
new file mode 100644
index 000000000..93509716d
--- /dev/null
+++ b/kunittest/tester.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2005 Jeroen Wijnhout <Jeroen.Wijnhout@kdemail.net>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <iostream>
+using namespace std;
+
+#include <qmetaobject.h>
+
+#include "tester.h"
+
+namespace KUnitTest
+{
+ SlotTester::SlotTester(const char *name) : Tester(name)
+ {
+ m_resultsList.setAutoDelete(true);
+ m_total = m_results;
+ }
+
+ void SlotTester::invokeMember(const QString &str)
+ {
+ QString slotname = QString::number(QSLOT_CODE) + str;
+ connect(this, SIGNAL(invoke()), this, slotname.ascii());
+ emit invoke();
+ disconnect(this, SIGNAL(invoke()), this, slotname.ascii());
+ }
+
+ void SlotTester::allTests()
+ {
+ QStrList allSlots = metaObject()->slotNames();
+
+ if ( allSlots.contains("setUp()") > 0 ) invokeMember("setUp()");
+
+ for ( char *sl = allSlots.first(); sl; sl = allSlots.next() )
+ {
+ QString str = sl;
+
+ if ( str.startsWith("test") )
+ {
+ m_results = results(sl);
+ m_results->clear();
+
+ cout << "KUnitTest_Debug_BeginSlot[" << sl << "]" << endl;
+ invokeMember(str);
+ cout << "KUnitTest_Debug_EndSlot[" << sl << "]" << endl;
+ }
+ }
+
+ if ( allSlots.contains("tearDown()") > 0 ) invokeMember("tearDown()");
+
+ m_total->clear();
+ }
+
+ TestResults *SlotTester::results(const char *sl)
+ {
+ if ( m_resultsList.find(sl) == 0L ) m_resultsList.insert(sl, new TestResults());
+
+ return m_resultsList[sl];
+ }
+}
+
+QTextStream& operator<<( QTextStream& str, const QRect& r ) {
+ str << "[" << r.x() << "," << r.y() << " - " << r.width() << "x" << r.height() << "]";
+ return str;
+}
+
+QTextStream& operator<<( QTextStream& str, const QPoint& r ) {
+ str << "(" << r.x() << "," << r.y() << ")";
+ return str;
+}
+
+QTextStream& operator<<( QTextStream& str, const QSize& r ) {
+ str << "[" << r.width() << "x" << r.height() << "]";
+ return str;
+}
+
+#include "tester.moc"
+
diff --git a/kunittest/tester.h b/kunittest/tester.h
new file mode 100644
index 000000000..f54b85982
--- /dev/null
+++ b/kunittest/tester.h
@@ -0,0 +1,716 @@
+/*
+ * tester.h
+ *
+ * Copyright (C) 2004 Zack Rusin <zack@kde.org>
+ * Copyright (C) 2005 Jeroen Wijnhout <Jeroen.Wijnhout@kdemail.net>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TESTER_H
+#define TESTER_H
+
+/*! @mainpage KUnitTest - a UnitTest library for KDE
+ *
+ * @section contents Contents
+ * @li @ref background
+ * @li @ref usage
+ * @li @ref integration
+ * @li @ref module
+ * @li @ref advanced
+ * @li @ref scripts
+ *
+ * @section background Background
+ *
+ * KUnitTest is based on the "in reality no one wants to write tests and
+ * if it takes a lot of code no one will. So the less code to write the
+ * better" design principle.
+ *
+ * Copyright and credits:
+ * @li (C) 2004 Zack Rusin (original author)
+ * @li Brad Hards (import into CVS)
+ * @li (C) 2005 Jeroen Wijnhout (GUI, library, module)
+ *
+ * You are responsible for what you do with it though. It
+ * is licensed under a BSD license - read the top of each file.
+ *
+ * All the GUI related stuff is in kdesdk/kunittest, the core libraries are in kdelibs/kunittest.
+ * A simple example modules is in kdelisbs/kunittest/samplemodule.{h,cpp}, however more examples
+ * can be found in kdesdk/kunittest/example.
+ *
+ * There are roughly two ways to use the KUnitTest library. Either you create dynamically
+ * loadable modules and use the kunittestmodrunner or kunittestguimodrunner programs to run
+ * the tests, or you use the kunittest/kunittestgui library to create your own test runner
+ * application.
+ *
+ * The main parts of the KUnitTest library are:
+ * @li runner.{h,cpp} - it is the tester runner, holds all tests and runs
+ * them.
+ * @li runnergui.{h,cpp} - the GUI wrapper around the runner. The GUI neatly organizes the
+ * test results. With the kunittest helper script it can even add the debug output
+ * to the test results. For this you need to have the kdesdk module installed.
+ * @li tester.h - which holds the base of a pure test object (Tester).
+ * @li module.h - defines macros to create a dynamically loadable module.
+ *
+ * @section usage Example usage
+ *
+ * This section describes how to use the library to create your own tests and runner
+ * application.
+ *
+ * Now lets see how you would add a new test to KUnitTest. You do that by
+ * writting a Tester derived class which has an "allTests()" method:
+ *
+ * @code
+ * class SampleTest : public Tester
+ * {
+ * public:
+ * SampleTest();
+ *
+ * void allTests();
+ * };
+ * @endcode
+ *
+ * Now in the allTests() method we implement our tests, which might look
+ * like:
+ *
+ * @code
+ * void SampleTest::allTests()
+ * {
+ * CHECK( 3+3, 6 );
+ * CHECK( QString( "hello%1" ).arg( " world not" ), QString( "hello world" ) );
+ * }
+ * @endcode
+ *
+ * CHECK() is implemented using a template, so you get type safe
+ * comparison. All that is needed is that the argument types have an
+ * @c operator==() defined.
+ *
+ * Now that you did that the only other thing to do is to tell the
+ * framework to add this test case, by using the KUNITTEST_REGISTER_TESTER(x) macro. Just
+ * put the following line in the implementation file:
+ *
+ * @code KUNITTEST_REGISTER_TESTER( SampleTest ); @endcode
+ *
+ * Note the ;, it is necessary.
+ *
+ * KUnitTest will do the rest. It will tell you which tests failed, how, what was the expected
+ * result, what was the result it got, what was the code that failed and so on. For example for
+ * the code above it would output:
+ *
+ * @verbatim
+SampleTest - 1 test passed, 1 test failed
+ Unexpected failure:
+ sampletest.cpp[38]: failed on "QString( "hello%1" ).arg( " world not" )"
+ result = 'hello world not', expected = 'hello world'
+@endverbatim
+ *
+ * If you use the RunnerGUI class then you will be presented with a scrollable list of the
+ * test results.
+ *
+ * @section integration Integration
+ *
+ * The KUnitTest library is easy to use. Let's say that you have the tests written in the
+ * sampletest.h and sampletest.cpp files. Then all you need is a main.cpp file and a Makefile.am.
+ * You can copy both from the example file provided with the library. A typical main.cpp file
+ * looks like this:
+ *
+ * @code
+ * #include <kaboutdata.h>
+ * #include <kapplication.h>
+ * #include <kcmdlineargs.h>
+ * #include <kcmdlineargs.h>
+ * #include <klocale.h>
+ * #include <kunittest/runnergui.h>
+ *
+ * static const char description[] = I18N_NOOP("SampleTests");
+ * static const char version[] = "0.1";
+ * static KCmdLineOptions options[] = { KCmdLineLastOption };
+ *
+ * int main( int argc, char** argv )
+ * {
+ * KAboutData about("SampleTests", I18N_NOOP("SampleTests"), version, description,
+ * KAboutData::License_BSD, "(C) 2005 You!", 0, 0, "mail@provider");
+ *
+ * KCmdLineArgs::init(argc, argv, &about);
+ * KCmdLineArgs::addCmdLineOptions( options );
+ * KApplication app;
+ *
+ * KUnitTest::RunnerGUI runner(0);
+ * runner.show();
+ * app.setMainWidget(&runner);
+ *
+ * return app.exec();
+ * }
+ * @endcode
+ *
+ * The Makefile.am file will look like:
+ *
+ * @code
+ * INCLUDES = -I$(top_srcdir)/src $(all_includes)
+ * METASOURCES = AUTO
+ * check_PROGRAMS = sampletests
+ * sampletests_SOURCES = main.cpp sampletest.cpp
+ * sampletests_LDFLAGS = $(KDE_RPATH) $(all_libraries)
+ * sampletests_LDADD = -lkunittest
+ * noinst_HEADERS = sampletest.h
+ *
+ * check:
+ * kunittest $(top_builddir)/src/sampletests SampleTests
+ * @endcode
+ *
+ * Most of this Makefile.am will be self-explanatory. After running
+ * "make check" the binary "sampletests" will be built. The reason for
+ * adding the extra make target "check" is that you probably do not want
+ * to rebuild the test suite everytime you run make.
+ *
+ * You can run the binary on its own, but you get more functionality if you use
+ * the kunittest helper script. The Makefile.am is set up in such
+ * a way that this helper script is automatically run after you do a
+ * "make check". This scripts take two arguments, the first is the path
+ * to the binary to run. The second the application name, in this case SampleTests.
+ * This name is important since it is used to let the script communicate with the application
+ * via DCOP. The helper scripts relies on the Perl DCOP bindings, so these need to be installed.
+ *
+ * @section module Creating test modules
+ *
+ * If you think that writing your own test runner if too much work then you can also
+ * use the kunittestermodrunner application or the kunitguimodrunner script to run
+ * the tests for you. You do have to put your tests in a dynamically loadable module though.
+ * Fortunately KUnitTest comes with a few macros to help you do this.
+ *
+ * First the good news, you don't have to change the header file sampletest.h. However, we
+ * will rename it to samplemodule.h, so we remember we are making a module. The
+ * implementation file should be rename to samplemodule.cpp. This file requires some
+ * modifications. First we need to include the module.h header:
+ *
+ * @code
+ * #include <kunittest/module.h>
+ * @endcode
+ *
+ * This header file is needed because it defines some macro you'll need. In fact this is
+ * how you use them:
+ *
+ * @code
+ * KUNITTEST_MODULE( kunittest_samplemodule, "Tests for sample module" );
+ * KUNITTEST_MODULE_REGISTER_TESTER( SimpleSampleTester );
+ * KUNITTEST_MODULE_REGISTER_TESTER( SomeSampleTester );
+ * @endcode
+ *
+ * The first macro, KUNITTEST_MODULE(), makes sure that the module can be loaded and that
+ * the test classes are created. The first argument "kunittest_samplemodule" is the library
+ * name, in this case the library we're creating a kunittest_samplemodule.la module. The
+ * second argument is name which will appear in the test runner for this test suite.
+ *
+ * The tester class are now added by the KUNITTEST_MODULE_REGISTER_TESTER() macro, not the
+ * KUNITTEST_REGISTER_TESTER(). The only difference between the two is that you have to
+ * pass the module class name to this macro.
+ *
+ * The Makefile.am is also a bit different, but not much:
+ *
+ * @code
+ * INCLUDES = -I$(top_srcdir)/include $(all_includes)
+ * METASOURCES = AUTO
+ * check_LTLIBRARIES = kunittest_samplemodule.la
+ * kunittest_samplemodule_la_SOURCES = samplemodule.cpp
+ * kunittest_samplemodule_la_LIBADD = $(LIB_KUNITTEST)
+ * kunittest_samplemodule_la_LDFLAGS = -module $(KDE_CHECK_PLUGIN) $(all_libraries)
+ * @endcode
+ *
+ * The $(KDE_CHECK_PLUGIN) macro is there to make sure a dynamically loadable
+ * module is created.
+ *
+ * After you have built the module you open a Konsole and cd into the build folder. Running
+ * the tests in the module is now as easy as:
+ *
+ * @code
+ * $ make check && kunittestmodrunner
+ * @endcode
+ *
+ * The kunittestmodrunner application loads all kunittest_*.la modules in the current
+ * directory. The exit code of this console application is the number of unexpected failures.
+ *
+ * If you want the GUI, you should use the kunittestmod script:
+ *
+ * @code
+ * $ make check && kunittestmod
+ * @endcode
+ *
+ * This script starts kunittestguimodrunner application and a helper script to take
+ * care of dealing with debug output.
+ *
+ * @section advanced Advanced usage
+ *
+ * Normally you just want to use CHECK(). If you are developing some more
+ * tests, and they are run (or not) based on some external dependency,
+ * you may need to skip some tests. In this case, rather than doing
+ * nothing (or worse, writing a test step that aborts the test run), you
+ * might want to use SKIP() to record that. Note that this is just a
+ * logging / reporting tool, so you just pass in a string:
+ *
+ * @code
+ * SKIP( "Test skipped because of lack of foo support." );
+ * @endcode
+ *
+ * Similarly, you may have a test step that you know will fail, but you
+ * don't want to delete the test step (because it is showing a bug), but
+ * equally you can't fix it right now (eg it would break binary
+ * compatibility, or would violate a string freeze). In that case, it
+ * might help to use XFAIL(), for "expected failure". The test will still
+ * be run, and recorded as a failure (assuming it does fail), but will
+ * also be recorded separately. Usage might be as follows:
+ *
+ * @code
+ * XFAIL( 2+1, 4 );
+ * @endcode
+ *
+ * You can mix CHECK(), SKIP() and XFAIL() within a single Tester derived
+ * class.
+ *
+ *
+ * @section exceptions Exceptions
+ *
+ * KUnitTest comes with simple support for testing whether an exception, such as a function call,
+ * throws an exception or not. Simply, for the usual macros there corresponding ones for
+ * exception testing: CHECK_EXCEPTION(), XFAIL_EXCEPTION(), and SKIP_EXCEPTION(). They all take two
+ * arguments: the expression that will catch the exception, and the expression that is supposed
+ * to throw the exception.
+ *
+ * For example:
+ *
+ * @code
+ * CHECK_EXCEPTION(EvilToothFairyException *, myFunction("I forgot to brush my teeth!"));
+ * @endcode
+ *
+ * @note The exception is not de-allocated in anyway.
+ *
+ * The macros does not allow introspection of the exceptions, such as testing a supplied
+ * identifier code on the exception object or similar; this requires manual coding, such
+ * as custom macros.
+ *
+ * @section scripts Scripts
+ *
+ * The library comes with several helper scripts:
+ *
+ * @li kunittest [app] [dcopobject] : Runs the application app and redirects all debug output to the dcopobject.
+ * @li kunittestmod --folder [folder] --query [query] : Loads and runs all modules in the folder matching the query. Use a GUI.
+ * @li kunittest_debughelper [dcopobject] : A PERL script that is able to redirect debug output to a RunnerGUI instance.
+ *
+ * These scripts are part of the kdesdk/kunittest module.
+ */
+
+/*!
+ * @file tester.h
+ * Defines macros for unit testing as well as some test classes.
+ */
+
+#include <iostream>
+using namespace std;
+
+#include <qobject.h>
+#include <qstringlist.h>
+#include <qasciidict.h>
+
+#include <kdelibs_export.h>
+
+/*! @def CHECK(x,y)
+ * Use this macro to perform an equality check. For example
+ *
+ * @code CHECK( numberOfErrors(), 0 ); @endcode
+ */
+#define CHECK( x, y ) check( __FILE__, __LINE__, #x, x, y, false )
+
+/// for source-compat with qttestlib: use COMPARE(x,y) if you plan to port to qttestlib later.
+#define COMPARE CHECK
+
+/// for source-compat with qttestlib: use VERIFY(x) if you plan to port to qttestlib later.
+#define VERIFY( x ) CHECK( x, true )
+
+/*! @def XFAIL(x,y)
+ * Use this macro to perform a check you expect to fail. For example
+ *
+ * @code XFAIL( numberOfErrors(), 1 ); @endcode
+ *
+ * If the test fails, it will be counted as such, however it will
+ * also be registered separately.
+ */
+#define XFAIL( x, y ) check( __FILE__, __LINE__, #x, x, y, true )
+
+/*! @def SKIP(x)
+ * Use this macro to indicate that a test is skipped.
+ *
+ * @code SKIP("Test skipped because of lack of foo support."); @endcode
+ */
+#define SKIP( x ) skip( __FILE__, __LINE__, QString::fromLatin1(#x))
+
+/*!
+ * A macro testing that @p expression throws an exception that is catched
+ * with @p exceptionCatch. Use it to test that an expression, such as a function call,
+ * throws a certain exception.
+ *
+ * @note this macro assumes it's used in a function which is a sub-class of the Tester class.
+ */
+#define CHECK_EXCEPTION(exceptionCatch, expression) \
+ try \
+ { \
+ expression; \
+ } \
+ catch(exceptionCatch) \
+ { \
+ setExceptionRaised(true); \
+ } \
+ if(exceptionRaised()) \
+ { \
+ success(QString(__FILE__) + "[" + QString::number(__LINE__) + "]: passed " + #expression); \
+ } \
+ else \
+ { \
+ failure(QString(__FILE__) + "[" + QString::number(__LINE__) + QString("]: failed to throw " \
+ "an exception on: ") + #expression); \
+ } \
+ setExceptionRaised(false);
+
+/*!
+ * This macro is similar to XFAIL, but is for exceptions instead. Flags @p expression
+ * as being expected to fail to throw an exception that @p exceptionCatch is supposed to catch.
+ */
+#define XFAIL_EXCEPTION(exceptionCatch, expression) \
+ try \
+ { \
+ expression; \
+ } \
+ catch(exceptionCatch) \
+ { \
+ setExceptionRaised(true); \
+ } \
+ if(exceptionRaised()) \
+ { \
+ unexpectedSuccess(QString(__FILE__) + "[" + QString::number(__LINE__) + "]: unexpectedly threw an exception and passed: " + #expression); \
+ }\
+ else \
+ { \
+ expectedFailure(QString(__FILE__) + "[" + QString::number(__LINE__) + QString("]: failed to throw an exception on: ") + #expression); \
+ } \
+ setExceptionRaised(false);
+
+/*!
+ * This macro is similar to SKIP, but is for exceptions instead. Skip testing @p expression
+ * and the @p exceptionCatch which is supposed to catch the exception, and register the test
+ * as being skipped.
+ */
+#define SKIP_EXCEPTION(exceptionCatch, expression) \
+ skip( __FILE__, __LINE__, QString("Exception catch: ")\
+ .arg(QString(#exceptionCatch)).arg(QString(" Test expression: ")).arg(QString(#expression)))
+
+/**
+ * Namespace for Unit testing classes
+ */
+namespace KUnitTest
+{
+ /*! A simple class that encapsulates a test result. A Tester class usually
+ * has a single TestResults instance associated with it, however the SlotTester
+ * class can have more TestResults instances (one for each test slot in fact).
+ */
+ class KUNITTEST_EXPORT TestResults
+ {
+ friend class Tester;
+
+ public:
+ TestResults() : m_tests( 0 ) {}
+
+ virtual ~TestResults() {}
+
+ /*! Clears the test results and debug info. Normally you do not need to call this.
+ */
+ virtual void clear()
+ {
+ m_errorList.clear();
+ m_xfailList.clear();
+ m_xpassList.clear();
+ m_skipList.clear();
+ m_successList.clear();
+ m_debug = "";
+ m_tests = 0;
+ }
+
+ /*! Add some debug info that can be view later. Normally you do not need to call this.
+ * @param debug The debug info.
+ */
+ virtual void addDebugInfo(const QString &debug)
+ {
+ m_debug += debug;
+ }
+
+ /*! @returns The debug info that was added to this Tester object.
+ */
+ QString debugInfo() const { return m_debug; }
+
+ /*! @returns The number of finished tests. */
+ int testsFinished() const { return m_tests; }
+
+ /*! @returns The number of failed tests. */
+ int errors() const { return m_errorList.count(); }
+
+ /*! @returns The number of expected failures. */
+ int xfails() const { return m_xfailList.count(); }
+
+ /*! @returns The number of unexpected successes. */
+ int xpasses() const { return m_xpassList.count(); }
+
+ /*! @returns The number of skipped tests. */
+ int skipped() const { return m_skipList.count(); }
+
+ /*! @returns The number of passed tests. */
+ int passed() const { return m_successList.count(); }
+
+ /*! @returns Details about the failed tests. */
+ QStringList errorList() const { return m_errorList; }
+
+ /*! @returns Details about tests that failed expectedly. */
+ QStringList xfailList() const { return m_xfailList; }
+
+ /*! @returns Details about tests that succeeded unexpectedly. */
+ QStringList xpassList() const { return m_xpassList; }
+
+ /*! @returns Details about which tests were skipped. */
+ QStringList skipList() const { return m_skipList; }
+
+ /*! @returns Details about the succeeded tests. */
+ QStringList successList() const { return m_successList; }
+
+ private:
+ QStringList m_errorList;
+ QStringList m_xfailList;
+ QStringList m_xpassList;
+ QStringList m_skipList;
+ QStringList m_successList;
+ QString m_debug;
+ int m_tests;
+ };
+
+ typedef QAsciiDict<TestResults> TestResultsListType;
+
+ /*! A type that can be used to iterate through the registry. */
+ typedef QAsciiDictIterator<TestResults> TestResultsListIteratorType;
+
+ /*! The abstract Tester class forms the base class for all test cases. Users must
+ * implement the void Tester::allTests() method. This method contains the actual test.
+ *
+ * Use the CHECK(x,y), XFAIL(x,y) and SKIP(x) macros in the allTests() method
+ * to perform the tests.
+ *
+ * @see CHECK, XFAIL, SKIP
+ */
+ class KUNITTEST_EXPORT Tester : public QObject
+ {
+ public:
+ Tester(const char *name = 0L)
+ : QObject(0L, name), m_results(new TestResults()), m_exceptionState(false)
+ {}
+
+ virtual ~Tester() { delete m_results; }
+
+ public:
+ /*! Implement this method with the tests and checks you want to perform.
+ */
+ virtual void allTests() = 0;
+
+ public:
+ /*! @return The TestResults instance.
+ */
+ virtual TestResults *results() { return m_results; }
+
+ protected:
+ /*! This is called when the SKIP(x) macro is used.
+ * @param file A C-string containing the name of the file where the skipped tests resides. Typically the __FILE__ macro is used to retrieve the filename.
+ * @param line The linenumber in the file @p file. Use the __LINE__ macro for this.
+ * @param msg The message that identifies the skipped test.
+ */
+ void skip( const char *file, int line, QString msg )
+ {
+ QString skipEntry;
+ QTextStream ts( &skipEntry, IO_WriteOnly );
+ ts << file << "["<< line <<"]: " << msg;
+ skipTest( skipEntry );
+ }
+
+ /*! This is called when the CHECK or XFAIL macro is used.
+ * @param file A C-string containing the name of the file where the skipped tests resides. Typically the __FILE__ macro is used to retrieve the filename.
+ * @param line The linenumber in the file @p file. Use the __LINE__ macro for this.
+ * @param str The message that identifies the skipped test.
+ * @param result The result of the test.
+ * @param expectedResult The expected result.
+ * @param expectedFail Indicates whether or not a failure is expected.
+ */
+ template<typename T>
+ void check( const char *file, int line, const char *str,
+ const T &result, const T &expectedResult,
+ bool expectedFail )
+ {
+ cout << "check: " << file << "["<< line <<"]" << endl;
+
+ if ( result != expectedResult )
+ {
+ QString error;
+ QTextStream ts( &error, IO_WriteOnly );
+ ts << file << "["<< line <<"]: failed on \"" << str
+ <<"\" result = '" << result << "' expected = '" << expectedResult << "'";
+
+ if ( expectedFail )
+ expectedFailure( error );
+ else
+ failure( error );
+
+ }
+ else
+ {
+ // then the test passed, but we want to record it if
+ // we were expecting a failure
+ if (expectedFail)
+ {
+ QString err;
+ QTextStream ts( &err, IO_WriteOnly );
+ ts << file << "["<< line <<"]: "
+ <<" unexpectedly passed on \""
+ << str <<"\"";
+ unexpectedSuccess( err );
+ }
+ else
+ {
+ QString succ;
+ QTextStream ts( &succ, IO_WriteOnly );
+ ts << file << "["<< line <<"]: "
+ <<" passed \""
+ << str <<"\"";
+ success( succ );
+ }
+ }
+
+ ++m_results->m_tests;
+ }
+
+ /*!
+ * This function can be used to flag succeeding tests, when
+ * doing customized tests while not using the check function.
+ *
+ * @param message the message describing what failed. Should be informative,
+ * such as mentioning the expression that failed and where, the file and file number.
+ */
+ void success(const QString &message) { m_results->m_successList.append(message); }
+
+ /*!
+ * This function can be used to flag failing tests, when
+ * doing customized tests while not using the check function.
+ *
+ * @param message the message describing what failed. Should be informative,
+ * such as mentioning the expression that failed and where, the file name and file number.
+ */
+ void failure(const QString &message) { m_results->m_errorList.append(message); }
+
+ /*!
+ * This function can be used to flag expected failures, when
+ * doing customized tests while not using the check function.
+ *
+ * @param message the message describing what failed. Should be informative,
+ * such as mentioning the expression that failed and where, the file name and file number.
+ */
+ void expectedFailure(const QString &message) { m_results->m_xfailList.append(message); }
+
+ /*!
+ * This function can be used to flag unexpected successes, when
+ * doing customized tests while not using the check function.
+ *
+ * @param message the message describing what failed. Should be informative,
+ * such as mentioning the expression that failed and where, the file name and file number.
+ */
+ void unexpectedSuccess(const QString &message) { m_results->m_xpassList.append(message); }
+
+ /*!
+ * This function can be used to flag a test as skipped, when
+ * doing customized tests while not using the check function.
+ *
+ * @param message the message describing what failed. Should be informative,
+ * such as mentioning the expression that failed and where, the file name and file number.
+ */
+ void skipTest(const QString &message) { m_results->m_skipList.append(message); }
+
+ /*!
+ * exceptionRaised and exceptionState are book-keeping functions for testing for
+ * exceptions. setExceptionRaised sets an internal boolean to true.
+ *
+ * @see exceptionRaised
+ * @param state the new
+ */
+ void setExceptionRaised(bool state) { m_exceptionState = state; }
+
+ /*!
+ * Returns what the currently tested exception state.
+ *
+ * @see setExceptionRaised
+ */
+ bool exceptionRaised() const
+ {
+ return m_exceptionState;
+ }
+
+ protected:
+ TestResults *m_results;
+
+ private:
+
+ bool m_exceptionState;
+ };
+
+ /*! The SlotTester class is a special Tester class, one that will
+ * execute all slots that start with the string "test". The method
+ * void allTests() is implemented and should not be overriden.
+ */
+ class KUNITTEST_EXPORT SlotTester : public Tester
+ {
+ Q_OBJECT
+
+ public:
+ SlotTester(const char *name = 0L);
+
+ void allTests();
+
+ TestResults *results(const char *sl);
+
+ TestResultsListType &resultsList() { return m_resultsList; }
+
+ signals:
+ void invoke();
+
+ private:
+ void invokeMember(const QString &str);
+
+ TestResultsListType m_resultsList;
+ TestResults *m_total;
+ };
+}
+
+KUNITTEST_EXPORT QTextStream& operator<<( QTextStream& str, const QRect& r );
+
+KUNITTEST_EXPORT QTextStream& operator<<( QTextStream& str, const QPoint& r );
+
+KUNITTEST_EXPORT QTextStream& operator<<( QTextStream& str, const QSize& r );
+
+#endif