From dd904c3c48c9c153a7904b0ee4da0251dd3cb9b4 Mon Sep 17 00:00:00 2001 From: Oliver Wolff Date: Mon, 29 Oct 2012 14:32:23 +0100 Subject: move QSharedMemory autotest from qtscript to qtbase As the script dependency for that autotest is not really needed it should be moved to qtbase. Task-number: QTBUG-27706 Change-Id: Ieda8b2182a20a77f53a0be9878e82e3236c79c2b Reviewed-by: Oswald Buddenhagen Reviewed-by: Joerg Bornemann --- tests/auto/corelib/kernel/kernel.pro | 4 +- .../corelib/kernel/qsharedmemory/qsharedmemory.pro | 3 + .../qsharedmemory/sharedmemoryhelper/main.cpp | 208 ++++++ .../sharedmemoryhelper/sharedmemoryhelper.pro | 17 + .../corelib/kernel/qsharedmemory/test/test.pro | 18 + .../qsharedmemory/test/tst_qsharedmemory.cpp | 809 +++++++++++++++++++++ 6 files changed, 1058 insertions(+), 1 deletion(-) create mode 100644 tests/auto/corelib/kernel/qsharedmemory/qsharedmemory.pro create mode 100644 tests/auto/corelib/kernel/qsharedmemory/sharedmemoryhelper/main.cpp create mode 100644 tests/auto/corelib/kernel/qsharedmemory/sharedmemoryhelper/sharedmemoryhelper.pro create mode 100644 tests/auto/corelib/kernel/qsharedmemory/test/test.pro create mode 100644 tests/auto/corelib/kernel/qsharedmemory/test/tst_qsharedmemory.cpp diff --git a/tests/auto/corelib/kernel/kernel.pro b/tests/auto/corelib/kernel/kernel.pro index 7ec01d4a36..584352c516 100644 --- a/tests/auto/corelib/kernel/kernel.pro +++ b/tests/auto/corelib/kernel/kernel.pro @@ -12,6 +12,7 @@ SUBDIRS=\ qmimedata \ qobject \ qpointer \ + qsharedmemory \ qsignalmapper \ qsocketnotifier \ qtimer \ @@ -20,7 +21,8 @@ SUBDIRS=\ qwineventnotifier !contains(QT_CONFIG, private_tests): SUBDIRS -= \ - qsocketnotifier + qsocketnotifier \ + qsharedmemory # This test is only applicable on Windows !win32*:SUBDIRS -= qwineventnotifier diff --git a/tests/auto/corelib/kernel/qsharedmemory/qsharedmemory.pro b/tests/auto/corelib/kernel/qsharedmemory/qsharedmemory.pro new file mode 100644 index 0000000000..a36b15c906 --- /dev/null +++ b/tests/auto/corelib/kernel/qsharedmemory/qsharedmemory.pro @@ -0,0 +1,3 @@ +TEMPLATE = subdirs + +SUBDIRS = sharedmemoryhelper test diff --git a/tests/auto/corelib/kernel/qsharedmemory/sharedmemoryhelper/main.cpp b/tests/auto/corelib/kernel/qsharedmemory/sharedmemoryhelper/main.cpp new file mode 100644 index 0000000000..63f38f43f3 --- /dev/null +++ b/tests/auto/corelib/kernel/qsharedmemory/sharedmemoryhelper/main.cpp @@ -0,0 +1,208 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include + +void set(QSharedMemory &sm, int pos, QChar value) +{ + ((char*)sm.data())[pos] = value.toLatin1(); +} + +QChar get(QSharedMemory &sm, int i) +{ + return QChar::fromLatin1(((char*)sm.data())[i]); +} + +int readonly_segfault() +{ + QSharedMemory sharedMemory; + sharedMemory.setKey("readonly_segfault"); + sharedMemory.create(1024, QSharedMemory::ReadOnly); + sharedMemory.lock(); + set(sharedMemory, 0, 'a'); + sharedMemory.unlock(); + return EXIT_SUCCESS; +} + +int producer() +{ + QSharedMemory producer; + producer.setKey("market"); + + int size = 1024; + if (!producer.create(size)) { + if (producer.error() == QSharedMemory::AlreadyExists) { + if (!producer.attach()) { + qWarning() << "Could not attach to" << producer.key(); + return EXIT_FAILURE; + } + } else { + qWarning() << "Could not create" << producer.key(); + return EXIT_FAILURE; + } + } + //qDebug("producer created and attached"); + + if (!producer.lock()) { + qWarning() << "Could not lock" << producer.key(); + return EXIT_FAILURE; + } + set(producer, 0, 'Q'); + if (!producer.unlock()) { + qWarning() << "Could not lock" << producer.key(); + return EXIT_FAILURE; + } + + int i = 0; + while (i < 5) { + if (!producer.lock()) { + qWarning() << "Could not lock" << producer.key(); + return EXIT_FAILURE; + } + if (get(producer, 0) == 'Q') { + if (!producer.unlock()) { + qWarning() << "Could not unlock" << producer.key(); + return EXIT_FAILURE; + } + QTest::qSleep(1); + continue; + } + //qDebug() << "producer:" << i); + ++i; + set(producer, 0, 'Q'); + if (!producer.unlock()) { + qWarning() << "Could not unlock" << producer.key(); + return EXIT_FAILURE; + } + QTest::qSleep(1); + } + if (!producer.lock()) { + qWarning() << "Could not lock" << producer.key(); + return EXIT_FAILURE; + } + set(producer, 0, 'E'); + if (!producer.unlock()) { + qWarning() << "Could not unlock" << producer.key(); + return EXIT_FAILURE; + } + + //qDebug("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. + QTest::qSleep(3000); + return EXIT_SUCCESS; +} + +int consumer() +{ + QSharedMemory consumer; + consumer.setKey("market"); + + //qDebug("consumer starting"); + int tries = 0; + while (!consumer.attach()) { + if (tries == 5000) { + qWarning() << "consumer exiting, waiting too long"; + return EXIT_FAILURE; + } + ++tries; + QTest::qSleep(1); + } + //qDebug("consumer attached"); + + + int i = 0; + while (true) { + if (!consumer.lock()) { + qWarning() << "Could not lock" << consumer.key(); + return EXIT_FAILURE; + } + if (get(consumer, 0) == 'Q') { + set(consumer, 0, ++i); + //qDebug() << "consumer sets" << i; + } + if (get(consumer, 0) == 'E') { + if (!consumer.unlock()) { + qWarning() << "Could not unlock" << consumer.key(); + return EXIT_FAILURE; + } + break; + } + if (!consumer.unlock()) { + qWarning() << "Could not unlock" << consumer.key(); + return EXIT_FAILURE; + } + QTest::qSleep(10); + } + + //qDebug("consumer detaching"); + if (!consumer.detach()) { + qWarning() << "Could not detach" << consumer.key(); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + + QStringList arguments = app.arguments(); + if (app.arguments().count() != 2) { + qWarning("Please call the helper with the function to call as argument"); + return EXIT_FAILURE; + } + QString function = arguments.at(1); + if (function == QLatin1String("readonly_segfault")) + return readonly_segfault(); + else if (function == QLatin1String("producer")) + return producer(); + else if (function == QLatin1String("consumer")) + return consumer(); + else + qWarning() << "Unknown function" << arguments.at(1); + + return EXIT_SUCCESS; +} diff --git a/tests/auto/corelib/kernel/qsharedmemory/sharedmemoryhelper/sharedmemoryhelper.pro b/tests/auto/corelib/kernel/qsharedmemory/sharedmemoryhelper/sharedmemoryhelper.pro new file mode 100644 index 0000000000..389015d504 --- /dev/null +++ b/tests/auto/corelib/kernel/qsharedmemory/sharedmemoryhelper/sharedmemoryhelper.pro @@ -0,0 +1,17 @@ +QT = core testlib + +win32: CONFIG += console +mac:CONFIG -= app_bundle + +SOURCES += main.cpp +TARGET = helperbinary + +CONFIG(debug_and_release) { + CONFIG(debug, debug|release) { + DESTDIR = ../debug + } else { + DESTDIR = ../release + } +} else { + DESTDIR = .. +} diff --git a/tests/auto/corelib/kernel/qsharedmemory/test/test.pro b/tests/auto/corelib/kernel/qsharedmemory/test/test.pro new file mode 100644 index 0000000000..97ba1153df --- /dev/null +++ b/tests/auto/corelib/kernel/qsharedmemory/test/test.pro @@ -0,0 +1,18 @@ +CONFIG += testcase + +QT = core-private testlib + +mac:CONFIG -= app_bundle + +SOURCES += tst_qsharedmemory.cpp +TARGET = tst_qsharedmemory + +CONFIG(debug_and_release) { + CONFIG(debug, debug|release) { + DESTDIR = ../debug + } else { + DESTDIR = ../release + } +} else { + DESTDIR = .. +} diff --git a/tests/auto/corelib/kernel/qsharedmemory/test/tst_qsharedmemory.cpp b/tests/auto/corelib/kernel/qsharedmemory/test/tst_qsharedmemory.cpp new file mode 100644 index 0000000000..5d9b470494 --- /dev/null +++ b/tests/auto/corelib/kernel/qsharedmemory/test/tst_qsharedmemory.cpp @@ -0,0 +1,809 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#define EXISTING_SHARE "existing" +#define EXISTING_SIZE 1024 + +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 initTestCase(); + 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 +#ifndef Q_OS_HPUX + void removeWhileAttached(); +#endif + void emptyMemory(); +#ifndef Q_OS_WIN + void readOnly(); +#endif + + // basics all together +#ifndef Q_OS_HPUX + void simpleProducerConsumer_data(); + void simpleProducerConsumer(); + void simpleDoubleProducerConsumer(); +#endif + + // with threads + void simpleThreadedProducerConsumer_data(); + void simpleThreadedProducerConsumer(); + + // with processes + void simpleProcessProducerConsumer_data(); + void simpleProcessProducerConsumer(); + + // extreme cases + void useTooMuchMemory(); +#if !defined(Q_OS_HPUX) && !defined(Q_OS_WINCE) + void attachTooMuch(); +#endif + + // unique keys + void uniqueKey_data(); + void uniqueKey(); + +protected: + QString helperBinary(); + 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 jail; + QSharedMemory *existingSharedMemory; +}; + +tst_QSharedMemory::tst_QSharedMemory() : + existingSharedMemory(0) +{ +} + +tst_QSharedMemory::~tst_QSharedMemory() +{ +} + +void tst_QSharedMemory::initTestCase() +{ + QVERIFY2(!helperBinary().isEmpty(), "Could not find helper binary"); +} + +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 +#include +#include +#include +#endif + +QString tst_QSharedMemory::helperBinary() +{ + QString binary = QStringLiteral("helperbinary"); +#ifdef Q_OS_WIN + binary += QStringLiteral(".exe"); +#endif + return QFINDTESTDATA(binary); +} + +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("constructorKey"); + QTest::addColumn("setKey"); + QTest::addColumn("setNativeKey"); + + QTest::newRow("null, null, null") << QString() << QString() << QString(); + QTest::newRow("one, null, null") << QString("one") << QString() << QString(); + QTest::newRow("null, one, null") << QString() << QString("one") << QString(); + QTest::newRow("null, null, one") << QString() << QString() << QString("one"); + QTest::newRow("one, two, null") << QString("one") << QString("two") << QString(); + QTest::newRow("one, null, two") << QString("one") << QString() << QString("two"); + QTest::newRow("null, one, two") << QString() << QString("one") << QString("two"); + QTest::newRow("one, two, three") << QString("one") << QString("two") << QString("three"); + QTest::newRow("invalid") << QString("o/e") << QString("t/o") << QString("|x"); +} + +/*! + Basic key testing + */ +void tst_QSharedMemory::key() +{ + QFETCH(QString, constructorKey); + QFETCH(QString, setKey); + QFETCH(QString, setNativeKey); + + QSharedMemory sm(constructorKey); + QCOMPARE(sm.key(), constructorKey); + QCOMPARE(sm.nativeKey().isEmpty(), constructorKey.isEmpty()); + sm.setKey(setKey); + QCOMPARE(sm.key(), setKey); + QCOMPARE(sm.nativeKey().isEmpty(), setKey.isEmpty()); + sm.setNativeKey(setNativeKey); + QVERIFY(sm.key().isNull()); + QCOMPARE(sm.nativeKey(), setNativeKey); + 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("key"); + QTest::addColumn("size"); + QTest::addColumn("canCreate"); + QTest::addColumn("error"); + + QTest::newRow("null key") << QString() << 1024 + << false << QSharedMemory::KeyError; + 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("key"); + QTest::addColumn("exists"); + QTest::addColumn("error"); + + QTest::newRow("null key") << QString() << false << QSharedMemory::KeyError; + QTest::newRow("doesn't exists") << QString("doesntexists") << false << QSharedMemory::NotFound; + + // HPUX doesn't allow for multiple attaches per process. +#ifndef Q_OS_HPUX + QTest::newRow("already exists") << QString(EXISTING_SHARE) << true << QSharedMemory::NoError; +#endif +} + +/*! + Basic attach/detach testing + */ +void tst_QSharedMemory::attach() +{ + QFETCH(QString, key); + QFETCH(bool, exists); + QFETCH(QSharedMemory::SharedMemoryError, error); + + 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.data() != 0); + QVERIFY(sm.size() != 0); + 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()); + // we didn't unlock(), so ignore the warning from auto-detach in destructor + QTest::ignoreMessage(QtWarningMsg, "QSharedMemory::lock: already locked"); +} + +/*! + Other shared memory are allowed to be attached after we remove, + but new shared memory are not allowed to attach after a remove. + */ +// HPUX doesn't allow for multiple attaches per process. +#ifndef Q_OS_HPUX +void tst_QSharedMemory::removeWhileAttached() +{ + 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); +} +#endif + +/*! + 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. +*/ +// This test opens a crash dialog on Windows. +#ifndef Q_OS_WIN +void tst_QSharedMemory::readOnly() +{ + rememberKey("readonly_segfault"); + // ### on windows disable the popup somehow + QProcess p; + p.start(helperBinary(), QStringList("readonly_segfault")); + p.setProcessChannelMode(QProcess::ForwardedChannels); + p.waitForFinished(); + QCOMPARE(p.error(), QProcess::Crashed); +} +#endif + +/*! + 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. + */ +// HPUX doesn't allow for multiple attaches per process. +// For WinCE, this test nearly kills the system, so skip it. +#if !defined(Q_OS_HPUX) && !defined(Q_OS_WINCE) +void tst_QSharedMemory::attachTooMuch() +{ + QSKIP("disabled"); + + 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()); + } + } +} +#endif + +// HPUX doesn't allow for multiple attaches per process. +#ifndef Q_OS_HPUX +void tst_QSharedMemory::simpleProducerConsumer_data() +{ + QTest::addColumn("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() +{ + 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 +#if !defined(Q_OS_WINCE) + QVERIFY(put != get); +#endif + for (int i = 0; i < size; ++i) { + put[i] = 'Q'; + QCOMPARE(get[i], 'Q'); + } + QVERIFY(consumer.detach()); +} +#endif + +// HPUX doesn't allow for multiple attaches per process. +#ifndef Q_OS_HPUX +void tst_QSharedMemory::simpleDoubleProducerConsumer() +{ + 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()); + } +} +#endif + +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()); + + } +private: + +}; + +void tst_QSharedMemory::simpleThreadedProducerConsumer_data() +{ + QTest::addColumn("producerIsThread"); + QTest::addColumn("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"); +#endif + + Producer p; + if (producerIsThread) + p.start(); + + QList consumers; + for (int i = 0; i < threads; ++i) { + consumers.append(new Consumer()); + consumers.last()->start(); + } + + if (!producerIsThread) + p.run(); + + p.wait(5000); + while (!consumers.isEmpty()) { + Consumer *c = consumers.first(); + QVERIFY(c->isFinished() || c->wait(5000)); + delete consumers.takeFirst(); + } +} + +void tst_QSharedMemory::simpleProcessProducerConsumer_data() +{ + QTest::addColumn("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() +{ + QFETCH(int, processes); + + rememberKey("market"); + + QProcess producer; + producer.setProcessChannelMode(QProcess::ForwardedChannels); + producer.start(helperBinary(), QStringList("producer")); + QVERIFY2(producer.waitForStarted(), "Could not start helper binary"); + + QList consumers; + unsigned int failedProcesses = 0; + const QStringList consumerArguments = QStringList("consumer"); + for (int i = 0; i < processes; ++i) { + QProcess *p = new QProcess; + p->setProcessChannelMode(QProcess::ForwardedChannels); + p->start(helperBinary(), consumerArguments); + 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)); +} + +void tst_QSharedMemory::uniqueKey_data() +{ + QTest::addColumn("key1"); + QTest::addColumn("key2"); + + QTest::newRow("null == null") << QString() << QString(); + QTest::newRow("key == key") << QString("key") << QString("key"); + QTest::newRow("key1 == key1") << QString("key1") << QString("key1"); + QTest::newRow("key != key1") << QString("key") << QString("key1"); + QTest::newRow("ke1y != key1") << QString("ke1y") << QString("key1"); + QTest::newRow("key1 != key2") << QString("key1") << QString("key2"); +} + +void tst_QSharedMemory::uniqueKey() +{ + QFETCH(QString, key1); + QFETCH(QString, key2); + + QSharedMemory sm1(key1); + QSharedMemory sm2(key2); + + bool setEqual = (key1 == key2); + bool keyEqual = (sm1.key() == sm2.key()); + bool nativeEqual = (sm1.nativeKey() == sm2.nativeKey()); + + QCOMPARE(keyEqual, setEqual); + QCOMPARE(nativeEqual, setEqual); +} + +QTEST_MAIN(tst_QSharedMemory) +#include "tst_qsharedmemory.moc" + -- cgit v1.2.3