File indexing completed on 2024-11-24 04:53:36
0001 /* Copyright (C) 2006 - 2016 Jan Kundrát <jkt@kde.org> 0002 0003 This file is part of the Trojita Qt IMAP e-mail client, 0004 http://trojita.flaska.net/ 0005 0006 This program is free software; you can redistribute it and/or 0007 modify it under the terms of the GNU General Public License as 0008 published by the Free Software Foundation; either version 2 of 0009 the License or (at your option) version 3 or any later version 0010 accepted by the membership of KDE e.V. (or its successor approved 0011 by the membership of KDE e.V.), which shall act as a proxy 0012 defined in Section 14 of version 3 of the license. 0013 0014 This program is distributed in the hope that it will be useful, 0015 but WITHOUT ANY WARRANTY; without even the implied warranty of 0016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0017 GNU General Public License for more details. 0018 0019 You should have received a copy of the GNU General Public License 0020 along with this program. If not, see <http://www.gnu.org/licenses/>. 0021 */ 0022 0023 #include <algorithm> 0024 #include <QStandardItemModel> 0025 #include <QTest> 0026 #include "test_QaimDfsIterator.h" 0027 #include "Common/StashingReverseIterator.h" 0028 #include "UiUtils/QaimDfsIterator.h" 0029 0030 #if defined(__has_feature) 0031 # if __has_feature(address_sanitizer) 0032 // better would be checking for UBSAN, but I don't think that there's an appropriate feature for this 0033 # define SKIP_QSTANDARDITEMMODEL 0034 # endif 0035 #endif 0036 0037 void TestQaimDfsIterator::testQaimDfsIterator() 0038 { 0039 #ifdef SKIP_QSTANDARDITEMMODEL 0040 QSKIP("ASAN build, https://bugreports.qt.io/browse/QTBUG-56027"); 0041 #else 0042 QFETCH(QString, order); 0043 QFETCH(QSharedPointer<QStandardItemModel>, model); 0044 QFETCH(UiUtils::QaimDfsIterator, begin); 0045 QFETCH(UiUtils::QaimDfsIterator, end); 0046 0047 QStringList buf; 0048 std::transform(begin, end, std::back_inserter(buf), 0049 [](const QModelIndex &what) -> QString { 0050 return what.data().toString(); 0051 }); 0052 QCOMPARE(buf.join(QLatin1Char(' ')), order); 0053 0054 // Check that operator++ can be reversed (while it points to a valid element or to end) 0055 for (auto it = begin; it != end; ++it) { 0056 auto another = it; 0057 ++it; 0058 --it; 0059 QVERIFY(it == another); 0060 auto reverse = Common::stashing_reverse_iterator<decltype(it)>(it); 0061 --reverse; 0062 QVERIFY(reverse->row() == it->row()); 0063 QVERIFY(reverse->internalPointer() == it->internalPointer()); 0064 } 0065 0066 if (begin != end) { 0067 // check iteration in reverse 0068 QStringList reversed; 0069 auto it = end; 0070 do { 0071 --it; 0072 reversed.push_back(it->data().toString()); 0073 } while (it != begin); 0074 std::reverse(reversed.begin(), reversed.end()); 0075 QCOMPARE(reversed.join(QLatin1Char(' ')), order); 0076 0077 // check that we won't wrap around 0078 if (*begin == model->index(0, 0, QModelIndex())) { 0079 --it; 0080 QVERIFY(!it->isValid()); 0081 --it; 0082 QVERIFY(!it->isValid()); 0083 } 0084 0085 // now check using an adapted version of STL's reverse_iterator 0086 reversed.clear(); 0087 Common::stashing_reverse_iterator<UiUtils::QaimDfsIterator> rbegin(end), rend(begin); 0088 std::transform(rbegin, rend, std::back_inserter(reversed), 0089 [](const QModelIndex &what) -> QString { 0090 return what.data().toString(); 0091 }); 0092 std::reverse(reversed.begin(), reversed.end()); 0093 QCOMPARE(reversed.join(QLatin1Char(' ')), order); 0094 } else { 0095 auto it = end; 0096 --it; 0097 ++it; 0098 QVERIFY(it == end); 0099 QVERIFY(!(it != end)); 0100 QVERIFY(it == begin); 0101 QVERIFY(!(it != begin)); 0102 } 0103 0104 if (order.isEmpty()) { 0105 QCOMPARE(std::distance(begin, end), 0); 0106 } else { 0107 QCOMPARE(std::distance(begin, end), order.count(QLatin1Char(' ')) + 1); 0108 } 0109 #endif 0110 } 0111 0112 void TestQaimDfsIterator::testQaimDfsIterator_data() 0113 { 0114 #ifdef SKIP_QSTANDARDITEMMODEL 0115 QSKIP("ASAN build, https://bugreports.qt.io/browse/QTBUG-56027"); 0116 #else 0117 QTest::addColumn<QString>("order"); 0118 QTest::addColumn<QSharedPointer<QStandardItemModel>>("model"); 0119 QTest::addColumn<UiUtils::QaimDfsIterator>("begin"); 0120 QTest::addColumn<UiUtils::QaimDfsIterator>("end"); 0121 QSharedPointer<QStandardItemModel> m; 0122 UiUtils::QaimDfsIterator begin, end; 0123 0124 m.reset(new QStandardItemModel()); 0125 begin = UiUtils::QaimDfsIterator(); 0126 Q_ASSERT(!begin->isValid()); 0127 end = UiUtils::QaimDfsIterator(QModelIndex(), m.data()); 0128 Q_ASSERT(!end->isValid()); 0129 QTest::newRow("empty-model") << QString() << m << begin << end; 0130 0131 m.reset(new QStandardItemModel()); 0132 m->appendRow(new QStandardItem("a")); 0133 begin = UiUtils::QaimDfsIterator(m->index(0, 0, QModelIndex())); 0134 Q_ASSERT(begin->data().toString() == "a"); 0135 end = UiUtils::QaimDfsIterator(QModelIndex(), m.data()); 0136 Q_ASSERT(!end->isValid()); 0137 QTest::newRow("one-item") << "a" << m << begin << end; 0138 0139 m.reset(new QStandardItemModel()); 0140 m->appendRow(new QStandardItem("a")); 0141 m->appendRow(new QStandardItem("b")); 0142 m->appendRow(new QStandardItem("c")); 0143 begin = UiUtils::QaimDfsIterator(m->index(0, 0, QModelIndex())); 0144 Q_ASSERT(begin->data().toString() == "a"); 0145 end = UiUtils::QaimDfsIterator(QModelIndex(), m.data()); 0146 Q_ASSERT(!end->isValid()); 0147 QTest::newRow("flat-list") << "a b c" << m << begin << end; 0148 0149 m.reset(new QStandardItemModel()); 0150 auto item3 = new QStandardItem("a.A.1"); 0151 auto item2 = new QStandardItem("a.A"); 0152 item2->appendRow(item3); 0153 auto item1 = new QStandardItem("a"); 0154 item1->appendRow(item2); 0155 m->appendRow(item1); 0156 item1 = item2 = item3 = nullptr; 0157 begin = UiUtils::QaimDfsIterator(m->index(0, 0, QModelIndex())); 0158 Q_ASSERT(begin->data().toString() == "a"); 0159 end = UiUtils::QaimDfsIterator(QModelIndex(), m.data()); 0160 Q_ASSERT(!end->isValid()); 0161 QTest::newRow("linear-hierarchy") << "a a.A a.A.1" << m << begin << end; 0162 0163 m.reset(new QStandardItemModel()); 0164 item3 = new QStandardItem("a.A.1"); 0165 item2 = new QStandardItem("a.A"); 0166 item2->appendRow(item3); 0167 item1 = new QStandardItem("a"); 0168 item1->appendRow(item2); 0169 item3 = new QStandardItem("a.B.1"); 0170 item2 = new QStandardItem("a.B"); 0171 item2->appendRow(item3); 0172 item1->appendRow(item2); 0173 m->appendRow(item1); 0174 item1 = new QStandardItem("b"); 0175 m->appendRow(item1); 0176 begin = UiUtils::QaimDfsIterator(m->index(0, 0, QModelIndex())); 0177 Q_ASSERT(begin->data().toString() == "a"); 0178 end = UiUtils::QaimDfsIterator(QModelIndex(), m.data()); 0179 Q_ASSERT(!end->isValid()); 0180 QTest::newRow("backtracking") << "a a.A a.A.1 a.B a.B.1 b" << m << begin << end; 0181 0182 m.reset(new QStandardItemModel()); 0183 item3 = new QStandardItem("a.A.1"); 0184 item2 = new QStandardItem("a.A"); 0185 item2->appendRow(item3); 0186 item1 = new QStandardItem("a"); 0187 item1->appendRow(item2); 0188 item3 = new QStandardItem("a.B.1"); 0189 item2 = new QStandardItem("a.B"); 0190 item2->appendRow(item3); 0191 item1->appendRow(item2); 0192 m->appendRow(item1); 0193 item1 = new QStandardItem("b"); 0194 m->appendRow(item1); 0195 QModelIndex index = m->index(0, 0, QModelIndex()); 0196 begin = UiUtils::QaimDfsIterator(index.model()->index(0, 0, index)); 0197 Q_ASSERT(begin->data().toString() == "a.A"); 0198 end = UiUtils::QaimDfsIterator(m->index(1, 0, QModelIndex())); 0199 Q_ASSERT(end->data().toString() == "b"); 0200 QTest::newRow("backtracking-until-top-level") << "a.A a.A.1 a.B a.B.1" << m << begin << end; 0201 #endif 0202 } 0203 0204 void TestQaimDfsIterator::testWrappedFind() 0205 { 0206 #ifdef SKIP_QSTANDARDITEMMODEL 0207 QSKIP("ASAN build, https://bugreports.qt.io/browse/QTBUG-56027"); 0208 #else 0209 QFETCH(QString, items); 0210 QFETCH(QString, commands); 0211 0212 QStandardItemModel model; 0213 const auto splitItems = items.split(QLatin1Char(' ')); 0214 for (const auto &i: splitItems) { 0215 model.appendRow(new QStandardItem(i)); 0216 } 0217 const auto cmdStream = commands.split(QLatin1Char(' ')); 0218 QVERIFY(cmdStream.size() % 2 == 0); 0219 0220 QModelIndex currentItem; 0221 0222 auto matcher = [](const QModelIndex &idx) { return idx.data().toString().contains(QLatin1Char('_')); }; 0223 0224 QStringList foundValues, expectedValues; 0225 0226 for (int i = 0; i < cmdStream.size() - 1; ++i) { 0227 bool numberOk; 0228 0229 auto positiveAction = [¤tItem, cmdStream, &i, &foundValues, &expectedValues](const QModelIndex &idx) { 0230 currentItem = idx; 0231 foundValues.push_back(idx.data().toString()); 0232 expectedValues.push_back(cmdStream[i] + "_"); 0233 QCOMPARE(foundValues, expectedValues); 0234 }; 0235 0236 auto negativeAction = [¤tItem, cmdStream, &i, &foundValues, &expectedValues]() { 0237 if (currentItem.isValid()) { 0238 // ensure we haven't moved 0239 foundValues.push_back(currentItem.data().toString()); 0240 expectedValues.push_back(cmdStream[i] + "_"); 0241 } else { 0242 // current index should be invalid 0243 foundValues.push_back("-1"); 0244 expectedValues.push_back(cmdStream[i]); 0245 }; 0246 QCOMPARE(foundValues, expectedValues); 0247 }; 0248 0249 if (cmdStream[i] == "C") { 0250 ++i; 0251 // set current item 0252 currentItem = model.index(cmdStream[i].toInt(&numberOk), 0); 0253 Q_ASSERT(numberOk); 0254 Q_ASSERT(currentItem.isValid()); 0255 foundValues.push_back("C"); 0256 expectedValues.push_back("C"); 0257 } else if (cmdStream[i] == "N") { 0258 ++i; 0259 UiUtils::gotoNext(&model, currentItem, matcher, positiveAction, negativeAction); 0260 } else if (cmdStream[i] == "P") { 0261 ++i; 0262 UiUtils::gotoPrevious(&model, currentItem, matcher, positiveAction, negativeAction); 0263 } else { 0264 Q_ASSERT(false); 0265 } 0266 } 0267 QCOMPARE(foundValues.size(), cmdStream.size() / 2); 0268 #endif 0269 } 0270 0271 void TestQaimDfsIterator::testWrappedFind_data() 0272 { 0273 QTest::addColumn<QString>("items"); 0274 QTest::addColumn<QString>("commands"); 0275 0276 // So this is a "fancy DSL" for controlling iteration through the contents of this random model. 0277 // At first, we specify the content. Our model is linear, not a subtree one (we have other tests 0278 // for that). Interesting items (those that we're looking for) are marked by an underscore. 0279 // 0280 // Next come the actual commands -- a character followed by a number. 0281 // - C: set the current index to row #something (note that it starts at zero). 0282 // - N: go to next interesting index and ensure that its number matches our expectation 0283 // - P: previous one, i.e., N in the other direction 0284 QTest::newRow("no-marked-items") << "1 2 3 4 5" << "N -1 P -1 N -1 N -1 P -1 P -1 P -1"; 0285 QTest::newRow("simple-forward") << "1 2_ 3 4 5_ 6_ 7_ 8" << "N 2 N 5 N 6 N 7 N 2 N 5 N 6"; 0286 QTest::newRow("simple-forward-interesting-at-beginning") << "1_ 2_ 3 4 5_ 6_ 7_ 8" << "N 1 N 2 N 5 N 6 N 7 N 1 N 2 N 5 N 6"; 0287 QTest::newRow("simple-backward") << "1 2_ 3 4 5_ 6_ 7_ 8" << "P 7 P 6 P 5 P 2 P 7 P 6"; 0288 QTest::newRow("simple-backward-interesting-at-end") << "1_ 2_ 3 4 5 6_ 7_ 8_" << "P 8 P 7 P 6 P 2 P 1 P 8 P 7 P 6"; 0289 QTest::newRow("simple-navigation") << "1 2_ 3 4 5_ 6_ 7_ 8" << "N 2 N 5 N 6 P 5 P 2 P 7 P 6 N 7 N 2"; 0290 QTest::newRow("navigation-with-initial") << "0 1 2_ 3 4 5_ 6_ 7_ 8" << "C 3 N 5 N 6 P 5 P 2 P 7 P 6 N 7 N 2"; 0291 } 0292 0293 0294 QTEST_GUILESS_MAIN(TestQaimDfsIterator)