// Copyright (C) 2021 Intel Corporation. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include #include #if QT_CONFIG(process) #include #endif class tst_QHashSeed : public QObject { Q_OBJECT public: static void initMain(); private Q_SLOTS: void initTestCase(); void environmentVariable_data(); void environmentVariable(); void deterministicSeed(); void reseeding(); void quality(); #if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) void compatibilityApi(); void deterministicSeed_compat(); #endif }; void tst_QHashSeed::initMain() { qunsetenv("QT_HASH_SEED"); } void tst_QHashSeed::initTestCase() { // in case the qunsetenv above didn't work if (qEnvironmentVariableIsSet("QT_HASH_SEED")) QSKIP("QT_HASH_SEED environment variable is set, please don't do that"); } void tst_QHashSeed::environmentVariable_data() { #ifdef Q_OS_ANDROID QSKIP("This test needs a helper binary, so is excluded from this platform."); #endif QTest::addColumn("envVar"); QTest::addColumn("isZero"); QTest::newRow("unset-environment") << QByteArray() << false; QTest::newRow("empty-environment") << QByteArray("") << false; QTest::newRow("zero-seed") << QByteArray("0") << true; } void tst_QHashSeed::environmentVariable() { #if QT_CONFIG(process) QFETCH(QByteArray, envVar); QFETCH(bool, isZero); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); if (envVar.isNull()) env.remove("QT_HASH_SEED"); else env.insert("QT_HASH_SEED", envVar); QProcess helper; helper.setProcessEnvironment(env); helper.setProgram("./tst_qhashseed_helper"); helper.start(); QVERIFY2(helper.waitForStarted(5000), qPrintable(helper.errorString())); QVERIFY2(helper.waitForFinished(5000), qPrintable(helper.errorString())); QCOMPARE(helper.exitStatus(), 0); QByteArray line1 = helper.readLine().trimmed(); QByteArray line2 = helper.readLine().trimmed(); QCOMPARE(line2, line1); QCOMPARE(line1 == "0", isZero); #endif } void tst_QHashSeed::deterministicSeed() { QHashSeed::setDeterministicGlobalSeed(); QCOMPARE(size_t(QHashSeed::globalSeed()), size_t(0)); // now reset QHashSeed::resetRandomGlobalSeed(); QVERIFY(QHashSeed::globalSeed() != 0); } void tst_QHashSeed::reseeding() { constexpr int Iterations = 4; size_t seeds[Iterations]; for (int i = 0; i < Iterations; ++i) { seeds[i] = QHashSeed::globalSeed(); QHashSeed::resetRandomGlobalSeed(); } // verify that they are all different QString fmt = QStringLiteral("seeds[%1] = 0x%3, seeds[%2] = 0x%4"); for (int i = 0; i < Iterations; ++i) { for (int j = i + 1; j < Iterations; ++j) { QVERIFY2(seeds[i] != seeds[j], qPrintable(fmt.arg(i).arg(j).arg(seeds[i], 16).arg(seeds[j], 16))); } } } void tst_QHashSeed::quality() { // this "bad seed" is used internally in qhash.cpp and should never leak! constexpr size_t BadSeed = size_t(Q_UINT64_C(0x5555'5555'5555'5555)); constexpr int Iterations = 24; // nicely divisible by 3 int oneThird = 0; int badSeeds = 0; int seedsToMinus1 = 0; size_t ored = 0; for (int i = 0; i < Iterations; ++i) { size_t seed = QHashSeed::globalSeed(); ored |= seed; int bits = qPopulationCount(quintptr(seed)); QVERIFY2(bits > 0, QByteArray::number(bits)); // mandatory if (bits >= std::numeric_limits::digits / 3) ++oneThird; if (seed == BadSeed) ++badSeeds; if (ored != size_t(-1)) ++seedsToMinus1; QHashSeed::resetRandomGlobalSeed(); } // report out qInfo() << "Number of seeds until all bits became set:" << seedsToMinus1 << '/' << Iterations; qInfo() << "Number of seeds with at least one third of the bits set:" << oneThird << '/' << Iterations; // we must have set all bits after all the iterations QCOMPARE(ored, size_t(-1)); // at least one third of the seeds must have one third of all the bits set QVERIFY(oneThird > (Iterations/3)); // at most one seed can be the bad seed, if 32-bit, none on 64-bit if (std::numeric_limits::digits > 32) QCOMPARE(badSeeds, 0); else QVERIFY2(badSeeds <= 1, "badSeeds = " + QByteArray::number(badSeeds)); // we must have taken at most two thirds of the iterations to have set each // bit at least once QVERIFY2(seedsToMinus1 < 2*Iterations/3, "seedsToMinus1 = " + QByteArray::number(seedsToMinus1)); } #if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) QT_WARNING_DISABLE_DEPRECATED void tst_QHashSeed::compatibilityApi() { int oldSeed = qGlobalQHashSeed(); size_t newSeed = QHashSeed::globalSeed(); QCOMPARE(size_t(oldSeed), newSeed & size_t(INT_MAX)); } void tst_QHashSeed::deterministicSeed_compat() { // same as above, but using the compat API qSetGlobalQHashSeed(0); QCOMPARE(size_t(QHashSeed::globalSeed()), size_t(0)); QCOMPARE(qGlobalQHashSeed(), 0); // now reset qSetGlobalQHashSeed(-1); QVERIFY(QHashSeed::globalSeed() != 0); QVERIFY(qGlobalQHashSeed() != 0); QVERIFY(qGlobalQHashSeed() != -1); // possible, but extremely unlikely } #endif // Qt 7 QTEST_MAIN(tst_QHashSeed) #include "tst_qhashseed.moc"