diff options
author | dac <qt-info@nokia.com> | 2010-07-28 11:28:18 +1000 |
---|---|---|
committer | dac <qt-info@nokia.com> | 2010-07-28 11:28:18 +1000 |
commit | 8210814717e2950e8fb552a608ecc7e3e8e7beff (patch) | |
tree | 62084690caf92a4974f246dd54cfaa1a8b4f39b3 | |
parent | 1169253f038774d4a6e2ce8b07b8ec9762ba009d (diff) |
Make libqsystemtest not depend on libqtuitest. Remove duplicate code
and conditional compilation required for Creator integration. Plus
other cleanups.
-rw-r--r-- | interpreter/main.cpp | 7 | ||||
-rw-r--r-- | interpreter/qscriptsystemtest.cpp | 122 | ||||
-rw-r--r-- | interpreter/qscriptsystemtest.h | 9 | ||||
-rw-r--r-- | libqsystemtest/gracefulquit.cpp | 192 | ||||
-rw-r--r-- | libqsystemtest/libqsystemtest.pro | 10 | ||||
-rw-r--r-- | libqsystemtest/qabstracttest.cpp | 169 | ||||
-rw-r--r-- | libqsystemtest/qabstracttest.h | 9 | ||||
-rw-r--r-- | libqsystemtest/qsystemtest.cpp | 234 | ||||
-rw-r--r-- | libqsystemtest/qsystemtest.h | 9 | ||||
-rw-r--r-- | libqsystemtest/qsystemtestmaster_p.h | 35 | ||||
-rw-r--r-- | libqsystemtest/qtestprotocol.cpp | 1182 | ||||
-rw-r--r-- | libqsystemtest/qtestprotocol_p.h | 207 | ||||
-rw-r--r-- | libqsystemtest/qtuitestglobal.h (renamed from libqsystemtest/gracefulquit.h) | 27 | ||||
-rw-r--r-- | libqsystemtest/recordevent_p.h | 135 |
14 files changed, 1707 insertions, 640 deletions
diff --git a/interpreter/main.cpp b/interpreter/main.cpp index 58fda65..3867727 100644 --- a/interpreter/main.cpp +++ b/interpreter/main.cpp @@ -40,9 +40,6 @@ ****************************************************************************/ #include "qscriptsystemtest.h" -#ifndef Q_OS_SYMBIAN -# include <gracefulquit.h> -#endif #include <QApplication> int main(int argc, char *argv[]) @@ -50,7 +47,6 @@ int main(int argc, char *argv[]) bool guiEnabled = true; // If in auto mode, don't need a GUI connection. - // This allows us to run system tests without an X server running. for (int i = 1; i < argc; ++i) { if (!strcasecmp(argv[i], "-auto")) { guiEnabled = false; @@ -58,9 +54,6 @@ int main(int argc, char *argv[]) } QApplication app(argc, argv, guiEnabled); -#if defined(Q_OS_UNIX) && !defined(Q_OS_SYMBIAN) - GracefulQuit::install(&app); -#endif QScriptSystemTest tc; return tc.exec(argc, argv); } diff --git a/interpreter/qscriptsystemtest.cpp b/interpreter/qscriptsystemtest.cpp index ad7f32b..2b6a2f4 100644 --- a/interpreter/qscriptsystemtest.cpp +++ b/interpreter/qscriptsystemtest.cpp @@ -490,7 +490,6 @@ bool QScriptSystemTest::setQueryError( const QString &errString ) return ret; } -#ifdef QTCREATOR_QTEST int QScriptSystemTest::runTest(const QString &fname, const QStringList ¶meters, const QStringList &environment) { @@ -569,95 +568,6 @@ int QScriptSystemTest::runTest(const QString &fname, const QStringList ¶mete // We don't want test failures to affect the exit code. return (retval < 0) ? retval : 0; } -#endif - -#ifndef QTCREATOR_QTEST -int QScriptSystemTest::runTest(int argc, char *argv[]) -{ - if (argc > 1) filename = argv[1]; - - if (filename.isEmpty()) qFatal("No test script specified."); - - /* Consume the first argument */ - argv[1] = argv[0]; - char **c_argv = &argv[1]; - int c_argc = argc-1; - - { - QFileInfo fi(filename); - if (!fi.exists()) qFatal("%s doesn't exist", qPrintable(filename)); - filename = fi.absoluteFilePath(); - } - - QFile file(filename); - if (!file.open(QFile::ReadOnly)) qFatal("Can't open %s", qPrintable(filename)); - QTextStream stream(&file); - QString script = stream.readAll(); - - ScriptPreprocessor().preprocess(script); - - setupEnums(&m_engine); - - // include() imports scripts directly into the parent script. - m_engine.globalObject().setProperty - ("include", m_engine.newFunction(includeFunction, 1)); - m_engine.globalObject().setProperty - ("setFlags", m_engine.newFunction(setFlags, 3)); - - - m_engine.globalObject().setProperty("_dateToString", m_engine.newFunction(dateToString)); - m_engine.evaluate("_old_date_toString = Date.prototype.toString;" - "Date.prototype.toString = function() {" - " if (arguments[0] == undefined)" - " return _old_date_toString.apply(this, arguments);" - " return _dateToString.apply(this, arguments);" - "}"); - - m_engine.globalObject().setProperty("ParentTestObject", m_engine.newQObject(this)); - m_engine.globalObject().setProperty("ParentTestMetaObject", m_engine.newQMetaObject(metaObject())); - - loadBuiltins(m_engine); - importIntoGlobalNamespace(m_engine, "ParentTestObject"); - - // Allow shebangs without giving syntax errors. - if (script.startsWith("#!")) script.prepend("//"); - script.prepend("with(ParentTestMetaObject){"); - script.append("\n}"); - - QtScriptTest tc(filename, script, &m_engine); - testObject = &tc; - - qScriptRegisterMetaType(&m_engine, variantToScriptValue, variantFromScriptValue); - qScriptRegisterSequenceMetaType<QList<qint64> >(&m_engine); - - // Only set up the test data path if not explicitly set by user - if (!QCoreApplication::arguments().contains("-data")) { - setupTestDataPath(qPrintable(filename)); - } - - enableQueryWarnings(false); - - // If we get here, the syntax of the script is definitely OK - // (a syntax error causes a qFatal in the QtScriptTest ctor). - if (m_checkOnly) - return 0; - - // If an IDE is connected, set the agent to enable script debugging - if (testIDE() && testIDE()->isConnected()) { - m_engine.setAgent(m_agent); - } - - int retval = QTest::qExec(testObject, c_argc, c_argv); - - testObject = 0; - - // After a full test run, QTestLib sometimes returns 0 or sometimes returns - // the number of test failures, depending on how it was compiled. In both - // cases, a negative number denotes an error. - // We don't want test failures to affect the exit code. - return (retval < 0) ? retval : 0; -} -#endif /*! \internal @@ -714,22 +624,22 @@ QVariantMap QScriptSystemTest::sendRaw(const QString& event, const QScriptValue& return ret; } -#ifndef QTCREATOR_QTEST +//#ifndef QTCREATOR_QTEST /*! \internal Print any special usage information which should be shown when test is launched with -help. */ -void QScriptSystemTest::printUsage(int argc, char *argv[]) const +void QScriptSystemTest::printUsage() const { - QSystemTest::printUsage(argc, argv); + QSystemTest::printUsage(); qWarning( " Script options:\n" " -c : Check the syntax of the test only. Returns a non-zero exit code if the test\n" " contains any syntax errors.\n" ); } -#endif +//#endif /*! \internal @@ -855,27 +765,19 @@ void QScriptSystemTest::processMessage(const QTestMessage& message) \internal Processes the command line parameters. */ -void QScriptSystemTest::processCommandLine( int &argc, char *argv[] ) +void QScriptSystemTest::processCommandLine( QStringList &args ) { - int offset = 0; - - for (int i=1; i<argc; ++i) { - - if ( !strcasecmp(argv[i], "-c") ) { - argv[i] = 0; - offset++; + QMutableStringListIterator it(args); + + while (it.hasNext()) { + QString arg = it.next(); + if (!arg.compare("-c", Qt::CaseInsensitive)) { m_checkOnly = true; - - } else { - if (offset > 0) { - argv[i-offset] = argv[i]; - argv[i] = 0; - } + it.remove(); } } - argc-=offset; - QSystemTest::processCommandLine(argc, argv); + QSystemTest::processCommandLine(args); } void QScriptSystemTest::scriptPositionChange(qint64 scriptId, int line, int column) diff --git a/interpreter/qscriptsystemtest.h b/interpreter/qscriptsystemtest.h index 61d8429..9624fd0 100644 --- a/interpreter/qscriptsystemtest.h +++ b/interpreter/qscriptsystemtest.h @@ -65,10 +65,8 @@ public: void scriptPositionChange(qint64, int, int); void scriptContextChange(bool); -#ifdef QTCREATOR_QTEST virtual int runTest(const QString &fname, const QStringList ¶meters, const QStringList &environment); -#endif public slots: virtual bool fail(QString const &message); @@ -91,11 +89,8 @@ protected: virtual bool setQueryError( const QTestMessage &message ); virtual bool setQueryError( const QString &errString ); -#ifndef QTCREATOR_QTEST - virtual int runTest(int argc, char *argv[]); - virtual void printUsage(int,char*[]) const; -#endif - virtual void processCommandLine(int&, char*[]); + virtual void printUsage() const; + virtual void processCommandLine(QStringList&); virtual bool isFailExpected(); diff --git a/libqsystemtest/gracefulquit.cpp b/libqsystemtest/gracefulquit.cpp deleted file mode 100644 index a50fb94..0000000 --- a/libqsystemtest/gracefulquit.cpp +++ /dev/null @@ -1,192 +0,0 @@ -/**************************************************************************** -** -** 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 "gracefulquit.h" - -#ifdef Q_OS_UNIX - -#include <QTimer> -#include <QSocketNotifier> - -#include <signal.h> -#include <errno.h> -#include <unistd.h> - -int graceful_quit_fds[2]; -sighandler_t graceful_quit_old_handlers[32]; -int graceful_quit_signal; - -void graceful_quit_alarm_handler(int) -{ - ::kill(::getpid(), graceful_quit_signal); - qFatal("Terminated."); -} - -void graceful_quit_signal_handler(int sig) -{ - graceful_quit_signal = sig; - - // Replace the old handlers. - signal(SIGINT, graceful_quit_old_handlers[SIGINT]); - signal(SIGQUIT, graceful_quit_old_handlers[SIGQUIT]); - signal(SIGTERM, graceful_quit_old_handlers[SIGTERM]); - signal(SIGHUP, graceful_quit_old_handlers[SIGHUP]); - - /* - Write to the monitored pipe to wake up any watching socket notifiers. - */ - char byte = 0x01; - write(graceful_quit_fds[1], &byte, sizeof(byte)); -} - -/* - This class ensures that the process really dies if it fails to gracefully - quit (e.g., it's stuck in a nested event loop). -*/ -class QKillTimer : public QObject -{ -Q_OBJECT -public: - QKillTimer(QSocketNotifier*); - -private slots: - void on_activated(); - -private: - QSocketNotifier* m_notifier; -}; - -QKillTimer::QKillTimer(QSocketNotifier* parent) - : QObject(parent), - m_notifier(parent) -{ - connect(m_notifier, SIGNAL(activated(int)), this, SLOT(on_activated())); -} - -void QKillTimer::on_activated() -{ - m_notifier->setEnabled(false); - signal(SIGALRM, graceful_quit_alarm_handler); - ::alarm(1); -} - -/*! - \class GracefulQuit - \inpublicgroup QtUiTestExtension - \brief The GracefulQuit class provides a method for quitting applications - gracefully when a terminating unix signal is received. -*/ - -/*! - Installs a graceful quit handler on \a object. - - \a object must have a quit() slot, such as QCoreApplication. - When a Unix signal occurs which would normally cause a termination, such - as SIGINT or SIGTERM, the quit() slot will be invoked on \a object. - - Example: - \code - int main(int argc, char** argv) - { - QApplication app(argc, argv); - - // Ensure we shut down sanely upon receiving SIGINT etc. - GracefulQuit::install(&app); - - // Do regular stuff... - MyWidget widget; - widget.show(); - - // If we get a signal, all objects will be destroyed as they should be. - return app.exec(); - } - \endcode -*/ -void GracefulQuit::install(QObject* object) -{ - Q_ASSERT(object); - - /* - Create a pipe for internal use. - When the pipe is written into, that means we've received an exit - signal. - */ - static bool made_pipe = false; - if (!made_pipe) { - if (-1 == pipe(graceful_quit_fds)) { - qWarning("GracefulQuit::install(): pipe() failed: %s", strerror(errno)); - return; - } else { - made_pipe = true; - - /* - Install the signal handler. - */ - graceful_quit_old_handlers[SIGINT] = signal(SIGINT, graceful_quit_signal_handler); - graceful_quit_old_handlers[SIGQUIT] = signal(SIGQUIT, graceful_quit_signal_handler); - graceful_quit_old_handlers[SIGTERM] = signal(SIGTERM, graceful_quit_signal_handler); - graceful_quit_old_handlers[SIGHUP] = signal(SIGHUP, graceful_quit_signal_handler); - } - } - - /* - Watch the readable fd. When it can be read from, it's time for us - to quit. - */ - static QSocketNotifier* sn = new QSocketNotifier - (graceful_quit_fds[0], QSocketNotifier::Read, object); - - // Give the application 1 second to gracefully quit. - static QKillTimer* kt = new QKillTimer(sn); - Q_UNUSED(kt); - - if (!object->connect(sn, SIGNAL(activated(int)), SLOT(quit()))) - Q_ASSERT(0); - -} - -#else -void GracefulQuit::install(QObject*) -{} -#endif - -#include "gracefulquit.moc" - diff --git a/libqsystemtest/libqsystemtest.pro b/libqsystemtest/libqsystemtest.pro index f12c431..83bf8c2 100644 --- a/libqsystemtest/libqsystemtest.pro +++ b/libqsystemtest/libqsystemtest.pro @@ -11,14 +11,13 @@ SEMI_PRIVATE_HEADERS += \ qtestverifydlg_p.h HEADERS +=\ - gracefulquit.h \ qabstracttest.h \ qsystemtest.h \ qtestide.h \ + qtestprotocol.h \ testdevicecontrol.h SOURCES +=\ - gracefulquit.cpp \ qabstracttest.cpp \ qtestide.cpp \ qtestremote.cpp \ @@ -26,6 +25,7 @@ SOURCES +=\ qsystemtest.cpp \ qsystemtest_p.cpp \ qsystemtestmaster.cpp \ + qtestprotocol.cpp \ testdevicecontrol.cpp HEADERS +=\ @@ -91,7 +91,7 @@ TEMPLATE=lib TARGET=qsystemtest TARGET=$$qtLibraryTarget($$TARGET) HEADERS*=$$SEMI_PRIVATE_HEADERS $$PRIVATE_HEADERS -INCLUDEPATH+=$$SRCROOT/libqtuitest +#INCLUDEPATH+=$$SRCROOT/libqtuitest CONFIG+=qtestlib QT+=network script LIBS+=-L$$BUILDROOT/lib -lBotan @@ -105,14 +105,10 @@ unix:!symbian { symbian { TARGET.EPOCALLOWDLLDATA=1 TARGET.CAPABILITY += AllFiles ReadDeviceData ReadUserData SwEvent WriteUserData - SOURCES-=gracefulquit.cpp - HEADERS-=gracefulquit.h LIBS+=-L$$OUT_PWD -lqtuitest -lws32 -leuser -lcone } win32 { - SOURCES-=gracefulquit.cpp - HEADERS-=gracefulquit.h CONFIG(debug,debug|release): LIBS+=-L$$BUILDROOT/libqtuitest -lqtuitestd CONFIG(release,debug|release):LIBS+=-L$$BUILDROOT/libqtuitest -lqtuitest target.path=$$[QT_INSTALL_BINS] diff --git a/libqsystemtest/qabstracttest.cpp b/libqsystemtest/qabstracttest.cpp index 2353e6d..f441938 100644 --- a/libqsystemtest/qabstracttest.cpp +++ b/libqsystemtest/qabstracttest.cpp @@ -322,25 +322,22 @@ QString QAbstractTest::currentTestFunction( bool fullName ) const int QAbstractTest::exec( int argc, char* argv[], char* filename ) { setupTestDataPath(filename); - QByteArray defOpt = qgetenv("QTUITEST_DEFAULT_OPTIONS"); - QList<QByteArray> defaultOptions; + + QStringList options; + for (int i = 1; i < argc; ++i) options << argv[i]; + + QString defOpt = qgetenv("QTUITEST_DEFAULT_OPTIONS"); if (defOpt.length()) { - defaultOptions = defOpt.split(' '); + options << defOpt.split(' '); } - int defArgc = defaultOptions.length(); - - int _argc(argc+defArgc); - char **_argv = new char*[argc+defArgc + 1]; - for (int i = 0; i < argc; ++i) _argv[i] = argv[i]; - for (int j = 0; j < defArgc; ++j) _argv[argc+j] = defaultOptions[j].data(); - processCommandLine(_argc, _argv); - int ret = runTest(_argc, _argv); - delete[] _argv; - return ret; + + processCommandLine(options); + QString script = options.first(); + return runTest(script, options, QStringList()); } #endif -#ifndef QTCREATOR_QTEST +//#ifndef QTCREATOR_QTEST /* \internal Print a usage message. @@ -348,10 +345,11 @@ int QAbstractTest::exec( int argc, char* argv[], char* filename ) Any subclass which overrides processCommandLine() should also override this function and provide documentation for their arguments. */ -void QAbstractTest::printUsage( int argc, char* argv[] ) const +void QAbstractTest::printUsage() const { - qWarning( " Usage:\n" - " %s [file] [options] [testfunction[:datatag]]...\n" + 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" @@ -384,109 +382,65 @@ void QAbstractTest::printUsage( int argc, char* argv[] ) const " 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" - , (argc) ? argv[0] : "test"); + ); } -#endif +//#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( int &argc, char* argv[] ) -{ - int offset = 0; - for (int i = 1; i < argc; ++i) { - if ( !strcasecmp(argv[i], "-data") ) { - argv[i] = 0; - offset += 2; - ++i; - if ( i < argc ) { - setBaseDataPath(QString(argv[i])); - argv[i] = 0; - } - - } else if ( !strcasecmp(argv[i], "-v") || - !strcasecmp(argv[i], "-v1") || - !strcasecmp(argv[i], "-v2") ) { +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 - if (!strcasecmp(argv[i], "-v")) { - argv[i] = 0; - offset++; - } Autotest_QLog::m_enabled = true; - - } else if ( !strcasecmp(argv[i], "-learn") ) { - argv[i] = 0; - offset++; + } else if ( !arg.compare("-learn", Qt::CaseInsensitive) ) { + it.remove(); setLearnMode(LearnNew); - - } else if ( !strcasecmp(argv[i], "-learn-all") ) { - argv[i] = 0; - offset++; + } else if ( !arg.compare("-learn-all", Qt::CaseInsensitive) ) { + it.remove(); setLearnMode(LearnAll); - - } else if ( !strcasecmp(argv[i], "-failempty") ) { - argv[i] = 0; - offset++; + } else if ( !arg.compare("-failempty", Qt::CaseInsensitive) ) { + it.remove(); d->failEmptyTest = true; - - } else if ( !strcasecmp(argv[i], "-timed") ) { - argv[i] = 0; - offset++; + } else if ( !arg.compare("-timed", Qt::CaseInsensitive) ) { + it.remove(); QTimedTest::pass_through = false; QTimedTest::print_times = true; - - } else if ( !strcasecmp(argv[i], "-maxtime") ) { - argv[i] = 0; - offset += 2; - ++i; - if ( i < argc ) { - QTimedTest::pass_through = false; - bool ok; - timeout_thread = new FatalTimeoutThread; - timeout_thread->setTimeout(QString::fromAscii(argv[i]).toInt(&ok)); - if (!ok) qFatal("Invalid parameter to -maxtime"); - argv[i] = 0; - } - - } else if ( !strcasecmp(argv[i], "-help") || - !strcasecmp(argv[i], "--help") || - !strcasecmp(argv[i], "-h") ) { - argv[i] = 0; - offset++; + } 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(argc-offset, argv); + printUsage(); exit(0); #endif - - // Silently ignore a few system test specific arguments. - // For compatibility, we'll silently ignore these so that - // the same arguments can be specified for unit and system tests. - } else if ( !strcasecmp(argv[i], "-autip") - || !strcasecmp(argv[i], "-authost") - || !strcasecmp(argv[i], "-autport") - || !strcasecmp(argv[i], "-remote") ) { - argv[i] = 0; - offset += 2; - ++i; - argv[i] = 0; - - } else if ( !strcasecmp(argv[i], "-keepaut") - || !strcasecmp(argv[i], "-silentaut") - || !strcasecmp(argv[i], "-auto") - || !strcasecmp(argv[i], "-demomode") ) { - argv[i] = 0; - offset++; - - } else { - if (offset > 0) { - argv[i-offset] = argv[i]; - argv[i] = 0; - } } - } - argc-=offset; + } } #if defined(Q_OS_UNIX) && !defined(Q_OS_SYMBIAN) @@ -501,7 +455,7 @@ void handle_segfault(int signum) } #endif -# ifndef QTCREATOR_QTEST +//# ifndef QTCREATOR_QTEST /* \internal Run test with arguments \a argc, \a argv, and return an exit code. @@ -509,16 +463,19 @@ void handle_segfault(int signum) using QTest::qExec(). Subclasses may reimplement this function to provide other behaviour. */ -int QAbstractTest::runTest(int argc, char *argv[]) +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, argc, argv ); + return QTest::qExec( this, args ); return -1; } -#endif +//#endif /*! \internal diff --git a/libqsystemtest/qabstracttest.h b/libqsystemtest/qabstracttest.h index 10a2a14..2a20d09 100644 --- a/libqsystemtest/qabstracttest.h +++ b/libqsystemtest/qabstracttest.h @@ -131,11 +131,10 @@ public slots: #ifndef Q_QDOC protected: -# ifndef QTCREATOR_QTEST - virtual int runTest(int argc, char *argv[]); - virtual void printUsage( int argc, char *argv[] ) const; -# endif - virtual void processCommandLine( int &argc, char *argv[] ); + virtual int runTest(const QString &fname, const QStringList ¶meters, + const QStringList &environment); + virtual void printUsage() const; + virtual void processCommandLine(QStringList&); virtual void setupTestDataPath(const char *filename); #endif diff --git a/libqsystemtest/qsystemtest.cpp b/libqsystemtest/qsystemtest.cpp index e8dae1b..f6e0c36 100644 --- a/libqsystemtest/qsystemtest.cpp +++ b/libqsystemtest/qsystemtest.cpp @@ -42,7 +42,7 @@ #include <qsystemtest.h> #include "qsystemtestmaster_p.h" #include "qtuitest_config.h" -#include "gracefulquit.h" +//#include "gracefulquit.h" #include "ui_recorddlg.h" #ifdef QTCREATOR_QTEST @@ -2786,21 +2786,17 @@ void QSystemTest::expectApplicationClose( bool value ) \internal Processes the command line parameters. */ -void QSystemTest::processCommandLine( int &argc, char *argv[] ) -{ - int offset = 0; - - // Whenever we discover an option that 'we' understand, eat away the option (and its parameters) so that QTest doesn't get - // confused by them. - for (int i=1; i<argc; ++i) { - if ( !strcasecmp(argv[i], "-remote") ) { - argv[i] = 0; - ++offset; - - if ( i+1 >= argc || !strlen(argv[i+1]) ) - qFatal("Expected a host:port argument after -remote"); - - QString host_port( argv[i+1] ); +void QSystemTest::processCommandLine( QStringList &args ) +{ + QMutableStringListIterator it(args); + + while (it.hasNext()) { + QString arg = it.next(); + if (!arg.compare("-remote", Qt::CaseInsensitive)) { + it.remove(); + if (!it.hasNext()) qFatal("Expected a value after %s", qPrintable(arg)); + QString host_port = it.next(); + it.remove(); QStringList host_port_parts = host_port.split(":"); bool ok = (host_port_parts.count() == 2); @@ -2813,158 +2809,78 @@ void QSystemTest::processCommandLine( int &argc, char *argv[] ) if (!ok) qFatal("'%s' is not a valid host:port argument", qPrintable(host_port)); - argv[i+1] = 0; - ++offset; - ++i; - testIDE()->openRemote( host, port ); - connect(testIDE(), SIGNAL(abort()), this, SLOT(abortTest())); - - } else if ( !strcasecmp(argv[i], "-autip") || !strcasecmp(argv[i], "-authost") ) { - char *given_arg = argv[i]; // so we can output 'autip' or 'authost' in error messages - - argv[i] = 0; - ++offset; - if ( i+1 >= argc || !strlen(argv[i+1]) ) - qFatal("Expected a host specifier after %s", given_arg); - - ssh_param.host = argv[i+1]; - argv[i+1] = 0; - ++offset; - ++i; + connect(testIDE(), SIGNAL(abort()), this, SLOT(abortTest())); + } else if ( !arg.compare("-autip", Qt::CaseInsensitive) || !arg.compare("-authost", Qt::CaseInsensitive) ) { + it.remove(); + if (!it.hasNext()) qFatal("Expected a value after %s", qPrintable(arg)); + ssh_param.host = it.next(); + it.remove(); ssh_param.timeout = 20000; - - } else if ( !strcasecmp(argv[i], "-username") ) { - char *given_arg = argv[i]; - - argv[i] = 0; - ++offset; - if ( i+1 >= argc || !strlen(argv[i+1]) ) - qFatal("Expected a user name after %s", given_arg); - - ssh_param.uname = argv[i+1]; - argv[i+1] = 0; - ++offset; - ++i; - - } else if ( !strcasecmp(argv[i], "-pwd") ) { - char *given_arg = argv[i]; - - argv[i] = 0; - ++offset; - if ( i+1 >= argc || !strlen(argv[i+1]) ) - qFatal("Expected a password after %s", given_arg); - - ssh_param.pwd = argv[i+1]; + } else if ( !arg.compare("-username", Qt::CaseInsensitive) ) { + it.remove(); + if (!it.hasNext()) qFatal("Expected a value after %s", qPrintable(arg)); + ssh_param.uname = it.next(); + it.remove(); + } else if ( !arg.compare("-pwd", Qt::CaseInsensitive) ) { + it.remove(); + if (!it.hasNext()) qFatal("Expected a value after %s", qPrintable(arg)); + ssh_param.pwd = it.next(); + it.remove(); ssh_param.authType = Core::SshConnectionParameters::AuthByPwd; - argv[i+1] = 0; - ++offset; - ++i; - ssh_param.privateKeyFile = ""; - - } else if ( !strcasecmp(argv[i], "-private-key") ) { - char *given_arg = argv[i]; - - argv[i] = 0; - ++offset; - if ( i+1 >= argc || !strlen(argv[i+1]) ) - qFatal("Expected a private key file after %s", given_arg); - - ssh_param.privateKeyFile = argv[i+1]; + } else if ( !arg.compare("-private-key", Qt::CaseInsensitive) ) { + it.remove(); + if (!it.hasNext()) qFatal("Expected a value after %s", qPrintable(arg)); + ssh_param.privateKeyFile = it.next(); + it.remove(); ssh_param.pwd = ""; ssh_param.authType = Core::SshConnectionParameters::AuthByKey; - argv[i+1] = 0; - ++offset; - ++i; - - } else if ( !strcasecmp(argv[i], "-autport") ) { - argv[i] = 0; - ++offset; - if ( i+1 >= argc || !strlen(argv[i+1]) ) - qFatal("Expected a port specifier after -autport"); - + } else if ( !arg.compare("-autport", Qt::CaseInsensitive) ) { + it.remove(); + if (!it.hasNext()) qFatal("Expected a value after %s", qPrintable(arg)); bool ok; - m_aut_port = QString(argv[i+1]).toUShort( &ok ); + m_aut_port = it.next().toUShort( &ok ); if (!ok) - qFatal("%s is not a valid port specifier", argv[i+1]); - - argv[i+1] = 0; - ++offset; - ++i; - - } else if ( !strcasecmp(argv[i], "-keepaut") ) { - argv[i] = 0; - ++offset; + qFatal("%s is not a valid port specifier", qPrintable(it.value())); + it.remove(); + } else if ( !arg.compare("-keepaut", Qt::CaseInsensitive) ) { + it.remove(); m_keep_aut = true; - - } else if ( !strcasecmp(argv[i], "-silentaut") ) { - argv[i] = 0; - offset++; + } else if ( !arg.compare("-silentaut", Qt::CaseInsensitive) ) { + it.remove(); m_silent_aut = true; - - } else if ( !strcasecmp(argv[i], "-noaut") ) { - argv[i] = 0; - offset++; + } else if ( !arg.compare("-noaut", Qt::CaseInsensitive) ) { + it.remove(); m_no_aut = true; - - } else if ( !strcasecmp(argv[i], "-auto") ) { - argv[i] = 0; - offset++; + } else if ( !arg.compare("-auto", Qt::CaseInsensitive) ) { + it.remove(); m_auto_mode = true; - - } else if ( !strcasecmp(argv[i], "-force-manual") ) { - argv[i] = 0; - offset++; + } else if ( !arg.compare("-force-manual", Qt::CaseInsensitive) ) { + it.remove(); m_run_as_manual_test = true; - - } else if ( !strcasecmp(argv[i], "-env") ) { - argv[i] = 0; - offset++; - - if ( i+1 >= argc || !strlen(argv[i+1]) ) - qFatal("Expected a string after -env"); - - m_env << QString::fromLocal8Bit(argv[i+1]); - argv[i+1] = 0; - ++offset; - ++i; - } else if ( !strcasecmp(argv[i], "-targetID") ) { - argv[i] = 0; - offset++; - - if ( i+1 >= argc || !strlen(argv[i+1]) ) - qFatal("Expected a string after -targetID"); - - m_targetID = QString::fromLocal8Bit(argv[i+1]); - argv[i+1] = 0; - ++offset; - ++i; - } else if ( !strcasecmp(argv[i], "-demomode") ) { - argv[i] = 0; - offset++; + } else if ( !arg.compare("-env", Qt::CaseInsensitive) ) { + it.remove(); + if (!it.hasNext()) qFatal("Expected a value after %s", qPrintable(arg)); + m_env << it.next(); + it.remove(); + } else if ( !arg.compare("-targetid", Qt::CaseInsensitive) ) { + it.remove(); + if (!it.hasNext()) qFatal("Expected a value after %s", qPrintable(arg)); + m_targetID = it.next(); + it.remove(); + } else if ( !arg.compare("-demomode", Qt::CaseInsensitive) ) { + it.remove(); m_demo_mode = true; - - } else if ( !strcasecmp(argv[i], "-verbose-perf") ) { - argv[i] = 0; - offset++; + } else if ( !arg.compare("-verbose-pref", Qt::CaseInsensitive) ) { + it.remove(); m_verbose_perf = true; - - } else if ( !strcasecmp(argv[i], "-v") ) { - argv[i] = 0; - offset++; + } else if ( !arg.compare("-v", Qt::CaseInsensitive) ) { + it.remove(); m_verbose = true; - - } else { - if (offset > 0) { - argv[i-offset] = argv[i]; - argv[i] = 0; - } } } - argc-=offset; - - QAbstractTest::processCommandLine(argc, argv); + QAbstractTest::processCommandLine(args); if (m_auto_mode && learnMode() != LearnNone) { qWarning("Can't learn in auto mode; learn options ignored."); @@ -3142,15 +3058,15 @@ void QSystemTest::abortTest() #endif } -#ifndef QTCREATOR_QTEST +//#ifndef QTCREATOR_QTEST /*! \internal Print any special usage information which should be shown when test is launched with -help. */ -void QSystemTest::printUsage(int argc, char *argv[]) const +void QSystemTest::printUsage() const { - QAbstractTest::printUsage(argc, argv); + QAbstractTest::printUsage(); qWarning( " System test options:\n" " -authost <host> : Specify the IP address or host name of the machine on which the AUT is running.\n" @@ -3182,7 +3098,7 @@ void QSystemTest::printUsage(int argc, char *argv[]) const , DEFAULT_AUT_PORT ); } -#endif +//#endif #ifndef Q_QDOC /* @@ -3371,18 +3287,20 @@ bool QSystemTest::setQueryError( const QTestMessage &message ) return false; // query is NOT successfull } -#ifndef QTCREATOR_QTEST +//#ifndef QTCREATOR_QTEST /*! \internal Launch AUT and run the test. */ -int QSystemTest::runTest(int argc, char *argv[]) { +int QSystemTest::runTest(const QString &fname, const QStringList &args, + const QStringList &environment) +{ // Try to launch the aut script, and if successful, start executing the test. // QTest::qExec will also start the application event loop, and will not return until the tests have finished. - return QAbstractTest::runTest(argc, argv); + return QAbstractTest::runTest(fname, args, environment); } -#endif +//#endif /*! \internal diff --git a/libqsystemtest/qsystemtest.h b/libqsystemtest/qsystemtest.h index 7201249..b2add54 100644 --- a/libqsystemtest/qsystemtest.h +++ b/libqsystemtest/qsystemtest.h @@ -396,11 +396,10 @@ protected slots: void abortTest(); protected: -#ifndef QTCREATOR_QTEST - virtual void printUsage(int,char*[]) const; - virtual int runTest(int,char*[]); -#endif - virtual void processCommandLine(int&,char*[]); + virtual void printUsage() const; + virtual int runTest(const QString &fname, const QStringList ¶meters, + const QStringList &environment); + virtual void processCommandLine(QStringList&); virtual void applicationStandardOutput(QList<QByteArray> const&); virtual void applicationStandardError(QList<QByteArray> const&); diff --git a/libqsystemtest/qsystemtestmaster_p.h b/libqsystemtest/qsystemtestmaster_p.h index 57c8377..f82bbf0 100644 --- a/libqsystemtest/qsystemtestmaster_p.h +++ b/libqsystemtest/qsystemtestmaster_p.h @@ -71,40 +71,5 @@ private: QSystemTest *test_case; }; -/* -class QTUITEST_EXPORT QSystemTestMasterServer: public QTestServerSocket -{ - Q_OBJECT -public: - QSystemTestMasterServer( quint16 port, QSystemTest *testCase ); - virtual ~QSystemTestMasterServer(); - - //static void delayConnection(); - - bool waitForApp( const QString &appName, uint timeOut ); - QSystemTestMaster *findApp( const QString &appName ); - QSystemTestMaster *getApp( uint index ); - uint appCount() { return app_count; }; - -protected: - virtual void onNewConnection( int socket ); - bool appendApp( QSystemTestMaster *app ); - //static void savePortInfo( int port ); - -protected slots: - void connectionClosed( QTestProtocol *socket ); - -signals: - void newGuiClient( QSystemTestMaster *newApp ); - -private: - // FIXME: Should use QList here - #define MAX_APPS 50 - QSystemTestMaster *apps[MAX_APPS]; - uint app_count; - QSystemTest *test_case; -}; -*/ - #endif diff --git a/libqsystemtest/qtestprotocol.cpp b/libqsystemtest/qtestprotocol.cpp new file mode 100644 index 0000000..9246a28 --- /dev/null +++ b/libqsystemtest/qtestprotocol.cpp @@ -0,0 +1,1182 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +/* + This cpp file contains a number of socket related classes that are used heavily in QtUiTest. + The classes are documented later in the file. +*/ + +#include "qtestprotocol_p.h" + +#include <QApplication> +#include <QString> +#include <QTimer> +#include <QUuid> +#include <QFileInfo> +#include <QDir> +#include <QtNetwork/QTcpSocket> +#include <QtNetwork/QTcpServer> +#include <QtNetwork/QHostAddress> +#include <QtNetwork/QHostInfo> +#include <QPointer> + +#include <QDebug> +#define qLog(A) if (1); else qDebug() + +#if defined(Q_OS_WIN32) && !defined(Q_OS_TEMP) +# include <io.h> +#endif + +static const int CONNECT_TIMEOUT = 20000; + +static const quint32 TEST_MSG_SIGNATURE = 0xEDBAEDBA; +static const quint32 TEST_MSG_END = 0xEDBAABDE; +static const quint8 TEST_MSG_VERSION = 3; +static uint g_unique_id = 0; + +bool waitForSignal(QObject* object, const char* signal, int timeout) +{ +#ifdef QTUITEST_TARGET + return QtUiTest::waitForSignal(object, signal, timeout); +#else + QEventLoop loop; + QTimer dummy; + dummy.setInterval(1000); + if (!QObject::connect(object, signal, &dummy, SLOT(start()))) + return false; + if (!QObject::connect(object, signal, &loop, SLOT(quit()))) + return false; + QTimer::singleShot(timeout, &loop, SLOT(quit())); + loop.exec(); + return dummy.isActive(); +#endif +} + +void wait(int timeout) +{ +#ifdef QTUITEST_TARGET + QtUiTest::wait(timeout); +#else + QEventLoop loop; + QTimer::singleShot(timeout, &loop, SLOT(quit())); + loop.exec(); +#endif +} + +// ******************************************************************************** +// ****************************** QTestMessage ************************************ +// ******************************************************************************** + +/* + \class QTestMessage QTestMessage.h + \inpublicgroup QtUiTestModule + + \brief The QTestMessage class can be used for exchanging messages between separate + processes. The class is never used directly but instead is used by QTestProtocol. + + The class basically wraps a number of strings (i.e. 'event' and 'message') and binary data + (i.e. a bytearray or a file) into a datastream that is sent to a peer using a socket + connection. On the receiving side a QTestMessage instance is decoding the datastream + and performs a number of sanity checks to make it a bit more robost. + + A TCP connection 'should' be reliable but in exceptional cases bytes may be lost. This + would result in the connection becoming useless because all future data reception would + be out of sync. To solve this a 'resync' function is implemented that can re-sync the datastream + by throwing away bytes until the data seems in sync again. The obvious downside is that + at least one message will be lost. + + Message format is as follows: + + Field: Length: Remarks: + + Start signature 4 Fixed value - 0xEDBAEDBA + Protocol version 1 Fixed value - 3 + Message number 2 + Event length 4 Length of following string + Event string Event length QString value + Message length 4 Length of following string + Messaga string Message length QString value + Data length 4 Length of following binary data + File data Data length Binary data + End signature 4 Fixed value - 0xEDBAABDE +*/ + +/* + \internal + + Constructs a default (empty) message. +*/ + +QTestMessage::QTestMessage(QString const &event, QVariantMap const &map) + : m_phase(0) + , m_msg_id(0) + , m_event(event) + , m_map(map) +{ +} + +QTestMessage::QTestMessage(QString const &event, const QTestMessage &other ) + : m_phase(0) + , m_msg_id(other.m_msg_id) + , m_map(other.m_map) +{ + m_event = event; +} + +QTestMessage::QTestMessage(QString const &event, QString const &queryApp, QString const &queryPath ) + : m_phase(0) + , m_msg_id(0) + , m_event(event) + , m_map(QVariantMap()) +{ + m_map["queryapp"] = queryApp; + m_map["querypath"] = queryPath; +} + +/* + \internal + + Copy constructor. +*/ + +QTestMessage::QTestMessage(const QTestMessage &other) + : m_phase(0) + , m_msg_id(other.m_msg_id) + , m_event(other.m_event) + , m_map(other.m_map) +{ +} + +/* + \internal + + Destroys the message. +*/ + +QTestMessage::~QTestMessage() +{ +} + +/* + \internal + + Assignment operator. +*/ + +QTestMessage& QTestMessage::operator=(const QTestMessage &other) +{ + m_msg_id = other.m_msg_id; + m_event = other.m_event; + m_map = other.m_map; + + return *this; +} + +QVariant &QTestMessage::operator[](QString const &key) +{ + return m_map[key.toLower()]; +} + +QVariant const QTestMessage::operator[](QString const &key) const +{ + return m_map[key.toLower()]; +} + +bool QTestMessage::contains(QString const &key) const +{ + return m_map.contains(key.toLower()); +} + +QList<QString> QTestMessage::keys() const +{ + return m_map.keys(); +} + +QString QTestMessage::toString() const +{ + QString ret; + foreach(QString k, m_map.keys()) { + if (!m_map[k].isValid()) continue; + ret += k + ": "; + if (m_map[k].canConvert<QStringList>()) ret += "'" + m_map[k].toStringList().join("','") + "'"; + else if (m_map[k].canConvert<QString>()) ret += "'" + m_map[k].toString() + "'"; + else ret += "(data)"; + ret += "\n"; + } + if (ret.endsWith("\n")) ret.chop(1); + return ret; +} + +QVariantMap QTestMessage::toMap() const +{ + return m_map; +} + +/* + \internal + Returns the event that was received. +*/ + +QString QTestMessage::event() const +{ + return m_event; +} + +/* + \internal + Returns the message number. +*/ + +quint16 QTestMessage::msgId() const +{ + return m_msg_id; +} + +bool QTestMessage::statusOK() const +{ + return m_map.contains("status") && m_map["status"].toString() == "OK"; +} + +bool QTestMessage::isNull() const +{ + return m_map.isEmpty(); +} + + +// ******************************************************************************** +// ************************* QTestServerSocket ************************************ +// ******************************************************************************** + +/* ! + \class QTestServerSocket qtestserversocket.h + \inpublicgroup QtUiTestModule + + \brief The QTestServerSocket class provides a TCP-based server. + + This class is a convenience class for accepting incoming TCP + connections. You can specify the port or have QTestServerSocket pick + one, and listen on just one address or on all the machine's + addresses. + + Using the API is very simple: subclass QTestServerSocket, call the + constructor of your choice, and implement onNewConnection() to + handle new incoming connections. There is nothing more to do. + + (Note that due to lack of support in the underlying APIs, + QTestServerSocket cannot accept or reject connections conditionally.) + + \sa QTcpSocket, QTcpServer, QHostAddress, QSocketNotifier +*/ + + +/* ! + Creates a server socket object, that will serve the given \a port + on all the addresses of this host. If \a port is 0, QTestServerSocket + will pick a suitable port in a system-dependent manner. Use \a + backlog to specify how many pending connections the server can + have. + + \warning On Tru64 Unix systems a value of 0 for \a backlog means + that you don't accept any connections at all; you should specify a + value larger than 0. +*/ + +QTestServerSocket::QTestServerSocket( quint16 port, int backlog ) + : QTcpServer() +{ + setMaxPendingConnections( backlog ); + listen( QHostAddress::Any, port ); + + if (this->serverPort() == 0) { + qWarning( QString("ERROR: port '%1' is already in use, application is aborted.").arg(port).toAscii() ); + QApplication::exit(777); + } +} + +/* ! + Destroys the socket. + + This causes any backlogged connections (connections that have + reached the host, but not yet been completely set up + to be severed. + + Existing connections continue to exist; this only affects the + acceptance of new connections. +*/ +QTestServerSocket::~QTestServerSocket() +{ +} + +/* ! + Returns true if the construction succeeded; otherwise returns false. +*/ +bool QTestServerSocket::ok() const +{ + return serverPort() > 0; +} + +/* ! + Returns the port number on which this server socket listens. This + is always non-zero; if you specify 0 in the constructor, + QTestServerSocket will pick a non-zero port itself. ok() must be true + before calling this function. + + \sa address() +*/ +quint16 QTestServerSocket::port() const +{ + return serverPort(); +} + +/* ! + Returns the address on which this object listens, or 0.0.0.0 if + this object listens on more than one address. ok() must be true + before calling this function. + + \sa port() +*/ +QString QTestServerSocket::address() const +{ + return serverAddress().toString(); +} + +void QTestServerSocket::incomingConnection( int socket ) +{ + onNewConnection( socket ); +} + +// ******************************************************************************** +// *************************** QTestProtocol ************************************ +// ******************************************************************************** + +/* + \class QTestProtocol qtestprotocol.h + \inpublicgroup QtUiTestModule + + \brief The QTestProtocol class can be used for exchanging messages between separate + processes. + + It is intended to be fast but at the same time ultra reliable and robust communication protocol. + + The main function that is used on the sending side is: + \code + myConnection.postMessage( "My-Event", "My-Message", ptr_to_my_data ); + \endcode + + On the receiving side the main function is a re-implemented 'processMessage': + \code + void MyTestConnection::processMessage( QTestMessage *msg ) + { + if (msg->event() == "My-Event") { + print( msg->message() ); + } + } + \endcode +*/ + +#include <stdio.h> + +QTestProtocol::QTestProtocol() + : QTcpSocket() + , tx_msg_id(1) + , host() + , port(0) + , onData_busy(false) + , enable_reconnect(false) + , reconnect_interval(10000) + , connect_timer() + , last_data_received(false) + , connection_valid(false) + , ping_enabled(false) + , ping_interval(10000) + , ping_timer() + , ping_count(0) + , ping_timeout_warning_issued(false) + , last_send_cmd("") + , unique_id() + , debug_on(false) +{ + static int id1 = qRegisterMetaType<QTestMessage>(); Q_UNUSED(id1); + static int id2 = qRegisterMetaType<QTestMessage*>(); Q_UNUSED(id2); + static int id3 = qRegisterMetaType<const QTestMessage*>(); Q_UNUSED(id3); + + unique_id = QString("%1").arg(++g_unique_id); + if (debug_on) { + qDebug() << QString("%1 QTestProtocol::QTestProtocol()").arg(uniqueId()).toLatin1(); + } + cur_message = 0; + rx_busy = false; + + QObject::connect( &connect_timer, SIGNAL(timeout()), this, SLOT(connectTimeout()), Qt::DirectConnection ); + + QObject::connect( this,SIGNAL(connected()),this,SLOT(onSocketConnected()), Qt::DirectConnection ); + QObject::connect( this,SIGNAL(disconnected()),this,SLOT(onSocketClosed()), Qt::DirectConnection ); + QObject::connect( this,SIGNAL(readyRead()),this,SLOT(onData()), Qt::DirectConnection ); + + // initialize. Any time is better than no time. + rx_timer.start(); +} + +/*! + Destructs the instance of QTestProtocol. +*/ + +QTestProtocol::~QTestProtocol() +{ + if (debug_on) { + qDebug() << QString("%1 QTestProtocol::~QTestProtocol()").arg(uniqueId()).toLatin1(); + } + enableReconnect( false, 0 ); + + // we can't send any more messages so disable pinging + enablePing( false ); + + // anything that is still in the tx buffer gets lost + abort(); + close(); + + while (send_msg_replies.count() > 0) + delete send_msg_replies.takeFirst(); + while (unhandled_msg.count() > 0) + delete unhandled_msg.takeFirst(); +} + +void QTestProtocol::setSocket( int socket ) +{ + if (debug_on) { + qDebug() << ( QString("%1 QTestProtocol::setSocket(socket=%2)"). + arg(uniqueId()). + arg(socket).toLatin1()); + } + setSocketDescriptor( socket ); + + rx_timer.start(); + testConnection(); +} + +void QTestProtocol::enableReconnect( bool enable, uint reconnectInterval ) +{ + if (debug_on) { + qDebug() << ( QString("%1 QTestProtocol::enableReconnect( enable=%2, interval=%3)"). + arg(uniqueId()). + arg(enable). + arg(reconnectInterval).toLatin1()); + } + enable_reconnect = enable; + reconnect_interval = reconnectInterval; +} + +/*! + Opens a socket connection to the specified \a hostname and \a port. + + After a connection is successfully opened the instance will listen for and process + incoming commands received from the remote host. +*/ +void QTestProtocol::connect( const QString& hostname, int port ) +{ + if (state() == ConnectedState) { + if (hostname == this->host && port == this->port) + return; + disconnect(); + } + + if (debug_on) { + qDebug() << ( QString("%1 QTestProtocol::connect(%2:%3)"). + arg(uniqueId()). + arg(hostname). + arg(port).toLatin1()); + } + + rx_timer.start(); + + this->host = hostname; + this->port = port; + + reconnect(); +} + +void QTestProtocol::disconnect( bool disableReconnect ) +{ + if (state() == ConnectedState) { + if (debug_on) { + qDebug() << ( QString("%1 QTestProtocol::disconnect(disableReconnect=%2)"). + arg(uniqueId()). + arg(disableReconnect).toLatin1()); + } + // be polite and tell the other side we are closing + postMessage( QTestMessage("QTEST_CLOSING_CONNECTION") ); + + // we are closing ourselves, so we don't want to reconnect + if (disableReconnect) enable_reconnect = false; + + onSocketClosed(); + } +} + +bool QTestProtocol::isConnected() +{ + return ((state() == ConnectedState) && connection_valid); +} + +bool QTestProtocol::waitForConnected( int timeout ) +{ + QTime t; + t.start(); + bool ok = false; + QTime t2; + t2.start(); + while (t.elapsed() < timeout && !ok) { + ok = QTcpSocket::waitForConnected(timeout); + if (!ok) { + wait(100); + if (t2.elapsed() > 500) { + t2.start(); + reconnect(); + } + } + } + + if (ok) { + if (debug_on) { + qDebug() << QString("%1 QTestProtocol::waitForConnected(%2) ...") + .arg(uniqueId()).arg(timeout).toLatin1(); + } + t2.start(); + while (t.elapsed() < timeout && !isConnected()) { + wait(10); + if (!isConnected() && t2.elapsed() > 500) { + postMessage( QTestMessage("QTEST_NEW_CONNECTION") ); + t2.start(); + } + } + ok = isConnected(); + } else { + qDebug() << errorStr(); + } + + if (debug_on) { + qDebug() << QString("%1 QTestProtocol::waitForConnected() ... %2 (%3 ms)").arg(uniqueId()).arg(ok ? "OK" : "FAILED" ).arg(t.elapsed()).toLatin1(); + } + return ok; +} + +/*! + \internal + Posts (e.g. non blocking) an \a event, \a message and contents of \a fileName to the remote host. +*/ + +uint QTestProtocol::postMessage(QTestMessage const &message ) +{ + if (debug_on && message.event() != "PING" && message.event() != "PONG") { + qDebug() << ( QString("%1 QTestProtocol::postMessage(%2)"). + arg(uniqueId()). + arg(message.event()).toLatin1()); + } + if (state() != ConnectedState) + return 0; + QTestMessage msg(message); + msg.m_msg_id = tx_msg_id++; + send( msg ); + return msg.m_msg_id; +} + +void QTestProtocol::onReplyReceived( QTestMessage* /*reply*/ ) +{ +} + +/*! + \internal + Sends an \a event, \a message and \a data to the remote host and waits for up to + \a timeout milliseconds for a reply. If a reply is received, the reply's message + string is placed in \a reply. +*/ +bool QTestProtocol::sendMessage( QTestMessage const &message, QTestMessage &reply, int timeout ) +{ + QTestMessage msg(message); + QPointer<QTestProtocol> safeThis(this); + bool safeDebugOn(debug_on); + QString safeUniqueId(uniqueId()); + + last_send_cmd = message.event(); + + if (state() == ConnectingState) { + wait(4000); + } + + if (state() == ConnectedState) { + msg.m_msg_id = tx_msg_id++; + + if (debug_on) { + qDebug() << ( QString("%1 QTestProtocol::sendMessage(%2) msgid=%3)"). + arg(uniqueId()). + arg(msg.event()). + arg(msg.msgId()). + toLatin1()); + } + + send( msg ); + +// QElapsedTimer t1; +// QElapsedTimer t2; + QTime t1; + t1.start(); + QTime t2; + t2.start(); + bool first_time = true; + while ( (state() == ConnectedState) && + (timeout < 0 || t1.elapsed()/*msecsSinceReference()*/ < timeout) ) { + + if (debug_on) { + if (first_time || t2.elapsed()/*msecsSinceReference()*/ > 1000) { + qDebug() << ( QString("%1 QTestProtocol::sendMessage(%2) ... waiting for reply"). + arg(uniqueId()). + arg(message.event()).toLatin1()); + t2.start(); + first_time = false; + } + } + + waitForSignal(this, SIGNAL(replyReceived()), 500); + + if (!safeThis) { + if (message["expectClose"].isValid()) { + return true; + } + if (safeDebugOn) { + qDebug() << ( QString("%1 QTestProtocol::sendMessage(%2) ... object deleted unexpectedly"). + arg(safeUniqueId). + arg(message.event()).toLatin1()); + } + reply["status"] = "ERROR: Connection was terminated unexpectedly. This may be caused by an application crash."; + reply["_q_inResponseTo"] = QString("%1\n%2").arg(message.event()).arg(message.toString()); + return false; + } else { + if (send_msg_replies.count() > 0) { + if (debug_on) { + qDebug() << ( QString("%1 QTestProtocol::sendMessage(%2) ... check replies"). + arg(uniqueId()). + arg(message.event()).toLatin1()); + } + for (int i=0; i<send_msg_replies.size(); i++) { + QTestMessage * possible_reply = send_msg_replies.at(i); + if (possible_reply && possible_reply->m_msg_id == msg.m_msg_id) { + + reply = *possible_reply; + delete send_msg_replies.takeAt( i ); + + if (debug_on) { + qDebug() << ( QString("%1 QTestProtocol::sendMessage(%2) ... reply received"). + arg(uniqueId()). + arg(message.event()).toLatin1()); + } + + onReplyReceived(&reply); + return true; + } + } + } + } + } + if (state() != ConnectedState) { + reply["status"] = "ERROR: Connection lost. This is likely caused by a crash in the Application Under Test."; + reply["_q_inResponseTo"] = QString("%1\n%2").arg(message.event()).arg(message.toString()); + } else { + qDebug() << "ERROR-REPLY-TIMEOUT: " << t1.elapsed()/*msecsSinceReference()*/ << t2.elapsed()/*msecsSinceReference()*/; + reply["status"] = "ERROR_REPLY_TIMEOUT"; + reply["_q_inResponseTo"] = QString("%1\n%2").arg(message.event()).arg(message.toString()); + } + reply["location"] = QString("%1:%2").arg(__FILE__).arg(__LINE__); + } else { + reply["status"] = "ERROR_NO_CONNECTION"; + reply["_q_inResponseTo"] = QString("%1\n%2").arg(message.event()).arg(message.toString()); + reply["location"] = QString("%1:%2").arg(__FILE__).arg(__LINE__); + } + + if (debug_on) { + qDebug() << ( QString("%1 QTestProtocol::sendMessage(%2) ... done. Status: %3"). + arg(uniqueId()). + arg(message.event()).arg(reply["status"].toString()).toLatin1()); + } + + return false; +} + +/*! + Send the string \a result as a reply to \a originalMsg. +*/ + +void QTestProtocol::replyMessage( QTestMessage *originalMsg, QTestMessage const &message ) +{ + if (debug_on) { + qDebug() << ( QString("%1 QTestProtocol::replyMessage(%2)"). + arg(uniqueId()). + arg(originalMsg->event()).toLatin1()); + } + + QTestMessage msg(message); + msg.m_msg_id = originalMsg->msgId(); + msg.m_event = "@REPLY@"; + send( msg ); +} + +bool QTestProtocol::lastDataReceived() +{ + return last_data_received; +} + +QString QTestProtocol::errorStr() +{ + QString S = "Connection error: "; + switch (error()) { + case ConnectionRefusedError: S += "A connection attempt was rejected by the peer"; break; + case HostNotFoundError: S += "Host not found"; break; + case RemoteHostClosedError: S += "RemoteHostClosedError"; break; + case SocketAccessError: S += "SocketAccessError"; break; + case SocketResourceError: S += "SocketResourceError"; break; + case SocketTimeoutError: S += "SocketTimeoutError"; break; + case DatagramTooLargeError: S += "DatagramTooLargeError"; break; + case NetworkError: S += "NetworkError"; break; + case AddressInUseError: S += "AddressInUseError"; break; + case SocketAddressNotAvailableError: S += "SocketAddressNotAvailableError"; break; + case UnsupportedSocketOperationError: S += "UnsupportedSocketOperationError"; break; + case UnknownSocketError: S += "UnknownSocketError"; break; + default: S += " Unknown error"; + } + + return S; +} + +void QTestProtocol::onConnectionFailed( const QString &reason ) +{ + emit connectionFailed( this, reason ); +} + +void QTestProtocol::testConnection() +{ + if (debug_on) { + qDebug() << QString("%1 QTestProtocol::testConnection()").arg(uniqueId()).toLatin1(); + } + + while (send_msg_replies.count() > 0) + delete send_msg_replies.takeFirst(); + + enablePing( true ); + postMessage( QTestMessage("QTEST_NEW_CONNECTION") ); +} + +void QTestProtocol::send( QTestMessage const &message ) +{ + QByteArray data; + if (!message.m_map.isEmpty()) { + QDataStream s(&data, QIODevice::WriteOnly); + s << message.m_map; + } + + QDataStream tmp(this); + sendPreamble( &tmp, message.msgId(), message.event() ); + + quint32 len = data.count(); + tmp << len; // phase 2 + if (len > 0) { + tmp.writeRawData( data.data(), (int)len ); // phase 3 + } + + tmp << TEST_MSG_END; // phase 4 + + flush(); // Force socket to send data now +} + +void QTestProtocol::sendPreamble( QDataStream *ds, quint16 msgId, const QString &event ) +{ + quint32 len; + *ds << TEST_MSG_SIGNATURE; // phase 0 + *ds << TEST_MSG_VERSION; + *ds << msgId; + + len = (event.length() *2) + 4; + *ds << len; + *ds << event; // phase 1 +} + +bool QTestProtocol::receive( QTestMessage *msg, bool &syncError ) +{ + syncError = false; + + QDataStream stream(this); + + quint8 rx_version; + if (msg->m_phase == uint(0)) { + msg->m_len = 0; + quint32 sig; + if (bytesAvailable() >= sizeof( sig ) + sizeof( rx_version ) + sizeof( msg->m_msg_id ) + sizeof( msg->m_len )) { + stream >> sig; + if (sig != TEST_MSG_SIGNATURE) { + qWarning( QString("QTestMessage::receive(), Invalid start signature (0x%1)").arg(sig,8,16).toLatin1() ); + syncError = true; + return false; + } else { + stream >> rx_version; // FIXME: something should be done to verify the version. + stream >> msg->m_msg_id; + stream >> msg->m_len; + msg->m_phase++; + } + } + } + + if (msg->m_phase == uint(1)) { + if (bytesAvailable() >= msg->m_len) { + stream >> msg->m_event; + msg->m_phase++; + } + } + + if (msg->m_phase == uint(2)) { + if (bytesAvailable() >= sizeof( msg->m_len )) { + stream >> msg->m_len; + msg->m_phase++; + } + } + + if (msg->m_phase == uint(3)) { + if (msg->m_len > 0) { + QByteArray buf; + quint32 len = msg->m_len; + uint bytes_available = bytesAvailable(); + if (bytes_available < len) + len = bytes_available; + buf.resize( len ); + stream.readRawData( buf.data(), len ); + + static QMap<QTestMessage*, QByteArray> data; + data[msg].append(buf); + + msg->m_len -= len; + if (msg->m_len == 0) { + QDataStream s(&data[msg], QIODevice::ReadOnly); + s >> msg->m_map; + data.remove(msg); + msg->m_phase++; + // received OK + } else { + // waiting for more data + return false; + } + } else { + msg->m_phase++; + } + } + + if (msg->m_phase == uint(4)) { + quint32 id2; + if (bytesAvailable() >= sizeof( id2 )) { + stream >> id2; + msg->m_phase = 0; + if (id2 != TEST_MSG_END) { + qWarning( QString("QTestMessage::receive(), Invalid end signature (0x%2)").arg(id2,8,16).toLatin1() ); + syncError = true; + return false; + } else { + return true; + } + } + } + + return false; +} + +bool QTestProtocol::rxBusy() +{ + return rx_busy; +} + +/*! + Reads the remote control connection and responds to received commands. +*/ + +void QTestProtocol::onData() +{ + if (onData_busy) return; + onData_busy = true; + + int sync_error_count = 0; + bool msg_received = true; + while (msg_received && bytesAvailable() > 0) { + msg_received = false; + // set the time to now :-) + rx_timer.start(); + ping_timeout_warning_issued = false; + + bool sync_error; + if (cur_message == 0) + cur_message = new QTestMessage(); + + if (receive( cur_message, sync_error )) { + msg_received = true; + QString last_event = cur_message->event(); + + if (debug_on) { + qDebug() << ( QString("%1 QTestProtocol::onData(%2) msgid = %3"). + arg(uniqueId()). + arg(last_event). + arg(cur_message->m_msg_id). + toLatin1()); + } + + // We received a full message + if (last_event == "@REPLY@") { + send_msg_replies.append( cur_message ); // add the reply to a list + int id = cur_message->m_msg_id; + QTestMessage *received_message(cur_message); + cur_message = 0; // and make sure we create a new one + emit replyReceived( id, received_message ); + } else if (last_event == "QTEST_NEW_CONNECTION") { + postMessage( QTestMessage("QTEST_ACK_CONNECTION") ); // Acknowledge the other side we can receive data + } else if (last_event == "QTEST_ACK_CONNECTION") { + connection_valid = true; // we don't assume we have a connection until both sides have actually received data from the other side + onConnected(); + connect_timer.stop(); + } else if (last_event == "QTEST_CLOSING_CONNECTION") { + last_data_received = true; + QTimer::singleShot( 0, this, SLOT(onSocketClosed())); + } else if (last_event == "PONG") { + // Do nothing + } else if (last_event == "TEST") { + if (!debug_on) { + // don't show the same information twice + qDebug() << QString("%1 Test message received").arg(uniqueId()).toLatin1(); + } + } else if (last_event == "PING") { + postMessage( QTestMessage("PONG") ); + } else { + unhandled_msg.append( cur_message ); // add the msg to a list + cur_message = 0; + QTimer::singleShot(0,this,SLOT(processMessages())); + } + + delete cur_message; + cur_message = 0; + } else { + // We didn't receive a full message + if (sync_error) { + sync_error_count++; + if (sync_error_count > 10) + return; + // receiving garbage messages - nothing can be done but closing the connection and try again. + delete cur_message; + cur_message = 0; + disconnect(!enable_reconnect); + reconnect(); + } + // else we are waiting on more fragments to arrive + } + } + onData_busy = false; +} + +void QTestProtocol::processMessages() +{ + while (!rx_busy && unhandled_msg.count() > 0) { + QTestMessage *tmp = unhandled_msg.takeFirst(); + if (tmp) { + rx_busy = true; + processMessage( tmp ); + delete tmp; + rx_busy = false; + } + } +} + +/*! + Signals the instance that the other side has closed the connection. +*/ +void QTestProtocol::onSocketClosed() +{ + if (debug_on) { + qDebug() << QString("%1 QTestProtocol::onSocketClosed()").arg(uniqueId()).toLatin1(); + } + + // we can't send any more messages so disable pinging + enablePing( false ); + + // anything that is still in the tx buffer gets lost + abort(); + + close(); + + connection_valid = false; + + // if the close was spontaneous and we want to keep the connection alive, we try to reconnect + if (enable_reconnect) { + if (debug_on) { + qDebug() << + QString("%1 QTestProtocol::onSocketClosed() singleshot reconnect in .5 seconds").arg(uniqueId()).toLatin1(); + } + QTimer::singleShot(500,this,SLOT(reconnect())); + } + + // tell the world we are closed + QTimer::singleShot(0, this, SLOT(emitConnectionClosed())); +} + +/*! + Signals the instance that a connection is established with a remote control host. +*/ +void QTestProtocol::onSocketConnected() +{ + connect_timer.stop(); + if (debug_on) { + qDebug() << QString("%1 QTestProtocol::onSocketConnected()").arg(uniqueId()).toLatin1(); + } + testConnection(); +} + +void QTestProtocol::reconnect() +{ + if (host != "" && state() != ConnectedState) { + if (debug_on) { + qDebug() << QString("%1 QTestProtocol::reconnect()").arg(uniqueId()).toLatin1(); + } + + connect_timer.stop(); + connect_timer.start( CONNECT_TIMEOUT ); + + // if we are trying to connect to the local machine, always use 127.0.0.1 + // (and avoid the need for dns) + QString hostname = QHostInfo::localHostName().toUpper(); + if (hostname == host.toUpper() || hostname.startsWith( host.toUpper() + "." )) + host = "127.0.0.1"; + + close(); + connectToHost( host, port ); + } else { + if (host == "") { + qWarning( "QTestProtocol::reconnect() FAILED, no host specified" ); + enable_reconnect = false; + } + } +} + +void QTestProtocol::connectTimeout() +{ + if (debug_on) { + qDebug() << QString("%1 QTestProtocol::connectTimeout()").arg(uniqueId()).toLatin1(); + } + + connect_timer.stop(); + if (enable_reconnect) { + reconnect(); + } else { + onConnectionFailed( errorStr() ); + } +} + +void QTestProtocol::pingTimeout() +{ + if (!ping_enabled) return; + + if (state() == ConnectedState) { +// int elapsed = rx_timer.msecsSinceReference(); + int elapsed = rx_timer.elapsed(); + if (state() == ClosingState) { + if (elapsed > 30000) { // no activity for x seconds when we are closing? + enablePing( false ); + if (enable_reconnect) { + disconnect(); + // close has reset the enable_reconnect value, so enable it again + enableReconnect( true, reconnect_interval ); + reconnect(); + } else { + disconnect(); + } + } else if (elapsed > 2000 ) { + postMessage( QTestMessage("PING") ); + } + } else { + if (elapsed > 10000) { + postMessage( QTestMessage("PING") ); + if (elapsed > 300000) { // no activity for 5 minutes in 'normal' cases?? + if (!ping_timeout_warning_issued) + qWarning( QString("%1 QTestProtocol::pingTimeout() WARNING: Did not receive a msg for %2 ms").arg(uniqueId()).arg(elapsed).toLatin1() ); + ping_timeout_warning_issued = true; + } + } + } + } else { + if (enable_reconnect) { + // Connection seems to be closed, try to reconnect + disconnect(); + + // disconnect has reset the enable_reconnect value, so enable it again + enableReconnect( true, reconnect_interval ); + reconnect(); + } + } +} + +void QTestProtocol::emitConnectionClosed() +{ + if (debug_on) { + qDebug() << ( QString("%1 QTestProtocol::emitConnectionClosed()").arg(uniqueId()).toLatin1()); + } + + emit replyReceived(); // force sendMessage to quit + emit connectionClosed( this ); +} + +void QTestProtocol::enablePing( bool enable ) +{ + if (ping_enabled != enable) { + ping_timer.stop(); + ping_enabled = enable; + if (enable) { + QObject::connect( &ping_timer, SIGNAL(timeout()), this, SLOT(pingTimeout()), Qt::DirectConnection ); + ping_timer.start( ping_interval ); + } else { + QObject::disconnect( &ping_timer, SIGNAL(timeout()), this, SLOT(pingTimeout()) ); + } + } +} + +QString QTestProtocol::uniqueId() +{ + return QString("%1 %2").arg(unique_id).arg(qApp->applicationName()); +} + +void QTestProtocol::enableDebug( bool debugOn ) +{ + debug_on = debugOn; + qDebug() << QString("Debugging is switched %1 for Test Protocol %2").arg(debugOn ? "ON" : "OFF").arg(uniqueId()).toLatin1() ; +} + +void QTestProtocol::disableDebug() +{ + debug_on = false; + qDebug() << QString("Debugging is switched %1 for Test Protocol %2").arg(debug_on ? "ON" : "OFF").arg(uniqueId()).toLatin1() ; +} + diff --git a/libqsystemtest/qtestprotocol_p.h b/libqsystemtest/qtestprotocol_p.h new file mode 100644 index 0000000..d2c2dc3 --- /dev/null +++ b/libqsystemtest/qtestprotocol_p.h @@ -0,0 +1,207 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef QTESTPROTOCOL_P_H +#define QTESTPROTOCOL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the QtUiTest API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QTimer> +#include <QTime> +#include <QObject> +#include <QString> +#include <QFile> +#include <QVariant> +#include <QMap> +#include <QtNetwork/QTcpSocket> +#include <QtNetwork/QTcpServer> +#include <QtCore/QElapsedTimer> + +#define REMOTE_CONNECT_ERROR 99 + +class QTestMessage +{ +public: + QTestMessage(QString const &event = QString(), QVariantMap const &map = QVariantMap() ); + QTestMessage(QString const &event, const QTestMessage &other ); + QTestMessage(QString const &event, QString const &queryApp, QString const &queryPath = QString() ); + QTestMessage(const QTestMessage &other); + virtual ~QTestMessage(); + + QTestMessage& operator=(const QTestMessage &other); + + QString event() const; + quint16 msgId() const; + + QVariant const operator[](QString const &key) const; + QVariant &operator[](QString const &key); + bool contains(QString const &key) const; + QList<QString> keys() const; + + QString toString() const; + QVariantMap toMap() const; + + bool statusOK() const; + bool isNull() const; + +protected: + uint m_phase; + quint32 m_len; + + quint16 m_msg_id; + QString m_event; + + QVariantMap m_map; + + friend class QTestProtocol; +}; +Q_DECLARE_METATYPE( QTestMessage ) +Q_DECLARE_METATYPE( QTestMessage* ) +Q_DECLARE_METATYPE( const QTestMessage* ) + +class QTestServerSocket : public QTcpServer +{ + Q_OBJECT +public: + QTestServerSocket( quint16 port, int backlog = 1 ); + virtual ~QTestServerSocket(); + + bool ok() const; + quint16 port() const; + QString address() const; + + virtual void onNewConnection( int socket ) = 0; + +private: + virtual void incomingConnection( int socket ); +}; + +class QTestProtocol : public QTcpSocket +{ + Q_OBJECT +public: + + QTestProtocol(); + virtual ~QTestProtocol(); + + virtual void setSocket( int socket ); + + void enableReconnect( bool enable, uint reconnectInterval = 5000 ); + + void connect( const QString& hostname, int port ); + void disconnect( bool disableReconnect = true ); + bool isConnected(); + virtual bool waitForConnected( int timeout = 10000 ); + + virtual uint postMessage( QTestMessage const &message ); + + virtual bool sendMessage( QTestMessage const &message, QTestMessage &reply, int timeout ); + virtual void replyMessage( QTestMessage *originalMsg, QTestMessage const &message ); + + bool lastDataReceived(); + bool rxBusy(); + virtual void onReplyReceived( QTestMessage *reply ); + + QString errorStr(); + + virtual void onConnectionFailed( const QString &reason ); + virtual void onConnected() {}; // re-implement in derived class + + QString uniqueId(); + void enableDebug( bool debugOn ); + +public slots: + void reconnect(); + void disableDebug(); + +protected: + virtual void processMessage( QTestMessage *msg ) = 0; + + void send( QTestMessage const &message ); + static void sendPreamble( QDataStream *ds, quint16 msgId, const QString &event ); + + bool receive( QTestMessage *msg, bool &syncError ); + +signals: + void connectionClosed( QTestProtocol *socket ); + void connectionFailed( QTestProtocol *socket, const QString &reason ); + void replyReceived(int id = -1, QTestMessage const *message = 0); + void replyConfirmed(); + +protected slots: + void onData(); + void onSocketConnected(); + void onSocketClosed(); + void connectTimeout(); + void pingTimeout(); + void emitConnectionClosed(); + void processMessages(); + void testConnection(); + +private: + void enablePing( bool enable ); + + quint16 tx_msg_id; + QString host; + int port; + bool onData_busy; + bool enable_reconnect; + uint reconnect_interval; + QTimer connect_timer; +// QElapsedTimer rx_timer; + QTime rx_timer; + bool last_data_received; + bool rx_busy; + bool connection_valid; + + QList<QTestMessage*> send_msg_replies; + QList<QTestMessage*> unhandled_msg; + QTestMessage *cur_message; + + bool ping_enabled; + uint ping_interval; + QTimer ping_timer; + uint ping_count; + bool ping_timeout_warning_issued; + QString last_send_cmd; + QString unique_id; + bool debug_on; +}; + +#endif + diff --git a/libqsystemtest/gracefulquit.h b/libqsystemtest/qtuitestglobal.h index 25eeb04..497a886 100644 --- a/libqsystemtest/gracefulquit.h +++ b/libqsystemtest/qtuitestglobal.h @@ -39,15 +39,26 @@ ** ****************************************************************************/ -#ifndef GRACEFULQUIT_P_H -#define GRACEFULQUIT_P_H -#include <qtuitestglobal.h> +#ifndef QTUITEST_GLOBAL_H +#define QTUITEST_GLOBAL_H -class QObject; -struct QSYSTEMTEST_EXPORT GracefulQuit -{ - static void install(QObject*); -}; +#include <QtGlobal> + +#if defined(Q_OS_WIN32) || defined(Q_OS_SYMBIAN) +# if defined(QSYSTEMTEST_TARGET) +# define QSYSTEMTEST_EXPORT Q_DECL_EXPORT +# else +# define QSYSTEMTEST_EXPORT Q_DECL_IMPORT +# endif +# if defined(QTUITESTRUNNER_TARGET) +# define QTUITESTRUNNER_EXPORT Q_DECL_EXPORT +# else +# define QTUITESTRUNNER_EXPORT Q_DECL_IMPORT +# endif +#else +# define QSYSTEMTEST_EXPORT Q_DECL_EXPORT +# define QTUITESTRUNNER_EXPORT Q_DECL_EXPORT +#endif #endif diff --git a/libqsystemtest/recordevent_p.h b/libqsystemtest/recordevent_p.h new file mode 100644 index 0000000..f8ad42a --- /dev/null +++ b/libqsystemtest/recordevent_p.h @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef RECORDEVENT_P_H +#define RECORDEVENT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the QtUiTest API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QString> +#include <QVariant> + +struct RecordEvent { + enum Type { + GotFocus, + Entered, + Selected, + Activated, + CheckStateChanged, + TitleChanged, + MessageBoxShown + }; + Type type; + QString widget; + QString focusWidget; + QVariant data; +}; + +#define Q_DECLARE_METATYPE_STREAM(TYPE) \ + QT_BEGIN_NAMESPACE \ + template <> \ + struct QMetaTypeId< TYPE > \ + { \ + enum { Defined = 1 }; \ + static int qt_metatype_id() \ + { \ + static QBasicAtomicInt metatype_id = Q_BASIC_ATOMIC_INITIALIZER(0); \ + if (!metatype_id) { \ + metatype_id = qRegisterMetaType< TYPE >(#TYPE, \ + reinterpret_cast< TYPE *>(quintptr(-1))); \ + qRegisterMetaTypeStreamOperators< TYPE >(#TYPE); \ + } \ + return metatype_id; \ + } \ + }; \ + QT_END_NAMESPACE + +Q_DECLARE_METATYPE_STREAM(RecordEvent); +Q_DECLARE_METATYPE_STREAM(QList<RecordEvent>); + +inline bool operator==(RecordEvent const& a, RecordEvent const& b) +{ return a.type == b.type && a.widget == b.widget && a.focusWidget == b.focusWidget && a.data == b.data; } + +inline bool operator!=(RecordEvent const& a, RecordEvent const& b) +{ return !(a == b); } + +inline QDataStream &operator<<(QDataStream &out, const RecordEvent &re) +{ return (out << static_cast<int>(re.type) << re.widget << re.focusWidget << re.data); } + +inline QDataStream &operator>>(QDataStream &in, RecordEvent &re) +{ + int reType; + QDataStream &ret = (in >> reType >> re.widget >> re.focusWidget >> re.data); + re.type = static_cast<RecordEvent::Type>(reType); + return ret; +} + +inline QDataStream &operator<<(QDataStream &out, const QList<RecordEvent> &l) +{ + out << l.count(); + foreach (RecordEvent re, l) { + out << re; + } + return out; +} +inline QDataStream &operator>>(QDataStream &in, QList<RecordEvent> &l) +{ + int count = 0; + in >> count; + RecordEvent re; + for (int i = 0; i < count; ++i) { + in >> re; + l << re; + } + return in; +} + +#endif + |