diff options
Diffstat (limited to 'tests/auto/corelib/tools/qhashfunctions')
3 files changed, 391 insertions, 63 deletions
diff --git a/tests/auto/corelib/tools/qhashfunctions/CMakeLists.txt b/tests/auto/corelib/tools/qhashfunctions/CMakeLists.txt index 86d4207d6e..6cbba503dc 100644 --- a/tests/auto/corelib/tools/qhashfunctions/CMakeLists.txt +++ b/tests/auto/corelib/tools/qhashfunctions/CMakeLists.txt @@ -1,9 +1,16 @@ -# Generated from qhashfunctions.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qhashfunctions Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qhashfunctions LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qhashfunctions SOURCES tst_qhashfunctions.cpp diff --git a/tests/auto/corelib/tools/qhashfunctions/qhashfunctions.pro b/tests/auto/corelib/tools/qhashfunctions/qhashfunctions.pro deleted file mode 100644 index 853e9f30e5..0000000000 --- a/tests/auto/corelib/tools/qhashfunctions/qhashfunctions.pro +++ /dev/null @@ -1,4 +0,0 @@ -CONFIG += testcase -TARGET = tst_qhashfunctions -QT = core testlib -SOURCES = $$PWD/tst_qhashfunctions.cpp diff --git a/tests/auto/corelib/tools/qhashfunctions/tst_qhashfunctions.cpp b/tests/auto/corelib/tools/qhashfunctions/tst_qhashfunctions.cpp index f04e1bdb12..00ee5763ed 100644 --- a/tests/auto/corelib/tools/qhashfunctions/tst_qhashfunctions.cpp +++ b/tests/auto/corelib/tools/qhashfunctions/tst_qhashfunctions.cpp @@ -1,34 +1,12 @@ -/**************************************************************************** -** -** 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> +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2024 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QTest> +#include <QVarLengthArray> #include <qhash.h> +#include <qfloat16.h> #include <iterator> #include <sstream> @@ -40,18 +18,28 @@ class tst_QHashFunctions : public QObject { Q_OBJECT public: - enum { - // random value - RandomSeed = 1045982819 - }; - uint seed; + // random values + static constexpr quint64 ZeroSeed = 0; + static constexpr quint64 RandomSeed32 = 1045982819; + static constexpr quint64 RandomSeed64 = QtPrivate::QHashCombine{}(RandomSeed32, RandomSeed32); + size_t seed; + + template <typename T1, typename T2> void stdPair_template(const T1 &t1, const T2 &t2); public slots: void initTestCase(); void init(); private Q_SLOTS: - void consistent(); + void unsignedIntegerConsistency_data(); + void unsignedIntegerConsistency(); + void signedIntegerConsistency_data(); + void signedIntegerConsistency(); + void extendedIntegerConsistency(); + void floatingPointConsistency_data(); + void floatingPointConsistency(); + void stringConsistency_data(); + void stringConsistency(); void qhash(); void qhash_of_empty_and_null_qstring(); void qhash_of_empty_and_null_qbytearray(); @@ -63,29 +51,289 @@ private Q_SLOTS: void stdHash(); + void stdPair_int_int() { stdPair_template(1, 2); } + void stdPair_ulong_llong() { stdPair_template(1UL, -2LL); } + void stdPair_ullong_long() { stdPair_template(1ULL, -2L); } + void stdPair_string_int() { stdPair_template(QString("Hello"), 2); } + void stdPair_int_string() { stdPair_template(1, QString("Hello")); } + void stdPair_bytearray_string() { stdPair_template(QByteArray("Hello"), QString("World")); } + void stdPair_string_bytearray() { stdPair_template(QString("Hello"), QByteArray("World")); } + void stdPair_int_pairIntInt() { stdPair_template(1, std::make_pair(2, 3)); } + void stdPair_2x_pairIntInt() { stdPair_template(std::make_pair(1, 2), std::make_pair(2, 3)); } + void stdPair_string_pairIntInt() { stdPair_template(QString("Hello"), std::make_pair(42, -47)); } // QTBUG-92910 + void stdPair_int_pairIntPairIntInt() { stdPair_template(1, std::make_pair(2, std::make_pair(3, 4))); } + + void enum_int_consistent_hash_qtbug108032(); + +#if QT_DEPRECATED_SINCE(6, 6) void setGlobalQHashSeed(); +#endif }; -void tst_QHashFunctions::consistent() +void tst_QHashFunctions::initTestCase() { - // QString-like - const QString s = QStringLiteral("abcdefghijklmnopqrstuvxyz").repeated(16); - QCOMPARE(qHash(s), qHash(QStringView(s))); + QTest::addColumn<quint64>("seedValue"); + + QTest::newRow("zero-seed") << ZeroSeed; + QTest::newRow("zero-seed-negated") << ~ZeroSeed; + QTest::newRow("non-zero-seed-32bit") << RandomSeed32; + QTest::newRow("non-zero-seed-32bit-negated") + << quint64{~quint32(RandomSeed32)}; // ensure this->seed gets same value on 32/64-bit + if constexpr (sizeof(size_t) == sizeof(quint64)) { + QTest::newRow("non-zero-seed-64bit") << RandomSeed64; + QTest::newRow("non-zero-seed-64bit-negated") << ~RandomSeed64; + } } -void tst_QHashFunctions::initTestCase() +void tst_QHashFunctions::init() { - static_assert(int(RandomSeed) > 0); + QFETCH_GLOBAL(quint64, seedValue); + seed = size_t(seedValue); +} - QTest::addColumn<uint>("seedValue"); - QTest::newRow("zero-seed") << 0U; - QTest::newRow("non-zero-seed") << uint(RandomSeed); +template <typename T> static void addPositiveCommonRows() +{ + QTest::addRow("zero") << T(0); + QTest::addRow("positive_7bit") << T(42); + QTest::addRow("positive_15bit") << T(0x1f3f); + QTest::addRow("positive_31bit") << T(0x4b3d'93c4); + QTest::addRow("positive_63bit") << T(Q_INT64_C(0x39df'7338'4b14'fcb0)); + + QTest::addRow("SCHAR_MAX") << T(SCHAR_MAX); + QTest::addRow("SHRT_MAX") << T(SHRT_MAX); + QTest::addRow("INT_MAX") << T(INT_MAX); + QTest::addRow("LLONG_MAX") << T(LLONG_MAX); } -void tst_QHashFunctions::init() +void tst_QHashFunctions::signedIntegerConsistency_data() +{ + QTest::addColumn<qint64>("value"); + addPositiveCommonRows<qint64>(); + QTest::addRow("negative_7bit") << Q_INT64_C(-28); + QTest::addRow("negative_15bit") << Q_INT64_C(-0x387c); + QTest::addRow("negative_31bit") << qint64(-0x7713'30f9); + + QTest::addRow("SCHAR_MIN") << qint64(SCHAR_MIN); + QTest::addRow("SHRT_MIN") << qint64(SHRT_MIN); + QTest::addRow("INT_MIN") << qint64(INT_MIN); + QTest::addRow("LLONG_MIN") << LLONG_MIN; +} + +void tst_QHashFunctions::unsignedIntegerConsistency_data() +{ + QTest::addColumn<quint64>("value"); + addPositiveCommonRows<quint64>(); + + QTest::addRow("positive_8bit") << Q_UINT64_C(0xE4); + QTest::addRow("positive_16bit") << Q_UINT64_C(0xcafe); + QTest::addRow("positive_32bit") << quint64(0xcafe'babe); + + QTest::addRow("UCHAR_MAX") << quint64(UCHAR_MAX); + QTest::addRow("UHRT_MAX") << quint64(USHRT_MAX); + QTest::addRow("UINT_MAX") << quint64(UINT_MAX); + QTest::addRow("ULLONG_MAX") << ULLONG_MAX; +} + +static void unsignedIntegerConsistency(quint64 value, size_t seed) +{ + quint8 v8 = quint8(value); + quint16 v16 = quint16(value); + quint32 v32 = quint32(value); + + const auto hu8 = qHash(v8, seed); + const auto hu16 = qHash(v16, seed); + const auto hu32 = qHash(v32, seed); + const auto hu64 = qHash(value, seed); + + if (v8 == value) + QCOMPARE(hu8, hu32); + if (v16 == value) + QCOMPARE(hu16, hu32); + if (v32 == value) + QCOMPARE(hu64, hu32); + +#if QT_SUPPORTS_INT128 + const auto hu128 = qHash(quint128(value), seed); + QCOMPARE(hu128, hu64); +#endif + + // there are a few more unsigned types: +#ifdef __cpp_char8_t + const auto hc8 = qHash(char8_t(value), seed); +#endif + const auto hc16 = qHash(char16_t(value), seed); + const auto hc32 = qHash(char32_t(value), seed); +#ifdef __cpp_char8_t + QCOMPARE(hc8, hu8); +#endif + QCOMPARE(hc16, hu16); + QCOMPARE(hc32, hu32); +} + +void tst_QHashFunctions::unsignedIntegerConsistency() +{ + QFETCH(quint64, value); + ::unsignedIntegerConsistency(value, seed); +} + +void tst_QHashFunctions::signedIntegerConsistency() { - QFETCH_GLOBAL(uint, seedValue); - seed = seedValue; + QFETCH(qint64, value); + qint8 v8 = qint8(value); + qint16 v16 = qint16(value); + qint32 v32 = qint32(value); + + const auto hs8 = qHash(v8, seed); + const auto hs16 = qHash(v16, seed); + const auto hs32 = qHash(v32, seed); + const auto hs64 = qHash(value, seed); + + if (v8 == value) + QCOMPARE(hs8, hs32); + if (v16 == value) + QCOMPARE(hs16, hs32); + if (v32 == value) { + // because of QTBUG-116080, this may not match, but we can't guarantee + // it mismatches 100% of the time either + if constexpr (sizeof(size_t) > sizeof(int) || QT_VERSION_MAJOR > 6) + QCOMPARE(hs64, hs32); + } + +#if QT_SUPPORTS_INT128 + const auto hs128 = qHash(qint128(value), seed); + QCOMPARE(hs128, hs64); +#endif + + if (value > 0) { + quint64 u64 = quint64(value); + const auto hu64 = qHash(u64, seed); + QCOMPARE(hu64, hs64); + ::unsignedIntegerConsistency(u64, seed); + // by A == B && B == C -> A == C, we've shown hsXX == huXX for all XX + } +} + +void tst_QHashFunctions::extendedIntegerConsistency() +{ +#ifdef QT_SUPPORTS_INT128 + // We only need to check qint128 and quint128 consistency here. + qint128 v65bit = Q_INT128_C(0x1'abea'06b7'dcf5'106a); + qint128 v127bit = Q_INT128_C(0x387c'ac7a'22a0'5242'9ee9'bcaa'6a53'13af); + + QCOMPARE(qHash(quint128(v65bit), seed), qHash(v65bit, seed)); + QCOMPARE(qHash(quint128(v127bit), seed), qHash(v127bit, seed)); +#else + QSKIP("This platform does not support extended integer types."); +#endif +} + +void tst_QHashFunctions::floatingPointConsistency_data() +{ + QTest::addColumn<double>("value"); + QTest::addRow("zero") << 0.0; + + QTest::addRow("1.0") << 1.0; + QTest::addRow("infinity") << std::numeric_limits<double>::infinity(); + + QTest::addRow("fp16_epsilon") << double(std::numeric_limits<qfloat16>::epsilon()); + QTest::addRow("fp16_min") << double(std::numeric_limits<qfloat16>::min()); + QTest::addRow("fp16_max") << double(std::numeric_limits<qfloat16>::max()); + + QTest::addRow("float_epsilon") << double(std::numeric_limits<float>::epsilon()); + QTest::addRow("float_min") << double(std::numeric_limits<float>::min()); + QTest::addRow("float_max") << double(std::numeric_limits<float>::max()); + + QTest::addRow("double_epsilon") << double(std::numeric_limits<double>::epsilon()); + QTest::addRow("double_min") << double(std::numeric_limits<double>::min()); + QTest::addRow("double_max") << double(std::numeric_limits<double>::max()); +} + +void tst_QHashFunctions::floatingPointConsistency() +{ + QFETCH(double, value); + long double lvalue = value; + float fp32 = float(value); + qfloat16 fp16 = qfloat16(value); + + const auto hfld = qHash(lvalue, seed); + const auto hf64 = qHash(value, seed); + const auto hf32 = qHash(fp32, seed); + const auto hf16 = qHash(fp16, seed); + + const auto hnfld = qHash(-lvalue, seed); + const auto hnf64 = qHash(-value, seed); + const auto hnf32 = qHash(-fp32, seed); + const auto hnf16 = qHash(-fp16, seed); + + if (fp16 == fp32) { + QCOMPARE(hf16, hf32); + QCOMPARE(hnf16, hnf32); + } + + // See QTBUG-116077; the rest isn't guaranteed to match (but we can't + // guarantee it will mismatch either). + return; + + if (fp32 == value) { + QCOMPARE(hf32, hf64); + QCOMPARE(hnf32, hnf64); + } + + QCOMPARE(hfld, hf64); + QCOMPARE(hnfld, hnf64); +} + +void tst_QHashFunctions::stringConsistency_data() +{ + QTest::addColumn<QString>("value"); + QTest::newRow("null") << QString(); + QTest::newRow("empty") << ""; + QTest::newRow("withnull") << QStringLiteral("A\0z"); + QTest::newRow("short-ascii") << "Hello"; // 10 bytes + QTest::newRow("medium-ascii") << "Hello, World"; // 24 bytes + QTest::newRow("long-ascii") << QStringLiteral("abcdefghijklmnopqrstuvxyz").repeated(16); + + QTest::newRow("short-latin1") << "Bokmål"; + QTest::newRow("medium-latin1") << "Det går bra!"; // 24 bytes + QTest::newRow("long-latin1") + << R"(Alle mennesker er født frie og med samme menneskeverd og menneskerettigheter. + De er utstyrt med fornuft og samvittighet og bør handle mot hverandre i brorskapets ånd.)"; + + QTest::newRow("short-nonlatin1") << "Ελληνικά"; + QTest::newRow("long-nonlatin1") + << R"('Ολοι οι άνθρωποι γεννιούνται ελεύθεροι και ίσοι στην αξιοπρέπεια και τα + δικαιώματα. Είναι προικισμένοι με λογική και συνείδηση, και οφείλουν να συμπεριφέρονται μεταξύ + τους με πνεύμα αδελφοσύνης.)"; +} + +void tst_QHashFunctions::stringConsistency() +{ + QFETCH(QString, value); + QStringView sv = value; + QByteArray u8ba = value.toUtf8(); + QByteArray u8bav = u8ba; + + // sanity checking: + QCOMPARE(sv.isNull(), value.isNull()); + QCOMPARE(sv.isEmpty(), value.isEmpty()); + QCOMPARE(u8ba.isNull(), value.isNull()); + QCOMPARE(u8ba.isEmpty(), value.isEmpty()); + QCOMPARE(u8bav.isNull(), value.isNull()); + QCOMPARE(u8bav.isEmpty(), value.isEmpty()); + + QCOMPARE(qHash(sv, seed), qHash(value, seed)); + QCOMPARE(qHash(u8bav, seed), qHash(u8ba, seed)); + + if (seed == 0 || QHashHeterogeneousSearch<QString, QLatin1StringView>::value) { + QByteArray l1ba = value.toLatin1(); + QLatin1StringView l1sv(l1ba.data(), l1ba.size()); +#ifdef Q_PROCESSOR_ARM + // zero-extending aeshash not implemented on ARM +#else + if (value == l1sv) + QCOMPARE(qHash(l1sv, seed), qHash(value, seed)); +#endif + } } void tst_QHashFunctions::qhash() @@ -188,9 +436,7 @@ void tst_QHashFunctions::qhash_of_zero_floating_points() { QCOMPARE(qHash(-0.0f, seed), qHash(0.0f, seed)); QCOMPARE(qHash(-0.0 , seed), qHash(0.0 , seed)); -#ifndef Q_OS_DARWIN QCOMPARE(qHash(-0.0L, seed), qHash(0.0L, seed)); -#endif } void tst_QHashFunctions::qthash_data() @@ -216,8 +462,14 @@ namespace SomeNamespace { struct Hashable { int i; }; inline size_t qHash(Hashable h, size_t seed = 0) { return QT_PREPEND_NAMESPACE(qHash)(h.i, seed); } -} + struct AdlHashable { + int i; + private: + friend size_t qHash(AdlHashable h, size_t seed = 0) + { return QT_PREPEND_NAMESPACE(qHash)(h.i, seed); } + }; +} void tst_QHashFunctions::range() { static const int ints[] = {0, 1, 2, 3, 4, 5}; @@ -239,10 +491,16 @@ void tst_QHashFunctions::range() QCOMPARE(qHashRange(ints, ints + numInts, seed), qHashRange(it, end, seed)); } - SomeNamespace::Hashable hashables[] = {{0}, {1}, {2}, {3}, {4}, {5}}; - static const size_t numHashables = sizeof hashables / sizeof *hashables; - // compile check: is qHash() found using ADL? - (void)qHashRange(hashables, hashables + numHashables, seed); + { + SomeNamespace::Hashable hashables[] = {{0}, {1}, {2}, {3}, {4}, {5}}; + // compile check: is qHash() found using ADL? + [[maybe_unused]] auto r = qHashRange(std::begin(hashables), std::end(hashables), seed); + } + { + SomeNamespace::AdlHashable hashables[] = {{0}, {1}, {2}, {3}, {4}, {5}}; + // compile check: is qHash() found as a hidden friend? + [[maybe_unused]] auto r = qHashRange(std::begin(hashables), std::end(hashables), seed); + } } void tst_QHashFunctions::rangeCommutative() @@ -265,15 +523,47 @@ void tst_QHashFunctions::rangeCommutative() QCOMPARE(qHashRangeCommutative(ints, ints + numInts, seed), qHashRangeCommutative(it, end, seed)); } - SomeNamespace::Hashable hashables[] = {{0}, {1}, {2}, {3}, {4}, {5}}; - static const size_t numHashables = sizeof hashables / sizeof *hashables; - // compile check: is qHash() found using ADL? - (void)qHashRangeCommutative(hashables, hashables + numHashables, seed); + { + SomeNamespace::Hashable hashables[] = {{0}, {1}, {2}, {3}, {4}, {5}}; + // compile check: is qHash() found using ADL? + [[maybe_unused]] auto r = qHashRangeCommutative(std::begin(hashables), std::end(hashables), seed); + } + { + SomeNamespace::AdlHashable hashables[] = {{0}, {1}, {2}, {3}, {4}, {5}}; + // compile check: is qHash() found as a hidden friend? + [[maybe_unused]] auto r = qHashRangeCommutative(std::begin(hashables), std::end(hashables), seed); + } } +// QVarLengthArray these days has a qHash() as a hidden friend. +// This checks that QT_SPECIALIZE_STD_HASH_TO_CALL_QHASH can deal with that: + +QT_BEGIN_NAMESPACE +QT_SPECIALIZE_STD_HASH_TO_CALL_QHASH_BY_CREF(QVarLengthArray<QVector<int>>) +QT_END_NAMESPACE + void tst_QHashFunctions::stdHash() { { + std::unordered_set<QVarLengthArray<QVector<int>>> s = { + { + {0, 1, 2}, + {42, 43, 44}, + {}, + }, { + {11, 12, 13}, + {}, + }, + }; + QCOMPARE(s.size(), 2UL); + s.insert({ + {11, 12, 13}, + {}, + }); + QCOMPARE(s.size(), 2UL); + } + + { std::unordered_set<QString> s = {QStringLiteral("Hello"), QStringLiteral("World")}; QCOMPARE(s.size(), 2UL); s.insert(QStringLiteral("Hello")); @@ -310,8 +600,41 @@ void tst_QHashFunctions::stdHash() } +template <typename T1, typename T2> +void tst_QHashFunctions::stdPair_template(const T1 &t1, const T2 &t2) +{ + std::pair<T1, T2> dpair{}; + std::pair<T1, T2> vpair{t1, t2}; + + // confirm proper working of the pair and of the underlying types + QVERIFY(t1 == t1); + QVERIFY(t2 == t2); + QCOMPARE(qHash(t1, seed), qHash(t1, seed)); + QCOMPARE(qHash(t2, seed), qHash(t2, seed)); + + QVERIFY(dpair == dpair); + QVERIFY(vpair == vpair); + + // therefore their hashes should be equal + QCOMPARE(qHash(dpair, seed), qHash(dpair, seed)); + QCOMPARE(qHash(vpair, seed), qHash(vpair, seed)); +} + +void tst_QHashFunctions::enum_int_consistent_hash_qtbug108032() +{ + enum E { E1, E2, E3 }; + + static_assert(QHashPrivate::HasQHashSingleArgOverload<E>); + + QCOMPARE(qHash(E1, seed), qHash(int(E1), seed)); + QCOMPARE(qHash(E2, seed), qHash(int(E2), seed)); + QCOMPARE(qHash(E3, seed), qHash(int(E3), seed)); +} + +#if QT_DEPRECATED_SINCE(6, 6) void tst_QHashFunctions::setGlobalQHashSeed() { +QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED // Setter works as advertised qSetGlobalQHashSeed(0); QCOMPARE(qGlobalQHashSeed(), 0); @@ -324,7 +647,9 @@ void tst_QHashFunctions::setGlobalQHashSeed() // Reset works as advertised qSetGlobalQHashSeed(-1); QVERIFY(qGlobalQHashSeed() > 0); +QT_WARNING_POP } +#endif // QT_DEPRECATED_SINCE(6, 6) QTEST_APPLESS_MAIN(tst_QHashFunctions) #include "tst_qhashfunctions.moc" |