// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include #include #include #include #include #include #include #include #include #include #include using namespace Qt::StringLiterals; class tst_QHash : public QObject { Q_OBJECT private slots: void insert1(); void erase(); void erase_edge_case(); void key(); void keys(); void swap(); void count(); // copied from tst_QMap void clear(); // copied from tst_QMap void empty(); // copied from tst_QMap void find(); // copied from tst_QMap void constFind(); // copied from tst_QMap void contains(); // copied from tst_QMap void qhash(); void take(); // copied from tst_QMap void operator_eq(); // slightly modified from tst_QMap void heterogeneousSearch(); void heterogeneousSearchConstKey(); void heterogeneousSearchByteArray(); void heterogeneousSearchString(); void heterogeneousSearchLatin1String(); void rehash_isnt_quadratic(); void dont_need_default_constructor(); void qmultihash_specific(); void qmultihash_qhash_rvalue_ref_ctor(); void qmultihash_qhash_rvalue_ref_unite(); void qmultihashUnite(); void qmultihashSize(); void qmultihashHeterogeneousSearch(); void qmultihashHeterogeneousSearchConstKey(); void qmultihashHeterogeneousSearchByteArray(); void qmultihashHeterogeneousSearchString(); void qmultihashHeterogeneousSearchLatin1String(); void compare(); void compare2(); void iterators(); // sligthly modified from tst_QMap void multihashIterators(); void iteratorsInEmptyHash(); void keyIterator(); void multihashKeyIterator(); void keyValueIterator(); void multihashKeyValueIterator(); void keyValueIteratorInEmptyHash(); void keys_values_uniqueKeys(); // slightly modified from tst_QMap void const_shared_null(); void twoArguments_qHash(); void initializerList(); void eraseValidIteratorOnSharedHash(); void equal_range(); void insert_hash(); void multiHashStoresInReverseInsertionOrder(); void emplace(); void badHashFunction(); void hashOfHash(); void stdHash(); void countInEmptyHash(); void removeInEmptyHash(); void valueInEmptyHash(); void fineTuningInEmptyHash(); void reserveShared(); void reserveLessThanCurrentAmount(); void reserveKeepCapacity_data(); void reserveKeepCapacity(); void QTBUG98265(); void detachAndReferences(); void lookupUsingKeyIterator(); void squeeze(); void squeezeShared(); }; struct IdentityTracker { int value, id; }; inline size_t qHash(IdentityTracker key) { return qHash(key.value); } inline bool operator==(IdentityTracker lhs, IdentityTracker rhs) { return lhs.value == rhs.value; } struct Foo { static int count; Foo():c(count) { ++count; } Foo(const Foo& o):c(o.c) { ++count; } ~Foo() { --count; } constexpr Foo &operator=(const Foo &o) noexcept { c = o.c; return *this; } int c; int data[8]; }; int Foo::count = 0; //copied from tst_QMap.cpp class MyClass { public: MyClass() { ++count; } MyClass( const QString& c) { count++; str = c; } MyClass(const QString &a, const QString &b) { count++; str = a + b; } ~MyClass() { count--; } MyClass( const MyClass& c ) { count++; ++copies; str = c.str; } MyClass &operator =(const MyClass &o) { str = o.str; ++copies; return *this; } MyClass(MyClass &&c) { count++; ++moves; str = c.str; } MyClass &operator =(MyClass &&o) { str = o.str; ++moves; return *this; } QString str; static int count; static int copies; static int moves; }; int MyClass::count = 0; int MyClass::copies = 0; int MyClass::moves = 0; typedef QHash MyMap; //void tst_QMap::count() void tst_QHash::count() { { MyMap map; MyMap map2( map ); QCOMPARE( map.size(), 0 ); QCOMPARE( map2.size(), 0 ); QCOMPARE( MyClass::count, 0 ); // detach map2["Hallo"] = MyClass( "Fritz" ); QCOMPARE( map.size(), 0 ); QCOMPARE( map2.size(), 1 ); #ifndef Q_CC_SUN QCOMPARE( MyClass::count, 1 ); #endif } QCOMPARE( MyClass::count, 0 ); { typedef QHash Map; Map map; QCOMPARE( map.size(), 0); map.insert( "Torben", MyClass("Weis") ); QCOMPARE( map.size(), 1 ); map.insert( "Claudia", MyClass("Sorg") ); QCOMPARE( map.size(), 2 ); map.insert( "Lars", MyClass("Linzbach") ); map.insert( "Matthias", MyClass("Ettrich") ); map.insert( "Sue", MyClass("Paludo") ); map.insert( "Eirik", MyClass("Eng") ); map.insert( "Haavard", MyClass("Nord") ); map.insert( "Arnt", MyClass("Gulbrandsen") ); map.insert( "Paul", MyClass("Tvete") ); QCOMPARE( map.size(), 9 ); map.insert( "Paul", MyClass("Tvete 1") ); map.insert( "Paul", MyClass("Tvete 2") ); map.insert( "Paul", MyClass("Tvete 3") ); map.insert( "Paul", MyClass("Tvete 4") ); map.insert( "Paul", MyClass("Tvete 5") ); map.insert( "Paul", MyClass("Tvete 6") ); QCOMPARE( map.size(), 9 ); #ifndef Q_CC_SUN QCOMPARE( MyClass::count, 9 ); #endif Map map2( map ); QVERIFY( map2.size() == 9 ); #ifndef Q_CC_SUN QCOMPARE( MyClass::count, 9 ); #endif map2.insert( "Kay", MyClass("Roemer") ); QVERIFY( map2.size() == 10 ); QVERIFY( map.size() == 9 ); #ifndef Q_CC_SUN QCOMPARE( MyClass::count, 19 ); #endif map2 = map; QVERIFY( map.size() == 9 ); QVERIFY( map2.size() == 9 ); #ifndef Q_CC_SUN QCOMPARE( MyClass::count, 9 ); #endif map2.insert( "Kay", MyClass("Roemer") ); QVERIFY( map2.size() == 10 ); #ifndef Q_CC_SUN QCOMPARE( MyClass::count, 19 ); #endif map2.clear(); QVERIFY( map.size() == 9 ); QVERIFY( map2.size() == 0 ); #ifndef Q_CC_SUN QCOMPARE( MyClass::count, 9 ); #endif map2 = map; QVERIFY( map.size() == 9 ); QVERIFY( map2.size() == 9 ); #ifndef Q_CC_SUN QCOMPARE( MyClass::count, 9 ); #endif map2.clear(); QVERIFY( map.size() == 9 ); QVERIFY( map2.size() == 0 ); #ifndef Q_CC_SUN QCOMPARE( MyClass::count, 9 ); #endif map.remove( "Lars" ); QVERIFY( map.size() == 8 ); QVERIFY( map2.size() == 0 ); #ifndef Q_CC_SUN QCOMPARE( MyClass::count, 8 ); #endif map.remove( "Mist" ); QVERIFY( map.size() == 8 ); QVERIFY( map2.size() == 0 ); #ifndef Q_CC_SUN QCOMPARE( MyClass::count, 8 ); #endif } QVERIFY( MyClass::count == 0 ); { typedef QHash Map; Map map; map["Torben"] = MyClass("Weis"); #ifndef Q_CC_SUN QVERIFY( MyClass::count == 1 ); #endif QVERIFY( map.size() == 1 ); (void)map["Torben"].str; (void)map["Lars"].str; #ifndef Q_CC_SUN QVERIFY( MyClass::count == 2 ); #endif QVERIFY( map.size() == 2 ); const Map& cmap = map; (void)cmap["Depp"].str; #ifndef Q_CC_SUN QVERIFY( MyClass::count == 2 ); #endif QVERIFY( map.size() == 2 ); QVERIFY( cmap.size() == 2 ); } QCOMPARE( MyClass::count, 0 ); { for ( int i = 0; i < 100; ++i ) { QHash map; for (int j = 0; j < i; ++j) map.insert(j, MyClass(QString::number(j))); } QCOMPARE( MyClass::count, 0 ); } QCOMPARE( MyClass::count, 0 ); } void tst_QHash::insert1() { const char *hello = "hello"; const char *world = "world"; const char *allo = "allo"; const char *monde = "monde"; { typedef QHash Hash; Hash hash; QString key = QLatin1String(" "); for (int i = 0; i < 10; ++i) { key[0] = QChar(i + '0'); for (int j = 0; j < 10; ++j) { key[1] = QChar(j + '0'); hash.insert(key, "V" + key); } } for (int i = 0; i < 10; ++i) { key[0] = QChar(i + '0'); for (int j = 0; j < 10; ++j) { key[1] = QChar(j + '0'); hash.remove(key); } } } { typedef QHash Hash; Hash hash; hash.insert(1, hello); hash.insert(2, world); QVERIFY(hash.size() == 2); QVERIFY(!hash.isEmpty()); QT_WARNING_PUSH QT_WARNING_DISABLE_CLANG("-Wself-assign-overloaded") { Hash hash2 = hash; hash2 = hash; hash = hash2; hash2 = hash2; hash = hash; hash2.clear(); hash2 = hash2; QVERIFY(hash2.size() == 0); QVERIFY(hash2.isEmpty()); } QVERIFY(hash.size() == 2); QT_WARNING_POP { Hash hash2 = hash; hash2[1] = allo; hash2[2] = monde; QVERIFY(hash2[1] == allo); QVERIFY(hash2[2] == monde); QVERIFY(hash[1] == hello); QVERIFY(hash[2] == world); hash2[1] = hash[1]; hash2[2] = hash[2]; QVERIFY(hash2[1] == hello); QVERIFY(hash2[2] == world); hash[1] = hash[1]; QVERIFY(hash[1] == hello); } { Hash hash2 = hash; hash2.detach(); hash2.remove(1); QVERIFY(hash2.size() == 1); hash2.remove(1); QVERIFY(hash2.size() == 1); hash2.remove(0); QVERIFY(hash2.size() == 1); hash2.remove(2); QVERIFY(hash2.size() == 0); QVERIFY(hash.size() == 2); } hash.detach(); { Hash::iterator it1 = hash.find(1); QVERIFY(it1 != hash.end()); Hash::iterator it2 = hash.find(0); QVERIFY(it2 != hash.begin()); QVERIFY(it2 == hash.end()); *it1 = monde; QVERIFY(*it1 == monde); QVERIFY(hash[1] == monde); *it1 = hello; QVERIFY(*it1 == hello); QVERIFY(hash[1] == hello); hash[1] = monde; QVERIFY(it1.key() == 1); QVERIFY(it1.value() == monde); QVERIFY(*it1 == monde); QVERIFY(hash[1] == monde); hash[1] = hello; QVERIFY(*it1 == hello); QVERIFY(hash[1] == hello); } { const Hash hash2 = hash; Hash::const_iterator it1 = hash2.find(1); QVERIFY(it1 != hash2.end()); QVERIFY(it1.key() == 1); QVERIFY(it1.value() == hello); QVERIFY(*it1 == hello); Hash::const_iterator it2 = hash2.find(2); QVERIFY(it1 != it2); QVERIFY(it1 != hash2.end()); QVERIFY(it2 != hash2.end()); int count = 0; it1 = hash2.begin(); while (it1 != hash2.end()) { count++; ++it1; } QVERIFY(count == 2); count = 0; it1 = hash.constBegin(); while (it1 != hash.constEnd()) { count++; ++it1; } QVERIFY(count == 2); } { QVERIFY(hash.contains(1)); QVERIFY(hash.contains(2)); QVERIFY(!hash.contains(0)); QVERIFY(!hash.contains(3)); } { QVERIFY(hash.value(1) == hello); QVERIFY(hash.value(2) == world); QVERIFY(hash.value(3) == 0); QVERIFY(hash.value(1, allo) == hello); QVERIFY(hash.value(2, allo) == world); QVERIFY(hash.value(3, allo) == allo); QVERIFY(hash.value(0, monde) == monde); } { QHash hash; for (int i = 0; i < 10; i++) hash.insert(i, Foo()); QVERIFY(Foo::count == 10); hash.remove(7); QVERIFY(Foo::count == 9); } QVERIFY(Foo::count == 0); { QHash hash; QVERIFY(((const QHash*) &hash)->operator[](7) == 0); } } { QHash hash; QCOMPARE(hash.size(), 0); QVERIFY(!hash.isDetached()); const int dummy = -1; IdentityTracker id00 = {0, 0}, id01 = {0, 1}, searchKey = {0, dummy}; QCOMPARE(hash.insert(id00, id00.id).key().id, id00.id); QCOMPARE(hash.size(), 1); QCOMPARE(hash.insert(id01, id01.id).key().id, id00.id); // first key inserted is kept QCOMPARE(hash.size(), 1); QCOMPARE(hash.find(searchKey).value(), id01.id); // last-inserted value QCOMPARE(hash.find(searchKey).key().id, id00.id); // but first-inserted key } } void tst_QHash::erase() { QHash h1; h1.insert(1, 2); h1.erase(h1.begin()); QVERIFY(h1.size() == 0); QVERIFY(h1.begin() == h1.end()); h1.insert(3, 4); QVERIFY(*h1.begin() == 4); h1.insert(5, 6); QVERIFY(h1.size() == 2); QHash::iterator it1 = h1.begin(); ++it1; it1 = h1.erase(it1); QVERIFY(it1 == h1.end()); h1.insert(7, 8); h1.insert(9, 10); it1 = h1.begin(); int n = 0; while (it1 != h1.end()) { it1 = h1.erase(it1); ++n; } QVERIFY(n == 3); QMultiHash h2; h2.insert(20, 41); h2.insert(20, 42); QVERIFY(h2.size() == 2); auto bit = h2.begin(); auto mit = h2.erase(bit); mit = h2.erase(h2.begin()); QVERIFY(mit == h2.end()); h2 = QMultiHash(); h2.emplace(1, 1); h2.emplace(1, 2); h2.emplace(3, 1); h2.emplace(3, 4); QMultiHash h3 = h2; auto it = h3.constFind(3); ++it; QVERIFY(h3.isSharedWith(h2)); it = h3.erase(it); QVERIFY(!h3.isSharedWith(h2)); if (it != h3.cend()) { auto it2 = h3.constFind(it.key()); QCOMPARE(it, it2); } } /* With a specific seed we could end up in a situation where, upon deleting the last entry in a QHash, the returned iterator would not point to the end() iterator. */ void tst_QHash::erase_edge_case() { QHashSeed::setDeterministicGlobalSeed(); auto resetSeed = qScopeGuard([&]() { QHashSeed::resetRandomGlobalSeed(); }); QHash h1; h1.reserve(2); qsizetype capacity = h1.capacity(); // Beholden to QHash internals: qsizetype numBuckets = capacity << 1; // Find some keys which will both be slotted into the last bucket: int keys[2]; int index = 0; for (qsizetype i = 0; i < numBuckets * 4 && index < 2; ++i) { const size_t hash = qHash(i, QHashSeed::globalSeed()); const size_t bucketForHash = QHashPrivate::GrowthPolicy::bucketForHash(numBuckets, hash); if (qsizetype(bucketForHash) == numBuckets - 1) keys[index++] = i; } QCOMPARE(index, 2); // Sanity check. If this fails then the test needs an update! // As mentioned earlier these are both calculated to be in the last bucket: h1.insert(keys[0], 4); h1.insert(keys[1], 6); // As a sanity-check, make sure that the key we inserted last is the first one (because its // allocation to the last bucket would make it wrap around): // NOTE: If this fails this then this test may need an update!!! QCOMPARE(h1.constBegin().key(), keys[1]); // Then we delete the last entry: QHash::iterator it1 = h1.begin(); ++it1; it1 = h1.erase(it1); // Now, since we deleted the last entry, the iterator should be at the end(): QVERIFY(it1 == h1.end()); } void tst_QHash::key() { { QString def("default value"); QHash hash1; QCOMPARE(hash1.key(1), QString()); QCOMPARE(hash1.key(1, def), def); QVERIFY(!hash1.isDetached()); hash1.insert("one", 1); QCOMPARE(hash1.key(1), QLatin1String("one")); QCOMPARE(hash1.key(1, def), QLatin1String("one")); QCOMPARE(hash1.key(2), QString()); QCOMPARE(hash1.key(2, def), def); hash1.insert("two", 2); QCOMPARE(hash1.key(1), QLatin1String("one")); QCOMPARE(hash1.key(1, def), QLatin1String("one")); QCOMPARE(hash1.key(2), QLatin1String("two")); QCOMPARE(hash1.key(2, def), QLatin1String("two")); QCOMPARE(hash1.key(3), QString()); QCOMPARE(hash1.key(3, def), def); hash1.insert("deux", 2); QCOMPARE(hash1.key(1), QLatin1String("one")); QCOMPARE(hash1.key(1, def), QLatin1String("one")); QVERIFY(hash1.key(2) == QLatin1String("deux") || hash1.key(2) == QLatin1String("two")); QVERIFY(hash1.key(2, def) == QLatin1String("deux") || hash1.key(2, def) == QLatin1String("two")); QCOMPARE(hash1.key(3), QString()); QCOMPARE(hash1.key(3, def), def); } { int def = 666; QHash hash2; QCOMPARE(hash2.key("one"), 0); QCOMPARE(hash2.key("one", def), def); QVERIFY(!hash2.isDetached()); hash2.insert(1, "one"); QCOMPARE(hash2.key("one"), 1); QCOMPARE(hash2.key("one", def), 1); QCOMPARE(hash2.key("two"), 0); QCOMPARE(hash2.key("two", def), def); hash2.insert(2, "two"); QCOMPARE(hash2.key("one"), 1); QCOMPARE(hash2.key("one", def), 1); QCOMPARE(hash2.key("two"), 2); QCOMPARE(hash2.key("two", def), 2); QCOMPARE(hash2.key("three"), 0); QCOMPARE(hash2.key("three", def), def); hash2.insert(3, "two"); QCOMPARE(hash2.key("one"), 1); QCOMPARE(hash2.key("one", def), 1); QVERIFY(hash2.key("two") == 2 || hash2.key("two") == 3); QVERIFY(hash2.key("two", def) == 2 || hash2.key("two", def) == 3); QCOMPARE(hash2.key("three"), 0); QCOMPARE(hash2.key("three", def), def); hash2.insert(-1, "two"); QVERIFY(hash2.key("two") == 2 || hash2.key("two") == 3 || hash2.key("two") == -1); QVERIFY(hash2.key("two", def) == 2 || hash2.key("two", def) == 3 || hash2.key("two", def) == -1); hash2.insert(0, "zero"); QCOMPARE(hash2.key("zero"), 0); QCOMPARE(hash2.key("zero", def), 0); } { const int def = -1; QMultiHash hash; QCOMPARE(hash.key("val"), 0); QCOMPARE(hash.key("val", def), def); QVERIFY(!hash.isDetached()); hash.insert(1, "value1"); hash.insert(1, "value2"); hash.insert(2, "value1"); QCOMPARE(hash.key("value2"), 1); const auto key = hash.key("value1"); QVERIFY(key == 1 || key == 2); QCOMPARE(hash.key("value"), 0); QCOMPARE(hash.key("value", def), def); } } template QList sorted(const QList &list) { QList res = list; std::sort(res.begin(), res.end()); return res; } void tst_QHash::keys() { { QHash hash; QVERIFY(hash.keys().isEmpty()); QVERIFY(hash.keys(1).isEmpty()); QVERIFY(!hash.isDetached()); hash.insert("key1", 1); hash.insert("key2", 2); hash.insert("key3", 1); QCOMPARE(sorted(hash.keys()), QStringList({ "key1", "key2", "key3" })); QCOMPARE(sorted(hash.keys(1)), QStringList({ "key1", "key3" })); } { QMultiHash hash; QVERIFY(hash.keys().isEmpty()); QVERIFY(hash.keys(1).isEmpty()); QVERIFY(!hash.isDetached()); hash.insert("key1", 1); hash.insert("key2", 1); hash.insert("key1", 2); QCOMPARE(sorted(hash.keys()), QStringList({ "key1", "key1", "key2" })); QCOMPARE(sorted(hash.keys(1)), QStringList({ "key1", "key2" })); } } void tst_QHash::swap() { QHash h1, h2; h1[0] = "h1[0]"; h2[1] = "h2[1]"; h1.swap(h2); QCOMPARE(h1.value(1),QLatin1String("h2[1]")); QCOMPARE(h2.value(0),QLatin1String("h1[0]")); } // copied from tst_QMap void tst_QHash::clear() { { MyMap map; map.clear(); QVERIFY( map.isEmpty() ); map.insert( "key", MyClass( "value" ) ); map.clear(); QVERIFY( map.isEmpty() ); map.insert( "key0", MyClass( "value0" ) ); map.insert( "key0", MyClass( "value1" ) ); map.insert( "key1", MyClass( "value2" ) ); map.clear(); QVERIFY( map.isEmpty() ); } QCOMPARE( MyClass::count, int(0) ); { QMultiHash multiHash; multiHash.clear(); QVERIFY(multiHash.isEmpty()); multiHash.insert("key", MyClass("value0")); QVERIFY(!multiHash.isEmpty()); multiHash.clear(); QVERIFY(multiHash.isEmpty()); multiHash.insert("key0", MyClass("value0")); multiHash.insert("key0", MyClass("value1")); multiHash.insert("key1", MyClass("value2")); QVERIFY(!multiHash.isEmpty()); multiHash.clear(); QVERIFY(multiHash.isEmpty()); } QCOMPARE(MyClass::count, int(0)); } //copied from tst_QMap void tst_QHash::empty() { QHash map1; QVERIFY(map1.isEmpty()); QVERIFY(map1.empty()); map1.insert(1, "one"); QVERIFY(!map1.isEmpty()); QVERIFY(!map1.empty()); map1.clear(); QVERIFY(map1.isEmpty()); QVERIFY(map1.empty()); } //copied from tst_QMap void tst_QHash::find() { const QHash constEmptyHash; QVERIFY(constEmptyHash.find(1) == constEmptyHash.end()); QVERIFY(!constEmptyHash.isDetached()); QHash map1; QString testString="Teststring %0"; QString compareString; int i,count=0; QVERIFY(map1.find(1) == map1.end()); QVERIFY(!map1.isDetached()); map1.insert(1,"Mensch"); map1.insert(1,"Mayer"); map1.insert(2,"Hej"); QCOMPARE(map1.find(1).value(), QLatin1String("Mayer")); QCOMPARE(map1.find(2).value(), QLatin1String("Hej")); const QMultiHash constEmptyMultiHash; QVERIFY(constEmptyMultiHash.find(1) == constEmptyMultiHash.cend()); QVERIFY(constEmptyMultiHash.find(1, "value") == constEmptyMultiHash.cend()); QVERIFY(!constEmptyMultiHash.isDetached()); QMultiHash emptyMultiHash; QVERIFY(emptyMultiHash.find(1) == emptyMultiHash.end()); QVERIFY(emptyMultiHash.find(1, "value") == emptyMultiHash.end()); QVERIFY(!emptyMultiHash.isDetached()); QMultiHash multiMap(map1); for (i = 3; i < 10; ++i) { compareString = testString.arg(i); multiMap.insert(4, compareString); } auto it = multiMap.constFind(4); for (i = 9; i > 2 && it != multiMap.constEnd() && it.key() == 4; --i) { compareString = testString.arg(i); QVERIFY(it.value() == compareString); ++it; ++count; } QCOMPARE(count, 7); } // copied from tst_QMap void tst_QHash::constFind() { QHash map1; QString testString="Teststring %0"; QString compareString; int i,count=0; QVERIFY(map1.constFind(1) == map1.constEnd()); QVERIFY(!map1.isDetached()); map1.insert(1,"Mensch"); map1.insert(1,"Mayer"); map1.insert(2,"Hej"); QCOMPARE(map1.constFind(1).value(), QLatin1String("Mayer")); QCOMPARE(map1.constFind(2).value(), QLatin1String("Hej")); QMultiHash emptyMultiHash; QVERIFY(emptyMultiHash.constFind(1) == emptyMultiHash.constEnd()); QVERIFY(!emptyMultiHash.isDetached()); QMultiHash multiMap(map1); for (i = 3; i < 10; ++i) { compareString = testString.arg(i); multiMap.insert(4, compareString); } auto it = multiMap.constFind(4); for (i = 9; i > 2 && it != multiMap.constEnd() && it.key() == 4; --i) { compareString = testString.arg(i); QVERIFY(it.value() == compareString); ++it; ++count; } QCOMPARE(count, 7); } // copied from tst_QMap void tst_QHash::contains() { QHash map1; int i; QVERIFY(!map1.contains(1)); QVERIFY(!map1.isDetached()); map1.insert(1, "one"); QVERIFY(map1.contains(1)); for (i=2; i < 100; ++i) map1.insert(i, "teststring"); for (i=99; i > 1; --i) QVERIFY(map1.contains(i)); map1.remove(43); QVERIFY(!map1.contains(43)); } namespace { class QGlobalQHashSeedResetter { int oldSeed; public: // not entirely correct (may lost changes made by another thread between the query // of the old and the setting of the new seed), but setHashSeed() can't // return the old value, so this is the best we can do: explicit QGlobalQHashSeedResetter(int newSeed) : oldSeed(getHashSeed()) { setHashSeed(newSeed); } ~QGlobalQHashSeedResetter() { setHashSeed(oldSeed); } private: // The functions are implemented to replace the deprecated // qGlobalQHashSeed() and qSetGlobalQHashSeed() static int getHashSeed() { return int(QHashSeed::globalSeed() & INT_MAX); } static void setHashSeed(int seed) { if (seed == 0) QHashSeed::setDeterministicGlobalSeed(); else QHashSeed::resetRandomGlobalSeed(); } }; template QHash inverted(const QHash &in) { QHash result; for (auto it = in.begin(), end = in.end(); it != end; ++it) result[it.value()] = it.key(); return result; } template void make_test_data(AssociativeContainer &c) { c["one"] = "1"; c["two"] = "2"; } } void tst_QHash::qhash() { const QGlobalQHashSeedResetter seed1(0); QHash hash1; make_test_data(hash1); const QHash hsah1 = inverted(hash1); const QGlobalQHashSeedResetter seed2(1); QHash hash2; make_test_data(hash2); const QHash hsah2 = inverted(hash2); QCOMPARE(hash1, hash2); QCOMPARE(hsah1, hsah2); QCOMPARE(qHash(hash1), qHash(hash2)); QCOMPARE(qHash(hsah1), qHash(hsah2)); // by construction this is almost impossible to cause false collisions: QVERIFY(hash1 != hsah1); QVERIFY(hash2 != hsah2); QVERIFY(qHash(hash1) != qHash(hsah1)); QVERIFY(qHash(hash2) != qHash(hsah2)); } //copied from tst_QMap void tst_QHash::take() { { QHash map; QCOMPARE(map.take(1), QString()); QVERIFY(!map.isDetached()); map.insert(2, "zwei"); map.insert(3, "drei"); QCOMPARE(map.take(3), QLatin1String("drei")); QVERIFY(!map.contains(3)); } { QMultiHash hash; QCOMPARE(hash.take(1), QString()); QVERIFY(!hash.isDetached()); hash.insert(1, "value1"); hash.insert(2, "value2"); hash.insert(1, "value3"); // The docs tell that if there are multiple values for a key, then the // most recent is returned. QCOMPARE(hash.take(1), "value3"); QCOMPARE(hash.take(1), "value1"); QCOMPARE(hash.take(1), QString()); QCOMPARE(hash.take(2), "value2"); } } // slightly modified from tst_QMap void tst_QHash::operator_eq() { { // compare for equality: QHash a; QHash b; QVERIFY(a == b); QVERIFY(!(a != b)); a.insert(1,1); b.insert(1,1); QVERIFY(a == b); QVERIFY(!(a != b)); a.insert(0,1); b.insert(0,1); QVERIFY(a == b); QVERIFY(!(a != b)); // compare for inequality: a.insert(42,0); QVERIFY(a != b); QVERIFY(!(a == b)); a.insert(65, -1); QVERIFY(a != b); QVERIFY(!(a == b)); b.insert(-1, -1); QVERIFY(a != b); QVERIFY(!(a == b)); } { // a more complex map QHash a; QHash b; QVERIFY(a == b); QVERIFY(!(a != b)); a.insert("Hello", "World"); QVERIFY(a != b); QVERIFY(!(a == b)); b.insert("Hello", "World"); QVERIFY(a == b); QVERIFY(!(a != b)); a.insert("Goodbye", "cruel world"); QVERIFY(a != b); QVERIFY(!(a == b)); b.insert("Goodbye", "cruel world"); // what happens if we insert nulls? a.insert(QString(), QString()); QVERIFY(a != b); QVERIFY(!(a == b)); // empty keys and null keys match: b.insert(QString(""), QString()); QVERIFY(a == b); QVERIFY(!(a != b)); } { QHash a; QHash b; a.insert("otto", 1); b.insert("willy", 1); QVERIFY(a != b); QVERIFY(!(a == b)); } // unlike multi-maps, multi-hashes should be equal iff their contents are equal, // regardless of insertion or iteration order { QMultiHash a; QMultiHash b; a.insert(0, 0); a.insert(0, 1); b.insert(0, 1); b.insert(0, 0); QVERIFY(a == b); QVERIFY(!(a != b)); } { QMultiHash a; QMultiHash b; enum { Count = 100 }; for (int key = 0; key < Count; ++key) { for (int value = 0; value < Count; ++value) a.insert(key, value); } for (int key = Count - 1; key >= 0; --key) { for (int value = 0; value < Count; ++value) b.insert(key, value); } QVERIFY(a == b); QVERIFY(!(a != b)); } { QMultiHash a; QMultiHash b; enum { Count = 100, KeyStep = 17, // coprime with Count ValueStep = 23, // coprime with Count }; for (int key = 0; key < Count; ++key) { for (int value = 0; value < Count; ++value) a.insert(key, value); } // Generates two permutations of [0, Count) for the keys and values, // so that b will be identical to a, just built in a very different order. for (int k = 0; k < Count; ++k) { const int key = (k * KeyStep) % Count; for (int v = 0; v < Count; ++v) b.insert(key, (v * ValueStep) % Count); } QVERIFY(a == b); QVERIFY(!(a != b)); } } #ifdef __cpp_concepts struct HeterogeneousHashingType { inline static int conversionCount = 0; QString s; Q_IMPLICIT operator QString() const { ++conversionCount; return s; } // std::equality_comparable_with requires we be self-comparable too friend bool operator==(const HeterogeneousHashingType &t1, const HeterogeneousHashingType &t2) = default; friend bool operator==(const QString &string, const HeterogeneousHashingType &tester) { return tester.s == string; } friend bool operator!=(const QString &string, const HeterogeneousHashingType &tester) { return !(tester.s == string); } friend size_t qHash(const HeterogeneousHashingType &tester, size_t seed) { return qHash(tester.s, seed); } }; QT_BEGIN_NAMESPACE template <> struct QHashHeterogeneousSearch : std::true_type {}; template <> struct QHashHeterogeneousSearch : std::true_type {}; QT_END_NAMESPACE static_assert(std::is_same_v>); static_assert(std::equality_comparable_with); static_assert(QHashPrivate::HeterogeneouslySearchableWith); static_assert(QHashPrivate::HeterogeneouslySearchableWith); template struct HeterogeneousSearchTestHelper { static void resetCounter() {} static void checkCounter() {} }; template <> struct HeterogeneousSearchTestHelper { static void resetCounter() { HeterogeneousHashingType::conversionCount = 0; } static void checkCounter() { QTest::setThrowOnFail(true); auto scopeExit = qScopeGuard([] { QTest::setThrowOnFail(false); }); QCOMPARE(HeterogeneousHashingType::conversionCount, 0); } }; #else using HeterogeneousHashingType = QString; #endif template