diff options
Diffstat (limited to 'tests/auto/corelib/ipc')
12 files changed, 2217 insertions, 0 deletions
diff --git a/tests/auto/corelib/ipc/CMakeLists.txt b/tests/auto/corelib/ipc/CMakeLists.txt new file mode 100644 index 0000000000..751d093788 --- /dev/null +++ b/tests/auto/corelib/ipc/CMakeLists.txt @@ -0,0 +1,14 @@ +# Copyright (C) 2022 Intel Corporation. +# SPDX-License-Identifier: BSD-3-Clause + +if(NOT ANDROID AND NOT UIKIT) + if(QT_FEATURE_sharedmemory OR QT_FEATURE_systemsemaphore) + add_subdirectory(qnativeipckey) + endif() + if(QT_FEATURE_sharedmemory) + add_subdirectory(qsharedmemory) + endif() + if(QT_FEATURE_systemsemaphore) + add_subdirectory(qsystemsemaphore) + endif() +endif() diff --git a/tests/auto/corelib/ipc/ipctestcommon.h b/tests/auto/corelib/ipc/ipctestcommon.h new file mode 100644 index 0000000000..25c7b8ce44 --- /dev/null +++ b/tests/auto/corelib/ipc/ipctestcommon.h @@ -0,0 +1,83 @@ +// Copyright (C) 2022 Intel Corporation. + +#include <QtTest/QTest> +#include <QtCore/QNativeIpcKey> + +namespace IpcTestCommon { +static QList<QNativeIpcKey::Type> supportedKeyTypes; + +template <typename IpcClass> void addGlobalTestRows() +{ + qDebug() << "Default key type is" << QNativeIpcKey::DefaultTypeForOs + << "and legacy key type is" << QNativeIpcKey::legacyDefaultTypeForOs(); + +#if defined(Q_OS_FREEBSD) || defined(Q_OS_DARWIN) || defined(Q_OS_WIN) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) + // only enforce that IPC works on the platforms above; other platforms may + // have no working backends (notably, Android) + QVERIFY(IpcClass::isKeyTypeSupported(QNativeIpcKey::DefaultTypeForOs)); + QVERIFY(IpcClass::isKeyTypeSupported(QNativeIpcKey::legacyDefaultTypeForOs())); +#endif + + auto addRowIfSupported = [](const char *name, QNativeIpcKey::Type type) { + if (IpcClass::isKeyTypeSupported(type)) { + supportedKeyTypes << type; + QTest::newRow(name) << type; + } + }; + + QTest::addColumn<QNativeIpcKey::Type>("keyType"); + + addRowIfSupported("Windows", QNativeIpcKey::Type::Windows); + addRowIfSupported("POSIX", QNativeIpcKey::Type::PosixRealtime); + addRowIfSupported("SystemV-Q", QNativeIpcKey::Type::SystemV); + addRowIfSupported("SystemV-T", QNativeIpcKey::Type('T')); + + if (supportedKeyTypes.isEmpty()) + QSKIP("System reports no supported IPC types."); +} + +// rotate through the supported types and find another +inline QNativeIpcKey::Type nextKeyType(QNativeIpcKey::Type type) +{ + qsizetype idx = supportedKeyTypes.indexOf(type); + Q_ASSERT(idx >= 0); + + ++idx; + if (idx == supportedKeyTypes.size()) + idx = 0; + return supportedKeyTypes.at(idx); +} +} // namespace IpcTestCommon + +QT_BEGIN_NAMESPACE +namespace QTest { +template<> inline char *toString(const QNativeIpcKey::Type &type) +{ + switch (type) { + case QNativeIpcKey::Type::SystemV: return qstrdup("SystemV"); + case QNativeIpcKey::Type::PosixRealtime: return qstrdup("PosixRealTime"); + case QNativeIpcKey::Type::Windows: return qstrdup("Windows"); + } + if (type == QNativeIpcKey::Type{}) + return qstrdup("Invalid"); + + char buf[32]; + qsnprintf(buf, sizeof(buf), "%u", unsigned(type)); + return qstrdup(buf); +} + +template<> inline char *toString(const QNativeIpcKey &key) +{ + if (!key.isValid()) + return qstrdup("<invalid>"); + + const char *type = toString(key.type()); + const char *text = toString(key.nativeKey()); + char buf[256]; + qsnprintf(buf, sizeof(buf), "QNativeIpcKey(%s, %s)", text, type); + delete[] type; + delete[] text; + return qstrdup(buf); +} +} // namespace QTest +QT_END_NAMESPACE diff --git a/tests/auto/corelib/ipc/qnativeipckey/CMakeLists.txt b/tests/auto/corelib/ipc/qnativeipckey/CMakeLists.txt new file mode 100644 index 0000000000..0cc6a7b18b --- /dev/null +++ b/tests/auto/corelib/ipc/qnativeipckey/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 Intel Corporation. +# SPDX-License-Identifier: BSD-3-Clause + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qnativeipckey LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qnativeipckey + SOURCES + tst_qnativeipckey.cpp + LIBRARIES + Qt::TestPrivate +) diff --git a/tests/auto/corelib/ipc/qnativeipckey/tst_qnativeipckey.cpp b/tests/auto/corelib/ipc/qnativeipckey/tst_qnativeipckey.cpp new file mode 100644 index 0000000000..a01a108591 --- /dev/null +++ b/tests/auto/corelib/ipc/qnativeipckey/tst_qnativeipckey.cpp @@ -0,0 +1,442 @@ +// Copyright (C) 2022 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtCore/QNativeIpcKey> +#include <QtTest/QTest> +#include <QtTest/private/qcomparisontesthelper_p.h> + +#include "../ipctestcommon.h" + +#if QT_CONFIG(sharedmemory) +# include <qsharedmemory.h> +#endif +#if QT_CONFIG(systemsemaphore) +# include <qsystemsemaphore.h> +#endif + +#if QT_CONFIG(sharedmemory) +static const auto makeLegacyKey = QSharedMemory::legacyNativeKey; +#else +static const auto makeLegacyKey = QSystemSemaphore::legacyNativeKey; +#endif + +using namespace Qt::StringLiterals; + +class tst_QNativeIpcKey : public QObject +{ + Q_OBJECT +private slots: + void compareCompiles(); + void defaultTypes(); + void construct(); + void getSetCheck(); + void equality(); + void hash(); + void swap(); + void toString_data(); + void toString(); + void fromString_data(); + void fromString(); + void legacyKeys_data(); + void legacyKeys(); +}; + +void tst_QNativeIpcKey::compareCompiles() +{ + QTestPrivate::testEqualityOperatorsCompile<QNativeIpcKey>(); +} + +void tst_QNativeIpcKey::defaultTypes() +{ + auto isKnown = [](QNativeIpcKey::Type t) { + switch (t) { + case QNativeIpcKey::Type::SystemV: + case QNativeIpcKey::Type::PosixRealtime: + case QNativeIpcKey::Type::Windows: + return true; + } + return false; + }; + + // because the letter Q looked nice in Håvard's Emacs font back in the 1990s + static_assert(qToUnderlying(QNativeIpcKey::Type::SystemV) == 'Q', + "QNativeIpcKey::Type::SystemV must be equal to the letter Q"); + + auto type = QNativeIpcKey::DefaultTypeForOs; + auto legacy = QNativeIpcKey::legacyDefaultTypeForOs(); + QVERIFY(isKnown(type)); + QVERIFY(isKnown(legacy)); + +#ifdef Q_OS_WIN + QCOMPARE(type, QNativeIpcKey::Type::Windows); +#else + QCOMPARE(type, QNativeIpcKey::Type::PosixRealtime); +#endif + +#if defined(Q_OS_WIN) + QCOMPARE(legacy, QNativeIpcKey::Type::Windows); +#elif defined(QT_POSIX_IPC) + QCOMPARE(legacy, QNativeIpcKey::Type::PosixRealtime); +#elif !defined(Q_OS_DARWIN) + QCOMPARE(legacy, QNativeIpcKey::Type::SystemV); +#endif +} + +void tst_QNativeIpcKey::construct() +{ + { + QNativeIpcKey invalid(QNativeIpcKey::Type{}); + QVERIFY(!invalid.isValid()); + } + + { + QNativeIpcKey key; + QVERIFY(key.nativeKey().isEmpty()); + QVERIFY(key.isEmpty()); + QVERIFY(key.isValid()); + QCOMPARE(key.type(), QNativeIpcKey::DefaultTypeForOs); + + QNativeIpcKey copy(key); + QVERIFY(copy.nativeKey().isEmpty()); + QVERIFY(copy.isEmpty()); + QVERIFY(copy.isValid()); + QCOMPARE(copy.type(), QNativeIpcKey::DefaultTypeForOs); + + QNativeIpcKey moved(std::move(copy)); + QVERIFY(moved.nativeKey().isEmpty()); + QVERIFY(moved.isEmpty()); + QVERIFY(moved.isValid()); + QCOMPARE(moved.type(), QNativeIpcKey::DefaultTypeForOs); + + key.setType({}); + key.setNativeKey("something else"); + key = std::move(moved); + QVERIFY(key.nativeKey().isEmpty()); + QVERIFY(key.isEmpty()); + QVERIFY(key.isValid()); + QCOMPARE(key.type(), QNativeIpcKey::DefaultTypeForOs); + + copy.setType({}); + copy.setNativeKey("something else"); + copy = key; + QVERIFY(copy.nativeKey().isEmpty()); + QVERIFY(copy.isEmpty()); + QVERIFY(copy.isValid()); + QCOMPARE(copy.type(), QNativeIpcKey::DefaultTypeForOs); + } + + { + QNativeIpcKey key("dummy"); + QCOMPARE(key.nativeKey(), "dummy"); + QVERIFY(!key.isEmpty()); + QVERIFY(key.isValid()); + QCOMPARE(key.type(), QNativeIpcKey::DefaultTypeForOs); + + QNativeIpcKey copy(key); + QCOMPARE(key.nativeKey(), "dummy"); + QCOMPARE(copy.nativeKey(), "dummy"); + QVERIFY(!copy.isEmpty()); + QVERIFY(copy.isValid()); + QCOMPARE(copy.type(), QNativeIpcKey::DefaultTypeForOs); + + QNativeIpcKey moved(std::move(copy)); + QCOMPARE(key.nativeKey(), "dummy"); + QCOMPARE(moved.nativeKey(), "dummy"); + QVERIFY(!moved.isEmpty()); + QVERIFY(moved.isValid()); + QCOMPARE(moved.type(), QNativeIpcKey::DefaultTypeForOs); + + key.setType({}); + key.setNativeKey("something else"); + key = std::move(moved); + QCOMPARE(key.nativeKey(), "dummy"); + QVERIFY(!key.isEmpty()); + QVERIFY(key.isValid()); + QCOMPARE(key.type(), QNativeIpcKey::DefaultTypeForOs); + + copy.setType({}); + copy.setNativeKey("something else"); + copy = key; + QCOMPARE(key.nativeKey(), "dummy"); + QCOMPARE(copy.nativeKey(), "dummy"); + QVERIFY(!copy.isEmpty()); + QVERIFY(copy.isValid()); + QCOMPARE(copy.type(), QNativeIpcKey::DefaultTypeForOs); + } +} + +void tst_QNativeIpcKey::getSetCheck() +{ + QNativeIpcKey key("key1", QNativeIpcKey::Type::Windows); + QVERIFY(key.isValid()); + QVERIFY(!key.isEmpty()); + QCOMPARE(key.nativeKey(), "key1"); + QCOMPARE(key.type(), QNativeIpcKey::Type::Windows); + + key.setType(QNativeIpcKey::Type::SystemV); + QVERIFY(key.isValid()); + QVERIFY(!key.isEmpty()); + QCOMPARE(key.type(), QNativeIpcKey::Type::SystemV); + + key.setNativeKey("key2"); + QCOMPARE(key.nativeKey(), "key2"); +} + +void tst_QNativeIpcKey::equality() +{ + QNativeIpcKey key1, key2; + QCOMPARE(key1, key2); + QVERIFY(!(key1 != key2)); + QT_TEST_EQUALITY_OPS(key1, key2, true); + + key1.setNativeKey("key1"); + QCOMPARE_NE(key1, key2); + QVERIFY(!(key1 == key2)); + QT_TEST_EQUALITY_OPS(key1, key2, false); + + key2.setType({}); + QCOMPARE_NE(key1, key2); + QVERIFY(!(key1 == key2)); + QT_TEST_EQUALITY_OPS(key1, key2, false); + + key2.setNativeKey(key1.nativeKey()); + QCOMPARE_NE(key1, key2); + QVERIFY(!(key1 == key2)); + QT_TEST_EQUALITY_OPS(key1, key2, false); + + key2.setType(QNativeIpcKey::DefaultTypeForOs); + QCOMPARE(key1, key2); + QVERIFY(!(key1 != key2)); + QT_TEST_EQUALITY_OPS(key1, key2, true); + + key1 = makeLegacyKey("key1", QNativeIpcKey::DefaultTypeForOs); + QCOMPARE_NE(key1, key2); + QVERIFY(!(key1 == key2)); + QT_TEST_EQUALITY_OPS(key1, key2, false); + + key2 = key1; + QCOMPARE(key1, key2); + QVERIFY(!(key1 != key2)); + QT_TEST_EQUALITY_OPS(key1, key2, true); + + // just setting the native key won't make them equal again! + key2.setNativeKey(key1.nativeKey()); + QCOMPARE_NE(key1, key2); + QVERIFY(!(key1 == key2)); + QT_TEST_EQUALITY_OPS(key1, key2, false); +} + +void tst_QNativeIpcKey::hash() +{ + QNativeIpcKey key1("key1", QNativeIpcKey::DefaultTypeForOs); + QNativeIpcKey key2(key1); + QCOMPARE_EQ(qHash(key1), qHash(key2)); + QCOMPARE_EQ(qHash(key1, 123), qHash(key2, 123)); +} + +void tst_QNativeIpcKey::swap() +{ + QNativeIpcKey key1("key1", QNativeIpcKey::Type::PosixRealtime); + QNativeIpcKey key2("key2", QNativeIpcKey::Type::Windows); + + // self-swaps + key1.swap(key1); + key2.swap(key2); + QCOMPARE(key1.nativeKey(), "key1"); + QCOMPARE(key1.type(), QNativeIpcKey::Type::PosixRealtime); + QCOMPARE(key2.nativeKey(), "key2"); + QCOMPARE(key2.type(), QNativeIpcKey::Type::Windows); + + key1.swap(key2); + QCOMPARE(key2.nativeKey(), "key1"); + QCOMPARE(key2.type(), QNativeIpcKey::Type::PosixRealtime); + QCOMPARE(key1.nativeKey(), "key2"); + QCOMPARE(key1.type(), QNativeIpcKey::Type::Windows); + + key1.swap(key2); + QCOMPARE(key1.nativeKey(), "key1"); + QCOMPARE(key1.type(), QNativeIpcKey::Type::PosixRealtime); + QCOMPARE(key2.nativeKey(), "key2"); + QCOMPARE(key2.type(), QNativeIpcKey::Type::Windows); + + key1 = makeLegacyKey("key1", QNativeIpcKey::DefaultTypeForOs); + QCOMPARE(key1.type(), QNativeIpcKey::DefaultTypeForOs); + key1.swap(key2); + QCOMPARE(key1.type(), QNativeIpcKey::Type::Windows); + QCOMPARE(key2.type(), QNativeIpcKey::DefaultTypeForOs); +} + +void tst_QNativeIpcKey::toString_data() +{ + QTest::addColumn<QString>("string"); + QTest::addColumn<QNativeIpcKey>("key"); + + QTest::newRow("invalid") << QString() << QNativeIpcKey(QNativeIpcKey::Type(0)); + + auto addRow = [](const char *prefix, QNativeIpcKey::Type type) { + auto add = [=](const char *name, QLatin1StringView key, QLatin1StringView encoded = {}) { + if (encoded.isNull()) + encoded = key; + QTest::addRow("%s-%s", prefix, name) + << prefix + u":"_s + encoded << QNativeIpcKey(key, type); + }; + add("empty", {}); + add("text", "foobar"_L1); + add("pathlike", "/sometext"_L1); + add("objectlike", "Global\\sometext"_L1); + add("colon-slash", ":/"_L1); + add("slash-colon", "/:"_L1); + add("percent", "%"_L1, "%25"_L1); + add("question-hash", "?#"_L1, "%3F%23"_L1); + add("hash-question", "#?"_L1, "%23%3F"_L1); + add("double-slash", "//"_L1, "/%2F"_L1); + add("triple-slash", "///"_L1, "/%2F/"_L1); + add("non-ascii", "\xe9"_L1); + add("non-utf8", "\xa0\xff"_L1); + QTest::addRow("%s-%s", prefix, "non-latin1") + << prefix + u":\u0100.\u2000.\U00010000"_s + << QNativeIpcKey(u"\u0100.\u2000.\U00010000"_s, type); + }; + addRow("systemv", QNativeIpcKey::Type::SystemV); + addRow("posix", QNativeIpcKey::Type::PosixRealtime); + addRow("windows", QNativeIpcKey::Type::Windows); + + addRow("systemv-1", QNativeIpcKey::Type(1)); + addRow("systemv-84", QNativeIpcKey::Type('T')); + addRow("systemv-255", QNativeIpcKey::Type(0xff)); +} + +void tst_QNativeIpcKey::toString() +{ + QFETCH(QString, string); + QFETCH(QNativeIpcKey, key); + + QCOMPARE(key.toString(), string); +} + +void tst_QNativeIpcKey::fromString_data() +{ + toString_data(); + QTest::addRow("systemv-alias") << "systemv-81:" << QNativeIpcKey(QNativeIpcKey::Type::SystemV); + QTest::addRow("systemv-zeropadded") << "systemv-009:" << QNativeIpcKey(QNativeIpcKey::Type(9)); + + QNativeIpcKey valid("/foo", QNativeIpcKey::Type::PosixRealtime); + QNativeIpcKey invalid(QNativeIpcKey::Type(0)); + + // percent-decoding + QTest::addRow("percent-encoded") << "posix:%2f%66o%6f" << valid; + QTest::addRow("percent-utf8") + << "posix:%C4%80.%E2%80%80.%F0%90%80%80" + << QNativeIpcKey(u"\u0100.\u2000.\U00010000"_s, QNativeIpcKey::Type::PosixRealtime); + + // fragments are ignored + QTest::addRow("with-fragment") << "posix:/foo#bar" << valid; + QTest::addRow("with-fragmentquery") << "posix:/foo#bar?baz" << valid; + + // but unknown query items are not + QTest::addRow("with-query") << "posix:/foo?bar" << invalid; + QTest::addRow("with-queryfragment") << "posix:/foo?bar#baz" << invalid; + + // add some ones that won't parse well + QTest::addRow("positive-number") << "81" << invalid; + QTest::addRow("negative-number") << "-81" << invalid; + QTest::addRow("invalidprefix") << "invalidprefix:" << invalid; + QTest::addRow("systemv-nodash") << "systemv255" << invalid; + QTest::addRow("systemv-doubledash") << "systemv--255:" << invalid; + QTest::addRow("systemv-plus") << "systemv+255" << invalid; + QTest::addRow("systemv-hex") << "systemv-0x01:" << invalid; + QTest::addRow("systemv-too-low") << "systemv-0:" << invalid; + QTest::addRow("systemv-too-high") << "systemv-256" << invalid; + QTest::addRow("systemv-overflow-15bit") << "systemv-32769:" << invalid; + QTest::addRow("systemv-overflow-16bit") << "systemv-65537:" << invalid; + QTest::addRow("systemv-overflow-31bit") << "systemv-2147483649:" << invalid; + QTest::addRow("systemv-overflow-32bit") << "systemv-4294967297:" << invalid; + + auto addRows = [=](const char *name) { + QTest::addRow("%s-nocolon", name) << name << invalid; + QTest::addRow("%s-junk", name) << name + u"junk"_s << invalid; + QTest::addRow("junk-%s-colon", name) << u"junk:"_s + name + u':' << invalid; + }; + addRows("systemv"); + addRows("posix"); + addRows("windows"); + addRows("systemv-1"); + addRows("systemv-255"); +} + +void tst_QNativeIpcKey::fromString() +{ + QFETCH(QString, string); + QFETCH(QNativeIpcKey, key); + + QCOMPARE(QNativeIpcKey::fromString(string), key); +} + +void tst_QNativeIpcKey::legacyKeys_data() +{ + QTest::addColumn<QNativeIpcKey::Type>("type"); + QTest::addColumn<QString>("legacyKey"); + auto addRows = [](QNativeIpcKey::Type type) { + const char *label = "<unknown-type>"; + switch (type) { + case QNativeIpcKey::Type::SystemV: + label = "systemv"; + break; + case QNativeIpcKey::Type::PosixRealtime: + label = "posix"; + break; + case QNativeIpcKey::Type::Windows: + label = "windows"; + break; + } + auto add = [=](const char *name, const QString &legacyKey) { + QTest::addRow("%s-%s", label, name) << type << legacyKey; + }; + add("empty", {}); + add("text", "foobar"_L1); + add("pathlike", "/sometext"_L1); + add("objectlike", "Global\\sometext"_L1); + add("colon-slash", ":/"_L1); + add("slash-colon", "/:"_L1); + add("percent", "%"_L1); + add("question-hash", "?#"_L1); + add("hash-question", "#?"_L1); + add("double-slash", "//"_L1); + add("triple-slash", "///"_L1); + add("non-ascii", "\xe9"_L1); + add("non-utf8", "\xa0\xff"_L1); + add("non-latin1", u":\u0100.\u2000.\U00010000"_s); + }; + + addRows(QNativeIpcKey::DefaultTypeForOs); + if (auto type = QNativeIpcKey::legacyDefaultTypeForOs(); + type != QNativeIpcKey::DefaultTypeForOs) + addRows(type); +} + +void tst_QNativeIpcKey::legacyKeys() +{ + QFETCH(QNativeIpcKey::Type, type); + QFETCH(QString, legacyKey); + + QNativeIpcKey key = makeLegacyKey(legacyKey, type); + QCOMPARE(key.type(), type); + + QString string = key.toString(); + QNativeIpcKey key2 = QNativeIpcKey::fromString(string); + QCOMPARE(key2, key); + QT_TEST_EQUALITY_OPS(key, key2, true); + + if (!legacyKey.isEmpty()) { + // confirm it shows up in the encoded form + Q_ASSERT(!legacyKey.contains(u'&')); // needs extra encoding + QUrl u; + u.setQuery("legacyKey="_L1 + legacyKey, QUrl::DecodedMode); + QString encodedLegacyKey = u.toString(QUrl::RemoveScheme | QUrl::RemoveAuthority + | QUrl::DecodeReserved); + QVERIFY2(string.contains(encodedLegacyKey), qPrintable(string)); + } +} + +QTEST_MAIN(tst_QNativeIpcKey) +#include "tst_qnativeipckey.moc" diff --git a/tests/auto/corelib/ipc/qsharedmemory/CMakeLists.txt b/tests/auto/corelib/ipc/qsharedmemory/CMakeLists.txt new file mode 100644 index 0000000000..e49c8d7828 --- /dev/null +++ b/tests/auto/corelib/ipc/qsharedmemory/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qsharedmemory LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qsharedmemory + SOURCES + tst_qsharedmemory.cpp + LIBRARIES + Qt::CorePrivate +) + +## Scopes: +##################################################################### + +qt_internal_extend_target(tst_qsharedmemory CONDITION LINUX + LIBRARIES + rt +) +add_subdirectory(producerconsumer) +if(QT_FEATURE_process) + add_dependencies(tst_qsharedmemory producerconsumer_helper) +endif() diff --git a/tests/auto/corelib/ipc/qsharedmemory/producerconsumer/CMakeLists.txt b/tests/auto/corelib/ipc/qsharedmemory/producerconsumer/CMakeLists.txt new file mode 100644 index 0000000000..bd039837e3 --- /dev/null +++ b/tests/auto/corelib/ipc/qsharedmemory/producerconsumer/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## producerconsumer_helper Binary: +##################################################################### + +qt_internal_add_test_helper(producerconsumer_helper + SOURCES + main.cpp + LIBRARIES + Qt::Test +) diff --git a/tests/auto/corelib/ipc/qsharedmemory/producerconsumer/main.cpp b/tests/auto/corelib/ipc/qsharedmemory/producerconsumer/main.cpp new file mode 100644 index 0000000000..102d505485 --- /dev/null +++ b/tests/auto/corelib/ipc/qsharedmemory/producerconsumer/main.cpp @@ -0,0 +1,174 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QSharedMemory> +#include <QStringList> +#include <QDebug> +#include <QTest> +#include <stdio.h> + +void set(QSharedMemory &sm, int pos, char value) +{ + ((char*)sm.data())[pos] = value; +} + +QChar get(QSharedMemory &sm, int i) +{ + return QChar::fromLatin1(((char*)sm.data())[i]); +} + +int readonly_segfault(const QNativeIpcKey &key) +{ + QSharedMemory sharedMemory(key); + sharedMemory.create(1024, QSharedMemory::ReadOnly); + sharedMemory.lock(); + set(sharedMemory, 0, 'a'); + sharedMemory.unlock(); + return EXIT_SUCCESS; +} + +int producer(const QNativeIpcKey &key) +{ + QSharedMemory producer(key); + + 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; + } + } + // tell parent we're ready + //qDebug("producer created and attached"); + puts(""); + fflush(stdout); + + 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 exit + getchar(); + return EXIT_SUCCESS; +} + +int consumer(const QNativeIpcKey &key) +{ + QSharedMemory consumer(key); + + //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().size() != 3) { + fprintf(stderr, "Usage: %s <mode> <key>\n" + "<mode> is one of: readonly_segfault, producer, consumer\n", + argv[0]); + return EXIT_FAILURE; + } + QString function = arguments.at(1); + QNativeIpcKey key = QNativeIpcKey::fromString(arguments.at(2)); + + if (function == QLatin1String("readonly_segfault")) + return readonly_segfault(key); + else if (function == QLatin1String("producer")) + return producer(key); + else if (function == QLatin1String("consumer")) + return consumer(key); + else + qWarning() << "Unknown function" << arguments.at(1); + + return EXIT_SUCCESS; +} diff --git a/tests/auto/corelib/ipc/qsharedmemory/tst_qsharedmemory.cpp b/tests/auto/corelib/ipc/qsharedmemory/tst_qsharedmemory.cpp new file mode 100644 index 0000000000..5bd74da532 --- /dev/null +++ b/tests/auto/corelib/ipc/qsharedmemory/tst_qsharedmemory.cpp @@ -0,0 +1,966 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2022 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QDebug> +#include <QFile> +#if QT_CONFIG(process) +# include <QProcess> +#endif +#include <QSharedMemory> +#include <QTest> +#include <QThread> +#include <QElapsedTimer> + +#include <errno.h> +#include <stdio.h> +#ifdef Q_OS_UNIX +# include <unistd.h> +#endif + +#include "private/qtcore-config_p.h" +#include "../ipctestcommon.h" + +#define EXISTING_SIZE 1024 + +Q_DECLARE_METATYPE(QSharedMemory::SharedMemoryError) +Q_DECLARE_METATYPE(QSharedMemory::AccessMode) + +using namespace Qt::StringLiterals; + +class tst_QSharedMemory : public QObject +{ + Q_OBJECT + +public Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + +private slots: + // basics + void constructor(); + void nativeKey_data(); + void nativeKey(); + void legacyKey_data() { nativeKey_data(); } + void legacyKey(); + void create_data(); + void create(); + void attach_data(); + void attach(); + void changeKeyType_data() { attach_data(); } + void changeKeyType(); + void lock(); + + // custom edge cases + void removeWhileAttached(); + void emptyMemory(); + void readOnly(); + void attachBeforeCreate_data(); + void attachBeforeCreate(); + + // 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(); + + // unique keys + void uniqueKey_data(); + void uniqueKey(); + + // legacy + void createWithSameKey(); + +protected: + void remove(const QNativeIpcKey &key); + + QString mangleKey(QStringView key) + { + if (key.isEmpty()) + return key.toString(); + return u"tstshm_%1-%2_%3"_s.arg(QCoreApplication::applicationPid()) + .arg(seq).arg(key); + } + + QNativeIpcKey platformSafeKey(const QString &key) + { + QFETCH_GLOBAL(QNativeIpcKey::Type, keyType); + return QSharedMemory::platformSafeKey(mangleKey(key), keyType); + } + + QNativeIpcKey rememberKey(const QString &key) + { + QNativeIpcKey ipcKey = platformSafeKey(key); + if (!keys.contains(ipcKey)) { + keys.append(ipcKey); + remove(ipcKey); + } + return ipcKey; + } + + QList<QNativeIpcKey> keys; + QList<QSharedMemory*> jail; + QSharedMemory *existingSharedMemory = nullptr; + int seq = 0; + +private: + const QString m_helperBinary = "./producerconsumer_helper"; +}; + +void tst_QSharedMemory::initTestCase() +{ + IpcTestCommon::addGlobalTestRows<QSharedMemory>(); +} + +void tst_QSharedMemory::init() +{ + QNativeIpcKey key = platformSafeKey("existing"); + existingSharedMemory = new QSharedMemory(key); + if (!existingSharedMemory->create(EXISTING_SIZE)) { + QCOMPARE(existingSharedMemory->error(), QSharedMemory::AlreadyExists); + } + keys.append(key); +} + +void tst_QSharedMemory::cleanup() +{ + delete existingSharedMemory; + qDeleteAll(jail.begin(), jail.end()); + jail.clear(); + + for (int i = 0; i < keys.size(); ++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)); + } + ++seq; +} + +#if QT_CONFIG(posix_shm) +#include <sys/types.h> +#include <sys/mman.h> +#endif +#if QT_CONFIG(sysv_shm) +#include <sys/types.h> +#include <sys/ipc.h> +#include <sys/shm.h> +#endif + +void tst_QSharedMemory::remove(const QNativeIpcKey &key) +{ + // On Unix, the shared memory might exist from a previously failed test + // or segfault, remove it it does + if (key.isEmpty()) + return; + + switch (key.type()) { + case QNativeIpcKey::Type::Windows: + return; + + case QNativeIpcKey::Type::PosixRealtime: +#if QT_CONFIG(posix_shm) + if (shm_unlink(QFile::encodeName(key.nativeKey()).constData()) == -1) { + if (errno != ENOENT) { + perror("shm_unlink"); + return; + } + } +#endif + return; + + case QNativeIpcKey::Type::SystemV: + break; + } + +#if QT_CONFIG(sysv_shm) + // ftok requires that an actual file exists somewhere + QString fileName = key.nativeKey(); + if (!QFile::exists(fileName)) { + //qDebug() << "exits failed"; + return; + } + + int unix_key = ftok(fileName.toLatin1().constData(), int(key.type())); + if (-1 == unix_key) { + perror("ftok"); + return; + } + + int id = shmget(unix_key, 0, 0600); + if (-1 == id) { + if (errno != ENOENT) + perror("shmget"); + return; + } + + struct shmid_ds shmid_ds; + if (-1 == shmctl(id, IPC_RMID, &shmid_ds)) { + perror("shmctl"); + return; + } + + QFile::remove(fileName); +#endif // Q_OS_WIN +} + +/*! + Tests the default values + */ +void tst_QSharedMemory::constructor() +{ + QSharedMemory sm; + QVERIFY(!sm.isAttached()); + QVERIFY(!sm.data()); + QCOMPARE(sm.nativeKey(), QString()); + QCOMPARE(sm.nativeIpcKey(), QNativeIpcKey()); + QCOMPARE(sm.size(), 0); + QCOMPARE(sm.error(), QSharedMemory::NoError); + QCOMPARE(sm.errorString(), QString()); + + QT_WARNING_PUSH + QT_WARNING_DISABLE_DEPRECATED + QCOMPARE(sm.key(), QString()); + QT_WARNING_POP +} + +void tst_QSharedMemory::nativeKey_data() +{ + QTest::addColumn<QString>("constructorKey"); + QTest::addColumn<QString>("setKey"); + QTest::addColumn<QString>("setNativeKey"); // only used in the legacyKey test + + 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::nativeKey() +{ + QFETCH(QString, constructorKey); + QFETCH(QString, setKey); + QFETCH(QString, setNativeKey); + + QNativeIpcKey constructorIpcKey = platformSafeKey(constructorKey); + QNativeIpcKey setIpcKey = platformSafeKey(setKey); + + QSharedMemory sm(constructorIpcKey); + QCOMPARE(sm.nativeIpcKey(), constructorIpcKey); + QCOMPARE(sm.nativeKey(), constructorIpcKey.nativeKey()); + sm.setNativeKey(setIpcKey); + QCOMPARE(sm.nativeIpcKey(), setIpcKey); + QCOMPARE(sm.nativeKey(), setIpcKey.nativeKey()); + + QCOMPARE(sm.isAttached(), false); + + QCOMPARE(sm.error(), QSharedMemory::NoError); + QCOMPARE(sm.errorString(), QString()); + QVERIFY(!sm.data()); + QCOMPARE(sm.size(), 0); + + QCOMPARE(sm.detach(), false); + + // change the key type + QNativeIpcKey::Type nextKeyType = IpcTestCommon::nextKeyType(setIpcKey.type()); + if (nextKeyType != setIpcKey.type()) { + QNativeIpcKey setIpcKey2 = QSharedMemory::platformSafeKey(setKey, nextKeyType); + sm.setNativeKey(setIpcKey2); + + QCOMPARE(sm.nativeIpcKey(), setIpcKey2); + QCOMPARE(sm.nativeKey(), setIpcKey2.nativeKey()); + + QCOMPARE(sm.isAttached(), false); + + QCOMPARE(sm.error(), QSharedMemory::NoError); + QCOMPARE(sm.errorString(), QString()); + QVERIFY(!sm.data()); + QCOMPARE(sm.size(), 0); + + QCOMPARE(sm.detach(), false); + } +} + +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED +void tst_QSharedMemory::legacyKey() +{ + QFETCH(QString, constructorKey); + QFETCH(QString, setKey); + QFETCH(QString, setNativeKey); + +#ifdef Q_OS_QNX + QSKIP("The legacy native key type is incorrectly set on QNX"); +#endif + 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); + QCOMPARE(sm.errorString(), QString()); + QVERIFY(!sm.data()); + QCOMPARE(sm.size(), 0); + + QCOMPARE(sm.detach(), false); +} +QT_WARNING_POP + +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::KeyError; + QTest::newRow("-1 size") << QString("negsize") << -1 + << false << QSharedMemory::InvalidSize; + QTest::newRow("nor size") << QString("norsize") << 1024 + << true << QSharedMemory::NoError; + QTest::newRow("existing") << QString("existing") << 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); + + QNativeIpcKey nativeKey = rememberKey(key); + QSharedMemory sm(nativeKey); + QCOMPARE(sm.create(size), canCreate); + if (sm.error() != error) + qDebug() << sm.errorString(); + QCOMPARE(sm.nativeIpcKey(), nativeKey); + if (canCreate) { + QCOMPARE(sm.errorString(), QString()); + QVERIFY(sm.data() != 0); + QVERIFY(sm.size() != 0); + } else { + QVERIFY(!sm.data()); + 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") << QString() << false << QSharedMemory::KeyError; + QTest::newRow("doesntexists") << QString("doesntexist") << false << QSharedMemory::NotFound; + + QTest::newRow("existing") << QString("existing") << true << QSharedMemory::NoError; +} + +/*! + Basic attach/detach testing + */ +void tst_QSharedMemory::attach() +{ + QFETCH(QString, key); + QFETCH(bool, exists); + QFETCH(QSharedMemory::SharedMemoryError, error); + + QNativeIpcKey nativeKey = platformSafeKey(key); + QSharedMemory sm(nativeKey); + QCOMPARE(sm.attach(), exists); + QCOMPARE(sm.isAttached(), exists); + QCOMPARE(sm.error(), error); + QCOMPARE(sm.nativeIpcKey(), nativeKey); + 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()); + } else { + QVERIFY(!sm.data()); + QCOMPARE(sm.size(), 0); + QVERIFY(sm.errorString() != QString()); + QVERIFY(!sm.detach()); + } +} + +void tst_QSharedMemory::changeKeyType() +{ + QFETCH(QString, key); + QFETCH(bool, exists); + QFETCH(QSharedMemory::SharedMemoryError, error); + + QNativeIpcKey nativeKey = platformSafeKey(key); + QNativeIpcKey::Type nextKeyType = IpcTestCommon::nextKeyType(nativeKey.type()); + if (nextKeyType == nativeKey.type()) + QSKIP("System only supports one key type"); +// qDebug() << "Changing from" << nativeKey.type() << "to" << nextKeyType; + + QSharedMemory sm(nativeKey); + QCOMPARE(sm.attach(), exists); + QCOMPARE(sm.error(), error); + + QNativeIpcKey nextKey = + QSharedMemory::platformSafeKey(mangleKey(key), nextKeyType); + sm.setNativeKey(nextKey); + QCOMPARE(sm.isAttached(), false); + QVERIFY(!sm.attach()); + + if (exists) + QCOMPARE(sm.error(), QSharedMemory::NotFound); + else + QCOMPARE(sm.error(), error); +} + +void tst_QSharedMemory::lock() +{ + QSharedMemory shm; + QVERIFY(!shm.lock()); + QCOMPARE(shm.error(), QSharedMemory::LockError); + + shm.setNativeKey(rememberKey(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. +void tst_QSharedMemory::removeWhileAttached() +{ + rememberKey("one"); + + // attach 1 + QNativeIpcKey keyOne = platformSafeKey("one"); + QSharedMemory *smOne = new QSharedMemory(keyOne); + QVERIFY(smOne->create(1024)); + QVERIFY(smOne->isAttached()); + + // attach 2 + QSharedMemory *smTwo = new QSharedMemory(platformSafeKey("one")); + QVERIFY(smTwo->attach()); + QVERIFY(smTwo->isAttached()); + + // detach 1 and remove, remove one first to catch another error. + delete smOne; + delete smTwo; + + if (keyOne.type() == QNativeIpcKey::Type::PosixRealtime) { + // POSIX IPC doesn't guarantee that the shared memory is removed + remove(keyOne); + } + + // three shouldn't be able to attach + QSharedMemory smThree(keyOne); + 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() +{ +#if !QT_CONFIG(process) + QSKIP("No qprocess support", SkipAll); +#elif defined(Q_OS_MACOS) + QSKIP("QTBUG-59936: Times out on macOS", SkipAll); +#elif defined(Q_OS_WIN) + QSKIP("This test opens a crash dialog on Windows."); +#elif defined(__SANITIZE_ADDRESS__) || __has_feature(address_sanitizer) + QSKIP("ASan prevents the crash this test is looking for.", SkipAll); +#else + QNativeIpcKey key = rememberKey("readonly_segfault"); + + // ### on windows disable the popup somehow + QProcess p; + p.setProcessChannelMode(QProcess::ForwardedChannels); + p.start(m_helperBinary, { "readonly_segfault", key.toString() }); + p.waitForFinished(); + QCOMPARE(p.error(), QProcess::Crashed); +#endif +} + +void tst_QSharedMemory::attachBeforeCreate_data() +{ + QTest::addColumn<bool>("legacy"); + + QTest::addRow("legacy") << true; + QTest::addRow("non-legacy") << false; +} + +void tst_QSharedMemory::attachBeforeCreate() +{ + QFETCH_GLOBAL(const QNativeIpcKey::Type, keyType); + QFETCH(const bool, legacy); + const QString keyStr(u"test"_s); + QNativeIpcKey key; + if (legacy) { + key = QSharedMemory::legacyNativeKey(keyStr, keyType); + // same as rememberKey(), but with legacy + if (!keys.contains(key)) { + keys.append(key); + remove(key); + } + } else { + key = rememberKey(keyStr); + } + const qsizetype sz = 100; + QSharedMemory mem(key); + QVERIFY(!mem.attach()); + QVERIFY(mem.create(sz)); +} + +/*! + Keep making shared memory until the kernel stops us. + */ +void tst_QSharedMemory::useTooMuchMemory() +{ + if (QSysInfo::kernelType() == QLatin1String("linux") + && QSysInfo::currentCpuArchitecture() == QLatin1String("arm64")) + QSKIP("This test is unstable: QTBUG-119321"); + +#ifdef Q_OS_LINUX + bool success = true; + int count = 0; + while (success) { + QString key = QLatin1String("maxmemorytest_") + QString::number(count++); + QNativeIpcKey nativeKey = rememberKey(key); + QSharedMemory *sm = new QSharedMemory(nativeKey); + 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->nativeIpcKey(), nativeKey); + QCOMPARE(sm->size(), 0); + QVERIFY(!sm->data()); + if (sm->error() != QSharedMemory::OutOfResources) + qDebug() << sm->error() << sm->errorString(); + // ### Linux won't 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"); + + QSharedMemory government(rememberKey("government")); + QVERIFY(government.create(1024)); + while (true) { + QSharedMemory *war = new QSharedMemory(government.nativeIpcKey()); + QVERIFY(war); + jail.append(war); + if (!war->attach()) { + QVERIFY(!war->isAttached()); + QCOMPARE(war->nativeIpcKey(), government.nativeIpcKey()); + QCOMPARE(war->size(), 0); + QVERIFY(!war->data()); + 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() +{ + QFETCH(QSharedMemory::AccessMode, mode); + + rememberKey(QLatin1String("market")); + QSharedMemory producer(platformSafeKey("market")); + QSharedMemory consumer(platformSafeKey("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 + QVERIFY(put != get); + for (int i = 0; i < size; ++i) { + put[i] = 'Q'; + QCOMPARE(get[i], 'Q'); + } + QVERIFY(consumer.detach()); +} + +void tst_QSharedMemory::simpleDoubleProducerConsumer() +{ + QNativeIpcKey nativeKey = rememberKey(QLatin1String("market")); + QSharedMemory producer(nativeKey); + int size = 512; + QVERIFY(producer.create(size)); + QVERIFY(producer.detach()); + + if (nativeKey.type() == QNativeIpcKey::Type::PosixRealtime) { + // POSIX IPC doesn't guarantee that the shared memory is removed + remove(nativeKey); + } + + QVERIFY(producer.create(size)); + + { + QSharedMemory consumer(nativeKey); + QVERIFY(consumer.attach()); + } +} + +class Consumer : public QThread +{ +public: + QNativeIpcKey nativeKey; + Consumer(const QNativeIpcKey &nativeKey) : nativeKey(nativeKey) {} + + void run() override + { + QSharedMemory consumer(nativeKey); + 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: + Producer(const QNativeIpcKey &nativeKey) : producer(nativeKey) + { + 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)); + } + } + } + + void run() override + { + + char *memory = (char*)producer.data(); + memory[1] = '0'; + QElapsedTimer 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()); + + } + + QSharedMemory producer; +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); + QNativeIpcKey nativeKey = rememberKey(QLatin1String("market")); + + Producer p(nativeKey); + QVERIFY(p.producer.isAttached()); + if (producerIsThread) + p.start(); + + QList<Consumer*> consumers; + for (int i = 0; i < threads; ++i) { + consumers.append(new Consumer(nativeKey)); + 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() +{ +#if QT_CONFIG(process) + QTest::addColumn<int>("processes"); + int tries = 5; + for (int i = 0; i < tries; ++i) { + QTest::newRow("1 process") << 1; + QTest::newRow("5 processes") << 5; + } +#endif +} + +/*! + Create external processes that produce and consume. + */ +void tst_QSharedMemory::simpleProcessProducerConsumer() +{ +#if !QT_CONFIG(process) + QSKIP("No qprocess support", SkipAll); +#else + QFETCH(int, processes); + + QSKIP("This test is unstable: QTBUG-25655"); + + QNativeIpcKey nativeKey = rememberKey("market"); + + QProcess producer; + producer.start(m_helperBinary, { "producer", nativeKey.toString() }); + QVERIFY2(producer.waitForStarted(), "Could not start helper binary"); + QVERIFY2(producer.waitForReadyRead(), "Helper process failed to create shared memory segment: " + + producer.readAllStandardError()); + + QList<QProcess*> consumers; + unsigned int failedProcesses = 0; + QStringList consumerArguments = { "consumer", nativeKey.toString() }; + for (int i = 0; i < processes; ++i) { + QProcess *p = new QProcess; + p->setProcessChannelMode(QProcess::ForwardedChannels); + p->start(m_helperBinary, consumerArguments); + if (p->waitForStarted(2000)) + consumers.append(p); + else + ++failedProcesses; + } + + 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)); + + // tell the producer to exit now + producer.write("", 1); + producer.waitForBytesWritten(); + QVERIFY(producer.waitForFinished(5000)); +#endif +} + +void tst_QSharedMemory::uniqueKey_data() +{ + QTest::addColumn<QString>("key1"); + QTest::addColumn<QString>("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"); + QTest::newRow("Noël -> Nol") << QString::fromUtf8("N\xc3\xabl") << QString("Nol"); +} + +void tst_QSharedMemory::uniqueKey() +{ + QFETCH(QString, key1); + QFETCH(QString, key2); + + QSharedMemory sm1(platformSafeKey(key1)); + QSharedMemory sm2(platformSafeKey(key2)); + + bool setEqual = (key1 == key2); + bool keyEqual = (sm1.nativeIpcKey() == sm2.nativeIpcKey()); + bool nativeEqual = (sm1.nativeKey() == sm2.nativeKey()); + + QCOMPARE(keyEqual, setEqual); + QCOMPARE(nativeEqual, setEqual); +} + +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED +void tst_QSharedMemory::createWithSameKey() +{ + const QString key = u"legacy_key"_s; + const qsizetype sz = 100; + QSharedMemory mem1(key); + QVERIFY(mem1.create(sz)); + + { + QSharedMemory mem2(key); + QVERIFY(!mem2.create(sz)); + QVERIFY(mem2.attach()); + } + // and the second create() should fail as well, QTBUG-111855 + { + QSharedMemory mem2(key); + QVERIFY(!mem2.create(sz)); + QVERIFY(mem2.attach()); + } +} +QT_WARNING_POP + +QTEST_MAIN(tst_QSharedMemory) +#include "tst_qsharedmemory.moc" + diff --git a/tests/auto/corelib/ipc/qsystemsemaphore/CMakeLists.txt b/tests/auto/corelib/ipc/qsystemsemaphore/CMakeLists.txt new file mode 100644 index 0000000000..a0f29ad18a --- /dev/null +++ b/tests/auto/corelib/ipc/qsystemsemaphore/CMakeLists.txt @@ -0,0 +1,22 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qsystemsemaphore Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qsystemsemaphore LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qsystemsemaphore + SOURCES + tst_qsystemsemaphore.cpp +) + +add_subdirectory(acquirerelease) +if(QT_FEATURE_process) + add_dependencies(tst_qsystemsemaphore acquirerelease_helper) +endif() diff --git a/tests/auto/corelib/ipc/qsystemsemaphore/acquirerelease/CMakeLists.txt b/tests/auto/corelib/ipc/qsystemsemaphore/acquirerelease/CMakeLists.txt new file mode 100644 index 0000000000..a0a7d84b17 --- /dev/null +++ b/tests/auto/corelib/ipc/qsystemsemaphore/acquirerelease/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## acquirerelease_helper Binary: +##################################################################### + +qt_internal_add_test_helper(acquirerelease_helper + SOURCES + main.cpp + LIBRARIES + Qt::Test +) diff --git a/tests/auto/corelib/ipc/qsystemsemaphore/acquirerelease/main.cpp b/tests/auto/corelib/ipc/qsystemsemaphore/acquirerelease/main.cpp new file mode 100644 index 0000000000..3cae7ad466 --- /dev/null +++ b/tests/auto/corelib/ipc/qsystemsemaphore/acquirerelease/main.cpp @@ -0,0 +1,80 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QCoreApplication> +#include <QDebug> +#include <QStringList> +#include <QSystemSemaphore> + +int acquire(const QNativeIpcKey &key, int count = 1) +{ + QSystemSemaphore sem(key); + + for (int i = 0; i < count; ++i) { + if (!sem.acquire()) { + qWarning() << "Could not acquire" << sem.key(); + return EXIT_FAILURE; + } + } + qDebug("done aquiring"); + return EXIT_SUCCESS; +} + +int release(const QNativeIpcKey &key) +{ + QSystemSemaphore sem(key); + if (!sem.release()) { + qWarning() << "Could not release" << sem.key(); + return EXIT_FAILURE; + } + qDebug("done releasing"); + return EXIT_SUCCESS; +} + +int acquirerelease(const QNativeIpcKey &key) +{ + QSystemSemaphore sem(key); + if (!sem.acquire()) { + qWarning() << "Could not acquire" << sem.key(); + return EXIT_FAILURE; + } + if (!sem.release()) { + qWarning() << "Could not release" << sem.key(); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + + QStringList arguments = app.arguments(); + // binary name is not used here + arguments.takeFirst(); + if (arguments.size() < 2) { + fprintf(stderr, + "Usage: %s <acquire|release|acquirerelease> <key> [other args...]\n", + argv[0]); + return EXIT_FAILURE; + } + + QString function = arguments.takeFirst(); + QNativeIpcKey key = QNativeIpcKey::fromString(arguments.takeFirst()); + if (function == QLatin1String("acquire")) { + int count = 1; + bool ok = true; + if (arguments.size()) + count = arguments.takeFirst().toInt(&ok); + if (!ok) + count = 1; + return acquire(key, count); + } else if (function == QLatin1String("release")) { + return release(key); + } else if (function == QLatin1String("acquirerelease")) { + return acquirerelease(key); + } else { + qWarning() << "Unknown function" << function; + } + return EXIT_SUCCESS; +} diff --git a/tests/auto/corelib/ipc/qsystemsemaphore/tst_qsystemsemaphore.cpp b/tests/auto/corelib/ipc/qsystemsemaphore/tst_qsystemsemaphore.cpp new file mode 100644 index 0000000000..2c053b91f6 --- /dev/null +++ b/tests/auto/corelib/ipc/qsystemsemaphore/tst_qsystemsemaphore.cpp @@ -0,0 +1,368 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2022 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QTest> +#if QT_CONFIG(process) +#include <QProcess> +#endif + +#include <QtCore/QList> +#include <QtCore/QSystemSemaphore> +#include <QtCore/QTemporaryDir> + +#include "../ipctestcommon.h" + +#define HELPERWAITTIME 10000 + +using namespace Qt::StringLiterals; + +class tst_QSystemSemaphore : public QObject +{ + Q_OBJECT + +public: + tst_QSystemSemaphore(); + + QString mangleKey(QStringView key) + { + if (key.isEmpty()) + return key.toString(); + return u"tstsyssem_%1-%2_%3"_s.arg(QCoreApplication::applicationPid()) + .arg(seq).arg(key); + } + + QNativeIpcKey platformSafeKey(const QString &key) + { + QFETCH_GLOBAL(QNativeIpcKey::Type, keyType); + return QSystemSemaphore::platformSafeKey(mangleKey(key), keyType); + } + +public Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + +private slots: + void nativeKey_data(); + void nativeKey(); + void legacyKey_data() { nativeKey_data(); } + void legacyKey(); + + void changeKeyType(); + void basicacquire(); + void complexacquire(); + void release(); + void twoSemaphores(); + + void basicProcesses(); + + void processes_data(); + void processes(); + + void undo(); + void initialValue(); + +private: + int seq = 0; + QSystemSemaphore *existingLock; + + const QString m_helperBinary; +}; + +tst_QSystemSemaphore::tst_QSystemSemaphore() + : m_helperBinary("./acquirerelease_helper") +{ +} + +void tst_QSystemSemaphore::initTestCase() +{ + IpcTestCommon::addGlobalTestRows<QSystemSemaphore>(); +} + +void tst_QSystemSemaphore::init() +{ + QNativeIpcKey key = platformSafeKey("existing"); + existingLock = new QSystemSemaphore(key, 1, QSystemSemaphore::Create); +} + +void tst_QSystemSemaphore::cleanup() +{ + delete existingLock; + ++seq; +} + +void tst_QSystemSemaphore::nativeKey_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::nativeKey() +{ + QFETCH(QString, constructorKey); + QFETCH(QString, setKey); + QNativeIpcKey constructorIpcKey = platformSafeKey(constructorKey); + QNativeIpcKey setIpcKey = platformSafeKey(setKey); + + QSystemSemaphore sem(constructorIpcKey); + QCOMPARE(sem.nativeIpcKey(), constructorIpcKey); + QCOMPARE(sem.error(), QSystemSemaphore::NoError); + QCOMPARE(sem.errorString(), QString()); + + sem.setNativeKey(setIpcKey); + QCOMPARE(sem.nativeIpcKey(), setIpcKey); + QCOMPARE(sem.error(), QSystemSemaphore::NoError); + QCOMPARE(sem.errorString(), QString()); + + // change the key type + QNativeIpcKey::Type nextKeyType = IpcTestCommon::nextKeyType(setIpcKey.type()); + if (nextKeyType != setIpcKey.type()) { + QNativeIpcKey setIpcKey2 = QSystemSemaphore::platformSafeKey(setKey, nextKeyType); + sem.setNativeKey(setIpcKey2); + QCOMPARE(sem.nativeIpcKey(), setIpcKey2); + } +} + +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED +void tst_QSystemSemaphore::legacyKey() +{ + 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()); +} +QT_WARNING_POP + +void tst_QSystemSemaphore::changeKeyType() +{ + QString keyName = "changeKeyType"; + QNativeIpcKey key = platformSafeKey(keyName); + QNativeIpcKey otherKey = + QSystemSemaphore::platformSafeKey(mangleKey(keyName), IpcTestCommon::nextKeyType(key.type())); + if (key == otherKey) + QSKIP("System only supports one key type"); + + QSystemSemaphore sem1(key, 1, QSystemSemaphore::Create); + QSystemSemaphore sem2(otherKey); + sem1.setNativeKey(otherKey); + sem2.setNativeKey(key); +} + +void tst_QSystemSemaphore::basicacquire() +{ + QNativeIpcKey key = platformSafeKey("basicacquire"); + QSystemSemaphore sem(key, 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() +{ + QNativeIpcKey key = platformSafeKey("complexacquire"); + QSystemSemaphore sem(key, 2, QSystemSemaphore::Create); + QVERIFY(sem.acquire()); + QCOMPARE(sem.error(), QSystemSemaphore::NoError); + QVERIFY(sem.release()); + QCOMPARE(sem.error(), QSystemSemaphore::NoError); + QVERIFY(sem.acquire()); + QCOMPARE(sem.error(), QSystemSemaphore::NoError); + QVERIFY(sem.release()); + QCOMPARE(sem.error(), QSystemSemaphore::NoError); + QVERIFY(sem.acquire()); + QCOMPARE(sem.error(), QSystemSemaphore::NoError); + QVERIFY(sem.acquire()); + QCOMPARE(sem.error(), QSystemSemaphore::NoError); + QVERIFY(sem.release()); + QCOMPARE(sem.error(), QSystemSemaphore::NoError); + QVERIFY(sem.release()); + QCOMPARE(sem.error(), QSystemSemaphore::NoError); + QCOMPARE(sem.errorString(), QString()); +} + +void tst_QSystemSemaphore::release() +{ + QNativeIpcKey key = platformSafeKey("release"); + QSystemSemaphore sem(key, 0, QSystemSemaphore::Create); + QVERIFY(sem.release()); + QCOMPARE(sem.error(), QSystemSemaphore::NoError); + QVERIFY(sem.release()); + QCOMPARE(sem.error(), QSystemSemaphore::NoError); + QVERIFY(sem.acquire()); + QCOMPARE(sem.error(), QSystemSemaphore::NoError); + QVERIFY(sem.acquire()); + QCOMPARE(sem.error(), QSystemSemaphore::NoError); + QVERIFY(sem.release()); + QCOMPARE(sem.error(), QSystemSemaphore::NoError); + QVERIFY(sem.release()); + QCOMPARE(sem.error(), QSystemSemaphore::NoError); + QCOMPARE(sem.errorString(), QString()); +} + +void tst_QSystemSemaphore::twoSemaphores() +{ + QNativeIpcKey key = platformSafeKey("twoSemaphores"); + QSystemSemaphore sem1(key, 1, QSystemSemaphore::Create); + QSystemSemaphore sem2(key); + QVERIFY(sem1.acquire()); + QVERIFY(sem2.release()); + QVERIFY(sem1.acquire()); + QVERIFY(sem2.release()); +} + +void tst_QSystemSemaphore::basicProcesses() +{ +#if !QT_CONFIG(process) + QSKIP("No qprocess support", SkipAll); +#else + QNativeIpcKey key = platformSafeKey("store"); + QSystemSemaphore sem(key, 0, QSystemSemaphore::Create); + + QProcess acquire; + acquire.setProcessChannelMode(QProcess::ForwardedChannels); + + QProcess release; + release.setProcessChannelMode(QProcess::ForwardedChannels); + + acquire.start(m_helperBinary, { "acquire", key.toString() }); + QVERIFY2(acquire.waitForStarted(), "Could not start helper binary"); + acquire.waitForFinished(HELPERWAITTIME); + QCOMPARE(acquire.state(), QProcess::Running); + acquire.kill(); + release.start(m_helperBinary, { "release", key.toString() }); + QVERIFY2(release.waitForStarted(), "Could not start helper binary"); + acquire.waitForFinished(HELPERWAITTIME); + release.waitForFinished(HELPERWAITTIME); + QCOMPARE(acquire.state(), QProcess::NotRunning); +#endif +} + +void tst_QSystemSemaphore::processes_data() +{ + QTest::addColumn<int>("processes"); + for (int i = 0; i < 5; ++i) { + QTest::addRow("1 process (%d)", i) << 1; + QTest::addRow("3 process (%d)", i) << 3; + QTest::addRow("10 process (%d)", i) << 10; + } +} + +void tst_QSystemSemaphore::processes() +{ +#if !QT_CONFIG(process) + QSKIP("No qprocess support", SkipAll); +#else + QNativeIpcKey key = platformSafeKey("store"); + QSystemSemaphore sem(key, 1, QSystemSemaphore::Create); + + QFETCH(int, processes); + QList<QString> scripts(processes, "acquirerelease"); + + QList<QProcess*> consumers; + for (int i = 0; i < scripts.size(); ++i) { + QProcess *p = new QProcess; + p->setProcessChannelMode(QProcess::ForwardedChannels); + consumers.append(p); + p->start(m_helperBinary, { scripts.at(i), key.toString() }); + } + + while (!consumers.isEmpty()) { + consumers.first()->waitForFinished(); + QCOMPARE(consumers.first()->exitStatus(), QProcess::NormalExit); + QCOMPARE(consumers.first()->exitCode(), 0); + delete consumers.takeFirst(); + } +#endif +} + +void tst_QSystemSemaphore::undo() +{ +#if !QT_CONFIG(process) + QSKIP("No qprocess support", SkipAll); +#else + QNativeIpcKey key = platformSafeKey("store"); + switch (key.type()) { + case QNativeIpcKey::Type::PosixRealtime: + case QNativeIpcKey::Type::Windows: + QSKIP("This test only checks a System V behavior."); + + case QNativeIpcKey::Type::SystemV: + break; + } + + QSystemSemaphore sem(key, 1, QSystemSemaphore::Create); + + QStringList acquireArguments = { "acquire", key.toString() }; + QProcess acquire; + acquire.setProcessChannelMode(QProcess::ForwardedChannels); + acquire.start(m_helperBinary, acquireArguments); + QVERIFY2(acquire.waitForStarted(), "Could not start helper binary"); + acquire.waitForFinished(HELPERWAITTIME); + QVERIFY(acquire.state()== QProcess::NotRunning); + + // At process exit the kernel should auto undo + + acquire.start(m_helperBinary, acquireArguments); + QVERIFY2(acquire.waitForStarted(), "Could not start helper binary"); + acquire.waitForFinished(HELPERWAITTIME); + QVERIFY(acquire.state()== QProcess::NotRunning); +#endif +} + +void tst_QSystemSemaphore::initialValue() +{ +#if !QT_CONFIG(process) + QSKIP("No qprocess support", SkipAll); +#else + QNativeIpcKey key = platformSafeKey("store"); + QSystemSemaphore sem(key, 1, QSystemSemaphore::Create); + + QStringList acquireArguments = { "acquire", key.toString() }; + QStringList releaseArguments = { "release", key.toString() }; + QProcess acquire; + acquire.setProcessChannelMode(QProcess::ForwardedChannels); + + QProcess release; + release.setProcessChannelMode(QProcess::ForwardedChannels); + + acquire.start(m_helperBinary, acquireArguments); + QVERIFY2(acquire.waitForStarted(), "Could not start helper binary"); + acquire.waitForFinished(HELPERWAITTIME); + QVERIFY(acquire.state()== QProcess::NotRunning); + + acquire.start(m_helperBinary, acquireArguments << QLatin1String("2")); + QVERIFY2(acquire.waitForStarted(), "Could not start helper binary"); + acquire.waitForFinished(HELPERWAITTIME); + QVERIFY(acquire.state()== QProcess::Running); + acquire.kill(); + + release.start(m_helperBinary, releaseArguments); + QVERIFY2(release.waitForStarted(), "Could not start helper binary"); + acquire.waitForFinished(HELPERWAITTIME); + release.waitForFinished(HELPERWAITTIME); + QVERIFY(acquire.state()== QProcess::NotRunning); +#endif +} + +QTEST_MAIN(tst_QSystemSemaphore) +#include "tst_qsystemsemaphore.moc" + |