diff options
Diffstat (limited to 'tests/auto/corelib/tools/qcache/tst_qcache.cpp')
-rw-r--r-- | tests/auto/corelib/tools/qcache/tst_qcache.cpp | 148 |
1 files changed, 119 insertions, 29 deletions
diff --git a/tests/auto/corelib/tools/qcache/tst_qcache.cpp b/tests/auto/corelib/tools/qcache/tst_qcache.cpp index f122e45e87..5fccb8f1d0 100644 --- a/tests/auto/corelib/tools/qcache/tst_qcache.cpp +++ b/tests/auto/corelib/tools/qcache/tst_qcache.cpp @@ -1,32 +1,7 @@ -/**************************************************************************** -** -** 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. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QTest> #include <qcache.h> @@ -37,6 +12,7 @@ public slots: void initTestCase(); void cleanupTestCase(); private slots: + void empty(); void maxCost(); void setMaxCost(); void totalCost(); @@ -48,6 +24,9 @@ private slots: void take(); void axioms_on_key_type(); void largeCache(); + void internalChainOrderAfterEntryUpdate(); + void emplaceLowerCost(); + void trimWithMovingAcrossSpans(); }; @@ -73,6 +52,21 @@ void tst_QCache::cleanupTestCase() QCOMPARE(Foo::count, 0); } +void tst_QCache::empty() +{ + QCache<int, int> cache; + QCOMPARE(cache.size(), 0); + QCOMPARE(cache.count(), 0); + QVERIFY(cache.isEmpty()); + QVERIFY(!cache.contains(1)); + QCOMPARE(cache.keys().size(), 0); + QCOMPARE(cache.take(1), nullptr); + QVERIFY(!cache.remove(1)); + QCOMPARE(cache.object(1), nullptr); + QCOMPARE(cache[1], nullptr); + QCOMPARE(cache.totalCost(), 0); +} + void tst_QCache::maxCost() { QCache<QString, int> cache1, cache2(100), cache3(200), cache4(-50); @@ -357,6 +351,7 @@ struct KeyType int foo; KeyType(int x) : foo(x) {} + constexpr KeyType(const KeyType &o) noexcept : foo(o.foo) {} private: KeyType &operator=(const KeyType &); @@ -414,5 +409,100 @@ void tst_QCache::largeCache() QVERIFY(cache.size() == 0); } +// The internal chain could lose track of some objects. +// Make sure it doesn't happen again. +void tst_QCache::internalChainOrderAfterEntryUpdate() +{ + QCache<QString, int> cache; + cache.setMaxCost(20); + cache.insert(QString::number(1), new int, 1); + cache.insert(QString::number(2), new int, 1); + cache.insert(QString::number(1), new int, 1); + // If the chain is still 'in order' then setting maxCost == 0 should + // a. not crash, and + // b. remove all the elements in the QHash + cache.setMaxCost(0); + QCOMPARE(cache.size(), 0); +} + +void tst_QCache::emplaceLowerCost() +{ + QCache<QString, int> cache; + cache.setMaxCost(5); + cache.insert("a", new int, 3); // insert high cost + cache.insert("a", new int, 1); // and then exchange it with a lower-cost object + QCOMPARE(cache.totalCost(), 1); + cache.remove("a"); // then remove the object + // The cache should now have a cost == 0 and be empty. + QCOMPARE(cache.totalCost(), 0); + QVERIFY(cache.isEmpty()); +} + +struct TrivialHashType { + int i = -1; + size_t hash = 0; + + TrivialHashType(int i, size_t hash) : i(i), hash(hash) {} + TrivialHashType(const TrivialHashType &o) noexcept = default; + TrivialHashType &operator=(const TrivialHashType &o) noexcept = default; + TrivialHashType(TrivialHashType &&o) noexcept : i(o.i), hash(o.hash) { + o.i = -1; + o.hash = 0; + } + TrivialHashType &operator=(TrivialHashType &&o) noexcept { + i = o.i; + hash = o.hash; + o.i = -1; + o.hash = 0; + return *this; + } + + + friend bool operator==(const TrivialHashType &lhs, const TrivialHashType &rhs) + { + return lhs.i == rhs.i; + } +}; +quint64 qHash(TrivialHashType t, size_t seed = 0) +{ + Q_UNUSED(seed); + return t.hash; +} + +// During trim(), if the Node we have a pointer to in the function is moved +// to another span in the hash table, our pointer would end up pointing to +// garbage memory. Test that this no longer happens +void tst_QCache::trimWithMovingAcrossSpans() +{ + qsizetype numBuckets = [](){ + QHash<int, int> h; + h.reserve(1); + // Beholden to QHash internals: + return h.capacity() << 1; + }(); + + QCache<TrivialHashType, int> cache; + cache.setMaxCost(1000); + + auto lastBucketInSpan = size_t(numBuckets - 1); + // If this fails then the test is no longer valid + QCOMPARE(QHashPrivate::GrowthPolicy::bucketForHash(numBuckets, lastBucketInSpan), + lastBucketInSpan); + + // Pad some space so we have two spans: + for (int i = 2; i < numBuckets; ++i) + cache.insert({i, 0}, nullptr); + + // These two are vying for the last bucket in the first span, + // when '0' is deleted, '1' is moved across the span boundary, + // invalidating any pointer to its Node. + cache.insert({0, lastBucketInSpan}, nullptr); + cache.insert({1, lastBucketInSpan}, nullptr); + + QCOMPARE(cache.size(), numBuckets); + cache.setMaxCost(0); + QCOMPARE(cache.size(), 0); +} + QTEST_APPLESS_MAIN(tst_QCache) #include "tst_qcache.moc" |