summaryrefslogtreecommitdiffstats
path: root/tests/auto/corelib/tools/qhashseed/tst_qhashseed.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/corelib/tools/qhashseed/tst_qhashseed.cpp')
-rw-r--r--tests/auto/corelib/tools/qhashseed/tst_qhashseed.cpp186
1 files changed, 186 insertions, 0 deletions
diff --git a/tests/auto/corelib/tools/qhashseed/tst_qhashseed.cpp b/tests/auto/corelib/tools/qhashseed/tst_qhashseed.cpp
new file mode 100644
index 0000000000..99fc7c5772
--- /dev/null
+++ b/tests/auto/corelib/tools/qhashseed/tst_qhashseed.cpp
@@ -0,0 +1,186 @@
+// Copyright (C) 2021 Intel Corporation.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QTest>
+
+#include <qhashfunctions.h>
+#if QT_CONFIG(process)
+#include <qprocess.h>
+#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<QByteArray>("envVar");
+ QTest::addColumn<bool>("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<size_t>::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<size_t>::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"