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 = [&currentItem, 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 = [&currentItem, 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)