diff options
Diffstat (limited to 'tests/auto/corelib')
88 files changed, 6206 insertions, 918 deletions
diff --git a/tests/auto/corelib/codecs/qtextcodec/tst_qtextcodec.cpp b/tests/auto/corelib/codecs/qtextcodec/tst_qtextcodec.cpp index 0f2e9b5d68..c3b53a2fc0 100644 --- a/tests/auto/corelib/codecs/qtextcodec/tst_qtextcodec.cpp +++ b/tests/auto/corelib/codecs/qtextcodec/tst_qtextcodec.cpp @@ -126,6 +126,7 @@ void tst_QTextCodec::toUnicode() } QVERIFY(!uniString.isEmpty()); QCOMPARE( ba, c->fromUnicode( uniString ) ); + QCOMPARE(ba, c->fromUnicode(QStringView(uniString)) ); char ch = '\0'; QVERIFY(c->toUnicode(&ch, 1).length() == 1); @@ -262,7 +263,7 @@ void tst_QTextCodec::fromUnicode() If the encoding is a superset of ASCII, test that the byte array is correct (no off by one, no trailing '\0'). */ - QByteArray result = codec->fromUnicode(QString("abc")); + QByteArray result = codec->fromUnicode(QStringViewLiteral("abc")); if (result.startsWith('a')) { QCOMPARE(result.size(), 3); QCOMPARE(result, QByteArray("abc")); @@ -397,6 +398,7 @@ void tst_QTextCodec::asciiToIscii() const QVERIFY2(textCodec->canEncode(ascii), qPrintable(QString::fromLatin1("Failed for full string with encoding %1") .arg(QString::fromLatin1(textCodec->name().constData())))); + QVERIFY(textCodec->canEncode(QStringView(ascii))); } } @@ -404,12 +406,11 @@ void tst_QTextCodec::nonFlaggedCodepointFFFF() const { //Check that the code point 0xFFFF (=non-character code 0xEFBFBF) is not flagged const QChar ch(0xFFFF); - QString input(ch); QTextCodec *const codec = QTextCodec::codecForMib(106); // UTF-8 QVERIFY(codec); - const QByteArray asDecoded(codec->fromUnicode(input)); + const QByteArray asDecoded = codec->fromUnicode(QStringView(&ch, 1)); QCOMPARE(asDecoded, QByteArray("\357\277\277")); QByteArray ffff("\357\277\277"); diff --git a/tests/auto/corelib/global/global.pro b/tests/auto/corelib/global/global.pro index b4cc8035e6..139e073644 100644 --- a/tests/auto/corelib/global/global.pro +++ b/tests/auto/corelib/global/global.pro @@ -7,6 +7,7 @@ SUBDIRS=\ qnumeric \ qfloat16 \ qrand \ + qrandomgenerator \ qlogging \ qtendian \ qglobalstatic \ diff --git a/tests/auto/corelib/global/qgetputenv/tst_qgetputenv.cpp b/tests/auto/corelib/global/qgetputenv/tst_qgetputenv.cpp index 09abb953ba..f02e902468 100644 --- a/tests/auto/corelib/global/qgetputenv/tst_qgetputenv.cpp +++ b/tests/auto/corelib/global/qgetputenv/tst_qgetputenv.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2016 Intel Corporation. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the test suite of the Qt Toolkit. @@ -30,12 +31,16 @@ #include <QtTest/QtTest> #include <qglobal.h> +#ifdef Q_OS_WIN +#include <qt_windows.h> +#endif class tst_QGetPutEnv : public QObject { Q_OBJECT private slots: void getSetCheck(); + void encoding(); void intValue_data(); void intValue(); }; @@ -53,7 +58,11 @@ void tst_QGetPutEnv::getSetCheck() QCOMPARE(qEnvironmentVariableIntValue(varName, &ok), 0); QVERIFY(!ok); QByteArray result = qgetenv(varName); - QCOMPARE(result, QByteArray()); + QVERIFY(result.isNull()); + QString sresult = qEnvironmentVariable(varName); + QVERIFY(sresult.isNull()); + sresult = qEnvironmentVariable(varName, "hello"); + QCOMPARE(sresult, QString("hello")); #ifndef Q_OS_WIN QVERIFY(qputenv(varName, "")); // deletes varName instead of making it empty, on Windows @@ -64,6 +73,16 @@ void tst_QGetPutEnv::getSetCheck() QCOMPARE(qEnvironmentVariableIntValue(varName), 0); QCOMPARE(qEnvironmentVariableIntValue(varName, &ok), 0); QVERIFY(!ok); + + result = qgetenv(varName); + QVERIFY(!result.isNull()); + QCOMPARE(result, QByteArray()); + sresult = qEnvironmentVariable(varName); + QVERIFY(!sresult.isNull()); + QCOMPARE(sresult, QString()); + sresult = qEnvironmentVariable(varName, "hello"); + QVERIFY(!sresult.isNull()); + QCOMPARE(sresult, QString()); #endif QVERIFY(qputenv(varName, QByteArray("supervalue"))); @@ -76,19 +95,61 @@ void tst_QGetPutEnv::getSetCheck() QVERIFY(!ok); result = qgetenv(varName); QCOMPARE(result, QByteArrayLiteral("supervalue")); + sresult = qEnvironmentVariable(varName); + QCOMPARE(sresult, QString("supervalue")); + sresult = qEnvironmentVariable(varName, "hello"); + QCOMPARE(sresult, QString("supervalue")); qputenv(varName,QByteArray()); // Now test qunsetenv QVERIFY(qunsetenv(varName)); - QVERIFY(!qEnvironmentVariableIsSet(varName)); + QVERIFY(!qEnvironmentVariableIsSet(varName)); // note: might fail on some systems! QVERIFY(qEnvironmentVariableIsEmpty(varName)); ok = true; QCOMPARE(qEnvironmentVariableIntValue(varName), 0); QCOMPARE(qEnvironmentVariableIntValue(varName, &ok), 0); QVERIFY(!ok); + result = qgetenv(varName); - QCOMPARE(result, QByteArray()); + QVERIFY(result.isNull()); + sresult = qEnvironmentVariable(varName); + QVERIFY(sresult.isNull()); + sresult = qEnvironmentVariable(varName, "hello"); + QCOMPARE(sresult, QString("hello")); +} + +void tst_QGetPutEnv::encoding() +{ + // The test string is: + // U+0061 LATIN SMALL LETTER A + // U+00E1 LATIN SMALL LETTER A WITH ACUTE + // U+03B1 GREEK SMALL LETTER ALPHA + // U+0430 CYRILLIC SMALL LETTER A + // This has letters in three different scripts, so no locale besides + // UTF-8 is able handle them all. + // The LATIN SMALL LETTER A WITH ACUTE is NFC for NFD: + // U+0061 U+0301 LATIN SMALL LETTER A + COMBINING ACUTE ACCENT + + const char varName[] = "should_not_exist"; + static const wchar_t rawvalue[] = { 'a', 0x00E1, 0x03B1, 0x0430, 0 }; + QString value = QString::fromWCharArray(rawvalue); + +#if defined(Q_OS_WINRT) + QSKIP("Test cannot be run on this platform"); +#elif defined(Q_OS_WIN) + const wchar_t wvarName[] = L"should_not_exist"; + _wputenv_s(wvarName, rawvalue); +#else + // confirm the locale is UTF-8 + if (value.toLocal8Bit() != "a\xc3\xa1\xce\xb1\xd0\xb0") + QSKIP("Locale is not UTF-8, cannot test"); + + qputenv(varName, QFile::encodeName(value)); +#endif + + QVERIFY(qEnvironmentVariableIsSet(varName)); + QCOMPARE(qEnvironmentVariable(varName), value); } void tst_QGetPutEnv::intValue_data() diff --git a/tests/auto/corelib/global/qrandomgenerator/qrandomgenerator.pro b/tests/auto/corelib/global/qrandomgenerator/qrandomgenerator.pro new file mode 100644 index 0000000000..0307b0c1eb --- /dev/null +++ b/tests/auto/corelib/global/qrandomgenerator/qrandomgenerator.pro @@ -0,0 +1,4 @@ +CONFIG += testcase +TARGET = tst_qrandomgenerator +QT = core-private testlib +SOURCES = tst_qrandomgenerator.cpp diff --git a/tests/auto/corelib/global/qrandomgenerator/tst_qrandomgenerator.cpp b/tests/auto/corelib/global/qrandomgenerator/tst_qrandomgenerator.cpp new file mode 100644 index 0000000000..220ec9a2f8 --- /dev/null +++ b/tests/auto/corelib/global/qrandomgenerator/tst_qrandomgenerator.cpp @@ -0,0 +1,960 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Intel Corporation. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest> +#include <qlinkedlist.h> +#include <qobject.h> +#include <qrandom.h> +#include <qvector.h> +#include <private/qrandom_p.h> + +#include <algorithm> +#include <random> + +#if !QT_CONFIG(getentropy) && (defined(Q_OS_BSD4) || defined(Q_OS_WIN)) +# define HAVE_FALLBACK_ENGINE +#endif + +#define COMMA , +#define QVERIFY_3TIMES(statement) \ + do {\ + if (!static_cast<bool>(statement))\ + if (!static_cast<bool>(statement))\ + if (!QTest::qVerify(static_cast<bool>(statement), #statement, "3rd try", __FILE__, __LINE__))\ + return;\ + } while (0) + +// values chosen at random +static const quint32 RandomValue32 = 0x4d1169f1U; +static const quint64 RandomValue64 = Q_UINT64_C(0x3ce63161b998aa91); +static const double RandomValueFP = double(0.3010463714599609f); + +static void setRNGControl(uint v) +{ +#ifdef QT_BUILD_INTERNAL + qt_randomdevice_control.store(v); +#else + Q_UNUSED(v); +#endif +} + +class tst_QRandomGenerator : public QObject +{ + Q_OBJECT + +public slots: + void cleanup() { setRNGControl(0); } + +private slots: + void basics(); + void knownSequence(); + void discard(); + void copying(); + void copyingGlobal(); + void copyingSystem(); + void systemRng(); + void securelySeeding(); + + void generate32_data(); + void generate32(); + void generate64_data() { generate32_data(); } + void generate64(); + void quality_data() { generate32_data(); } + void quality(); + void fillRangeUInt_data() { generate32_data(); } + void fillRangeUInt(); + void fillRangeULong_data() { generate32_data(); } + void fillRangeULong(); + void fillRangeULLong_data() { generate32_data(); } + void fillRangeULLong(); + void generateUInt_data() { generate32_data(); } + void generateUInt(); + void generateULLong_data() { generate32_data(); } + void generateULLong(); + void generateNonContiguous_data() { generate32_data(); } + void generateNonContiguous(); + + void bounded_data(); + void bounded(); + void boundedQuality_data() { generate32_data(); } + void boundedQuality(); + + void generateReal_data() { generate32_data(); } + void generateReal(); + void qualityReal_data() { generate32_data(); } + void qualityReal(); + + void seedStdRandomEngines(); + void stdUniformIntDistribution_data(); + void stdUniformIntDistribution(); + void stdGenerateCanonical_data() { generateReal_data(); } + void stdGenerateCanonical(); + void stdUniformRealDistribution_data(); + void stdUniformRealDistribution(); + void stdRandomDistributions(); +}; + +// The first 20 results of the sequence: +static const quint32 defaultRngResults[] = { + 853323747U, 2396352728U, 3025954838U, 2985633182U, 2815751046U, + 340588426U, 3587208406U, 298087538U, 2912478009U, 3642122814U, + 3202916223U, 799257577U, 1872145992U, 639469699U, 3201121432U, + 2388658094U, 1735523408U, 2215232359U, 668106566U, 2554687763U +}; + + +using namespace std; +QT_WARNING_DISABLE_GCC("-Wfloat-equal") +QT_WARNING_DISABLE_CLANG("-Wfloat-equal") + +struct RandomGenerator : public QRandomGenerator +{ + RandomGenerator(uint control) + : QRandomGenerator(control ? + QRandomGenerator(control & RandomDataMask) : + *QRandomGenerator::global()) + { + setRNGControl(control); + } +}; + +void tst_QRandomGenerator::basics() +{ + // default constructible + QRandomGenerator rng; + + // copyable && movable + rng = rng; + rng = std::move(rng); + + // 64-bit + QRandomGenerator64 rng64; + rng64 = rng64; + rng64 = std::move(rng64); + + // 32- and 64-bit should be interchangeable: + rng = rng64; + rng64 = rng; + rng = std::move(rng64); + rng64 = std::move(rng); + + rng = QRandomGenerator64::securelySeeded(); + rng64 = QRandomGenerator::securelySeeded(); + + // access global + QRandomGenerator *global = QRandomGenerator::global(); + QRandomGenerator globalCopy = *global; + globalCopy = *global; + QRandomGenerator64 *global64 = QRandomGenerator64::global(); + QRandomGenerator64 globalCopy64 = *global64; + globalCopy64 = *global64; + + // access system + QRandomGenerator *system = QRandomGenerator::system(); + QRandomGenerator systemRng = *system; + systemRng = *system; + + QRandomGenerator64 *system64 = QRandomGenerator64::system(); + QRandomGenerator64 systemRng64 = *system64; + systemRng64 = *system64; + + Q_STATIC_ASSERT(std::is_same<decltype(rng64.generate()) COMMA quint64>::value); + Q_STATIC_ASSERT(std::is_same<decltype(system64->generate()) COMMA quint64>::value); +} + +void tst_QRandomGenerator::knownSequence() +{ + QRandomGenerator rng; + for (quint32 x : defaultRngResults) + QCOMPARE(rng(), x); + + // should work again if we reseed it + rng.seed(); + for (quint32 x : defaultRngResults) + QCOMPARE(rng(), x); +} + +void tst_QRandomGenerator::discard() +{ + QRandomGenerator rng; + rng.discard(1); + QCOMPARE(rng(), defaultRngResults[1]); + + rng.discard(9); + QCOMPARE(rng(), defaultRngResults[11]); +} + +void tst_QRandomGenerator::copying() +{ + QRandomGenerator rng1; + QRandomGenerator rng2 = rng1; + QCOMPARE(rng1, rng2); + + quint32 samples[20]; + rng1.fillRange(samples); + + // not equal anymore + QVERIFY(rng1 != rng2); + + // should produce the same sequence, whichever it was + for (quint32 x : samples) + QCOMPARE(rng2(), x); + + // now they should compare equal again + QCOMPARE(rng1, rng2); +} + +void tst_QRandomGenerator::copyingGlobal() +{ + QRandomGenerator &global = *QRandomGenerator::global(); + QRandomGenerator copy = global; + QCOMPARE(copy, global); + QCOMPARE(global, copy); + + quint32 samples[20]; + global.fillRange(samples); + + // not equal anymore + QVERIFY(copy != global); + + // should produce the same sequence, whichever it was + for (quint32 x : samples) + QCOMPARE(copy(), x); + + // equal again + QCOMPARE(copy, global); + QCOMPARE(global, copy); +} + +void tst_QRandomGenerator::copyingSystem() +{ + QRandomGenerator &system = *QRandomGenerator::system(); + QRandomGenerator copy = system; + QRandomGenerator copy2 = copy; + copy2 = copy; + QCOMPARE(system, copy); + QCOMPARE(copy, copy2); + + quint32 samples[20]; + copy2.fillRange(samples); + + // they still compre equally + QCOMPARE(system, copy); + QCOMPARE(copy, copy2); + + // should NOT produce the same sequence, whichever it was + int sameCount = 0; + for (quint32 x : samples) + sameCount += (copy() == x); + QVERIFY(sameCount < 20); + + QCOMPARE(system, copy); + QCOMPARE(copy, copy2); +} + +void tst_QRandomGenerator::systemRng() +{ + QRandomGenerator *rng = QRandomGenerator::system(); + rng->generate(); + rng->generate64(); + rng->generateDouble(); + rng->bounded(100); + rng->bounded(100U); + +#ifdef QT_BUILD_INTERNAL + quint32 setpoint = std::numeric_limits<int>::max(); + ++setpoint; + quint64 setpoint64 = quint64(setpoint) << 32 | setpoint; + setRNGControl(SetRandomData | setpoint); + + QCOMPARE(rng->generate(), setpoint); + QCOMPARE(rng->generate64(), setpoint64); + QCOMPARE(rng->generateDouble(), ldexp(setpoint64, -64)); + QCOMPARE(rng->bounded(100), 50); +#endif +} + +void tst_QRandomGenerator::securelySeeding() +{ + QRandomGenerator rng1 = QRandomGenerator::securelySeeded(); + QRandomGenerator rng2 = QRandomGenerator::securelySeeded(); + + quint32 samples[20]; + rng1.fillRange(samples); + + // should NOT produce the same sequence, whichever it was + int sameCount = 0; + for (quint32 x : samples) + sameCount += (rng2() == x); + QVERIFY(sameCount < 20); +} + +void tst_QRandomGenerator::generate32_data() +{ + QTest::addColumn<uint>("control"); + QTest::newRow("fixed") << (RandomValue32 & RandomDataMask); + QTest::newRow("global") << 0U; +#ifdef QT_BUILD_INTERNAL + if (qt_has_hwrng()) + QTest::newRow("hwrng") << uint(UseSystemRNG); + QTest::newRow("system") << uint(UseSystemRNG | SkipHWRNG); +# ifdef HAVE_FALLBACK_ENGINE + QTest::newRow("system-fallback") << uint(UseSystemRNG | SkipHWRNG | SkipSystemRNG); +# endif +#endif +} + +void tst_QRandomGenerator::generate32() +{ + QFETCH(uint, control); + RandomGenerator rng(control); + + for (int i = 0; i < 4; ++i) { + QVERIFY_3TIMES([&] { + quint32 value = rng.generate(); + return value != 0 && value != RandomValue32; + }()); + } + + // and should hopefully be different from repeated calls + for (int i = 0; i < 4; ++i) + QVERIFY_3TIMES(rng.generate() != rng.generate()); +} + +void tst_QRandomGenerator::generate64() +{ + QFETCH(uint, control); + RandomGenerator rng(control); + + QVERIFY_3TIMES(rng.generate64() > std::numeric_limits<quint32>::max()); + for (int i = 0; i < 4; ++i) { + QVERIFY_3TIMES([&] { + quint64 value = rng.generate64(); + return value != 0 && value != RandomValue32 && value != RandomValue64; + }()); + } + + // and should hopefully be different from repeated calls + for (int i = 0; i < 4; ++i) + QVERIFY_3TIMES(rng.generate64() != rng.generate64()); + for (int i = 0; i < 4; ++i) + QVERIFY_3TIMES(rng.generate() != quint32(rng.generate64())); + for (int i = 0; i < 4; ++i) + QVERIFY_3TIMES(rng.generate() != (rng.generate64() >> 32)); +} + +void tst_QRandomGenerator::quality() +{ + enum { + BufferSize = 2048, + BufferCount = BufferSize / sizeof(quint32), + + // if the distribution were perfect, each byte in the buffer would + // appear exactly: + PerfectDistribution = BufferSize / (UCHAR_MAX + 1), + + // The chance of a value appearing N times above its perfect + // distribution is the same as it appearing N times in a row: + // N Probability + // 1 100% + // 2 0.390625% + // 3 15.25 in a million + // 4 59.60 in a billion + // 8 5.421e-20 + // 16 2.938e-39 + + AcceptableThreshold = 4 * PerfectDistribution, + FailureThreshold = 16 * PerfectDistribution + }; + Q_STATIC_ASSERT(FailureThreshold > AcceptableThreshold); + + QFETCH(uint, control); + if (control & RandomDataMask) + return; + RandomGenerator rng(control); + + int histogram[UCHAR_MAX + 1]; + memset(histogram, 0, sizeof(histogram)); + + { + // test the quality of the generator + quint32 buffer[BufferCount]; + memset(buffer, 0xcc, sizeof(buffer)); + generate_n(buffer, +BufferCount, [&] { return rng.generate(); }); + + quint8 *ptr = reinterpret_cast<quint8 *>(buffer); + quint8 *end = ptr + sizeof(buffer); + for ( ; ptr != end; ++ptr) + histogram[*ptr]++; + } + + for (uint i = 0; i < sizeof(histogram)/sizeof(histogram[0]); ++i) { + int v = histogram[i]; + if (v > AcceptableThreshold) + qDebug() << i << "above threshold:" << v; + QVERIFY2(v < FailureThreshold, QByteArray::number(i)); + } + qDebug() << "Average:" << (std::accumulate(begin(histogram), end(histogram), 0) / (1. * (UCHAR_MAX + 1))) + << "(expected" << int(PerfectDistribution) << "ideally)" + << "Max:" << *std::max_element(begin(histogram), end(histogram)) + << "at" << std::max_element(begin(histogram), end(histogram)) - histogram + << "Min:" << *std::min_element(begin(histogram), end(histogram)) + << "at" << std::min_element(begin(histogram), end(histogram)) - histogram; +} + +template <typename T> void fillRange_template() +{ + QFETCH(uint, control); + RandomGenerator rng(control); + + for (int i = 0; i < 4; ++i) { + QVERIFY_3TIMES([&] { + T value[1] = { RandomValue32 }; + rng.fillRange(value); + return value[0] != 0 && value[0] != RandomValue32; + }()); + } + + for (int i = 0; i < 4; ++i) { + QVERIFY_3TIMES([&] { + T array[2] = {}; + rng.fillRange(array); + return array[0] != array[1]; + }()); + } + + if (sizeof(T) > sizeof(quint32)) { + // just to shut up a warning about shifting uint more than the width + enum { Shift = sizeof(T) / 2 * CHAR_BIT }; + QVERIFY_3TIMES([&] { + T value[1] = { }; + rng.fillRange(value); + return quint32(value[0] >> Shift) != quint32(value[0]); + }()); + } + + // fill in a longer range + auto longerArrayCheck = [&] { + T array[32]; + memset(array, 0, sizeof(array)); + rng.fillRange(array); + if (sizeof(T) == sizeof(RandomValue64) + && find(begin(array), end(array), RandomValue64) != end(array)) + return false; + return find(begin(array), end(array), 0) == end(array) && + find(begin(array), end(array), RandomValue32) == end(array); + }; + QVERIFY_3TIMES(longerArrayCheck()); +} + +void tst_QRandomGenerator::fillRangeUInt() { fillRange_template<uint>(); } +void tst_QRandomGenerator::fillRangeULong() { fillRange_template<ulong>(); } +void tst_QRandomGenerator::fillRangeULLong() { fillRange_template<qulonglong>(); } + +template <typename T> void generate_template() +{ + QFETCH(uint, control); + RandomGenerator rng(control); + + // almost the same as fillRange, but limited to 32 bits + for (int i = 0; i < 4; ++i) { + QVERIFY_3TIMES([&] { + T value[1] = { RandomValue32 }; + QRandomGenerator().generate(begin(value), end(value)); + return value[0] != 0 && value[0] != RandomValue32 + && value[0] <= numeric_limits<quint32>::max(); + }()); + } + + // fill in a longer range + auto longerArrayCheck = [&] { + T array[72] = {}; // at least 256 bytes + QRandomGenerator().generate(begin(array), end(array)); + return find_if(begin(array), end(array), [&](T cur) { + return cur == 0 || cur == RandomValue32 || + cur == RandomValue64 || cur > numeric_limits<quint32>::max(); + }) == end(array); + }; + QVERIFY_3TIMES(longerArrayCheck()); +} + +void tst_QRandomGenerator::generateUInt() { generate_template<uint>(); } +void tst_QRandomGenerator::generateULLong() { generate_template<qulonglong>(); } + +void tst_QRandomGenerator::generateNonContiguous() +{ + QFETCH(uint, control); + RandomGenerator rng(control); + + QLinkedList<quint64> list = { 0, 0, 0, 0, 0, 0, 0, 0 }; + auto longerArrayCheck = [&] { + QRandomGenerator().generate(list.begin(), list.end()); + return find_if(list.begin(), list.end(), [&](quint64 cur) { + return cur == 0 || cur == RandomValue32 || + cur == RandomValue64 || cur > numeric_limits<quint32>::max(); + }) == list.end(); + }; + QVERIFY_3TIMES(longerArrayCheck()); +} + +void tst_QRandomGenerator::bounded_data() +{ +#ifndef QT_BUILD_INTERNAL + QSKIP("Test only possible in developer builds"); +#endif + + QTest::addColumn<uint>("control"); + QTest::addColumn<quint32>("sup"); + QTest::addColumn<quint32>("expected"); + + auto newRow = [&](quint32 val, quint32 sup) { + // calculate the scaled value + quint64 scaled = val; + scaled <<= 32; + scaled /= sup; + unsigned shifted = unsigned(scaled); + Q_ASSERT(val < sup); + Q_ASSERT((shifted & RandomDataMask) == shifted); + + unsigned control = SetRandomData | shifted; + QTest::addRow("%u,%u", val, sup) << control << sup << val; + }; + + // useless: we can only generate zeroes: + newRow(0, 1); + + newRow(25, 200); + newRow(50, 200); + newRow(75, 200); +} + +void tst_QRandomGenerator::bounded() +{ + QFETCH(uint, control); + QFETCH(quint32, sup); + QFETCH(quint32, expected); + RandomGenerator rng(control); + + quint32 value = rng.bounded(sup); + QVERIFY(value < sup); + QCOMPARE(value, expected); + + int ivalue = rng.bounded(sup); + QVERIFY(ivalue < int(sup)); + QCOMPARE(ivalue, int(expected)); + + // confirm only the bound now + setRNGControl(control & (SkipHWRNG|SkipSystemRNG|UseSystemRNG)); + value = rng.bounded(sup); + QVERIFY(value < sup); + + value = rng.bounded(sup / 2, 3 * sup / 2); + QVERIFY(value >= sup / 2); + QVERIFY(value < 3 * sup / 2); + + ivalue = rng.bounded(-int(sup), int(sup)); + QVERIFY(ivalue >= -int(sup)); + QVERIFY(ivalue < int(sup)); + + // wholly negative range + ivalue = rng.bounded(-int(sup), 0); + QVERIFY(ivalue >= -int(sup)); + QVERIFY(ivalue < 0); +} + +void tst_QRandomGenerator::boundedQuality() +{ + enum { Bound = 283 }; // a prime number + enum { + BufferCount = Bound * 32, + + // if the distribution were perfect, each byte in the buffer would + // appear exactly: + PerfectDistribution = BufferCount / Bound, + + // The chance of a value appearing N times above its perfect + // distribution is the same as it appearing N times in a row: + // N Probability + // 1 100% + // 2 0.390625% + // 3 15.25 in a million + // 4 59.60 in a billion + // 8 5.421e-20 + // 16 2.938e-39 + + AcceptableThreshold = 4 * PerfectDistribution, + FailureThreshold = 16 * PerfectDistribution + }; + Q_STATIC_ASSERT(FailureThreshold > AcceptableThreshold); + + QFETCH(uint, control); + if (control & RandomDataMask) + return; + RandomGenerator rng(control); + + int histogram[Bound]; + memset(histogram, 0, sizeof(histogram)); + + { + // test the quality of the generator + QVector<quint32> buffer(BufferCount, 0xcdcdcdcd); + generate(buffer.begin(), buffer.end(), [&] { return rng.bounded(Bound); }); + + for (quint32 value : qAsConst(buffer)) { + QVERIFY(value < Bound); + histogram[value]++; + } + } + + for (unsigned i = 0; i < sizeof(histogram)/sizeof(histogram[0]); ++i) { + int v = histogram[i]; + if (v > AcceptableThreshold) + qDebug() << i << "above threshold:" << v; + QVERIFY2(v < FailureThreshold, QByteArray::number(i)); + } + + qDebug() << "Average:" << (std::accumulate(begin(histogram), end(histogram), 0) / qreal(Bound)) + << "(expected" << int(PerfectDistribution) << "ideally)" + << "Max:" << *std::max_element(begin(histogram), end(histogram)) + << "at" << std::max_element(begin(histogram), end(histogram)) - histogram + << "Min:" << *std::min_element(begin(histogram), end(histogram)) + << "at" << std::min_element(begin(histogram), end(histogram)) - histogram; +} + +void tst_QRandomGenerator::generateReal() +{ + QFETCH(uint, control); + RandomGenerator rng(control); + + for (int i = 0; i < 4; ++i) { + QVERIFY_3TIMES([&] { + qreal value = rng.generateDouble(); + return value >= 0 && value < 1 && value != RandomValueFP; + }()); + } + + // and should hopefully be different from repeated calls + for (int i = 0; i < 4; ++i) + QVERIFY_3TIMES(rng.generateDouble() != rng.generateDouble()); +} + +void tst_QRandomGenerator::qualityReal() +{ + QFETCH(uint, control); + if (control & RandomDataMask) + return; + RandomGenerator rng(control); + + enum { + SampleSize = 160, + + // Expected value: sample size times proportion of the range: + PerfectOctile = SampleSize / 8, + PerfectHalf = SampleSize / 2, + + // Variance is (1 - proportion of range) * expected; sqrt() for standard deviations. + // Should usually be within twice that and almost never outside four times: + RangeHalf = 25, // floor(4 * sqrt((1 - 0.5) * PerfectHalf)) + RangeOctile = 16 // floor(4 * sqrt((1 - 0.125) * PerfectOctile)) + }; + + double data[SampleSize]; + std::generate(std::begin(data), std::end(data), [&rng] { return rng.generateDouble(); }); + + int aboveHalf = 0; + int belowOneEighth = 0; + int aboveSevenEighths = 0; + for (double x : data) { + aboveHalf += x >= 0.5; + belowOneEighth += x < 0.125; + aboveSevenEighths += x >= 0.875; + + // these are strict requirements + QVERIFY(x >= 0); + QVERIFY(x < 1); + } + + qInfo("Halfway distribution: %.1f - %.1f", 100. * aboveHalf / SampleSize, 100 - 100. * aboveHalf / SampleSize); + qInfo("%.1f below 1/8 (expected 12.5%% ideally)", 100. * belowOneEighth / SampleSize); + qInfo("%.1f above 7/8 (expected 12.5%% ideally)", 100. * aboveSevenEighths / SampleSize); + + QVERIFY(aboveHalf < PerfectHalf + RangeHalf); + QVERIFY(aboveHalf > PerfectHalf - RangeHalf); + QVERIFY(aboveSevenEighths < PerfectOctile + RangeOctile); + QVERIFY(aboveSevenEighths > PerfectOctile - RangeOctile); + QVERIFY(belowOneEighth < PerfectOctile + RangeOctile); + QVERIFY(belowOneEighth > PerfectOctile - RangeOctile); +} + +template <typename Engine> void seedStdRandomEngine() +{ + { + QRandomGenerator &rd = *QRandomGenerator::system(); + Engine e(rd); + QVERIFY_3TIMES(e() != 0); + + e.seed(rd); + QVERIFY_3TIMES(e() != 0); + } + { + QRandomGenerator64 &rd = *QRandomGenerator64::system(); + Engine e(rd); + QVERIFY_3TIMES(e() != 0); + + e.seed(rd); + QVERIFY_3TIMES(e() != 0); + } +} + +void tst_QRandomGenerator::seedStdRandomEngines() +{ + seedStdRandomEngine<std::default_random_engine>(); + seedStdRandomEngine<std::minstd_rand0>(); + seedStdRandomEngine<std::minstd_rand>(); + seedStdRandomEngine<std::mt19937>(); + seedStdRandomEngine<std::mt19937_64>(); + seedStdRandomEngine<std::ranlux24_base>(); + seedStdRandomEngine<std::ranlux48_base>(); + seedStdRandomEngine<std::ranlux24>(); + seedStdRandomEngine<std::ranlux48>(); +} + +void tst_QRandomGenerator::stdUniformIntDistribution_data() +{ +#ifndef QT_BUILD_INTERNAL + QSKIP("Test only possible in developer builds"); +#endif + + QTest::addColumn<uint>("control"); + QTest::addColumn<quint32>("max"); + + auto newRow = [&](quint32 max) { +#ifdef QT_BUILD_INTERNAL + if (qt_has_hwrng()) + QTest::addRow("hwrng:%u", max) << uint(UseSystemRNG) << max; + QTest::addRow("system:%u", max) << uint(UseSystemRNG | SkipHWRNG) << max; +# ifdef HAVE_FALLBACK_ENGINE + QTest::addRow("system-fallback:%u", max) << uint(UseSystemRNG | SkipHWRNG | SkipSystemRNG) << max; +# endif +#endif + QTest::addRow("global:%u", max) << 0U << max; + }; + + // useless: we can only generate zeroes: + newRow(0); + + newRow(1); + newRow(199); + newRow(numeric_limits<quint32>::max()); +} + +void tst_QRandomGenerator::stdUniformIntDistribution() +{ + QFETCH(uint, control); + QFETCH(quint32, max); + RandomGenerator rng(control); + + { + QRandomGenerator rd; + { + std::uniform_int_distribution<quint32> dist(0, max); + quint32 value = dist(rd); + QVERIFY(value >= dist.min()); + QVERIFY(value <= dist.max()); + } + if ((3 * max / 2) > max) { + std::uniform_int_distribution<quint32> dist(max / 2, 3 * max / 2); + quint32 value = dist(rd); + QVERIFY(value >= dist.min()); + QVERIFY(value <= dist.max()); + } + + { + std::uniform_int_distribution<quint64> dist(0, quint64(max) << 32); + quint64 value = dist(rd); + QVERIFY(value >= dist.min()); + QVERIFY(value <= dist.max()); + } + { + std::uniform_int_distribution<quint64> dist(max / 2, 3 * quint64(max) / 2); + quint64 value = dist(rd); + QVERIFY(value >= dist.min()); + QVERIFY(value <= dist.max()); + } + } + + { + QRandomGenerator64 rd; + { + std::uniform_int_distribution<quint32> dist(0, max); + quint32 value = dist(rd); + QVERIFY(value >= dist.min()); + QVERIFY(value <= dist.max()); + } + if ((3 * max / 2) > max) { + std::uniform_int_distribution<quint32> dist(max / 2, 3 * max / 2); + quint32 value = dist(rd); + QVERIFY(value >= dist.min()); + QVERIFY(value <= dist.max()); + } + + { + std::uniform_int_distribution<quint64> dist(0, quint64(max) << 32); + quint64 value = dist(rd); + QVERIFY(value >= dist.min()); + QVERIFY(value <= dist.max()); + } + { + std::uniform_int_distribution<quint64> dist(max / 2, 3 * quint64(max) / 2); + quint64 value = dist(rd); + QVERIFY(value >= dist.min()); + QVERIFY(value <= dist.max()); + } + } +} + +void tst_QRandomGenerator::stdGenerateCanonical() +{ +#if defined(Q_CC_MSVC) && Q_CC_MSVC < 1900 + // see https://connect.microsoft.com/VisualStudio/feedback/details/811611 + QSKIP("MSVC 2013's std::generate_canonical is broken"); +#else + QFETCH(uint, control); + RandomGenerator rng(control); + + for (int i = 0; i < 4; ++i) { + QVERIFY_3TIMES([&] { + qreal value = std::generate_canonical<qreal COMMA 32>(rng); + return value > 0 && value < 1 && value != RandomValueFP; + }()); + } + + // and should hopefully be different from repeated calls + for (int i = 0; i < 4; ++i) + QVERIFY_3TIMES(std::generate_canonical<qreal COMMA 32>(rng) != + std::generate_canonical<qreal COMMA 32>(rng)); +#endif +} + +void tst_QRandomGenerator::stdUniformRealDistribution_data() +{ +#ifndef QT_BUILD_INTERNAL + QSKIP("Test only possible in developer builds"); +#endif + + QTest::addColumn<uint>("control"); + QTest::addColumn<double>("min"); + QTest::addColumn<double>("sup"); + + auto newRow = [&](double min, double sup) { +#ifdef QT_BUILD_INTERNAL + if (qt_has_hwrng()) + QTest::addRow("hwrng:%g-%g", min, sup) << uint(UseSystemRNG) << min << sup; + QTest::addRow("system:%g-%g", min, sup) << uint(UseSystemRNG | SkipHWRNG) << min << sup; +# ifdef HAVE_FALLBACK_ENGINE + QTest::addRow("system-fallback:%g-%g", min, sup) << uint(UseSystemRNG | SkipHWRNG | SkipSystemRNG) << min << sup; +# endif +#endif + QTest::addRow("global:%g-%g", min, sup) << 0U << min << sup; + }; + + newRow(0, 0); // useless: we can only generate zeroes + newRow(0, 1); // canonical + newRow(0, 200); + newRow(0, numeric_limits<quint32>::max() + 1.); + newRow(0, numeric_limits<quint64>::max() + 1.); + newRow(-1, 1.6); +} + +void tst_QRandomGenerator::stdUniformRealDistribution() +{ + QFETCH(uint, control); + QFETCH(double, min); + QFETCH(double, sup); + RandomGenerator rng(control & (SkipHWRNG|SkipSystemRNG|UseSystemRNG)); + + { + QRandomGenerator rd; + { + std::uniform_real_distribution<double> dist(min, sup); + double value = dist(rd); + QVERIFY(value >= dist.min()); + if (min != sup) + QVERIFY(value < dist.max()); + } + } + + { + QRandomGenerator64 rd; + { + std::uniform_real_distribution<double> dist(min, sup); + double value = dist(rd); + QVERIFY(value >= dist.min()); + if (min != sup) + QVERIFY(value < dist.max()); + } + } +} + +template <typename Generator> void stdRandomDistributions_template() +{ + Generator rd; + + std::bernoulli_distribution()(rd); + + std::binomial_distribution<quint32>()(rd); + std::binomial_distribution<quint64>()(rd); + + std::negative_binomial_distribution<quint32>()(rd); + std::negative_binomial_distribution<quint64>()(rd); + + std::poisson_distribution<int>()(rd); + std::poisson_distribution<qint64>()(rd); + + std::normal_distribution<qreal>()(rd); + + { + std::discrete_distribution<int> discrete{0, 1, 1, 10000, 2}; + QVERIFY(discrete(rd) != 0); + QVERIFY_3TIMES(discrete(rd) == 3); + } +} + +void tst_QRandomGenerator::stdRandomDistributions() +{ + // just a compile check for some of the distributions, besides + // std::uniform_int_distribution and std::uniform_real_distribution (tested + // above) + + stdRandomDistributions_template<QRandomGenerator>(); + stdRandomDistributions_template<QRandomGenerator64>(); +} + +QTEST_APPLESS_MAIN(tst_QRandomGenerator) + +#include "tst_qrandomgenerator.moc" diff --git a/tests/auto/corelib/global/qtendian/qtendian.pro b/tests/auto/corelib/global/qtendian/qtendian.pro index 214c706ca5..890976d26c 100644 --- a/tests/auto/corelib/global/qtendian/qtendian.pro +++ b/tests/auto/corelib/global/qtendian/qtendian.pro @@ -1,4 +1,4 @@ CONFIG += testcase TARGET = tst_qtendian -QT = core testlib +QT = core core-private testlib SOURCES = tst_qtendian.cpp diff --git a/tests/auto/corelib/global/qtendian/tst_qtendian.cpp b/tests/auto/corelib/global/qtendian/tst_qtendian.cpp index 915e40954b..6934818dcf 100644 --- a/tests/auto/corelib/global/qtendian/tst_qtendian.cpp +++ b/tests/auto/corelib/global/qtendian/tst_qtendian.cpp @@ -29,6 +29,7 @@ #include <QtTest/QtTest> #include <QtCore/qendian.h> +#include <QtCore/private/qendian_p.h> class tst_QtEndian: public QObject @@ -41,6 +42,11 @@ private slots: void toBigEndian(); void toLittleEndian(); + + void endianIntegers_data(); + void endianIntegers(); + + void endianBitfields(); }; struct TestData @@ -129,5 +135,83 @@ void tst_QtEndian::toLittleEndian() #undef ENDIAN_TEST +void tst_QtEndian::endianIntegers_data() +{ + QTest::addColumn<int>("val"); + + QTest::newRow("-30000") << -30000; + QTest::newRow("-1") << -1; + QTest::newRow("0") << 0; + QTest::newRow("1020") << 1020; + QTest::newRow("16385") << 16385; +} + +void tst_QtEndian::endianIntegers() +{ + QFETCH(int, val); + + qint16 vi16 = val; + qint32 vi32 = val; + qint64 vi64 = val; + quint16 vu16 = val; + quint32 vu32 = val; + quint64 vu64 = val; + +#if Q_BYTE_ORDER == Q_BIG_ENDIAN + QCOMPARE(*reinterpret_cast<qint16_be*>(&vi16), vi16); + QCOMPARE(*reinterpret_cast<qint32_be*>(&vi32), vi32); + QCOMPARE(*reinterpret_cast<qint64_be*>(&vi64), vi64); + QCOMPARE(*reinterpret_cast<qint16_le*>(&vi16), qbswap(vi16)); + QCOMPARE(*reinterpret_cast<qint32_le*>(&vi32), qbswap(vi32)); + QCOMPARE(*reinterpret_cast<qint64_le*>(&vi64), qbswap(vi64)); + QCOMPARE(*reinterpret_cast<quint16_be*>(&vu16), vu16); + QCOMPARE(*reinterpret_cast<quint32_be*>(&vu32), vu32); + QCOMPARE(*reinterpret_cast<quint64_be*>(&vu64), vu64); + QCOMPARE(*reinterpret_cast<quint16_le*>(&vu16), qbswap(vu16)); + QCOMPARE(*reinterpret_cast<quint32_le*>(&vu32), qbswap(vu32)); + QCOMPARE(*reinterpret_cast<quint64_le*>(&vu64), qbswap(vu64)); +#else + QCOMPARE(*reinterpret_cast<qint16_be*>(&vi16), qbswap(vi16)); + QCOMPARE(*reinterpret_cast<qint32_be*>(&vi32), qbswap(vi32)); + QCOMPARE(*reinterpret_cast<qint64_be*>(&vi64), qbswap(vi64)); + QCOMPARE(*reinterpret_cast<qint16_le*>(&vi16), vi16); + QCOMPARE(*reinterpret_cast<qint32_le*>(&vi32), vi32); + QCOMPARE(*reinterpret_cast<qint64_le*>(&vi64), vi64); + QCOMPARE(*reinterpret_cast<quint16_be*>(&vu16), qbswap(vu16)); + QCOMPARE(*reinterpret_cast<quint32_be*>(&vu32), qbswap(vu32)); + QCOMPARE(*reinterpret_cast<quint64_be*>(&vu64), qbswap(vu64)); + QCOMPARE(*reinterpret_cast<quint16_le*>(&vu16), vu16); + QCOMPARE(*reinterpret_cast<quint32_le*>(&vu32), vu32); + QCOMPARE(*reinterpret_cast<quint64_le*>(&vu64), vu64); +#endif +} + +void tst_QtEndian::endianBitfields() +{ + union { + quint32_be_bitfield<21, 11> upper; + quint32_be_bitfield<10, 11> lower; + qint32_be_bitfield<0, 10> bottom; + } u; + + u.upper = 200; + QCOMPARE(u.upper, 200U); + u.lower = 1000; + u.bottom = -8; + QCOMPARE(u.lower, 1000U); + QCOMPARE(u.upper, 200U); + + u.lower += u.upper; + QCOMPARE(u.upper, 200U); + QCOMPARE(u.lower, 1200U); + + u.upper = 65536 + 7; + u.lower = 65535; + QCOMPARE(u.lower, 65535U & ((1<<11) - 1)); + QCOMPARE(u.upper, 7U); + + QCOMPARE(u.bottom, -8); +} + QTEST_MAIN(tst_QtEndian) #include "tst_qtendian.moc" diff --git a/tests/auto/corelib/io/io.pro b/tests/auto/corelib/io/io.pro index 01ed84fda9..bd2a9f4c8e 100644 --- a/tests/auto/corelib/io/io.pro +++ b/tests/auto/corelib/io/io.pro @@ -34,12 +34,6 @@ SUBDIRS=\ qurl \ qurlinternal \ qurlquery \ - qwinoverlappedionotifier \ - -!win32 { - SUBDIRS -=\ - qwinoverlappedionotifier -} !qtHaveModule(gui): SUBDIRS -= \ qdatastream \ @@ -73,5 +67,4 @@ win32:!qtConfig(private_tests): SUBDIRS -= \ qprocess-noapplication winrt: SUBDIRS -= \ - qstorageinfo \ - qwinoverlappedionotifier + qstorageinfo diff --git a/tests/auto/corelib/io/largefile/tst_largefile.cpp b/tests/auto/corelib/io/largefile/tst_largefile.cpp index 5975303ca6..2d13e6166d 100644 --- a/tests/auto/corelib/io/largefile/tst_largefile.cpp +++ b/tests/auto/corelib/io/largefile/tst_largefile.cpp @@ -31,6 +31,7 @@ #include <QtAlgorithms> #include <QFile> #include <QFileInfo> +#include <QRandomGenerator> #include <qplatformdefs.h> #include <QDebug> @@ -174,8 +175,7 @@ static inline QByteArray generateDataBlock(int blockSize, QString text, qint64 u static qint64 counter = 0; - qint64 randomBits = ((qint64)qrand() << 32) - | ((qint64)qrand() & 0x00000000ffffffff); + qint64 randomBits = QRandomGenerator::global()->generate64(); appendRaw(block, randomBits); appendRaw(block, userBits); diff --git a/tests/auto/corelib/io/qabstractfileengine/tst_qabstractfileengine.cpp b/tests/auto/corelib/io/qabstractfileengine/tst_qabstractfileengine.cpp index ddfd14550b..1072cb44f9 100644 --- a/tests/auto/corelib/io/qabstractfileengine/tst_qabstractfileengine.cpp +++ b/tests/auto/corelib/io/qabstractfileengine/tst_qabstractfileengine.cpp @@ -341,8 +341,10 @@ public: if (file) { QMutexLocker lock(&file->mutex); switch (time) { - case CreationTime: - return file->creation; + case BirthTime: + return file->birth; + case MetadataChangeTime: + return file->change; case ModificationTime: return file->modification; case AccessTime: @@ -353,6 +355,13 @@ public: return QDateTime(); } + bool setFileTime(const QDateTime &newDate, FileTime time) + { + Q_UNUSED(newDate); + Q_UNUSED(time); + return false; + } + void setFileName(const QString &file) { if (openForRead_ || openForWrite_) @@ -449,7 +458,7 @@ protected: uint userId, groupId; QAbstractFileEngine::FileFlags fileFlags; - QDateTime creation, modification, access; + QDateTime birth, change, modification, access; QByteArray content; }; diff --git a/tests/auto/corelib/io/qdebug/tst_qdebug.cpp b/tests/auto/corelib/io/qdebug/tst_qdebug.cpp index 7147405f3b..b43ea7cfa5 100644 --- a/tests/auto/corelib/io/qdebug/tst_qdebug.cpp +++ b/tests/auto/corelib/io/qdebug/tst_qdebug.cpp @@ -51,6 +51,7 @@ private slots: void qDebugQChar() const; void qDebugQString() const; void qDebugQStringRef() const; + void qDebugQStringView() const; void qDebugQLatin1String() const; void qDebugQByteArray() const; void qDebugQFlags() const; @@ -492,6 +493,46 @@ void tst_QDebug::qDebugQStringRef() const } } +void tst_QDebug::qDebugQStringView() const +{ + /* Use a basic string. */ + { + QLatin1String file, function; + int line = 0; + const QStringView inView = QStringViewLiteral("input"); + + MessageHandlerSetter mhs(myMessageHandler); + { qDebug() << inView; } +#ifndef QT_NO_MESSAGELOGCONTEXT + file = QLatin1String(__FILE__); line = __LINE__ - 2; function = QLatin1String(Q_FUNC_INFO); +#endif + QCOMPARE(s_msgType, QtDebugMsg); + QCOMPARE(s_msg, QLatin1String("\"input\"")); + QCOMPARE(QLatin1String(s_file), file); + QCOMPARE(s_line, line); + QCOMPARE(QLatin1String(s_function), function); + } + + /* Use a null QStringView. */ + { + QString file, function; + int line = 0; + + const QStringView inView; + + MessageHandlerSetter mhs(myMessageHandler); + { qDebug() << inView; } +#ifndef QT_NO_MESSAGELOGCONTEXT + file = __FILE__; line = __LINE__ - 2; function = Q_FUNC_INFO; +#endif + QCOMPARE(s_msgType, QtDebugMsg); + QCOMPARE(s_msg, QLatin1String("\"\"")); + QCOMPARE(QLatin1String(s_file), file); + QCOMPARE(s_line, line); + QCOMPARE(QLatin1String(s_function), function); + } +} + void tst_QDebug::qDebugQLatin1String() const { QString file, function; diff --git a/tests/auto/corelib/io/qfile/BLACKLIST b/tests/auto/corelib/io/qfile/BLACKLIST index 8d636d40b8..8366667166 100644 --- a/tests/auto/corelib/io/qfile/BLACKLIST +++ b/tests/auto/corelib/io/qfile/BLACKLIST @@ -5,7 +5,3 @@ msvc-2017 ci [readLineStdin_lineByLine] msvc-2015 ci msvc-2017 ci -[openStandardStreamsFileDescriptors] -osx -[openStandardStreamsBufferedStreams] -osx diff --git a/tests/auto/corelib/io/qfile/tst_qfile.cpp b/tests/auto/corelib/io/qfile/tst_qfile.cpp index 5f0eae6fc3..c173a87a41 100644 --- a/tests/auto/corelib/io/qfile/tst_qfile.cpp +++ b/tests/auto/corelib/io/qfile/tst_qfile.cpp @@ -2138,6 +2138,7 @@ public: uint ownerId(FileOwner) const { return 0; } QString owner(FileOwner) const { return QString(); } QDateTime fileTime(FileTime) const { return QDateTime(); } + bool setFileTime(const QDateTime &newDate, FileTime time) { return false; } private: int number; @@ -3247,22 +3248,39 @@ static qint64 streamExpectedSize(int fd) QT_STATBUF sb; if (QT_FSTAT(fd, &sb) != -1) return sb.st_size; + qErrnoWarning("Could not fstat fd %d", fd); return 0; } static qint64 streamCurrentPosition(int fd) { - QT_OFF_T pos = QT_LSEEK(fd, 0, SEEK_CUR); - if (pos != -1) - return pos; + QT_STATBUF sb; + if (QT_FSTAT(fd, &sb) != -1) { + QT_OFF_T pos = -1; + if ((sb.st_mode & QT_STAT_MASK) == QT_STAT_REG) + pos = QT_LSEEK(fd, 0, SEEK_CUR); + if (pos != -1) + return pos; + // failure to lseek() is not a problem + } else { + qErrnoWarning("Could not fstat fd %d", fd); + } return 0; } static qint64 streamCurrentPosition(FILE *f) { - QT_OFF_T pos = QT_FTELL(f); - if (pos != -1) - return pos; + QT_STATBUF sb; + if (QT_FSTAT(QT_FILENO(f), &sb) != -1) { + QT_OFF_T pos = -1; + if ((sb.st_mode & QT_STAT_MASK) == QT_STAT_REG) + pos = QT_FTELL(f); + if (pos != -1) + return pos; + // failure to ftell() is not a problem + } else { + qErrnoWarning("Could not fstat fd %d", QT_FILENO(f)); + } return 0; } diff --git a/tests/auto/corelib/io/qfileinfo/tst_qfileinfo.cpp b/tests/auto/corelib/io/qfileinfo/tst_qfileinfo.cpp index 38db4c7b63..87d5675e7a 100644 --- a/tests/auto/corelib/io/qfileinfo/tst_qfileinfo.cpp +++ b/tests/auto/corelib/io/qfileinfo/tst_qfileinfo.cpp @@ -35,6 +35,7 @@ #include <qtemporarydir.h> #include <qdir.h> #include <qfileinfo.h> +#include <qstorageinfo.h> #ifdef Q_OS_UNIX #include <errno.h> #include <fcntl.h> @@ -47,7 +48,6 @@ #endif #ifdef Q_OS_WIN #include <qt_windows.h> -#include <qlibrary.h> #if !defined(Q_OS_WINRT) #include <lm.h> #endif @@ -73,83 +73,38 @@ bool IsUserAdmin(); # endif #endif -#if defined(Q_OS_UNIX) && !defined(Q_OS_VXWORKS) -inline bool qt_isEvilFsTypeName(const char *name) -{ - return (qstrncmp(name, "nfs", 3) == 0 - || qstrncmp(name, "autofs", 6) == 0 - || qstrncmp(name, "cachefs", 7) == 0); -} - -#if defined(Q_OS_BSD4) && !defined(Q_OS_NETBSD) -# include <sys/param.h> -# include <sys/mount.h> - -bool qIsLikelyToBeNfs(int handle) +inline bool qIsLikelyToBeFat(const QString &path) { - struct statfs buf; - if (fstatfs(handle, &buf) != 0) - return false; - return qt_isEvilFsTypeName(buf.f_fstypename); + QByteArray name = QStorageInfo(path).fileSystemType().toLower(); + return name.contains("fat") || name.contains("msdos"); } -#elif defined(Q_OS_LINUX) || defined(Q_OS_HURD) - -# include <sys/vfs.h> -# ifdef QT_LINUXBASE - // LSB 3.2 has fstatfs in sys/statfs.h, sys/vfs.h is just an empty dummy header -# include <sys/statfs.h> -# endif - -# ifndef NFS_SUPER_MAGIC -# define NFS_SUPER_MAGIC 0x00006969 -# endif -# ifndef AUTOFS_SUPER_MAGIC -# define AUTOFS_SUPER_MAGIC 0x00000187 -# endif -# ifndef AUTOFSNG_SUPER_MAGIC -# define AUTOFSNG_SUPER_MAGIC 0x7d92b1a0 -# endif - -bool qIsLikelyToBeNfs(int handle) +inline bool qIsLikelyToBeNfs(const QString &path) { - struct statfs buf; - if (fstatfs(handle, &buf) != 0) - return false; - return buf.f_type == NFS_SUPER_MAGIC - || buf.f_type == AUTOFS_SUPER_MAGIC - || buf.f_type == AUTOFSNG_SUPER_MAGIC; -} - -#elif defined(Q_OS_SOLARIS) || defined(Q_OS_IRIX) || defined(Q_OS_AIX) || defined(Q_OS_HPUX) \ - || defined(Q_OS_OSF) || defined(Q_OS_QNX) || defined(Q_OS_SCO) \ - || defined(Q_OS_UNIXWARE) || defined(Q_OS_RELIANT) || defined(Q_OS_NETBSD) - -# include <sys/statvfs.h> - -bool qIsLikelyToBeNfs(int handle) -{ - struct statvfs buf; - if (fstatvfs(handle, &buf) != 0) - return false; -#if defined(Q_OS_NETBSD) - return qt_isEvilFsTypeName(buf.f_fstypename); +#ifdef Q_OS_WIN + Q_UNUSED(path); + return false; #else - return qt_isEvilFsTypeName(buf.f_basetype); + QByteArray type = QStorageInfo(path).fileSystemType(); + const char *name = type.constData(); + + return (qstrncmp(name, "nfs", 3) == 0 + || qstrncmp(name, "autofs", 6) == 0 + || qstrncmp(name, "autofsng", 8) == 0 + || qstrncmp(name, "cachefs", 7) == 0); #endif } -#else -inline bool qIsLikelyToBeNfs(int /* handle */) -{ - return false; -} -#endif -#endif static QString seedAndTemplate() { - qsrand(QDateTime::currentSecsSinceEpoch()); - return QDir::tempPath() + "/tst_qfileinfo-XXXXXX"; + QString base; +#if defined(Q_OS_UNIX) && !defined(Q_OS_ANDROID) + // use XDG_RUNTIME_DIR as it's a fully-capable FS + base = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); +#endif + if (base.isEmpty()) + base = QDir::tempPath(); + return base + "/tst_qfileinfo-XXXXXX"; } static QByteArray msgDoesNotExist(const QString &name) @@ -285,8 +240,9 @@ private slots: #endif void group(); + void invalidState_data(); void invalidState(); - void nonExistingFileDates(); + void nonExistingFile(); private: const QString m_currentDir; @@ -736,18 +692,12 @@ void tst_QFileInfo::canonicalFilePath() #endif #if defined(Q_OS_WIN) && !defined(Q_OS_WINRT) - typedef BOOL (WINAPI *PtrCreateSymbolicLink)(LPTSTR, LPTSTR, DWORD); - PtrCreateSymbolicLink ptrCreateSymbolicLink = - (PtrCreateSymbolicLink)QLibrary::resolve(QLatin1String("kernel32"), "CreateSymbolicLinkW"); - - if (!ptrCreateSymbolicLink) { - QSKIP("Symbolic links aren't supported by FS"); - } else { + { // CreateSymbolicLink can return TRUE & still fail to create the link, // the error code in that case is ERROR_PRIVILEGE_NOT_HELD (1314) SetLastError(0); const QString linkTarget = QStringLiteral("res"); - BOOL ret = ptrCreateSymbolicLink((wchar_t*)linkTarget.utf16(), (wchar_t*)m_resourcesDir.utf16(), 1); + BOOL ret = CreateSymbolicLink((wchar_t*)linkTarget.utf16(), (wchar_t*)m_resourcesDir.utf16(), 1); DWORD dwErr = GetLastError(); if (!ret) QSKIP("Symbolic links aren't supported by FS"); @@ -1042,6 +992,11 @@ void tst_QFileInfo::systemFiles() QVERIFY2(fi.exists(), msgDoesNotExist(fi.absoluteFilePath()).constData()); QVERIFY(fi.size() > 0); QVERIFY(fi.lastModified().isValid()); + QVERIFY(fi.metadataChangeTime().isValid()); + QCOMPARE(fi.metadataChangeTime(), fi.lastModified()); // On Windows, they're the same + QVERIFY(fi.birthTime().isValid()); + QVERIFY(fi.birthTime() <= fi.lastModified()); + QCOMPARE(fi.created(), fi.birthTime()); // On Windows, they're the same } void tst_QFileInfo::compare_data() @@ -1132,43 +1087,92 @@ void tst_QFileInfo::fileTimes_data() void tst_QFileInfo::fileTimes() { - int sleepTime = 2000; + auto datePairString = [](const QDateTime &actual, const QDateTime &before) { + return (actual.toString(Qt::ISODateWithMs) + " (should be >) " + before.toString(Qt::ISODateWithMs)) + .toLatin1(); + }; + QFETCH(QString, fileName); + int sleepTime = 100; + + // on Linux and Windows, the filesystem timestamps may be slightly out of + // sync with the system clock (maybe they're using CLOCK_REALTIME_COARSE), + // so add a margin of error to our comparisons + int fsClockSkew = 10; +#ifdef Q_OS_WIN + fsClockSkew = 500; +#endif + + // NFS clocks may be WAY out of sync + if (qIsLikelyToBeNfs(fileName)) + QSKIP("This test doesn't work on NFS"); + + bool noAccessTime = false; + { + // try to guess if file times on this filesystem round to the second + QFileInfo cwd("."); + if (cwd.lastModified().toMSecsSinceEpoch() % 1000 == 0 + && cwd.lastRead().toMSecsSinceEpoch() % 1000 == 0) { + fsClockSkew = sleepTime = 1000; + + noAccessTime = qIsLikelyToBeFat(fileName); + if (noAccessTime) { + // FAT filesystems (but maybe not exFAT) store timestamps with 2-second + // granularity and access time with 1-day granularity + fsClockSkew = sleepTime = 2000; + } + } + } + if (QFile::exists(fileName)) { QVERIFY(QFile::remove(fileName)); } - QTest::qSleep(sleepTime); + + QDateTime beforeBirth, beforeWrite, beforeMetadataChange, beforeRead; + QDateTime birthTime, writeTime, metadataChangeTime, readTime; + + // --- Create file and write to it + beforeBirth = QDateTime::currentDateTime().addMSecs(-fsClockSkew); { QFile file(fileName); QVERIFY(file.open(QFile::WriteOnly | QFile::Text)); -#if defined(Q_OS_UNIX) && !defined(Q_OS_VXWORKS) - if (qIsLikelyToBeNfs(file.handle())) - QSKIP("This Test doesn't work on NFS"); -#endif + QFileInfo fileInfo(fileName); + birthTime = fileInfo.birthTime(); + QVERIFY2(!birthTime.isValid() || birthTime > beforeBirth, + datePairString(birthTime, beforeBirth)); + + QTest::qSleep(sleepTime); + beforeWrite = QDateTime::currentDateTime().addMSecs(-fsClockSkew); QTextStream ts(&file); ts << fileName << endl; } - QTest::qSleep(sleepTime); - QDateTime beforeWrite = QDateTime::currentDateTime(); - QTest::qSleep(sleepTime); { QFileInfo fileInfo(fileName); - QVERIFY(fileInfo.created() < beforeWrite); - QFile file(fileName); - QVERIFY(file.open(QFile::ReadWrite | QFile::Text)); - QTextStream ts(&file); - ts << fileName << endl; + writeTime = fileInfo.lastModified(); + QVERIFY2(writeTime > beforeWrite, datePairString(writeTime, beforeWrite)); + QCOMPARE(fileInfo.birthTime(), birthTime); // mustn't have changed } + + // --- Change the file's metadata QTest::qSleep(sleepTime); - QDateTime beforeRead = QDateTime::currentDateTime(); - QTest::qSleep(sleepTime); + beforeMetadataChange = QDateTime::currentDateTime().addMSecs(-fsClockSkew); + { + QFile file(fileName); + file.setPermissions(file.permissions()); + } { QFileInfo fileInfo(fileName); -// On unix created() returns the same as lastModified(). -#if !defined(Q_OS_UNIX) - QVERIFY(fileInfo.created() < beforeWrite); -#endif - QVERIFY(fileInfo.lastModified() > beforeWrite); + metadataChangeTime = fileInfo.metadataChangeTime(); + QVERIFY2(metadataChangeTime > beforeMetadataChange, + datePairString(metadataChangeTime, beforeMetadataChange)); + QVERIFY(metadataChangeTime >= writeTime); // not all filesystems can store both times + QCOMPARE(fileInfo.birthTime(), birthTime); // mustn't have changed + } + + // --- Read the file + QTest::qSleep(sleepTime); + beforeRead = QDateTime::currentDateTime().addMSecs(-fsClockSkew); + { QFile file(fileName); QVERIFY(file.open(QFile::ReadOnly | QFile::Text)); QTextStream ts(&file); @@ -1177,13 +1181,17 @@ void tst_QFileInfo::fileTimes() } QFileInfo fileInfo(fileName); -#if !defined(Q_OS_UNIX) - QVERIFY(fileInfo.created() < beforeWrite); -#endif + readTime = fileInfo.lastRead(); + QCOMPARE(fileInfo.lastModified(), writeTime); // mustn't have changed + QCOMPARE(fileInfo.birthTime(), birthTime); // mustn't have changed + QVERIFY(readTime.isValid()); + +#if defined(Q_OS_WINRT) || defined(Q_OS_QNX) || (defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED)) + noAccessTime = true; +#elif defined(Q_OS_WIN) //In Vista the last-access timestamp is not updated when the file is accessed/touched (by default). //To enable this the HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\NtfsDisableLastAccessUpdate //is set to 0, in the test machine. -#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT) HKEY key; if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\FileSystem", 0, KEY_READ, &key)) { @@ -1192,75 +1200,43 @@ void tst_QFileInfo::fileTimes() LONG error = RegQueryValueEx(key, L"NtfsDisableLastAccessUpdate" , NULL, NULL, (LPBYTE)&disabledAccessTimes, &size); if (ERROR_SUCCESS == error && disabledAccessTimes) - QEXPECT_FAIL("", "File access times are disabled in windows registry (this is the default setting)", Continue); + noAccessTime = true; RegCloseKey(key); } #endif -#if defined(Q_OS_WINRT) - QEXPECT_FAIL("", "WinRT does not allow timestamp handling change in the filesystem due to sandboxing", Continue); -#elif defined(Q_OS_QNX) - QEXPECT_FAIL("", "QNX uses the noatime filesystem option", Continue); -#elif defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED) - if (fileInfo.lastRead() <= beforeRead) - QEXPECT_FAIL("", "Android may use relatime or noatime on mounts", Continue); -#endif - QVERIFY(fileInfo.lastRead() > beforeRead); - QVERIFY(fileInfo.lastModified() > beforeWrite); - QVERIFY(fileInfo.lastModified() < beforeRead); + if (noAccessTime) + return; + + QVERIFY2(readTime > beforeRead, datePairString(readTime, beforeRead)); + QVERIFY(writeTime < beforeRead); } void tst_QFileInfo::fileTimes_oldFile() { - // This is not supported on WinRT -#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT) - // All files are opened in share mode (both read and write). - DWORD shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; - - // All files on Windows can be read; there's no such thing as an - // unreadable file. Add GENERIC_WRITE if WriteOnly is passed. - int accessRights = GENERIC_READ | GENERIC_WRITE; - - SECURITY_ATTRIBUTES securityAtts = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE }; - - // Regular file mode. In Unbuffered mode, pass the no-buffering flag. - DWORD flagsAndAtts = FILE_ATTRIBUTE_NORMAL; - - // WriteOnly can create files, ReadOnly cannot. - DWORD creationDisp = OPEN_ALWAYS; - - // Create the file handle. - HANDLE fileHandle = CreateFile(L"oldfile.txt", - accessRights, - shareMode, - &securityAtts, - creationDisp, - flagsAndAtts, - NULL); - - // Set file times back to 1601. - SYSTEMTIME stime; - stime.wYear = 1601; - stime.wMonth = 1; - stime.wDayOfWeek = 1; - stime.wDay = 1; - stime.wHour = 1; - stime.wMinute = 0; - stime.wSecond = 0; - stime.wMilliseconds = 0; - - FILETIME ctime; - QVERIFY(SystemTimeToFileTime(&stime, &ctime)); - FILETIME atime = ctime; - FILETIME mtime = atime; - QVERIFY(fileHandle); - QVERIFY(SetFileTime(fileHandle, &ctime, &atime, &mtime) != 0); + // This is 2^{31} seconds before 1970-01-01 15:14:8, + // i.e. shortly after the start of time_t, in any time-zone: + const QDateTime early(QDate(1901, 12, 14), QTime(12, 0)); + QFile file("ancientfile.txt"); + file.open(QIODevice::WriteOnly); + file.write("\n", 1); + file.close(); - CloseHandle(fileHandle); + /* + QFile's setFileTime calls QFSFileEngine::setFileTime() which fails unless + the file is open at the time. Of course, when writing, close() changes + modification time, so need to re-open for read in order to setFileTime(). + */ + file.open(QIODevice::ReadOnly); + bool ok = file.setFileTime(early, QFileDevice::FileModificationTime); + file.close(); - QFileInfo info("oldfile.txt"); - QCOMPARE(info.lastModified(), QDateTime(QDate(1601, 1, 1), QTime(1, 0), Qt::UTC).toLocalTime()); -#endif + if (ok) { + QFileInfo info(file.fileName()); + QCOMPARE(info.lastModified(), early); + } else { + QSKIP("Unable to set file metadata to ancient values"); + } } void tst_QFileInfo::isSymLink_data() @@ -1400,7 +1376,7 @@ void tst_QFileInfo::isNativePath_data() QTest::addColumn<bool>("isNativePath"); QTest::newRow("default-constructed") << QString() << false; - QTest::newRow("empty") << QString("") << true; + QTest::newRow("empty") << QString("") << false; QTest::newRow("local root") << QString::fromLatin1("/") << true; QTest::newRow("local non-existent file") << QString::fromLatin1("/abrakadabra.boo") << true; @@ -1469,16 +1445,6 @@ void tst_QFileInfo::ntfsJunctionPointsAndSymlinks_data() QDir pwd; pwd.mkdir("target"); - QLibrary kernel32("kernel32"); - typedef BOOLEAN (WINAPI *PtrCreateSymbolicLink)(LPCWSTR, LPCWSTR, DWORD); - PtrCreateSymbolicLink createSymbolicLinkW = 0; - createSymbolicLinkW = (PtrCreateSymbolicLink) kernel32.resolve("CreateSymbolicLinkW"); - if (!createSymbolicLinkW) { - //we need at least one data set for the test not to fail when skipping _data function - QDir target("target"); - QTest::newRow("dummy") << target.path() << false << "" << target.canonicalPath(); - QSKIP("symbolic links not supported by operating system"); - } { //Directory symlinks QDir target("target"); @@ -1498,10 +1464,10 @@ void tst_QFileInfo::ntfsJunctionPointsAndSymlinks_data() DWORD err = ERROR_SUCCESS ; if (!pwd.exists("abs_symlink")) - if (!createSymbolicLinkW((wchar_t*)absSymlink.utf16(),(wchar_t*)absTarget.utf16(),0x1)) + if (!CreateSymbolicLink((wchar_t*)absSymlink.utf16(),(wchar_t*)absTarget.utf16(),0x1)) err = GetLastError(); if (err == ERROR_SUCCESS && !pwd.exists(relSymlink)) - if (!createSymbolicLinkW((wchar_t*)relSymlink.utf16(),(wchar_t*)relTarget.utf16(),0x1)) + if (!CreateSymbolicLink((wchar_t*)relSymlink.utf16(),(wchar_t*)relTarget.utf16(),0x1)) err = GetLastError(); if (err != ERROR_SUCCESS) { wchar_t errstr[0x100]; @@ -1531,10 +1497,9 @@ void tst_QFileInfo::ntfsJunctionPointsAndSymlinks_data() QString relSymlink = "rel_symlink.cpp"; QString relToRelTarget = QDir::toNativeSeparators(relativeDir.relativeFilePath(target.absoluteFilePath())); QString relToRelSymlink = "relative/rel_symlink"; - QVERIFY(pwd.exists("abs_symlink.cpp") || createSymbolicLinkW((wchar_t*)absSymlink.utf16(),(wchar_t*)absTarget.utf16(),0x0)); - QVERIFY(pwd.exists(relSymlink) || createSymbolicLinkW((wchar_t*)relSymlink.utf16(),(wchar_t*)relTarget.utf16(),0x0)); - QVERIFY(pwd.exists(relToRelSymlink) - || createSymbolicLinkW((wchar_t*)relToRelSymlink.utf16(), (wchar_t*)relToRelTarget.utf16(),0x0)); + QVERIFY(pwd.exists("abs_symlink.cpp") || CreateSymbolicLink((wchar_t*)absSymlink.utf16(),(wchar_t*)absTarget.utf16(),0x0)); + QVERIFY(pwd.exists(relSymlink) || CreateSymbolicLink((wchar_t*)relSymlink.utf16(),(wchar_t*)relTarget.utf16(),0x0)); + QVERIFY(pwd.exists(relToRelSymlink) || CreateSymbolicLink((wchar_t*)relToRelSymlink.utf16(), (wchar_t*)relToRelTarget.utf16(),0x0)); QTest::newRow("absolute file symlink") << absSymlink << true << QDir::fromNativeSeparators(absTarget) << target.canonicalFilePath(); QTest::newRow("relative file symlink") << relSymlink << true << QDir::fromNativeSeparators(absTarget) << target.canonicalFilePath(); QTest::newRow("relative to relative file symlink") << relToRelSymlink << true << QDir::fromNativeSeparators(absTarget) << target.canonicalFilePath(); @@ -1561,20 +1526,14 @@ void tst_QFileInfo::ntfsJunctionPointsAndSymlinks_data() QTest::newRow("junction_root") << junction << false << QString() << QString(); //Mountpoint - typedef BOOLEAN (WINAPI *PtrGetVolumeNameForVolumeMountPointW)(LPCWSTR, LPWSTR, DWORD); - PtrGetVolumeNameForVolumeMountPointW getVolumeNameForVolumeMountPointW = 0; - getVolumeNameForVolumeMountPointW = (PtrGetVolumeNameForVolumeMountPointW) kernel32.resolve("GetVolumeNameForVolumeMountPointW"); - if(getVolumeNameForVolumeMountPointW) - { - wchar_t buffer[MAX_PATH]; - QString rootPath = QDir::toNativeSeparators(QDir::rootPath()); - QVERIFY(getVolumeNameForVolumeMountPointW((wchar_t*)rootPath.utf16(), buffer, MAX_PATH)); - QString rootVolume = QString::fromWCharArray(buffer); - junction = "mountpoint"; - rootVolume.replace("\\\\?\\","\\??\\"); - FileSystem::createNtfsJunction(rootVolume, junction); - QTest::newRow("mountpoint") << junction << false << QString() << QString(); - } + wchar_t buffer[MAX_PATH]; + QString rootPath = QDir::toNativeSeparators(QDir::rootPath()); + QVERIFY(GetVolumeNameForVolumeMountPoint((wchar_t*)rootPath.utf16(), buffer, MAX_PATH)); + QString rootVolume = QString::fromWCharArray(buffer); + junction = "mountpoint"; + rootVolume.replace("\\\\?\\","\\??\\"); + FileSystem::createNtfsJunction(rootVolume, junction); + QTest::newRow("mountpoint") << junction << false << QString() << QString(); } void tst_QFileInfo::ntfsJunctionPointsAndSymlinks() @@ -1941,56 +1900,96 @@ void tst_QFileInfo::group() QCOMPARE(fi.group(), expected); } -void tst_QFileInfo::invalidState() +static void stateCheck(const QFileInfo &info, const QString &dirname, const QString &filename) { - // Shouldn't crash; - - { - QFileInfo info; - QCOMPARE(info.size(), qint64(0)); - QVERIFY(!info.exists()); - - info.setCaching(false); + QCOMPARE(info.size(), qint64(0)); + QVERIFY(!info.exists()); - info.created(); - info.lastRead(); - info.lastModified(); + QString path; + QString abspath; + if (!dirname.isEmpty()) { + path = "."; + abspath = dirname + '/' + filename; } - { - QFileInfo info(""); - QCOMPARE(info.size(), qint64(0)); - QVERIFY(!info.exists()); - - info.setCaching(false); + QCOMPARE(info.filePath(), filename); + QCOMPARE(info.absoluteFilePath(), abspath); + QCOMPARE(info.canonicalFilePath(), QString()); + QCOMPARE(info.fileName(), filename); + QCOMPARE(info.baseName(), filename); + QCOMPARE(info.completeBaseName(), filename); + QCOMPARE(info.suffix(), QString()); + QCOMPARE(info.bundleName(), QString()); + QCOMPARE(info.completeSuffix(), QString()); + + QVERIFY(info.isRelative()); + QCOMPARE(info.path(), path); + QCOMPARE(info.absolutePath(), dirname); + QCOMPARE(info.dir().path(), "."); + + // these don't look right + QCOMPARE(info.canonicalPath(), path); + QCOMPARE(info.absoluteDir().path(), dirname.isEmpty() ? "." : dirname); + + QVERIFY(!info.isReadable()); + QVERIFY(!info.isWritable()); + QVERIFY(!info.isExecutable()); + QVERIFY(!info.isHidden()); + QVERIFY(!info.isFile()); + QVERIFY(!info.isDir()); + QVERIFY(!info.isSymLink()); + QVERIFY(!info.isBundle()); + QVERIFY(!info.isRoot()); + QCOMPARE(info.isNativePath(), !filename.isEmpty()); + + QCOMPARE(info.readLink(), QString()); + QCOMPARE(info.ownerId(), uint(-2)); + QCOMPARE(info.groupId(), uint(-2)); + QCOMPARE(info.owner(), QString()); + QCOMPARE(info.group(), QString()); + + QCOMPARE(info.permissions(), QFile::Permissions()); - info.created(); - info.lastRead(); - info.lastModified(); - } + QVERIFY(!info.created().isValid()); + QVERIFY(!info.birthTime().isValid()); + QVERIFY(!info.metadataChangeTime().isValid()); + QVERIFY(!info.lastRead().isValid()); + QVERIFY(!info.lastModified().isValid()); +}; - { - QFileInfo info("file-doesn't-really-exist.txt"); - QCOMPARE(info.size(), qint64(0)); - QVERIFY(!info.exists()); +void tst_QFileInfo::invalidState_data() +{ + QTest::addColumn<int>("mode"); + QTest::newRow("default") << 0; + QTest::newRow("empty") << 1; + QTest::newRow("copy-of-default") << 2; + QTest::newRow("copy-of-empty") << 3; +} - info.setCaching(false); +void tst_QFileInfo::invalidState() +{ + // Shouldn't crash or produce warnings + QFETCH(int, mode); + const QFileInfo &info = (mode & 1 ? QFileInfo("") : QFileInfo()); - info.created(); - info.lastRead(); - info.lastModified(); + if (mode & 2) { + QFileInfo copy(info); + stateCheck(copy, QString(), QString()); + } else { + stateCheck(info, QString(), QString()); } - - QVERIFY(true); } -void tst_QFileInfo::nonExistingFileDates() +void tst_QFileInfo::nonExistingFile() { - QFileInfo info("non-existing-file.foobar"); - QVERIFY(!info.exists()); - QVERIFY(!info.created().isValid()); - QVERIFY(!info.lastRead().isValid()); - QVERIFY(!info.lastModified().isValid()); + QString dirname = QDir::currentPath(); + QString cdirname = QFileInfo(dirname).canonicalFilePath(); + if (dirname != cdirname) + QDir::setCurrent(cdirname); // chdir() to our canonical path + + QString filename = "non-existing-file-foobar"; + QFileInfo info(filename); + stateCheck(info, dirname, filename); } QTEST_MAIN(tst_QFileInfo) diff --git a/tests/auto/corelib/io/qfilesystemmetadata/tst_qfilesystemmetadata.cpp b/tests/auto/corelib/io/qfilesystemmetadata/tst_qfilesystemmetadata.cpp index 1c104688a5..ea4da39cd5 100644 --- a/tests/auto/corelib/io/qfilesystemmetadata/tst_qfilesystemmetadata.cpp +++ b/tests/auto/corelib/io/qfilesystemmetadata/tst_qfilesystemmetadata.cpp @@ -66,14 +66,16 @@ void tst_QFileSystemMetaData::timeSinceEpoch() /* data.ftLastAccessTime = data.ftLastWriteTime = */ data.ftCreationTime = epochToFileTime(afterEpochUtc); meta.fillFromFindData(data); + QCOMPARE(meta.birthTime().toUTC(), + QDateTime::fromMSecsSinceEpoch(afterEpochUtc * qint64(1000), Qt::UTC)); #else QT_STATBUF data; memset(&data, 0, sizeof(data)); data.st_ctime = afterEpochUtc; meta.fillFromStatBuf(data); -#endif - QCOMPARE(meta.creationTime().toUTC(), + QCOMPARE(meta.metadataChangeTime().toUTC(), QDateTime::fromMSecsSinceEpoch(afterEpochUtc * qint64(1000), Qt::UTC)); +#endif } #else // i.e. no Q_AUTOTEST_EXPORT void tst_QFileSystemMetaData::timeSinceEpoch() diff --git a/tests/auto/corelib/io/qiodevice/tst_qiodevice.cpp b/tests/auto/corelib/io/qiodevice/tst_qiodevice.cpp index 9e0b9c2329..618b7f338e 100644 --- a/tests/auto/corelib/io/qiodevice/tst_qiodevice.cpp +++ b/tests/auto/corelib/io/qiodevice/tst_qiodevice.cpp @@ -55,6 +55,10 @@ private slots: void readAllKeepPosition(); void writeInTextMode(); + void skip_data(); + void skip(); + void skipAfterPeek_data(); + void skipAfterPeek(); void transaction_data(); void transaction(); @@ -628,6 +632,93 @@ void tst_QIODevice::writeInTextMode() #endif } +void tst_QIODevice::skip_data() +{ + QTest::addColumn<bool>("sequential"); + QTest::addColumn<QByteArray>("data"); + QTest::addColumn<int>("read"); + QTest::addColumn<int>("skip"); + QTest::addColumn<int>("skipped"); + QTest::addColumn<char>("expect"); + + QByteArray bigData; + bigData.fill('a', 20000); + bigData[10001] = 'x'; + + bool sequential = true; + do { + QByteArray devName(sequential ? "sequential" : "random-access"); + + QTest::newRow(qPrintable(devName + "-small_data")) << true << QByteArray("abcdefghij") + << 3 << 6 << 6 << 'j'; + QTest::newRow(qPrintable(devName + "-big_data")) << true << bigData + << 1 << 10000 << 10000 << 'x'; + QTest::newRow(qPrintable(devName + "-beyond_the_end")) << true << bigData + << 1 << 20000 << 19999 << '\0'; + + sequential = !sequential; + } while (!sequential); +} + +void tst_QIODevice::skip() +{ + QFETCH(bool, sequential); + QFETCH(QByteArray, data); + QFETCH(int, read); + QFETCH(int, skip); + QFETCH(int, skipped); + QFETCH(char, expect); + char lastChar = 0; + + QScopedPointer<QIODevice> dev(sequential ? (QIODevice *) new SequentialReadBuffer(&data) + : (QIODevice *) new QBuffer(&data)); + dev->open(QIODevice::ReadOnly); + + for (int i = 0; i < read; ++i) + dev->getChar(nullptr); + + QCOMPARE(dev->skip(skip), skipped); + dev->getChar(&lastChar); + QCOMPARE(lastChar, expect); +} + +void tst_QIODevice::skipAfterPeek_data() +{ + QTest::addColumn<bool>("sequential"); + QTest::addColumn<QByteArray>("data"); + + QByteArray bigData; + for (int i = 0; i < 1000; ++i) + bigData += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + QTest::newRow("sequential") << true << bigData; + QTest::newRow("random-access") << false << bigData; +} + +void tst_QIODevice::skipAfterPeek() +{ + QFETCH(bool, sequential); + QFETCH(QByteArray, data); + + QScopedPointer<QIODevice> dev(sequential ? (QIODevice *) new SequentialReadBuffer(&data) + : (QIODevice *) new QBuffer(&data)); + int readSoFar = 0; + qint64 bytesToSkip = 1; + + dev->open(QIODevice::ReadOnly); + forever { + QByteArray chunk = dev->peek(bytesToSkip); + if (chunk.isEmpty()) + break; + + QCOMPARE(dev->skip(bytesToSkip), qint64(chunk.size())); + QCOMPARE(chunk, data.mid(readSoFar, chunk.size())); + readSoFar += chunk.size(); + bytesToSkip <<= 1; + } + QCOMPARE(readSoFar, data.size()); +} + void tst_QIODevice::transaction_data() { QTest::addColumn<bool>("sequential"); diff --git a/tests/auto/corelib/io/qlockfile/qlockfiletesthelper/qlockfile_test_helper.cpp b/tests/auto/corelib/io/qlockfile/qlockfiletesthelper/qlockfile_test_helper.cpp index f10c2a7c98..e086bf1904 100644 --- a/tests/auto/corelib/io/qlockfile/qlockfiletesthelper/qlockfile_test_helper.cpp +++ b/tests/auto/corelib/io/qlockfile/qlockfiletesthelper/qlockfile_test_helper.cpp @@ -31,6 +31,12 @@ #include <QLockFile> #include <QThread> +#ifdef Q_OS_UNIX +# include <unistd.h> +#else +# include <stdlib.h> +#endif + int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); @@ -44,11 +50,11 @@ int main(int argc, char *argv[]) if (argc > 2) option = QString::fromLocal8Bit(argv[2]); - if (option == "-crash") { + if (option == "-uncleanexit") { QLockFile lockFile(lockName); lockFile.lock(); // exit on purpose, so that the lock remains! - exit(0); + _exit(0); } else if (option == "-busy") { QLockFile lockFile(lockName); lockFile.lock(); diff --git a/tests/auto/corelib/io/qlockfile/tst_qlockfile.cpp b/tests/auto/corelib/io/qlockfile/tst_qlockfile.cpp index 59196dc40b..be523c8c0d 100644 --- a/tests/auto/corelib/io/qlockfile/tst_qlockfile.cpp +++ b/tests/auto/corelib/io/qlockfile/tst_qlockfile.cpp @@ -262,7 +262,7 @@ void tst_QLockFile::staleLockFromCrashedProcess() QFETCH(int, staleLockTime); const QString fileName = dir.path() + "/staleLockFromCrashedProcess"; - int ret = QProcess::execute(m_helperApp, QStringList() << fileName << "-crash"); + int ret = QProcess::execute(m_helperApp, QStringList() << fileName << "-uncleanexit"); QCOMPARE(ret, int(QLockFile::NoError)); QTRY_VERIFY(QFile::exists(fileName)); @@ -288,7 +288,7 @@ void tst_QLockFile::staleLockFromCrashedProcessReusedPid() #else const QString fileName = dir.path() + "/staleLockFromCrashedProcessReusedPid"; - int ret = QProcess::execute(m_helperApp, QStringList() << fileName << "-crash"); + int ret = QProcess::execute(m_helperApp, QStringList() << fileName << "-uncleanexit"); QCOMPARE(ret, int(QLockFile::NoError)); QVERIFY(QFile::exists(fileName)); QVERIFY(overwritePidInLockFile(fileName, QCoreApplication::applicationPid())); @@ -397,7 +397,7 @@ void tst_QLockFile::staleLockRace() // Only one thread should delete it, otherwise a race will ensue const QString fileName = dir.path() + "/sharedFile"; const QString lockName = fileName + ".lock"; - int ret = QProcess::execute(m_helperApp, QStringList() << lockName << "-crash"); + int ret = QProcess::execute(m_helperApp, QStringList() << lockName << "-uncleanexit"); QCOMPARE(ret, int(QLockFile::NoError)); QTRY_VERIFY(QFile::exists(lockName)); diff --git a/tests/auto/corelib/io/qnodebug/tst_qnodebug.cpp b/tests/auto/corelib/io/qnodebug/tst_qnodebug.cpp index b0acb1c58d..b78fa29fb6 100644 --- a/tests/auto/corelib/io/qnodebug/tst_qnodebug.cpp +++ b/tests/auto/corelib/io/qnodebug/tst_qnodebug.cpp @@ -64,7 +64,7 @@ void tst_QNoDebug::noDebugOutput() const void tst_QNoDebug::streaming() const { QDateTime dt(QDate(1,2,3),QTime(4,5,6)); - const QByteArray debugString = dt.toString(QStringLiteral("yyyy-MM-dd HH:mm:ss.zzz t")).toLatin1(); + const QByteArray debugString = dt.toString(QStringViewLiteral("yyyy-MM-dd HH:mm:ss.zzz t")).toLatin1(); const QByteArray message = "QDateTime(" + debugString + " Qt::TimeSpec(LocalTime))"; QTest::ignoreMessage(QtWarningMsg, message.constData()); qWarning() << dt; diff --git a/tests/auto/corelib/io/qprocess/testDetached/main.cpp b/tests/auto/corelib/io/qprocess/testDetached/main.cpp index 702cabe873..c10e32d584 100644 --- a/tests/auto/corelib/io/qprocess/testDetached/main.cpp +++ b/tests/auto/corelib/io/qprocess/testDetached/main.cpp @@ -40,32 +40,89 @@ #include <windows.h> #endif +static void writeStuff(QFile &f) +{ + f.write(QDir::currentPath().toUtf8()); + f.putChar('\n'); +#if defined(Q_OS_UNIX) + f.write(QByteArray::number(quint64(getpid()))); +#elif defined(Q_OS_WIN) + f.write(QByteArray::number(quint64(GetCurrentProcessId()))); +#endif + f.putChar('\n'); + f.write(qgetenv("tst_QProcess")); + f.putChar('\n'); +} + +struct Args +{ + int exitCode = 0; + QByteArray errorMessage; + QString fileName; + FILE *channel = nullptr; + QByteArray channelName; +}; + +static Args parseArguments(const QStringList &args) +{ + Args result; + if (args.count() < 2) { + result.exitCode = 128; + result.errorMessage = "Usage: testDetached [--out-channel={stdout|stderr}] filename.txt\n"; + return result; + } + for (const QString &arg : args) { + if (arg.startsWith("--")) { + if (!arg.startsWith("--out-channel=")) { + result.exitCode = 2; + result.errorMessage = "Unknown argument " + arg.toLocal8Bit(); + return result; + } + result.channelName = arg.mid(14).toLocal8Bit(); + if (result.channelName == "stdout") { + result.channel = stdout; + } else if (result.channelName == "stderr") { + result.channel = stderr; + } else { + result.exitCode = 3; + result.errorMessage = "Unknown channel " + result.channelName; + return result; + } + } else { + result.fileName = arg; + } + } + return result; +} + int main(int argc, char **argv) { QCoreApplication app(argc, argv); - QStringList args = app.arguments(); - if (args.count() != 2) { - fprintf(stderr, "Usage: testDetached filename.txt\n"); - return 128; + const Args args = parseArguments(app.arguments()); + if (args.exitCode) { + fprintf(stderr, "testDetached: %s\n", args.errorMessage.constData()); + return args.exitCode; } - QFile f(args.at(1)); + if (args.channel) { + QFile channel; + if (!channel.open(args.channel, QIODevice::WriteOnly | QIODevice::Text)) { + fprintf(stderr, "Cannot open channel %s for writing: %s\n", + qPrintable(args.channelName), qPrintable(channel.errorString())); + return 4; + } + writeStuff(channel); + } + + QFile f(args.fileName); if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { fprintf(stderr, "Cannot open %s for writing: %s\n", qPrintable(f.fileName()), qPrintable(f.errorString())); return 1; } - f.write(QDir::currentPath().toUtf8()); - f.putChar('\n'); -#if defined(Q_OS_UNIX) - f.write(QByteArray::number(quint64(getpid()))); -#elif defined(Q_OS_WIN) - f.write(QByteArray::number(quint64(GetCurrentProcessId()))); -#endif - f.putChar('\n'); - + writeStuff(f); f.close(); return 0; diff --git a/tests/auto/corelib/io/qprocess/tst_qprocess.cpp b/tests/auto/corelib/io/qprocess/tst_qprocess.cpp index f4d6d5cb40..de6eb28503 100644 --- a/tests/auto/corelib/io/qprocess/tst_qprocess.cpp +++ b/tests/auto/corelib/io/qprocess/tst_qprocess.cpp @@ -127,7 +127,8 @@ private slots: void systemEnvironment(); void lockupsInStartDetached(); void waitForReadyReadForNonexistantProcess(); - void detachedWorkingDirectoryAndPid(); + void detachedProcessParameters_data(); + void detachedProcessParameters(); void startFinishStartFinish(); void invalidProgramString_data(); void invalidProgramString(); @@ -2072,21 +2073,54 @@ void tst_QProcess::fileWriterProcess() } while (stopWatch.elapsed() < 3000); } -void tst_QProcess::detachedWorkingDirectoryAndPid() +void tst_QProcess::detachedProcessParameters_data() { + QTest::addColumn<QString>("outChannel"); + QTest::newRow("none") << QString(); + QTest::newRow("stdout") << QString("stdout"); + QTest::newRow("stderr") << QString("stderr"); +} + +void tst_QProcess::detachedProcessParameters() +{ + QFETCH(QString, outChannel); qint64 pid; QFile infoFile(m_temporaryDir.path() + QLatin1String("/detachedinfo.txt")); if (infoFile.exists()) QVERIFY(infoFile.remove()); + QFile channelFile(m_temporaryDir.path() + QLatin1String("detachedinfo2.txt")); + if (channelFile.exists()) + QVERIFY(channelFile.remove()); QString workingDir = QDir::currentPath() + "/testDetached"; QVERIFY(QFile::exists(workingDir)); - QStringList args; - args << infoFile.fileName(); - QVERIFY(QProcess::startDetached(QDir::currentPath() + QLatin1String("/testDetached/testDetached"), args, workingDir, &pid)); + QVERIFY(qgetenv("tst_QProcess").isEmpty()); + QByteArray envVarValue("foobarbaz"); + QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); + environment.insert(QStringLiteral("tst_QProcess"), QString::fromUtf8(envVarValue)); + + QProcess process; + process.setProgram(QDir::currentPath() + QLatin1String("/testDetached/testDetached")); +#ifdef Q_OS_WIN + int modifierCalls = 0; + process.setCreateProcessArgumentsModifier( + [&modifierCalls] (QProcess::CreateProcessArguments *) { modifierCalls++; }); +#endif + QStringList args(infoFile.fileName()); + if (!outChannel.isEmpty()) { + args << QStringLiteral("--out-channel=") + outChannel; + if (outChannel == "stdout") + process.setStandardOutputFile(channelFile.fileName()); + else if (outChannel == "stderr") + process.setStandardErrorFile(channelFile.fileName()); + } + process.setArguments(args); + process.setWorkingDirectory(workingDir); + process.setProcessEnvironment(environment); + QVERIFY(process.startDetached(&pid)); QFileInfo fi(infoFile); fi.setCaching(false); @@ -2097,19 +2131,35 @@ void tst_QProcess::detachedWorkingDirectoryAndPid() } QVERIFY(infoFile.open(QIODevice::ReadOnly | QIODevice::Text)); - QString actualWorkingDir = QString::fromUtf8(infoFile.readLine()); - actualWorkingDir.chop(1); // strip off newline - QByteArray processIdString = infoFile.readLine(); - processIdString.chop(1); + QString actualWorkingDir = QString::fromUtf8(infoFile.readLine()).trimmed(); + QByteArray processIdString = infoFile.readLine().trimmed(); + QByteArray actualEnvVarValue = infoFile.readLine().trimmed(); + QByteArray infoFileContent; + if (!outChannel.isEmpty()) { + infoFile.seek(0); + infoFileContent = infoFile.readAll(); + } infoFile.close(); infoFile.remove(); + if (!outChannel.isEmpty()) { + QVERIFY(channelFile.open(QIODevice::ReadOnly | QIODevice::Text)); + QByteArray channelContent = channelFile.readAll(); + channelFile.close(); + channelFile.remove(); + QCOMPARE(channelContent, infoFileContent); + } + bool ok = false; qint64 actualPid = processIdString.toLongLong(&ok); QVERIFY(ok); QCOMPARE(actualWorkingDir, workingDir); QCOMPARE(actualPid, pid); + QCOMPARE(actualEnvVarValue, envVarValue); +#ifdef Q_OS_WIN + QCOMPARE(modifierCalls, 1); +#endif } void tst_QProcess::switchReadChannels() diff --git a/tests/auto/corelib/io/qsavefile/tst_qsavefile.cpp b/tests/auto/corelib/io/qsavefile/tst_qsavefile.cpp index 9573f3078b..96970421d3 100644 --- a/tests/auto/corelib/io/qsavefile/tst_qsavefile.cpp +++ b/tests/auto/corelib/io/qsavefile/tst_qsavefile.cpp @@ -82,6 +82,11 @@ private slots: void transactionalWriteErrorRenaming(); void symlink(); void directory(); + +#ifdef Q_OS_WIN + void alternateDataStream_data(); + void alternateDataStream(); +#endif }; static inline QByteArray msgCannotOpen(const QFileDevice &f) @@ -474,5 +479,60 @@ void tst_QSaveFile::directory() #endif } +#ifdef Q_OS_WIN +void tst_QSaveFile::alternateDataStream_data() +{ + QTest::addColumn<bool>("directWriteFallback"); + QTest::addColumn<bool>("success"); + + QTest::newRow("default") << false << false; + QTest::newRow("directWriteFallback") << true << true; +} + +void tst_QSaveFile::alternateDataStream() +{ + QFETCH(bool, directWriteFallback); + QFETCH(bool, success); + static const char newContent[] = "New content\r\n"; + + QTemporaryDir dir; + QVERIFY2(dir.isValid(), qPrintable(dir.errorString())); + QString baseName = dir.path() + QLatin1String("/base"); + { + QFile baseFile(baseName); + QVERIFY2(baseFile.open(QIODevice::ReadWrite), qPrintable(baseFile.errorString())); + } + + // First, create a file with old content + QString adsName = baseName + QLatin1String(":outfile"); + { + QFile targetFile(adsName); + if (!targetFile.open(QIODevice::ReadWrite)) + QSKIP("Failed to ceate ADS file (" + targetFile.errorString().toUtf8() + + "). Temp dir is FAT?"); + targetFile.write("Old content\r\n"); + } + + // And write to it again using QSaveFile; only works if directWriteFallback is enabled + QSaveFile file(adsName); + file.setDirectWriteFallback(directWriteFallback); + QCOMPARE(file.directWriteFallback(), directWriteFallback); + + if (success) { + QVERIFY2(file.open(QIODevice::WriteOnly), qPrintable(file.errorString())); + file.write(newContent); + QVERIFY2(file.commit(), qPrintable(file.errorString())); + + // check the contents + QFile targetFile(adsName); + QVERIFY2(targetFile.open(QIODevice::ReadOnly), qPrintable(targetFile.errorString())); + QByteArray contents = targetFile.readAll(); + QCOMPARE(contents, QByteArray(newContent)); + } else { + QVERIFY(!file.open(QIODevice::WriteOnly)); + } +} +#endif + QTEST_MAIN(tst_QSaveFile) #include "tst_qsavefile.moc" diff --git a/tests/auto/corelib/io/qsettings/tst_qsettings.cpp b/tests/auto/corelib/io/qsettings/tst_qsettings.cpp index 332c2dcc01..012ce5f2f5 100644 --- a/tests/auto/corelib/io/qsettings/tst_qsettings.cpp +++ b/tests/auto/corelib/io/qsettings/tst_qsettings.cpp @@ -118,6 +118,10 @@ private slots: void remove(); void contains(); void sync(); + void syncNonWriteableDir(); +#ifdef Q_OS_WIN + void syncAlternateDataStream(); +#endif void setFallbacksEnabled(); void setFallbacksEnabled_data(); void fromFile_data(); @@ -1750,6 +1754,78 @@ void tst_QSettings::sync() QCOMPARE(settings1.allKeys().count(), 11); } +void tst_QSettings::syncNonWriteableDir() +{ + QTemporaryDir tempDir; + QVERIFY2(tempDir.isValid(), qUtf8Printable(tempDir.errorString())); + + // first, create a file + QString filename = tempDir.path() + "/config.ini"; + { + QFile f(filename); + QVERIFY2(f.open(QIODevice::WriteOnly), qUtf8Printable(f.errorString())); + } + + // second, make the dir unwriteable + QVERIFY(QFile::setPermissions(tempDir.path(), QFile::ReadUser | QFile::ExeUser)); + struct UndoSetPermissions { + QString name; + UndoSetPermissions(const QString &name) : name(name) {} + ~UndoSetPermissions() + { QFile::setPermissions(name, QFile::ReadUser | QFile::WriteUser | QFile::ExeUser); } + }; + UndoSetPermissions undo(tempDir.path()); // otherwise, QTemporaryDir will fail + + { + QSettings settings(filename, QSettings::IniFormat); + QVERIFY(settings.isAtomicSyncRequired()); + settings.setAtomicSyncRequired(false); + settings.setValue("alpha/beta", 1); + settings.sync(); + QCOMPARE(settings.status(), QSettings::NoError); + } + + QVERIFY(QFileInfo(filename).size() != 0); + QSettings settings(filename, QSettings::IniFormat); + QCOMPARE(settings.value("alpha/beta"), QVariant(1)); +} + +#ifdef Q_OS_WIN +void tst_QSettings::syncAlternateDataStream() +{ + QTemporaryDir tempDir; + QVERIFY2(tempDir.isValid(), qUtf8Printable(tempDir.errorString())); + + // first, create a file + QString filename = tempDir.path() + "/file"; + { + QFile f(filename); + QVERIFY2(f.open(QIODevice::WriteOnly), qUtf8Printable(f.errorString())); + } + + // then create an ADS + filename += ":config.ini"; + { + QFile f(filename); + if (!f.open(QIODevice::WriteOnly)) + QSKIP("Could not create ADS file (" + f.errorString().toUtf8() + ") - FAT drive?"); + } + + { + QSettings settings(filename, QSettings::IniFormat); + QVERIFY(settings.isAtomicSyncRequired()); + settings.setAtomicSyncRequired(false); + settings.setValue("alpha/beta", 1); + settings.sync(); + QCOMPARE(settings.status(), QSettings::NoError); + } + + QVERIFY(QFileInfo(filename).size() != 0); + QSettings settings(filename, QSettings::IniFormat); + QCOMPARE(settings.value("alpha/beta"), QVariant(1)); +} +#endif + void tst_QSettings::setFallbacksEnabled_data() { populateWithFormats(); diff --git a/tests/auto/corelib/io/qstandardpaths/BLACKLIST b/tests/auto/corelib/io/qstandardpaths/BLACKLIST new file mode 100644 index 0000000000..d5ee9650cd --- /dev/null +++ b/tests/auto/corelib/io/qstandardpaths/BLACKLIST @@ -0,0 +1,3 @@ +[testFindExecutable] +# QTBUG-64404 +b2qt 64bit diff --git a/tests/auto/corelib/io/qtemporarydir/tst_qtemporarydir.cpp b/tests/auto/corelib/io/qtemporarydir/tst_qtemporarydir.cpp index 21936faca5..6dbb8ddd0d 100644 --- a/tests/auto/corelib/io/qtemporarydir/tst_qtemporarydir.cpp +++ b/tests/auto/corelib/io/qtemporarydir/tst_qtemporarydir.cpp @@ -139,25 +139,24 @@ void tst_QTemporaryDir::fileTemplate_data() { QTest::addColumn<QString>("constructorTemplate"); QTest::addColumn<QString>("prefix"); + QTest::addColumn<QString>("suffix"); + + QTest::newRow("default") << "" << "tst_qtemporarydir-" << ""; - QTest::newRow("constructor default") << "" << "tst_qtemporarydir-"; - - QTest::newRow("constructor with xxx sufix") << "qt_XXXXXXxxx" << "qt_XXXXXXxxx"; - QTest::newRow("constructor with xXx sufix") << "qt_XXXXXXxXx" << "qt_XXXXXXxXx"; - QTest::newRow("constructor with no suffix") << "qt_XXXXXX" << "qt_"; - QTest::newRow("constructor with >6 X's, no suffix") << "qt_XXXXXXXXXX" << "qt_"; - // When more than 6 X are present at the end, linux and windows will only replace the last 6, - // while Mac OS will actually replace all of them so we can only expect "qt_" (and check isValid). - QTest::newRow("constructor with XXXX suffix") << "qt_XXXXXX_XXXX" << "qt_"; - QTest::newRow("constructor with XXXX prefix") << "qt_XXXX" << "qt_"; - QTest::newRow("constructor with XXXXX prefix") << "qt_XXXXX" << "qt_"; + QTest::newRow("xxx-suffix") << "qt_XXXXXXxxx" << "qt_" << "xxx"; + QTest::newRow("xXx-suffix") << "qt_XXXXXXxXx" << "qt_" << "xXx"; + QTest::newRow("no-suffix") << "qt_XXXXXX" << "qt_" << ""; + QTest::newRow("10X") << "qt_XXXXXXXXXX" << "qt_" << ""; + QTest::newRow("4Xsuffix") << "qt_XXXXXX_XXXX" << "qt_" << "_XXXX"; + QTest::newRow("4Xprefix") << "qt_XXXX" << "qt_XXXX" << ""; + QTest::newRow("5Xprefix") << "qt_XXXXX" << "qt_XXXXX" << ""; if (QTestPrivate::canHandleUnicodeFileNames()) { // Test Umlauts (contained in Latin1) QString prefix = "qt_" + umlautTestText(); - QTest::newRow("Umlauts") << (prefix + "XXXXXX") << prefix; - // Test Chinese + QTest::newRow("Umlauts") << (prefix + "XXXXXX") << prefix << ""; + // test non-Latin1 prefix = "qt_" + hanTestText(); - QTest::newRow("Chinese characters") << (prefix + "XXXXXX") << prefix; + QTest::newRow("Chinese") << (prefix + "XXXXXX" + umlautTestText()) << prefix << umlautTestText(); } } @@ -165,14 +164,17 @@ void tst_QTemporaryDir::fileTemplate() { QFETCH(QString, constructorTemplate); QFETCH(QString, prefix); + QFETCH(QString, suffix); QTemporaryDir tempDir(constructorTemplate); QVERIFY(tempDir.isValid()); QString dirName = QDir(tempDir.path()).dirName(); - if (prefix.length()) + if (prefix.length()) { QCOMPARE(dirName.left(prefix.length()), prefix); + QCOMPARE(dirName.right(suffix.length()), suffix); + } } diff --git a/tests/auto/corelib/io/qtemporaryfile/tst_qtemporaryfile.cpp b/tests/auto/corelib/io/qtemporaryfile/tst_qtemporaryfile.cpp index a4e7aa0316..dbc3d68e93 100644 --- a/tests/auto/corelib/io/qtemporaryfile/tst_qtemporaryfile.cpp +++ b/tests/auto/corelib/io/qtemporaryfile/tst_qtemporaryfile.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2017 Intel Corporation. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the test suite of the Qt Toolkit. @@ -32,6 +33,7 @@ #include <qtemporarydir.h> #include <qtemporaryfile.h> #include <qfile.h> +#include <qdatetime.h> #include <qdir.h> #include <qset.h> #include <qtextcodec.h> @@ -65,9 +67,10 @@ private slots: void fileNameIsEmpty(); void autoRemove(); void nonWritableCurrentDir(); - void write(); + void io(); void openCloseOpenClose(); void removeAndReOpen(); + void removeUnnamed(); void size(); void resize(); void openOnRootDrives(); @@ -278,6 +281,18 @@ void tst_QTemporaryFile::autoRemove() fileName = file.fileName(); file.close(); } + QVERIFY(!fileName.isEmpty()); + QVERIFY(!QFile::exists(fileName)); + + // same, but gets the file name after closing + { + QTemporaryFile file("tempXXXXXX"); + file.setAutoRemove(true); + QVERIFY(file.open()); + file.close(); + fileName = file.fileName(); + } + QVERIFY(!fileName.isEmpty()); QVERIFY(!QFile::exists(fileName)); // Test if disabling auto remove works. @@ -288,6 +303,19 @@ void tst_QTemporaryFile::autoRemove() fileName = file.fileName(); file.close(); } + QVERIFY(!fileName.isEmpty()); + QVERIFY(QFile::exists(fileName)); + QVERIFY(QFile::remove(fileName)); + + // same, but gets the file name after closing + { + QTemporaryFile file("tempXXXXXX"); + file.setAutoRemove(false); + QVERIFY(file.open()); + file.close(); + fileName = file.fileName(); + } + QVERIFY(!fileName.isEmpty()); QVERIFY(QFile::exists(fileName)); QVERIFY(QFile::remove(fileName)); @@ -338,17 +366,51 @@ void tst_QTemporaryFile::nonWritableCurrentDir() #endif } -void tst_QTemporaryFile::write() +void tst_QTemporaryFile::io() { QByteArray data("OLE\nOLE\nOLE"); QTemporaryFile file; + QDateTime before = QDateTime::currentDateTimeUtc().addMSecs(-250); + + // discard msec component (round down) - not all FSs and OSs support them + before.setSecsSinceEpoch(before.toSecsSinceEpoch()); + QVERIFY(file.open()); + QVERIFY(file.readLink().isEmpty()); // it's not a link! + QFile::Permissions perm = file.permissions(); + QVERIFY(perm & QFile::ReadOwner); + QVERIFY(file.setPermissions(perm)); + + QCOMPARE(int(file.size()), 0); + QVERIFY(file.resize(data.size())); + QCOMPARE(int(file.size()), data.size()); QCOMPARE((int)file.write(data), data.size()); + QCOMPARE(int(file.size()), data.size()); + + QDateTime mtime = file.fileTime(QFile::FileModificationTime).toUTC(); + QDateTime btime = file.fileTime(QFile::FileBirthTime).toUTC(); + QDateTime ctime = file.fileTime(QFile::FileMetadataChangeTime).toUTC(); + QDateTime atime = file.fileTime(QFile::FileAccessTime).toUTC(); + + QDateTime after = QDateTime::currentDateTimeUtc().toUTC().addMSecs(250); + // round msecs up + after.setSecsSinceEpoch(after.toSecsSinceEpoch() + 1); + + // mtime must be valid, the rest could fail + QVERIFY(mtime <= after && mtime >= before); + QVERIFY(!btime.isValid() || (btime <= after && btime >= before)); + QVERIFY(!ctime.isValid() || (ctime <= after && ctime >= before)); + QVERIFY(!btime.isValid() || (btime <= after && btime >= before)); + + QVERIFY(file.setFileTime(before.addSecs(-10), QFile::FileModificationTime)); + mtime = file.fileTime(QFile::FileModificationTime).toUTC(); + QCOMPARE(mtime, before.addSecs(-10)); + file.reset(); QFile compare(file.fileName()); compare.open(QIODevice::ReadOnly); QCOMPARE(compare.readAll() , data); - file.close(); + QCOMPARE(compare.fileTime(QFile::FileModificationTime), mtime); } void tst_QTemporaryFile::openCloseOpenClose() @@ -381,11 +443,13 @@ void tst_QTemporaryFile::removeAndReOpen() { QTemporaryFile file; file.open(); - fileName = file.fileName(); + fileName = file.fileName(); // materializes any unnamed file QVERIFY(QFile::exists(fileName)); - file.remove(); + QVERIFY(file.remove()); + QVERIFY(file.fileName().isEmpty()); QVERIFY(!QFile::exists(fileName)); + QVERIFY(!file.remove()); QVERIFY(file.open()); QCOMPARE(QFileInfo(file.fileName()).path(), QFileInfo(fileName).path()); @@ -395,21 +459,36 @@ void tst_QTemporaryFile::removeAndReOpen() QVERIFY(!QFile::exists(fileName)); } +void tst_QTemporaryFile::removeUnnamed() +{ + QTemporaryFile file; + file.open(); + + // we did not call fileName(), so the file name may not have a name + QVERIFY(file.remove()); + QVERIFY(file.fileName().isEmpty()); + + // if it was unnamed, this will succeed again, so we can't check the result + file.remove(); +} + void tst_QTemporaryFile::size() { QTemporaryFile file; QVERIFY(file.open()); - QVERIFY(file.exists()); QVERIFY(!file.isSequential()); QByteArray str("foobar"); file.write(str); - QVERIFY(QFile::exists(file.fileName())); + // On CE it takes more time for the filesystem to update // the information. Usually you have to close it or seek // to get latest information. flush() does not help either. QCOMPARE(file.size(), qint64(6)); file.seek(0); QCOMPARE(file.size(), qint64(6)); + + QVERIFY(QFile::exists(file.fileName())); + QVERIFY(file.exists()); } void tst_QTemporaryFile::resize() @@ -806,6 +885,14 @@ void tst_QTemporaryFile::QTBUG_4796() QCOMPARE(file5.open(), openResult); QCOMPARE(file6.open(), openResult); + // force the files to exist, if they are supposed to + QCOMPARE(!file1.fileName().isEmpty(), openResult); + QCOMPARE(!file2.fileName().isEmpty(), openResult); + QCOMPARE(!file3.fileName().isEmpty(), openResult); + QCOMPARE(!file4.fileName().isEmpty(), openResult); + QCOMPARE(!file5.fileName().isEmpty(), openResult); + QCOMPARE(!file6.fileName().isEmpty(), openResult); + QCOMPARE(file1.exists(), openResult); QCOMPARE(file2.exists(), openResult); QCOMPARE(file3.exists(), openResult); @@ -861,8 +948,6 @@ void tst_QTemporaryFile::guaranteeUnique() // First pass. See which filename QTemporaryFile will try first. { - // Fix the random seed. - qsrand(1135); QTemporaryFile tmpFile("testFile1.XXXXXX"); tmpFile.open(); takenFileName = tmpFile.fileName(); @@ -876,8 +961,6 @@ void tst_QTemporaryFile::guaranteeUnique() // Second pass, now we have blocked its first attempt with a directory. { - // Fix the random seed. - qsrand(1135); QTemporaryFile tmpFile("testFile1.XXXXXX"); QVERIFY(tmpFile.open()); QString uniqueFileName = tmpFile.fileName(); diff --git a/tests/auto/corelib/io/qurl/tst_qurl.cpp b/tests/auto/corelib/io/qurl/tst_qurl.cpp index 0bbd1b9f15..20282068cb 100644 --- a/tests/auto/corelib/io/qurl/tst_qurl.cpp +++ b/tests/auto/corelib/io/qurl/tst_qurl.cpp @@ -1384,18 +1384,6 @@ void tst_QUrl::compat_constructor_01_data() void tst_QUrl::compat_constructor_01() { - /* The following should work as expected: - * - * QUrlOperator op; - * op.copy( QString( "Makefile" ), - * QString("ftp://rms:grmpf12@nibbler/home/rms/tmp"), - * false ); - * - * as well as the following: - * - * QUrlOperator op; - * op.copy(QString("ftp://ftp.qt-project.org/qt/INSTALL"), "."); - */ QFETCH( QString, urlStr ); { @@ -1425,11 +1413,6 @@ void tst_QUrl::compat_constructor_02_data() void tst_QUrl::compat_constructor_02() { - /* The following should work as expected: - * - * QUrlOperator op( "ftp://ftp.qt-project.org/qt" ); - * op.copy(QString("INSTALL"), "."); - */ QFETCH( QString, urlStr ); QFETCH( QString, fileName ); diff --git a/tests/auto/corelib/io/qwinoverlappedionotifier/qwinoverlappedionotifier.pro b/tests/auto/corelib/io/qwinoverlappedionotifier/qwinoverlappedionotifier.pro deleted file mode 100644 index 4f0e9da3c2..0000000000 --- a/tests/auto/corelib/io/qwinoverlappedionotifier/qwinoverlappedionotifier.pro +++ /dev/null @@ -1,4 +0,0 @@ -CONFIG += testcase -TARGET = tst_qwinoverlappedionotifier -QT = core-private testlib -SOURCES = tst_qwinoverlappedionotifier.cpp diff --git a/tests/auto/corelib/io/qwinoverlappedionotifier/tst_qwinoverlappedionotifier.cpp b/tests/auto/corelib/io/qwinoverlappedionotifier/tst_qwinoverlappedionotifier.cpp deleted file mode 100644 index 7034c2c9fd..0000000000 --- a/tests/auto/corelib/io/qwinoverlappedionotifier/tst_qwinoverlappedionotifier.cpp +++ /dev/null @@ -1,331 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** 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 The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include <QtTest/QtTest> -#include <private/qwinoverlappedionotifier_p.h> -#include <qbytearray.h> -#include <qt_windows.h> - -#ifndef PIPE_REJECT_REMOTE_CLIENTS -#define PIPE_REJECT_REMOTE_CLIENTS 0x08 -#endif - -class tst_QWinOverlappedIoNotifier : public QObject -{ - Q_OBJECT - -private slots: - void initTestCase(); - void readFile_data(); - void readFile(); - void waitForNotified_data(); - void waitForNotified(); - void waitForAnyNotified(); - void brokenPipe(); - void multipleOperations(); - -private: - QFileInfo sourceFileInfo; - DWORD notifiedBytesRead; - DWORD notifiedErrorCode; -}; - -class NotifierSink : public QObject -{ - Q_OBJECT -public: - NotifierSink(QWinOverlappedIoNotifier *notifier) - : QObject(notifier), - threshold(1) - { - connect(notifier, &QWinOverlappedIoNotifier::notified, this, &NotifierSink::notified); - } - -protected slots: - void notified(DWORD bytesRead, DWORD errorCode, OVERLAPPED *overlapped) - { - IOResult ioResult; - ioResult.bytes = bytesRead; - ioResult.errorCode = errorCode; - ioResult.overlapped = overlapped; - notifications.append(ioResult); - if (notifications.count() >= threshold) - emit notificationReceived(); - } - -signals: - void notificationReceived(); - -public: - int threshold; - - struct IOResult - { - IOResult() - : bytes(0), errorCode(ERROR_SUCCESS), overlapped(0) - {} - DWORD bytes; - DWORD errorCode; - OVERLAPPED *overlapped; - }; - - QList<IOResult> notifications; -}; - -void tst_QWinOverlappedIoNotifier::initTestCase() -{ - sourceFileInfo.setFile(QFINDTESTDATA("tst_qwinoverlappedionotifier.cpp")); - QVERIFY2(sourceFileInfo.exists(), "File tst_qwinoverlappedionotifier.cpp not found."); -} - -void tst_QWinOverlappedIoNotifier::readFile_data() -{ - QTest::addColumn<QString>("fileName"); - QTest::addColumn<int>("readBufferSize"); - QTest::addColumn<DWORD>("expectedBytesRead"); - - QString sourceFileName = QDir::toNativeSeparators(sourceFileInfo.absoluteFilePath()); - int sourceFileSize = sourceFileInfo.size(); - - QTest::newRow("read file, less than available") - << sourceFileName << sourceFileSize / 2 << DWORD(sourceFileSize / 2); - QTest::newRow("read file, more than available") - << sourceFileName << sourceFileSize * 2 << DWORD(sourceFileSize); -} - -void tst_QWinOverlappedIoNotifier::readFile() -{ - QFETCH(QString, fileName); - QFETCH(int, readBufferSize); - QFETCH(DWORD, expectedBytesRead); - - QWinOverlappedIoNotifier notifier; - NotifierSink sink(¬ifier); - connect(&sink, &NotifierSink::notificationReceived, &QTestEventLoop::instance(), &QTestEventLoop::exitLoop); - - HANDLE hFile = CreateFile(reinterpret_cast<const wchar_t*>(fileName.utf16()), - GENERIC_READ, FILE_SHARE_READ, - NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); - notifier.setHandle(hFile); - notifier.setEnabled(true); - - OVERLAPPED overlapped; - ZeroMemory(&overlapped, sizeof(OVERLAPPED)); - QByteArray buffer(readBufferSize, 0); - BOOL readSuccess = ReadFile(hFile, buffer.data(), buffer.size(), NULL, &overlapped); - QVERIFY(readSuccess || GetLastError() == ERROR_IO_PENDING); - - QTestEventLoop::instance().enterLoop(3); - CloseHandle(hFile); - QCOMPARE(sink.notifications.count(), 1); - QCOMPARE(sink.notifications.last().bytes, expectedBytesRead); - QCOMPARE(sink.notifications.last().errorCode, DWORD(ERROR_SUCCESS)); - QCOMPARE(sink.notifications.last().overlapped, &overlapped); -} - -void tst_QWinOverlappedIoNotifier::waitForNotified_data() -{ - readFile_data(); -} - -void tst_QWinOverlappedIoNotifier::waitForNotified() -{ - QFETCH(QString, fileName); - QFETCH(int, readBufferSize); - QFETCH(DWORD, expectedBytesRead); - - QWinOverlappedIoNotifier notifier; - NotifierSink sink(¬ifier); - HANDLE hFile = CreateFile(reinterpret_cast<const wchar_t*>(fileName.utf16()), - GENERIC_READ, FILE_SHARE_READ, - NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); - notifier.setHandle(hFile); - notifier.setEnabled(true); - QCOMPARE(notifier.waitForNotified(100, 0), false); - - OVERLAPPED overlapped; - ZeroMemory(&overlapped, sizeof(OVERLAPPED)); - QByteArray buffer(readBufferSize, 0); - BOOL readSuccess = ReadFile(hFile, buffer.data(), buffer.size(), NULL, &overlapped); - QVERIFY(readSuccess || GetLastError() == ERROR_IO_PENDING); - - QCOMPARE(notifier.waitForNotified(3000, &overlapped), true); - CloseHandle(hFile); - QCOMPARE(sink.notifications.count(), 1); - QCOMPARE(sink.notifications.last().bytes, expectedBytesRead); - QCOMPARE(sink.notifications.last().errorCode, DWORD(ERROR_SUCCESS)); - QCOMPARE(sink.notifications.last().overlapped, &overlapped); - QCOMPARE(notifier.waitForNotified(100, &overlapped), false); -} - -void tst_QWinOverlappedIoNotifier::waitForAnyNotified() -{ - const QString fileName = QDir::toNativeSeparators(sourceFileInfo.absoluteFilePath()); - const int readBufferSize = sourceFileInfo.size(); - - QWinOverlappedIoNotifier notifier; - HANDLE hFile = CreateFile(reinterpret_cast<const wchar_t*>(fileName.utf16()), - GENERIC_READ, FILE_SHARE_READ, - NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); - notifier.setHandle(hFile); - notifier.setEnabled(true); - QVERIFY(!notifier.waitForAnyNotified(100)); - - OVERLAPPED overlapped1; - ZeroMemory(&overlapped1, sizeof(OVERLAPPED)); - QByteArray buffer1(readBufferSize, 0); - BOOL readSuccess = ReadFile(hFile, buffer1.data(), buffer1.size(), NULL, &overlapped1); - QVERIFY(readSuccess || GetLastError() == ERROR_IO_PENDING); - - OVERLAPPED overlapped2; - ZeroMemory(&overlapped2, sizeof(OVERLAPPED)); - QByteArray buffer2(readBufferSize, 0); - readSuccess = ReadFile(hFile, buffer2.data(), buffer2.size(), NULL, &overlapped2); - QVERIFY(readSuccess || GetLastError() == ERROR_IO_PENDING); - - QSet<OVERLAPPED *> overlappedObjects; - overlappedObjects << &overlapped1 << &overlapped2; - - for (int i = 1; i <= 2; ++i) { - OVERLAPPED *notifiedOverlapped = notifier.waitForAnyNotified(3000); - QVERIFY(overlappedObjects.contains(notifiedOverlapped)); - overlappedObjects.remove(notifiedOverlapped); - } - - CloseHandle(hFile); - QVERIFY(overlappedObjects.isEmpty()); - QVERIFY(!notifier.waitForAnyNotified(100)); -} - -void tst_QWinOverlappedIoNotifier::brokenPipe() -{ - QWinOverlappedIoNotifier notifier; - NotifierSink sink(¬ifier); - connect(&sink, &NotifierSink::notificationReceived, &QTestEventLoop::instance(), &QTestEventLoop::exitLoop); - - wchar_t pipeName[] = L"\\\\.\\pipe\\tst_QWinOverlappedIoNotifier_brokenPipe"; - HANDLE hPipe = CreateNamedPipe(pipeName, - PIPE_ACCESS_DUPLEX, - PIPE_TYPE_BYTE | PIPE_NOWAIT | PIPE_REJECT_REMOTE_CLIENTS, - 1, 0, 0, 0, NULL); - QVERIFY(hPipe != INVALID_HANDLE_VALUE); - HANDLE hReadEnd = CreateFile(pipeName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0); - QVERIFY(hReadEnd != INVALID_HANDLE_VALUE); - notifier.setHandle(hReadEnd); - notifier.setEnabled(true); - - OVERLAPPED overlapped; - ZeroMemory(&overlapped, sizeof(OVERLAPPED)); - QByteArray buffer(1024, 0); - BOOL readSuccess = ReadFile(hReadEnd, buffer.data(), buffer.size(), NULL, &overlapped); - QVERIFY(readSuccess || GetLastError() == ERROR_IO_PENDING); - - // close the write end of the pipe - CloseHandle(hPipe); - - QTestEventLoop::instance().enterLoop(3); - CloseHandle(hReadEnd); - QCOMPARE(sink.notifications.count(), 1); - QCOMPARE(sink.notifications.last().bytes, DWORD(0)); - QCOMPARE(sink.notifications.last().errorCode, DWORD(ERROR_BROKEN_PIPE)); - QCOMPARE(sink.notifications.last().overlapped, &overlapped); -} - -void tst_QWinOverlappedIoNotifier::multipleOperations() -{ - QWinOverlappedIoNotifier clientNotifier; - NotifierSink sink(&clientNotifier); - sink.threshold = 2; - connect(&sink, &NotifierSink::notificationReceived, - &QTestEventLoop::instance(), &QTestEventLoop::exitLoop); - - wchar_t pipeName[] = L"\\\\.\\pipe\\tst_QWinOverlappedIoNotifier_multipleOperations"; - HANDLE hServer = CreateNamedPipe(pipeName, - PIPE_ACCESS_DUPLEX, - PIPE_TYPE_BYTE | PIPE_NOWAIT | PIPE_REJECT_REMOTE_CLIENTS, - 1, 0, 0, 0, NULL); - QVERIFY(hServer != INVALID_HANDLE_VALUE); - HANDLE hClient = CreateFile(pipeName, GENERIC_READ | GENERIC_WRITE, 0, NULL, - OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0); - QVERIFY(hClient != INVALID_HANDLE_VALUE); - clientNotifier.setHandle(hClient); - clientNotifier.setEnabled(true); - - // start async read on client - QByteArray clientReadBuffer(377, Qt::Uninitialized); - OVERLAPPED clientReadOverlapped; - ZeroMemory(&clientReadOverlapped, sizeof(clientReadOverlapped)); - BOOL readSuccess = ReadFile(hClient, clientReadBuffer.data(), clientReadBuffer.size(), - NULL, &clientReadOverlapped); - QVERIFY(readSuccess || GetLastError() == ERROR_IO_PENDING); - - // start async write client -> server - QByteArray clientDataToWrite(233, 'B'); - OVERLAPPED clientWriteOverlapped; - ZeroMemory(&clientWriteOverlapped, sizeof(clientWriteOverlapped)); - BOOL writeSuccess = WriteFile(hClient, clientDataToWrite.data(), clientDataToWrite.size(), - NULL, &clientWriteOverlapped); - QVERIFY(writeSuccess || GetLastError() == ERROR_IO_PENDING); - - // start async write server -> client - QByteArray serverDataToWrite(144, 'A'); - OVERLAPPED serverOverlapped; - ZeroMemory(&serverOverlapped, sizeof(serverOverlapped)); - writeSuccess = WriteFile(hServer, serverDataToWrite.data(), serverDataToWrite.size(), - NULL, &serverOverlapped); - QVERIFY(writeSuccess || GetLastError() == ERROR_IO_PENDING); - - // read synchronously on server to complete the client -> server write - QByteArray serverReadBuffer(610, Qt::Uninitialized); - DWORD dwBytesRead = 0; - readSuccess = ReadFile(hServer, serverReadBuffer.data(), serverReadBuffer.size(), - &dwBytesRead, NULL); - QVERIFY(readSuccess); - QCOMPARE(int(dwBytesRead), clientDataToWrite.size()); - serverReadBuffer.resize(dwBytesRead); - QCOMPARE(serverReadBuffer, clientDataToWrite); - - QTestEventLoop::instance().enterLoop(3); - QTRY_COMPARE(sink.notifications.count(), 2); - foreach (const NotifierSink::IOResult &r, sink.notifications) { - QCOMPARE(r.errorCode, DWORD(ERROR_SUCCESS)); - if (r.bytes == DWORD(serverDataToWrite.count())) - QCOMPARE(r.overlapped, &clientReadOverlapped); - else if (r.bytes == DWORD(clientDataToWrite.count())) - QCOMPARE(r.overlapped, &clientWriteOverlapped); - else - QVERIFY2(false, "Unexpected number of bytes received."); - } - - CloseHandle(hClient); - CloseHandle(hServer); -} - -QTEST_MAIN(tst_QWinOverlappedIoNotifier) - -#include "tst_qwinoverlappedionotifier.moc" diff --git a/tests/auto/corelib/itemmodels/itemmodels.pro b/tests/auto/corelib/itemmodels/itemmodels.pro index c1d75cc2cb..a09f03a7b4 100644 --- a/tests/auto/corelib/itemmodels/itemmodels.pro +++ b/tests/auto/corelib/itemmodels/itemmodels.pro @@ -7,6 +7,7 @@ qtHaveModule(gui): SUBDIRS += \ qabstractproxymodel \ qidentityproxymodel \ qitemselectionmodel \ + qsortfilterproxymodel_recursive \ qtHaveModule(widgets) { SUBDIRS += \ diff --git a/tests/auto/corelib/itemmodels/qsortfilterproxymodel/tst_qsortfilterproxymodel.cpp b/tests/auto/corelib/itemmodels/qsortfilterproxymodel/tst_qsortfilterproxymodel.cpp index bc8f0c1c51..1fb51490db 100644 --- a/tests/auto/corelib/itemmodels/qsortfilterproxymodel/tst_qsortfilterproxymodel.cpp +++ b/tests/auto/corelib/itemmodels/qsortfilterproxymodel/tst_qsortfilterproxymodel.cpp @@ -94,6 +94,7 @@ private slots: void changeSourceDataKeepsStableSorting_qtbug1548(); void changeSourceDataForwardsRoles_qtbug35440(); void resortingDoesNotBreakTreeModels(); + void dynamicFilterWithoutSort(); void sortFilterRole(); void selectionFilteredOut(); void match_data(); @@ -148,6 +149,9 @@ private slots: void sourceLayoutChangeLeavesValidPersistentIndexes(); void rowMoveLeavesValidPersistentIndexes(); + void emitLayoutChangedOnlyIfSortingChanged_data(); + void emitLayoutChangedOnlyIfSortingChanged(); + protected: void buildHierarchy(const QStringList &data, QAbstractItemModel *model); void checkHierarchy(const QStringList &data, const QAbstractItemModel *model); @@ -2057,8 +2061,6 @@ static void checkSortedTableModel(const QAbstractItemModel *model, const QString void tst_QSortFilterProxyModel::changeSourceDataKeepsStableSorting_qtbug1548() { - QSKIP("This test will fail, see QTBUG-1548"); - // Check that emitting dataChanged from the source model // for a change of a role which is not the sorting role // doesn't alter the sorting. In this case, we sort on the DisplayRole, @@ -3568,6 +3570,13 @@ void tst_QSortFilterProxyModel::testParentLayoutChanged() parentItem = item; } } + // item 0 + // item 10 + // - item 1 + // - item 11 + // - item 2 + // - item 12 + // ... QSortFilterProxyModel proxy; proxy.sort(0, Qt::AscendingOrder); @@ -3609,11 +3618,12 @@ void tst_QSortFilterProxyModel::testParentLayoutChanged() QVERIFY(proxy2ParentsChangedSpy.isValid()); QStandardItem *item = model.invisibleRootItem()->child(1)->child(1); + QCOMPARE(item->text(), QStringLiteral("item 11")); // Ensure mapped: proxy.mapFromSource(model.indexFromItem(item)); - item->setData("Changed"); + item->setText("Changed"); QCOMPARE(dataChangedSpy.size(), 1); QCOMPARE(layoutAboutToBeChangedSpy.size(), 1); @@ -3883,7 +3893,7 @@ void tst_QSortFilterProxyModel::hierarchyFilterInvalidation() view.setCurrentIndex(proxy.index(2, 0).child(0, 0)); view.show(); - QTest::qWaitForWindowExposed(&view); + QVERIFY(QTest::qWaitForWindowExposed(&view)); proxy.setMode(true); } @@ -3940,7 +3950,7 @@ void tst_QSortFilterProxyModel::simpleFilterInvalidation() view.setModel(&proxy); view.show(); - QTest::qWaitForWindowExposed(&view); + QVERIFY(QTest::qWaitForWindowExposed(&view)); proxy.setMode(true); model.insertRow(0, new QStandardItem("extra")); @@ -4026,7 +4036,7 @@ public: } - QModelIndex mapToSource(const QModelIndex &proxyIndex) const + QModelIndex mapToSource(const QModelIndex &proxyIndex) const override { Q_ASSERT(sourceModel()); return QSortFilterProxyModel::mapToSource(proxyIndex); @@ -4353,5 +4363,152 @@ void tst_QSortFilterProxyModel::rowMoveLeavesValidPersistentIndexes() QVERIFY(persistentIndex.parent().isValid()); } +void tst_QSortFilterProxyModel::emitLayoutChangedOnlyIfSortingChanged_data() +{ + QTest::addColumn<int>("changedRow"); + QTest::addColumn<Qt::ItemDataRole>("changedRole"); + QTest::addColumn<QString>("newData"); + QTest::addColumn<QString>("expectedSourceRowTexts"); + QTest::addColumn<QString>("expectedProxyRowTexts"); + QTest::addColumn<int>("expectedLayoutChanged"); + + // Starting point: + // a source model with 8,7,6,5,4,3,2,1 + // a proxy model keeping only even rows and sorting them, therefore showing 2,4,6,8 + + // When setData changes ordering, layoutChanged should be emitted + QTest::newRow("ordering_change") << 0 << Qt::DisplayRole << "0" << "07654321" << "0246" << 1; + + // When setData on visible row doesn't change ordering, layoutChanged should not be emitted + QTest::newRow("no_ordering_change") << 6 << Qt::DisplayRole << "0" << "87654301" << "0468" << 0; + + // When setData happens on a filtered out row, layoutChanged should not be emitted + QTest::newRow("filtered_out") << 1 << Qt::DisplayRole << "9" << "89654321" << "2468" << 0; + + // When setData makes a row visible, layoutChanged should not be emitted (rowsInserted is emitted instead) + QTest::newRow("make_row_visible") << 7 << Qt::DisplayRole << "0" << "87654320" << "02468" << 0; + + // When setData makes a row hidden, layoutChanged should not be emitted (rowsRemoved is emitted instead) + QTest::newRow("make_row_hidden") << 4 << Qt::DisplayRole << "1" << "87651321" << "268" << 0; + + // When setData happens on an unrelated role, layoutChanged should not be emitted + QTest::newRow("unrelated_role") << 0 << Qt::DecorationRole << "" << "87654321" << "2468" << 0; + + // When many changes happen together... and trigger removal, insertion, and layoutChanged + QTest::newRow("many_changes") << -1 << Qt::DisplayRole << "3,4,2,5,6,0,7,9" << "34256079" << "0246" << 1; + + // When many changes happen together... and trigger removal, insertion, but no change in ordering of visible rows => no layoutChanged + QTest::newRow("many_changes_no_layoutChanged") << -1 << Qt::DisplayRole << "7,5,4,3,2,1,0,8" << "75432108" << "0248" << 0; +} + +void tst_QSortFilterProxyModel::emitLayoutChangedOnlyIfSortingChanged() +{ + QFETCH(int, changedRow); + QFETCH(QString, newData); + QFETCH(Qt::ItemDataRole, changedRole); + QFETCH(QString, expectedSourceRowTexts); + QFETCH(QString, expectedProxyRowTexts); + QFETCH(int, expectedLayoutChanged); + + // Custom version of QStringListModel which supports emitting dataChanged for many rows at once + class CustomStringListModel : public QAbstractListModel + { + public: + bool setData(const QModelIndex &index, const QVariant &value, int role) override + { + if (index.row() >= 0 && index.row() < lst.size() + && (role == Qt::EditRole || role == Qt::DisplayRole)) { + lst.replace(index.row(), value.toString()); + emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole}); + return true; + } + return false; + } + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override + { + if (role == Qt::DisplayRole || role == Qt::EditRole) + return lst.at(index.row()); + return QVariant(); + } + int rowCount(const QModelIndex & = QModelIndex()) const override + { + return lst.count(); + } + + void replaceData(const QStringList &newData) + { + lst = newData; + emit dataChanged(index(0, 0), index(rowCount()-1, 0), {Qt::DisplayRole, Qt::EditRole}); + } + + void emitDecorationChangedSignal() + { + const QModelIndex idx = index(0, 0); + emit dataChanged(idx, idx, {Qt::DecorationRole}); + } + private: + QStringList lst; + }; + CustomStringListModel model; + QStringList strings; + for (auto i = 8; i >= 1; --i) + strings.append(QString::number(i)); + model.replaceData(strings); + QCOMPARE(rowTexts(&model), QStringLiteral("87654321")); + + class FilterEvenRowsProxyModel : public QSortFilterProxyModel + { + public: + bool filterAcceptsRow(int srcRow, const QModelIndex& srcParent) const override + { + return sourceModel()->index(srcRow, 0, srcParent).data().toInt() % 2 == 0; + } + }; + + FilterEvenRowsProxyModel proxy; + proxy.sort(0); + proxy.setSourceModel(&model); + QCOMPARE(rowTexts(&proxy), QStringLiteral("2468")); + + QSignalSpy modelDataChangedSpy(&model, &QAbstractItemModel::dataChanged); + QSignalSpy proxyLayoutChangedSpy(&proxy, &QAbstractItemModel::layoutChanged); + + if (changedRole == Qt::DecorationRole) + model.emitDecorationChangedSignal(); + else if (changedRow == -1) + model.replaceData(newData.split(QLatin1Char(','))); + else + model.setData(model.index(changedRow, 0), newData, changedRole); + + QCOMPARE(rowTexts(&model), expectedSourceRowTexts); + QCOMPARE(rowTexts(&proxy), expectedProxyRowTexts); + QCOMPARE(modelDataChangedSpy.size(), 1); + QCOMPARE(proxyLayoutChangedSpy.size(), expectedLayoutChanged); +} + +void tst_QSortFilterProxyModel::dynamicFilterWithoutSort() +{ + QStringListModel model; + const QStringList initial = QString("bravo charlie delta echo").split(QLatin1Char(' ')); + model.setStringList(initial); + QSortFilterProxyModel proxy; + proxy.setDynamicSortFilter(true); + proxy.setSourceModel(&model); + + QSignalSpy layoutChangeSpy(&proxy, &QAbstractItemModel::layoutChanged); + QSignalSpy resetSpy(&proxy, &QAbstractItemModel::modelReset); + + QVERIFY(layoutChangeSpy.isValid()); + QVERIFY(resetSpy.isValid()); + + model.setStringList(QStringList() << "Monday" << "Tuesday" << "Wednesday" << "Thursday" << "Friday"); + + QVERIFY(layoutChangeSpy.isEmpty()); + + QCOMPARE(model.stringList(), QStringList() << "Monday" << "Tuesday" << "Wednesday" << "Thursday" << "Friday"); + + QCOMPARE(resetSpy.count(), 1); +} + QTEST_MAIN(tst_QSortFilterProxyModel) #include "tst_qsortfilterproxymodel.moc" diff --git a/tests/auto/corelib/itemmodels/qsortfilterproxymodel_recursive/.gitignore b/tests/auto/corelib/itemmodels/qsortfilterproxymodel_recursive/.gitignore new file mode 100644 index 0000000000..2007aaabbd --- /dev/null +++ b/tests/auto/corelib/itemmodels/qsortfilterproxymodel_recursive/.gitignore @@ -0,0 +1 @@ +tst_qsortfilterproxymodel_recursive diff --git a/tests/auto/corelib/itemmodels/qsortfilterproxymodel_recursive/qsortfilterproxymodel_recursive.pro b/tests/auto/corelib/itemmodels/qsortfilterproxymodel_recursive/qsortfilterproxymodel_recursive.pro new file mode 100644 index 0000000000..a8b793dbc6 --- /dev/null +++ b/tests/auto/corelib/itemmodels/qsortfilterproxymodel_recursive/qsortfilterproxymodel_recursive.pro @@ -0,0 +1,8 @@ +CONFIG += testcase +CONFIG += parallel_test +TARGET = tst_qsortfilterproxymodel_recursive + +QT += testlib + +SOURCES += tst_qsortfilterproxymodel_recursive.cpp +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0 diff --git a/tests/auto/corelib/itemmodels/qsortfilterproxymodel_recursive/tst_qsortfilterproxymodel_recursive.cpp b/tests/auto/corelib/itemmodels/qsortfilterproxymodel_recursive/tst_qsortfilterproxymodel_recursive.cpp new file mode 100644 index 0000000000..852d9adb46 --- /dev/null +++ b/tests/auto/corelib/itemmodels/qsortfilterproxymodel_recursive/tst_qsortfilterproxymodel_recursive.cpp @@ -0,0 +1,723 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, authors Filipe Azevedo <filipe.azevedo@kdab.com> and David Faure <david.faure@kdab.com> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QTest> +#include <QSignalSpy> + +#include <QtCore/QSortFilterProxyModel> +#include <QtGui/QStandardItem> + +Q_DECLARE_METATYPE(QModelIndex) + +class ModelSignalSpy : public QObject { + Q_OBJECT +public: + explicit ModelSignalSpy(QAbstractItemModel &model) { + connect(&model, &QAbstractItemModel::rowsInserted, this, &ModelSignalSpy::onRowsInserted); + connect(&model, &QAbstractItemModel::rowsRemoved, this, &ModelSignalSpy::onRowsRemoved); + connect(&model, &QAbstractItemModel::rowsAboutToBeInserted, this, &ModelSignalSpy::onRowsAboutToBeInserted); + connect(&model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &ModelSignalSpy::onRowsAboutToBeRemoved); + connect(&model, &QAbstractItemModel::rowsMoved, this, &ModelSignalSpy::onRowsMoved); + connect(&model, &QAbstractItemModel::dataChanged, this, &ModelSignalSpy::onDataChanged); + connect(&model, &QAbstractItemModel::layoutChanged, this, &ModelSignalSpy::onLayoutChanged); + connect(&model, &QAbstractItemModel::modelReset, this, &ModelSignalSpy::onModelReset); + } + + QStringList mSignals; + +private Q_SLOTS: + void onRowsInserted(QModelIndex p, int start, int end) { + mSignals << QLatin1String("rowsInserted(") + textForRowSpy(p, start, end) + ')'; + } + void onRowsRemoved(QModelIndex p, int start, int end) { + mSignals << QLatin1String("rowsRemoved(") + textForRowSpy(p, start, end) + ')'; + } + void onRowsAboutToBeInserted(QModelIndex p, int start, int end) { + mSignals << QLatin1String("rowsAboutToBeInserted(") + textForRowSpy(p, start, end) + ')'; + } + void onRowsAboutToBeRemoved(QModelIndex p, int start, int end) { + mSignals << QLatin1String("rowsAboutToBeRemoved(") + textForRowSpy(p, start, end) + ')'; + } + void onRowsMoved(QModelIndex,int,int,QModelIndex,int) { + mSignals << QStringLiteral("rowsMoved"); + } + void onDataChanged(const QModelIndex &from, const QModelIndex& ) { + mSignals << QStringLiteral("dataChanged(%1)").arg(from.data().toString()); + } + void onLayoutChanged() { + mSignals << QStringLiteral("layoutChanged"); + } + void onModelReset() { + mSignals << QStringLiteral("modelReset"); + } +private: + QString textForRowSpy(const QModelIndex &parent, int start, int end) + { + QString txt = parent.data().toString(); + if (!txt.isEmpty()) + txt += QLatin1Char('.'); + txt += QString::number(start+1); + if (start != end) + txt += QLatin1Char('-') + QString::number(end+1); + return txt; + } +}; + +class TestModel : public QSortFilterProxyModel +{ + Q_OBJECT +public: + TestModel(QAbstractItemModel *sourceModel) + : QSortFilterProxyModel() + { + setRecursiveFilteringEnabled(true); + setSourceModel(sourceModel); + } + + virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override + { + return sourceModel()->index(sourceRow, 0, sourceParent).data(Qt::UserRole +1).toBool() + && QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); + } +}; + +// Represents this tree +// - A +// - - B +// - - - C +// - - - D +// - - E +// as a single string, englobing children in brackets, like this: +// [A[B[C D] E]] +// In addition, items that match the filtering (data(UserRole+1) == true) have a * after their value. +static QString treeAsString(const QAbstractItemModel &model, const QModelIndex &parent = QModelIndex()) +{ + QString ret; + const int rowCount = model.rowCount(parent); + if (rowCount > 0) { + ret += QLatin1Char('['); + for (int row = 0 ; row < rowCount; ++row) { + if (row > 0) { + ret += ' '; + } + const QModelIndex child = model.index(row, 0, parent); + ret += child.data().toString(); + if (child.data(Qt::UserRole+1).toBool()) + ret += QLatin1Char('*'); + ret += treeAsString(model, child); + } + ret += QLatin1Char(']'); + } + return ret; +} + +// Fill a tree model based on a string representation (see treeAsString) +static void fillModel(QStandardItemModel &model, const QString &str) +{ + QCOMPARE(str.count('['), str.count(']')); + QStandardItem *item = 0; + QString data; + for ( int i = 0 ; i < str.length() ; ++i ) { + const QChar ch = str.at(i); + if ((ch == '[' || ch == ']' || ch == ' ') && !data.isEmpty()) { + if (data.endsWith('*')) { + item->setData(true, Qt::UserRole + 1); + data.chop(1); + } + item->setText(data); + data.clear(); + } + if (ch == '[') { + // Create new child + QStandardItem *child = new QStandardItem; + if (item) + item->appendRow(child); + else + model.appendRow(child); + item = child; + } else if (ch == ']') { + // Go up to parent + item = item->parent(); + } else if (ch == ' ') { + // Create new sibling + QStandardItem *child = new QStandardItem; + QStandardItem *parent = item->parent(); + if (parent) + parent->appendRow(child); + else + model.appendRow(child); + item = child; + } else { + data += ch; + } + } +} + +class tst_QSortFilterProxyModel_Recursive : public QObject +{ + Q_OBJECT +private: +private Q_SLOTS: + void testInitialFiltering_data() + { + QTest::addColumn<QString>("sourceStr"); + QTest::addColumn<QString>("proxyStr"); + + QTest::newRow("empty") << "[]" << ""; + QTest::newRow("no") << "[1]" << ""; + QTest::newRow("yes") << "[1*]" << "[1*]"; + QTest::newRow("second") << "[1 2*]" << "[2*]"; + QTest::newRow("child_yes") << "[1 2[2.1*]]" << "[2[2.1*]]"; + QTest::newRow("grandchild_yes") << "[1 2[2.1[2.1.1*]]]" << "[2[2.1[2.1.1*]]]"; + // 1, 3.1 and 4.2.1 match, so their parents are in the model + QTest::newRow("more") << "[1* 2[2.1] 3[3.1*] 4[4.1 4.2[4.2.1*]]]" << "[1* 3[3.1*] 4[4.2[4.2.1*]]]"; + } + + void testInitialFiltering() + { + QFETCH(QString, sourceStr); + QFETCH(QString, proxyStr); + + QStandardItemModel model; + fillModel(model, sourceStr); + QCOMPARE(treeAsString(model), sourceStr); + + TestModel proxy(&model); + QVERIFY(proxy.isRecursiveFilteringEnabled()); + QCOMPARE(treeAsString(proxy), proxyStr); + } + + // Test changing a role that is unrelated to the filtering. + void testUnrelatedDataChange() + { + QStandardItemModel model; + const QString sourceStr = QStringLiteral("[1[1.1[1.1.1*]]]"); + fillModel(model, sourceStr); + QCOMPARE(treeAsString(model), sourceStr); + + TestModel proxy(&model); + QCOMPARE(treeAsString(proxy), sourceStr); + + ModelSignalSpy spy(proxy); + QStandardItem *item_1_1_1 = model.item(0)->child(0)->child(0); + + // When changing the text on the item + item_1_1_1->setText(QStringLiteral("ME")); + + QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[ME*]]]")); + + QCOMPARE(spy.mSignals, QStringList() + << QStringLiteral("dataChanged(ME)") + << QStringLiteral("dataChanged(1.1)") + << QStringLiteral("dataChanged(1)")); + } + + // Test changing a role that is unrelated to the filtering, in a hidden item. + void testHiddenDataChange() + { + QStandardItemModel model; + const QString sourceStr = QStringLiteral("[1[1.1[1.1.1]]]"); + fillModel(model, sourceStr); + QCOMPARE(treeAsString(model), sourceStr); + + TestModel proxy(&model); + QCOMPARE(treeAsString(proxy), QString()); + + ModelSignalSpy spy(proxy); + QStandardItem *item_1_1_1 = model.item(0)->child(0)->child(0); + + // When changing the text on a hidden item + item_1_1_1->setText(QStringLiteral("ME")); + + QCOMPARE(treeAsString(proxy), QString()); + QCOMPARE(spy.mSignals, QStringList()); + } + + // Test that we properly react to a data-changed signal in a descendant and include all required rows + void testDataChangeIn_data() + { + QTest::addColumn<QString>("sourceStr"); + QTest::addColumn<QString>("initialProxyStr"); + QTest::addColumn<QString>("add"); // set the flag on this item + QTest::addColumn<QString>("expectedProxyStr"); + QTest::addColumn<QStringList>("expectedSignals"); + + QTest::newRow("toplevel") << "[1]" << "" << "1" << "[1*]" + << (QStringList() << QStringLiteral("rowsAboutToBeInserted(1)") << QStringLiteral("rowsInserted(1)")); + QTest::newRow("show_parents") << "[1[1.1[1.1.1]]]" << "" << "1.1.1" << "[1[1.1[1.1.1*]]]" + << (QStringList() << QStringLiteral("rowsAboutToBeInserted(1)") << QStringLiteral("rowsInserted(1)")); + + const QStringList insert_1_1_1 = QStringList() + << QStringLiteral("rowsAboutToBeInserted(1.1.1)") + << QStringLiteral("rowsInserted(1.1.1)") + << QStringLiteral("dataChanged(1.1)") + << QStringLiteral("dataChanged(1)") + ; + QTest::newRow("parent_visible") << "[1[1.1*[1.1.1]]]" << "[1[1.1*]]" << "1.1.1" << "[1[1.1*[1.1.1*]]]" + << insert_1_1_1; + + QTest::newRow("sibling_visible") << "[1[1.1[1.1.1 1.1.2*]]]" << "[1[1.1[1.1.2*]]]" << "1.1.1" << "[1[1.1[1.1.1* 1.1.2*]]]" + << insert_1_1_1; + + QTest::newRow("visible_cousin") << "[1[1.1[1.1.1 1.1.2[1.1.2.1*]]]]" << "[1[1.1[1.1.2[1.1.2.1*]]]]" << "1.1.1" << "[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]" + << insert_1_1_1; + + QTest::newRow("show_parent") << "[1[1.1[1.1.1 1.1.2] 1.2*]]" << "[1[1.2*]]" << "1.1.1" << "[1[1.1[1.1.1*] 1.2*]]" + << (QStringList() + << QStringLiteral("rowsAboutToBeInserted(1.1)") + << QStringLiteral("rowsInserted(1.1)") + << QStringLiteral("dataChanged(1)")); + + QTest::newRow("with_children") << "[1[1.1[1.1.1[1.1.1.1*]]] 2*]" << "[1[1.1[1.1.1[1.1.1.1*]]] 2*]" << "1.1.1" << "[1[1.1[1.1.1*[1.1.1.1*]]] 2*]" + << (QStringList() + << QStringLiteral("dataChanged(1.1.1)") + << QStringLiteral("dataChanged(1.1)") + << QStringLiteral("dataChanged(1)")); + + } + + void testDataChangeIn() + { + QFETCH(QString, sourceStr); + QFETCH(QString, initialProxyStr); + QFETCH(QString, add); + QFETCH(QString, expectedProxyStr); + QFETCH(QStringList, expectedSignals); + + QStandardItemModel model; + fillModel(model, sourceStr); + QCOMPARE(treeAsString(model), sourceStr); + + TestModel proxy(&model); + QCOMPARE(treeAsString(proxy), initialProxyStr); + + ModelSignalSpy spy(proxy); + // When changing the data on the designated item to show this row + QStandardItem *itemToChange = itemByText(model, add); + QVERIFY(!itemToChange->data().toBool()); + itemToChange->setData(true); + + // The proxy should update as expected + QCOMPARE(treeAsString(proxy), expectedProxyStr); + + //qDebug() << spy.mSignals; + QCOMPARE(spy.mSignals, expectedSignals); + } + + void testDataChangeOut_data() + { + QTest::addColumn<QString>("sourceStr"); + QTest::addColumn<QString>("initialProxyStr"); + QTest::addColumn<QString>("remove"); // unset the flag on this item + QTest::addColumn<QString>("expectedProxyStr"); + QTest::addColumn<QStringList>("expectedSignals"); + + const QStringList remove1_1_1 = (QStringList() + << QStringLiteral("rowsAboutToBeRemoved(1.1.1)") + << QStringLiteral("rowsRemoved(1.1.1)") + << QStringLiteral("dataChanged(1.1)") + << QStringLiteral("dataChanged(1)")); + + QTest::newRow("toplevel") << "[1*]" << "[1*]" << "1" << "" + << (QStringList() << QStringLiteral("rowsAboutToBeRemoved(1)") << QStringLiteral("rowsRemoved(1)")); + + QTest::newRow("hide_parent") << "[1[1.1[1.1.1*]]]" << "[1[1.1[1.1.1*]]]" << "1.1.1" << "" << + (QStringList() + << QStringLiteral("rowsAboutToBeRemoved(1.1.1)") + << QStringLiteral("rowsRemoved(1.1.1)") + << QStringLiteral("rowsAboutToBeRemoved(1.1)") + << QStringLiteral("rowsRemoved(1.1)") + << QStringLiteral("rowsAboutToBeRemoved(1)") + << QStringLiteral("rowsRemoved(1)")); + + QTest::newRow("parent_visible") << "[1[1.1*[1.1.1*]]]" << "[1[1.1*[1.1.1*]]]" << "1.1.1" << "[1[1.1*]]" + << remove1_1_1; + + QTest::newRow("visible") << "[1[1.1[1.1.1* 1.1.2*]]]" << "[1[1.1[1.1.1* 1.1.2*]]]" << "1.1.1" << "[1[1.1[1.1.2*]]]" + << remove1_1_1; + QTest::newRow("visible_cousin") << "[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]" << "[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]" << "1.1.1" << "[1[1.1[1.1.2[1.1.2.1*]]]]" + << remove1_1_1; + + // The following tests trigger the removal of an ascendant. + QTest::newRow("remove_parent") << "[1[1.1[1.1.1* 1.1.2] 1.2*]]" << "[1[1.1[1.1.1*] 1.2*]]" << "1.1.1" << "[1[1.2*]]" + << (QStringList() + << QStringLiteral("rowsAboutToBeRemoved(1.1.1)") + << QStringLiteral("rowsRemoved(1.1.1)") + << QStringLiteral("rowsAboutToBeRemoved(1.1)") + << QStringLiteral("rowsRemoved(1.1)") + << QStringLiteral("dataChanged(1)")); + + QTest::newRow("with_children") << "[1[1.1[1.1.1*[1.1.1.1*]]] 2*]" << "[1[1.1[1.1.1*[1.1.1.1*]]] 2*]" << "1.1.1" << "[1[1.1[1.1.1[1.1.1.1*]]] 2*]" + << (QStringList() + << QStringLiteral("dataChanged(1.1.1)") + << QStringLiteral("dataChanged(1.1)") + << QStringLiteral("dataChanged(1)")); + + QTest::newRow("last_visible") << "[1[1.1[1.1.1* 1.1.2]]]" << "[1[1.1[1.1.1*]]]" << "1.1.1" << "" + << (QStringList() + << QStringLiteral("rowsAboutToBeRemoved(1.1.1)") + << QStringLiteral("rowsRemoved(1.1.1)") + << QStringLiteral("rowsAboutToBeRemoved(1.1)") + << QStringLiteral("rowsRemoved(1.1)") + << QStringLiteral("rowsAboutToBeRemoved(1)") + << QStringLiteral("rowsRemoved(1)")); + + } + + void testDataChangeOut() + { + QFETCH(QString, sourceStr); + QFETCH(QString, initialProxyStr); + QFETCH(QString, remove); + QFETCH(QString, expectedProxyStr); + QFETCH(QStringList, expectedSignals); + + QStandardItemModel model; + fillModel(model, sourceStr); + QCOMPARE(treeAsString(model), sourceStr); + + TestModel proxy(&model); + QCOMPARE(treeAsString(proxy), initialProxyStr); + + ModelSignalSpy spy(proxy); + + // When changing the data on the designated item to exclude this row again + QStandardItem *itemToChange = itemByText(model, remove); + QVERIFY(itemToChange->data().toBool()); + itemToChange->setData(false); + + // The proxy should update as expected + QCOMPARE(treeAsString(proxy), expectedProxyStr); + + //qDebug() << spy.mSignals; + QCOMPARE(spy.mSignals, expectedSignals); + } + + void testInsert() + { + QStandardItemModel model; + const QString sourceStr = QStringLiteral("[1[1.1[1.1.1]]]"); + fillModel(model, sourceStr); + QCOMPARE(treeAsString(model), sourceStr); + + TestModel proxy(&model); + QCOMPARE(treeAsString(proxy), QString()); + + ModelSignalSpy spy(proxy); + QStandardItem *item_1_1_1 = model.item(0)->child(0)->child(0); + QStandardItem *item_1_1_1_1 = new QStandardItem(QStringLiteral("1.1.1.1")); + item_1_1_1_1->setData(true); + item_1_1_1->appendRow(item_1_1_1_1); + QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[1.1.1[1.1.1.1*]]]]")); + + QCOMPARE(spy.mSignals, QStringList() << QStringLiteral("rowsAboutToBeInserted(1)") + << QStringLiteral("rowsInserted(1)")); + } + + // Start from [1[1.1[1.1.1 1.1.2[1.1.2.1*]]]] + // where 1.1.1 is hidden but 1.1 is shown, we want to insert a shown child in 1.1.1. + // The proxy ensures dataChanged is called on 1.1, + // so that 1.1.1 and 1.1.1.1 are included in the model. + void testInsertCousin() + { + QStandardItemModel model; + const QString sourceStr = QStringLiteral("[1[1.1[1.1.1 1.1.2[1.1.2.1*]]]]"); + fillModel(model, sourceStr); + QCOMPARE(treeAsString(model), sourceStr); + + TestModel proxy(&model); + QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[1.1.2[1.1.2.1*]]]]")); + + ModelSignalSpy spy(proxy); + { + QStandardItem *item_1_1_1_1 = new QStandardItem(QStringLiteral("1.1.1.1")); + item_1_1_1_1->setData(true); + QStandardItem *item_1_1_1 = model.item(0)->child(0)->child(0); + item_1_1_1->appendRow(item_1_1_1_1); + } + + QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[1.1.1[1.1.1.1*] 1.1.2[1.1.2.1*]]]]")); + //qDebug() << spy.mSignals; + QCOMPARE(spy.mSignals, QStringList() + << QStringLiteral("rowsAboutToBeInserted(1.1.1)") + << QStringLiteral("rowsInserted(1.1.1)") + << QStringLiteral("dataChanged(1.1)") + << QStringLiteral("dataChanged(1)")); + } + + void testInsertWithChildren() + { + QStandardItemModel model; + const QString sourceStr = QStringLiteral("[1[1.1]]"); + fillModel(model, sourceStr); + QCOMPARE(treeAsString(model), sourceStr); + + TestModel proxy(&model); + QCOMPARE(treeAsString(proxy), QString()); + + ModelSignalSpy spy(proxy); + { + QStandardItem *item_1_1_1 = new QStandardItem(QStringLiteral("1.1.1")); + QStandardItem *item_1_1_1_1 = new QStandardItem(QStringLiteral("1.1.1.1")); + item_1_1_1_1->setData(true); + item_1_1_1->appendRow(item_1_1_1_1); + + QStandardItem *item_1_1 = model.item(0)->child(0); + item_1_1->appendRow(item_1_1_1); + } + + QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[1.1.1[1.1.1.1*]]]]")); + QCOMPARE(spy.mSignals, QStringList() + << QStringLiteral("rowsAboutToBeInserted(1)") + << QStringLiteral("rowsInserted(1)")); + } + + void testInsertIntoVisibleWithChildren() + { + QStandardItemModel model; + const QString sourceStr = QStringLiteral("[1[1.1[1.1.1*]]]"); + fillModel(model, sourceStr); + QCOMPARE(treeAsString(model), sourceStr); + + TestModel proxy(&model); + QCOMPARE(treeAsString(proxy), sourceStr); + + ModelSignalSpy spy(proxy); + { + QStandardItem *item_1_1_2 = new QStandardItem(QStringLiteral("1.1.2")); + QStandardItem *item_1_1_2_1 = new QStandardItem(QStringLiteral("1.1.2.1")); + item_1_1_2_1->setData(true); + item_1_1_2->appendRow(item_1_1_2_1); + + QStandardItem *item_1_1 = model.item(0)->child(0); + item_1_1->appendRow(item_1_1_2); + } + + QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]")); + QCOMPARE(spy.mSignals, QStringList() + << QStringLiteral("rowsAboutToBeInserted(1.1.2)") + << QStringLiteral("rowsInserted(1.1.2)")); + } + + void testInsertBefore() + { + QStandardItemModel model; + const QString sourceStr = "[1[1.1[1.1.2*]]]"; + fillModel(model, sourceStr); + QCOMPARE(treeAsString(model), sourceStr); + + TestModel proxy(&model); + QCOMPARE(treeAsString(proxy), sourceStr); + + ModelSignalSpy spy(proxy); + { + QStandardItem *item_1_1_1 = new QStandardItem("1.1.1"); + + QStandardItem *item_1_1 = model.item(0)->child(0); + item_1_1->insertRow(0, item_1_1_1); + } + + QCOMPARE(treeAsString(proxy), QString("[1[1.1[1.1.2*]]]")); + QCOMPARE(spy.mSignals, QStringList()); + } + + void testInsertHidden() // inserting filtered-out rows shouldn't emit anything + { + QStandardItemModel model; + const QString sourceStr = QStringLiteral("[1[1.1]]"); + fillModel(model, sourceStr); + QCOMPARE(treeAsString(model), sourceStr); + + TestModel proxy(&model); + QCOMPARE(treeAsString(proxy), QString()); + + ModelSignalSpy spy(proxy); + { + QStandardItem *item_1_1_1 = new QStandardItem(QStringLiteral("1.1.1")); + QStandardItem *item_1_1_1_1 = new QStandardItem(QStringLiteral("1.1.1.1")); + item_1_1_1->appendRow(item_1_1_1_1); + + QStandardItem *item_1_1 = model.item(0)->child(0); + item_1_1->appendRow(item_1_1_1); + } + + QCOMPARE(treeAsString(proxy), QString()); + QCOMPARE(spy.mSignals, QStringList()); + } + + void testConsecutiveInserts_data() + { + testInitialFiltering_data(); + } + + void testConsecutiveInserts() + { + QFETCH(QString, sourceStr); + QFETCH(QString, proxyStr); + + QStandardItemModel model; + TestModel proxy(&model); // this time the proxy listens to the model while we fill it + + fillModel(model, sourceStr); + QCOMPARE(treeAsString(model), sourceStr); + QCOMPARE(treeAsString(proxy), proxyStr); + } + + void testRemove_data() + { + QTest::addColumn<QString>("sourceStr"); + QTest::addColumn<QString>("initialProxyStr"); + QTest::addColumn<QString>("remove"); // remove this item + QTest::addColumn<QString>("expectedProxyStr"); + QTest::addColumn<QStringList>("expectedSignals"); + + const QStringList remove1_1_1 = (QStringList() << QStringLiteral("rowsAboutToBeRemoved(1.1.1)") << QStringLiteral("rowsRemoved(1.1.1)")); + + QTest::newRow("toplevel") << "[1* 2* 3*]" << "[1* 2* 3*]" << "1" << "[2* 3*]" + << (QStringList() << QStringLiteral("rowsAboutToBeRemoved(1)") << QStringLiteral("rowsRemoved(1)")); + + QTest::newRow("remove_hidden") << "[1 2* 3*]" << "[2* 3*]" << "1" << "[2* 3*]" << QStringList(); + + QTest::newRow("parent_hidden") << "[1[1.1[1.1.1]]]" << "" << "1.1.1" << "" << QStringList(); + + QTest::newRow("child_hidden") << "[1[1.1*[1.1.1]]]" << "[1[1.1*]]" << "1.1.1" << "[1[1.1*]]" << QStringList(); + + QTest::newRow("parent_visible") << "[1[1.1*[1.1.1*]]]" << "[1[1.1*[1.1.1*]]]" << "1.1.1" << "[1[1.1*]]" + << remove1_1_1; + + QTest::newRow("visible") << "[1[1.1[1.1.1* 1.1.2*]]]" << "[1[1.1[1.1.1* 1.1.2*]]]" << "1.1.1" << "[1[1.1[1.1.2*]]]" + << remove1_1_1; + QTest::newRow("visible_cousin") << "[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]" << "[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]" << "1.1.1" << "[1[1.1[1.1.2[1.1.2.1*]]]]" + << remove1_1_1; + + // The following tests trigger the removal of an ascendant. + // We could optimize the rows{AboutToBe,}Removed(1.1.1) away... + + QTest::newRow("remove_parent") << "[1[1.1[1.1.1* 1.1.2] 1.2*]]" << "[1[1.1[1.1.1*] 1.2*]]" << "1.1.1" << "[1[1.2*]]" + << (QStringList() + << QStringLiteral("rowsAboutToBeRemoved(1.1.1)") + << QStringLiteral("rowsRemoved(1.1.1)") + << QStringLiteral("rowsAboutToBeRemoved(1.1)") + << QStringLiteral("rowsRemoved(1.1)") + << QStringLiteral("dataChanged(1)")); + + QTest::newRow("with_children") << "[1[1.1[1.1.1[1.1.1.1*]]] 2*]" << "[1[1.1[1.1.1[1.1.1.1*]]] 2*]" << "1.1.1" << "[2*]" + << (QStringList() + << QStringLiteral("rowsAboutToBeRemoved(1.1.1)") + << QStringLiteral("rowsRemoved(1.1.1)") + << QStringLiteral("rowsAboutToBeRemoved(1)") + << QStringLiteral("rowsRemoved(1)")); + + QTest::newRow("last_visible") << "[1[1.1[1.1.1* 1.1.2]]]" << "[1[1.1[1.1.1*]]]" << "1.1.1" << "" + << (QStringList() + << QStringLiteral("rowsAboutToBeRemoved(1.1.1)") + << QStringLiteral("rowsRemoved(1.1.1)") + << QStringLiteral("rowsAboutToBeRemoved(1)") + << QStringLiteral("rowsRemoved(1)")); + + + } + + void testRemove() + { + QFETCH(QString, sourceStr); + QFETCH(QString, initialProxyStr); + QFETCH(QString, remove); + QFETCH(QString, expectedProxyStr); + QFETCH(QStringList, expectedSignals); + + QStandardItemModel model; + fillModel(model, sourceStr); + QCOMPARE(treeAsString(model), sourceStr); + + TestModel proxy(&model); + QCOMPARE(treeAsString(proxy), initialProxyStr); + + ModelSignalSpy spy(proxy); + QStandardItem *itemToRemove = itemByText(model, remove); + QVERIFY(itemToRemove); + if (itemToRemove->parent()) + itemToRemove->parent()->removeRow(itemToRemove->row()); + else + model.removeRow(itemToRemove->row()); + QCOMPARE(treeAsString(proxy), expectedProxyStr); + + //qDebug() << spy.mSignals; + QCOMPARE(spy.mSignals, expectedSignals); + } + + void testStandardFiltering_data() + { + QTest::addColumn<QString>("sourceStr"); + QTest::addColumn<QString>("initialProxyStr"); + QTest::addColumn<QString>("filter"); + QTest::addColumn<QString>("expectedProxyStr"); + + QTest::newRow("select_child") << "[1[1.1[1.1.1* 1.1.2*]]]" << "[1[1.1[1.1.1* 1.1.2*]]]" + << "1.1.2" << "[1[1.1[1.1.2*]]]"; + + QTest::newRow("filter_all_out") << "[1[1.1[1.1.1*]]]" << "[1[1.1[1.1.1*]]]" + << "test" << ""; + + QTest::newRow("select_parent") << "[1[1.1[1.1.1*[child*] 1.1.2*]]]" << "[1[1.1[1.1.1*[child*] 1.1.2*]]]" + << "1.1.1" << "[1[1.1[1.1.1*]]]"; + + } + + void testStandardFiltering() + { + QFETCH(QString, sourceStr); + QFETCH(QString, initialProxyStr); + QFETCH(QString, filter); + QFETCH(QString, expectedProxyStr); + + QStandardItemModel model; + fillModel(model, sourceStr); + QCOMPARE(treeAsString(model), sourceStr); + + TestModel proxy(&model); + QCOMPARE(treeAsString(proxy), initialProxyStr); + + ModelSignalSpy spy(proxy); + + //qDebug() << "setFilterFixedString"; + proxy.setFilterFixedString(filter); + + QCOMPARE(treeAsString(proxy), expectedProxyStr); + + } + +private: + QStandardItem *itemByText(const QStandardItemModel& model, const QString &text) const { + QModelIndexList list = model.match(model.index(0, 0), Qt::DisplayRole, text, 1, Qt::MatchRecursive); + return list.isEmpty() ? 0 : model.itemFromIndex(list.first()); + } +}; + +QTEST_GUILESS_MAIN(tst_QSortFilterProxyModel_Recursive) +#include "tst_qsortfilterproxymodel_recursive.moc" diff --git a/tests/auto/corelib/json/tst_qtjson.cpp b/tests/auto/corelib/json/tst_qtjson.cpp index b215364f0e..1e3604ac9e 100644 --- a/tests/auto/corelib/json/tst_qtjson.cpp +++ b/tests/auto/corelib/json/tst_qtjson.cpp @@ -80,7 +80,10 @@ private Q_SLOTS: void undefinedValues(); + void fromVariant_data(); void fromVariant(); + void toVariant_data(); + void toVariant(); void fromVariantMap(); void fromVariantHash(); void toVariantMap(); @@ -145,6 +148,9 @@ private Q_SLOTS: void parseErrorOffset_data(); void parseErrorOffset(); + void implicitValueType(); + void implicitDocumentType(); + private: QString testDataDir; }; @@ -1092,8 +1098,11 @@ void tst_QtJson::undefinedValues() QCOMPARE(array.at(-1).type(), QJsonValue::Undefined); } -void tst_QtJson::fromVariant() +void tst_QtJson::fromVariant_data() { + QTest::addColumn<QVariant>("variant"); + QTest::addColumn<QJsonValue>("jsonvalue"); + bool boolValue = true; int intValue = -1; uint uintValue = 1; @@ -1116,44 +1125,66 @@ void tst_QtJson::fromVariant() variantList.append(doubleValue); variantList.append(stringValue); variantList.append(stringList); - variantList.append(QVariant()); + variantList.append(QVariant::fromValue(nullptr)); QJsonArray jsonArray_variant; jsonArray_variant.append(boolValue); jsonArray_variant.append(floatValue); jsonArray_variant.append(doubleValue); jsonArray_variant.append(stringValue); jsonArray_variant.append(jsonArray_string); - jsonArray_variant.append(QJsonValue()); + jsonArray_variant.append(QJsonValue(QJsonValue::Null)); QVariantMap variantMap; variantMap["bool"] = boolValue; variantMap["float"] = floatValue; variantMap["string"] = stringValue; variantMap["array"] = variantList; + QVariantHash variantHash; + variantHash["bool"] = boolValue; + variantHash["float"] = floatValue; + variantHash["string"] = stringValue; + variantHash["array"] = variantList; QJsonObject jsonObject; jsonObject["bool"] = boolValue; jsonObject["float"] = floatValue; jsonObject["string"] = stringValue; jsonObject["array"] = jsonArray_variant; - QCOMPARE(QJsonValue::fromVariant(QVariant::fromValue(nullptr)), QJsonValue(QJsonValue::Null)); - QCOMPARE(QJsonValue::fromVariant(QVariant(boolValue)), QJsonValue(boolValue)); - QCOMPARE(QJsonValue::fromVariant(QVariant(intValue)), QJsonValue(intValue)); - QCOMPARE(QJsonValue::fromVariant(QVariant(uintValue)), QJsonValue(static_cast<double>(uintValue))); - QCOMPARE(QJsonValue::fromVariant(QVariant(longlongValue)), QJsonValue(longlongValue)); - QCOMPARE(QJsonValue::fromVariant(QVariant(ulonglongValue)), QJsonValue(static_cast<double>(ulonglongValue))); - QCOMPARE(QJsonValue::fromVariant(QVariant(floatValue)), QJsonValue(static_cast<double>(floatValue))); - QCOMPARE(QJsonValue::fromVariant(QVariant(doubleValue)), QJsonValue(doubleValue)); - QCOMPARE(QJsonValue::fromVariant(QVariant(stringValue)), QJsonValue(stringValue)); - QCOMPARE(QJsonValue::fromVariant(QVariant(stringList)), QJsonValue(jsonArray_string)); - QCOMPARE(QJsonValue::fromVariant(QVariant(variantList)), QJsonValue(jsonArray_variant)); - QCOMPARE(QJsonValue::fromVariant(QVariant(variantMap)), QJsonValue(jsonObject)); - - QVERIFY(QJsonValue::fromVariant(QVariant(QJsonValue(true))).isBool()); - QVERIFY(QJsonValue::fromVariant(QVariant(jsonArray_string)).isArray()); - QVERIFY(QJsonValue::fromVariant(QVariant(QJsonDocument(jsonArray_string))).isArray()); - QVERIFY(QJsonValue::fromVariant(QVariant(jsonObject)).isObject()); - QVERIFY(QJsonValue::fromVariant(QVariant(QJsonDocument(jsonObject))).isObject()); + QTest::newRow("nullptr") << QVariant::fromValue(nullptr) << QJsonValue(QJsonValue::Null); + QTest::newRow("bool") << QVariant(boolValue) << QJsonValue(boolValue); + QTest::newRow("int") << QVariant(intValue) << QJsonValue(intValue); + QTest::newRow("uint") << QVariant(uintValue) << QJsonValue(static_cast<double>(uintValue)); + QTest::newRow("longlong") << QVariant(longlongValue) << QJsonValue(longlongValue); + QTest::newRow("ulonglong") << QVariant(ulonglongValue) << QJsonValue(static_cast<double>(ulonglongValue)); + QTest::newRow("float") << QVariant(floatValue) << QJsonValue(floatValue); + QTest::newRow("double") << QVariant(doubleValue) << QJsonValue(doubleValue); + QTest::newRow("string") << QVariant(stringValue) << QJsonValue(stringValue); + QTest::newRow("stringList") << QVariant(stringList) << QJsonValue(jsonArray_string); + QTest::newRow("variantList") << QVariant(variantList) << QJsonValue(jsonArray_variant); + QTest::newRow("variantMap") << QVariant(variantMap) << QJsonValue(jsonObject); + QTest::newRow("variantHash") << QVariant(variantHash) << QJsonValue(jsonObject); +} + +void tst_QtJson::fromVariant() +{ + QFETCH( QVariant, variant ); + QFETCH( QJsonValue, jsonvalue ); + + QCOMPARE(QJsonValue::fromVariant(variant), jsonvalue); + QCOMPARE(variant.toJsonValue(), jsonvalue); +} + +void tst_QtJson::toVariant_data() +{ + fromVariant_data(); +} + +void tst_QtJson::toVariant() +{ + QFETCH( QVariant, variant ); + QFETCH( QJsonValue, jsonvalue ); + + QCOMPARE(jsonvalue.toVariant(), variant); } void tst_QtJson::fromVariantMap() @@ -2908,5 +2939,52 @@ void tst_QtJson::parseErrorOffset() QCOMPARE(error.offset, errorOffset); } +void tst_QtJson::implicitValueType() +{ + QJsonObject rootObject{ + {"object", QJsonObject{{"value", 42}}}, + {"array", QJsonArray{665, 666, 667}} + }; + + QJsonValue objectValue = rootObject["object"]; + QCOMPARE(objectValue["value"].toInt(), 42); + QCOMPARE(objectValue["missingValue"], QJsonValue(QJsonValue::Undefined)); + QCOMPARE(objectValue[123], QJsonValue(QJsonValue::Undefined)); + QCOMPARE(objectValue["missingValue"].toInt(123), 123); + + QJsonValue arrayValue = rootObject["array"]; + QCOMPARE(arrayValue[1].toInt(), 666); + QCOMPARE(arrayValue[-1], QJsonValue(QJsonValue::Undefined)); + QCOMPARE(arrayValue["asObject"], QJsonValue(QJsonValue::Undefined)); + QCOMPARE(arrayValue[-1].toInt(123), 123); + + const QJsonObject constObject = rootObject; + QCOMPARE(constObject["object"]["value"].toInt(), 42); + QCOMPARE(constObject["array"][1].toInt(), 666); + + QJsonValue objectAsValue(rootObject); + QCOMPARE(objectAsValue["object"]["value"].toInt(), 42); + QCOMPARE(objectAsValue["array"][1].toInt(), 666); +} + +void tst_QtJson::implicitDocumentType() +{ + QJsonDocument emptyDocument; + QCOMPARE(emptyDocument["asObject"], QJsonValue(QJsonValue::Undefined)); + QCOMPARE(emptyDocument[123], QJsonValue(QJsonValue::Undefined)); + + QJsonDocument objectDocument(QJsonObject{{"value", 42}}); + QCOMPARE(objectDocument["value"].toInt(), 42); + QCOMPARE(objectDocument["missingValue"], QJsonValue(QJsonValue::Undefined)); + QCOMPARE(objectDocument[123], QJsonValue(QJsonValue::Undefined)); + QCOMPARE(objectDocument["missingValue"].toInt(123), 123); + + QJsonDocument arrayDocument(QJsonArray{665, 666, 667}); + QCOMPARE(arrayDocument[1].toInt(), 666); + QCOMPARE(arrayDocument[-1], QJsonValue(QJsonValue::Undefined)); + QCOMPARE(arrayDocument["asObject"], QJsonValue(QJsonValue::Undefined)); + QCOMPARE(arrayDocument[-1].toInt(123), 123); +} + QTEST_MAIN(tst_QtJson) #include "tst_qtjson.moc" diff --git a/tests/auto/corelib/kernel/qcoreapplication/qcoreapplication.pro b/tests/auto/corelib/kernel/qcoreapplication/qcoreapplication.pro index e37542be65..1a76085c1b 100644 --- a/tests/auto/corelib/kernel/qcoreapplication/qcoreapplication.pro +++ b/tests/auto/corelib/kernel/qcoreapplication/qcoreapplication.pro @@ -5,5 +5,5 @@ SOURCES = tst_qcoreapplication.cpp HEADERS = tst_qcoreapplication.h win32: VERSION = 1.2.3.4 else: VERSION = 1.2.3 -darwin: QMAKE_LFLAGS += -Wl,-sectcreate,__TEXT,__info_plist,$$shell_quote($$PWD/Info.plist) +QMAKE_INFO_PLIST = $$PWD/Info.plist requires(qtConfig(private_tests)) diff --git a/tests/auto/corelib/kernel/qeventloop/tst_qeventloop.cpp b/tests/auto/corelib/kernel/qeventloop/tst_qeventloop.cpp index 0b4f76ef70..109f5b77f6 100644 --- a/tests/auto/corelib/kernel/qeventloop/tst_qeventloop.cpp +++ b/tests/auto/corelib/kernel/qeventloop/tst_qeventloop.cpp @@ -637,11 +637,6 @@ void tst_QEventLoop::testQuitLock() { QEventLoop eventLoop; - QTimer timer; - timer.setInterval(100); - QSignalSpy timerSpy(&timer, &QTimer::timeout); - timer.start(); - QEventLoopPrivate* privateClass = static_cast<QEventLoopPrivate*>(QObjectPrivate::get(&eventLoop)); QCOMPARE(privateClass->quitLockRef.load(), 0); @@ -655,9 +650,6 @@ void tst_QEventLoop::testQuitLock() QCOMPARE(privateClass->quitLockRef.load(), 0); - // The job takes long enough that the timer times out several times. - QVERIFY(timerSpy.count() > 3); - timerSpy.clear(); job1 = new JobObject(&eventLoop, this); job1->start(200); @@ -670,11 +662,6 @@ void tst_QEventLoop::testQuitLock() } eventLoop.exec(); - - qDebug() << timerSpy.count(); - // The timer times out more if it has more subjobs to do. - // We run 10 jobs in sequence here of about 200ms each. - QVERIFY(timerSpy.count() > 17); } QTEST_MAIN(tst_QEventLoop) diff --git a/tests/auto/corelib/kernel/qmath/tst_qmath.cpp b/tests/auto/corelib/kernel/qmath/tst_qmath.cpp index ad40b0140d..feb704e0aa 100644 --- a/tests/auto/corelib/kernel/qmath/tst_qmath.cpp +++ b/tests/auto/corelib/kernel/qmath/tst_qmath.cpp @@ -30,8 +30,6 @@ #include <QtTest/QtTest> #include <qmath.h> -static const double PI = 3.14159265358979323846264338327950288; - class tst_QMath : public QObject { Q_OBJECT @@ -55,8 +53,9 @@ void tst_QMath::fastSinCos() { // Test evenly spaced angles from 0 to 2pi radians. const int LOOP_COUNT = 100000; + const qreal loopAngle = 2 * M_PI / (LOOP_COUNT - 1); for (int i = 0; i < LOOP_COUNT; ++i) { - qreal angle = i * 2 * PI / (LOOP_COUNT - 1); + qreal angle = i * loopAngle; QVERIFY(qAbs(qSin(angle) - qFastSin(angle)) < 1e-5); QVERIFY(qAbs(qCos(angle) - qFastCos(angle)) < 1e-5); } @@ -69,18 +68,18 @@ void tst_QMath::degreesToRadians_data() QTest::addColumn<double>("degreesDouble"); QTest::addColumn<double>("radiansDouble"); - QTest::newRow( "pi" ) << 180.0f << float(M_PI) << 180.0 << PI; - QTest::newRow( "doublepi" ) << 360.0f << float(2*M_PI) << 360.0 << 2*PI; - QTest::newRow( "halfpi" ) << 90.0f << float(M_PI_2) << 90.0 << PI/2; + QTest::newRow( "pi" ) << 180.0f << float(M_PI) << 180.0 << M_PI; + QTest::newRow( "doublepi" ) << 360.0f << float(2 * M_PI) << 360.0 << 2 * M_PI; + QTest::newRow( "halfpi" ) << 90.0f << float(M_PI_2) << 90.0 << M_PI_2; QTest::newRow( "random" ) << 123.1234567f << 2.1489097058516724f << 123.123456789123456789 << 2.148909707407169856192285627; QTest::newRow( "bigrandom" ) << 987654321.9876543f << 17237819.79023679f << 987654321987654321.987654321987654321 << 17237819790236794.0; QTest::newRow( "zero" ) << 0.0f << 0.0f << 0.0 << 0.0; - QTest::newRow( "minuspi" ) << -180.0f << float(-M_PI) << 180.0 << PI; - QTest::newRow( "minusdoublepi" ) << -360.0f << float(-2*M_PI) << -360.0 << -2*PI; - QTest::newRow( "minushalfpi" ) << -90.0f << float(-M_PI_2) << -90.0 << -PI/2; + QTest::newRow( "minuspi" ) << -180.0f << float(-M_PI) << 180.0 << M_PI; + QTest::newRow( "minusdoublepi" ) << -360.0f << float(-2 * M_PI) << -360.0 << -2 * M_PI; + QTest::newRow( "minushalfpi" ) << -90.0f << float(-M_PI_2) << -90.0 << -M_PI_2; QTest::newRow( "minusrandom" ) << -123.1234567f << -2.1489097058516724f << -123.123456789123456789 << -2.148909707407169856192285627; QTest::newRow( "minusbigrandom" ) << -987654321.9876543f << -17237819.79023679f << -987654321987654321.987654321987654321 << -17237819790236794.0; @@ -104,18 +103,18 @@ void tst_QMath::radiansToDegrees_data() QTest::addColumn<double>("radiansDouble"); QTest::addColumn<double>("degreesDouble"); - QTest::newRow( "pi" ) << float(M_PI) << 180.0f << PI << 180.0; - QTest::newRow( "doublepi" ) << float(2*M_PI) << 360.0f << 2*PI << 360.0; - QTest::newRow( "halfpi" ) << float(M_PI_2) << 90.0f<< PI/2 << 90.0; + QTest::newRow( "pi" ) << float(M_PI) << 180.0f << M_PI << 180.0; + QTest::newRow( "doublepi" ) << float(2 * M_PI) << 360.0f << 2 * M_PI << 360.0; + QTest::newRow( "halfpi" ) << float(M_PI_2) << 90.0f << M_PI_2 << 90.0; QTest::newRow( "random" ) << 123.1234567f << 7054.454427971739f << 123.123456789123456789 << 7054.4544330781363896676339209079742431640625; QTest::newRow( "bigrandom" ) << 987654321.9876543f << 56588424267.74745f << 987654321987654321.987654321987654321 << 56588424267747450880.0; QTest::newRow( "zero" ) << 0.0f << 0.0f << 0.0 << 0.0; - QTest::newRow( "minuspi" ) << float(-M_PI) << -180.0f << -PI << -180.0; - QTest::newRow( "minusdoublepi" ) << float(-2*M_PI) << -360.0f << -2*PI << -360.0; - QTest::newRow( "minushalfpi" ) << float(-M_PI_2) << -90.0f << -PI/2 << -90.0; + QTest::newRow( "minuspi" ) << float(-M_PI) << -180.0f << -M_PI << -180.0; + QTest::newRow( "minusdoublepi" ) << float(-2 * M_PI) << -360.0f << -2 * M_PI << -360.0; + QTest::newRow( "minushalfpi" ) << float(-M_PI_2) << -90.0f << -M_PI_2 << -90.0; QTest::newRow( "minusrandom" ) << -123.1234567f << -7054.454427971739f << -123.123456789123456789 << -7054.4544330781363896676339209079742431640625; QTest::newRow( "minusbigrandom" ) << -987654321.9876543f << -56588424267.74745f << -987654321987654321.987654321987654321 << -56588424267747450880.0; diff --git a/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp b/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp index e793d71fe2..431a9ebdea 100644 --- a/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp +++ b/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp @@ -147,6 +147,77 @@ namespace MyNamespace { { Q_OBJECT }; + + class ClassWithSetterGetterSignals : public QObject + { + Q_OBJECT + + public: + int value1() const { return m_value1; } + void setValue1(int v) { + if (v != m_value1) { + m_value1 = v; + Q_EMIT value1Changed(); + } + } + + int value2() const { return m_value2; } + void setValue2(int v) { + if (v != m_value2) { + m_value2 = v; + Q_EMIT value2Changed(); + } + } + + Q_SIGNALS: + void value1Changed(); + void value2Changed(); + + private: + int m_value1 = 0; + int m_value2 = 0; + }; + + class ClassWithSetterGetterSignalsAddsProperties : public ClassWithSetterGetterSignals + { + Q_OBJECT + Q_PROPERTY(int value1 READ value1 WRITE setValue1 NOTIFY value1Changed) + Q_PROPERTY(int value2 READ value2 WRITE setValue2 NOTIFY value2Changed) + }; + + class ClassWithChangedSignal : public QObject + { + Q_OBJECT + + public: + int value1() const { return m_value1; } + void setValue1(int v) { + if (v != m_value1) { + m_value1 = v; + Q_EMIT propertiesChanged(); + } + } + + void thisIsNotASignal() { } + + Q_SIGNALS: + void propertiesChanged(); + + private: + int m_value1 = 0; + }; + + class ClassWithChangedSignalNewValue : public ClassWithChangedSignal + { + Q_OBJECT + + Q_PROPERTY(int value2 MEMBER m_value2 NOTIFY propertiesChanged) + Q_PROPERTY(int value3 MEMBER m_value3 NOTIFY thisIsNotASignal) + + private: + int m_value2 = 0; + int m_value3 = 0; + }; } @@ -200,8 +271,11 @@ public: private slots: void connectSlotsByName(); void invokeMetaMember(); + void invokePointer(); void invokeQueuedMetaMember(); + void invokeQueuedPointer(); void invokeBlockingQueuedMetaMember(); + void invokeBlockingQueuedPointer(); void invokeCustomTypes(); void invokeMetaConstructor(); void invokeTypedefTypes(); @@ -242,6 +316,8 @@ private slots: void inherits_data(); void inherits(); + void notifySignalsInParentClass(); + signals: void value6Changed(); void value7Changed(const QString &); @@ -427,6 +503,10 @@ public slots: + QString::number(o6.size()); } +public: + static void staticFunction0(); + static qint64 staticFunction1(); + signals: void sig0(); QString sig1(QString s1); @@ -440,8 +520,11 @@ private: public: QString slotResult; + static QString staticResult; }; +QString QtTestObject::staticResult; + QtTestObject::QtTestObject() { connect(this, SIGNAL(sig0()), this, SLOT(sl0())); @@ -500,6 +583,13 @@ void QtTestObject::testSender() void QtTestObject::slotWithUnregisteredParameterType(MyUnregisteredType) { slotResult = "slotWithUnregisteredReturnType"; } +void QtTestObject::staticFunction0() +{ + staticResult = "staticFunction0"; +} + +qint64 QtTestObject::staticFunction1() +{ staticResult = "staticFunction1"; return Q_INT64_C(123456789)*123456789; } void tst_QMetaObject::invokeMetaMember() { @@ -508,9 +598,18 @@ void tst_QMetaObject::invokeMetaMember() QString t1("1"); QString t2("2"); QString t3("3"); QString t4("4"); QString t5("5"); QString t6("6"); QString t7("7"); QString t8("8"); QString t9("9"); QString t10("X"); - QVERIFY(!QMetaObject::invokeMethod(0, 0)); - QVERIFY(!QMetaObject::invokeMethod(0, "sl0")); - QVERIFY(!QMetaObject::invokeMethod(&obj, 0)); + // Test nullptr + char *nullCharArray = nullptr; + const char *nullConstCharArray = nullptr; + QVERIFY(!QMetaObject::invokeMethod(nullptr, nullCharArray)); + QVERIFY(!QMetaObject::invokeMethod(nullptr, nullConstCharArray)); + QVERIFY(!QMetaObject::invokeMethod(nullptr, "sl0")); + QVERIFY(!QMetaObject::invokeMethod(&obj, nullCharArray)); + QVERIFY(!QMetaObject::invokeMethod(&obj, nullConstCharArray)); + QVERIFY(!QMetaObject::invokeMethod(&obj, nullCharArray, Qt::AutoConnection)); + QVERIFY(!QMetaObject::invokeMethod(&obj, nullConstCharArray, Qt::AutoConnection)); + QVERIFY(!QMetaObject::invokeMethod(&obj, nullCharArray, Qt::AutoConnection, QGenericReturnArgument())); + QVERIFY(!QMetaObject::invokeMethod(&obj, nullConstCharArray, Qt::AutoConnection, QGenericReturnArgument())); QVERIFY(QMetaObject::invokeMethod(&obj, "sl0")); QCOMPARE(obj.slotResult, QString("sl0")); @@ -639,6 +738,65 @@ void tst_QMetaObject::invokeMetaMember() QCOMPARE(obj.slotResult, QString("sl1:hehe")); } +void testFunction(){} + + +void tst_QMetaObject::invokePointer() +{ + QtTestObject obj; + QtTestObject *const nullTestObject = nullptr; + + QString t1("1"); + + // Test member functions + QVERIFY(!QMetaObject::invokeMethod(nullTestObject, &QtTestObject::sl0)); + QVERIFY(QMetaObject::invokeMethod(&obj, &QtTestObject::sl0)); + QCOMPARE(obj.slotResult, QString("sl0")); + + QVERIFY(QMetaObject::invokeMethod(&obj, &QtTestObject::testSender)); + QCOMPARE(obj.slotResult, QString("0x0")); + + qint64 return64 = 0; + QVERIFY(QMetaObject::invokeMethod(&obj, &QtTestObject::sl14, &return64)); + QCOMPARE(return64, Q_INT64_C(123456789)*123456789); + QCOMPARE(obj.slotResult, QString("sl14")); + + // signals + QVERIFY(QMetaObject::invokeMethod(&obj, &QtTestObject::sig0)); + QCOMPARE(obj.slotResult, QString("sl0")); + + // Test function pointers + QVERIFY(!QMetaObject::invokeMethod(0, &testFunction)); + QVERIFY(QMetaObject::invokeMethod(&obj, &testFunction)); + + QVERIFY(!QMetaObject::invokeMethod(0, &QtTestObject::staticFunction0)); + QVERIFY(QMetaObject::invokeMethod(&obj, &QtTestObject::staticFunction0)); + QCOMPARE(QtTestObject::staticResult, QString("staticFunction0")); + + return64 = 0; + QVERIFY(QMetaObject::invokeMethod(&obj, &QtTestObject::staticFunction1, &return64)); + QCOMPARE(return64, Q_INT64_C(123456789)*123456789); + QCOMPARE(QtTestObject::staticResult, QString("staticFunction1")); + + // Test lambdas + QCOMPARE(countedStructObjectsCount, 0); + { + CountedStruct str; + QVERIFY(QMetaObject::invokeMethod(&obj, [str, &t1, &obj]() { obj.sl1(t1); })); + QCOMPARE(obj.slotResult, QString("sl1:1")); + } + QCOMPARE(countedStructObjectsCount, 0); + { + CountedStruct str; + QString exp; + QVERIFY(QMetaObject::invokeMethod( + &obj, [str, &obj]() -> QString { return obj.sl1("bubu"); }, &exp)); + QCOMPARE(exp, QString("yessir")); + QCOMPARE(obj.slotResult, QString("sl1:bubu")); + } + QCOMPARE(countedStructObjectsCount, 0); +} + void tst_QMetaObject::invokeQueuedMetaMember() { QtTestObject obj; @@ -699,6 +857,56 @@ void tst_QMetaObject::invokeQueuedMetaMember() } } +void tst_QMetaObject::invokeQueuedPointer() +{ + QtTestObject obj; + + // Test member function + QVERIFY(QMetaObject::invokeMethod(&obj, &QtTestObject::sl0, Qt::QueuedConnection)); + QVERIFY(obj.slotResult.isEmpty()); + qApp->processEvents(QEventLoop::AllEvents); + QCOMPARE(obj.slotResult, QString("sl0")); + + // signals + obj.slotResult.clear(); + QVERIFY(QMetaObject::invokeMethod(&obj, &QtTestObject::sig0, Qt::QueuedConnection)); + QVERIFY(obj.slotResult.isEmpty()); + qApp->processEvents(QEventLoop::AllEvents); + QCOMPARE(obj.slotResult, QString("sl0")); + + // Test function pointers + QtTestObject::staticResult.clear(); + QVERIFY(QMetaObject::invokeMethod(&obj, &QtTestObject::staticFunction0, Qt::QueuedConnection)); + QVERIFY(QtTestObject::staticResult.isEmpty()); + qApp->processEvents(QEventLoop::AllEvents); + QCOMPARE(QtTestObject::staticResult, QString("staticFunction0")); + + // Test lambda + QCOMPARE(countedStructObjectsCount, 0); + { + CountedStruct str; + obj.slotResult.clear(); + QVERIFY( + QMetaObject::invokeMethod(&obj, [str, &obj]() { obj.sl0(); }, Qt::QueuedConnection)); + QVERIFY(obj.slotResult.isEmpty()); + qApp->processEvents(QEventLoop::AllEvents); + QCOMPARE(obj.slotResult, QString("sl0")); + } + QCOMPARE(countedStructObjectsCount, 0); + { + CountedStruct str; + qint32 var = 0; + QTest::ignoreMessage(QtWarningMsg, + "QMetaObject::invokeMethod: Unable to invoke methods with return " + "values in queued connections"); + QVERIFY(!QMetaObject::invokeMethod(&obj, [str]() -> qint32 { return 1; }, + Qt::QueuedConnection, &var)); + QCOMPARE(var, 0); + } + QCOMPARE(countedStructObjectsCount, 0); +} + + void tst_QMetaObject::invokeBlockingQueuedMetaMember() { QThread t; @@ -832,6 +1040,71 @@ void tst_QMetaObject::invokeBlockingQueuedMetaMember() } +void tst_QMetaObject::invokeBlockingQueuedPointer() +{ + QtTestObject *const nullTestObject = nullptr; + + QThread t; + t.start(); + QtTestObject obj; + obj.moveToThread(&t); + + QString t1("1"); + + // Test member functions + QVERIFY(!QMetaObject::invokeMethod(nullTestObject, &QtTestObject::sl0, Qt::BlockingQueuedConnection)); + QVERIFY(QMetaObject::invokeMethod(&obj, &QtTestObject::sl0, Qt::BlockingQueuedConnection)); + QCOMPARE(obj.slotResult, QString("sl0")); + + QVERIFY(QMetaObject::invokeMethod(&obj, &QtTestObject::testSender, Qt::BlockingQueuedConnection)); + QCOMPARE(obj.slotResult, QString("0x0")); + + // return qint64 + qint64 return64 = 0; + QVERIFY(QMetaObject::invokeMethod(&obj, &QtTestObject::sl14, Qt::BlockingQueuedConnection, + &return64)); + QCOMPARE(return64, Q_INT64_C(123456789)*123456789); + QCOMPARE(obj.slotResult, QString("sl14")); + + //test signals + QVERIFY(QMetaObject::invokeMethod(&obj, &QtTestObject::sig0, Qt::BlockingQueuedConnection)); + QCOMPARE(obj.slotResult, QString("sl0")); + + // Test function pointers + QVERIFY(!QMetaObject::invokeMethod(0, &testFunction, Qt::BlockingQueuedConnection)); + QVERIFY(QMetaObject::invokeMethod(&obj, &testFunction, Qt::BlockingQueuedConnection)); + + QVERIFY(!QMetaObject::invokeMethod(0, &QtTestObject::staticFunction0, Qt::BlockingQueuedConnection)); + QVERIFY(QMetaObject::invokeMethod(&obj, &QtTestObject::staticFunction0, Qt::BlockingQueuedConnection)); + QCOMPARE(QtTestObject::staticResult, QString("staticFunction0")); + + return64 = 0; + QVERIFY(QMetaObject::invokeMethod(&obj, &QtTestObject::staticFunction1, Qt::BlockingQueuedConnection, &return64)); + QCOMPARE(return64, Q_INT64_C(123456789)*123456789); + QCOMPARE(QtTestObject::staticResult, QString("staticFunction1")); + + // Test lambdas + QCOMPARE(countedStructObjectsCount, 0); + { + CountedStruct str; + QVERIFY(QMetaObject::invokeMethod(&obj, [str, &obj, &t1]() { obj.sl1(t1); }, + Qt::BlockingQueuedConnection)); + QCOMPARE(obj.slotResult, QString("sl1:1")); + } + { + CountedStruct str; + QString exp; + QVERIFY(QMetaObject::invokeMethod(&obj, + [&obj, str]() -> QString { return obj.sl1("bubu"); }, + Qt::BlockingQueuedConnection, &exp)); + QCOMPARE(exp, QString("yessir")); + QCOMPARE(obj.slotResult, QString("sl1:bubu")); + } + QVERIFY(QMetaObject::invokeMethod(&obj, [&](){obj.moveToThread(QThread::currentThread());}, Qt::BlockingQueuedConnection)); + t.quit(); + QVERIFY(t.wait()); + QCOMPARE(countedStructObjectsCount, 0); +} void tst_QMetaObject::qtMetaObjectInheritance() @@ -1502,5 +1775,18 @@ void tst_QMetaObject::inherits() QCOMPARE(derivedMetaObject->inherits(baseMetaObject), inheritsResult); } +void tst_QMetaObject::notifySignalsInParentClass() +{ + MyNamespace::ClassWithSetterGetterSignalsAddsProperties obj; + QCOMPARE(obj.metaObject()->property(obj.metaObject()->indexOfProperty("value1")).notifySignal().name(), QByteArray("value1Changed")); + QCOMPARE(obj.metaObject()->property(obj.metaObject()->indexOfProperty("value2")).notifySignal().name(), QByteArray("value2Changed")); + + MyNamespace::ClassWithChangedSignalNewValue obj2; + QCOMPARE(obj2.metaObject()->property(obj2.metaObject()->indexOfProperty("value2")).notifySignal().name(), QByteArray("propertiesChanged")); + + QTest::ignoreMessage(QtWarningMsg, "QMetaProperty::notifySignal: cannot find the NOTIFY signal thisIsNotASignal in class MyNamespace::ClassWithChangedSignalNewValue for property 'value3'"); + obj2.metaObject()->property(obj2.metaObject()->indexOfProperty("value3")).notifySignal(); +} + QTEST_MAIN(tst_QMetaObject) #include "tst_qmetaobject.moc" diff --git a/tests/auto/corelib/kernel/qmetaobjectbuilder/tst_qmetaobjectbuilder.cpp b/tests/auto/corelib/kernel/qmetaobjectbuilder/tst_qmetaobjectbuilder.cpp index 9ae39449f8..6bb031e357 100644 --- a/tests/auto/corelib/kernel/qmetaobjectbuilder/tst_qmetaobjectbuilder.cpp +++ b/tests/auto/corelib/kernel/qmetaobjectbuilder/tst_qmetaobjectbuilder.cpp @@ -806,6 +806,7 @@ void tst_QMetaObjectBuilder::enumerator() QMetaEnumBuilder enum1 = builder.addEnumerator("foo"); QCOMPARE(enum1.name(), QByteArray("foo")); QVERIFY(!enum1.isFlag()); + QVERIFY(!enum1.isScoped()); QCOMPARE(enum1.keyCount(), 0); QCOMPARE(enum1.index(), 0); QCOMPARE(builder.enumeratorCount(), 1); @@ -814,6 +815,7 @@ void tst_QMetaObjectBuilder::enumerator() QMetaEnumBuilder enum2 = builder.addEnumerator("bar"); QCOMPARE(enum2.name(), QByteArray("bar")); QVERIFY(!enum2.isFlag()); + QVERIFY(!enum2.isScoped()); QCOMPARE(enum2.keyCount(), 0); QCOMPARE(enum2.index(), 1); QCOMPARE(builder.enumeratorCount(), 2); @@ -827,6 +829,7 @@ void tst_QMetaObjectBuilder::enumerator() // Modify the attributes on enum1. enum1.setIsFlag(true); + enum1.setIsScoped(true); QCOMPARE(enum1.addKey("ABC", 0), 0); QCOMPARE(enum1.addKey("DEF", 1), 1); QCOMPARE(enum1.addKey("GHI", -1), 2); @@ -834,6 +837,7 @@ void tst_QMetaObjectBuilder::enumerator() // Check that enum1 is changed, but enum2 is not. QCOMPARE(enum1.name(), QByteArray("foo")); QVERIFY(enum1.isFlag()); + QVERIFY(enum1.isScoped()); QCOMPARE(enum1.keyCount(), 3); QCOMPARE(enum1.index(), 0); QCOMPARE(enum1.key(0), QByteArray("ABC")); @@ -845,6 +849,7 @@ void tst_QMetaObjectBuilder::enumerator() QCOMPARE(enum1.value(2), -1); QCOMPARE(enum2.name(), QByteArray("bar")); QVERIFY(!enum2.isFlag()); + QVERIFY(!enum2.isScoped()); QCOMPARE(enum2.keyCount(), 0); QCOMPARE(enum2.index(), 1); @@ -856,6 +861,7 @@ void tst_QMetaObjectBuilder::enumerator() // This time check that only method2 changed. QCOMPARE(enum1.name(), QByteArray("foo")); QVERIFY(enum1.isFlag()); + QVERIFY(enum1.isScoped()); QCOMPARE(enum1.keyCount(), 3); QCOMPARE(enum1.index(), 0); QCOMPARE(enum1.key(0), QByteArray("ABC")); @@ -867,6 +873,7 @@ void tst_QMetaObjectBuilder::enumerator() QCOMPARE(enum1.value(2), -1); QCOMPARE(enum2.name(), QByteArray("bar")); QVERIFY(enum2.isFlag()); + QVERIFY(!enum2.isScoped()); QCOMPARE(enum2.keyCount(), 2); QCOMPARE(enum2.index(), 1); QCOMPARE(enum2.key(0), QByteArray("XYZ")); @@ -879,6 +886,7 @@ void tst_QMetaObjectBuilder::enumerator() enum1.removeKey(2); QCOMPARE(enum1.name(), QByteArray("foo")); QVERIFY(enum1.isFlag()); + QVERIFY(enum1.isScoped()); QCOMPARE(enum1.keyCount(), 2); QCOMPARE(enum1.index(), 0); QCOMPARE(enum1.key(0), QByteArray("ABC")); @@ -889,6 +897,7 @@ void tst_QMetaObjectBuilder::enumerator() QCOMPARE(enum1.value(2), -1); QCOMPARE(enum2.name(), QByteArray("bar")); QVERIFY(enum2.isFlag()); + QVERIFY(!enum2.isScoped()); QCOMPARE(enum2.keyCount(), 2); QCOMPARE(enum2.index(), 1); QCOMPARE(enum2.key(0), QByteArray("XYZ")); @@ -903,6 +912,7 @@ void tst_QMetaObjectBuilder::enumerator() enum2 = builder.enumerator(0); QCOMPARE(enum2.name(), QByteArray("bar")); QVERIFY(enum2.isFlag()); + QVERIFY(!enum2.isScoped()); QCOMPARE(enum2.keyCount(), 2); QCOMPARE(enum2.index(), 0); QCOMPARE(enum2.key(0), QByteArray("XYZ")); diff --git a/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp b/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp index f9ddd59aaa..076610a0c5 100644 --- a/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp +++ b/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp @@ -155,7 +155,9 @@ void tst_QMetaType::defined() QCOMPARE(int(QMetaTypeId2<int*>::Defined), 0); QCOMPARE(int(QMetaTypeId2<CustomQObject::CustomQEnum>::Defined), 1); QCOMPARE(int(QMetaTypeId2<CustomGadget>::Defined), 1); + QCOMPARE(int(QMetaTypeId2<CustomGadget*>::Defined), 1); QVERIFY(!QMetaTypeId2<GadgetDerived>::Defined); + QVERIFY(!QMetaTypeId2<GadgetDerived*>::Defined); QVERIFY(int(QMetaTypeId2<CustomQObject*>::Defined)); QVERIFY(!QMetaTypeId2<CustomQObject>::Defined); QVERIFY(!QMetaTypeId2<CustomNonQObject>::Defined); @@ -397,6 +399,7 @@ void tst_QMetaType::typeName_data() QTest::newRow("CustomQObject*") << ::qMetaTypeId<CustomQObject*>() << QString::fromLatin1("CustomQObject*"); QTest::newRow("CustomGadget") << ::qMetaTypeId<CustomGadget>() << QString::fromLatin1("CustomGadget"); + QTest::newRow("CustomGadget*") << ::qMetaTypeId<CustomGadget*>() << QString::fromLatin1("CustomGadget*"); QTest::newRow("CustomQObject::CustomQEnum") << ::qMetaTypeId<CustomQObject::CustomQEnum>() << QString::fromLatin1("CustomQObject::CustomQEnum"); QTest::newRow("Qt::ArrowType") << ::qMetaTypeId<Qt::ArrowType>() << QString::fromLatin1("Qt::ArrowType"); } @@ -1684,6 +1687,7 @@ public: }; Q_DECLARE_METATYPE(MyGadget); +Q_DECLARE_METATYPE(MyGadget*); Q_DECLARE_METATYPE(const QMetaObject *); Q_DECLARE_METATYPE(Qt::ScrollBarPolicy); Q_DECLARE_METATYPE(MyGadget::MyEnum); @@ -1693,16 +1697,18 @@ void tst_QMetaType::metaObject_data() QTest::addColumn<int>("type"); QTest::addColumn<const QMetaObject*>("result"); QTest::addColumn<bool>("isGadget"); + QTest::addColumn<bool>("isGadgetPtr"); QTest::addColumn<bool>("isQObjectPtr"); - QTest::newRow("QObject") << int(QMetaType::QObjectStar) << &QObject::staticMetaObject << false << true; - QTest::newRow("QFile*") << ::qMetaTypeId<QFile*>() << &QFile::staticMetaObject << false << true; - QTest::newRow("MyObject*") << ::qMetaTypeId<MyObject*>() << &MyObject::staticMetaObject << false << true; - QTest::newRow("int") << int(QMetaType::Int) << static_cast<const QMetaObject *>(0) << false << false; - QTest::newRow("QEasingCurve") << ::qMetaTypeId<QEasingCurve>() << &QEasingCurve::staticMetaObject << true << false; - QTest::newRow("MyGadget") << ::qMetaTypeId<MyGadget>() << &MyGadget::staticMetaObject << true << false; - QTest::newRow("MyEnum") << ::qMetaTypeId<MyGadget::MyEnum>() << &MyGadget::staticMetaObject << false << false; - QTest::newRow("Qt::ScrollBarPolicy") << ::qMetaTypeId<Qt::ScrollBarPolicy>() << &QObject::staticQtMetaObject << false << false; + QTest::newRow("QObject") << int(QMetaType::QObjectStar) << &QObject::staticMetaObject << false << false << true; + QTest::newRow("QFile*") << ::qMetaTypeId<QFile*>() << &QFile::staticMetaObject << false << false << true; + QTest::newRow("MyObject*") << ::qMetaTypeId<MyObject*>() << &MyObject::staticMetaObject << false << false << true; + QTest::newRow("int") << int(QMetaType::Int) << static_cast<const QMetaObject *>(0) << false << false << false; + QTest::newRow("QEasingCurve") << ::qMetaTypeId<QEasingCurve>() << &QEasingCurve::staticMetaObject << true << false << false; + QTest::newRow("MyGadget") << ::qMetaTypeId<MyGadget>() << &MyGadget::staticMetaObject << true << false << false; + QTest::newRow("MyGadget*") << ::qMetaTypeId<MyGadget*>() << &MyGadget::staticMetaObject << false << true << false; + QTest::newRow("MyEnum") << ::qMetaTypeId<MyGadget::MyEnum>() << &MyGadget::staticMetaObject << false << false << false; + QTest::newRow("Qt::ScrollBarPolicy") << ::qMetaTypeId<Qt::ScrollBarPolicy>() << &QObject::staticQtMetaObject << false << false << false; } @@ -1711,12 +1717,14 @@ void tst_QMetaType::metaObject() QFETCH(int, type); QFETCH(const QMetaObject *, result); QFETCH(bool, isGadget); + QFETCH(bool, isGadgetPtr); QFETCH(bool, isQObjectPtr); QCOMPARE(QMetaType::metaObjectForType(type), result); QMetaType mt(type); QCOMPARE(mt.metaObject(), result); QCOMPARE(!!(mt.flags() & QMetaType::IsGadget), isGadget); + QCOMPARE(!!(mt.flags() & QMetaType::PointerToGadget), isGadgetPtr); QCOMPARE(!!(mt.flags() & QMetaType::PointerToQObject), isQObjectPtr); } diff --git a/tests/auto/corelib/kernel/qobject/tst_qobject.cpp b/tests/auto/corelib/kernel/qobject/tst_qobject.cpp index f1e58921c0..1f98f64340 100644 --- a/tests/auto/corelib/kernel/qobject/tst_qobject.cpp +++ b/tests/auto/corelib/kernel/qobject/tst_qobject.cpp @@ -140,6 +140,7 @@ private slots: void connectFunctorWithContext(); void connectFunctorWithContextUnique(); void connectFunctorDeadlock(); + void connectFunctorMoveOnly(); void connectStaticSlotWithObject(); void disconnectDoesNotLeakFunctor(); void contextDoesNotLeakFunctor(); @@ -2362,8 +2363,8 @@ void tst_QObject::testUserData() // Randomize the table a bit for (int i=0; i<100; ++i) { - int p1 = rand() % USER_DATA_COUNT; - int p2 = rand() % USER_DATA_COUNT; + int p1 = QRandomGenerator::global()->bounded(USER_DATA_COUNT); + int p2 = QRandomGenerator::global()->bounded(USER_DATA_COUNT); int tmp = user_data_ids[p1]; user_data_ids[p1] = user_data_ids[p2]; @@ -6238,6 +6239,47 @@ void tst_QObject::connectFunctorDeadlock() sender.emitSignal1(); } +void tst_QObject::connectFunctorMoveOnly() +{ + struct MoveOnlyFunctor { + Q_DISABLE_COPY(MoveOnlyFunctor) + MoveOnlyFunctor(int *status) : status(status) {} + MoveOnlyFunctor(MoveOnlyFunctor &&o) : status(o.status) { o.status = nullptr; }; + void operator()(int i) { *status = i; } + void operator()() { *status = -8; } + int *status; + }; + + int status = 1; + SenderObject obj; + QEventLoop e; + + connect(&obj, &SenderObject::signal1, MoveOnlyFunctor(&status)); + QCOMPARE(status, 1); + obj.signal1(); + QCOMPARE(status, -8); + + connect(&obj, &SenderObject::signal7, MoveOnlyFunctor(&status)); + QCOMPARE(status, -8); + obj.signal7(7888, "Hello"); + QCOMPARE(status, 7888); + + // With a context + status = 1; + connect(&obj, &SenderObject::signal2, this, MoveOnlyFunctor(&status)); + QCOMPARE(status, 1); + obj.signal2(); + QCOMPARE(status, -8); + + // QueuedConnection + status = 1; + connect(&obj, &SenderObject::signal3, this, MoveOnlyFunctor(&status), Qt::QueuedConnection); + obj.signal3(); + QCOMPARE(status, 1); + QCoreApplication::processEvents(); + QCOMPARE(status, -8); +} + static int s_static_slot_checker = 1; class StaticSlotChecker : public QObject diff --git a/tests/auto/corelib/kernel/qtimer/qtimer.pro b/tests/auto/corelib/kernel/qtimer/qtimer.pro index b27d862bc5..710dfea682 100644 --- a/tests/auto/corelib/kernel/qtimer/qtimer.pro +++ b/tests/auto/corelib/kernel/qtimer/qtimer.pro @@ -1,6 +1,6 @@ CONFIG += testcase TARGET = tst_qtimer -QT = core testlib +QT = core core-private testlib SOURCES = tst_qtimer.cpp # Force C++17 if available diff --git a/tests/auto/corelib/kernel/qtimer/tst_qtimer.cpp b/tests/auto/corelib/kernel/qtimer/tst_qtimer.cpp index fd704f582d..b921c0f13d 100644 --- a/tests/auto/corelib/kernel/qtimer/tst_qtimer.cpp +++ b/tests/auto/corelib/kernel/qtimer/tst_qtimer.cpp @@ -33,11 +33,11 @@ # include <QtCore/QCoreApplication> #endif +#include <QtCore/private/qglobal_p.h> #include <QtTest/QtTest> #include <qtimer.h> #include <qthread.h> -#include <qoperatingsystemversion.h> #if defined Q_OS_UNIX #include <unistd.h> @@ -500,7 +500,7 @@ void tst_QTimer::moveToThread() #if defined(Q_OS_WIN32) QSKIP("Does not work reliably on Windows :("); #elif defined(Q_OS_MACOS) - if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSSierra) + if (__builtin_available(macOS 10.12, *)) QSKIP("Does not work reliably on macOS 10.12 (QTBUG-59679)"); #endif QTimer ti1; @@ -836,7 +836,6 @@ void tst_QTimer::singleShotToFunctors() QTest::qWait(800); QCOMPARE(count, 2); -#if defined(Q_COMPILER_LAMBDA) QTimer::singleShot(0, [&count] { ++count; }); QCoreApplication::processEvents(); QCOMPARE(count, 3); @@ -855,7 +854,15 @@ void tst_QTimer::singleShotToFunctors() thread.quit(); thread.wait(); -#endif + + struct MoveOnly : CountedStruct { + Q_DISABLE_COPY(MoveOnly); + MoveOnly(MoveOnly &&o) : CountedStruct(std::move(o)) {}; + MoveOnly(int *c) : CountedStruct(c) {} + }; + QTimer::singleShot(0, MoveOnly(&count)); + QCoreApplication::processEvents(); + QCOMPARE(count, 5); _e.reset(); _t = Q_NULLPTR; diff --git a/tests/auto/corelib/kernel/qtranslator/tst_qtranslator.cpp b/tests/auto/corelib/kernel/qtranslator/tst_qtranslator.cpp index e3eee6dbdd..451f96339e 100644 --- a/tests/auto/corelib/kernel/qtranslator/tst_qtranslator.cpp +++ b/tests/auto/corelib/kernel/qtranslator/tst_qtranslator.cpp @@ -50,6 +50,7 @@ private slots: void loadFromResource(); void loadDirectory(); void dependencies(); + void translationInThreadWhileInstallingTranslator(); private: int languageChangeEventCounter; @@ -287,6 +288,52 @@ void tst_QTranslator::dependencies() } } +struct TranslateThread : public QThread +{ + bool ok = false; + QAtomicInt terminate; + QMutex startupLock; + QWaitCondition runningCondition; + + void run() { + bool startSignalled = false; + + while (terminate.load() == 0) { + const QString result = QCoreApplication::translate("QPushButton", "Hello %n world(s)!", 0, 0); + + if (!startSignalled) { + QMutexLocker startupLocker(&startupLock); + runningCondition.wakeAll(); + startSignalled = true; + } + + ok = (result == QLatin1String("Hallo 0 Welten!")) + || (result == QLatin1String("Hello 0 world(s)!")); + if (!ok) + break; + } + } +}; + +void tst_QTranslator::translationInThreadWhileInstallingTranslator() +{ + TranslateThread thread; + + QMutexLocker startupLocker(&thread.startupLock); + + thread.start(); + + thread.runningCondition.wait(&thread.startupLock); + + QTranslator *tor = new QTranslator; + tor->load("hellotr_la"); + QCoreApplication::installTranslator(tor); + + ++thread.terminate; + + QVERIFY(thread.wait()); + QVERIFY(thread.ok); +} QTEST_MAIN(tst_QTranslator) #include "tst_qtranslator.moc" diff --git a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp index e43b7acfb8..0d45159d09 100644 --- a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp +++ b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp @@ -277,6 +277,8 @@ private slots: void compareSanity(); void compareRich(); + void nullConvert(); + void accessSequentialContainerKey(); private: @@ -369,6 +371,8 @@ void tst_QVariant::copy_constructor() QVERIFY(var8.isNull()); } +Q_DECLARE_METATYPE(int*) + void tst_QVariant::isNull() { QVariant var; @@ -411,6 +415,18 @@ void tst_QVariant::isNull() QVERIFY(var9.isNull()); var9 = QVariant::fromValue<QJsonValue>(QJsonValue(QJsonValue::Null)); QVERIFY(var9.isNull()); + + QVariant var10(QMetaType::VoidStar, nullptr); + QVERIFY(var10.isNull()); + var10 = QVariant::fromValue<void*>(nullptr); + QVERIFY(var10.isNull()); + + QVariant var11(QMetaType::QObjectStar, nullptr); + QVERIFY(var11.isNull()); + var11 = QVariant::fromValue<QObject*>(nullptr); + QVERIFY(var11.isNull()); + + QVERIFY(QVariant::fromValue<int*>(nullptr).isNull()); } void tst_QVariant::swap() @@ -1046,6 +1062,7 @@ void tst_QVariant::toByteArray_data() QTest::newRow( "longlong" ) << QVariant( (qlonglong)34 ) << QByteArray( "34" ); QTest::newRow( "ulonglong" ) << QVariant( (qulonglong)34 ) << QByteArray( "34" ); + QTest::newRow( "nullptr" ) << QVariant::fromValue(nullptr) << QByteArray(); } void tst_QVariant::toByteArray() @@ -1055,7 +1072,13 @@ void tst_QVariant::toByteArray() QVERIFY( value.isValid() ); QVERIFY( value.canConvert( QVariant::ByteArray ) ); QByteArray ba = value.toByteArray(); + QCOMPARE( ba.isNull(), result.isNull() ); QCOMPARE( ba, result ); + + QVERIFY( value.convert( QVariant::ByteArray ) ); + QCOMPARE( value.isNull(), result.isNull() ); + QCOMPARE( value.toByteArray().isNull(), result.isNull() ); + QCOMPARE( value.toByteArray(), result ); } void tst_QVariant::toString_data() @@ -1082,6 +1105,7 @@ void tst_QVariant::toString_data() QString( "123456789012" ); QTest::newRow("QJsonValue") << QVariant(QJsonValue(QString("hello"))) << QString("hello"); QTest::newRow("QJsonValue(Null)") << QVariant(QJsonValue(QJsonValue::Null)) << QString(); + QTest::newRow("nullptr") << QVariant::fromValue(nullptr) << QString(); } void tst_QVariant::toString() @@ -1091,7 +1115,13 @@ void tst_QVariant::toString() QVERIFY( value.isValid() ); QVERIFY( value.canConvert( QVariant::String ) ); QString str = value.toString(); + QCOMPARE( str.isNull(), result.isNull() ); QCOMPARE( str, result ); + + QVERIFY( value.convert( QVariant::String ) ); + QCOMPARE( value.isNull(), result.isNull() ); + QCOMPARE( value.toString().isNull(), result.isNull() ); + QCOMPARE( value.toString(), result ); } void tst_QVariant::toDate_data() @@ -2648,7 +2678,7 @@ void tst_QVariant::qvariant_cast_QObject_data() QTest::newRow("null QObject") << QVariant::fromValue<QObject*>(0) << true << true; QTest::newRow("null derived QObject") << QVariant::fromValue<CustomQObject*>(0) << true << true; QTest::newRow("null custom object") << QVariant::fromValue<CustomNonQObject*>(0) << false << true; - QTest::newRow("null int") << QVariant::fromValue<int>(0) << false << true; + QTest::newRow("zero int") << QVariant::fromValue<int>(0) << false << false; } void tst_QVariant::qvariant_cast_QObject() @@ -2666,12 +2696,14 @@ void tst_QVariant::qvariant_cast_QObject() QVERIFY(data.canConvert(QMetaType::QObjectStar)); QVERIFY(data.canConvert(::qMetaTypeId<QObject*>())); QCOMPARE(data.value<QObject*>() == 0, isNull); + QCOMPARE(data.isNull(), isNull); QVERIFY(data.convert(QMetaType::QObjectStar)); QCOMPARE(data.userType(), int(QMetaType::QObjectStar)); } else { QVERIFY(!data.canConvert<QObject*>()); QVERIFY(!data.canConvert(QMetaType::QObjectStar)); QVERIFY(!data.canConvert(::qMetaTypeId<QObject*>())); + QCOMPARE(data.isNull(), isNull); QVERIFY(!data.value<QObject*>()); QVERIFY(!data.convert(QMetaType::QObjectStar)); QVERIFY(data.userType() != QMetaType::QObjectStar); @@ -3403,21 +3435,6 @@ void tst_QVariant::toIntFromDouble() const QCOMPARE(result, 2147483630); } -void tst_QVariant::setValue() -{ - QJsonDocument t; //we just take a value so that we're sure that it will be shared - QVariant v1 = QVariant::fromValue(t); - QVERIFY( v1.isDetached() ); - QVariant v2 = v1; - QVERIFY( !v1.isDetached() ); - QVERIFY( !v2.isDetached() ); - - v2.setValue(3); //set an integer value - - QVERIFY( v1.isDetached() ); - QVERIFY( v2.isDetached() ); -} - void tst_QVariant::fpStringRoundtrip_data() const { QTest::addColumn<QVariant>("number"); @@ -3649,6 +3666,20 @@ Q_DECLARE_METATYPE(MyMovable *) Q_DECLARE_METATYPE(MyNotMovable *) Q_DECLARE_METATYPE(QSharedDataPointer<MyShared>) +void tst_QVariant::setValue() +{ + MyNotMovable t; //we just take a value so that we're sure that it will be shared + QVariant v1 = QVariant::fromValue(t); + QVERIFY( v1.isDetached() ); + QVariant v2 = v1; + QVERIFY( !v1.isDetached() ); + QVERIFY( !v2.isDetached() ); + + v2.setValue(3); //set an integer value + + QVERIFY( v1.isDetached() ); + QVERIFY( v2.isDetached() ); +} void tst_QVariant::moreCustomTypes() { @@ -3751,7 +3782,7 @@ void tst_QVariant::moreCustomTypes() { int i = 5; PLAY_WITH_VARIANT((void *)(&i), false, QString(), 0, false); - PLAY_WITH_VARIANT((void *)(0), false, QString(), 0, false); + PLAY_WITH_VARIANT((void *)(0), true, QString(), 0, false); } { @@ -4862,6 +4893,33 @@ void tst_QVariant::compareRich() << QStringLiteral("d")); } +void tst_QVariant::nullConvert() +{ + // Test quirks with QVariants different types of null states. + + // null variant with no initialized value + QVariant nullVar(QVariant::String); + QVERIFY(nullVar.isValid()); + QVERIFY(nullVar.isNull()); + // We can not convert a variant with no value + QVERIFY(!nullVar.convert(QVariant::Url)); + QCOMPARE(nullVar.type(), QVariant::Url); + QVERIFY(nullVar.isNull()); + + // variant initialized with null value + QVariant nullStr = QVariant::fromValue(QString()); + QVERIFY(nullStr.isValid()); + QVERIFY(nullStr.isNull()); + // We can convert an initialized null value however + QVERIFY(nullStr.convert(QVariant::Url)); + QCOMPARE(nullStr.type(), QVariant::Url); + QVERIFY(nullStr.isValid()); + // QUrl does not have an isNull method + QVERIFY(!nullStr.isNull()); + // The URL is not valid however + QVERIFY(!nullStr.toUrl().isValid()); +} + void tst_QVariant::accessSequentialContainerKey() { QString nameResult; @@ -4886,6 +4944,5 @@ void tst_QVariant::accessSequentialContainerKey() QCOMPARE(nameResult, QStringLiteral("Seven")); } - QTEST_MAIN(tst_QVariant) #include "tst_qvariant.moc" diff --git a/tests/auto/corelib/kernel/qwineventnotifier/tst_qwineventnotifier.cpp b/tests/auto/corelib/kernel/qwineventnotifier/tst_qwineventnotifier.cpp index 3221587300..76efa008f7 100644 --- a/tests/auto/corelib/kernel/qwineventnotifier/tst_qwineventnotifier.cpp +++ b/tests/auto/corelib/kernel/qwineventnotifier/tst_qwineventnotifier.cpp @@ -29,8 +29,13 @@ #include <QtTest/QtTest> #include <qwineventnotifier.h> #include <qtimer.h> +#include <qvarlengtharray.h> +#include <qvector.h> #include <qt_windows.h> +#include <algorithm> +#include <memory> + class tst_QWinEventNotifier : public QObject { Q_OBJECT @@ -39,7 +44,11 @@ protected slots: void simple_activated(); void simple_timerSet(); private slots: + void simple_data(); void simple(); + void manyNotifiers(); + void disableNotifiersInActivatedSlot_data(); + void disableNotifiersInActivatedSlot(); private: HANDLE simpleHEvent; @@ -58,9 +67,17 @@ void tst_QWinEventNotifier::simple_timerSet() SetEvent((HANDLE)simpleHEvent); } +void tst_QWinEventNotifier::simple_data() +{ + QTest::addColumn<bool>("resetManually"); + QTest::newRow("manual_reset") << true; + QTest::newRow("auto_reset") << false; +} + void tst_QWinEventNotifier::simple() { - simpleHEvent = CreateEvent(0, true, false, 0); + QFETCH(bool, resetManually); + simpleHEvent = CreateEvent(0, resetManually, false, 0); QVERIFY(simpleHEvent); QWinEventNotifier n(simpleHEvent); @@ -87,6 +104,142 @@ void tst_QWinEventNotifier::simple() QVERIFY(simpleActivated); } +class EventWithNotifier : public QObject +{ + Q_OBJECT +public: + EventWithNotifier() + { + connect(¬ifier, &QWinEventNotifier::activated, + this, &EventWithNotifier::onNotifierActivated); + notifier.setHandle(CreateEvent(0, TRUE, FALSE, 0)); + notifier.setEnabled(true); + } + + ~EventWithNotifier() + { + notifier.setEnabled(false); + CloseHandle(notifier.handle()); + } + + HANDLE eventHandle() const { return notifier.handle(); } + int numberOfTimesActivated() const { return activatedCount; } + void setEnabled(bool b) { notifier.setEnabled(b); } + +signals: + void activated(); + +public slots: + void onNotifierActivated() + { + ResetEvent(notifier.handle()); + activatedCount++; + emit activated(); + } + +private: + QWinEventNotifier notifier; + int activatedCount = 0; +}; + +void tst_QWinEventNotifier::manyNotifiers() +{ + const size_t maxEvents = 100; + const size_t middleEvenEvent = maxEvents / 2; + Q_ASSERT(middleEvenEvent % 2 == 0); + using EventWithNotifierPtr = std::unique_ptr<EventWithNotifier>; + std::vector<EventWithNotifierPtr> events(maxEvents); + std::generate(events.begin(), events.end(), [] () { + return EventWithNotifierPtr(new EventWithNotifier); + }); + + QTestEventLoop loop; + auto connection = connect(events.at(8).get(), &EventWithNotifier::activated, &loop, &QTestEventLoop::exitLoop); + for (const auto &ewn : events) { + connect(ewn.get(), &EventWithNotifier::activated, [&events, &loop] () { + if (std::all_of(events.cbegin(), events.cend(), + [] (const EventWithNotifierPtr &ewn) { + return ewn->numberOfTimesActivated() > 0; })) { + loop.exitLoop(); + } + }); + } + + // Activate all even events before running the event loop. + for (size_t i = 0; i < events.size(); i += 2) + SetEvent(events.at(i)->eventHandle()); + + // Wait until event notifier with index 8 has been activated. + loop.enterLoop(30); + QObject::disconnect(connection); + + // Activate all odd events after the event loop has run for a bit. + for (size_t i = 1; i < events.size(); i += 2) + SetEvent(events.at(i)->eventHandle()); + + // Wait until all event notifiers have fired. + loop.enterLoop(30); + + // All notifiers must have been activated exactly once. + QVERIFY(std::all_of(events.cbegin(), events.cend(), [] (const EventWithNotifierPtr &ewn) { + return ewn->numberOfTimesActivated() == 1; + })); +} + +using Indices = QVector<int>; + +void tst_QWinEventNotifier::disableNotifiersInActivatedSlot_data() +{ + QTest::addColumn<int>("count"); + QTest::addColumn<Indices>("notifiersToSignal"); + QTest::addColumn<Indices>("notifiersToDisable"); + QTest::addColumn<bool>("deleteNotifiers"); + QTest::newRow("disable_signaled") << 3 << Indices{1} << Indices{1} << false; + QTest::newRow("disable_signaled2") << 3 << Indices{1, 2} << Indices{1} << false; + QTest::newRow("disable_before_signaled") << 3 << Indices{1} << Indices{0, 1} << false; + QTest::newRow("disable_after_signaled") << 3 << Indices{1} << Indices{1, 2} << false; + QTest::newRow("delete_signaled") << 3 << Indices{1} << Indices{1} << true; + QTest::newRow("delete_before_signaled1") << 3 << Indices{1} << Indices{0} << true; + QTest::newRow("delete_before_signaled2") << 3 << Indices{1} << Indices{0, 1} << true; + QTest::newRow("delete_before_signaled3") << 4 << Indices{3, 1} << Indices{0, 1} << true; + QTest::newRow("delete_after_signaled1") << 3 << Indices{1} << Indices{1, 2} << true; + QTest::newRow("delete_after_signaled2") << 4 << Indices{1, 3} << Indices{1, 2} << true; + QTest::newRow("delete_after_signaled3") << 5 << Indices{1} << Indices{1, 4} << true; +} + +void tst_QWinEventNotifier::disableNotifiersInActivatedSlot() +{ + QFETCH(int, count); + QFETCH(Indices, notifiersToSignal); + QFETCH(Indices, notifiersToDisable); + QFETCH(bool, deleteNotifiers); + + QVarLengthArray<std::unique_ptr<EventWithNotifier>, 10> events(count); + for (int i = 0; i < count; ++i) + events[i].reset(new EventWithNotifier); + + auto isActivatedOrNull = [&events](int i) { + return !events.at(i) || events.at(i)->numberOfTimesActivated() > 0; + }; + + for (auto &e : events) { + connect(e.get(), &EventWithNotifier::activated, [&]() { + for (int i : notifiersToDisable) { + if (deleteNotifiers) + events[i].reset(); + else + events.at(i)->setEnabled(false); + } + if (std::all_of(notifiersToSignal.begin(), notifiersToSignal.end(), isActivatedOrNull)) + QTimer::singleShot(0, &QTestEventLoop::instance(), SLOT(exitLoop())); + }); + } + for (int i : notifiersToSignal) + SetEvent(events.at(i)->eventHandle()); + QTestEventLoop::instance().enterLoop(30); + QVERIFY(!QTestEventLoop::instance().timeout()); +} + QTEST_MAIN(tst_QWinEventNotifier) #include "tst_qwineventnotifier.moc" diff --git a/tests/auto/corelib/mimetypes/qmimedatabase/tst_qmimedatabase.cpp b/tests/auto/corelib/mimetypes/qmimedatabase/tst_qmimedatabase.cpp index 8a46bc1c55..8883b6360f 100644 --- a/tests/auto/corelib/mimetypes/qmimedatabase/tst_qmimedatabase.cpp +++ b/tests/auto/corelib/mimetypes/qmimedatabase/tst_qmimedatabase.cpp @@ -115,7 +115,6 @@ Q_CONSTRUCTOR_FUNCTION(initializeLang) static QString seedAndTemplate() { - qsrand(QDateTime::currentSecsSinceEpoch()); return QDir::tempPath() + "/tst_qmimedatabase-XXXXXX"; } diff --git a/tests/auto/corelib/mimetypes/qmimetype/tst_qmimetype.cpp b/tests/auto/corelib/mimetypes/qmimetype/tst_qmimetype.cpp index d93603f641..c74bce3b5b 100644 --- a/tests/auto/corelib/mimetypes/qmimetype/tst_qmimetype.cpp +++ b/tests/auto/corelib/mimetypes/qmimetype/tst_qmimetype.cpp @@ -46,6 +46,7 @@ private slots: void genericIconName(); void iconName(); void suffixes(); + void gadget(); }; // ------------------------------------------------------------------------------------------------ @@ -201,5 +202,44 @@ void tst_qmimetype::suffixes() // ------------------------------------------------------------------------------------------------ +void tst_qmimetype::gadget() +{ + QMimeType instantiatedQMimeType ( + buildQMimeType ( + qMimeTypeName(), + qMimeTypeGenericIconName(), + qMimeTypeIconName(), + qMimeTypeGlobPatterns() + ) + ); + + const QMetaObject *metaObject = &instantiatedQMimeType.staticMetaObject; + + QCOMPARE(metaObject->className(), "QMimeType"); + QVariantMap properties; + for (int i = metaObject->propertyOffset(); i < metaObject->propertyCount(); i++) { + QMetaProperty property = metaObject->property(i); + properties[property.name()] = property.readOnGadget(&instantiatedQMimeType); + } + + QCOMPARE(properties["valid"].toBool(), instantiatedQMimeType.isValid()); + QCOMPARE(properties["isDefault"].toBool(), instantiatedQMimeType.isDefault()); + QCOMPARE(properties["name"].toString(), instantiatedQMimeType.name()); + QCOMPARE(properties["comment"].toString(), instantiatedQMimeType.comment()); + QCOMPARE(properties["genericIconName"].toString(), instantiatedQMimeType.genericIconName()); + QCOMPARE(properties["iconName"].toString(), instantiatedQMimeType.iconName()); + QCOMPARE(properties["globPatterns"].toStringList(), instantiatedQMimeType.globPatterns()); + QCOMPARE(properties["parentMimeTypes"].toStringList(), instantiatedQMimeType.parentMimeTypes()); + QCOMPARE(properties["allAncestors"].toStringList(), instantiatedQMimeType.allAncestors()); + QCOMPARE(properties["aliases"].toStringList(), instantiatedQMimeType.aliases()); + QCOMPARE(properties["suffixes"].toStringList(), instantiatedQMimeType.suffixes()); + QCOMPARE(properties["preferredSuffix"].toString(), instantiatedQMimeType.preferredSuffix()); + QCOMPARE(properties["filterString"].toString(), instantiatedQMimeType.filterString()); + + QVERIFY(metaObject->indexOfMethod("inherits(QString)") >= 0); +} + +// ------------------------------------------------------------------------------------------------ + QTEST_GUILESS_MAIN(tst_qmimetype) #include "tst_qmimetype.moc" diff --git a/tests/auto/corelib/plugin/quuid/testProcessUniqueness/main.cpp b/tests/auto/corelib/plugin/quuid/testProcessUniqueness/main.cpp index daf9b1579a..d1e138d8eb 100644 --- a/tests/auto/corelib/plugin/quuid/testProcessUniqueness/main.cpp +++ b/tests/auto/corelib/plugin/quuid/testProcessUniqueness/main.cpp @@ -35,9 +35,6 @@ int main(int argc, char **argv) Q_UNUSED(argc) Q_UNUSED(argv) - // First, break QUuid. - qrand(); - // Now print a few uuids. printf("%s", qPrintable(QUuid::createUuid().toString())); printf("%s", qPrintable(QUuid::createUuid().toString())); diff --git a/tests/auto/corelib/plugin/quuid/tst_quuid.cpp b/tests/auto/corelib/plugin/quuid/tst_quuid.cpp index d3102c7ee5..9e3edc96ec 100644 --- a/tests/auto/corelib/plugin/quuid/tst_quuid.cpp +++ b/tests/auto/corelib/plugin/quuid/tst_quuid.cpp @@ -41,6 +41,7 @@ private slots: void fromChar(); void toString(); + void fromString_data(); void fromString(); void toByteArray(); void fromByteArray(); @@ -127,15 +128,58 @@ void tst_QUuid::toString() QCOMPARE(uuidB.toString(), QString("{1ab6e93a-b1cb-4a87-ba47-ec7e99039a7b}")); } +void tst_QUuid::fromString_data() +{ + QTest::addColumn<QUuid>("expected"); + QTest::addColumn<QString>("input"); + + QUuid invalid = {}; + +#define ROW(which, string) \ + QTest::addRow("%-38s -> %s", string, #which) << which << string + ROW(uuidA, "{fc69b59e-cc34-4436-a43c-ee95d128b8c5}"); + ROW(uuidA, "fc69b59e-cc34-4436-a43c-ee95d128b8c5}"); + ROW(uuidA, "{fc69b59e-cc34-4436-a43c-ee95d128b8c5" ); + ROW(uuidA, "fc69b59e-cc34-4436-a43c-ee95d128b8c5" ); + + ROW(uuidA, "{fc69b59e-cc34-4436-a43c-ee95d128b8c56"); // too long (not an error!) + ROW(invalid, "{fc69b59e-cc34-4436-a43c-ee95d128b8c" ); // premature end (within length limits) + ROW(invalid, " fc69b59e-cc34-4436-a43c-ee95d128b8c5}"); // leading space + ROW(uuidA, "{fc69b59e-cc34-4436-a43c-ee95d128b8c5 "); // trailing space (not an error!) + ROW(invalid, "{gc69b59e-cc34-4436-a43c-ee95d128b8c5}"); // non-hex digit in 1st group + ROW(invalid, "{fc69b59e-cp34-4436-a43c-ee95d128b8c5}"); // non-hex digit in 2nd group + ROW(invalid, "{fc69b59e-cc34-44r6-a43c-ee95d128b8c5}"); // non-hex digit in 3rd group + ROW(invalid, "{fc69b59e-cc34-4436-a4yc-ee95d128b8c5}"); // non-hex digit in 4th group + ROW(invalid, "{fc69b59e-cc34-4436-a43c-ee95d128j8c5}"); // non-hex digit in last group + ROW(invalid, "(fc69b59e-cc34-4436-a43c-ee95d128b8c5}"); // wrong initial character + ROW(invalid, "{fc69b59e+cc34-4436-a43c-ee95d128b8c5}"); // wrong 1st separator + ROW(invalid, "{fc69b59e-cc34*4436-a43c-ee95d128b8c5}"); // wrong 2nd separator + ROW(invalid, "{fc69b59e-cc34-44366a43c-ee95d128b8c5}"); // wrong 3rd separator + ROW(invalid, "{fc69b59e-cc34-4436-a43c\303\244ee95d128b8c5}"); // wrong 4th separator (ä) + ROW(uuidA, "{fc69b59e-cc34-4436-a43c-ee95d128b8c5)"); // wrong final character (not an error!) + + ROW(uuidB, "{1ab6e93a-b1cb-4a87-ba47-ec7e99039a7b}"); +#undef ROW +} + void tst_QUuid::fromString() { - QCOMPARE(uuidA, QUuid(QString("{fc69b59e-cc34-4436-a43c-ee95d128b8c5}"))); - QCOMPARE(uuidA, QUuid(QString("fc69b59e-cc34-4436-a43c-ee95d128b8c5}"))); - QCOMPARE(uuidA, QUuid(QString("{fc69b59e-cc34-4436-a43c-ee95d128b8c5"))); - QCOMPARE(uuidA, QUuid(QString("fc69b59e-cc34-4436-a43c-ee95d128b8c5"))); - QCOMPARE(QUuid(), QUuid(QString("{fc69b59e-cc34-4436-a43c-ee95d128b8c"))); + QFETCH(const QUuid, expected); + QFETCH(const QString, input); - QCOMPARE(uuidB, QUuid(QString("{1ab6e93a-b1cb-4a87-ba47-ec7e99039a7b}"))); + const auto inputL1 = input.toLatin1(); + const auto inputU8 = input.toUtf8(); + + QCOMPARE(expected, QUuid(input)); + QCOMPARE(expected, QUuid(inputU8)); + QCOMPARE(expected, QUuid(inputL1)); + + QCOMPARE(expected, QUuid::fromString(input)); + + // for QLatin1String, construct one whose data() is not NUL-terminated: + const auto longerInputL1 = inputL1 + '5'; // the '5' makes the premature end check incorrectly succeed + const auto inputL1S = QLatin1String(longerInputL1.data(), inputL1.size()); + QCOMPARE(expected, QUuid::fromString(inputL1S)); } void tst_QUuid::toByteArray() diff --git a/tests/auto/corelib/plugin/quuid/tst_quuid_darwin.mm b/tests/auto/corelib/plugin/quuid/tst_quuid_darwin.mm index d90bff65b3..41ccece115 100644 --- a/tests/auto/corelib/plugin/quuid/tst_quuid_darwin.mm +++ b/tests/auto/corelib/plugin/quuid/tst_quuid_darwin.mm @@ -36,7 +36,7 @@ void tst_QUuid_darwinTypes() { // QUuid <-> CFUUID { - QUuid qtUuid(QByteArrayLiteral("0f7169cc-5711-4af9-99d9-fecb2329fdef")); + const auto qtUuid = QUuid::fromString(QLatin1String("0f7169cc-5711-4af9-99d9-fecb2329fdef")); const CFUUIDRef cfuuid = qtUuid.toCFUUID(); QCOMPARE(QUuid::fromCFUUID(cfuuid), qtUuid); CFStringRef cfstring = CFUUIDCreateString(0, cfuuid); @@ -45,10 +45,10 @@ void tst_QUuid_darwinTypes() CFRelease(cfuuid); } { - QUuid qtUuid(QByteArrayLiteral("0f7169cc-5711-4af9-99d9-fecb2329fdef")); + auto qtUuid = QUuid::fromString(QLatin1String("0f7169cc-5711-4af9-99d9-fecb2329fdef")); const CFUUIDRef cfuuid = qtUuid.toCFUUID(); QUuid qtUuidCopy(qtUuid); - qtUuid = QUuid(QByteArrayLiteral("93eec131-13c5-4d13-aaea-e456b4c57efa")); // modify + qtUuid = QUuid::fromString(QLatin1String("93eec131-13c5-4d13-aaea-e456b4c57efa")); // modify QCOMPARE(QUuid::fromCFUUID(cfuuid), qtUuidCopy); CFStringRef cfstring = CFUUIDCreateString(0, cfuuid); QCOMPARE(QString::fromCFString(cfstring), qtUuidCopy.toString().mid(1, 36).toUpper()); @@ -59,7 +59,7 @@ void tst_QUuid_darwinTypes() { QMacAutoReleasePool pool; - QUuid qtUuid(QByteArrayLiteral("0f7169cc-5711-4af9-99d9-fecb2329fdef")); + const auto qtUuid = QUuid::fromString(QLatin1String("0f7169cc-5711-4af9-99d9-fecb2329fdef")); const NSUUID *nsuuid = qtUuid.toNSUUID(); QCOMPARE(QUuid::fromNSUUID(nsuuid), qtUuid); QCOMPARE(QString::fromNSString([nsuuid UUIDString]), qtUuid.toString().mid(1, 36).toUpper()); @@ -67,10 +67,10 @@ void tst_QUuid_darwinTypes() { QMacAutoReleasePool pool; - QUuid qtUuid(QByteArrayLiteral("0f7169cc-5711-4af9-99d9-fecb2329fdef")); + auto qtUuid = QUuid::fromString(QLatin1String("0f7169cc-5711-4af9-99d9-fecb2329fdef")); const NSUUID *nsuuid = qtUuid.toNSUUID(); QUuid qtUuidCopy(qtUuid); - qtUuid = QUuid(QByteArrayLiteral("93eec131-13c5-4d13-aaea-e456b4c57efa")); // modify + qtUuid = QUuid::fromString(QLatin1String("93eec131-13c5-4d13-aaea-e456b4c57efa")); // modify QCOMPARE(QUuid::fromNSUUID(nsuuid), qtUuidCopy); QCOMPARE(QString::fromNSString([nsuuid UUIDString]), qtUuidCopy.toString().mid(1, 36).toUpper()); } diff --git a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp index a546cad225..58bebe19ac 100644 --- a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp +++ b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp @@ -36,6 +36,7 @@ #include <qresultstore.h> #include <qthreadpool.h> #include <qexception.h> +#include <qrandom.h> #include <private/qfutureinterface_p.h> // COM interface macro. @@ -1205,8 +1206,6 @@ void tst_QFuture::pause() Interface.reportFinished(); } -const int resultCount = 1000; - class ResultObject : public QObject { Q_OBJECT @@ -1458,7 +1457,7 @@ void tst_QFuture::nonGlobalThreadPool() void run() Q_DECL_OVERRIDE { - const int ms = 100 + (qrand() % 100 - 100/2); + const int ms = 100 + (QRandomGenerator::global()->bounded(100) - 100/2); QThread::msleep(ms); reportResult(Answer); reportFinished(); diff --git a/tests/auto/corelib/thread/qsemaphore/tst_qsemaphore.cpp b/tests/auto/corelib/thread/qsemaphore/tst_qsemaphore.cpp index ce093f769c..a67ecc2471 100644 --- a/tests/auto/corelib/thread/qsemaphore/tst_qsemaphore.cpp +++ b/tests/auto/corelib/thread/qsemaphore/tst_qsemaphore.cpp @@ -44,6 +44,7 @@ private slots: void tryAcquireWithTimeoutForever_data(); void tryAcquireWithTimeoutForever(); void producerConsumer(); + void raii(); }; static QSemaphore *semaphore = 0; @@ -480,5 +481,54 @@ void tst_QSemaphore::producerConsumer() consumer.wait(); } +void tst_QSemaphore::raii() +{ + QSemaphore sem; + + QCOMPARE(sem.available(), 0); + + // basic operation: + { + QSemaphoreReleaser r0; + const QSemaphoreReleaser r1(sem); + const QSemaphoreReleaser r2(sem, 2); + + QCOMPARE(r0.semaphore(), nullptr); + QCOMPARE(r1.semaphore(), &sem); + QCOMPARE(r2.semaphore(), &sem); + } + + QCOMPARE(sem.available(), 3); + + // cancel: + { + const QSemaphoreReleaser r1(sem); + QSemaphoreReleaser r2(sem, 2); + + QCOMPARE(r2.cancel(), &sem); + QCOMPARE(r2.semaphore(), nullptr); + } + + QCOMPARE(sem.available(), 4); + + // move-assignment: + { + const QSemaphoreReleaser r1(sem); + QSemaphoreReleaser r2(sem, 2); + + QCOMPARE(sem.available(), 4); + + r2 = QSemaphoreReleaser(); + + QCOMPARE(sem.available(), 6); + + r2 = QSemaphoreReleaser(sem, 42); + + QCOMPARE(sem.available(), 6); + } + + QCOMPARE(sem.available(), 49); +} + QTEST_MAIN(tst_QSemaphore) #include "tst_qsemaphore.moc" diff --git a/tests/auto/corelib/thread/qthread/qthread.pro b/tests/auto/corelib/thread/qthread/qthread.pro index e0ef506d2c..37552f1fca 100644 --- a/tests/auto/corelib/thread/qthread/qthread.pro +++ b/tests/auto/corelib/thread/qthread/qthread.pro @@ -2,6 +2,8 @@ CONFIG += testcase TARGET = tst_qthread QT = core testlib SOURCES = tst_qthread.cpp +qtConfig(c++14):CONFIG += c++14 +qtConfig(c++1z):CONFIG += c++1z INCLUDEPATH += ../../../../shared/ HEADERS += ../../../../shared/emulationdetector.h diff --git a/tests/auto/corelib/thread/qthread/tst_qthread.cpp b/tests/auto/corelib/thread/qthread/tst_qthread.cpp index 5c8522a313..0405896ca7 100644 --- a/tests/auto/corelib/thread/qthread/tst_qthread.cpp +++ b/tests/auto/corelib/thread/qthread/tst_qthread.cpp @@ -47,6 +47,10 @@ #endif #endif +#ifndef QT_NO_EXCEPTIONS +#include <exception> +#endif + #include "emulationdetector.h" class tst_QThread : public QObject @@ -100,6 +104,8 @@ private slots: void stressTest(); void quitLock(); + + void create(); }; enum { one_minute = 60 * 1000, five_minutes = 5 * one_minute }; @@ -1330,6 +1336,259 @@ void tst_QThread::quitLock() QVERIFY(exitThreadCalled); } +void tst_QThread::create() +{ +#if !QT_CONFIG(cxx11_future) + QSKIP("This test requires QThread::create"); +#else + { + const auto &function = [](){}; + QScopedPointer<QThread> thread(QThread::create(function)); + QVERIFY(thread); + QVERIFY(!thread->isRunning()); + thread->start(); + QVERIFY(thread->wait()); + } + + { + // no side effects before starting + int i = 0; + const auto &function = [&i]() { i = 42; }; + QScopedPointer<QThread> thread(QThread::create(function)); + QVERIFY(thread); + QVERIFY(!thread->isRunning()); + QCOMPARE(i, 0); + thread->start(); + QVERIFY(thread->wait()); + QCOMPARE(i, 42); + } + + { + // control thread progress + QSemaphore semaphore1; + QSemaphore semaphore2; + + const auto &function = [&semaphore1, &semaphore2]() -> void + { + semaphore1.acquire(); + semaphore2.release(); + }; + + QScopedPointer<QThread> thread(QThread::create(function)); + + QVERIFY(thread); + thread->start(); + QTRY_VERIFY(thread->isRunning()); + semaphore1.release(); + semaphore2.acquire(); + QVERIFY(thread->wait()); + QVERIFY(!thread->isRunning()); + } + + { + // ignore return values + const auto &function = []() { return 42; }; + QScopedPointer<QThread> thread(QThread::create(function)); + QVERIFY(thread); + QVERIFY(!thread->isRunning()); + thread->start(); + QVERIFY(thread->wait()); + } + + { + // return value of create + QScopedPointer<QThread> thread; + QSemaphore s; + const auto &function = [&thread, &s]() -> void + { + s.acquire(); + QCOMPARE(thread.data(), QThread::currentThread()); + }; + + thread.reset(QThread::create(function)); + QVERIFY(thread); + thread->start(); + QTRY_VERIFY(thread->isRunning()); + s.release(); + QVERIFY(thread->wait()); + } + + { + // move-only parameters + struct MoveOnlyValue { + explicit MoveOnlyValue(int v) : v(v) {} + ~MoveOnlyValue() = default; + MoveOnlyValue(const MoveOnlyValue &) = delete; + MoveOnlyValue(MoveOnlyValue &&) = default; + MoveOnlyValue &operator=(const MoveOnlyValue &) = delete; + MoveOnlyValue &operator=(MoveOnlyValue &&) = default; + int v; + }; + + struct MoveOnlyFunctor { + explicit MoveOnlyFunctor(int *i) : i(i) {} + ~MoveOnlyFunctor() = default; + MoveOnlyFunctor(const MoveOnlyFunctor &) = delete; + MoveOnlyFunctor(MoveOnlyFunctor &&) = default; + MoveOnlyFunctor &operator=(const MoveOnlyFunctor &) = delete; + MoveOnlyFunctor &operator=(MoveOnlyFunctor &&) = default; + int operator()() { return (*i = 42); } + int *i; + }; + + { + int i = 0; + MoveOnlyFunctor f(&i); + QScopedPointer<QThread> thread(QThread::create(std::move(f))); + QVERIFY(thread); + QVERIFY(!thread->isRunning()); + thread->start(); + QVERIFY(thread->wait()); + QCOMPARE(i, 42); + } + +#if defined(__cpp_init_captures) && __cpp_init_captures >= 201304 + { + int i = 0; + MoveOnlyValue mo(123); + auto moveOnlyFunction = [&i, mo = std::move(mo)]() { i = mo.v; }; + QScopedPointer<QThread> thread(QThread::create(std::move(moveOnlyFunction))); + QVERIFY(thread); + QVERIFY(!thread->isRunning()); + thread->start(); + QVERIFY(thread->wait()); + QCOMPARE(i, 123); + } +#endif // __cpp_init_captures + +#ifdef QTHREAD_HAS_VARIADIC_CREATE + { + int i = 0; + const auto &function = [&i](MoveOnlyValue &&mo) { i = mo.v; }; + QScopedPointer<QThread> thread(QThread::create(function, MoveOnlyValue(123))); + QVERIFY(thread); + QVERIFY(!thread->isRunning()); + thread->start(); + QVERIFY(thread->wait()); + QCOMPARE(i, 123); + } + + { + int i = 0; + const auto &function = [&i](MoveOnlyValue &&mo) { i = mo.v; }; + MoveOnlyValue mo(-1); + QScopedPointer<QThread> thread(QThread::create(function, std::move(mo))); + QVERIFY(thread); + QVERIFY(!thread->isRunning()); + thread->start(); + QVERIFY(thread->wait()); + QCOMPARE(i, -1); + } +#endif // QTHREAD_HAS_VARIADIC_CREATE + } + +#ifdef QTHREAD_HAS_VARIADIC_CREATE + { + // simple parameter passing + int i = 0; + const auto &function = [&i](int j, int k) { i = j * k; }; + QScopedPointer<QThread> thread(QThread::create(function, 3, 4)); + QVERIFY(thread); + QVERIFY(!thread->isRunning()); + QCOMPARE(i, 0); + thread->start(); + QVERIFY(thread->wait()); + QCOMPARE(i, 12); + } + + { + // ignore return values (with parameters) + const auto &function = [](double d) { return d * 2.0; }; + QScopedPointer<QThread> thread(QThread::create(function, 3.14)); + QVERIFY(thread); + QVERIFY(!thread->isRunning()); + thread->start(); + QVERIFY(thread->wait()); + } + + { + // handling of pointers to member functions, std::ref, etc. + struct S { + S() : v(0) {} + void doSomething() { ++v; } + int v; + }; + + S object; + + QCOMPARE(object.v, 0); + + QScopedPointer<QThread> thread; + thread.reset(QThread::create(&S::doSomething, object)); + QVERIFY(thread); + QVERIFY(!thread->isRunning()); + thread->start(); + QVERIFY(thread->wait()); + + QCOMPARE(object.v, 0); // a copy was passed, this should still be 0 + + thread.reset(QThread::create(&S::doSomething, std::ref(object))); + QVERIFY(thread); + QVERIFY(!thread->isRunning()); + thread->start(); + QVERIFY(thread->wait()); + + QCOMPARE(object.v, 1); + + thread.reset(QThread::create(&S::doSomething, &object)); + QVERIFY(thread); + QVERIFY(!thread->isRunning()); + thread->start(); + QVERIFY(thread->wait()); + + QCOMPARE(object.v, 2); + } + + { + // std::ref into ordinary reference + int i = 42; + const auto &function = [](int &i) { i *= 2; }; + QScopedPointer<QThread> thread(QThread::create(function, std::ref(i))); + QVERIFY(thread); + thread->start(); + QVERIFY(thread->wait()); + QCOMPARE(i, 84); + } + +#ifndef QT_NO_EXCEPTIONS + { + // exceptions when copying/decaying the arguments are thrown at build side and won't terminate + class ThreadException : public std::exception + { + }; + + struct ThrowWhenCopying + { + ThrowWhenCopying() = default; + ThrowWhenCopying(const ThrowWhenCopying &) + { + throw ThreadException(); + } + ~ThrowWhenCopying() = default; + ThrowWhenCopying &operator=(const ThrowWhenCopying &) = default; + }; + + const auto &function = [](const ThrowWhenCopying &){}; + QScopedPointer<QThread> thread; + ThrowWhenCopying t; + QVERIFY_EXCEPTION_THROWN(thread.reset(QThread::create(function, t)), ThreadException); + QVERIFY(!thread); + } +#endif // QT_NO_EXCEPTIONS +#endif // QTHREAD_HAS_VARIADIC_CREATE +#endif // QT_CONFIG(cxx11_future) +} + class StopableJob : public QObject { Q_OBJECT diff --git a/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp b/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp index 0eaf8a4b77..1092216fb7 100644 --- a/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp +++ b/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp @@ -92,6 +92,7 @@ private slots: void tryTake(); void waitForDoneTimeout(); void destroyingWaitsForTasksToFinish(); + void stackSize(); void stressTest(); void takeAllAndIncreaseMaxThreadCount(); void waitForDoneAfterTake(); @@ -963,21 +964,6 @@ void tst_QThreadPool::cancel() QSemaphore sem(0); QSemaphore startedThreads(0); - class SemaphoreReleaser - { - QSemaphore &sem; - int n; - Q_DISABLE_COPY(SemaphoreReleaser) - public: - explicit SemaphoreReleaser(QSemaphore &sem, int n) - : sem(sem), n(n) {} - - ~SemaphoreReleaser() - { - sem.release(n); - } - }; - class BlockingRunnable : public QRunnable { public: @@ -1016,7 +1002,7 @@ void tst_QThreadPool::cancel() // ensure that the QThreadPool doesn't deadlock if any of the checks fail // and cause an early return: - const SemaphoreReleaser semReleaser(sem, runs); + const QSemaphoreReleaser semReleaser(sem, runs); count.store(0); QAtomicInt dtorCounter = 0; @@ -1050,21 +1036,6 @@ void tst_QThreadPool::tryTake() QSemaphore sem(0); QSemaphore startedThreads(0); - class SemaphoreReleaser - { - QSemaphore &sem; - int n; - Q_DISABLE_COPY(SemaphoreReleaser) - public: - explicit SemaphoreReleaser(QSemaphore &sem, int n) - : sem(sem), n(n) {} - - ~SemaphoreReleaser() - { - sem.release(n); - } - }; - class BlockingRunnable : public QRunnable { public: @@ -1103,7 +1074,7 @@ void tst_QThreadPool::tryTake() // ensure that the QThreadPool doesn't deadlock if any of the checks fail // and cause an early return: - const SemaphoreReleaser semReleaser(sem, Runs); + const QSemaphoreReleaser semReleaser(sem, Runs); count.store(0); QAtomicInt dtorCounter = 0; @@ -1168,6 +1139,39 @@ void tst_QThreadPool::destroyingWaitsForTasksToFinish() } } +// Verify that QThreadPool::stackSize is used when creating +// new threads. Note that this tests the Qt property only +// since QThread::stackSize() does not reflect the actual +// stack size used by the native thread. +void tst_QThreadPool::stackSize() +{ + uint targetStackSize = 512 * 1024; + uint threadStackSize = 1; // impossible value + + class StackSizeChecker : public QRunnable + { + public: + uint *stackSize; + + StackSizeChecker(uint *stackSize) + :stackSize(stackSize) + { + + } + + void run() + { + *stackSize = QThread::currentThread()->stackSize(); + } + }; + + QThreadPool threadPool; + threadPool.setStackSize(targetStackSize); + threadPool.start(new StackSizeChecker(&threadStackSize)); + QVERIFY(threadPool.waitForDone(30000)); // 30s timeout + QCOMPARE(threadStackSize, targetStackSize); +} + void tst_QThreadPool::stressTest() { class Task : public QRunnable diff --git a/tests/auto/corelib/tools/containerapisymmetry/.gitignore b/tests/auto/corelib/tools/containerapisymmetry/.gitignore new file mode 100644 index 0000000000..172ca970f2 --- /dev/null +++ b/tests/auto/corelib/tools/containerapisymmetry/.gitignore @@ -0,0 +1 @@ +tst_containerapisymmetry diff --git a/tests/auto/corelib/tools/containerapisymmetry/containerapisymmetry.pro b/tests/auto/corelib/tools/containerapisymmetry/containerapisymmetry.pro new file mode 100644 index 0000000000..30dc8026ef --- /dev/null +++ b/tests/auto/corelib/tools/containerapisymmetry/containerapisymmetry.pro @@ -0,0 +1,7 @@ +CONFIG += testcase +TARGET = tst_containerapisymmetry +SOURCES += tst_containerapisymmetry.cpp +QT = core testlib + +# This test does not work with strict iterators +DEFINES -= QT_STRICT_ITERATORS diff --git a/tests/auto/corelib/tools/containerapisymmetry/tst_containerapisymmetry.cpp b/tests/auto/corelib/tools/containerapisymmetry/tst_containerapisymmetry.cpp new file mode 100644 index 0000000000..3b8111f1a3 --- /dev/null +++ b/tests/auto/corelib/tools/containerapisymmetry/tst_containerapisymmetry.cpp @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Marc Mutz <marc.mutz@kdab.com> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> + +#include "qbytearray.h" +#include "qlinkedlist.h" +#include "qlist.h" +#include "qstring.h" +#include "qvarlengtharray.h" +#include "qvector.h" + +#include <vector> // for reference + +class tst_ContainerApiSymmetry : public QObject +{ + Q_OBJECT + +private: + template <typename Container> + void front_back_impl() const; + +private Q_SLOTS: + void front_back_std_vector() { front_back_impl<std::vector<int>>(); } + void front_back_QVector() { front_back_impl<QVector<int>>(); } + void front_back_QList() { front_back_impl<QList<qintptr>>(); } + void front_back_QLinkedList() { front_back_impl<QLinkedList<int>>(); } + void front_back_QVarLengthArray() { front_back_impl<QVarLengthArray<int>>(); } + void front_back_QString() { front_back_impl<QString>(); } + void front_back_QStringRef() { front_back_impl<QStringRef>(); } + void front_back_QStringView() { front_back_impl<QStringView>(); } + void front_back_QLatin1String() { front_back_impl<QLatin1String>(); } + void front_back_QByteArray() { front_back_impl<QByteArray>(); } +}; + +template <typename Container> +Container make(int size) +{ + Container c; + int i = 1; + while (size--) + c.push_back(typename Container::value_type(i++)); + return c; +} + +static QString s_string = QStringLiteral("\1\2\3\4\5\6\7"); + +template <> QStringRef make(int size) { return s_string.leftRef(size); } +template <> QStringView make(int size) { return QStringView(s_string).left(size); } +template <> QLatin1String make(int size) { return QLatin1String("\1\2\3\4\5\6\7", size); } + +template <typename T> T clean(T &&t) { return std::forward<T>(t); } +inline QChar clean(QCharRef ch) { return ch; } +inline char clean(QByteRef ch) { return ch; } +inline char clean(QLatin1Char ch) { return ch.toLatin1(); } + +template <typename Container> +void tst_ContainerApiSymmetry::front_back_impl() const +{ + using V = typename Container::value_type; + auto c1 = make<Container>(1); + QCOMPARE(clean(c1.front()), V(1)); + QCOMPARE(clean(c1.back()), V(1)); + QCOMPARE(clean(qAsConst(c1).front()), V(1)); + QCOMPARE(clean(qAsConst(c1).back()), V(1)); + + auto c2 = make<Container>(2); + QCOMPARE(clean(c2.front()), V(1)); + QCOMPARE(clean(c2.back()), V(2)); + QCOMPARE(clean(qAsConst(c2).front()), V(1)); + QCOMPARE(clean(qAsConst(c2).back()), V(2)); +} + +QTEST_APPLESS_MAIN(tst_ContainerApiSymmetry) +#include "tst_containerapisymmetry.moc" diff --git a/tests/auto/corelib/tools/qalgorithms/tst_qalgorithms.cpp b/tests/auto/corelib/tools/qalgorithms/tst_qalgorithms.cpp index e13c2894af..72299402f0 100644 --- a/tests/auto/corelib/tools/qalgorithms/tst_qalgorithms.cpp +++ b/tests/auto/corelib/tools/qalgorithms/tst_qalgorithms.cpp @@ -37,6 +37,7 @@ #include <qalgorithms.h> #include <QStringList> #include <QString> +#include <QRandomGenerator> #include <QVector> #define Q_TEST_PERFORMANCE 0 @@ -133,7 +134,7 @@ QVector<DataType> generateData(QString dataSetType, const int length) QVector<DataType> container; if (dataSetType == "Random") { for (int i = 0; i < length; ++i) - container.append(rand()); + container.append(QRandomGenerator::global()->generate()); } else if (dataSetType == "Ascending") { for (int i = 0; i < length; ++i) container.append(i); @@ -1082,12 +1083,12 @@ void tst_QAlgorithms::popCount_data_impl(size_t sizeof_T_Int) // and some random ones: if (sizeof_T_Int >= 8) for (size_t i = 0; i < 1000; ++i) { - const quint64 input = quint64(qrand()) << 32 | quint32(qrand()); + const quint64 input = QRandomGenerator::global()->generate64(); QTest::addRow("0x%016llx", input) << input << bitsSetInInt64(input); } else if (sizeof_T_Int >= 2) for (size_t i = 0; i < 1000 ; ++i) { - const quint32 input = qrand(); + const quint32 input = QRandomGenerator::global()->generate(); if (sizeof_T_Int >= 4) QTest::addRow("0x%08x", input) << quint64(input) << bitsSetInInt(input); else @@ -1129,7 +1130,7 @@ void tst_QAlgorithms::countTrailing_data_impl(size_t sizeof_T_Int) // and some random ones: for (uint i = 0; i < sizeof_T_Int*8; ++i) { for (uint j = 0; j < sizeof_T_Int*3; ++j) { // 3 is arbitrary - const quint64 r = quint64(qrand()) << 32 | quint32(qrand()); + const quint64 r = QRandomGenerator::global()->generate64(); const quint64 b = Q_UINT64_C(1) << i; const quint64 mask = ((~(b-1)) ^ b) & type_mask; const quint64 input = (r&mask) | b; @@ -1166,7 +1167,7 @@ void tst_QAlgorithms::countLeading_data_impl(size_t sizeof_T_Int) // and some random ones: for (uint i = 0; i < sizeof_T_Int*8; ++i) { for (uint j = 0; j < sizeof_T_Int*3; ++j) { // 3 is arbitrary - const quint64 r = quint64(qrand()) << 32 | quint32(qrand()); + const quint64 r = QRandomGenerator::global()->generate64(); const quint64 b = Q_UINT64_C(1) << i; const quint64 mask = b-1; const quint64 input = (r&mask) | b; diff --git a/tests/auto/corelib/tools/qchar/tst_qchar.cpp b/tests/auto/corelib/tools/qchar/tst_qchar.cpp index e5a6e953d3..cf4f6d21e2 100644 --- a/tests/auto/corelib/tools/qchar/tst_qchar.cpp +++ b/tests/auto/corelib/tools/qchar/tst_qchar.cpp @@ -36,6 +36,8 @@ class tst_QChar : public QObject { Q_OBJECT private slots: + void fromChar16_t(); + void fromWchar_t(); void operator_eqeq_null(); void operators_data(); void operators(); @@ -72,6 +74,33 @@ private slots: void unicodeVersion(); }; +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED + +void tst_QChar::fromChar16_t() +{ +#if defined(Q_COMPILER_UNICODE_STRINGS) + QChar aUmlaut = u'\u00E4'; // German small letter a-umlaut + QCOMPARE(aUmlaut, QChar(0xE4)); + QChar replacementCharacter = u'\uFFFD'; + QCOMPARE(replacementCharacter, QChar(QChar::ReplacementCharacter)); +#else + QSKIP("This test requires C++11 char16_t support enabled in the compiler."); +#endif +} + +void tst_QChar::fromWchar_t() +{ +#if defined(Q_OS_WIN) + QChar aUmlaut = L'\u00E4'; // German small letter a-umlaut + QCOMPARE(aUmlaut, QChar(0xE4)); + QChar replacementCharacter = L'\uFFFD'; + QCOMPARE(replacementCharacter, QChar(QChar::ReplacementCharacter)); +#else + QSKIP("This is a Windows-only test."); +#endif +} + void tst_QChar::operator_eqeq_null() { { diff --git a/tests/auto/corelib/tools/qdate/tst_qdate.cpp b/tests/auto/corelib/tools/qdate/tst_qdate.cpp index f88eac1a9f..ff31f01d7c 100644 --- a/tests/auto/corelib/tools/qdate/tst_qdate.cpp +++ b/tests/auto/corelib/tools/qdate/tst_qdate.cpp @@ -1490,7 +1490,7 @@ void tst_QDate::roundtrip() const void tst_QDate::qdebug() const { - QTest::ignoreMessage(QtDebugMsg, "QDate(\"\")"); + QTest::ignoreMessage(QtDebugMsg, "QDate(Invalid)"); qDebug() << QDate(); QTest::ignoreMessage(QtDebugMsg, "QDate(\"1983-08-07\")"); qDebug() << QDate(1983, 8, 7); diff --git a/tests/auto/corelib/tools/qdatetime/tst_qdatetime.cpp b/tests/auto/corelib/tools/qdatetime/tst_qdatetime.cpp index 91b5966fa8..d2ffe50ce8 100644 --- a/tests/auto/corelib/tools/qdatetime/tst_qdatetime.cpp +++ b/tests/auto/corelib/tools/qdatetime/tst_qdatetime.cpp @@ -131,6 +131,8 @@ private slots: void setOffsetFromUtc(); void toOffsetFromUtc(); + void zoneAtTime_data(); + void zoneAtTime(); void timeZoneAbbreviation(); void getDate(); @@ -1378,11 +1380,13 @@ void tst_QDateTime::toTimeSpec() QCOMPARE(utcToLocal.date(), fromLocal.date()); QCOMPARE(utcToLocal.time(), fromLocal.time()); QCOMPARE(utcToLocal.timeSpec(), Qt::LocalTime); + QCOMPARE(utcToLocal.toTimeSpec(Qt::UTC), fromUtc); QCOMPARE(localToUtc, fromUtc); QCOMPARE(localToUtc.date(), fromUtc.date()); QCOMPARE(localToUtc.time(), fromUtc.time()); QCOMPARE(localToUtc.timeSpec(), Qt::UTC); + QCOMPARE(localToUtc.toTimeSpec(Qt::LocalTime), fromLocal); QCOMPARE(utcToUtc, localToUtc); QCOMPARE(utcToUtc.date(), localToUtc.date()); @@ -1399,11 +1403,13 @@ void tst_QDateTime::toTimeSpec() QCOMPARE(utcToOffset.date(), fromUtc.date()); QCOMPARE(utcToOffset.time(), fromUtc.time()); QCOMPARE(utcToOffset.timeSpec(), Qt::UTC); + QCOMPARE(utcToOffset.toTimeSpec(Qt::UTC), fromUtc); QCOMPARE(localToOffset, fromUtc); QCOMPARE(localToOffset.date(), fromUtc.date()); QCOMPARE(localToOffset.time(), fromUtc.time()); QCOMPARE(localToOffset.timeSpec(), Qt::UTC); + QCOMPARE(localToOffset.toTimeSpec(Qt::LocalTime), fromLocal); } else { QSKIP("Not tested with timezone other than Central European (CET/CEST)"); } @@ -1906,7 +1912,7 @@ void tst_QDateTime::operator_eqeq_data() QDateTime dateTime1a = dateTime1.addMSecs(1); QDateTime dateTime2(QDate(2012, 20, 6), QTime(14, 33, 2, 500)); QDateTime dateTime2a = dateTime2.addMSecs(-1); - QDateTime dateTime3(QDate(1970, 1, 1), QTime(0, 0, 0, 0), Qt::UTC); + QDateTime dateTime3(QDate(1970, 1, 1), QTime(0, 0, 0, 0), Qt::UTC); // UTC epoch QDateTime dateTime3a = dateTime3.addDays(1); QDateTime dateTime3b = dateTime3.addDays(-1); // Ensure that different times may be equal when considering timezone. @@ -1914,8 +1920,7 @@ void tst_QDateTime::operator_eqeq_data() dateTime3c.setOffsetFromUtc(3600); QDateTime dateTime3d(dateTime3.addSecs(-3600)); dateTime3d.setOffsetFromUtc(-3600); - // Convert from UTC to local. - QDateTime dateTime3e(dateTime3.date(), dateTime3.time()); + QDateTime dateTime3e(dateTime3.date(), dateTime3.time()); // Local time's epoch QTest::newRow("data0") << dateTime1 << dateTime1 << true << false; QTest::newRow("data1") << dateTime2 << dateTime2 << true << false; @@ -1930,8 +1935,11 @@ void tst_QDateTime::operator_eqeq_data() QTest::newRow("data10") << dateTime3 << dateTime3c << true << false; QTest::newRow("data11") << dateTime3 << dateTime3d << true << false; QTest::newRow("data12") << dateTime3c << dateTime3d << true << false; - QTest::newRow("data13") << dateTime3 << dateTime3e - << (localTimeType == LocalTimeIsUtc) << false; + if (localTimeType == LocalTimeIsUtc) + QTest::newRow("data13") << dateTime3 << dateTime3e << true << false; + // ... but a zone (sometimes) ahead of or behind UTC (e.g. Europe/London) + // might agree with UTC about the epoch, all the same. + QTest::newRow("invalid == invalid") << invalidDateTime() << invalidDateTime() << true << false; QTest::newRow("invalid == valid #1") << invalidDateTime() << dateTime1 << false << false; @@ -2391,8 +2399,8 @@ void tst_QDateTime::fromStringStringFormat_data() QTest::newRow("data14") << QString("32.01.2004") << QString("dd.MM.yyyy") << invalidDateTime(); QTest::newRow("data15") << QString("Thu January 2004") << QString("ddd MMMM yyyy") << QDateTime(QDate(2004, 1, 1), QTime()); QTest::newRow("data16") << QString("2005-06-28T07:57:30.001Z") - << QString("yyyy-MM-ddThh:mm:ss.zZ") - << QDateTime(QDate(2005, 06, 28), QTime(07, 57, 30, 1)); + << QString("yyyy-MM-ddThh:mm:ss.zt") + << QDateTime(QDate(2005, 06, 28), QTime(07, 57, 30, 1), Qt::UTC); } void tst_QDateTime::fromStringStringFormat() @@ -2480,21 +2488,19 @@ void tst_QDateTime::fromStringToStringLocale() QLocale def; QLocale::setDefault(QLocale(QLocale::French, QLocale::France)); +#define ROUNDTRIP(format) \ + QCOMPARE(QDateTime::fromString(dateTime.toString(format), format), dateTime) - QCOMPARE(QDateTime::fromString(dateTime.toString(Qt::DefaultLocaleShortDate), Qt::DefaultLocaleShortDate), dateTime); - QCOMPARE(QDateTime::fromString(dateTime.toString(Qt::SystemLocaleShortDate), Qt::SystemLocaleShortDate), dateTime); + ROUNDTRIP(Qt::DefaultLocaleShortDate); + ROUNDTRIP(Qt::SystemLocaleShortDate); // obsolete - QCOMPARE(QDateTime::fromString(dateTime.toString(Qt::SystemLocaleDate), Qt::SystemLocaleDate), dateTime); - QCOMPARE(QDateTime::fromString(dateTime.toString(Qt::LocaleDate), Qt::LocaleDate), dateTime); - - QEXPECT_FAIL("data0", "This format is apparently failing because of a bug in the datetime parser. (QTBUG-22833)", Continue); - QCOMPARE(QDateTime::fromString(dateTime.toString(Qt::DefaultLocaleLongDate), Qt::DefaultLocaleLongDate), dateTime); -#ifndef Q_OS_WIN - QEXPECT_FAIL("data0", "This format is apparently failing because of a bug in the datetime parser. (QTBUG-22833)", Continue); -#endif - QCOMPARE(QDateTime::fromString(dateTime.toString(Qt::SystemLocaleLongDate), Qt::SystemLocaleLongDate), dateTime); + ROUNDTRIP(Qt::SystemLocaleDate); + ROUNDTRIP(Qt::LocaleDate); + ROUNDTRIP(Qt::DefaultLocaleLongDate); + ROUNDTRIP(Qt::SystemLocaleLongDate); +#undef ROUNDTRIP QLocale::setDefault(def); } @@ -2629,6 +2635,71 @@ void tst_QDateTime::toOffsetFromUtc() QCOMPARE(dt2.time(), QTime(0, 0, 0)); } +void tst_QDateTime::zoneAtTime_data() +{ + QTest::addColumn<QByteArray>("ianaID"); + QTest::addColumn<QDate>("date"); + QTest::addColumn<int>("offset"); +#define ADDROW(name, zone, date, offset) \ + QTest::newRow(name) << QByteArray(zone) << (date) << (offset) + + // Check DST handling around epoch: + { + QDate epoch(1970, 1, 1); + ADDROW("epoch:UTC", "UTC", epoch, 0); + // Paris and Berlin skipped DST around 1970; but Rome used it. + ADDROW("epoch:CET", "Europe/Rome", epoch, 3600); + ADDROW("epoch:PST", "America/Vancouver", epoch, -8 * 3600); + ADDROW("epoch:EST", "America/New_York", epoch, -5 * 3600); + } + { + // QDateTime deliberately ignores DST before the epoch. + QDate summer69(1969, 8, 15); // Woodstock started + ADDROW("summer69:UTC", "UTC", summer69, 0); + ADDROW("summer69:CET", "Europe/Rome", summer69, 3600); + ADDROW("summer69:PST", "America/Vancouver", summer69, -8 * 3600); + ADDROW("summer69:EST", "America/New_York", summer69, -5 * 3600); + } + { + // ... but takes it into account after: + QDate summer70(1970, 8, 26); // Isle of Wight festival + ADDROW("summer70:UTC", "UTC", summer70, 0); + ADDROW("summer70:CET", "Europe/Rome", summer70, 2 * 3600); + ADDROW("summer70:PST", "America/Vancouver", summer70, -7 * 3600); + ADDROW("summer70:EST", "America/New_York", summer70, -4 * 3600); + } + +#ifndef Q_OS_WIN + // Bracket a few noteworthy transitions: + ADDROW("before:ACWST", "Australia/Eucla", QDate(1974, 10, 26), 31500); // 8:45 + ADDROW("after:ACWST", "Australia/Eucla", QDate(1974, 10, 27), 35100); // 9:45 + ADDROW("before:NPT", "Asia/Kathmandu", QDate(1985, 12, 31), 19800); // 5:30 + ADDROW("after:NPT", "Asia/Kathmandu", QDate(1986, 1, 1), 20700); // 5:45 + // The two that have skipped a day (each): + ADDROW("before:LINT", "Pacific/Kiritimati", QDate(1994, 12, 31), -36000); + ADDROW("after:LINT", "Pacific/Kiritimati", QDate(1995, 2, 1), 14 * 3600); + ADDROW("after:WST", "Pacific/Apia", QDate(2011, 12, 31), 14 * 3600); +#endif // MS lacks ACWST, NPT; doesn't grok date-line crossings; and Windows 7 lacks LINT. + ADDROW("before:WST", "Pacific/Apia", QDate(2011, 12, 29), -36000); +#undef ADDROW +} + +void tst_QDateTime::zoneAtTime() +{ + QFETCH(QByteArray, ianaID); + QFETCH(QDate, date); + QFETCH(int, offset); + const QTime noon(12, 0); + + QTimeZone zone(ianaID); + QVERIFY(zone.isValid()); + QCOMPARE(QDateTime(date, noon, zone).offsetFromUtc(), offset); + if (date.year() < 1970) + QCOMPARE(zone.standardTimeOffset(QDateTime(date, noon, zone)), offset); + else // zone.offsetFromUtc *does* include DST, even before epoch + QCOMPARE(zone.offsetFromUtc(QDateTime(date, noon, zone)), offset); +} + void tst_QDateTime::timeZoneAbbreviation() { QDateTime dt1(QDate(2013, 1, 1), QTime(1, 0, 0), Qt::OffsetFromUTC, 60 * 60); diff --git a/tests/auto/corelib/tools/qhash/tst_qhash.cpp b/tests/auto/corelib/tools/qhash/tst_qhash.cpp index 943b4316ff..0015efacfa 100644 --- a/tests/auto/corelib/tools/qhash/tst_qhash.cpp +++ b/tests/auto/corelib/tools/qhash/tst_qhash.cpp @@ -60,6 +60,7 @@ private slots: void compare2(); void iterators(); // sligthly modified from tst_QMap void keyIterator(); + void keyValueIterator(); void keys_values_uniqueKeys(); // slightly modified from tst_QMap void noNeedlessRehashes(); @@ -1124,6 +1125,60 @@ void tst_QHash::keyIterator() Q_STATIC_ASSERT(std::is_default_constructible<keyIterator>::value); } +void tst_QHash::keyValueIterator() +{ + QHash<int, int> hash; + typedef QHash<int, int>::const_key_value_iterator::value_type entry_type; + + for (int i = 0; i < 100; ++i) + hash.insert(i, i * 100); + + auto key_value_it = hash.constKeyValueBegin(); + auto it = hash.cbegin(); + + + for (int i = 0; i < hash.size(); ++i) { + QVERIFY(key_value_it != hash.constKeyValueEnd()); + QVERIFY(it != hash.cend()); + + entry_type pair(it.key(), it.value()); + QCOMPARE(*key_value_it, pair); + ++key_value_it; + ++it; + } + + QVERIFY(key_value_it == hash.constKeyValueEnd()); + QVERIFY(it == hash.cend()); + + int key = 50; + int value = 50 * 100; + entry_type pair(key, value); + key_value_it = std::find(hash.constKeyValueBegin(), hash.constKeyValueEnd(), pair); + it = std::find(hash.cbegin(), hash.cend(), value); + + QVERIFY(key_value_it != hash.constKeyValueEnd()); + QCOMPARE(*key_value_it, entry_type(it.key(), it.value())); + + ++it; + ++key_value_it; + QCOMPARE(*key_value_it, entry_type(it.key(), it.value())); + + --it; + --key_value_it; + QCOMPARE(*key_value_it, entry_type(it.key(), it.value())); + + ++it; + ++key_value_it; + QCOMPARE(*key_value_it, entry_type(it.key(), it.value())); + + --it; + --key_value_it; + QCOMPARE(*key_value_it, entry_type(it.key(), it.value())); + key = 99; + value = 99 * 100; + QCOMPARE(std::count(hash.constKeyValueBegin(), hash.constKeyValueEnd(), entry_type(key, value)), 1); +} + void tst_QHash::rehash_isnt_quadratic() { // this test should be incredibly slow if rehash() is quadratic diff --git a/tests/auto/corelib/tools/qhashfunctions/tst_qhashfunctions.cpp b/tests/auto/corelib/tools/qhashfunctions/tst_qhashfunctions.cpp index 4a81adf9fe..124e3cdf00 100644 --- a/tests/auto/corelib/tools/qhashfunctions/tst_qhashfunctions.cpp +++ b/tests/auto/corelib/tools/qhashfunctions/tst_qhashfunctions.cpp @@ -49,6 +49,7 @@ public slots: void init(); private Q_SLOTS: + void consistent(); void qhash(); void qhash_of_empty_and_null_qstring(); void qhash_of_empty_and_null_qbytearray(); @@ -61,6 +62,17 @@ private Q_SLOTS: void setGlobalQHashSeed(); }; +void tst_QHashFunctions::consistent() +{ + // QString-like + { + const QString s = QStringLiteral("abcdefghijklmnopqrstuvxyz").repeated(16); + + QCOMPARE(qHash(s), qHash(QStringRef(&s))); + QCOMPARE(qHash(s), qHash(QStringView(s))); + } +} + void tst_QHashFunctions::initTestCase() { Q_STATIC_ASSERT(int(RandomSeed) > 0); @@ -160,6 +172,14 @@ void tst_QHashFunctions::qhash_of_empty_and_null_qstring() QString null, empty(""); QCOMPARE(null, empty); QCOMPARE(qHash(null, seed), qHash(empty, seed)); + + QStringRef nullRef, emptyRef(&empty); + QCOMPARE(nullRef, emptyRef); + QCOMPARE(qHash(nullRef, seed), qHash(emptyRef, seed)); + + QStringView nullView, emptyView(empty); + QCOMPARE(nullView, emptyView); + QCOMPARE(qHash(nullView, seed), qHash(emptyView, seed)); } void tst_QHashFunctions::qhash_of_empty_and_null_qbytearray() @@ -264,17 +284,17 @@ void tst_QHashFunctions::rangeCommutative() void tst_QHashFunctions::setGlobalQHashSeed() { // Setter works as advertised - qSetGlobalQHashSeed(0x10101010); - QCOMPARE(qGlobalQHashSeed(), 0x10101010); + qSetGlobalQHashSeed(0); + QCOMPARE(qGlobalQHashSeed(), 0); // Creating a new QHash doesn't reset the seed QHash<QString, int> someHash; someHash.insert("foo", 42); - QCOMPARE(qGlobalQHashSeed(), 0x10101010); + QCOMPARE(qGlobalQHashSeed(), 0); // Reset works as advertised qSetGlobalQHashSeed(-1); - QVERIFY(qGlobalQHashSeed() != -1); + QVERIFY(qGlobalQHashSeed() > 0); } QTEST_APPLESS_MAIN(tst_QHashFunctions) diff --git a/tests/auto/corelib/tools/qlatin1string/tst_qlatin1string.cpp b/tests/auto/corelib/tools/qlatin1string/tst_qlatin1string.cpp index a68671d899..c8373b6ae9 100644 --- a/tests/auto/corelib/tools/qlatin1string/tst_qlatin1string.cpp +++ b/tests/auto/corelib/tools/qlatin1string/tst_qlatin1string.cpp @@ -48,6 +48,7 @@ private Q_SLOTS: void midLeftRight(); void nullString(); void emptyString(); + void iterators(); void relationalOperators_data(); void relationalOperators(); }; @@ -155,6 +156,22 @@ void tst_QLatin1String::emptyString() } } +void tst_QLatin1String::iterators() +{ + QLatin1String hello("hello"); + QLatin1String olleh("olleh"); + + QVERIFY(std::equal(hello.begin(), hello.end(), + olleh.rbegin())); + QVERIFY(std::equal(hello.rbegin(), hello.rend(), + QT_MAKE_CHECKED_ARRAY_ITERATOR(olleh.begin(), olleh.size()))); + + QVERIFY(std::equal(hello.cbegin(), hello.cend(), + olleh.rbegin())); + QVERIFY(std::equal(hello.crbegin(), hello.crend(), + QT_MAKE_CHECKED_ARRAY_ITERATOR(olleh.begin(), olleh.size()))); +} + void tst_QLatin1String::relationalOperators_data() { QTest::addColumn<QLatin1StringContainer>("lhs"); diff --git a/tests/auto/corelib/tools/qlocale/tst_qlocale.cpp b/tests/auto/corelib/tools/qlocale/tst_qlocale.cpp index 10d78b1f2f..d424e6086d 100644 --- a/tests/auto/corelib/tools/qlocale/tst_qlocale.cpp +++ b/tests/auto/corelib/tools/qlocale/tst_qlocale.cpp @@ -137,6 +137,8 @@ private slots: void textDirection_data(); void textDirection(); + void formattedDataSize_data(); + void formattedDataSize(); void bcp47Name(); private: @@ -1221,6 +1223,9 @@ void tst_QLocale::dayOfWeek() QCOMPARE(QLocale::c().toString(date, "ddd"), shortName); QCOMPARE(QLocale::c().toString(date, "dddd"), longName); + + QCOMPARE(QLocale::c().toString(date, QStringViewLiteral("ddd")), shortName); + QCOMPARE(QLocale::c().toString(date, QStringViewLiteral("dddd")), longName); } void tst_QLocale::formatDate_data() @@ -1263,6 +1268,7 @@ void tst_QLocale::formatDate() QLocale l(QLocale::C); QCOMPARE(l.toString(date, format), result); + QCOMPARE(l.toString(date, QStringView(format)), result); } @@ -1319,6 +1325,7 @@ void tst_QLocale::formatTime() QLocale l(QLocale::C); QCOMPARE(l.toString(time, format), result); + QCOMPARE(l.toString(time, QStringView(format)), result); } @@ -1480,6 +1487,7 @@ void tst_QLocale::formatDateTime() QLocale l(localeName); QCOMPARE(l.toString(dateTime, format), result); + QCOMPARE(l.toString(dateTime, QStringView(format)), result); } void tst_QLocale::formatTimeZone() @@ -2203,9 +2211,9 @@ void tst_QLocale::timeFormat() QCOMPARE(c.timeFormat(QLocale::NarrowFormat), c.timeFormat(QLocale::ShortFormat)); const QLocale no("no_NO"); - QCOMPARE(no.timeFormat(QLocale::NarrowFormat), QLatin1String("HH.mm")); - QCOMPARE(no.timeFormat(QLocale::ShortFormat), QLatin1String("HH.mm")); - QCOMPARE(no.timeFormat(QLocale::LongFormat), QLatin1String("HH.mm.ss t")); + QCOMPARE(no.timeFormat(QLocale::NarrowFormat), QLatin1String("HH:mm")); + QCOMPARE(no.timeFormat(QLocale::ShortFormat), QLatin1String("HH:mm")); + QCOMPARE(no.timeFormat(QLocale::LongFormat), QLatin1String("HH:mm:ss t")); const QLocale id("id_ID"); QCOMPARE(id.timeFormat(QLocale::ShortFormat), QLatin1String("HH.mm")); @@ -2227,9 +2235,9 @@ void tst_QLocale::dateTimeFormat() QCOMPARE(c.dateTimeFormat(QLocale::NarrowFormat), c.dateTimeFormat(QLocale::ShortFormat)); const QLocale no("no_NO"); - QCOMPARE(no.dateTimeFormat(QLocale::NarrowFormat), QLatin1String("dd.MM.yyyy HH.mm")); - QCOMPARE(no.dateTimeFormat(QLocale::ShortFormat), QLatin1String("dd.MM.yyyy HH.mm")); - QCOMPARE(no.dateTimeFormat(QLocale::LongFormat), QLatin1String("dddd d. MMMM yyyy HH.mm.ss t")); + QCOMPARE(no.dateTimeFormat(QLocale::NarrowFormat), QLatin1String("dd.MM.yyyy HH:mm")); + QCOMPARE(no.dateTimeFormat(QLocale::ShortFormat), QLatin1String("dd.MM.yyyy HH:mm")); + QCOMPARE(no.dateTimeFormat(QLocale::LongFormat), QLatin1String("dddd d. MMMM yyyy HH:mm:ss t")); } void tst_QLocale::monthName() @@ -2510,8 +2518,8 @@ void tst_QLocale::textDirection_data() default: break; } - QString testName = QLocalePrivate::languageToCode(QLocale::Language(language)); - QTest::newRow(testName.toLatin1().constData()) << language << int(QLocale::AnyScript) << rightToLeft; + const QLatin1String testName = QLocalePrivate::languageToCode(QLocale::Language(language)); + QTest::newRow(qPrintable(testName)) << language << int(QLocale::AnyScript) << rightToLeft; } QTest::newRow("pa_Arab") << int(QLocale::Punjabi) << int(QLocale::ArabicScript) << true; QTest::newRow("uz_Arab") << int(QLocale::Uzbek) << int(QLocale::ArabicScript) << true; @@ -2527,6 +2535,81 @@ void tst_QLocale::textDirection() QCOMPARE(locale.textDirection() == Qt::RightToLeft, rightToLeft); } +void tst_QLocale::formattedDataSize_data() +{ + QTest::addColumn<QLocale::Language>("language"); + QTest::addColumn<int>("decimalPlaces"); + QTest::addColumn<QLocale::DataSizeFormats>("units"); + QTest::addColumn<int>("bytes"); + QTest::addColumn<QString>("output"); + + struct { + const char *name; + QLocale::Language lang; + const char *bytes; + const char abbrev; + const char sep; // decimal separator + } data[] = { + { "English", QLocale::English, "bytes", 'B', '.' }, + { "French", QLocale::French, "octets", 'o', ',' }, + { "C", QLocale::C, "bytes", 'B', '.' } + }; + + for (const auto row : data) { +#define ROWB(id, deci, num, text) \ + QTest::addRow("%s-%s", row.name, id) \ + << row.lang << deci << format \ + << num << (QString(text) + QChar(' ') + QString(row.bytes)) +#define ROWQ(id, deci, num, head, tail) \ + QTest::addRow("%s-%s", row.name, id) \ + << row.lang << deci << format \ + << num << (QString(head) + QChar(row.sep) + QString(tail) + QChar(row.abbrev)) + + // Metatype system fails to handle raw enum members as format; needs variable + { + const QLocale::DataSizeFormats format = QLocale::DataSizeIecFormat; + ROWB("IEC-0", 2, 0, "0"); + ROWB("IEC-10", 2, 10, "10"); + ROWQ("IEC-12Ki", 2, 12345, "12", "06 Ki"); + ROWQ("IEC-16Ki", 2, 16384, "16", "00 Ki"); + ROWQ("IEC-1235k", 2, 1234567, "1", "18 Mi"); + ROWQ("IEC-1374k", 2, 1374744, "1", "31 Mi"); + ROWQ("IEC-1234M", 2, 1234567890, "1", "15 Gi"); + } + { + const QLocale::DataSizeFormats format = QLocale::DataSizeTraditionalFormat; + ROWB("Trad-0", 2, 0, "0"); + ROWB("Trad-10", 2, 10, "10"); + ROWQ("Trad-12Ki", 2, 12345, "12", "06 k"); + ROWQ("Trad-16Ki", 2, 16384, "16", "00 k"); + ROWQ("Trad-1235k", 2, 1234567, "1", "18 M"); + ROWQ("Trad-1374k", 2, 1374744, "1", "31 M"); + ROWQ("Trad-1234M", 2, 1234567890, "1", "15 G"); + } + { + const QLocale::DataSizeFormats format = QLocale::DataSizeSIFormat; + ROWB("Decimal-0", 2, 0, "0"); + ROWB("Decimal-10", 2, 10, "10"); + ROWQ("Decimal-16Ki", 2, 16384, "16", "38 k"); + ROWQ("Decimal-1234k", 2, 1234567, "1", "23 M"); + ROWQ("Decimal-1374k", 2, 1374744, "1", "37 M"); + ROWQ("Decimal-1234M", 2, 1234567890, "1", "23 G"); + } +#undef ROWQ +#undef ROWB + } +} + +void tst_QLocale::formattedDataSize() +{ + QFETCH(QLocale::Language, language); + QFETCH(int, decimalPlaces); + QFETCH(QLocale::DataSizeFormats, units); + QFETCH(int, bytes); + QFETCH(QString, output); + QCOMPARE(QLocale(language).formattedDataSize(bytes, decimalPlaces, units), output); +} + void tst_QLocale::bcp47Name() { QCOMPARE(QLocale("C").bcp47Name(), QStringLiteral("en")); diff --git a/tests/auto/corelib/tools/qmacautoreleasepool/qmacautoreleasepool.pro b/tests/auto/corelib/tools/qmacautoreleasepool/qmacautoreleasepool.pro new file mode 100644 index 0000000000..26b3a47472 --- /dev/null +++ b/tests/auto/corelib/tools/qmacautoreleasepool/qmacautoreleasepool.pro @@ -0,0 +1,4 @@ +CONFIG += testcase +TARGET = tst_qmacautoreleasepool +QT = core testlib +SOURCES = tst_qmacautoreleasepool.mm diff --git a/tests/auto/corelib/tools/qmacautoreleasepool/tst_qmacautoreleasepool.mm b/tests/auto/corelib/tools/qmacautoreleasepool/tst_qmacautoreleasepool.mm new file mode 100644 index 0000000000..8f1069f419 --- /dev/null +++ b/tests/auto/corelib/tools/qmacautoreleasepool/tst_qmacautoreleasepool.mm @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> + +#include <Foundation/Foundation.h> + +class tst_QMacAutoreleasePool : public QObject +{ + Q_OBJECT +private slots: + void noPool(); + void rootLevelPool(); + void stackAllocatedPool(); + void heapAllocatedPool(); +}; + +static id lastDeallocedObject = nil; + +@interface DeallocTracker : NSObject @end +@implementation DeallocTracker +-(void)dealloc +{ + lastDeallocedObject = self; + [super dealloc]; +} +@end + +void tst_QMacAutoreleasePool::noPool() +{ + // No pool, will not be released, but should not crash + + [[[DeallocTracker alloc] init] autorelease]; +} + +void tst_QMacAutoreleasePool::rootLevelPool() +{ + // The root level case, no NSAutoreleasePool since we're not in the main + // runloop, and objects autoreleased as part of main. + + NSObject *allocedObject = nil; + { + QMacAutoReleasePool qtPool; + allocedObject = [[[DeallocTracker alloc] init] autorelease]; + } + QCOMPARE(lastDeallocedObject, allocedObject); +} + +void tst_QMacAutoreleasePool::stackAllocatedPool() +{ + // The normal case, other pools surrounding our pool, draining + // our pool before any other pool. + + NSObject *allocedObject = nil; + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + { + QMacAutoReleasePool qtPool; + allocedObject = [[[DeallocTracker alloc] init] autorelease]; + } + QCOMPARE(lastDeallocedObject, allocedObject); + [pool drain]; +} + +void tst_QMacAutoreleasePool::heapAllocatedPool() +{ + // The special case, a pool allocated on the heap, or as a member of a + // heap allocated object. This is not a supported use of QMacAutoReleasePool, + // and will result in warnings if the pool is prematurely drained. + + NSObject *allocedObject = nil; + { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + QMacAutoReleasePool *qtPool = nullptr; + { + qtPool = new QMacAutoReleasePool; + allocedObject = [[[DeallocTracker alloc] init] autorelease]; + } + [pool drain]; + delete qtPool; + } + QCOMPARE(lastDeallocedObject, allocedObject); +} + +QTEST_APPLESS_MAIN(tst_QMacAutoreleasePool) + +#include "tst_qmacautoreleasepool.moc" diff --git a/tests/auto/corelib/tools/qmap/tst_qmap.cpp b/tests/auto/corelib/tools/qmap/tst_qmap.cpp index f42ffc0471..b39444e76f 100644 --- a/tests/auto/corelib/tools/qmap/tst_qmap.cpp +++ b/tests/auto/corelib/tools/qmap/tst_qmap.cpp @@ -61,6 +61,7 @@ private slots: void iterators(); void keyIterator(); + void keyValueIterator(); void keys_values_uniqueKeys(); void qmultimap_specific(); @@ -863,6 +864,59 @@ void tst_QMap::keyIterator() Q_STATIC_ASSERT(std::is_default_constructible<keyIterator>::value); } +void tst_QMap::keyValueIterator() +{ + QMap<int, int> map; + typedef QMap<int, int>::const_key_value_iterator::value_type entry_type; + + for (int i = 0; i < 100; ++i) + map.insert(i, i * 100); + + auto key_value_it = map.constKeyValueBegin(); + auto it = map.cbegin(); + + for (int i = 0; i < map.size(); ++i) { + QVERIFY(key_value_it != map.constKeyValueEnd()); + QVERIFY(it != map.cend()); + + entry_type pair(it.key(), it.value()); + QCOMPARE(*key_value_it, pair); + ++key_value_it; + ++it; + } + + QVERIFY(key_value_it == map.constKeyValueEnd()); + QVERIFY(it == map.cend()); + + int key = 50; + int value = 50 * 100; + entry_type pair(key, value); + key_value_it = std::find(map.constKeyValueBegin(), map.constKeyValueEnd(), pair); + it = std::find(map.cbegin(), map.cend(), value); + + QVERIFY(key_value_it != map.constKeyValueEnd()); + QCOMPARE(*key_value_it, entry_type(it.key(), it.value())); + + ++it; + ++key_value_it; + QCOMPARE(*key_value_it, entry_type(it.key(), it.value())); + + --it; + --key_value_it; + QCOMPARE(*key_value_it, entry_type(it.key(), it.value())); + + ++it; + ++key_value_it; + QCOMPARE(*key_value_it, entry_type(it.key(), it.value())); + + --it; + --key_value_it; + QCOMPARE(*key_value_it, entry_type(it.key(), it.value())); + key = 99; + value = 99 * 100; + QCOMPARE(std::count(map.constKeyValueBegin(), map.constKeyValueEnd(), entry_type(key, value)), 1); +} + void tst_QMap::keys_values_uniqueKeys() { QMap<QString, int> map; diff --git a/tests/auto/corelib/tools/qrect/tst_qrect.cpp b/tests/auto/corelib/tools/qrect/tst_qrect.cpp index d3c6412b0d..1c2221ec29 100644 --- a/tests/auto/corelib/tools/qrect/tst_qrect.cpp +++ b/tests/auto/corelib/tools/qrect/tst_qrect.cpp @@ -171,6 +171,7 @@ private slots: void containsPointF_data(); void containsPointF(); void smallRects() const; + void toRect(); }; // Used to work around some floating point precision problems. @@ -4331,5 +4332,36 @@ void tst_QRect::smallRects() const QVERIFY(r1 != r2); } +void tst_QRect::toRect() +{ + for (qreal x = 1.0; x < 2.0; x += 0.25) { + for (qreal y = 1.0; y < 2.0; y += 0.25) { + for (qreal w = 1.0; w < 2.0; w += 0.25) { + for (qreal h = 1.0; h < 2.0; h += 0.25) { + const QRectF rectf(x, y, w, h); + const QRectF rect = rectf.toRect(); + QVERIFY(qAbs(rect.x() - rectf.x()) < 1.0); + QVERIFY(qAbs(rect.y() - rectf.y()) < 1.0); + QVERIFY(qAbs(rect.width() - rectf.width()) < 1.0); + QVERIFY(qAbs(rect.height() - rectf.height()) < 1.0); + QVERIFY(qAbs(rect.right() - rectf.right()) < 1.0); + QVERIFY(qAbs(rect.bottom() - rectf.bottom()) < 1.0); + + const QRectF arect = rectf.toAlignedRect(); + QVERIFY(qAbs(arect.x() - rectf.x()) < 1.0); + QVERIFY(qAbs(arect.y() - rectf.y()) < 1.0); + QVERIFY(qAbs(arect.width() - rectf.width()) < 2.0); + QVERIFY(qAbs(arect.height() - rectf.height()) < 2.0); + QVERIFY(qAbs(arect.right() - rectf.right()) < 1.0); + QVERIFY(qAbs(arect.bottom() - rectf.bottom()) < 1.0); + + QVERIFY(arect.contains(rectf)); + QVERIFY(arect.contains(rect)); + } + } + } + } +} + QTEST_MAIN(tst_QRect) #include "tst_qrect.moc" diff --git a/tests/auto/corelib/tools/qregularexpression/tst_qregularexpression.cpp b/tests/auto/corelib/tools/qregularexpression/tst_qregularexpression.cpp index 2a93250ba5..c828551e44 100644 --- a/tests/auto/corelib/tools/qregularexpression/tst_qregularexpression.cpp +++ b/tests/auto/corelib/tools/qregularexpression/tst_qregularexpression.cpp @@ -169,6 +169,7 @@ void consistencyCheck(const QRegularExpressionMatch &match) int length = match.capturedLength(i); QString captured = match.captured(i); QStringRef capturedRef = match.capturedRef(i); + QStringView capturedView = match.capturedView(i); if (!captured.isNull()) { QVERIFY(startPos >= 0); @@ -177,11 +178,13 @@ void consistencyCheck(const QRegularExpressionMatch &match) QVERIFY(endPos >= startPos); QVERIFY((endPos - startPos) == length); QVERIFY(captured == capturedRef); + QVERIFY(captured == capturedView); } else { QVERIFY(startPos == -1); QVERIFY(endPos == -1); QVERIFY((endPos - startPos) == length); QVERIFY(capturedRef.isNull()); + QVERIFY(capturedView.isNull()); } } } diff --git a/tests/auto/corelib/tools/qscopedpointer/tst_qscopedpointer.cpp b/tests/auto/corelib/tools/qscopedpointer/tst_qscopedpointer.cpp index 21efaede00..a4b06d1b3b 100644 --- a/tests/auto/corelib/tools/qscopedpointer/tst_qscopedpointer.cpp +++ b/tests/auto/corelib/tools/qscopedpointer/tst_qscopedpointer.cpp @@ -88,7 +88,7 @@ class MySubClass : public MyClass void tst_QScopedPointer::useSubClassInConstructor() { /* Use a syntax which users typically would do. */ - QScopedPointer<MyClass> p(new MyClass()); + QScopedPointer<MyClass> p(new MySubClass()); } void tst_QScopedPointer::dataOnValue() diff --git a/tests/auto/corelib/tools/qsharedpointer/tst_qsharedpointer.cpp b/tests/auto/corelib/tools/qsharedpointer/tst_qsharedpointer.cpp index 442d4d089c..e1dcdb8407 100644 --- a/tests/auto/corelib/tools/qsharedpointer/tst_qsharedpointer.cpp +++ b/tests/auto/corelib/tools/qsharedpointer/tst_qsharedpointer.cpp @@ -1884,7 +1884,7 @@ class StrongThread: public QThread protected: void run() { - usleep(rand() % 2000); + usleep(QRandomGenerator::global()->bounded(2000)); ptr->ref(); ptr.clear(); } @@ -1897,7 +1897,7 @@ class WeakThread: public QThread protected: void run() { - usleep(rand() % 2000); + usleep(QRandomGenerator::global()->bounded(2000)); QSharedPointer<ThreadData> ptr = weak; if (ptr) ptr->ref(); @@ -1959,7 +1959,6 @@ void tst_QSharedPointer::threadStressTest() base.clear(); - srand(time(NULL)); // start threads for (int i = 0; i < allThreads.count(); ++i) if (allThreads[i]) allThreads[i]->start(); diff --git a/tests/auto/corelib/tools/qstring/tst_qstring.cpp b/tests/auto/corelib/tools/qstring/tst_qstring.cpp index 24f16b9911..70ccc72630 100644 --- a/tests/auto/corelib/tools/qstring/tst_qstring.cpp +++ b/tests/auto/corelib/tools/qstring/tst_qstring.cpp @@ -4726,8 +4726,8 @@ void tst_QString::arg() QString s14( "%1%2%3" ); QCOMPARE( s4.arg("foo"), QLatin1String("[foo]") ); - QCOMPARE( s5.arg("foo"), QLatin1String("[foo]") ); - QCOMPARE( s6.arg("foo"), QLatin1String("[foo]") ); + QCOMPARE( s5.arg(QLatin1String("foo")), QLatin1String("[foo]") ); + QCOMPARE( s6.arg(QStringViewLiteral("foo")), QLatin1String("[foo]") ); QCOMPARE( s7.arg("foo"), QLatin1String("[foo]") ); QCOMPARE( s8.arg("foo"), QLatin1String("[foo %1]") ); QCOMPARE( s8.arg("foo").arg("bar"), QLatin1String("[foo bar]") ); @@ -4788,11 +4788,11 @@ void tst_QString::arg() QCOMPARE( QString("%%%1%%%2").arg("foo").arg("bar"), QLatin1String("%%foo%%bar") ); QCOMPARE( QString("%1").arg("hello", -10), QLatin1String("hello ") ); - QCOMPARE( QString("%1").arg("hello", -5), QLatin1String("hello") ); - QCOMPARE( QString("%1").arg("hello", -2), QLatin1String("hello") ); + QCOMPARE( QString("%1").arg(QLatin1String("hello"), -5), QLatin1String("hello") ); + QCOMPARE( QString("%1").arg(QStringViewLiteral("hello"), -2), QLatin1String("hello") ); QCOMPARE( QString("%1").arg("hello", 0), QLatin1String("hello") ); - QCOMPARE( QString("%1").arg("hello", 2), QLatin1String("hello") ); - QCOMPARE( QString("%1").arg("hello", 5), QLatin1String("hello") ); + QCOMPARE( QString("%1").arg(QLatin1String("hello"), 2), QLatin1String("hello") ); + QCOMPARE( QString("%1").arg(QStringViewLiteral("hello"), 5), QLatin1String("hello") ); QCOMPARE( QString("%1").arg("hello", 10), QLatin1String(" hello") ); QCOMPARE( QString("%1%1").arg("hello"), QLatin1String("hellohello") ); QCOMPARE( QString("%2%1").arg("hello"), QLatin1String("%2hello") ); @@ -6067,14 +6067,6 @@ void tst_QString::compare_data() lower += QChar(QChar::lowSurrogate(0x10428)); QTest::newRow("data8") << upper << lower << -1 << 0; - QTest::newRow("vectorized-boundaries-7") << QString("1234567") << QString("abcdefg") << -1 << -1; - QTest::newRow("vectorized-boundaries-8") << QString("12345678") << QString("abcdefgh") << -1 << -1; - QTest::newRow("vectorized-boundaries-9") << QString("123456789") << QString("abcdefghi") << -1 << -1; - - QTest::newRow("vectorized-boundaries-15") << QString("123456789012345") << QString("abcdefghiklmnop") << -1 << -1; - QTest::newRow("vectorized-boundaries-16") << QString("1234567890123456") << QString("abcdefghiklmnopq") << -1 << -1; - QTest::newRow("vectorized-boundaries-17") << QString("12345678901234567") << QString("abcdefghiklmnopqr") << -1 << -1; - // embedded nulls // These don't work as of now. It's OK that these don't work since \0 is not a valid unicode /*QTest::newRow("data10") << QString(QByteArray("\0", 1)) << QString(QByteArray("\0", 1)) << 0 << 0; @@ -6083,6 +6075,44 @@ void tst_QString::compare_data() QTest::newRow("data13") << QString("ab\0c") << QString(QByteArray("ab\0c", 4)) << 0 << 0; QTest::newRow("data14") << QString(QByteArray("ab\0c", 4)) << QString("abc") << -1 << -1; QTest::newRow("data15") << QString("abc") << QString(QByteArray("ab\0c", 4)) << 1 << 1;*/ + + // All tests below (generated by the 3 for-loops) are meant to excercise the vectorized versions + // of ucstrncmp. + + QString in1, in2; + for (int i = 0; i < 70; ++i) { + in1 += QString::number(i % 10); + in2 += QString::number((70 - i + 1) % 10); + } + Q_ASSERT(in1.length() == in2.length()); + Q_ASSERT(in1 != in2); + Q_ASSERT(in1.at(0) < in2.at(0)); + for (int i = 0; i < in1.length(); ++i) { + Q_ASSERT(in1.at(i) != in2.at(i)); + } + + for (int i = 1; i <= 65; ++i) { + QString inp1 = in1.left(i); + QString inp2 = in2.left(i); + QTest::addRow("all-different-%d", i) << inp1 << inp2 << -1 << -1; + } + + for (int i = 1; i <= 65; ++i) { + QString start(i - 1, 'a'); + + QString in = start + QLatin1Char('a'); + QTest::addRow("all-same-%d", i) << in << in << 0 << 0; + + QString in2 = start + QLatin1Char('b'); + QTest::addRow("last-different-%d", i) << in << in2 << -1 << -1; + } + + for (int i = 0; i < 16; ++i) { + QString in1(16, 'a'); + QString in2 = in1; + in2[i] = 'b'; + QTest::addRow("all-same-except-char-%d", i) << in1 << in2 << -1 << -1; + } } static bool isLatin(const QString &s) diff --git a/tests/auto/corelib/tools/qstringapisymmetry/qstringapisymmetry.pro b/tests/auto/corelib/tools/qstringapisymmetry/qstringapisymmetry.pro index bc38b17949..a4e91e38bd 100644 --- a/tests/auto/corelib/tools/qstringapisymmetry/qstringapisymmetry.pro +++ b/tests/auto/corelib/tools/qstringapisymmetry/qstringapisymmetry.pro @@ -3,3 +3,4 @@ TARGET = tst_qstringapisymmetry QT = core testlib SOURCES = tst_qstringapisymmetry.cpp qtConfig(c++14): CONFIG += c++14 +qtConfig(c++1z): CONFIG += c++1z diff --git a/tests/auto/corelib/tools/qstringapisymmetry/tst_qstringapisymmetry.cpp b/tests/auto/corelib/tools/qstringapisymmetry/tst_qstringapisymmetry.cpp index 9d9b47b61e..61d1f86f00 100644 --- a/tests/auto/corelib/tools/qstringapisymmetry/tst_qstringapisymmetry.cpp +++ b/tests/auto/corelib/tools/qstringapisymmetry/tst_qstringapisymmetry.cpp @@ -31,6 +31,7 @@ #undef QT_ASCII_CAST_WARNINGS #include <QString> +#include <QStringView> #include <QChar> #include <QStringRef> #include <QLatin1String> @@ -44,6 +45,7 @@ Q_DECLARE_METATYPE(QStringRef) template <typename T> QString toQString(const T &t) { return QString(t); } QString toQString(const QStringRef &ref) { return ref.toString(); } +QString toQString(QStringView view) { return view.toString(); } // FIXME: these are missing at the time of writing, add them, then remove the dummies here: #define MAKE_RELOP(op, A1, A2) \ @@ -81,13 +83,15 @@ class tst_QStringApiSymmetry : public QObject void compare_impl() const; private Q_SLOTS: - // test all combinations of {QChar, QStringRef, QString, QLatin1String, QByteArray, const char*} + // test all combinations of {QChar, QStringRef, QString, QStringView, QLatin1String, QByteArray, const char*} void compare_QChar_QChar_data() { compare_data(false); } void compare_QChar_QChar() { compare_impl<QChar, QChar>(); } void compare_QChar_QStringRef_data() { compare_data(false); } void compare_QChar_QStringRef() { compare_impl<QChar, QStringRef>(); } void compare_QChar_QString_data() { compare_data(false); } void compare_QChar_QString() { compare_impl<QChar, QString>(); } + void compare_QChar_QStringView_data() { compare_data(false); } + void compare_QChar_QStringView() { compare_impl<QChar, QStringView>(); } void compare_QChar_QLatin1String_data() { compare_data(false); } void compare_QChar_QLatin1String() { compare_impl<QChar, QLatin1String>(); } void compare_QChar_QByteArray_data() { compare_data(false); } @@ -101,6 +105,8 @@ private Q_SLOTS: void compare_QStringRef_QStringRef() { compare_impl<QStringRef, QStringRef>(); } void compare_QStringRef_QString_data() { compare_data(); } void compare_QStringRef_QString() { compare_impl<QStringRef, QString>(); } + void compare_QStringRef_QStringView_data() { compare_data(); } + void compare_QStringRef_QStringView() { compare_impl<QStringRef, QStringView>(); } void compare_QStringRef_QLatin1String_data() { compare_data(); } void compare_QStringRef_QLatin1String() { compare_impl<QStringRef, QLatin1String>(); } void compare_QStringRef_QByteArray_data() { compare_data(); } @@ -114,6 +120,8 @@ private Q_SLOTS: void compare_QString_QStringRef() { compare_impl<QString, QStringRef>(); } void compare_QString_QString_data() { compare_data(); } void compare_QString_QString() { compare_impl<QString, QString>(); } + void compare_QString_QStringView_data() { compare_data(); } + void compare_QString_QStringView() { compare_impl<QString, QStringView>(); } void compare_QString_QLatin1String_data() { compare_data(); } void compare_QString_QLatin1String() { compare_impl<QString, QLatin1String>(); } void compare_QString_QByteArray_data() { compare_data(); } @@ -121,12 +129,25 @@ private Q_SLOTS: void compare_QString_const_char_star_data() { compare_data(); } void compare_QString_const_char_star() { compare_impl<QString, const char *>(); } + void compare_QStringView_QChar_data() { compare_data(false); } + void compare_QStringView_QChar() { compare_impl<QStringView, QChar>(); } + void compare_QStringView_QStringRef_data() { compare_data(); } + void compare_QStringView_QStringRef() { compare_impl<QStringView, QStringRef>(); } + void compare_QStringView_QString_data() { compare_data(); } + void compare_QStringView_QString() { compare_impl<QStringView, QString>(); } + void compare_QStringView_QStringView_data() { compare_data(); } + void compare_QStringView_QStringView() { compare_impl<QStringView, QStringView>(); } + void compare_QStringView_QLatin1String_data() { compare_data(); } + void compare_QStringView_QLatin1String() { compare_impl<QStringView, QLatin1String>(); } + void compare_QLatin1String_QChar_data() { compare_data(false); } void compare_QLatin1String_QChar() { compare_impl<QLatin1String, QChar>(); } void compare_QLatin1String_QStringRef_data() { compare_data(); } void compare_QLatin1String_QStringRef() { compare_impl<QLatin1String, QStringRef>(); } void compare_QLatin1String_QString_data() { compare_data(); } void compare_QLatin1String_QString() { compare_impl<QLatin1String, QString>(); } + void compare_QLatin1String_QStringView_data() { compare_data(); } + void compare_QLatin1String_QStringView() { compare_impl<QLatin1String, QStringView>(); } void compare_QLatin1String_QLatin1String_data() { compare_data(); } void compare_QLatin1String_QLatin1String() { compare_impl<QLatin1String, QLatin1String>(); } void compare_QLatin1String_QByteArray_data() { compare_data(); } @@ -160,6 +181,181 @@ private Q_SLOTS: //void compare_const_char_star_const_char_star_data() { compare_data(); } //void compare_const_char_star_const_char_star() { compare_impl<const char *, const char *>(); } +private: + void startsWith_data(bool rhsIsQChar = false); + template <typename Haystack, typename Needle> void startsWith_impl() const; + + void endsWith_data(bool rhsIsQChar = false); + template <typename Haystack, typename Needle> void endsWith_impl() const; + +private Q_SLOTS: + // test all combinations of {QString, QStringRef, QStringView, QLatin1String} x {QString, QStringRef, QStringView, QLatin1String, QChar}: + void startsWith_QString_QString_data() { startsWith_data(); } + void startsWith_QString_QString() { startsWith_impl<QString, QString>(); } + void startsWith_QString_QStringRef_data() { startsWith_data(); } + void startsWith_QString_QStringRef() { startsWith_impl<QString, QStringRef>(); } + void startsWith_QString_QStringView_data() { startsWith_data(); } + void startsWith_QString_QStringView() { startsWith_impl<QString, QStringView>(); } + void startsWith_QString_QLatin1String_data() { startsWith_data(); } + void startsWith_QString_QLatin1String() { startsWith_impl<QString, QLatin1String>(); } + void startsWith_QString_QChar_data() { startsWith_data(false); } + void startsWith_QString_QChar() { startsWith_impl<QString, QChar>(); } + + void startsWith_QStringRef_QString_data() { startsWith_data(); } + void startsWith_QStringRef_QString() { startsWith_impl<QStringRef, QString>(); } + void startsWith_QStringRef_QStringRef_data() { startsWith_data(); } + void startsWith_QStringRef_QStringRef() { startsWith_impl<QStringRef, QStringRef>(); } + void startsWith_QStringRef_QStringView_data() { startsWith_data(); } + void startsWith_QStringRef_QStringView() { startsWith_impl<QStringRef, QStringView>(); } + void startsWith_QStringRef_QLatin1String_data() { startsWith_data(); } + void startsWith_QStringRef_QLatin1String() { startsWith_impl<QStringRef, QLatin1String>(); } + void startsWith_QStringRef_QChar_data() { startsWith_data(false); } + void startsWith_QStringRef_QChar() { startsWith_impl<QStringRef, QChar>(); } + + void startsWith_QStringView_QString_data() { startsWith_data(); } + void startsWith_QStringView_QString() { startsWith_impl<QStringView, QString>(); } + void startsWith_QStringView_QStringRef_data() { startsWith_data(); } + void startsWith_QStringView_QStringRef() { startsWith_impl<QStringView, QStringRef>(); } + void startsWith_QStringView_QStringView_data() { startsWith_data(); } + void startsWith_QStringView_QStringView() { startsWith_impl<QStringView, QStringView>(); } + void startsWith_QStringView_QLatin1String_data() { startsWith_data(); } + void startsWith_QStringView_QLatin1String() { startsWith_impl<QStringView, QLatin1String>(); } + void startsWith_QStringView_QChar_data() { startsWith_data(false); } + void startsWith_QStringView_QChar() { startsWith_impl<QStringView, QChar>(); } + + void startsWith_QLatin1String_QString_data() { startsWith_data(); } + void startsWith_QLatin1String_QString() { startsWith_impl<QLatin1String, QString>(); } + void startsWith_QLatin1String_QStringRef_data() { startsWith_data(); } + void startsWith_QLatin1String_QStringRef() { startsWith_impl<QLatin1String, QStringRef>(); } + void startsWith_QLatin1String_QStringView_data() { startsWith_data(); } + void startsWith_QLatin1String_QStringView() { startsWith_impl<QLatin1String, QStringView>(); } + void startsWith_QLatin1String_QLatin1String_data() { startsWith_data(); } + void startsWith_QLatin1String_QLatin1String() { startsWith_impl<QLatin1String, QLatin1String>(); } + void startsWith_QLatin1String_QChar_data() { startsWith_data(false); } + void startsWith_QLatin1String_QChar() { startsWith_impl<QLatin1String, QChar>(); } + + void endsWith_QString_QString_data() { endsWith_data(); } + void endsWith_QString_QString() { endsWith_impl<QString, QString>(); } + void endsWith_QString_QStringRef_data() { endsWith_data(); } + void endsWith_QString_QStringRef() { endsWith_impl<QString, QStringRef>(); } + void endsWith_QString_QStringView_data() { endsWith_data(); } + void endsWith_QString_QStringView() { endsWith_impl<QString, QStringView>(); } + void endsWith_QString_QLatin1String_data() { endsWith_data(); } + void endsWith_QString_QLatin1String() { endsWith_impl<QString, QLatin1String>(); } + void endsWith_QString_QChar_data() { endsWith_data(false); } + void endsWith_QString_QChar() { endsWith_impl<QString, QChar>(); } + + void endsWith_QStringRef_QString_data() { endsWith_data(); } + void endsWith_QStringRef_QString() { endsWith_impl<QStringRef, QString>(); } + void endsWith_QStringRef_QStringRef_data() { endsWith_data(); } + void endsWith_QStringRef_QStringRef() { endsWith_impl<QStringRef, QStringRef>(); } + void endsWith_QStringRef_QStringView_data() { endsWith_data(); } + void endsWith_QStringRef_QStringView() { endsWith_impl<QStringRef, QStringView>(); } + void endsWith_QStringRef_QLatin1String_data() { endsWith_data(); } + void endsWith_QStringRef_QLatin1String() { endsWith_impl<QStringRef, QLatin1String>(); } + void endsWith_QStringRef_QChar_data() { endsWith_data(false); } + void endsWith_QStringRef_QChar() { endsWith_impl<QStringRef, QChar>(); } + + void endsWith_QStringView_QString_data() { endsWith_data(); } + void endsWith_QStringView_QString() { endsWith_impl<QStringView, QString>(); } + void endsWith_QStringView_QStringRef_data() { endsWith_data(); } + void endsWith_QStringView_QStringRef() { endsWith_impl<QStringView, QStringRef>(); } + void endsWith_QStringView_QStringView_data() { endsWith_data(); } + void endsWith_QStringView_QStringView() { endsWith_impl<QStringView, QStringView>(); } + void endsWith_QStringView_QLatin1String_data() { endsWith_data(); } + void endsWith_QStringView_QLatin1String() { endsWith_impl<QStringView, QLatin1String>(); } + void endsWith_QStringView_QChar_data() { endsWith_data(false); } + void endsWith_QStringView_QChar() { endsWith_impl<QStringView, QChar>(); } + + void endsWith_QLatin1String_QString_data() { endsWith_data(); } + void endsWith_QLatin1String_QString() { endsWith_impl<QLatin1String, QString>(); } + void endsWith_QLatin1String_QStringRef_data() { endsWith_data(); } + void endsWith_QLatin1String_QStringRef() { endsWith_impl<QLatin1String, QStringRef>(); } + void endsWith_QLatin1String_QStringView_data() { endsWith_data(); } + void endsWith_QLatin1String_QStringView() { endsWith_impl<QLatin1String, QStringView>(); } + void endsWith_QLatin1String_QLatin1String_data() { endsWith_data(); } + void endsWith_QLatin1String_QLatin1String() { endsWith_impl<QLatin1String, QLatin1String>(); } + void endsWith_QLatin1String_QChar_data() { endsWith_data(false); } + void endsWith_QLatin1String_QChar() { endsWith_impl<QLatin1String, QChar>(); } + +private: + void mid_data(); + template <typename String> void mid_impl(); + + void left_data(); + template <typename String> void left_impl(); + + void right_data(); + template <typename String> void right_impl(); + + void chop_data(); + template <typename String> void chop_impl(); + + void truncate_data() { left_data(); } + template <typename String> void truncate_impl(); + +private Q_SLOTS: + + void mid_QString_data() { mid_data(); } + void mid_QString() { mid_impl<QString>(); } + void mid_QStringRef_data() { mid_data(); } + void mid_QStringRef() { mid_impl<QStringRef>(); } + void mid_QStringView_data() { mid_data(); } + void mid_QStringView() { mid_impl<QStringView>(); } + void mid_QLatin1String_data() { mid_data(); } + void mid_QLatin1String() { mid_impl<QLatin1String>(); } + void mid_QByteArray_data() { mid_data(); } + void mid_QByteArray() { mid_impl<QByteArray>(); } + + void left_truncate_QString_data() { left_data(); } + void left_truncate_QString() { left_impl<QString>(); } + void left_truncate_QStringRef_data() { left_data(); } + void left_truncate_QStringRef() { left_impl<QStringRef>(); } + void left_truncate_QStringView_data() { left_data(); } + void left_truncate_QStringView() { left_impl<QStringView>(); } + void left_truncate_QLatin1String_data() { left_data(); } + void left_truncate_QLatin1String() { left_impl<QLatin1String>(); } + void left_truncate_QByteArray_data() { left_data(); } + void left_truncate_QByteArray() { left_impl<QByteArray>(); } + + void right_QString_data() { right_data(); } + void right_QString() { right_impl<QString>(); } + void right_QStringRef_data() { right_data(); } + void right_QStringRef() { right_impl<QStringRef>(); } + void right_QStringView_data() { right_data(); } + void right_QStringView() { right_impl<QStringView>(); } + void right_QLatin1String_data() { right_data(); } + void right_QLatin1String() { right_impl<QLatin1String>(); } + void right_QByteArray_data() { right_data(); } + void right_QByteArray() { right_impl<QByteArray>(); } + + void chop_QString_data() { chop_data(); } + void chop_QString() { chop_impl<QString>(); } + void chop_QStringRef_data() { chop_data(); } + void chop_QStringRef() { chop_impl<QStringRef>(); } + void chop_QStringView_data() { chop_data(); } + void chop_QStringView() { chop_impl<QStringView>(); } + void chop_QLatin1String_data() { chop_data(); } + void chop_QLatin1String() { chop_impl<QLatin1String>(); } + void chop_QByteArray_data() { chop_data(); } + void chop_QByteArray() { chop_impl<QByteArray>(); } + +private: + void trimmed_data(); + template <typename String> void trimmed_impl(); + +private Q_SLOTS: + void trim_trimmed_QString_data() { trimmed_data(); } + void trim_trimmed_QString() { trimmed_impl<QString>(); } + void trim_trimmed_QStringRef_data() { trimmed_data(); } + void trim_trimmed_QStringRef() { trimmed_impl<QStringRef>(); } + void trim_trimmed_QStringView_data() { trimmed_data(); } + void trim_trimmed_QStringView() { trimmed_impl<QStringView>(); } + void trim_trimmed_QLatin1String_data() { trimmed_data(); } + void trim_trimmed_QLatin1String() { trimmed_impl<QLatin1String>(); } + void trim_trimmed_QByteArray_data() { trimmed_data(); } + void trim_trimmed_QByteArray() { trimmed_impl<QByteArray>(); } + // // UTF-16-only checks: // @@ -183,21 +379,29 @@ private Q_SLOTS: void toLocal8Bit_QString() { toLocal8Bit_impl<QString>(); } void toLocal8Bit_QStringRef_data() { toLocal8Bit_data(); } void toLocal8Bit_QStringRef() { toLocal8Bit_impl<QStringRef>(); } + void toLocal8Bit_QStringView_data() { toLocal8Bit_data(); } + void toLocal8Bit_QStringView() { toLocal8Bit_impl<QStringView>(); } void toLatin1_QString_data() { toLatin1_data(); } void toLatin1_QString() { toLatin1_impl<QString>(); } void toLatin1_QStringRef_data() { toLatin1_data(); } void toLatin1_QStringRef() { toLatin1_impl<QStringRef>(); } + void toLatin1_QStringView_data() { toLatin1_data(); } + void toLatin1_QStringView() { toLatin1_impl<QStringView>(); } void toUtf8_QString_data() { toUtf8_data(); } void toUtf8_QString() { toUtf8_impl<QString>(); } void toUtf8_QStringRef_data() { toUtf8_data(); } void toUtf8_QStringRef() { toUtf8_impl<QStringRef>(); } + void toUtf8_QStringView_data() { toUtf8_data(); } + void toUtf8_QStringView() { toUtf8_impl<QStringView>(); } void toUcs4_QString_data() { toUcs4_data(); } void toUcs4_QString() { toUcs4_impl<QString>(); } void toUcs4_QStringRef_data() { toUcs4_data(); } void toUcs4_QStringRef() { toUcs4_impl<QStringRef>(); } + void toUcs4_QStringView_data() { toUcs4_data(); } + void toUcs4_QStringView() { toUcs4_impl<QStringView>(); } }; void tst_QStringApiSymmetry::compare_data(bool hasConceptOfNullAndEmpty) @@ -217,6 +421,9 @@ void tst_QStringApiSymmetry::compare_data(bool hasConceptOfNullAndEmpty) QTest::newRow("null <> empty") << QStringRef() << QLatin1String() << QStringRef(&empty) << QLatin1String("") << 0 << 0; + QTest::newRow("empty <> null") << QStringRef(&empty) << QLatin1String("") + << QStringRef() << QLatin1String() + << 0 << 0; } #define ROW(lhs, rhs) \ @@ -239,10 +446,20 @@ void tst_QStringApiSymmetry::compare_data(bool hasConceptOfNullAndEmpty) #undef ROW } +template <typename String> String detached(String s) +{ + if (!s.isNull()) { // detaching loses nullness, but we need to preserve it + auto d = s.data(); + Q_UNUSED(d); + } + return s; +} + template <class Str> Str make(const QStringRef &sf, QLatin1String l1, const QByteArray &u8); template <> QChar make(const QStringRef &sf, QLatin1String, const QByteArray &) { return sf.isEmpty() ? QChar() : sf.at(0); } template <> QStringRef make(const QStringRef &sf, QLatin1String, const QByteArray &) { return sf; } template <> QString make(const QStringRef &sf, QLatin1String, const QByteArray &) { return sf.toString(); } +template <> QStringView make(const QStringRef &sf, QLatin1String, const QByteArray &) { return sf; } template <> QLatin1String make(const QStringRef &, QLatin1String l1, const QByteArray &) { return l1; } template <> QByteArray make(const QStringRef &, QLatin1String, const QByteArray &u8) { return u8; } template <> const char * make(const QStringRef &, QLatin1String, const QByteArray &u8) { return u8.data(); } @@ -260,6 +477,12 @@ struct has_nothrow_compare { }; template <typename LHS, typename RHS> +struct has_qCompareStrings { + enum { value = !std::is_same<LHS, QChar>::value && !std::is_same<RHS, QChar>::value && + !is_utf8_encoded<LHS>::value && !is_utf8_encoded<RHS>::value }; +}; + +template <typename LHS, typename RHS> void tst_QStringApiSymmetry::compare_impl() const { QFETCH(QStringRef, lhsUnicode); @@ -267,6 +490,8 @@ void tst_QStringApiSymmetry::compare_impl() const QFETCH(QStringRef, rhsUnicode); QFETCH(QLatin1String, rhsLatin1); QFETCH(int, caseSensitiveCompareResult); + QFETCH(const int, caseInsensitiveCompareResult); + Q_UNUSED(caseInsensitiveCompareResult); const auto lhsU8 = lhsUnicode.toUtf8(); const auto rhsU8 = rhsUnicode.toUtf8(); @@ -300,6 +525,529 @@ void tst_QStringApiSymmetry::compare_impl() const #undef CHECK } +static QString empty = QLatin1String(""); +// the tests below rely on the fact that these objects' names match their contents: +static QString a = QStringLiteral("a"); +static QString A = QStringLiteral("A"); +static QString b = QStringLiteral("b"); +static QString B = QStringLiteral("B"); +static QString c = QStringLiteral("c"); +static QString C = QStringLiteral("C"); +static QString ab = QStringLiteral("ab"); +static QString aB = QStringLiteral("aB"); +static QString Ab = QStringLiteral("Ab"); +static QString AB = QStringLiteral("AB"); +static QString bc = QStringLiteral("bc"); +static QString bC = QStringLiteral("bC"); +static QString Bc = QStringLiteral("Bc"); +static QString BC = QStringLiteral("BC"); +static QString abc = QStringLiteral("abc"); +static QString abC = QStringLiteral("abC"); +static QString aBc = QStringLiteral("aBc"); +static QString aBC = QStringLiteral("aBC"); +static QString Abc = QStringLiteral("Abc"); +static QString AbC = QStringLiteral("AbC"); +static QString ABc = QStringLiteral("ABc"); +static QString ABC = QStringLiteral("ABC"); + +void tst_QStringApiSymmetry::startsWith_data(bool rhsHasVariableLength) +{ + QTest::addColumn<QStringRef>("haystackU16"); + QTest::addColumn<QLatin1String>("haystackL1"); + QTest::addColumn<QStringRef>("needleU16"); + QTest::addColumn<QLatin1String>("needleL1"); + QTest::addColumn<bool>("resultCS"); + QTest::addColumn<bool>("resultCIS"); + + if (rhsHasVariableLength) { + QTest::addRow("null ~= ^null") << QStringRef() << QLatin1String() + << QStringRef() << QLatin1String() << true << true; + QTest::addRow("empty ~= ^null") << QStringRef(&empty) << QLatin1String("") + << QStringRef() << QLatin1String() << true << true; + QTest::addRow("a ~= ^null") << QStringRef(&a) << QLatin1String("a") + << QStringRef() << QLatin1String() << true << true; + QTest::addRow("null ~= ^empty") << QStringRef() << QLatin1String() + << QStringRef(&empty) << QLatin1String("") << false << false; + QTest::addRow("a ~= ^empty") << QStringRef(&a) << QLatin1String("a") + << QStringRef(&empty) << QLatin1String("") << true << true; + QTest::addRow("empty ~= ^empty") << QStringRef(&empty) << QLatin1String("") + << QStringRef(&empty) << QLatin1String("") << true << true; + } + QTest::addRow("null ~= ^a") << QStringRef() << QLatin1String() + << QStringRef(&a) << QLatin1String("a") << false << false; + QTest::addRow("empty ~= ^a") << QStringRef(&empty) << QLatin1String("") + << QStringRef(&a) << QLatin1String("a") << false << false; + +#define ROW(h, n, cs, cis) \ + QTest::addRow("%s ~= ^%s", #h, #n) << QStringRef(&h) << QLatin1String(#h) \ + << QStringRef(&n) << QLatin1String(#n) \ + << bool(cs) << bool(cis) + ROW(a, a, 1, 1); + ROW(a, A, 0, 1); + ROW(a, b, 0, 0); + + if (rhsHasVariableLength) + ROW(a, aB, 0, 0); + + ROW(ab, a, 1, 1); + if (rhsHasVariableLength) { + ROW(ab, ab, 1, 1); + ROW(ab, aB, 0, 1); + ROW(ab, Ab, 0, 1); + } + ROW(ab, c, 0, 0); + + if (rhsHasVariableLength) + ROW(ab, abc, 0, 0); + + ROW(Abc, c, 0, 0); + if (rhsHasVariableLength) { + ROW(Abc, ab, 0, 1); + ROW(Abc, aB, 0, 1); + ROW(Abc, Ab, 1, 1); + ROW(Abc, AB, 0, 1); + ROW(aBC, ab, 0, 1); + ROW(aBC, aB, 1, 1); + ROW(aBC, Ab, 0, 1); + ROW(aBC, AB, 0, 1); + } + ROW(ABC, b, 0, 0); + ROW(ABC, a, 0, 1); +#undef ROW +} + +template <typename Haystack, typename Needle> +void tst_QStringApiSymmetry::startsWith_impl() const +{ + QFETCH(const QStringRef, haystackU16); + QFETCH(const QLatin1String, haystackL1); + QFETCH(const QStringRef, needleU16); + QFETCH(const QLatin1String, needleL1); + QFETCH(const bool, resultCS); + QFETCH(const bool, resultCIS); + + const auto haystackU8 = haystackU16.toUtf8(); + const auto needleU8 = needleU16.toUtf8(); + + const auto haystack = make<Haystack>(haystackU16, haystackL1, haystackU8); + const auto needle = make<Needle>(needleU16, needleL1, needleU8); + + QCOMPARE(haystack.startsWith(needle), resultCS); + QCOMPARE(haystack.startsWith(needle, Qt::CaseSensitive), resultCS); + QCOMPARE(haystack.startsWith(needle, Qt::CaseInsensitive), resultCIS); +} + +void tst_QStringApiSymmetry::endsWith_data(bool rhsHasVariableLength) +{ + QTest::addColumn<QStringRef>("haystackU16"); + QTest::addColumn<QLatin1String>("haystackL1"); + QTest::addColumn<QStringRef>("needleU16"); + QTest::addColumn<QLatin1String>("needleL1"); + QTest::addColumn<bool>("resultCS"); + QTest::addColumn<bool>("resultCIS"); + + if (rhsHasVariableLength) { + QTest::addRow("null ~= null$") << QStringRef() << QLatin1String() + << QStringRef() << QLatin1String() << true << true; + QTest::addRow("empty ~= null$") << QStringRef(&empty) << QLatin1String("") + << QStringRef() << QLatin1String() << true << true; + QTest::addRow("a ~= null$") << QStringRef(&a) << QLatin1String("a") + << QStringRef() << QLatin1String() << true << true; + QTest::addRow("null ~= empty$") << QStringRef() << QLatin1String() + << QStringRef(&empty) << QLatin1String("") << false << false; + QTest::addRow("a ~= empty$") << QStringRef(&a) << QLatin1String("a") + << QStringRef(&empty) << QLatin1String("") << true << true; + QTest::addRow("empty ~= empty$") << QStringRef(&empty) << QLatin1String("") + << QStringRef(&empty) << QLatin1String("") << true << true; + } + QTest::addRow("null ~= a$") << QStringRef() << QLatin1String() + << QStringRef(&a) << QLatin1String("a") << false << false; + QTest::addRow("empty ~= a$") << QStringRef(&empty) << QLatin1String("") + << QStringRef(&a) << QLatin1String("a") << false << false; + +#define ROW(h, n, cs, cis) \ + QTest::addRow("%s ~= %s$", #h, #n) << QStringRef(&h) << QLatin1String(#h) \ + << QStringRef(&n) << QLatin1String(#n) \ + << bool(cs) << bool(cis) + ROW(a, a, 1, 1); + ROW(a, A, 0, 1); + ROW(a, b, 0, 0); + + if (rhsHasVariableLength) + ROW(b, ab, 0, 0); + + ROW(ab, b, 1, 1); + if (rhsHasVariableLength) { + ROW(ab, ab, 1, 1); + ROW(ab, aB, 0, 1); + ROW(ab, Ab, 0, 1); + } + ROW(ab, c, 0, 0); + + if (rhsHasVariableLength) + ROW(bc, abc, 0, 0); + + ROW(Abc, c, 1, 1); + if (rhsHasVariableLength) { + ROW(Abc, bc, 1, 1); + ROW(Abc, bC, 0, 1); + ROW(Abc, Bc, 0, 1); + ROW(Abc, BC, 0, 1); + ROW(aBC, bc, 0, 1); + ROW(aBC, bC, 0, 1); + ROW(aBC, Bc, 0, 1); + ROW(aBC, BC, 1, 1); + } + ROW(ABC, b, 0, 0); + ROW(ABC, a, 0, 0); +#undef ROW +} + +template <typename Haystack, typename Needle> +void tst_QStringApiSymmetry::endsWith_impl() const +{ + QFETCH(const QStringRef, haystackU16); + QFETCH(const QLatin1String, haystackL1); + QFETCH(const QStringRef, needleU16); + QFETCH(const QLatin1String, needleL1); + QFETCH(const bool, resultCS); + QFETCH(const bool, resultCIS); + + const auto haystackU8 = haystackU16.toUtf8(); + const auto needleU8 = needleU16.toUtf8(); + + const auto haystack = make<Haystack>(haystackU16, haystackL1, haystackU8); + const auto needle = make<Needle>(needleU16, needleL1, needleU8); + + QCOMPARE(haystack.endsWith(needle), resultCS); + QCOMPARE(haystack.endsWith(needle, Qt::CaseSensitive), resultCS); + QCOMPARE(haystack.endsWith(needle, Qt::CaseInsensitive), resultCIS); +} + +void tst_QStringApiSymmetry::mid_data() +{ + QTest::addColumn<QStringRef>("unicode"); + QTest::addColumn<QLatin1String>("latin1"); + QTest::addColumn<int>("pos"); + QTest::addColumn<int>("n"); + QTest::addColumn<QStringRef>("result"); + QTest::addColumn<QStringRef>("result2"); + + QTest::addRow("null") << QStringRef() << QLatin1String() << 0 << 0 << QStringRef() << QStringRef(); + QTest::addRow("empty") << QStringRef(&empty) << QLatin1String("") << 0 << 0 << QStringRef(&empty) << QStringRef(&empty); + + // Some classes' mid() implementations have a wide contract, others a narrow one + // so only test valid arguents here: +#define ROW(base, p, n, r1, r2) \ + QTest::addRow("%s%d%d", #base, p, n) << QStringRef(&base) << QLatin1String(#base) << p << n << QStringRef(&r1) << QStringRef(&r2) + + ROW(a, 0, 0, a, empty); + ROW(a, 0, 1, a, a); + ROW(a, 1, 0, empty, empty); + + ROW(ab, 0, 0, ab, empty); + ROW(ab, 0, 1, ab, a); + ROW(ab, 0, 2, ab, ab); + ROW(ab, 1, 0, b, empty); + ROW(ab, 1, 1, b, b); + ROW(ab, 2, 0, empty, empty); + + ROW(abc, 0, 0, abc, empty); + ROW(abc, 0, 1, abc, a); + ROW(abc, 0, 2, abc, ab); + ROW(abc, 0, 3, abc, abc); + ROW(abc, 1, 0, bc, empty); + ROW(abc, 1, 1, bc, b); + ROW(abc, 1, 2, bc, bc); + ROW(abc, 2, 0, c, empty); + ROW(abc, 2, 1, c, c); + ROW(abc, 3, 0, empty, empty); +#undef ROW +} + +template <typename String> +void tst_QStringApiSymmetry::mid_impl() +{ + QFETCH(const QStringRef, unicode); + QFETCH(const QLatin1String, latin1); + QFETCH(const int, pos); + QFETCH(const int, n); + QFETCH(const QStringRef, result); + QFETCH(const QStringRef, result2); + + const auto utf8 = unicode.toUtf8(); + + const auto s = make<String>(unicode, latin1, utf8); + + { + const auto mid = s.mid(pos); + const auto mid2 = s.mid(pos, n); + + QCOMPARE(mid, result); + QCOMPARE(mid.isNull(), result.isNull()); + QCOMPARE(mid.isEmpty(), result.isEmpty()); + + QCOMPARE(mid2, result2); + QCOMPARE(mid2.isNull(), result2.isNull()); + QCOMPARE(mid2.isEmpty(), result2.isEmpty()); + } + { + const auto mid = detached(s).mid(pos); + const auto mid2 = detached(s).mid(pos, n); + + QCOMPARE(mid, result); + QCOMPARE(mid.isNull(), result.isNull()); + QCOMPARE(mid.isEmpty(), result.isEmpty()); + + QCOMPARE(mid2, result2); + QCOMPARE(mid2.isNull(), result2.isNull()); + QCOMPARE(mid2.isEmpty(), result2.isEmpty()); + } +} + +void tst_QStringApiSymmetry::left_data() +{ + QTest::addColumn<QStringRef>("unicode"); + QTest::addColumn<QLatin1String>("latin1"); + QTest::addColumn<int>("n"); + QTest::addColumn<QStringRef>("result"); + + QTest::addRow("null") << QStringRef() << QLatin1String() << 0 << QStringRef(); + QTest::addRow("empty") << QStringRef(&empty) << QLatin1String("") << 0 << QStringRef(&empty); + + // Some classes' left() implementations have a wide contract, others a narrow one + // so only test valid arguents here: +#define ROW(base, n, res) \ + QTest::addRow("%s%d", #base, n) << QStringRef(&base) << QLatin1String(#base) << n << QStringRef(&res); + + ROW(a, 0, empty); + ROW(a, 1, a); + + ROW(ab, 0, empty); + ROW(ab, 1, a); + ROW(ab, 2, ab); + + ROW(abc, 0, empty); + ROW(abc, 1, a); + ROW(abc, 2, ab); + ROW(abc, 3, abc); +#undef ROW +} + +template <typename String> +void tst_QStringApiSymmetry::left_impl() +{ + QFETCH(const QStringRef, unicode); + QFETCH(const QLatin1String, latin1); + QFETCH(const int, n); + QFETCH(const QStringRef, result); + + const auto utf8 = unicode.toUtf8(); + + const auto s = make<String>(unicode, latin1, utf8); + + { + const auto left = s.left(n); + + QCOMPARE(left, result); + QCOMPARE(left.isNull(), result.isNull()); + QCOMPARE(left.isEmpty(), result.isEmpty()); + } + { + const auto left = detached(s).left(n); + + QCOMPARE(left, result); + QCOMPARE(left.isNull(), result.isNull()); + QCOMPARE(left.isEmpty(), result.isEmpty()); + } + { + auto left = s; + left.truncate(n); + + QCOMPARE(left, result); + QCOMPARE(left.isNull(), result.isNull()); + QCOMPARE(left.isEmpty(), result.isEmpty()); + } +} + +void tst_QStringApiSymmetry::right_data() +{ + QTest::addColumn<QStringRef>("unicode"); + QTest::addColumn<QLatin1String>("latin1"); + QTest::addColumn<int>("n"); + QTest::addColumn<QStringRef>("result"); + + QTest::addRow("null") << QStringRef() << QLatin1String() << 0 << QStringRef(); + QTest::addRow("empty") << QStringRef(&empty) << QLatin1String("") << 0 << QStringRef(&empty); + + // Some classes' right() implementations have a wide contract, others a narrow one + // so only test valid arguents here: +#define ROW(base, n, res) \ + QTest::addRow("%s%d", #base, n) << QStringRef(&base) << QLatin1String(#base) << n << QStringRef(&res); + + ROW(a, 0, empty); + ROW(a, 1, a); + + ROW(ab, 0, empty); + ROW(ab, 1, b); + ROW(ab, 2, ab); + + ROW(abc, 0, empty); + ROW(abc, 1, c); + ROW(abc, 2, bc); + ROW(abc, 3, abc); +#undef ROW +} + +template <typename String> +void tst_QStringApiSymmetry::right_impl() +{ + QFETCH(const QStringRef, unicode); + QFETCH(const QLatin1String, latin1); + QFETCH(const int, n); + QFETCH(const QStringRef, result); + + const auto utf8 = unicode.toUtf8(); + + const auto s = make<String>(unicode, latin1, utf8); + + { + const auto right = s.right(n); + + QCOMPARE(right, result); + QCOMPARE(right.isNull(), result.isNull()); + QCOMPARE(right.isEmpty(), result.isEmpty()); + } + { + const auto right = detached(s).right(n); + + QCOMPARE(right, result); + QCOMPARE(right.isNull(), result.isNull()); + QCOMPARE(right.isEmpty(), result.isEmpty()); + } +} + +void tst_QStringApiSymmetry::chop_data() +{ + QTest::addColumn<QStringRef>("unicode"); + QTest::addColumn<QLatin1String>("latin1"); + QTest::addColumn<int>("n"); + QTest::addColumn<QStringRef>("result"); + + QTest::addRow("null") << QStringRef() << QLatin1String() << 0 << QStringRef(); + QTest::addRow("empty") << QStringRef(&empty) << QLatin1String("") << 0 << QStringRef(&empty); + + // Some classes' truncate() implementations have a wide contract, others a narrow one + // so only test valid arguents here: +#define ROW(base, n, res) \ + QTest::addRow("%s%d", #base, n) << QStringRef(&base) << QLatin1String(#base) << n << QStringRef(&res); + + ROW(a, 0, a); + ROW(a, 1, empty); + + ROW(ab, 0, ab); + ROW(ab, 1, a); + ROW(ab, 2, empty); + + ROW(abc, 0, abc); + ROW(abc, 1, ab); + ROW(abc, 2, a); + ROW(abc, 3, empty); +#undef ROW +} + +template <typename String> +void tst_QStringApiSymmetry::chop_impl() +{ + QFETCH(const QStringRef, unicode); + QFETCH(const QLatin1String, latin1); + QFETCH(const int, n); + QFETCH(const QStringRef, result); + + const auto utf8 = unicode.toUtf8(); + + const auto s = make<String>(unicode, latin1, utf8); + + { + const auto chopped = s.chopped(n); + + QCOMPARE(chopped, result); + QCOMPARE(chopped.isNull(), result.isNull()); + QCOMPARE(chopped.isEmpty(), result.isEmpty()); + } + { + const auto chopped = detached(s).chopped(n); + + QCOMPARE(chopped, result); + QCOMPARE(chopped.isNull(), result.isNull()); + QCOMPARE(chopped.isEmpty(), result.isEmpty()); + } + { + auto chopped = s; + chopped.chop(n); + + QCOMPARE(chopped, result); + QCOMPARE(chopped.isNull(), result.isNull()); + QCOMPARE(chopped.isEmpty(), result.isEmpty()); + } +} + +void tst_QStringApiSymmetry::trimmed_data() +{ + QTest::addColumn<QString>("unicode"); + QTest::addColumn<QStringRef>("result"); + + const auto latin1Whitespace = QLatin1String(" \r\n\t\f\v"); + + QTest::addRow("null") << QString() << QStringRef(); + + auto add = [latin1Whitespace](const QString &str) { + // run through all substrings of latin1Whitespace + for (int len = 0; len < latin1Whitespace.size(); ++len) { + for (int pos = 0; pos < latin1Whitespace.size() - len; ++pos) { + const QString unicode = latin1Whitespace.mid(pos, len) + str + latin1Whitespace.mid(pos, len); + const QScopedPointer<const char> escaped(QTest::toString(unicode)); + QTest::addRow("%s", escaped.data()) << unicode << QStringRef(&str); + } + } + }; + + add(empty); + add(a); + add(ab); +} + +template <typename String> +void tst_QStringApiSymmetry::trimmed_impl() +{ + QFETCH(const QString, unicode); + QFETCH(const QStringRef, result); + + const auto utf8 = unicode.toUtf8(); + const auto l1s = unicode.toLatin1(); + const auto l1 = l1s.isNull() ? QLatin1String() : QLatin1String(l1s); + + const auto ref = unicode.isNull() ? QStringRef() : QStringRef(&unicode); + const auto s = make<String>(ref, l1, utf8); + + QCOMPARE(s.isNull(), unicode.isNull()); + + { + const auto trimmed = s.trimmed(); + + QCOMPARE(trimmed, result); + QCOMPARE(trimmed.isNull(), result.isNull()); + QCOMPARE(trimmed.isEmpty(), result.isEmpty()); + } + { + const auto trimmed = detached(s).trimmed(); + + QCOMPARE(trimmed, result); + QCOMPARE(trimmed.isNull(), result.isNull()); + QCOMPARE(trimmed.isEmpty(), result.isEmpty()); + } +} + // // // UTF-16-only checks: @@ -309,6 +1057,7 @@ void tst_QStringApiSymmetry::compare_impl() const template <class Str> Str make(const QString &s); template <> QStringRef make(const QString &s) { return QStringRef(&s); } template <> QString make(const QString &s) { return s; } +template <> QStringView make(const QString &s) { return s; } #define REPEAT_16X(X) X X X X X X X X X X X X X X X X #define LONG_STRING_256 REPEAT_16X("0123456789abcdef") diff --git a/tests/auto/corelib/tools/qstringiterator/tst_qstringiterator.cpp b/tests/auto/corelib/tools/qstringiterator/tst_qstringiterator.cpp index 319f772516..7d5504c22c 100644 --- a/tests/auto/corelib/tools/qstringiterator/tst_qstringiterator.cpp +++ b/tests/auto/corelib/tools/qstringiterator/tst_qstringiterator.cpp @@ -326,22 +326,22 @@ void tst_QStringIterator::position() QLatin1Char('p') // codeunit count: 35 }; + static const int stringDataSize = sizeof(stringData) / sizeof(stringData[0]); - const QString string(stringData, sizeof(stringData) / sizeof(stringData[0])); - QStringIterator i(string); + QStringIterator i(QStringView(stringData, stringDataSize)); - QCOMPARE(i.position(), string.constBegin()); + QCOMPARE(i.position(), stringData); QVERIFY(i.hasNext()); QVERIFY(!i.hasPrevious()); - i.setPosition(string.constEnd()); - QCOMPARE(i.position(), string.constEnd()); + i.setPosition(stringData + stringDataSize); + QCOMPARE(i.position(), stringData + stringDataSize); QVERIFY(!i.hasNext()); QVERIFY(i.hasPrevious()); #define QCHAR_UNICODE_VALUE(x) ((uint)(QChar(x).unicode())) - const QString::const_iterator begin = string.constBegin(); + const QChar *begin = stringData; i.setPosition(begin); QCOMPARE(i.position(), begin); QCOMPARE(i.peekNext(), QCHAR_UNICODE_VALUE(QLatin1Char('a'))); diff --git a/tests/auto/corelib/tools/qstringlist/tst_qstringlist.cpp b/tests/auto/corelib/tools/qstringlist/tst_qstringlist.cpp index 2385aa992c..9f054190e5 100644 --- a/tests/auto/corelib/tools/qstringlist/tst_qstringlist.cpp +++ b/tests/auto/corelib/tools/qstringlist/tst_qstringlist.cpp @@ -264,6 +264,15 @@ void tst_QStringList::contains() QVERIFY(list.contains("ARTHUR", Qt::CaseInsensitive)); QVERIFY(list.contains("dent", Qt::CaseInsensitive)); QVERIFY(!list.contains("hans", Qt::CaseInsensitive)); + + QVERIFY(list.contains(QLatin1String("arthur"))); + QVERIFY(!list.contains(QLatin1String("ArthuR"))); + QVERIFY(!list.contains(QLatin1String("Hans"))); + QVERIFY(list.contains(QLatin1String("arthur"), Qt::CaseInsensitive)); + QVERIFY(list.contains(QLatin1String("ArthuR"), Qt::CaseInsensitive)); + QVERIFY(list.contains(QLatin1String("ARTHUR"), Qt::CaseInsensitive)); + QVERIFY(list.contains(QLatin1String("dent"), Qt::CaseInsensitive)); + QVERIFY(!list.contains(QLatin1String("hans"), Qt::CaseInsensitive)); } void tst_QStringList::removeDuplicates_data() diff --git a/tests/auto/corelib/tools/qstringref/tst_qstringref.cpp b/tests/auto/corelib/tools/qstringref/tst_qstringref.cpp index d2374fe0ae..473f563f9b 100644 --- a/tests/auto/corelib/tools/qstringref/tst_qstringref.cpp +++ b/tests/auto/corelib/tools/qstringref/tst_qstringref.cpp @@ -1862,7 +1862,9 @@ void tst_QStringRef::double_conversion() void tst_QStringRef::trimmed() { - QString a; + QVERIFY(QStringRef().trimmed().isNull()); + QString a = ""; + QVERIFY(!QStringRef(&a).trimmed().isNull()); QStringRef b; a = "Text"; b = a.leftRef(-1); diff --git a/tests/auto/corelib/tools/qstringview/.gitignore b/tests/auto/corelib/tools/qstringview/.gitignore new file mode 100644 index 0000000000..5f757d448a --- /dev/null +++ b/tests/auto/corelib/tools/qstringview/.gitignore @@ -0,0 +1 @@ +tst_qstringview diff --git a/tests/auto/corelib/tools/qstringview/qstringview.pro b/tests/auto/corelib/tools/qstringview/qstringview.pro new file mode 100644 index 0000000000..e0e9973c91 --- /dev/null +++ b/tests/auto/corelib/tools/qstringview/qstringview.pro @@ -0,0 +1,6 @@ +CONFIG += testcase +TARGET = tst_qstringview +QT = core testlib +contains(QT_CONFIG, c++14):CONFIG *= c++14 +contains(QT_CONFIG, c++1z):CONFIG *= c++1z +SOURCES += tst_qstringview.cpp diff --git a/tests/auto/corelib/tools/qstringview/tst_qstringview.cpp b/tests/auto/corelib/tools/qstringview/tst_qstringview.cpp new file mode 100644 index 0000000000..4ae96005d0 --- /dev/null +++ b/tests/auto/corelib/tools/qstringview/tst_qstringview.cpp @@ -0,0 +1,619 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Marc Mutz <marc.mutz@kdab.com> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QStringView> +#include <QString> +#include <QChar> +#include <QStringRef> + +#include <QTest> + +#include <string> + +template <typename T> +using CanConvert = std::is_convertible<T, QStringView>; + +Q_STATIC_ASSERT(!CanConvert<QLatin1String>::value); +Q_STATIC_ASSERT(!CanConvert<const char*>::value); +Q_STATIC_ASSERT(!CanConvert<QByteArray>::value); + +// QStringView qchar_does_not_compile() { return QStringView(QChar('a')); } +// QStringView qlatin1string_does_not_compile() { return QStringView(QLatin1String("a")); } +// QStringView const_char_star_does_not_compile() { return QStringView("a"); } +// QStringView qbytearray_does_not_compile() { return QStringView(QByteArray("a")); } + +// +// QChar +// + +Q_STATIC_ASSERT(!CanConvert<QChar>::value); + +Q_STATIC_ASSERT(CanConvert<QChar[123]>::value); + +Q_STATIC_ASSERT(CanConvert< QString >::value); +Q_STATIC_ASSERT(CanConvert<const QString >::value); +Q_STATIC_ASSERT(CanConvert< QString&>::value); +Q_STATIC_ASSERT(CanConvert<const QString&>::value); + +Q_STATIC_ASSERT(CanConvert< QStringRef >::value); +Q_STATIC_ASSERT(CanConvert<const QStringRef >::value); +Q_STATIC_ASSERT(CanConvert< QStringRef&>::value); +Q_STATIC_ASSERT(CanConvert<const QStringRef&>::value); + + +// +// ushort +// + +Q_STATIC_ASSERT(!CanConvert<ushort>::value); + +Q_STATIC_ASSERT(CanConvert<ushort[123]>::value); + +Q_STATIC_ASSERT(CanConvert< ushort*>::value); +Q_STATIC_ASSERT(CanConvert<const ushort*>::value); + + +// +// char16_t +// + +#if defined(Q_COMPILER_UNICODE_STRINGS) + +Q_STATIC_ASSERT(!CanConvert<char16_t>::value); + +Q_STATIC_ASSERT(CanConvert< char16_t*>::value); +Q_STATIC_ASSERT(CanConvert<const char16_t*>::value); + +#endif + +#if defined(Q_STDLIB_UNICODE_STRINGS) + +Q_STATIC_ASSERT(CanConvert< std::u16string >::value); +Q_STATIC_ASSERT(CanConvert<const std::u16string >::value); +Q_STATIC_ASSERT(CanConvert< std::u16string&>::value); +Q_STATIC_ASSERT(CanConvert<const std::u16string&>::value); + +#endif + + +// +// wchar_t +// + +Q_CONSTEXPR bool CanConvertFromWCharT = +#ifdef Q_OS_WIN + true +#else + false +#endif + ; + +Q_STATIC_ASSERT(!CanConvert<wchar_t>::value); + +Q_STATIC_ASSERT(CanConvert< wchar_t*>::value == CanConvertFromWCharT); +Q_STATIC_ASSERT(CanConvert<const wchar_t*>::value == CanConvertFromWCharT); + +Q_STATIC_ASSERT(CanConvert< std::wstring >::value == CanConvertFromWCharT); +Q_STATIC_ASSERT(CanConvert<const std::wstring >::value == CanConvertFromWCharT); +Q_STATIC_ASSERT(CanConvert< std::wstring&>::value == CanConvertFromWCharT); +Q_STATIC_ASSERT(CanConvert<const std::wstring&>::value == CanConvertFromWCharT); + + +class tst_QStringView : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void constExpr() const; + void basics() const; + void literals() const; + void at() const; + + void fromQString() const; + void fromQStringRef() const; + + void fromQCharStar() const + { + const QChar str[] = { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '!', 0 }; + fromLiteral(str); + } + + void fromUShortStar() const + { + const ushort str[] = { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '!', 0 }; + fromLiteral(str); + } + + void fromChar16TStar() const + { +#if defined(Q_COMPILER_UNICODE_STRINGS) + fromLiteral(u"Hello, World!"); +#else + QSKIP("This test requires C++11 char16_t support enabled in the compiler"); +#endif + } + + void fromWCharTStar() const + { +#ifdef Q_OS_WIN + fromLiteral(L"Hello, World!"); +#else + QSKIP("This is a Windows-only test"); +#endif + } + + void fromQCharRange() const + { + const QChar str[] = { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '!' }; + fromRange(std::begin(str), std::end(str)); + } + + void fromUShortRange() const + { + const ushort str[] = { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '!' }; + fromRange(std::begin(str), std::end(str)); + } + + void fromChar16TRange() const + { +#if defined(Q_COMPILER_UNICODE_STRINGS) + const char16_t str[] = { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '!' }; + fromRange(std::begin(str), std::end(str)); +#else + QSKIP("This test requires C++11 char16_t support enabled in the compiler"); +#endif + } + + void fromWCharTRange() const + { +#ifdef Q_OS_WIN + const wchar_t str[] = { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '!' }; + fromRange(std::begin(str), std::end(str)); +#else + QSKIP("This is a Windows-only test"); +#endif + } + + // std::basic_string + void fromStdStringWCharT() const + { +#ifdef Q_OS_WIN + fromStdString<wchar_t>(); +#else + QSKIP("This is a Windows-only test"); +#endif + } + void fromStdStringChar16T() const + { +#ifdef Q_STDLIB_UNICODE_STRINGS + fromStdString<char16_t>(); +#else + QSKIP("This test requires C++11 char16_t support enabled in compiler & stdlib"); +#endif + } + +private: + template <typename String> + void conversion_tests(String arg) const; + template <typename Char> + void fromLiteral(const Char *arg) const; + template <typename Char> + void fromRange(const Char *first, const Char *last) const; + template <typename Char, typename Container> + void fromContainer() const; + template <typename Char> + void fromStdString() const { fromContainer<Char, std::basic_string<Char> >(); } +}; + +void tst_QStringView::constExpr() const +{ + // compile-time checks +#ifdef Q_COMPILER_CONSTEXPR + { + constexpr QStringView sv; + Q_STATIC_ASSERT(sv.size() == 0); + Q_STATIC_ASSERT(sv.isNull()); + Q_STATIC_ASSERT(sv.empty()); + Q_STATIC_ASSERT(sv.isEmpty()); + Q_STATIC_ASSERT(sv.utf16() == nullptr); + + constexpr QStringView sv2(sv.utf16(), sv.utf16() + sv.size()); + Q_STATIC_ASSERT(sv2.isNull()); + Q_STATIC_ASSERT(sv2.empty()); + } + { + constexpr QStringView sv = QStringViewLiteral(""); + Q_STATIC_ASSERT(sv.size() == 0); + Q_STATIC_ASSERT(!sv.isNull()); + Q_STATIC_ASSERT(sv.empty()); + Q_STATIC_ASSERT(sv.isEmpty()); + Q_STATIC_ASSERT(sv.utf16() != nullptr); + + constexpr QStringView sv2(sv.utf16(), sv.utf16() + sv.size()); + Q_STATIC_ASSERT(!sv2.isNull()); + Q_STATIC_ASSERT(sv2.empty()); + } + { + constexpr QStringView sv = QStringViewLiteral("Hello"); + Q_STATIC_ASSERT(sv.size() == 5); + Q_STATIC_ASSERT(!sv.empty()); + Q_STATIC_ASSERT(!sv.isEmpty()); + Q_STATIC_ASSERT(!sv.isNull()); + Q_STATIC_ASSERT(*sv.utf16() == 'H'); + Q_STATIC_ASSERT(sv[0] == QLatin1Char('H')); + Q_STATIC_ASSERT(sv.at(0) == QLatin1Char('H')); + Q_STATIC_ASSERT(sv.front() == QLatin1Char('H')); + Q_STATIC_ASSERT(sv.first() == QLatin1Char('H')); + Q_STATIC_ASSERT(sv[4] == QLatin1Char('o')); + Q_STATIC_ASSERT(sv.at(4) == QLatin1Char('o')); + Q_STATIC_ASSERT(sv.back() == QLatin1Char('o')); + Q_STATIC_ASSERT(sv.last() == QLatin1Char('o')); + + constexpr QStringView sv2(sv.utf16(), sv.utf16() + sv.size()); + Q_STATIC_ASSERT(!sv2.isNull()); + Q_STATIC_ASSERT(!sv2.empty()); + Q_STATIC_ASSERT(sv2.size() == 5); + } +#if !defined(Q_OS_WIN) || defined(Q_COMPILER_UNICODE_STRINGS) + { + Q_STATIC_ASSERT(QStringView(u"Hello").size() == 5); + constexpr QStringView sv = u"Hello"; + Q_STATIC_ASSERT(sv.size() == 5); + Q_STATIC_ASSERT(!sv.empty()); + Q_STATIC_ASSERT(!sv.isEmpty()); + Q_STATIC_ASSERT(!sv.isNull()); + Q_STATIC_ASSERT(*sv.utf16() == 'H'); + Q_STATIC_ASSERT(sv[0] == QLatin1Char('H')); + Q_STATIC_ASSERT(sv.at(0) == QLatin1Char('H')); + Q_STATIC_ASSERT(sv.front() == QLatin1Char('H')); + Q_STATIC_ASSERT(sv.first() == QLatin1Char('H')); + Q_STATIC_ASSERT(sv[4] == QLatin1Char('o')); + Q_STATIC_ASSERT(sv.at(4) == QLatin1Char('o')); + Q_STATIC_ASSERT(sv.back() == QLatin1Char('o')); + Q_STATIC_ASSERT(sv.last() == QLatin1Char('o')); + + constexpr QStringView sv2(sv.utf16(), sv.utf16() + sv.size()); + Q_STATIC_ASSERT(!sv2.isNull()); + Q_STATIC_ASSERT(!sv2.empty()); + Q_STATIC_ASSERT(sv2.size() == 5); + + constexpr char16_t *null = nullptr; + constexpr QStringView sv3(null); + Q_STATIC_ASSERT(sv3.isNull()); + Q_STATIC_ASSERT(sv3.isEmpty()); + Q_STATIC_ASSERT(sv3.size() == 0); + } +#else // storage_type is wchar_t + { + Q_STATIC_ASSERT(QStringView(L"Hello").size() == 5); + constexpr QStringView sv = L"Hello"; + Q_STATIC_ASSERT(sv.size() == 5); + Q_STATIC_ASSERT(!sv.empty()); + Q_STATIC_ASSERT(!sv.isEmpty()); + Q_STATIC_ASSERT(!sv.isNull()); + Q_STATIC_ASSERT(*sv.utf16() == 'H'); + Q_STATIC_ASSERT(sv[0] == QLatin1Char('H')); + Q_STATIC_ASSERT(sv.at(0) == QLatin1Char('H')); + Q_STATIC_ASSERT(sv.front() == QLatin1Char('H')); + Q_STATIC_ASSERT(sv.first() == QLatin1Char('H')); + Q_STATIC_ASSERT(sv[4] == QLatin1Char('o')); + Q_STATIC_ASSERT(sv.at(4) == QLatin1Char('o')); + Q_STATIC_ASSERT(sv.back() == QLatin1Char('o')); + Q_STATIC_ASSERT(sv.last() == QLatin1Char('o')); + + constexpr QStringView sv2(sv.utf16(), sv.utf16() + sv.size()); + Q_STATIC_ASSERT(!sv2.isNull()); + Q_STATIC_ASSERT(!sv2.empty()); + Q_STATIC_ASSERT(sv2.size() == 5); + + constexpr wchar_t *null = nullptr; + constexpr QStringView sv3(null); + Q_STATIC_ASSERT(sv3.isNull()); + Q_STATIC_ASSERT(sv3.isEmpty()); + Q_STATIC_ASSERT(sv3.size() == 0); + } +#endif +#endif +} + +void tst_QStringView::basics() const +{ + QStringView sv1; + + // a default-constructed QStringView is null: + QVERIFY(sv1.isNull()); + // which implies it's empty(); + QVERIFY(sv1.isEmpty()); + + QStringView sv2; + + QVERIFY(sv2 == sv1); + QVERIFY(!(sv2 != sv1)); +} + +void tst_QStringView::literals() const +{ +#if !defined(Q_OS_WIN) || defined(Q_COMPILER_UNICODE_STRINGS) + const char16_t hello[] = u"Hello"; + const char16_t longhello[] = + u"Hello World. This is a much longer message, to exercise qustrlen."; + const char16_t withnull[] = u"a\0zzz"; +#else // storage_type is wchar_t + const wchar_t hello[] = L"Hello"; + const wchar_t longhello[] = + L"Hello World. This is a much longer message, to exercise qustrlen."; + const wchar_t withnull[] = L"a\0zzz"; +#endif + Q_STATIC_ASSERT(sizeof(longhello) >= 16); + + QCOMPARE(QStringView(hello).size(), 5); + QCOMPARE(QStringView(hello + 0).size(), 5); // forces decay to pointer + QStringView sv = hello; + QCOMPARE(sv.size(), 5); + QVERIFY(!sv.empty()); + QVERIFY(!sv.isEmpty()); + QVERIFY(!sv.isNull()); + QCOMPARE(*sv.utf16(), 'H'); + QCOMPARE(sv[0], QLatin1Char('H')); + QCOMPARE(sv.at(0), QLatin1Char('H')); + QCOMPARE(sv.front(), QLatin1Char('H')); + QCOMPARE(sv.first(), QLatin1Char('H')); + QCOMPARE(sv[4], QLatin1Char('o')); + QCOMPARE(sv.at(4), QLatin1Char('o')); + QCOMPARE(sv.back(), QLatin1Char('o')); + QCOMPARE(sv.last(), QLatin1Char('o')); + + QStringView sv2(sv.utf16(), sv.utf16() + sv.size()); + QVERIFY(!sv2.isNull()); + QVERIFY(!sv2.empty()); + QCOMPARE(sv2.size(), 5); + + QStringView sv3(longhello); + QCOMPARE(size_t(sv3.size()), sizeof(longhello)/sizeof(longhello[0]) - 1); + QCOMPARE(sv3.last(), QLatin1Char('.')); + sv3 = longhello; + QCOMPARE(size_t(sv3.size()), sizeof(longhello)/sizeof(longhello[0]) - 1); + + for (int i = 0; i < sv3.size(); ++i) { + QStringView sv4(longhello + i); + QCOMPARE(size_t(sv4.size()), sizeof(longhello)/sizeof(longhello[0]) - 1 - i); + QCOMPARE(sv4.last(), QLatin1Char('.')); + sv4 = longhello + i; + QCOMPARE(size_t(sv4.size()), sizeof(longhello)/sizeof(longhello[0]) - 1 - i); + } + + // these are different results + QCOMPARE(size_t(QStringView(withnull).size()), sizeof(withnull)/sizeof(withnull[0]) - 1); + QCOMPARE(QStringView(withnull + 0).size(), 1); +} + +void tst_QStringView::at() const +{ + QString hello("Hello"); + QStringView sv(hello); + QCOMPARE(sv.at(0), QChar('H')); QCOMPARE(sv[0], QChar('H')); + QCOMPARE(sv.at(1), QChar('e')); QCOMPARE(sv[1], QChar('e')); + QCOMPARE(sv.at(2), QChar('l')); QCOMPARE(sv[2], QChar('l')); + QCOMPARE(sv.at(3), QChar('l')); QCOMPARE(sv[3], QChar('l')); + QCOMPARE(sv.at(4), QChar('o')); QCOMPARE(sv[4], QChar('o')); +} + +void tst_QStringView::fromQString() const +{ + QString null; + QString empty = ""; + + QVERIFY( QStringView(null).isNull()); + QVERIFY( QStringView(null).isEmpty()); + QVERIFY( QStringView(empty).isEmpty()); + QVERIFY(!QStringView(empty).isNull()); + + conversion_tests(QString("Hello World!")); +} + +void tst_QStringView::fromQStringRef() const +{ + QStringRef null; + QString emptyS = ""; + QStringRef empty(&emptyS); + + QVERIFY( QStringView(null).isNull()); + QVERIFY( QStringView(null).isEmpty()); + QVERIFY( QStringView(empty).isEmpty()); + QVERIFY(!QStringView(empty).isNull()); + + conversion_tests(QString("Hello World!").midRef(6)); +} + +template <typename Char> +void tst_QStringView::fromLiteral(const Char *arg) const +{ + const Char *null = nullptr; + const Char empty[] = { 0 }; + + QCOMPARE(QStringView(null).size(), qsizetype(0)); + QCOMPARE(QStringView(null).data(), nullptr); + QCOMPARE(QStringView(empty).size(), qsizetype(0)); + QCOMPARE(static_cast<const void*>(QStringView(empty).data()), + static_cast<const void*>(empty)); + + QVERIFY( QStringView(null).isNull()); + QVERIFY( QStringView(null).isEmpty()); + QVERIFY( QStringView(empty).isEmpty()); + QVERIFY(!QStringView(empty).isNull()); + + conversion_tests(arg); +} + +template <typename Char> +void tst_QStringView::fromRange(const Char *first, const Char *last) const +{ + const Char *null = nullptr; + QCOMPARE(QStringView(null, null).size(), 0); + QCOMPARE(QStringView(null, null).data(), nullptr); + QCOMPARE(QStringView(first, first).size(), 0); + QCOMPARE(static_cast<const void*>(QStringView(first, first).data()), + static_cast<const void*>(first)); + + const auto sv = QStringView(first, last); + QCOMPARE(sv.size(), last - first); + QCOMPARE(static_cast<const void*>(sv.data()), + static_cast<const void*>(first)); + + // can't call conversion_tests() here, as it requires a single object +} + +template <typename Char, typename Container> +void tst_QStringView::fromContainer() const +{ + const QString s = "Hello World!"; + + Container c; + // unspecified whether empty containers make null QStringViews + QVERIFY(QStringView(c).isEmpty()); + + QCOMPARE(sizeof(Char), sizeof(QChar)); + + const auto *data = reinterpret_cast<const Char *>(s.utf16()); + std::copy(data, data + s.size(), std::back_inserter(c)); + conversion_tests(std::move(c)); +} + +namespace help { +template <typename T> +size_t size(const T &t) { return size_t(t.size()); } +template <typename T> +size_t size(const T *t) +{ + size_t result = 0; + if (t) { + while (*t++) + ++result; + } + return result; +} +size_t size(const QChar *t) +{ + size_t result = 0; + if (t) { + while (!t++->isNull()) + ++result; + } + return result; +} + +template <typename T> +typename T::const_iterator cbegin(const T &t) { return t.cbegin(); } +template <typename T> +const T * cbegin(const T *t) { return t; } + +template <typename T> +typename T::const_iterator cend(const T &t) { return t.cend(); } +template <typename T> +const T * cend(const T *t) { return t + size(t); } + +template <typename T> +typename T::const_reverse_iterator crbegin(const T &t) { return t.crbegin(); } +template <typename T> +std::reverse_iterator<const T*> crbegin(const T *t) { return std::reverse_iterator<const T*>(cend(t)); } + +template <typename T> +typename T::const_reverse_iterator crend(const T &t) { return t.crend(); } +template <typename T> +std::reverse_iterator<const T*> crend(const T *t) { return std::reverse_iterator<const T*>(cbegin(t)); } + +} // namespace help + +template <typename String> +void tst_QStringView::conversion_tests(String string) const +{ + // copy-construct: + { + QStringView sv = string; + + QCOMPARE(help::size(sv), help::size(string)); + + // check iterators: + + QVERIFY(std::equal(help::cbegin(string), help::cend(string), + QT_MAKE_CHECKED_ARRAY_ITERATOR(sv.cbegin(), sv.size()))); + QVERIFY(std::equal(help::cbegin(string), help::cend(string), + QT_MAKE_CHECKED_ARRAY_ITERATOR(sv.begin(), sv.size()))); + QVERIFY(std::equal(help::crbegin(string), help::crend(string), + sv.crbegin())); + QVERIFY(std::equal(help::crbegin(string), help::crend(string), + sv.rbegin())); + + QCOMPARE(sv, string); + } + + QStringView sv; + + // copy-assign: + { + sv = string; + + QCOMPARE(help::size(sv), help::size(string)); + + // check relational operators: + + QCOMPARE(sv, string); + QCOMPARE(string, sv); + + QVERIFY(!(sv != string)); + QVERIFY(!(string != sv)); + + QVERIFY(!(sv < string)); + QVERIFY(sv <= string); + QVERIFY(!(sv > string)); + QVERIFY(sv >= string); + + QVERIFY(!(string < sv)); + QVERIFY(string <= sv); + QVERIFY(!(string > sv)); + QVERIFY(string >= sv); + } + + // copy-construct from rvalue (QStringView never assumes ownership): + { + QStringView sv2 = std::move(string); + QCOMPARE(sv2, sv); + QCOMPARE(sv2, string); + } + + // copy-assign from rvalue (QStringView never assumes ownership): + { + QStringView sv2; + sv2 = std::move(string); + QCOMPARE(sv2, sv); + QCOMPARE(sv2, string); + } +} + +QTEST_APPLESS_MAIN(tst_QStringView) +#include "tst_qstringview.moc" diff --git a/tests/auto/corelib/tools/qtimezone/tst_qtimezone.cpp b/tests/auto/corelib/tools/qtimezone/tst_qtimezone.cpp index e75ed5cc67..9f22c3d51d 100644 --- a/tests/auto/corelib/tools/qtimezone/tst_qtimezone.cpp +++ b/tests/auto/corelib/tools/qtimezone/tst_qtimezone.cpp @@ -62,7 +62,9 @@ private slots: private: void printTimeZone(const QTimeZone &tz); #ifdef QT_BUILD_INTERNAL + // Generic tests of privates, called by implementation-specific private tests: void testCetPrivate(const QTimeZonePrivate &tzp); + void testEpochTranPrivate(const QTimeZonePrivate &tzp); #endif // QT_BUILD_INTERNAL const bool debug; }; @@ -167,11 +169,11 @@ void tst_QTimeZone::createTest() QDateTime jun = QDateTime(QDate(2012, 6, 1), QTime(0, 0, 0), Qt::UTC); QDateTime janPrev = QDateTime(QDate(2011, 1, 1), QTime(0, 0, 0), Qt::UTC); - QCOMPARE(tz.offsetFromUtc(jan), 46800); - QCOMPARE(tz.offsetFromUtc(jun), 43200); + QCOMPARE(tz.offsetFromUtc(jan), 13 * 3600); + QCOMPARE(tz.offsetFromUtc(jun), 12 * 3600); - QCOMPARE(tz.standardTimeOffset(jan), 43200); - QCOMPARE(tz.standardTimeOffset(jun), 43200); + QCOMPARE(tz.standardTimeOffset(jan), 12 * 3600); + QCOMPARE(tz.standardTimeOffset(jun), 12 * 3600); QCOMPARE(tz.daylightTimeOffset(jan), 3600); QCOMPARE(tz.daylightTimeOffset(jun), 0); @@ -183,38 +185,46 @@ void tst_QTimeZone::createTest() // Only test transitions if host system supports them if (tz.hasTransitions()) { QTimeZone::OffsetData tran = tz.nextTransition(jan); - QCOMPARE(tran.atUtc.toMSecsSinceEpoch(), (qint64)1333202400000); - QCOMPARE(tran.offsetFromUtc, 43200); - QCOMPARE(tran.standardTimeOffset, 43200); + // 2012-04-01 03:00 NZDT, +13 -> +12 + QCOMPARE(tran.atUtc, + QDateTime(QDate(2012, 4, 1), QTime(3, 0), Qt::OffsetFromUTC, 13 * 3600)); + QCOMPARE(tran.offsetFromUtc, 12 * 3600); + QCOMPARE(tran.standardTimeOffset, 12 * 3600); QCOMPARE(tran.daylightTimeOffset, 0); tran = tz.nextTransition(jun); - QCOMPARE(tran.atUtc.toMSecsSinceEpoch(), (qint64)1348927200000); - QCOMPARE(tran.offsetFromUtc, 46800); - QCOMPARE(tran.standardTimeOffset, 43200); + // 2012-09-30 02:00 NZST, +12 -> +13 + QCOMPARE(tran.atUtc, + QDateTime(QDate(2012, 9, 30), QTime(2, 0), Qt::OffsetFromUTC, 12 * 3600)); + QCOMPARE(tran.offsetFromUtc, 13 * 3600); + QCOMPARE(tran.standardTimeOffset, 12 * 3600); QCOMPARE(tran.daylightTimeOffset, 3600); tran = tz.previousTransition(jan); - QCOMPARE(tran.atUtc.toMSecsSinceEpoch(), (qint64)1316872800000); - QCOMPARE(tran.offsetFromUtc, 46800); - QCOMPARE(tran.standardTimeOffset, 43200); + // 2011-09-25 02:00 NZST, +12 -> +13 + QCOMPARE(tran.atUtc, + QDateTime(QDate(2011, 9, 25), QTime(2, 0), Qt::OffsetFromUTC, 12 * 3600)); + QCOMPARE(tran.offsetFromUtc, 13 * 3600); + QCOMPARE(tran.standardTimeOffset, 12 * 3600); QCOMPARE(tran.daylightTimeOffset, 3600); tran = tz.previousTransition(jun); - QCOMPARE(tran.atUtc.toMSecsSinceEpoch(), (qint64)1333202400000); - QCOMPARE(tran.offsetFromUtc, 43200); - QCOMPARE(tran.standardTimeOffset, 43200); + // 2012-04-01 03:00 NZDT, +13 -> +12 (again) + QCOMPARE(tran.atUtc, + QDateTime(QDate(2012, 4, 1), QTime(3, 0), Qt::OffsetFromUTC, 13 * 3600)); + QCOMPARE(tran.offsetFromUtc, 12 * 3600); + QCOMPARE(tran.standardTimeOffset, 12 * 3600); QCOMPARE(tran.daylightTimeOffset, 0); QTimeZone::OffsetDataList expected; - tran.atUtc = QDateTime::fromMSecsSinceEpoch(1301752800000, Qt::UTC); - tran.offsetFromUtc = 46800; - tran.standardTimeOffset = 43200; + tran.atUtc = QDateTime(QDate(2011, 4, 3), QTime(2, 0), Qt::OffsetFromUTC, 13 * 3600); + tran.offsetFromUtc = 13 * 3600; + tran.standardTimeOffset = 12 * 3600; tran.daylightTimeOffset = 3600; expected << tran; - tran.atUtc = QDateTime::fromMSecsSinceEpoch(1316872800000, Qt::UTC); - tran.offsetFromUtc = 43200; - tran.standardTimeOffset = 43200; + tran.atUtc = QDateTime(QDate(2011, 9, 25), QTime(2, 0), Qt::OffsetFromUTC, 12 * 3600); + tran.offsetFromUtc = 12 * 3600; + tran.standardTimeOffset = 12 * 3600; tran.daylightTimeOffset = 0; expected << tran; QTimeZone::OffsetDataList result = tz.transitions(janPrev, jan); @@ -749,12 +759,13 @@ void tst_QTimeZone::icuTest() } testCetPrivate(tzp); + testEpochTranPrivate(QIcuTimeZonePrivate("America/Toronto")); #endif // QT_USE_ICU } void tst_QTimeZone::tzTest() { -#if defined QT_BUILD_INTERNAL && defined Q_OS_UNIX && !defined Q_OS_MAC +#if defined QT_BUILD_INTERNAL && defined Q_OS_UNIX && !defined Q_OS_DARWIN // Known datetimes qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0, 0), Qt::UTC).toMSecsSinceEpoch(); qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0, 0), Qt::UTC).toMSecsSinceEpoch(); @@ -822,6 +833,7 @@ void tst_QTimeZone::tzTest() } testCetPrivate(tzp); + testEpochTranPrivate(QTzTimeZonePrivate("America/Toronto")); // Test first and last transition rule // Warning: This could vary depending on age of TZ file! @@ -845,7 +857,8 @@ void tst_QTimeZone::tzTest() } dat = tzp.nextTransition(-9999999999999); - QCOMPARE(dat.atMSecsSinceEpoch, (qint64)-2422054408000); + QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, Qt::OffsetFromUTC, 3600), + QDateTime(QDate(1893, 4, 1), QTime(0, 6, 32), Qt::OffsetFromUTC, 3600)); QCOMPARE(dat.standardTimeOffset, 3600); QCOMPARE(dat.daylightTimeOffset, 0); @@ -855,37 +868,41 @@ void tst_QTimeZone::tzTest() // Tets high dates use the POSIX rule dat = tzp.data(stdHi); - QCOMPARE(dat.atMSecsSinceEpoch, (qint64)stdHi); + QCOMPARE(dat.atMSecsSinceEpoch - stdHi, (qint64)0); QCOMPARE(dat.offsetFromUtc, 3600); QCOMPARE(dat.standardTimeOffset, 3600); QCOMPARE(dat.daylightTimeOffset, 0); dat = tzp.data(dstHi); - QCOMPARE(dat.atMSecsSinceEpoch, (qint64)dstHi); + QCOMPARE(dat.atMSecsSinceEpoch - dstHi, (qint64)0); QCOMPARE(dat.offsetFromUtc, 7200); QCOMPARE(dat.standardTimeOffset, 3600); QCOMPARE(dat.daylightTimeOffset, 3600); dat = tzp.previousTransition(stdHi); - QCOMPARE(dat.atMSecsSinceEpoch, (qint64)4096659600000); + QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, Qt::OffsetFromUTC, 3600), + QDateTime(QDate(2099, 10, 26), QTime(2, 0), Qt::OffsetFromUTC, 3600)); QCOMPARE(dat.offsetFromUtc, 3600); QCOMPARE(dat.standardTimeOffset, 3600); QCOMPARE(dat.daylightTimeOffset, 0); dat = tzp.previousTransition(dstHi); - QCOMPARE(dat.atMSecsSinceEpoch, (qint64)4109965200000); + QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, Qt::OffsetFromUTC, 3600), + QDateTime(QDate(2100, 3, 29), QTime(2, 0), Qt::OffsetFromUTC, 3600)); QCOMPARE(dat.offsetFromUtc, 7200); QCOMPARE(dat.standardTimeOffset, 3600); QCOMPARE(dat.daylightTimeOffset, 3600); dat = tzp.nextTransition(stdHi); - QCOMPARE(dat.atMSecsSinceEpoch, (qint64)4109965200000); + QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, Qt::OffsetFromUTC, 3600), + QDateTime(QDate(2100, 3, 29), QTime(2, 0), Qt::OffsetFromUTC, 3600)); QCOMPARE(dat.offsetFromUtc, 7200); QCOMPARE(dat.standardTimeOffset, 3600); QCOMPARE(dat.daylightTimeOffset, 3600); dat = tzp.nextTransition(dstHi); - QCOMPARE(dat.atMSecsSinceEpoch, (qint64)4128109200000); + QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, Qt::OffsetFromUTC, 3600), + QDateTime(QDate(2100, 10, 25), QTime(2, 0), Qt::OffsetFromUTC, 3600)); QCOMPARE(dat.offsetFromUtc, 3600); QCOMPARE(dat.standardTimeOffset, 3600); QCOMPARE(dat.daylightTimeOffset, 0); @@ -917,12 +934,12 @@ void tst_QTimeZone::tzTest() QDateTime dt(QDate(2016, 3, 28), QTime(0, 0, 0), Qt::UTC); QCOMPARE(tzBarnaul.data(dt.toMSecsSinceEpoch()).abbreviation, QString("+07")); } -#endif // Q_OS_UNIX +#endif // QT_BUILD_INTERNAL && Q_OS_UNIX && !Q_OS_DARWIN } void tst_QTimeZone::macTest() { -#if defined(QT_BUILD_INTERNAL) && defined (Q_OS_MAC) +#if defined(QT_BUILD_INTERNAL) && defined(Q_OS_DARWIN) // Known datetimes qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0, 0), Qt::UTC).toMSecsSinceEpoch(); qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0, 0), Qt::UTC).toMSecsSinceEpoch(); @@ -969,7 +986,8 @@ void tst_QTimeZone::macTest() } testCetPrivate(tzp); -#endif // Q_OS_MAC + testEpochTranPrivate(QMacTimeZonePrivate("America/Toronto")); +#endif // QT_BUILD_INTERNAL && Q_OS_DARWIN } void tst_QTimeZone::darwinTypes() @@ -1034,6 +1052,7 @@ void tst_QTimeZone::winTest() } testCetPrivate(tzp); + testEpochTranPrivate(QWinTimeZonePrivate("America/Toronto")); #endif // Q_OS_WIN } @@ -1076,50 +1095,103 @@ void tst_QTimeZone::testCetPrivate(const QTimeZonePrivate &tzp) // Only test transitions if host system supports them if (tzp.hasTransitions()) { QTimeZonePrivate::Data tran = tzp.nextTransition(std); - QCOMPARE(tran.atMSecsSinceEpoch, (qint64)1332637200000); + // 2012-03-25 02:00 CET, +1 -> +2 + QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, Qt::UTC), + QDateTime(QDate(2012, 3, 25), QTime(2, 0), Qt::OffsetFromUTC, 3600)); QCOMPARE(tran.offsetFromUtc, 7200); QCOMPARE(tran.standardTimeOffset, 3600); QCOMPARE(tran.daylightTimeOffset, 3600); tran = tzp.nextTransition(dst); - QCOMPARE(tran.atMSecsSinceEpoch, (qint64)1351386000000); + // 2012-10-28 03:00 CEST, +2 -> +1 + QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, Qt::UTC), + QDateTime(QDate(2012, 10, 28), QTime(3, 0), Qt::OffsetFromUTC, 2 * 3600)); QCOMPARE(tran.offsetFromUtc, 3600); QCOMPARE(tran.standardTimeOffset, 3600); QCOMPARE(tran.daylightTimeOffset, 0); tran = tzp.previousTransition(std); - QCOMPARE(tran.atMSecsSinceEpoch, (qint64)1319936400000); + // 2011-10-30 03:00 CEST, +2 -> +1 + QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, Qt::UTC), + QDateTime(QDate(2011, 10, 30), QTime(3, 0), Qt::OffsetFromUTC, 2 * 3600)); QCOMPARE(tran.offsetFromUtc, 3600); QCOMPARE(tran.standardTimeOffset, 3600); QCOMPARE(tran.daylightTimeOffset, 0); tran = tzp.previousTransition(dst); - QCOMPARE(tran.atMSecsSinceEpoch, (qint64)1332637200000); + // 2012-03-25 02:00 CET, +1 -> +2 (again) + QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, Qt::UTC), + QDateTime(QDate(2012, 3, 25), QTime(2, 0), Qt::OffsetFromUTC, 3600)); QCOMPARE(tran.offsetFromUtc, 7200); QCOMPARE(tran.standardTimeOffset, 3600); QCOMPARE(tran.daylightTimeOffset, 3600); QTimeZonePrivate::DataList expected; - tran.atMSecsSinceEpoch = (qint64)1301752800000; + // 2011-03-27 02:00 CET, +1 -> +2 + tran.atMSecsSinceEpoch = QDateTime(QDate(2011, 3, 27), QTime(2, 0), + Qt::OffsetFromUTC, 3600).toMSecsSinceEpoch(); tran.offsetFromUtc = 7200; tran.standardTimeOffset = 3600; tran.daylightTimeOffset = 3600; expected << tran; - tran.atMSecsSinceEpoch = (qint64)1316872800000; + // 2011-10-30 03:00 CEST, +2 -> +1 + tran.atMSecsSinceEpoch = QDateTime(QDate(2011, 10, 30), QTime(3, 0), + Qt::OffsetFromUTC, 2 * 3600).toMSecsSinceEpoch(); tran.offsetFromUtc = 3600; tran.standardTimeOffset = 3600; tran.daylightTimeOffset = 0; expected << tran; QTimeZonePrivate::DataList result = tzp.transitions(prev, std); QCOMPARE(result.count(), expected.count()); - for (int i = 0; i > expected.count(); ++i) { - QCOMPARE(result.at(i).atMSecsSinceEpoch, expected.at(i).atMSecsSinceEpoch); + for (int i = 0; i < expected.count(); ++i) { + QCOMPARE(QDateTime::fromMSecsSinceEpoch(result.at(i).atMSecsSinceEpoch, + Qt::OffsetFromUTC, 3600), + QDateTime::fromMSecsSinceEpoch(expected.at(i).atMSecsSinceEpoch, + Qt::OffsetFromUTC, 3600)); QCOMPARE(result.at(i).offsetFromUtc, expected.at(i).offsetFromUtc); QCOMPARE(result.at(i).standardTimeOffset, expected.at(i).standardTimeOffset); QCOMPARE(result.at(i).daylightTimeOffset, expected.at(i).daylightTimeOffset); } } } + +// Needs a zone with DST around the epoch; currently America/Toronto (EST5EDT) +void tst_QTimeZone::testEpochTranPrivate(const QTimeZonePrivate &tzp) +{ + if (!tzp.hasTransitions()) + return; // test only viable for transitions + + QTimeZonePrivate::Data tran = tzp.nextTransition(0); // i.e. first after epoch + // 1970-04-26 02:00 EST, -5 -> -4 + const QDateTime after = QDateTime(QDate(1970, 4, 26), QTime(2, 0), Qt::OffsetFromUTC, -5 * 3600); + const QDateTime found = QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, Qt::UTC); +#ifdef Q_OS_WIN // MS gets the date wrong: 5th April instead of 26th. + QCOMPARE(found.toOffsetFromUtc(-5 * 3600).time(), after.time()); +#else + QCOMPARE(found, after); +#endif + QCOMPARE(tran.offsetFromUtc, -4 * 3600); + QCOMPARE(tran.standardTimeOffset, -5 * 3600); + QCOMPARE(tran.daylightTimeOffset, 3600); + + // Pre-epoch time-zones might not be supported at all: + tran = tzp.nextTransition(QDateTime(QDate(1601, 1, 1), QTime(0, 0), + Qt::UTC).toMSecsSinceEpoch()); + if (tran.atMSecsSinceEpoch != QTimeZonePrivate::invalidSeconds() + && tran.atMSecsSinceEpoch < 0) { + // ... but, if they are, we should be able to search back to them: + tran = tzp.previousTransition(0); // i.e. last before epoch + // 1969-10-26 02:00 EDT, -4 -> -5 + QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, Qt::UTC), + QDateTime(QDate(1969, 10, 26), QTime(2, 0), Qt::OffsetFromUTC, -4 * 3600)); + QCOMPARE(tran.offsetFromUtc, -5 * 3600); + QCOMPARE(tran.standardTimeOffset, -5 * 3600); + QCOMPARE(tran.daylightTimeOffset, 0); + } else { + // Do not use QSKIP(): that would discard the rest of this sub-test's caller. + qDebug() << "No support for pre-epoch time-zone transitions"; + } +} #endif // QT_BUILD_INTERNAL QTEST_APPLESS_MAIN(tst_QTimeZone) diff --git a/tests/auto/corelib/tools/qvarlengtharray/tst_qvarlengtharray.cpp b/tests/auto/corelib/tools/qvarlengtharray/tst_qvarlengtharray.cpp index 0806ad1318..3971353cbb 100644 --- a/tests/auto/corelib/tools/qvarlengtharray/tst_qvarlengtharray.cpp +++ b/tests/auto/corelib/tools/qvarlengtharray/tst_qvarlengtharray.cpp @@ -30,8 +30,6 @@ #include <qvarlengtharray.h> #include <qvariant.h> -const int N = 1; - class tst_QVarLengthArray : public QObject { Q_OBJECT diff --git a/tests/auto/corelib/tools/qversionnumber/tst_qversionnumber.cpp b/tests/auto/corelib/tools/qversionnumber/tst_qversionnumber.cpp index 2e34e82388..9812d93a50 100644 --- a/tests/auto/corelib/tools/qversionnumber/tst_qversionnumber.cpp +++ b/tests/auto/corelib/tools/qversionnumber/tst_qversionnumber.cpp @@ -513,6 +513,14 @@ void tst_QVersionNumber::fromString() QCOMPARE(QVersionNumber::fromString(constructionString), expectedVersion); QCOMPARE(QVersionNumber::fromString(constructionString, &index), expectedVersion); QCOMPARE(index, suffixIndex); + + QCOMPARE(QVersionNumber::fromString(QStringView(constructionString)), expectedVersion); + QCOMPARE(QVersionNumber::fromString(QStringView(constructionString), &index), expectedVersion); + QCOMPARE(index, suffixIndex); + + QCOMPARE(QVersionNumber::fromString(QLatin1String(constructionString.toLatin1())), expectedVersion); + QCOMPARE(QVersionNumber::fromString(QLatin1String(constructionString.toLatin1()), &index), expectedVersion); + QCOMPARE(index, suffixIndex); } void tst_QVersionNumber::toString_data() diff --git a/tests/auto/corelib/tools/tools.pro b/tests/auto/corelib/tools/tools.pro index e45771a704..f35ed026ac 100644 --- a/tests/auto/corelib/tools/tools.pro +++ b/tests/auto/corelib/tools/tools.pro @@ -1,6 +1,7 @@ TEMPLATE=subdirs SUBDIRS=\ collections \ + containerapisymmetry \ qalgorithms \ qarraydata \ qarraydata_strictiterators \ @@ -56,6 +57,7 @@ SUBDIRS=\ qstringlist \ qstringmatcher \ qstringref \ + qstringview \ qtextboundaryfinder \ qtime \ qtimezone \ @@ -65,3 +67,4 @@ SUBDIRS=\ qvector_strictiterators \ qversionnumber +darwin: SUBDIRS += qmacautoreleasepool |