diff options
Diffstat (limited to 'tests/auto/qtipc')
27 files changed, 2889 insertions, 0 deletions
diff --git a/tests/auto/qtipc/lackey/lackey.pro b/tests/auto/qtipc/lackey/lackey.pro new file mode 100644 index 0000000..3912312 --- /dev/null +++ b/tests/auto/qtipc/lackey/lackey.pro @@ -0,0 +1,20 @@ +include(../qsharedmemory/src/src.pri) + +QT = core script + +CONFIG += qtestlib + +DESTDIR = ./ + +win32: CONFIG += console +mac:CONFIG -= app_bundle + +requires(contains(QT_CONFIG,script)) + +DEFINES += QSHAREDMEMORY_DEBUG +DEFINES += QSYSTEMSEMAPHORE_DEBUG + +SOURCES += main.cpp +TARGET = lackey + + diff --git a/tests/auto/qtipc/lackey/main.cpp b/tests/auto/qtipc/lackey/main.cpp new file mode 100644 index 0000000..fef8d22 --- /dev/null +++ b/tests/auto/qtipc/lackey/main.cpp @@ -0,0 +1,370 @@ +/**************************************************************************** +** +** 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 the test suite of the Qt Toolkit. +** +** $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 <qscriptengine.h> + +#include <QtCore/QFile> +#include <QtCore/QTextStream> +#include <QTest> + +#include <qstringlist.h> +#include <stdlib.h> +#include <qsharedmemory.h> +#include <qsystemsemaphore.h> +#include <qsystemlock.h> + +class ScriptSystemSemaphore : public QObject +{ + Q_OBJECT + +public: + ScriptSystemSemaphore(QObject *parent = 0) : QObject(parent), ss(QString()) + { + } + +public slots: + bool acquire() + { + return ss.acquire(); + }; + + bool release(int n = 1) + { + return ss.release(n); + }; + + void setKey(const QString &key, int n = 0) + { + ss.setKey(key, n); + }; + + QString key() const + { + return ss.key(); + } + +private: + QSystemSemaphore ss; +}; + +class ScriptSystemLock : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString key WRITE setKey READ key) + +public: + ScriptSystemLock(QObject *parent = 0) : QObject(parent), sl(QString()) + { + } + +public slots: + + bool lockReadOnly() + { + return sl.lock(QSystemLock::ReadOnly); + } + + bool lock() + { + return sl.lock(); + }; + + bool unlock() + { + return sl.unlock(); + }; + + void setKey(const QString &key) + { + sl.setKey(key); + }; + + QString key() const + { + return sl.key(); + } + +private: + QSystemLock sl; +}; + +class ScriptSharedMemory : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool attached READ isAttached) + Q_PROPERTY(QString key WRITE setKey READ key) + +public: + enum SharedMemoryError + { + NoError = 0, + PermissionDenied = 1, + InvalidSize = 2, + KeyError = 3, + AlreadyExists = 4, + NotFound = 5, + LockError = 6, + OutOfResources = 7, + UnknownError = 8 + }; + + ScriptSharedMemory(QObject *parent = 0) : QObject(parent) + { + } + +public slots: + void sleep(int x) const + { + QTest::qSleep(x); + } + + bool create(int size) + { + return sm.create(size); + }; + + bool createReadOnly(int size) + { + return sm.create(size, QSharedMemory::ReadOnly); + }; + + int size() const + { + return sm.size(); + }; + + bool attach() + { + return sm.attach(); + }; + + bool attachReadOnly() + { + return sm.attach(QSharedMemory::ReadOnly); + }; + + bool isAttached() const + { + return sm.isAttached(); + }; + + bool detach() + { + return sm.detach(); + }; + + int error() const + { + return (int)sm.error(); + }; + + QString errorString() const + { + return sm.errorString(); + }; + + void set(int i, QChar value) + { + ((char*)sm.data())[i] = value.toLatin1(); + } + + QString get(int i) + { + return QChar::fromLatin1(((char*)sm.data())[i]); + } + + char *data() const + { + return (char*)sm.data(); + }; + + void setKey(const QString &key) + { + sm.setKey(key); + }; + + QString key() const + { + return sm.key(); + } + + bool lock() + { + return sm.lock(); + } + + bool unlock() + { + return sm.unlock(); + } + +private: + QSharedMemory sm; +}; + +QT_BEGIN_NAMESPACE +Q_SCRIPT_DECLARE_QMETAOBJECT(ScriptSharedMemory, QObject*); +Q_SCRIPT_DECLARE_QMETAOBJECT(ScriptSystemLock, QObject*); +Q_SCRIPT_DECLARE_QMETAOBJECT(ScriptSystemSemaphore, QObject*); +QT_END_NAMESPACE + +static void interactive(QScriptEngine &eng) +{ +#ifdef Q_OS_WINCE + fprintf(stderr, "Interactive mode not supported on Windows CE\n"); + return; +#endif + QTextStream qin(stdin, QFile::ReadOnly); + + const char *qscript_prompt = "qs> "; + const char *dot_prompt = ".... "; + const char *prompt = qscript_prompt; + + QString code; + + forever { + QString line; + + printf("%s", prompt); + fflush(stdout); + + line = qin.readLine(); + if (line.isNull()) + break; + + code += line; + code += QLatin1Char('\n'); + + if (line.trimmed().isEmpty()) { + continue; + + } else if (! eng.canEvaluate(code)) { + prompt = dot_prompt; + + } else { + QScriptValue result = eng.evaluate(code); + code.clear(); + prompt = qscript_prompt; + if (!result.isUndefined()) + fprintf(stderr, "%s\n", qPrintable(result.toString())); + } + } +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + + QScriptEngine eng; + QScriptValue globalObject = eng.globalObject(); + + QScriptValue sm = qScriptValueFromQMetaObject<ScriptSharedMemory>(&eng); + eng.globalObject().setProperty("ScriptSharedMemory", sm); + + QScriptValue sl = qScriptValueFromQMetaObject<ScriptSystemLock>(&eng); + eng.globalObject().setProperty("ScriptSystemLock", sl); + + QScriptValue ss = qScriptValueFromQMetaObject<ScriptSystemSemaphore>(&eng); + eng.globalObject().setProperty("ScriptSystemSemaphore", ss); + + + if (! *++argv) { + interactive(eng); + return EXIT_SUCCESS; + } + + QStringList arguments = app.arguments(); + arguments.takeFirst(); + + while (!arguments.isEmpty()) { + QString fn = arguments.takeFirst(); + + if (fn == QLatin1String("-i")) { + interactive(eng); + break; + } + + QString contents; + + if (fn == QLatin1String("-")) { + QTextStream stream(stdin, QFile::ReadOnly); + contents = stream.readAll(); + } else { + QFile file(fn); + if (!file.exists()) { + fprintf(stderr, "%s doesn't exists\n", qPrintable(fn)); + return EXIT_FAILURE; + } + if (file.open(QFile::ReadOnly)) { + QTextStream stream(&file); + contents = stream.readAll(); + file.close(); + } + } + + if (contents.isEmpty()) + continue; + + if (contents[0] == '#') { + contents.prepend("//"); + QScriptValue args = eng.newArray(); + args.setProperty("0", QScriptValue(&eng, fn)); + int i = 1; + while (!arguments.isEmpty()) + args.setProperty(i++, QScriptValue(&eng, arguments.takeFirst())); + eng.currentContext()->activationObject().setProperty("args", args); + } + QScriptValue r = eng.evaluate(contents); + if (eng.hasUncaughtException()) { + int line = eng.uncaughtExceptionLineNumber(); + fprintf(stderr, "%d: %s\n\t%s\n\n", line, qPrintable(fn), qPrintable(r.toString())); + return EXIT_FAILURE; + } + if (r.isNumber()) + return r.toInt32(); + } + + return EXIT_SUCCESS; +} + +#include "main.moc" diff --git a/tests/auto/qtipc/lackey/scripts/consumer.js b/tests/auto/qtipc/lackey/scripts/consumer.js new file mode 100644 index 0000000..4d12dca --- /dev/null +++ b/tests/auto/qtipc/lackey/scripts/consumer.js @@ -0,0 +1,41 @@ +function QVERIFY(x, debugInfo) { + if (!(x)) { + print(debugInfo); + throw(debugInfo); + } +} + +var consumer = new ScriptSharedMemory; +consumer.setKey("market"); + +//print("consumer starting"); +var tries = 0;; +while(!consumer.attach()) { + if (tries == 5000) { + var message = "consumer exiting, waiting too long"; + print(message); + throw(message); + } + ++tries; + consumer.sleep(1); +} +//print("consumer attached"); + + +var i = 0; +while(true) { + QVERIFY(consumer.lock(), "lock"); + if (consumer.get(0) == 'Q') { + consumer.set(0, ++i); + //print ("consumer sets" + i); + } + if (consumer.get(0) == 'E') { + QVERIFY(consumer.unlock(), "unlock"); + break; + } + QVERIFY(consumer.unlock(), "unlock"); + consumer.sleep(10); +} + +//print("consumer detaching"); +QVERIFY(consumer.detach()); diff --git a/tests/auto/qtipc/lackey/scripts/producer.js b/tests/auto/qtipc/lackey/scripts/producer.js new file mode 100644 index 0000000..e02cd8b --- /dev/null +++ b/tests/auto/qtipc/lackey/scripts/producer.js @@ -0,0 +1,44 @@ +function QVERIFY(x, debugInfo) { + if (!(x)) { + print(debugInfo); + throw(debugInfo); + } +} + +var producer = new ScriptSharedMemory; +producer.setKey("market"); + +var size = 1024; +if (!producer.create(size)) { + QVERIFY(producer.error() == 4, "create"); + QVERIFY(producer.attach()); +} +//print ("producer created and attached"); + +QVERIFY(producer.lock()); +producer.set(0, 'Q'); +QVERIFY(producer.unlock()); + +var i = 0; +while(i < 5) { + QVERIFY(producer.lock(), "lock"); + if (producer.get(0) == 'Q') { + QVERIFY(producer.unlock(), "unlock"); + producer.sleep(1); + continue; + } + //print("producer: " + i); + ++i; + producer.set(0, 'Q'); + QVERIFY(producer.unlock(), "unlock"); + producer.sleep(1); +} +QVERIFY(producer.lock()); +producer.set(0, 'E'); +QVERIFY(producer.unlock()); + +//print ("producer done"); + +// Sleep for a bit to let all consumers start, otherwise they will get stuck in the attach loop, +// because at least in Symbian the shared memory will be destroyed if there are no active handles to it. +producer.sleep(3000);
\ No newline at end of file diff --git a/tests/auto/qtipc/lackey/scripts/readonly_segfault.js b/tests/auto/qtipc/lackey/scripts/readonly_segfault.js new file mode 100644 index 0000000..3eaf789 --- /dev/null +++ b/tests/auto/qtipc/lackey/scripts/readonly_segfault.js @@ -0,0 +1,4 @@ +var sm = new ScriptSharedMemory; +sm.setKey("readonly_segfault"); +sm.createReadOnly(1024); +var data = sm.set(0, "a"); diff --git a/tests/auto/qtipc/lackey/scripts/systemlock_read.js b/tests/auto/qtipc/lackey/scripts/systemlock_read.js new file mode 100644 index 0000000..1048bc7 --- /dev/null +++ b/tests/auto/qtipc/lackey/scripts/systemlock_read.js @@ -0,0 +1,11 @@ +function QVERIFY(x, debugInfo) { + if (!(x)) { + print(debugInfo); + throw(debugInfo); + } +} + +var lock = new ScriptSystemLock; +lock.setKey("market"); +QVERIFY(lock.lockReadOnly()); +QVERIFY(lock.unlock());
\ No newline at end of file diff --git a/tests/auto/qtipc/lackey/scripts/systemlock_readwrite.js b/tests/auto/qtipc/lackey/scripts/systemlock_readwrite.js new file mode 100644 index 0000000..fc6367f --- /dev/null +++ b/tests/auto/qtipc/lackey/scripts/systemlock_readwrite.js @@ -0,0 +1,11 @@ +function QVERIFY(x, debugInfo) { + if (!(x)) { + print(debugInfo); + throw(debugInfo); + } +} + +var lock = new ScriptSystemLock; +lock.setKey("market"); +QVERIFY(lock.lock()); +QVERIFY(lock.unlock()); diff --git a/tests/auto/qtipc/lackey/scripts/systemsemaphore_acquire.js b/tests/auto/qtipc/lackey/scripts/systemsemaphore_acquire.js new file mode 100644 index 0000000..5cff429 --- /dev/null +++ b/tests/auto/qtipc/lackey/scripts/systemsemaphore_acquire.js @@ -0,0 +1,18 @@ +#/bin/qscript +function QVERIFY(x, debugInfo) { + if (!(x)) { + print(debugInfo); + throw(debugInfo); + } +} + + +var sem = new ScriptSystemSemaphore; +sem.setKey("store"); + +var count = Number(args[1]); +if (isNaN(count)) + count = 1; +for (var i = 0; i < count; ++i) + QVERIFY(sem.acquire()); +print("done aquiring"); diff --git a/tests/auto/qtipc/lackey/scripts/systemsemaphore_acquirerelease.js b/tests/auto/qtipc/lackey/scripts/systemsemaphore_acquirerelease.js new file mode 100644 index 0000000..cedde3f --- /dev/null +++ b/tests/auto/qtipc/lackey/scripts/systemsemaphore_acquirerelease.js @@ -0,0 +1,11 @@ +function QVERIFY(x, debugInfo) { + if (!(x)) { + print(debugInfo); + throw(debugInfo); + } +} + +var lock = new ScriptSystemSemaphore; +lock.setKey("store"); +QVERIFY(lock.acquire()); +QVERIFY(lock.release()); diff --git a/tests/auto/qtipc/lackey/scripts/systemsemaphore_release.js b/tests/auto/qtipc/lackey/scripts/systemsemaphore_release.js new file mode 100644 index 0000000..c805e0f --- /dev/null +++ b/tests/auto/qtipc/lackey/scripts/systemsemaphore_release.js @@ -0,0 +1,11 @@ +function QVERIFY(x, debugInfo) { + if (!(x)) { + print(debugInfo); + throw(debugInfo); + } +} + +var sem = new ScriptSystemSemaphore; +sem.setKey("store"); +QVERIFY(sem.release()); +print ("done releasing"); diff --git a/tests/auto/qtipc/qsharedmemory/.gitignore b/tests/auto/qtipc/qsharedmemory/.gitignore new file mode 100644 index 0000000..03ddcf2 --- /dev/null +++ b/tests/auto/qtipc/qsharedmemory/.gitignore @@ -0,0 +1,3 @@ +tst_qsharedmemory +lackey/lackey.exe +qsystemlock/tst_qsystemlock.exe diff --git a/tests/auto/qtipc/qsharedmemory/qsharedmemory.pro b/tests/auto/qtipc/qsharedmemory/qsharedmemory.pro new file mode 100644 index 0000000..9fef8e4 --- /dev/null +++ b/tests/auto/qtipc/qsharedmemory/qsharedmemory.pro @@ -0,0 +1,4 @@ +TEMPLATE = subdirs +SUBDIRS = test qsystemlock + + diff --git a/tests/auto/qtipc/qsharedmemory/qsystemlock/qsystemlock.pro b/tests/auto/qtipc/qsharedmemory/qsystemlock/qsystemlock.pro new file mode 100644 index 0000000..e232443 --- /dev/null +++ b/tests/auto/qtipc/qsharedmemory/qsystemlock/qsystemlock.pro @@ -0,0 +1,22 @@ +CONFIG += qttest_p4 +#QT = core + +include(../src/src.pri) +win32: CONFIG += console +mac:CONFIG -= app_bundle + +wince* { + DEFINES += SRCDIR=\\\"\\\" +} else:!symbian { + DEFINES += SRCDIR=\\\"$$PWD\\\" +} + +DESTDIR = ./ + +DEFINES += QSHAREDMEMORY_DEBUG +DEFINES += QSYSTEMSEMAPHORE_DEBUG + +SOURCES += tst_qsystemlock.cpp +TARGET = tst_qsystemlock + + diff --git a/tests/auto/qtipc/qsharedmemory/qsystemlock/tst_qsystemlock.cpp b/tests/auto/qtipc/qsharedmemory/qsystemlock/tst_qsystemlock.cpp new file mode 100644 index 0000000..ea906b7 --- /dev/null +++ b/tests/auto/qtipc/qsharedmemory/qsystemlock/tst_qsystemlock.cpp @@ -0,0 +1,239 @@ +/**************************************************************************** +** +** 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 the test suite of the Qt Toolkit. +** +** $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 <QtTest/QtTest> +#include <qsystemlock.h> + +//TESTED_CLASS= +//TESTED_FILES= + +#ifdef Q_OS_SYMBIAN +// In Symbian OS test data is located in applications private dir +// And underlying Open C have application private dir in default search path +#define SRCDIR "" +#endif + + +#define EXISTING_SHARE "existing" + +class tst_QSystemLock : public QObject +{ + Q_OBJECT + +public: + tst_QSystemLock(); + virtual ~tst_QSystemLock(); + +public Q_SLOTS: + void init(); + void cleanup(); + +private slots: + void key_data(); + void key(); + + void basicLock(); + void complexLock(); + void lockModes(); + void sucessive(); + void processes_data(); + void processes(); + +private: + QSystemLock *existingLock; + +}; + +tst_QSystemLock::tst_QSystemLock() +{ +} + +tst_QSystemLock::~tst_QSystemLock() +{ +} + +void tst_QSystemLock::init() +{ + existingLock = new QSystemLock(EXISTING_SHARE); +} + +void tst_QSystemLock::cleanup() +{ + delete existingLock; +} + +void tst_QSystemLock::key_data() +{ + QTest::addColumn<QString>("constructorKey"); + QTest::addColumn<QString>("setKey"); + + QTest::newRow("null, null") << QString() << QString(); + QTest::newRow("null, one") << QString() << QString("one"); + QTest::newRow("one, two") << QString("one") << QString("two"); +} + +/*! + Basic key testing + */ +void tst_QSystemLock::key() +{ + QFETCH(QString, constructorKey); + QFETCH(QString, setKey); + + QSystemLock sl(constructorKey); + QCOMPARE(sl.key(), constructorKey); + sl.setKey(setKey); + QCOMPARE(sl.key(), setKey); +} + +void tst_QSystemLock::basicLock() +{ + QSystemLock lock("foo"); + QVERIFY(lock.lock()); + QVERIFY(lock.unlock()); +} + +void tst_QSystemLock::complexLock() +{ + QSystemLock lock("foo"); + QVERIFY(lock.lock(QSystemLock::ReadOnly)); + QVERIFY(lock.unlock()); + + QVERIFY(lock.lock(QSystemLock::ReadWrite)); + QVERIFY(lock.unlock()); + + QVERIFY(lock.lock(QSystemLock::ReadOnly)); + QVERIFY(lock.lock(QSystemLock::ReadOnly)); + QVERIFY(lock.unlock()); + QVERIFY(lock.unlock()); +} + +void tst_QSystemLock::lockModes() +{ + QSystemLock reader1("library"); + QSystemLock reader2("library"); + + QSystemLock librarian("library"); + QVERIFY(reader1.lock(QSystemLock::ReadOnly)); + QVERIFY(reader2.lock(QSystemLock::ReadOnly)); + QVERIFY(reader1.unlock()); + QVERIFY(reader2.unlock()); + QVERIFY(librarian.lock(QSystemLock::ReadWrite)); + QVERIFY(librarian.unlock()); +} + +void tst_QSystemLock::sucessive() +{ + QSystemLock lock("library"); + QVERIFY(lock.lock(QSystemLock::ReadOnly)); + QVERIFY(lock.lock(QSystemLock::ReadOnly)); + QVERIFY(lock.lock(QSystemLock::ReadOnly)); + QVERIFY(lock.lock(QSystemLock::ReadOnly)); + QVERIFY(lock.lock(QSystemLock::ReadOnly)); + QVERIFY(lock.unlock()); + QVERIFY(lock.unlock()); + QVERIFY(lock.unlock()); + QVERIFY(lock.unlock()); + QVERIFY(lock.unlock()); + QVERIFY(!lock.unlock()); +} + +void tst_QSystemLock::processes_data() +{ + QTest::addColumn<int>("readOnly"); + QTest::addColumn<int>("readWrite"); + for (int i = 0; i < 5; ++i) { + QTest::newRow("1/0 process") << 1 << 0; + QTest::newRow("0/1 process") << 0 << 1; + QTest::newRow("0/4 process") << 0 << 4; + QTest::newRow("1/1 process") << 1 << 1; + QTest::newRow("10/1 process") << 10 << 1; + QTest::newRow("1/10 process") << 1 << 10; + QTest::newRow("10/10 processes") << 10 << 10; + } +} + +/*! + Create external processes + */ +void tst_QSystemLock::processes() +{ + QSKIP("This test takes about 15 minutes and needs to be trimmed down before we can re-enable it", SkipAll); + QFETCH(int, readOnly); + QFETCH(int, readWrite); + + QStringList scripts; + for (int i = 0; i < readOnly; ++i) + scripts.append(QFileInfo(SRCDIR "/../lackey/scripts/systemlock_read.js").absoluteFilePath() ); + for (int i = 0; i < readWrite; ++i) + scripts.append(QFileInfo(SRCDIR "/../lackey/scripts/systemlock_readwrite.js").absoluteFilePath()); + + QList<QProcess*> consumers; + unsigned int failedProcesses = 0; + for (int i = 0; i < scripts.count(); ++i) { + + QStringList arguments = QStringList() << scripts.at(i); + QProcess *p = new QProcess; + p->setProcessChannelMode(QProcess::ForwardedChannels); + + p->start("../lackey/lackey", arguments); + // test, if the process could be started. + + if (p->waitForStarted(2000)) + consumers.append(p); + else + ++failedProcesses; + } + + while (!consumers.isEmpty()) { + consumers.first()->waitForFinished(3000); + consumers.first()->kill(); + QCOMPARE(consumers.first()->exitStatus(), QProcess::NormalExit); + QCOMPARE(consumers.first()->exitCode(), 0); + delete consumers.takeFirst(); + } + QCOMPARE(failedProcesses, (unsigned int)(0)); +} + +QTEST_MAIN(tst_QSystemLock) +#include "tst_qsystemlock.moc" + diff --git a/tests/auto/qtipc/qsharedmemory/src/qsystemlock.cpp b/tests/auto/qtipc/qsharedmemory/src/qsystemlock.cpp new file mode 100644 index 0000000..4ead748 --- /dev/null +++ b/tests/auto/qtipc/qsharedmemory/src/qsystemlock.cpp @@ -0,0 +1,246 @@ +/**************************************************************************** +** +** 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 the test suite of the Qt Toolkit. +** +** $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 "qsystemlock.h" +#include "qsystemlock_p.h" + +#include <qdebug.h> + +/*! \class QSystemLocker + + \brief The QSystemLocker class is a convenience class that simplifies + locking and unlocking system locks. + + The purpose of QSystemLocker is to simplify QSystemLock locking and + unlocking. Locking and unlocking a QSystemLock in complex functions and + statements or in exception handling code is error-prone and difficult to + debug. QSystemLocker can be used in such situations to ensure that the + state of the locks is always well-defined. + + QSystemLocker should be created within a function where a QSystemLock needs + to be locked. The system lock is locked when QSystemLocker is created. If + locked, the system lock will be unlocked when the QSystemLocker is + destroyed. QSystemLocker can be unlocked with unlock() and relocked with + relock(). + + \sa QSystemLock + */ + +/*! \fn QSystemLocker::QSystemLocker() + + Constructs a QSystemLocker and locks \a lock. The \a lock will be + unlocked when the QSystemLocker is destroyed. If lock is zero, + QSystemLocker does nothing. + + \sa QSystemLock::lock() + */ + +/*! \fn QSystemLocker::~QSystemLocker() + + Destroys the QSystemLocker and unlocks it if it was + locked in the constructor. + + \sa QSystemLock::unlock() + */ + +/*! \fn QSystemLocker::systemLock() + + Returns a pointer to the lock that was locked in the constructor. + */ + +/*! \fn QSystemLocker::relock() + + Relocks an unlocked locker. + + \sa unlock() + */ + +/*! \fn QSystemLocker::unlock() + + Unlocks this locker. You can use relock() to lock it again. + It does not need to be locked when destroyed. + + \sa relock() + */ + +/*! \class QSystemLock + + \brief The QSystemLock class provides a system wide lock + that can be used between threads or processes. + + The purpose of a QSystemLocker is to protect an object that can be + accessed by multiple threads or processes such as shared memory or a file. + + For example, say there is a method which prints a message to a log file: + + void log(const QString &logText) + { + QSystemLock systemLock(QLatin1String("logfile")); + systemLock.lock(); + QFile file(QDir::temp() + QLatin1String("/log")); + if (file.open(QIODevice::Append)) { + QTextStream out(&file); + out << logText; + } + systemLock.unlock(); + } + + If this is called from two seperate processes the resulting log file is + guaranteed to contain both lines. + + When you call lock(), other threads or processes that try to call lock() + with the same key will block until the thread or process that got the lock + calls unlock(). + + A non-blocking alternative to lock() is tryLock(). + */ + +/*! + Constructs a new system lock with \a key. The lock is created in an + unlocked state. + + \sa lock(), key(). + */ +QSystemLock::QSystemLock(const QString &key) +{ + d = new QSystemLockPrivate; + setKey(key); +} + +/*! + Destroys a system lock. + + warning: This will not unlock the system lock if it has been locked. +*/ +QSystemLock::~QSystemLock() +{ + d->cleanHandle(); + delete d; +} + +/*! + Sets a new key to this system lock. + + \sa key() + */ +void QSystemLock::setKey(const QString &key) +{ + if (key == d->key) + return; + d->cleanHandle(); + d->lockCount = 0; + d->key = key; + // cache the file name so it doesn't have to be generated all the time. + d->fileName = d->makeKeyFileName(); + d->error = QSystemLock::NoError; + d->errorString = QString(); + d->handle(); +} + +/*! + Returns the key assigned to this system lock + + \sa setKey() + */ +QString QSystemLock::key() const +{ + return d->key; +} + +/*! + Locks the system lock. Lock \a mode can either be ReadOnly or ReadWrite. + If a mode is ReadOnly, attempts by other processes to obtain + ReadOnly locks will succeed, and ReadWrite attempts will block until + all of the ReadOnly locks are unlocked. If locked as ReadWrite, all + other attempts to lock will block until the lock is unlocked. A given + QSystemLock can be locked multiple times without blocking, and will + only be unlocked after a corresponding number of unlock() + calls are made. Returns true on success; otherwise returns false. + + \sa unlock(), tryLock() + */ +bool QSystemLock::lock(LockMode mode) +{ + if (d->lockCount > 0 && mode == ReadOnly && d->lockedMode == ReadWrite) { + qWarning() << "QSystemLock::lock readwrite lock on top of readonly lock."; + return false; + } + return d->modifySemaphore(QSystemLockPrivate::Lock, mode); +} + +/*! + Unlocks the system lock. + Returns true on success; otherwise returns false. + + \sa lock() + */ +bool QSystemLock::unlock() +{ + if (d->lockCount == 0) { + qWarning() << "QSystemLock::unlock: unlock with no lock."; + return false; + } + return d->modifySemaphore(QSystemLockPrivate::Unlock, d->lockedMode); +} + +/*! + Returns the type of error that occurred last or NoError. + + \sa errorString() + */ +QSystemLock::SystemLockError QSystemLock::error() const +{ + return d->error; +} + +/*! + Returns the human-readable message appropriate to the current error + reported by error(). If no suitable string is available, an empty + string is returned. + + \sa error() + */ +QString QSystemLock::errorString() const +{ + return d->errorString; +} + diff --git a/tests/auto/qtipc/qsharedmemory/src/qsystemlock.h b/tests/auto/qtipc/qsharedmemory/src/qsystemlock.h new file mode 100644 index 0000000..7cd6b89 --- /dev/null +++ b/tests/auto/qtipc/qsharedmemory/src/qsystemlock.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 the test suite of the Qt Toolkit. +** +** $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 QSYSTEMLOCK_H +#define QSYSTEMLOCK_H + +#include <QtCore/qstring.h> + +QT_BEGIN_HEADER + +#ifndef QT_NO_SYSTEMLOCK + +QT_FORWARD_DECLARE_CLASS(QSystemLockPrivate) + +class QSystemLock +{ + +public: + enum SystemLockError + { + NoError, + UnknownError + }; + + QSystemLock(const QString &key); + ~QSystemLock(); + + void setKey(const QString &key); + QString key() const; + + enum LockMode + { + ReadOnly, + ReadWrite + }; + + bool lock(LockMode mode = ReadWrite); + bool unlock(); + + SystemLockError error() const; + QString errorString() const; + +private: + Q_DISABLE_COPY(QSystemLock) + + QSystemLockPrivate *d; +}; + +class QSystemLocker +{ + +public: + inline QSystemLocker(QSystemLock *systemLock, + QSystemLock::LockMode mode = QSystemLock::ReadWrite) : q_lock(systemLock) + { + autoUnLocked = relock(mode); + } + + inline ~QSystemLocker() + { + if (autoUnLocked) + unlock(); + } + + inline QSystemLock *systemLock() const + { + return q_lock; + } + + inline bool relock(QSystemLock::LockMode mode = QSystemLock::ReadWrite) + { + return (q_lock && q_lock->lock(mode)); + } + + inline bool unlock() + { + if (q_lock && q_lock->unlock()) { + autoUnLocked = false; + return true; + } + return false; + } + +private: + Q_DISABLE_COPY(QSystemLocker) + + bool autoUnLocked; + QSystemLock *q_lock; +}; + +#endif // QT_NO_SYSTEMLOCK + +QT_END_HEADER + +#endif // QSYSTEMLOCK_H + diff --git a/tests/auto/qtipc/qsharedmemory/src/qsystemlock_p.h b/tests/auto/qtipc/qsharedmemory/src/qsystemlock_p.h new file mode 100644 index 0000000..7995a91 --- /dev/null +++ b/tests/auto/qtipc/qsharedmemory/src/qsystemlock_p.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** 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 the test suite of the Qt Toolkit. +** +** $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 QSYSTEMLOCK_P_H +#define QSYSTEMLOCK_P_H + +#ifndef QT_NO_SYSTEMLOCK + +#include "qsystemlock.h" +#include "private/qsharedmemory_p.h" +#ifndef Q_OS_WINCE +#include <sys/types.h> +#endif + +#define MAX_LOCKS 64 + +class QSystemLockPrivate +{ + +public: + QSystemLockPrivate(); + + QString makeKeyFileName() + { + return QSharedMemoryPrivate::makePlatformSafeKey(key, QLatin1String("qipc_systemlock_")); + } + + void setErrorString(const QString &function); + +#ifdef Q_OS_WIN + HANDLE handle(); + bool lock(HANDLE, int count); + bool unlock(HANDLE, int count); +#else + key_t handle(); +#endif + void cleanHandle(); + + enum Operation { + Lock, + Unlock + }; + bool modifySemaphore(Operation op, QSystemLock::LockMode mode = QSystemLock::ReadOnly); + + QString key; + QString fileName; +#ifdef Q_OS_WIN + HANDLE semaphore; + HANDLE semaphoreLock; +#else + int semaphore; +#endif + int lockCount; + QSystemLock::LockMode lockedMode; + + QSystemLock::SystemLockError error; + QString errorString; + +private: +#ifndef Q_OS_WIN + key_t unix_key; + bool createdFile; + bool createdSemaphore; +#endif +}; + +#endif // QT_NO_SYSTEMLOCK + +#endif // QSYSTEMLOCK_P_H + diff --git a/tests/auto/qtipc/qsharedmemory/src/qsystemlock_unix.cpp b/tests/auto/qtipc/qsharedmemory/src/qsystemlock_unix.cpp new file mode 100644 index 0000000..525aa78 --- /dev/null +++ b/tests/auto/qtipc/qsharedmemory/src/qsystemlock_unix.cpp @@ -0,0 +1,233 @@ +/**************************************************************************** +** +** 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 the test suite of the Qt Toolkit. +** +** $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 "qsystemlock.h" +#include "qsystemlock_p.h" + +#include <qdebug.h> +#include <qfile.h> + +#include <sys/types.h> +#include <sys/ipc.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/shm.h> +#include <unistd.h> + +#include <sys/sem.h> +// We have to define this as on some sem.h will have it +union qt_semun { + int val; /* value for SETVAL */ + struct semid_ds *buf; /* buffer for IPC_STAT, IPC_SET */ + unsigned short *array; /* array for GETALL, SETALL */ +}; + +#define tr(x) QT_TRANSLATE_NOOP(QLatin1String("QSystemLock"), (x)) + +#if defined(Q_OS_SYMBIAN) +int createUnixKeyFile(const QString &fileName) +{ + if (QFile::exists(fileName)) + return 0; + + int fd = open(QFile::encodeName(fileName).constData(), + O_EXCL | O_CREAT | O_RDWR, 0640); + if (-1 == fd) { + if (errno == EEXIST) + return 0; + return -1; + } else { + close(fd); + } + return 1; +} +#endif + +QSystemLockPrivate::QSystemLockPrivate() : + semaphore(-1), lockCount(0), + error(QSystemLock::NoError), unix_key(-1), createdFile(false), createdSemaphore(false) +{ +} + +void QSystemLockPrivate::setErrorString(const QString &function) +{ + switch (errno) { + case EIDRM: + errorString = function + QLatin1String(": ") + tr("The semaphore set was removed"); + error = QSystemLock::UnknownError; + break; + default: + errorString = function + QLatin1String(": ") + tr("unknown error"); + error = QSystemLock::UnknownError; + qWarning() << errorString << "key" << key << "errno" << errno << ERANGE << ENOMEM << EINVAL << EINTR << EFBIG << EFAULT << EAGAIN << EACCES << E2BIG; + } +} + +/*! + \internal + + Setup unix_key + */ +key_t QSystemLockPrivate::handle() +{ + if (key.isEmpty()) + return -1; + + // ftok requires that an actual file exists somewhere + // If we have already made at some point in the past, + // double check that it is still there. + if (-1 != unix_key) { + int aNewunix_key = ftok(QFile::encodeName(fileName).constData(), 'Q'); + if (aNewunix_key != unix_key) { + cleanHandle(); + } else { + return unix_key; + } + } + + // Create the file needed for ftok +#if defined(Q_OS_SYMBIAN) + int built = createUnixKeyFile(fileName); +#else + int built = QSharedMemoryPrivate::createUnixKeyFile(fileName); +#endif + if (-1 == built) + return -1; + createdFile = (1 == built); + + // Get the unix key for the created file + unix_key = ftok(QFile::encodeName(fileName).constData(), 'Q'); + if (-1 == unix_key) { + setErrorString(QLatin1String("QSystemLock::handle ftok")); + return -1; + } + + // Get semaphore + semaphore = semget(unix_key, 1, 0666 | IPC_CREAT | IPC_EXCL); + if (-1 == semaphore) { + if (errno == EEXIST) + semaphore = semget(unix_key, 1, 0666 | IPC_CREAT); + if (-1 == semaphore) { + setErrorString(QLatin1String("QSystemLock::handle semget")); + cleanHandle(); + return -1; + } + } else { + // Created semaphore, initialize value. + createdSemaphore = true; + qt_semun init_op; + init_op.val = MAX_LOCKS; + if (-1 == semctl(semaphore, 0, SETVAL, init_op)) { + setErrorString(QLatin1String("QSystemLock::handle semctl")); + cleanHandle(); + return -1; + } + } + + return unix_key; +} + +/*! + \internal + + Cleanup the unix_key + */ +void QSystemLockPrivate::cleanHandle() +{ + unix_key = -1; + + // remove the file if we made it + if (createdFile) { + if (!QFile::remove(fileName)) + setErrorString(QLatin1String("QSystemLock::cleanHandle QFile::remove")); + createdFile = false; + } + + if (createdSemaphore) { + if (-1 != semaphore) { + if (-1 == semctl(semaphore, 0, IPC_RMID)) { + setErrorString(QLatin1String("QSystemLock::cleanHandle semctl")); + } + semaphore = -1; + } + createdSemaphore = false; + } +} + +/*! + \internal + + modifySemaphore generates operation.sem_op and handles recursive behavior. + */ +bool QSystemLockPrivate::modifySemaphore(QSystemLockPrivate::Operation op, + QSystemLock::LockMode mode) +{ + if (-1 == handle()) + return false; + + if ((lockCount == 0 && op == Lock) || (lockCount > 0 && op == Unlock)) { + if (op == Unlock) { + --lockCount; + Q_ASSERT(lockCount >= 0); + if (lockCount > 0) + return true; + } + + struct sembuf operation; + operation.sem_num = 0; + operation.sem_op = (mode == QSystemLock::ReadWrite) ? MAX_LOCKS : 1; + if (op == Lock) + operation.sem_op *= -1; + operation.sem_flg = SEM_UNDO; + + if (-1 == semop(semaphore, &operation, 1)) { + setErrorString(QLatin1String("QSystemLock::modify")); + return false; + } + lockedMode = mode; + } + if (op == Lock) + lockCount++; + + return true; +} + diff --git a/tests/auto/qtipc/qsharedmemory/src/qsystemlock_win.cpp b/tests/auto/qtipc/qsharedmemory/src/qsystemlock_win.cpp new file mode 100644 index 0000000..ac97100 --- /dev/null +++ b/tests/auto/qtipc/qsharedmemory/src/qsystemlock_win.cpp @@ -0,0 +1,182 @@ +/**************************************************************************** +** +** 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 the test suite of the Qt Toolkit. +** +** $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 "qsystemlock.h" +#include "qsystemlock_p.h" +#include <qdebug.h> +#include <QtCore> +QSystemLockPrivate::QSystemLockPrivate() : + semaphore(0), semaphoreLock(0), + lockCount(0), error(QSystemLock::NoError) +{ +} + +void QSystemLockPrivate::setErrorString(const QString &function) +{ + BOOL windowsError = GetLastError(); + if (windowsError == 0) + return; + errorString = function + QLatin1String(": ") + + QLatin1String("Unknown error"); + error = QSystemLock::UnknownError; + qWarning() << errorString << "key" << key << (int)windowsError << semaphore << semaphoreLock; +} + +/*! + \internal + + Setup the semaphore + */ +HANDLE QSystemLockPrivate::handle() +{ + // don't allow making handles on empty keys + if (key.isEmpty()) + return 0; + + // Create it if it doesn't already exists. + if (semaphore == 0) { + QString safeName = makeKeyFileName(); + semaphore = CreateSemaphore(0, MAX_LOCKS, MAX_LOCKS, (wchar_t*)safeName.utf16()); + + if (semaphore == 0) { + setErrorString(QLatin1String("QSystemLockPrivate::handle")); + return 0; + } + } + + if (semaphoreLock == 0) { + QString safeLockName = QSharedMemoryPrivate::makePlatformSafeKey(key + QLatin1String("lock"), QLatin1String("qipc_systemlock_")); + semaphoreLock = CreateSemaphore(0, 1, 1, (wchar_t*)safeLockName.utf16()); + + if (semaphoreLock == 0) { + setErrorString(QLatin1String("QSystemLockPrivate::handle")); + return 0; + } + } + + return semaphore; +} + +/*! + \internal + + Cleanup the semaphore + */ +void QSystemLockPrivate::cleanHandle() +{ + if (semaphore && !CloseHandle(semaphore)) + setErrorString(QLatin1String("QSystemLockPrivate::cleanHandle:")); + if (semaphoreLock && !CloseHandle(semaphoreLock)) + setErrorString(QLatin1String("QSystemLockPrivate::cleanHandle:")); + semaphore = 0; + semaphoreLock = 0; +} + +bool QSystemLockPrivate::lock(HANDLE handle, int count) +{ + if (count == 1) { + WaitForSingleObject(handle, INFINITE); + return true; + } + + int i = count; + while (i > 0) { + if (WAIT_OBJECT_0 == WaitForSingleObject(handle, 0)) { + --i; + } else { + // undo what we have done, sleep and then try again later + ReleaseSemaphore(handle, (count - i), 0); + i = count; + ReleaseSemaphore(semaphoreLock, 1, 0); + Sleep(1); + WaitForSingleObject(semaphoreLock, INFINITE); + } + } + return true; +} + +bool QSystemLockPrivate::unlock(HANDLE handle, int count) +{ + if (0 == ReleaseSemaphore(handle, count, 0)) { + setErrorString(QLatin1String("QSystemLockPrivate::unlock")); + return false; + } + return true; +} + +/*! + \internal + + modifySemaphore handles recursive behavior and modifies the semaphore. + */ +bool QSystemLockPrivate::modifySemaphore(QSystemLockPrivate::Operation op, + QSystemLock::LockMode mode) +{ + if (0 == handle()) + return false; + + if ((lockCount == 0 && op == Lock) || (lockCount > 0 && op == Unlock)) { + if (op == Unlock) { + --lockCount; + Q_ASSERT(lockCount >= 0); + if (lockCount > 0) + return true; + } + + int count = (mode == QSystemLock::ReadWrite) ? MAX_LOCKS : 1; + if (op == Lock) { + lock(semaphoreLock, 1); + lock(semaphore, count); + if (count != MAX_LOCKS) unlock(semaphoreLock, 1); + lockedMode = mode; + } else { + if (count == MAX_LOCKS) unlock(semaphoreLock, 1); + unlock(semaphore, count); + } + + } + if (op == Lock) + lockCount++; + + return true; +} + diff --git a/tests/auto/qtipc/qsharedmemory/src/src.pri b/tests/auto/qtipc/qsharedmemory/src/src.pri new file mode 100644 index 0000000..5bc9de6 --- /dev/null +++ b/tests/auto/qtipc/qsharedmemory/src/src.pri @@ -0,0 +1,10 @@ +INCLUDEPATH += $$PWD +DEPENDPATH += $$PWD + +SOURCES += $$PWD/qsystemlock.cpp + +HEADERS += $$PWD/qsystemlock.h \ + $$PWD/qsystemlock_p.h + +unix:SOURCES += $$PWD/qsystemlock_unix.cpp +win32:SOURCES += $$PWD/qsystemlock_win.cpp diff --git a/tests/auto/qtipc/qsharedmemory/test/test.pro b/tests/auto/qtipc/qsharedmemory/test/test.pro new file mode 100644 index 0000000..40ba8b9 --- /dev/null +++ b/tests/auto/qtipc/qsharedmemory/test/test.pro @@ -0,0 +1,37 @@ +load(qttest_p4) + +include(../src/src.pri) +QT -= gui + +DEFINES += QSHAREDMEMORY_DEBUG +DEFINES += QSYSTEMSEMAPHORE_DEBUG + +SOURCES += ../tst_qsharedmemory.cpp +TARGET = ../tst_qsharedmemory + +!wince*:win32 { + CONFIG(debug, debug|release) { + TARGET = ../../debug/tst_qsharedmemory +} else { + TARGET = ../../release/tst_qsharedmemory + } +} + +wince*:{ +requires(contains(QT_CONFIG,script)) +QT += gui script +addFiles.sources = ../../lackey/lackey.exe ../../lackey/scripts +addFiles.path = lackey +DEPLOYMENT += addFiles +DEFINES += SRCDIR=\\\"\\\" +}else:symbian*{ +requires(contains(QT_CONFIG,script)) +QT += gui script +addFiles.sources = ../../lackey/scripts +addFiles.path = /data/qsharedmemorytemp/lackey +addBin.sources = lackey.exe +addBin.path = /sys/bin +DEPLOYMENT += addFiles addBin +} else { +DEFINES += SRCDIR=\\\"$$PWD/../\\\" +} diff --git a/tests/auto/qtipc/qsharedmemory/tst_qsharedmemory.cpp b/tests/auto/qtipc/qsharedmemory/tst_qsharedmemory.cpp new file mode 100644 index 0000000..83f65b0 --- /dev/null +++ b/tests/auto/qtipc/qsharedmemory/tst_qsharedmemory.cpp @@ -0,0 +1,776 @@ +/**************************************************************************** +** +** 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 the test suite of the Qt Toolkit. +** +** $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 <QtTest/QtTest> +#include <qsharedmemory.h> + +//TESTED_CLASS= +//TESTED_FILES= + +#define EXISTING_SHARE "existing" +#define EXISTING_SIZE 1024 + +#ifdef Q_OS_SYMBIAN +#define SRCDIR "c:/data/qsharedmemorytemp/" +#define LACKEYDIR SRCDIR "lackey" +#elif Q_OS_WINCE +#define LACKEYDIR SRCDIR "lackey" +#else +#define LACKEYDIR SRCDIR "../lackey" +#endif + +Q_DECLARE_METATYPE(QSharedMemory::SharedMemoryError) +Q_DECLARE_METATYPE(QSharedMemory::AccessMode) + +class tst_QSharedMemory : public QObject +{ + Q_OBJECT + +public: + tst_QSharedMemory(); + virtual ~tst_QSharedMemory(); + +public Q_SLOTS: + void init(); + void cleanup(); + +private slots: + // basics + void constructor(); + void key_data(); + void key(); + void create_data(); + void create(); + void attach_data(); + void attach(); + void lock(); + + // custom edge cases + void removeWhileAttached(); + void emptyMemory(); + void readOnly(); + + // basics all together + void simpleProducerConsumer_data(); + void simpleProducerConsumer(); + void simpleDoubleProducerConsumer(); + + // with threads + void simpleThreadedProducerConsumer_data(); + void simpleThreadedProducerConsumer(); + + // with processes + void simpleProcessProducerConsumer_data(); + void simpleProcessProducerConsumer(); + + // extreme cases + void useTooMuchMemory(); + void attachTooMuch(); + +protected: + int remove(const QString &key); + + QString rememberKey(const QString &key) + { + if (key == EXISTING_SHARE) + return key; + if (!keys.contains(key)) { + keys.append(key); + remove(key); + } + return key; + } + + QStringList keys; + QList<QSharedMemory*> jail; + QSharedMemory *existingSharedMemory; +}; + +tst_QSharedMemory::tst_QSharedMemory() : existingSharedMemory(0) +{ +} + +tst_QSharedMemory::~tst_QSharedMemory() +{ +} + +void tst_QSharedMemory::init() +{ + existingSharedMemory = new QSharedMemory(EXISTING_SHARE); + if (!existingSharedMemory->create(EXISTING_SIZE)) { + QVERIFY(existingSharedMemory->error() == QSharedMemory::AlreadyExists); + } +} + +void tst_QSharedMemory::cleanup() +{ + delete existingSharedMemory; + qDeleteAll(jail.begin(), jail.end()); + jail.clear(); + + keys.append(EXISTING_SHARE); + for (int i = 0; i < keys.count(); ++i) { + QSharedMemory sm(keys.at(i)); + if (!sm.create(1024)) { + //if(sm.error() != QSharedMemory::KeyError) + // qWarning() << "test cleanup: remove failed:" << keys.at(i) << sm.error() << sm.errorString(); + sm.attach(); + sm.detach(); + remove(keys.at(i)); + } + } +} + +#ifndef Q_OS_WIN +#include "private/qsharedmemory_p.h" +#include <sys/types.h> +#include <sys/ipc.h> +#include <sys/shm.h> +#endif + +int tst_QSharedMemory::remove(const QString &key) +{ +#ifndef Q_OS_WIN + // On unix the shared memory might exists from a previously failed test + // or segfault, remove it it does + if (key.isEmpty()) + return -1; + + // ftok requires that an actual file exists somewhere + QString fileName = QSharedMemoryPrivate::makePlatformSafeKey(key); + if (!QFile::exists(fileName)) { + //qDebug() << "exits failed"; + return -2; + } + + int unix_key = ftok(fileName.toLatin1().constData(), 'Q'); + if (-1 == unix_key) { + qDebug() << "ftok failed"; + return -3; + } + + int id = shmget(unix_key, 0, 0660); + if (-1 == id) { + qDebug() << "shmget failed"; + return -4; + } + + struct shmid_ds shmid_ds; + if (-1 == shmctl(id, IPC_RMID, &shmid_ds)) { + qDebug() << "shmctl failed"; + return -5; + } + return QFile::remove(fileName); +#else + Q_UNUSED(key); + return 0; +#endif +} + +/*! + Tests the default values + */ +void tst_QSharedMemory::constructor() +{ + QSharedMemory sm; + QCOMPARE(sm.key(), QString()); + QVERIFY(!sm.isAttached()); + QVERIFY(sm.data() == 0); + QCOMPARE(sm.size(), 0); + QCOMPARE(sm.error(), QSharedMemory::NoError); + QVERIFY(sm.errorString() == QString()); +} + +void tst_QSharedMemory::key_data() +{ + QTest::addColumn<QString>("constructorKey"); + QTest::addColumn<QString>("setKey"); + + QTest::newRow("null, null") << QString() << QString(); + QTest::newRow("null, one") << QString() << QString("one"); + QTest::newRow("one, two") << QString("one") << QString("two"); + QTest::newRow("invalid") << QString("o/e") << QString("t/o"); +} + +/*! + Basic key testing + */ +void tst_QSharedMemory::key() +{ + QFETCH(QString, constructorKey); + QFETCH(QString, setKey); + + QSharedMemory sm(constructorKey); + QCOMPARE(sm.key(), constructorKey); + sm.setKey(setKey); + QCOMPARE(sm.key(), setKey); + QCOMPARE(sm.isAttached(), false); + + QCOMPARE(sm.error(), QSharedMemory::NoError); + QVERIFY(sm.errorString() == QString()); + QVERIFY(sm.data() == 0); + QCOMPARE(sm.size(), 0); + + QCOMPARE(sm.detach(), false); +} + +void tst_QSharedMemory::create_data() +{ + QTest::addColumn<QString>("key"); + QTest::addColumn<int>("size"); + QTest::addColumn<bool>("canCreate"); + QTest::addColumn<QSharedMemory::SharedMemoryError>("error"); + + QTest::newRow("null key") << QString() << 1024 + << false << QSharedMemory::LockError; + QTest::newRow("-1 size") << QString("negsize") << -1 + << false << QSharedMemory::InvalidSize; + QTest::newRow("nor size") << QString("norsize") << 1024 + << true << QSharedMemory::NoError; + QTest::newRow("already exists") << QString(EXISTING_SHARE) << EXISTING_SIZE + << false << QSharedMemory::AlreadyExists; +} + +/*! + Basic create testing + */ +void tst_QSharedMemory::create() +{ + QFETCH(QString, key); + QFETCH(int, size); + QFETCH(bool, canCreate); + QFETCH(QSharedMemory::SharedMemoryError, error); + + QSharedMemory sm(rememberKey(key)); + QCOMPARE(sm.create(size), canCreate); + if(sm.error() != error) + qDebug() << sm.errorString(); + QCOMPARE(sm.key(), key); + if (canCreate) { + QVERIFY(sm.errorString() == QString()); + QVERIFY(sm.data() != 0); + QVERIFY(sm.size() != 0); + } else { + QVERIFY(sm.data() == 0); + QVERIFY(sm.errorString() != QString()); + } +} + +void tst_QSharedMemory::attach_data() +{ + QTest::addColumn<QString>("key"); + QTest::addColumn<bool>("exists"); + QTest::addColumn<QSharedMemory::SharedMemoryError>("error"); + + QTest::newRow("null key") << QString() << false << QSharedMemory::LockError; + QTest::newRow("doesn't exists") << QString("doesntexists") << false << QSharedMemory::NotFound; + QTest::newRow("already exists") << QString(EXISTING_SHARE) << true << QSharedMemory::NoError; +} + +/*! + Basic attach/detach testing + */ +void tst_QSharedMemory::attach() +{ + QFETCH(QString, key); + QFETCH(bool, exists); + QFETCH(QSharedMemory::SharedMemoryError, error); +#ifdef Q_OS_HPUX + if (QLatin1String(QTest::currentDataTag()) == QLatin1String("already exists")) { + QSKIP("HPUX doesn't allow for multiple attaches per process", SkipSingle); + } +#endif + QSharedMemory sm(key); + QCOMPARE(sm.attach(), exists); + QCOMPARE(sm.isAttached(), exists); + QCOMPARE(sm.error(), error); + QCOMPARE(sm.key(), key); + if (exists) { + QVERIFY(sm.data() != 0); + QVERIFY(sm.size() != 0); + QCOMPARE(sm.errorString(), QString()); + QVERIFY(sm.detach()); + // Make sure detach doesn't screw up something and we can't re-attach. + QVERIFY(sm.attach()); + QVERIFY(sm.detach()); + QCOMPARE(sm.size(), 0); + QVERIFY(sm.data() == 0); + } else { + QVERIFY(sm.data() == 0); + QVERIFY(sm.size() == 0); + QVERIFY(sm.errorString() != QString()); + QVERIFY(!sm.detach()); + } +} + +void tst_QSharedMemory::lock() +{ + QSharedMemory shm; + QVERIFY(!shm.lock()); + QCOMPARE(shm.error(), QSharedMemory::LockError); + + shm.setKey(QLatin1String("qsharedmemory")); + + QVERIFY(!shm.lock()); + QCOMPARE(shm.error(), QSharedMemory::LockError); + + QVERIFY(shm.create(100)); + QVERIFY(shm.lock()); + QTest::ignoreMessage(QtWarningMsg, "QSharedMemory::lock: already locked"); + QVERIFY(shm.lock()); + // don't lock forever +} + +/*! + Other shared memory are allowed to be attached after we remove, + but new shared memory are not allowed to attach after a remove. + */ +void tst_QSharedMemory::removeWhileAttached() +{ +#ifdef Q_OS_HPUX + QSKIP("HPUX doesn't allow for multiple attaches per process", SkipAll); +#endif + rememberKey("one"); + + // attach 1 + QSharedMemory *smOne = new QSharedMemory(QLatin1String("one")); + QVERIFY(smOne->create(1024)); + QVERIFY(smOne->isAttached()); + + // attach 2 + QSharedMemory *smTwo = new QSharedMemory(QLatin1String("one")); + QVERIFY(smTwo->attach()); + QVERIFY(smTwo->isAttached()); + + // detach 1 and remove, remove one first to catch another error. + delete smOne; + delete smTwo; + + // three shouldn't be able to attach + QSharedMemory smThree(QLatin1String("one")); + QVERIFY(!smThree.attach()); + QCOMPARE(smThree.error(), QSharedMemory::NotFound); +} + +/*! + The memory should be set to 0 after created. + */ +void tst_QSharedMemory::emptyMemory() +{ + QSharedMemory sm(rememberKey(QLatin1String("voidland"))); + int size = 1024; + QVERIFY(sm.create(size, QSharedMemory::ReadOnly)); + char *get = (char*)sm.data(); + char null = 0; + for (int i = 0; i < size; ++i) + QCOMPARE(get[i], null); +} + +/*! + Verify that attach with ReadOnly is actually read only + by writing to data and causing a segfault. +*/ +void tst_QSharedMemory::readOnly() +{ +#ifdef Q_OS_WIN + QSKIP("This test opens a crash dialog on Windows", SkipSingle); +#endif +#if defined (Q_OS_SYMBIAN) + QSKIP("Readonly shared memory is not supported in symbian", SkipAll); +#endif + QString program = LACKEYDIR "/lackey"; + QStringList arguments; + rememberKey("readonly_segfault"); + arguments << LACKEYDIR "/scripts/readonly_segfault.js"; + + // ### on windows disable the popup somehow + QProcess p; + p.start(program, arguments); + p.setProcessChannelMode(QProcess::ForwardedChannels); + p.waitForFinished(); + QCOMPARE(p.error(), QProcess::Crashed); +} + +/*! + Keep making shared memory until the kernel stops us. + */ +void tst_QSharedMemory::useTooMuchMemory() +{ +#ifdef Q_OS_LINUX + bool success = true; + int count = 0; + while (success) { + QString key = QString("maxmemorytest_%1").arg(count++); + QSharedMemory *sm = new QSharedMemory(rememberKey(key)); + QVERIFY(sm); + jail.append(sm); + int size = 32768 * 1024; + success = sm->create(size); + if (!success && sm->error() == QSharedMemory::AlreadyExists) { + // left over from a crash, clean it up + sm->attach(); + sm->detach(); + success = sm->create(size); + } + + if (!success) { + QVERIFY(!sm->isAttached()); + QCOMPARE(sm->key(), key); + QCOMPARE(sm->size(), 0); + QVERIFY(sm->data() == 0); + if (sm->error() != QSharedMemory::OutOfResources) + qDebug() << sm->error() << sm->errorString(); + // ### Linux wont return OutOfResources if there are not enough semaphores to use. + QVERIFY(sm->error() == QSharedMemory::OutOfResources + || sm->error() == QSharedMemory::LockError); + QVERIFY(sm->errorString() != QString()); + QVERIFY(!sm->attach()); + QVERIFY(!sm->detach()); + } else { + QVERIFY(sm->isAttached()); + } + } +#endif +} + +/*! + Create one shared memory (government) and see how many other shared memories (wars) we can + attach before the system runs out of resources. + */ +void tst_QSharedMemory::attachTooMuch() +{ + QSKIP("disabled", SkipAll); +#ifdef Q_OS_HPUX + QSKIP("HPUX doesn't allow for multiple attaches per process", SkipAll); +#endif +#ifdef Q_OS_WINCE + QSKIP("This nearly kills the system itself, so skip for Qt/WinCE", SkipAll); +#endif + QSharedMemory government(rememberKey("government")); + QVERIFY(government.create(1024)); + while (true) { + QSharedMemory *war = new QSharedMemory(government.key()); + QVERIFY(war); + jail.append(war); + if (!war->attach()) { + QVERIFY(!war->isAttached()); + QCOMPARE(war->key(), government.key()); + QCOMPARE(war->size(), 0); + QVERIFY(war->data() == 0); + QCOMPARE(war->error(), QSharedMemory::OutOfResources); + QVERIFY(war->errorString() != QString()); + QVERIFY(!war->detach()); + break; + } else { + QVERIFY(war->isAttached()); + } + } +} + +void tst_QSharedMemory::simpleProducerConsumer_data() +{ + QTest::addColumn<QSharedMemory::AccessMode>("mode"); + + QTest::newRow("readonly") << QSharedMemory::ReadOnly; + QTest::newRow("readwrite") << QSharedMemory::ReadWrite; +} + +/*! + The basic consumer producer that rounds out the basic testing. + If this fails then any muli-threading/process might fail (but be + harder to debug) + + This doesn't require nor test any locking system. + */ +void tst_QSharedMemory::simpleProducerConsumer() +{ +#ifdef Q_OS_HPUX + QSKIP("HPUX doesn't allow for multiple attaches per process", SkipAll); +#endif + QFETCH(QSharedMemory::AccessMode, mode); + + rememberKey(QLatin1String("market")); + QSharedMemory producer(QLatin1String("market")); + QSharedMemory consumer(QLatin1String("market")); + int size = 512; + QVERIFY(producer.create(size)); + QVERIFY(consumer.attach(mode)); + + char *put = (char*)producer.data(); + char *get = (char*)consumer.data(); + // On Windows CE you always have ReadWrite access. Thus + // ViewMapOfFile returns the same pointer + // On Symbian, the address will always be same, as + // write protection of chunks is not currently supported by Symbian +#if !defined(Q_OS_WINCE) && !defined(Q_OS_SYMBIAN) + QVERIFY(put != get); +#endif + for (int i = 0; i < size; ++i) { + put[i] = 'Q'; + QCOMPARE(get[i], 'Q'); + } + QVERIFY(consumer.detach()); +} + +void tst_QSharedMemory::simpleDoubleProducerConsumer() +{ +#ifdef Q_OS_HPUX + QSKIP("HPUX doesn't allow for multiple attaches per process", SkipAll); +#endif + rememberKey(QLatin1String("market")); + QSharedMemory producer(QLatin1String("market")); + int size = 512; + QVERIFY(producer.create(size)); + QVERIFY(producer.detach()); + QVERIFY(producer.create(size)); + + { + QSharedMemory consumer(QLatin1String("market")); + QVERIFY(consumer.attach()); + } +} + +class Consumer : public QThread +{ + +public: + void run() + { + QSharedMemory consumer(QLatin1String("market")); + while (!consumer.attach()) { + if (consumer.error() != QSharedMemory::NotFound) + qDebug() << "consumer: failed to connect" << consumer.error() << consumer.errorString(); + QVERIFY(consumer.error() == QSharedMemory::NotFound || consumer.error() == QSharedMemory::KeyError); + QTest::qWait(1); + } + + char *memory = (char*)consumer.data(); + + int i = 0; + while (true) { + if(!consumer.lock()) + break; + if (memory[0] == 'Q') + memory[0] = ++i; + if (memory[0] == 'E') { + memory[1]++; + QVERIFY(consumer.unlock()); + break; + } + QVERIFY(consumer.unlock()); + QTest::qWait(1); + } + + QVERIFY(consumer.detach()); + } +}; + +class Producer : public QThread +{ + +public: + void run() + { + QSharedMemory producer(QLatin1String("market")); + int size = 1024; + if (!producer.create(size)) { + // left over from a crash... + if (producer.error() == QSharedMemory::AlreadyExists) { + producer.attach(); + producer.detach(); + QVERIFY(producer.create(size)); + } + } + QVERIFY(producer.isAttached()); + char *memory = (char*)producer.data(); + memory[1] = '0'; + QTime timer; + timer.start(); + int i = 0; + while (i < 5 && timer.elapsed() < 5000) { + QVERIFY(producer.lock()); + if (memory[0] == 'Q') { + QVERIFY(producer.unlock()); + QTest::qWait(1); + continue; + } + ++i; + memory[0] = 'Q'; + QVERIFY(producer.unlock()); + QTest::qWait(1); + } + + // tell everyone to quit + QVERIFY(producer.lock()); + memory[0] = 'E'; + QVERIFY(producer.unlock()); + +#if defined(Q_OS_SYMBIAN) + // Sleep a while to ensure that consumers start properly + QTest::qSleep(1000); +#endif + } +private: + +}; + +void tst_QSharedMemory::simpleThreadedProducerConsumer_data() +{ + QTest::addColumn<bool>("producerIsThread"); + QTest::addColumn<int>("threads"); + for (int i = 0; i < 5; ++i) { + QTest::newRow("1 consumer, producer is thread") << true << 1; + QTest::newRow("1 consumer, producer is this") << false << 1; + QTest::newRow("5 consumers, producer is thread") << true << 5; + QTest::newRow("5 consumers, producer is this") << false << 5; + } +} + +/*! + The basic producer/consumer, but this time using threads. + */ +void tst_QSharedMemory::simpleThreadedProducerConsumer() +{ + QFETCH(bool, producerIsThread); + QFETCH(int, threads); + rememberKey(QLatin1String("market")); + +#if defined Q_OS_HPUX && defined __ia64 + QSKIP("This test locks up on gravlaks.troll.no", SkipSingle); +#endif + + Producer p; +#if defined(Q_OS_SYMBIAN) + enum + { + /** + * The maximum stack size. + */ + SymbianStackSize = 0x14000 + }; + p.setStackSize(SymbianStackSize); +#endif + if (producerIsThread) + p.start(); + + QList<Consumer*> consumers; + for (int i = 0; i < threads; ++i) { + consumers.append(new Consumer()); +#if defined(Q_OS_SYMBIAN) + consumers.last()->setStackSize(SymbianStackSize); +#endif + consumers.last()->start(); + } + + if (!producerIsThread) + p.run(); + + p.wait(5000); + while (!consumers.isEmpty()) { + QVERIFY(consumers.first()->wait(5000)); + delete consumers.takeFirst(); + } +} + +void tst_QSharedMemory::simpleProcessProducerConsumer_data() +{ + QTest::addColumn<int>("processes"); + int tries = 5; + for (int i = 0; i < tries; ++i) { + QTest::newRow("1 process") << 1; + QTest::newRow("5 processes") << 5; + } +} + +/*! + Create external processes that produce and consume. + */ +void tst_QSharedMemory::simpleProcessProducerConsumer() +{ +#if defined (Q_OS_SYMBIAN) && defined(Q_CC_NOKIAX86) + QSKIP("Cannot launch multiple Qt processes in Symbian emulator", SkipAll); +#endif + QFETCH(int, processes); + + rememberKey("market"); + + QStringList arguments = QStringList() << LACKEYDIR "/scripts/producer.js"; + QProcess producer; + producer.setProcessChannelMode(QProcess::ForwardedChannels); + producer.start( LACKEYDIR "/lackey", arguments); + producer.waitForStarted(); + QVERIFY(producer.error() != QProcess::FailedToStart); + + QList<QProcess*> consumers; + unsigned int failedProcesses = 0; + for (int i = 0; i < processes; ++i) { + QStringList arguments = QStringList() << LACKEYDIR "/scripts/consumer.js"; + QProcess *p = new QProcess; + p->setProcessChannelMode(QProcess::ForwardedChannels); + p->start(LACKEYDIR "/lackey", arguments); + + if (p->waitForStarted(2000)) + consumers.append(p); + else + ++failedProcesses; + } + + QVERIFY(producer.waitForFinished(5000)); + + bool consumerFailed = false; + + while (!consumers.isEmpty()) { + QVERIFY(consumers.first()->waitForFinished(3000)); + if (consumers.first()->state() == QProcess::Running || + consumers.first()->exitStatus() != QProcess::NormalExit || + consumers.first()->exitCode() != 0) { + consumerFailed = true; + } + delete consumers.takeFirst(); + } + QCOMPARE(consumerFailed, false); + QCOMPARE(failedProcesses, (unsigned int)(0)); +} + +QTEST_MAIN(tst_QSharedMemory) +#include "tst_qsharedmemory.moc" + diff --git a/tests/auto/qtipc/qsystemsemaphore/.gitignore b/tests/auto/qtipc/qsystemsemaphore/.gitignore new file mode 100644 index 0000000..2b60bfb --- /dev/null +++ b/tests/auto/qtipc/qsystemsemaphore/.gitignore @@ -0,0 +1 @@ +tst_qsystemsemaphore diff --git a/tests/auto/qtipc/qsystemsemaphore/files.qrc b/tests/auto/qtipc/qsystemsemaphore/files.qrc new file mode 100644 index 0000000..6296690 --- /dev/null +++ b/tests/auto/qtipc/qsystemsemaphore/files.qrc @@ -0,0 +1,7 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource> + <file alias="systemsemaphore_acquire.js">../lackey/scripts/systemsemaphore_acquire.js</file> + <file alias="systemsemaphore_release.js">../lackey/scripts/systemsemaphore_release.js</file> + <file alias="systemsemaphore_acquirerelease.js">../lackey/scripts/systemsemaphore_acquirerelease.js</file> +</qresource> +</RCC> diff --git a/tests/auto/qtipc/qsystemsemaphore/qsystemsemaphore.pro b/tests/auto/qtipc/qsystemsemaphore/qsystemsemaphore.pro new file mode 100644 index 0000000..1f9205e --- /dev/null +++ b/tests/auto/qtipc/qsystemsemaphore/qsystemsemaphore.pro @@ -0,0 +1,36 @@ +CONFIG += qttest_p4 +#QT = core + +include(../qsharedmemory/src/src.pri) +win32: CONFIG += console + +DEFINES += QSHAREDMEMORY_DEBUG +DEFINES += QSYSTEMSEMAPHORE_DEBUG + +SOURCES += tst_qsystemsemaphore.cpp +TARGET = tst_qsystemsemaphore + +RESOURCES += files.qrc + +wince*: { +requires(contains(QT_CONFIG,script)) +# this test calls lackey, which then again depends on QtScript. +# let's add it here so that it gets deployed easily +QT += script +lackey.sources = ../lackey/lackey.exe +lackey.path = ../lackey + +DEPLOYMENT += lackey +} + +symbian: { +requires(contains(QT_CONFIG,script)) +# this test calls lackey, which then again depends on QtScript. +# let's add it here so that it gets deployed easily +QT += script + +lackey.sources = ../lackey/lackey.exe +lackey.path = /sys/bin +DEPLOYMENT += lackey +} + diff --git a/tests/auto/qtipc/qsystemsemaphore/tst_qsystemsemaphore.cpp b/tests/auto/qtipc/qsystemsemaphore/tst_qsystemsemaphore.cpp new file mode 100644 index 0000000..eb82fd4 --- /dev/null +++ b/tests/auto/qtipc/qsystemsemaphore/tst_qsystemsemaphore.cpp @@ -0,0 +1,302 @@ +/**************************************************************************** +** +** 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 the test suite of the Qt Toolkit. +** +** $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 <QtTest/QtTest> +#include <qsystemsemaphore.h> +//TESTED_CLASS= +//TESTED_FILES= + +#define EXISTING_SHARE "existing" +#define LACKYLOC "../lackey" +#define LACKYWAITTIME 10000 + +class tst_QSystemSemaphore : public QObject +{ + Q_OBJECT + +public: + tst_QSystemSemaphore(); + virtual ~tst_QSystemSemaphore(); + +public Q_SLOTS: + void init(); + void cleanup(); + +private slots: + void key_data(); + void key(); + + void basicacquire(); + void complexacquire(); + + void basicProcesses(); + + void processes_data(); + void processes(); + + void undo(); + void initialValue(); + +private: + QSystemSemaphore *existingLock; + + QString makeFile(const QString &resource) + { + QFile memory(resource); + if (!memory.open(QIODevice::ReadOnly)) { + qDebug() << "error reading resource" << resource; + return QString(); + } + QTemporaryFile *file = new QTemporaryFile; + file->open(); + file->write(memory.readAll()); + tempFiles.append(file); + file->flush(); +#ifdef Q_OS_WINCE + // flush does not flush to disk on Windows CE. It flushes it into its application + // cache. Thus we need to close the file to be able that other processes(lackey) can read it + QString fileName = file->fileName(); + file->close(); + return fileName; +#endif + return file->fileName(); + } + + QString acquire_js() { return makeFile(":/systemsemaphore_acquire.js"); } + QString release_js() { return makeFile(":/systemsemaphore_release.js"); } + QString acquirerelease_js() { return makeFile(":/systemsemaphore_acquirerelease.js"); } + QList<QTemporaryFile*> tempFiles; +}; + +tst_QSystemSemaphore::tst_QSystemSemaphore() +{ + if (!QFile::exists(LACKYLOC "/lackey")) + qWarning() << "lackey executable doesn't exists!"; +} + +tst_QSystemSemaphore::~tst_QSystemSemaphore() +{ + qDeleteAll(tempFiles); +} + +void tst_QSystemSemaphore::init() +{ + existingLock = new QSystemSemaphore(EXISTING_SHARE, 1, QSystemSemaphore::Create); +} + +void tst_QSystemSemaphore::cleanup() +{ + delete existingLock; +} + +void tst_QSystemSemaphore::key_data() +{ + QTest::addColumn<QString>("constructorKey"); + QTest::addColumn<QString>("setKey"); + + QTest::newRow("null, null") << QString() << QString(); + QTest::newRow("null, one") << QString() << QString("one"); + QTest::newRow("one, two") << QString("one") << QString("two"); +} + +/*! + Basic key testing + */ +void tst_QSystemSemaphore::key() +{ + QFETCH(QString, constructorKey); + QFETCH(QString, setKey); + + QSystemSemaphore sem(constructorKey); + QCOMPARE(sem.key(), constructorKey); + QCOMPARE(sem.error(), QSystemSemaphore::NoError); + QCOMPARE(sem.errorString(), QString()); + + sem.setKey(setKey); + QCOMPARE(sem.key(), setKey); + QCOMPARE(sem.error(), QSystemSemaphore::NoError); + QCOMPARE(sem.errorString(), QString()); +} + +void tst_QSystemSemaphore::basicacquire() +{ + QSystemSemaphore sem("QSystemSemaphore_basicacquire", 1, QSystemSemaphore::Create); + QVERIFY(sem.acquire()); + QCOMPARE(sem.error(), QSystemSemaphore::NoError); + QVERIFY(sem.release()); + QCOMPARE(sem.error(), QSystemSemaphore::NoError); + QCOMPARE(sem.errorString(), QString()); +} + +void tst_QSystemSemaphore::complexacquire() +{ + QSystemSemaphore sem("QSystemSemaphore_complexacquire", 2, QSystemSemaphore::Create); + QVERIFY(sem.acquire()); + QVERIFY(sem.release()); + QVERIFY(sem.acquire()); + QVERIFY(sem.release()); + QVERIFY(sem.acquire()); + QVERIFY(sem.acquire()); + QVERIFY(sem.release()); + QVERIFY(sem.release()); + QCOMPARE(sem.error(), QSystemSemaphore::NoError); + QCOMPARE(sem.errorString(), QString()); +} + +void tst_QSystemSemaphore::basicProcesses() +{ +#if defined (Q_OS_SYMBIAN) && defined(Q_CC_NOKIAX86) + QSKIP("Cannot launch multiple Qt processes in Symbian emulator", SkipAll); +#endif + QSystemSemaphore sem("store", 0, QSystemSemaphore::Create); + + QStringList acquireArguments = QStringList() << acquire_js(); + QStringList releaseArguments = QStringList() << release_js(); + QProcess acquire; + acquire.setProcessChannelMode(QProcess::ForwardedChannels); + + QProcess release; + release.setProcessChannelMode(QProcess::ForwardedChannels); + + acquire.start(LACKYLOC "/lackey", acquireArguments); + acquire.waitForFinished(LACKYWAITTIME); + QVERIFY(acquire.state() == QProcess::Running); + acquire.kill(); + release.start(LACKYLOC "/lackey", releaseArguments); + acquire.waitForFinished(LACKYWAITTIME); + release.waitForFinished(LACKYWAITTIME); + QVERIFY(acquire.state() == QProcess::NotRunning); +} + +void tst_QSystemSemaphore::processes_data() +{ + QTest::addColumn<int>("processes"); + for (int i = 0; i < 5; ++i) { + QTest::newRow("1 process") << 1; + QTest::newRow("3 process") << 3; + QTest::newRow("10 process") << 10; + } +} + +void tst_QSystemSemaphore::processes() +{ +#if defined (Q_OS_SYMBIAN) && defined(Q_CC_NOKIAX86) + QSKIP("Cannot launch multiple Qt processes in Symbian emulator", SkipAll); +#endif + QSystemSemaphore sem("store", 1, QSystemSemaphore::Create); + + QFETCH(int, processes); + QStringList scripts; + for (int i = 0; i < processes; ++i) + scripts.append(acquirerelease_js()); + + QList<QProcess*> consumers; + for (int i = 0; i < scripts.count(); ++i) { + QStringList arguments = QStringList() << scripts.at(i); + QProcess *p = new QProcess; + p->setProcessChannelMode(QProcess::ForwardedChannels); + consumers.append(p); + p->start(LACKYLOC "/lackey", arguments); + } + + while (!consumers.isEmpty()) { + consumers.first()->waitForFinished(); + QCOMPARE(consumers.first()->exitStatus(), QProcess::NormalExit); + QCOMPARE(consumers.first()->exitCode(), 0); + delete consumers.takeFirst(); + } +} + +void tst_QSystemSemaphore::undo() +{ +#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) + QSKIP("This test only checks a unix behavior", SkipSingle); +#endif + + QSystemSemaphore sem("store", 1, QSystemSemaphore::Create); + + QStringList acquireArguments = QStringList() << acquire_js(); + QProcess acquire; + acquire.setProcessChannelMode(QProcess::ForwardedChannels); + acquire.start(LACKYLOC "/lackey", acquireArguments); + acquire.waitForFinished(LACKYWAITTIME); + QVERIFY(acquire.state()== QProcess::NotRunning); + + // At process exit the kernel should auto undo + + acquire.start(LACKYLOC "/lackey", acquireArguments); + acquire.waitForFinished(LACKYWAITTIME); + QVERIFY(acquire.state()== QProcess::NotRunning); +} + +void tst_QSystemSemaphore::initialValue() +{ +#if defined (Q_OS_SYMBIAN) && defined(Q_CC_NOKIAX86) + QSKIP("Cannot launch multiple Qt processes in Symbian emulator", SkipAll); +#endif + QSystemSemaphore sem("store", 1, QSystemSemaphore::Create); + + QStringList acquireArguments = QStringList() << acquire_js(); + QStringList releaseArguments = QStringList() << release_js(); + QProcess acquire; + acquire.setProcessChannelMode(QProcess::ForwardedChannels); + + QProcess release; + release.setProcessChannelMode(QProcess::ForwardedChannels); + + acquire.start(LACKYLOC "/lackey", acquireArguments); + acquire.waitForFinished(LACKYWAITTIME); + QVERIFY(acquire.state()== QProcess::NotRunning); + + acquire.start(LACKYLOC "/lackey", acquireArguments << "2"); + acquire.waitForFinished(LACKYWAITTIME); + QVERIFY(acquire.state()== QProcess::Running); + acquire.kill(); + + release.start(LACKYLOC "/lackey", releaseArguments); + acquire.waitForFinished(LACKYWAITTIME); + release.waitForFinished(LACKYWAITTIME); + QVERIFY(acquire.state()== QProcess::NotRunning); +} +QTEST_MAIN(tst_QSystemSemaphore) +#include "tst_qsystemsemaphore.moc" + diff --git a/tests/auto/qtipc/qtipc.pro b/tests/auto/qtipc/qtipc.pro new file mode 100644 index 0000000..60037d2 --- /dev/null +++ b/tests/auto/qtipc/qtipc.pro @@ -0,0 +1,6 @@ +TEMPLATE=subdirs +SUBDIRS=\ + lackey \ + qsharedmemory \ + qsystemsemaphore \ + |