summaryrefslogtreecommitdiffstats
path: root/old/libqsystemtest/qabstracttest.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'old/libqsystemtest/qabstracttest.cpp')
-rw-r--r--old/libqsystemtest/qabstracttest.cpp743
1 files changed, 743 insertions, 0 deletions
diff --git a/old/libqsystemtest/qabstracttest.cpp b/old/libqsystemtest/qabstracttest.cpp
new file mode 100644
index 0000000..7b1a02a
--- /dev/null
+++ b/old/libqsystemtest/qabstracttest.cpp
@@ -0,0 +1,743 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of QtUiTest.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <qabstracttest.h>
+#include <QDir>
+#include <QFileInfo>
+#include <QMetaMethod>
+#include <QThread>
+#include <QTimer>
+
+#ifdef Q_OS_UNIX
+# include <unistd.h>
+# include <time.h>
+# include <signal.h>
+# include <setjmp.h>
+#endif
+
+/*!
+ \enum QAbstractTest::LearnMode
+
+ This enum specifies the learn mode setting.
+
+ The learn mode affects the behavior of certain functions in QSystemTest, such as verifyImage().
+ Additionally, a test may choose to use the current learn mode to determine how to handle
+ a test failure.
+
+ \value LearnNone
+ Learn mode is off. Any mismatches encountered will cause a test failure.
+ \value LearnNew
+ The test should attempt to learn data which does not match existing test data.
+ \value LearnAll
+ The test should attempt to learn all data, even if it matches existing test data.
+
+ \sa learnMode(), setLearnMode(), verifyImage()
+*/
+
+bool Autotest_QLog::m_enabled = false;
+
+QDebug Autotest_QLog::log(const char* category)
+{
+ QDebug r = QDebug(QtDebugMsg);
+ if ( category )
+ r << category << ": ";
+ return r;
+}
+
+class FatalTimeoutThread : public QThread
+{
+Q_OBJECT
+public:
+ FatalTimeoutThread();
+ bool inEventLoop() const { return m_inEventLoop; }
+ void setTimeout(int timeout) { m_timeout = timeout; }
+
+ void startTimer();
+ void stopTimer();
+ void stopThread();
+
+protected:
+ void run();
+
+private slots:
+ void enteredEventLoop();
+ void _startTimer();
+ void _stopTimer();
+ void _stopThread();
+
+private:
+ enum ExitCodes { TimedOut = 0, DidNotTimeOut, StartTimer, Exit };
+ int m_timeout;
+ bool m_inEventLoop;
+};
+
+class QAbstractTestPrivate
+{
+public:
+ QAbstractTest::LearnMode learnMode;
+ QString baseDataPath;
+ bool failEmptyTest;
+};
+
+static QString noslash(QString const &in)
+{
+ QString out(in);
+ while (out.endsWith("/")) out.chop(1);
+ return out;
+}
+
+/*
+ \internal
+ \class QAbstractTest
+ \inpublicgroup QtUiTestExtension
+ \mainclass
+ \brief The QAbstractTest class is the base class for all QtUiTest System Tests.
+
+ \ingroup qtuitest_unittest
+ \ingroup qtuitest_systemtest
+ \internal
+
+ QAbstractTest provides some functionality helpful for all kinds of tests.
+
+ In practice, a test class will almost always subclass QSystemTest.
+
+ \sa QSystemTest, QtUiTest, QTestLib
+*/
+
+/*
+ \internal
+ Construct the test with the specified \a parent.
+*/
+QAbstractTest::QAbstractTest(QString const &srcdir, QObject *parent)
+ : QTimedTest(parent)
+{
+ d = new QAbstractTestPrivate;
+ d->baseDataPath = noslash(QDir::homePath()) + "/.qtest";
+ d->learnMode = LearnNone;
+ d->failEmptyTest = false;
+ if (!srcdir.isEmpty())
+ setupTestDataPath(qPrintable(QString("%1/tst_phonytest.cpp").arg(srcdir)));
+}
+
+/*
+ \internal
+*/
+QAbstractTest::~QAbstractTest()
+{
+ delete d;
+}
+
+/*!
+ Returns the current learn mode.
+*/
+QAbstractTest::LearnMode QAbstractTest::learnMode() const
+{
+ return d->learnMode;
+}
+
+/*!
+ Sets the current learn \a mode.
+*/
+void QAbstractTest::setLearnMode(QAbstractTest::LearnMode mode)
+{
+ d->learnMode = mode;
+}
+
+bool QAbstractTest::failEmptyTest() const
+{
+ return d->failEmptyTest;
+}
+
+/*!
+ Returns the path in which data for the current test function will be saved to/retrieved from.
+
+ The path follows the format \c baseDataPath/testCaseName/testFunctionName/dataTag,
+ where \c baseDataPath is the path returned by \l baseDataPath().
+
+ Internally, the current data path does not affect the behaviour of QAbstractTest.
+ However, subclasses may use the value returned by this function to decide where to
+ store learned data.
+
+ \sa baseDataPath(), learnMode()
+*/
+QString QAbstractTest::currentDataPath() const
+{
+ return d->baseDataPath + "/" + currentTestFunction() + "/" + currentDataTag();
+}
+
+
+/*!
+ Returns the path which functions as the root directory to all the test data.
+
+ This path defaults to the "testdata" subdirectory of the directory in which the source file of
+ the current test is located; if the source file no longer exists, the path defaults to
+ \c $HOME/.qtest/ . In either case, it can be overridden by the user with the \c -data command line switch.
+
+ The base data path contains the test data for functions such as verifyImage().
+ It can also be used within a test to use testdata stored in files.
+
+ \sa currentDataPath(), learnMode()
+*/
+QString QAbstractTest::baseDataPath() const
+{
+ return d->baseDataPath + "/";
+}
+
+/*!
+ Set the \a path which functions as the root directory to all of the test data.
+
+ The base data path contains the test data for functions such as verifyImage().
+ It can also be used within a test to use testdata stored in files.
+
+ \sa baseDataPath()
+*/
+void QAbstractTest::setBaseDataPath(QString const &path)
+{
+ d->baseDataPath = path;
+}
+
+#ifdef TESTS_SHARED_DIR
+/*!
+ Returns the path which functions as the shared directory available to tests.
+
+ The shared data path contains scripts and other functions available to tests. This is the default search
+ location for the \c include feature.
+
+*/
+QString QAbstractTest::sharedDataPath() const
+{
+ return noslash(TESTS_SHARED_DIR);
+}
+#endif
+/*!
+ Returns the current data tag, or an empty string if the test function has no test data associated
+ with it. The data tag is the string used to identify each row in the test data table.
+
+ Example:
+ \code
+ walkTheDog: function(street) {
+ // At the moment, application crashes if we walk on concrete; this is
+ // a known failure
+ if (currentDataTag().startsWith("concrete sidewalk"))
+ expectFail("concrete is known to break");
+
+ enter( street, "Destination" );
+ select( "Walk the Dog" );
+ compare( getText("Status"), "Walked" );
+ }
+ walkTheDog_data: {
+ "concrete sidewalk 1": [ "East St." ],
+ "concrete sidewalk 2": [ "West St." ],
+ grassy: [ "North St." ],
+ muddy: [ "South St." ]
+ }
+ \endcode
+*/
+QString QAbstractTest::currentDataTag() const
+{
+ return QTest::currentDataTag();
+}
+
+/*!
+ Returns the name of the currently executing test case.
+
+ \sa currentTestFunction()
+*/
+QString QAbstractTest::testCaseName() const
+{
+ return metaObject()->className();
+}
+
+/*!
+ Returns the name of the testfunction that is currently being executed.
+ If \a fullName is true, the fully qualified name (including the test case name) will be returned.
+
+ Example, in a file named sys_mytest:
+ \code
+ testYoyo: function() {
+ currentTestFunction(); // returns "testYoyo"
+ currentTestFunction(true); // returns "sys_mytest::testYoyo"
+ }
+ \endcode
+
+ \sa testCaseName()
+*/
+QString QAbstractTest::currentTestFunction( bool fullName ) const
+{
+ return fullName ? QString("%1::%2").arg(testCaseName()).arg(QTest::currentTestFunction()) : (QString(QTest::currentTestFunction()));
+}
+
+#ifndef QTCREATOR_QTEST
+/*
+ \internal
+ Executes all test functions as specified on the command line, while running the
+ application event loop.
+
+ The \a argc and \a argv parameters should be passed in from the main()
+ function of the application and are used to parse command-line arguments
+ specific to QAbstractTest. Also, subclasses may override the runTest(),
+
+ The \a filename parameter contains the full path to the source file containing the
+ test in question. It is used to determine where test data should be located.
+
+*/
+int QAbstractTest::exec( int argc, char* argv[], char* filename )
+{
+ setupTestDataPath(filename);
+
+ QStringList options;
+ for (int i = 1; i < argc; ++i) options << argv[i];
+
+ QString defOpt = qgetenv("QTUITEST_DEFAULT_OPTIONS");
+ if (defOpt.length()) {
+ options << defOpt.split(' ');
+ }
+
+ processCommandLine(options);
+ if (options.isEmpty()) {
+ qWarning("No script specified");
+ exit(1);
+ }
+
+ return runTest(options.first(), options, QStringList());
+}
+#endif
+
+//#ifndef QTCREATOR_QTEST
+/*
+ \internal
+ Print a usage message.
+ \a argc and \a argv are command-line arguments.
+ Any subclass which overrides processCommandLine() should also override this
+ function and provide documentation for their arguments.
+*/
+void QAbstractTest::printUsage() const
+{
+ qWarning(
+ " Usage:\n"
+ " qtuitestrunner [file] [options] [testfunction[:datatag]]...\n"
+ "\n"
+ " 'file' is a QtScript file containing the testcase.\n"
+ " 'testfunctions' is a list of functions to execute (separated by spaces).\n"
+ " If no testfunctions are specified, ALL functions will be executed.\n"
+ "\n"
+ " basic test options:\n"
+ " -functions : Returns a list of current testfunctions\n"
+ " -xunitxml : Outputs results as XML XUnit document\n"
+ " -xml : Outputs results as XML document\n"
+ " -lightxml : Outputs results as stream of XML tags\n"
+ " -flush : Flushes the results (use with -xml or -lightxml)\n"
+ " -o filename : Writes all output into a file\n"
+ " -silent : Only outputs warnings and failures\n"
+ " -v : Print 'Autotest' log messages\n"
+ " -v1 : Also print enter messages for each testfunction\n"
+ " -v2 : Also print out each QVERIFY/QCOMPARE/QTEST\n"
+ " -vs : Print every signal emitted\n"
+ " -timed : Print time elapsed (in milliseconds) for each testfunction\n"
+ " -maxtime ms : Maximum time allowed per test, in milliseconds (implies -timed).\n"
+ " If this time is reached, a fatal error occurs and subsequent tests will not run.\n"
+ " -maxwarnings n : Sets the maximum amount of messages to output.\n"
+ " 0 means unlimited, default: 2000\n"
+ " -failempty : Test functions that test nothing will FAIL\n"
+ " -help : This help\n"
+ "\n"
+ " learn mode options:\n"
+ " -learn : All 'learnable' new and changed data may be added/updated.\n"
+ " -learn-all : All 'learnable' data may be added/updated, even if it has not changed.\n"
+ " -data d : The location of the testdata; this value is used for dynamic data loading (i.e. dynamic\n"
+ " data that can't be hardcoded in the testcase itself).\n"
+ " If -data is not specified the testcase will first look in the testdata subdirectory\n"
+ " of the directory containing the test source file, then in $HOME/.qtest\n"
+ );
+}
+//#endif
+
+/*
+ \internal
+ Processes the command line parameters specified by \a argc, \a argv.
+ Subclasses may reimplement this function to handle specific arguments.
+*/
+void QAbstractTest::processCommandLine( QStringList &args )
+{
+ QMutableStringListIterator it(args);
+
+ while (it.hasNext()) {
+ QString arg = it.next();
+ if ( !arg.compare("-data", Qt::CaseInsensitive) ) {
+ it.remove();
+ if (!it.hasNext()) qFatal("Expected a value after %s", qPrintable(arg));
+ setBaseDataPath(it.next());
+ it.remove();
+ } else if ( !arg.compare("-v", Qt::CaseInsensitive) ) {
+ it.remove();
+ Autotest_QLog::m_enabled = true;
+ } else if ( !arg.compare("-v1", Qt::CaseInsensitive) ||
+ !arg.compare("-v2", Qt::CaseInsensitive) ) {
+ // Don't consume -v1 or -v2
+ Autotest_QLog::m_enabled = true;
+ } else if ( !arg.compare("-learn", Qt::CaseInsensitive) ) {
+ it.remove();
+ setLearnMode(LearnNew);
+ } else if ( !arg.compare("-learn-all", Qt::CaseInsensitive) ) {
+ it.remove();
+ setLearnMode(LearnAll);
+ } else if ( !arg.compare("-failempty", Qt::CaseInsensitive) ) {
+ it.remove();
+ d->failEmptyTest = true;
+ } else if ( !arg.compare("-timed", Qt::CaseInsensitive) ) {
+ it.remove();
+ QTimedTest::pass_through = false;
+ QTimedTest::print_times = true;
+ } else if ( !arg.compare("-maxtime", Qt::CaseInsensitive) ) {
+ it.remove();
+ if (!it.hasNext()) qFatal("Expected a value after %s", qPrintable(arg));
+ QTimedTest::pass_through = false;
+ bool ok;
+ timeout_thread = new FatalTimeoutThread;
+ timeout_thread->setTimeout(it.next().toInt(&ok));
+ if (!ok) qFatal("Invalid parameter to -maxtime");
+ it.remove();
+ } else if ( !arg.compare("-help", Qt::CaseInsensitive) ||
+ !arg.compare("--help", Qt::CaseInsensitive) ||
+ !arg.compare("-h", Qt::CaseInsensitive) ) {
+ it.remove();
+#ifndef QTCREATOR_QTEST
+ printUsage();
+ exit(0);
+#endif
+ }
+ }
+}
+
+#if defined(Q_OS_UNIX) && !defined(Q_OS_SYMBIAN)
+jmp_buf segfault_jmp;
+
+void handle_segfault(int signum)
+{
+ signal(signum, SIG_DFL);
+ QTest::qFail("A segmentation fault occurred.", "Unknown file", 0);
+ longjmp(segfault_jmp, 1);
+ _exit(0);
+}
+#endif
+
+//# ifndef QTCREATOR_QTEST
+/*
+ \internal
+ Run test with arguments \a argc, \a argv, and return an exit code.
+ The base implementation executes private slots as testfunctions
+ using QTest::qExec(). Subclasses may reimplement this function to provide
+ other behaviour.
+*/
+int QAbstractTest::runTest(const QString &fname, const QStringList &args,
+ const QStringList &environment)
+{
+ Q_UNUSED(fname);
+ Q_UNUSED(environment);
+#if defined(Q_OS_UNIX) && !defined(Q_OS_SYMBIAN)
+ signal(SIGSEGV, handle_segfault);
+ if (!setjmp(segfault_jmp))
+#endif
+ return QTest::qExec( this, args );
+ return -1;
+}
+//#endif
+
+/*!
+ \internal
+ Set up the test data path, where \a filename is the name of the test source file.
+*/
+void QAbstractTest::setupTestDataPath(const char *filename)
+{
+ d->baseDataPath = noslash(QDir::homePath()) + "/.qtest/" + testCaseName();
+
+ if (filename) {
+ // If we are given a filename, try to use it as test path
+ QFileInfo sourceFile( filename );
+ QString sfPath = sourceFile.canonicalPath();
+ QString dirPath = sourceFile.dir().canonicalPath();
+ QString path = sfPath.isEmpty() ? dirPath : sfPath;
+ if (!path.isEmpty()) {
+ d->baseDataPath = noslash(path) + "/testdata";
+ }
+ }
+
+ // Try to ensure the data directory exists
+ QDir dir;
+ dir.mkpath(d->baseDataPath);
+}
+
+class Timer
+{
+public:
+ virtual ~Timer() {}
+ virtual void start() = 0;
+ virtual int elapsed() const = 0;
+};
+
+class RealTimeTimer : public Timer
+{
+public:
+ void start() { t.start(); }
+ int elapsed() const { return t.elapsed(); }
+private:
+ QTime t;
+};
+
+#if defined(Q_OS_UNIX) && !defined(Q_OS_SYMBIAN)
+class ProcessorTimeTimer : public Timer
+{
+public:
+ void start() { s = clock(); }
+ int elapsed() const { return (int)(((double)(clock() - s))/((double)CLOCKS_PER_SEC)*1000.0); }
+private:
+ clock_t s;
+};
+#endif
+
+
+QTimedTest::QTimedTest(QObject *parent)
+ : QObject(parent), pass_through(true), print_times(false),
+ timeout_thread(0), realTimer(0), cpuTimer(0)
+{}
+
+QTimedTest::~QTimedTest()
+{
+ if (timeout_thread) {
+ timeout_thread->stopThread();
+ timeout_thread->wait();
+ delete timeout_thread;
+ }
+ delete realTimer;
+ delete cpuTimer;
+}
+
+static bool isTimeableSlot(const QMetaMethod &sl)
+{
+ if (sl.access() != QMetaMethod::Private || !sl.parameterTypes().isEmpty()
+ || qstrlen(sl.typeName()) || sl.methodType() != QMetaMethod::Slot)
+ return false;
+ const char *sig = sl.signature();
+ int len = qstrlen(sig);
+ if (len < 2)
+ return false;
+ if (sig[len - 2] != '(' || sig[len - 1] != ')')
+ return false;
+ if (len > 7 && strcmp(sig + (len - 7), "_data()") == 0)
+ return false;
+/*
+ It makes sense to time init() etc also.
+ if (strcmp(sig, "initTestCase()") == 0 || strcmp(sig, "cleanupTestCase()") == 0
+ || strcmp(sig, "cleanup()") == 0 || strcmp(sig, "init()") == 0)
+ return false;
+*/
+
+ return true;
+}
+
+FatalTimeoutThread::FatalTimeoutThread() : QThread()
+{
+ m_timeout = 0;
+ m_inEventLoop = false;
+}
+
+void FatalTimeoutThread::enteredEventLoop()
+{
+ m_inEventLoop = true;
+}
+
+void FatalTimeoutThread::startTimer()
+{
+ QMetaObject::invokeMethod(this, "_startTimer");
+}
+
+void FatalTimeoutThread::stopTimer()
+{
+ QMetaObject::invokeMethod(this, "_stopTimer");
+}
+
+void FatalTimeoutThread::stopThread()
+{
+ QMetaObject::invokeMethod(this, "_stopThread");
+}
+
+void FatalTimeoutThread::_startTimer()
+{
+ exit(StartTimer);
+}
+
+void FatalTimeoutThread::_stopTimer()
+{
+ exit(DidNotTimeOut);
+}
+
+void FatalTimeoutThread::_stopThread()
+{
+ exit(Exit);
+}
+
+void FatalTimeoutThread::run()
+{
+ QTimer t;
+ connect(&t, SIGNAL(timeout()), this, SLOT(quit()));
+ QTimer::singleShot(0, this, SLOT(enteredEventLoop()));
+
+ int code;
+ do {
+ code = exec();
+ switch (code) {
+ case TimedOut:
+ QTest::qFail(qPrintable(QString("Test did not finish within the allowed %1 ms").arg(m_timeout)), "Unknown file", 0);
+#if defined(Q_OS_UNIX) && !defined(Q_OS_SYMBIAN)
+ longjmp(segfault_jmp, 1);
+#endif
+ _exit(0);
+ case DidNotTimeOut:
+ t.stop();
+ break;
+ case StartTimer:
+ t.setInterval(m_timeout);
+ t.start();
+ break;
+ default: break;
+ }
+ } while(code != Exit);
+}
+
+int QTimedTest::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
+{
+ if (!realTimer) {
+ have_init = isTimeableSlot(metaObject()->method(metaObject()->indexOfMethod("init()")));
+ have_cleanup = isTimeableSlot(metaObject()->method(metaObject()->indexOfMethod("cleanup()")));
+ realTimer = new RealTimeTimer;
+#if defined(Q_OS_UNIX) && !defined(Q_OS_SYMBIAN)
+ cpuTimer = new ProcessorTimeTimer;
+#endif
+ }
+
+ if (pass_through)
+ return real_qt_metacall(_c, _id, _a);
+
+ bool timed = isTimeableSlot(metaObject()->method(_id));
+ bool is_init = (_id == metaObject()->indexOfMethod("init()"));
+ bool is_cleanup = (_id == metaObject()->indexOfMethod("cleanup()"));
+ bool is_single = (_id == metaObject()->indexOfMethod("initTestCase()") ||
+ _id == metaObject()->indexOfMethod("cleanupTestCase()"));
+
+ bool should_start_timer = timed && (is_single || is_init || (!have_init && !is_cleanup));
+ bool should_stop_timer = timed && (is_single || (!is_init && (!have_cleanup || is_cleanup)));
+
+ if (should_start_timer) {
+ if (timeout_thread) {
+ if (timeout_thread != timeout_thread->thread()) {
+ timeout_thread->moveToThread(timeout_thread);
+ timeout_thread->start();
+ while (!timeout_thread->inEventLoop()) {
+ timeout_thread->wait(50);
+ }
+ }
+ timeout_thread->startTimer();
+ }
+ realTimer->start();
+ if (cpuTimer) cpuTimer->start();
+ }
+
+ pass_through = true;
+ int ret = this->qt_metacall(_c, _id, _a);
+ pass_through = false;
+
+ if (should_stop_timer) {
+ if (print_times) QDebug(QtDebugMsg) <<
+ qPrintable(
+ QString("Test time: %1 ms CPU, %2 ms real")
+ .arg(cpuTimer ? cpuTimer->elapsed() : -1)
+ .arg(realTimer->elapsed())
+ );
+ if (timeout_thread) timeout_thread->stopTimer();
+ }
+
+ return ret;
+}
+
+static const uint qt_meta_data_QTimedTest[] = {
+
+ // content:
+ 1, // revision
+ 0, // classname
+ 0, 0, // classinfo
+ 0, 0, // methods
+ 0, 0, // properties
+ 0, 0, // enums/sets
+
+ 0 // eod
+};
+
+static const char qt_meta_stringdata_QTimedTest[] = {
+ "QTimedTest\0"
+};
+
+const QMetaObject QTimedTest::staticMetaObject = {
+ { &QObject::staticMetaObject, qt_meta_stringdata_QTimedTest,
+ qt_meta_data_QTimedTest, 0 }
+};
+
+const QMetaObject *QTimedTest::metaObject() const
+{
+ return &staticMetaObject;
+}
+
+void *QTimedTest::qt_metacast(const char *_clname)
+{
+ if (!_clname) return 0;
+ if (!strcmp(_clname, qt_meta_stringdata_QTimedTest))
+ return static_cast<void*>(const_cast< QTimedTest*>(this));
+ return QObject::qt_metacast(_clname);
+}
+
+int QTimedTest::real_qt_metacall(QMetaObject::Call _c, int _id, void **_a)
+{
+ _id = QObject::qt_metacall(_c, _id, _a);
+ if (_id < 0)
+ return _id;
+ return _id;
+}
+
+#include "qabstracttest.moc"