diff options
Diffstat (limited to 'kunittest')
-rw-r--r-- | kunittest/Makefile.am | 27 | ||||
-rw-r--r-- | kunittest/modrunner.cpp | 67 | ||||
-rw-r--r-- | kunittest/module.h | 120 | ||||
-rw-r--r-- | kunittest/runner.cpp | 322 | ||||
-rw-r--r-- | kunittest/runner.h | 219 | ||||
-rw-r--r-- | kunittest/samplemodule.cpp | 40 | ||||
-rw-r--r-- | kunittest/samplemodule.h | 38 | ||||
-rw-r--r-- | kunittest/tester.cpp | 99 | ||||
-rw-r--r-- | kunittest/tester.h | 716 |
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 ®istry(); + + /*! @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 |