File indexing completed on 2024-11-10 04:40:22

0001 /*
0002     SPDX-FileCopyrightText: 2018 Daniel Vrátil <dvratil@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include <QObject>
0008 #include <QTest>
0009 
0010 #include "shared/akranges.h"
0011 
0012 #include <iostream>
0013 
0014 using namespace AkRanges;
0015 
0016 namespace
0017 {
0018 int transformFreeFunc(int i)
0019 {
0020     return i * 2;
0021 }
0022 
0023 struct TransformHelper {
0024 public:
0025     static int transform(int i)
0026     {
0027         return transformFreeFunc(i);
0028     }
0029 
0030     int operator()(int i) const
0031     {
0032         return transformFreeFunc(i);
0033     }
0034 };
0035 
0036 bool filterFreeFunc(int i)
0037 {
0038     return i % 2 == 0;
0039 }
0040 
0041 struct FilterHelper {
0042 public:
0043     static bool filter(int i)
0044     {
0045         return filterFreeFunc(i);
0046     }
0047 
0048     bool operator()(int i)
0049     {
0050         return filterFreeFunc(i);
0051     }
0052 };
0053 
0054 } // namespace
0055 
0056 class AkRangesTest : public QObject
0057 {
0058     Q_OBJECT
0059 
0060 private Q_SLOTS:
0061     void testTraits()
0062     {
0063         QVERIFY(AkTraits::isAppendable<QList<int>>);
0064         QVERIFY(!AkTraits::isInsertable<QList<int>>);
0065         QVERIFY(AkTraits::isReservable<QList<int>>);
0066 
0067         QVERIFY(!AkTraits::isAppendable<QSet<int>>);
0068         QVERIFY(AkTraits::isInsertable<QSet<int>>);
0069         QVERIFY(AkTraits::isReservable<QSet<int>>);
0070 
0071         QVERIFY(!AkTraits::isAppendable<QString>);
0072         QVERIFY(!AkTraits::isInsertable<QString>);
0073         QVERIFY(AkTraits::isReservable<QString>);
0074     }
0075 
0076     void testContainerConversion()
0077     {
0078         QHashSeed::setDeterministicGlobalSeed();
0079         {
0080             QList<int> in = {1, 2, 3, 4, 5};
0081             QCOMPARE(in | Actions::toQList, in.toList());
0082             QCOMPARE(in | Actions::toQList | Actions::toQVector, in);
0083             QCOMPARE(in | Actions::toQSet, QSet<int>(in.begin(), in.end()));
0084         }
0085         {
0086             QList<int> in = {1, 2, 3, 4, 5};
0087             QCOMPARE(in | Actions::toQVector, in.toVector());
0088             QCOMPARE(in | Actions::toQVector | Actions::toQList, in);
0089             QCOMPARE(in | Actions::toQSet, QSet<int>(in.begin(), in.end()));
0090         }
0091         QHashSeed::resetRandomGlobalSeed();
0092     }
0093 
0094     void testAssociativeContainerConversion()
0095     {
0096         QList<std::pair<int, QString>> in = {{1, QStringLiteral("One")}, {2, QStringLiteral("Two")}, {3, QStringLiteral("Three")}};
0097         QMap<int, QString> out = {{1, QStringLiteral("One")}, {2, QStringLiteral("Two")}, {3, QStringLiteral("Three")}};
0098         QCOMPARE(in | Actions::toQMap, out);
0099     }
0100 
0101     void testRangeConversion()
0102     {
0103         {
0104             QList<int> in = {1, 2, 3, 4, 5};
0105             AkRanges::detail::Range<QList<int>::const_iterator> range(in.cbegin(), in.cend());
0106             QCOMPARE(range | Actions::toQVector, QList<int>::fromList(in));
0107         }
0108 
0109         {
0110             QList<int> in = {1, 2, 3, 4, 5};
0111             AkRanges::detail::Range<QList<int>::const_iterator> range(in.cbegin(), in.cend());
0112             QCOMPARE(range | Actions::toQList, in.toList());
0113         }
0114 
0115         {
0116             QList<std::pair<int, QString>> in = {{1, QStringLiteral("One")}, {2, QStringLiteral("Two")}, {3, QStringLiteral("Three")}};
0117             QMap<int, QString> out = {{1, QStringLiteral("One")}, {2, QStringLiteral("Two")}, {3, QStringLiteral("Three")}};
0118             AkRanges::detail::Range<QList<std::pair<int, QString>>::const_iterator> range(in.cbegin(), in.cend());
0119             QCOMPARE(range | Actions::toQMap, out);
0120         }
0121     }
0122 
0123     void testTransform()
0124     {
0125         QList<int> in = {1, 2, 3, 4, 5};
0126         QList<int> out = {2, 4, 6, 8, 10};
0127         QCOMPARE(in | Views::transform([](int i) {
0128                      return i * 2;
0129                  }) | Actions::toQList,
0130                  out);
0131         QCOMPARE(in | Views::transform(transformFreeFunc) | Actions::toQList, out);
0132         QCOMPARE(in | Views::transform(&TransformHelper::transform) | Actions::toQList, out);
0133         QCOMPARE(in | Views::transform(TransformHelper()) | Actions::toQList, out);
0134     }
0135 
0136 private:
0137     class CopyCounter
0138     {
0139     public:
0140         CopyCounter() = default;
0141         CopyCounter(const CopyCounter &other)
0142             : copyCount(other.copyCount + 1)
0143             , transformed(other.transformed)
0144         {
0145         }
0146         CopyCounter(CopyCounter &&other) = default;
0147         CopyCounter &operator=(const CopyCounter &other)
0148         {
0149             copyCount = other.copyCount + 1;
0150             transformed = other.transformed;
0151             return *this;
0152         }
0153         CopyCounter &operator=(CopyCounter &&other) = default;
0154         ~CopyCounter() = default;
0155 
0156         int copyCount = 0;
0157         bool transformed = false;
0158     };
0159 
0160 private Q_SLOTS:
0161 
0162     void testTransformCopyCount()
0163     {
0164         {
0165             QList<CopyCounter> in = {{}}; // 1st copy (QList::append())
0166             QList<CopyCounter> out = in | Views::transform([](const auto &c) {
0167                                          CopyCounter r(c); // 2nd copy (expected)
0168                                          r.transformed = true;
0169                                          return r;
0170                                      })
0171                 | Actions::toQList; // 3rd copy (QList::append()) (Qt5 only)
0172             QCOMPARE(out.size(), in.size());
0173             QCOMPARE(out[0].copyCount, 2);
0174             QCOMPARE(out[0].transformed, true);
0175         }
0176 
0177         {
0178             QList<CopyCounter> in(1); // construct vector of one element, so no copying
0179                                       // occurs at initialization
0180             QList<CopyCounter> out = in | Views::transform([](const auto &c) {
0181                                          CopyCounter r(c); // 1st copy
0182                                          r.transformed = true;
0183                                          return r;
0184                                      })
0185                 | Actions::toQVector;
0186             QCOMPARE(out.size(), in.size());
0187             QCOMPARE(out[0].copyCount, 1);
0188             QCOMPARE(out[0].transformed, true);
0189         }
0190     }
0191 
0192     void testTransformConvert()
0193     {
0194         {
0195             QList<int> in = {1, 2, 3, 4, 5};
0196             QList<int> out = {2, 4, 6, 8, 10};
0197             QCOMPARE(in | Views::transform([](int i) {
0198                          return i * 2;
0199                      }) | Actions::toQVector,
0200                      out);
0201         }
0202 
0203         {
0204             QList<int> in = {1, 2, 3, 4, 5};
0205             QList<int> out = {2, 4, 6, 8, 10};
0206             QCOMPARE(in | Views::transform([](int i) {
0207                          return i * 2;
0208                      }) | Actions::toQList,
0209                      out);
0210         }
0211     }
0212 
0213     void testCreateRange()
0214     {
0215         {
0216             QList<int> in = {1, 2, 3, 4, 5, 6};
0217             QList<int> out = {3, 4, 5};
0218             QCOMPARE(Views::range(in.begin() + 2, in.begin() + 5) | Actions::toQList, out);
0219         }
0220     }
0221 
0222     void testRangeWithTransform()
0223     {
0224         {
0225             QList<int> in = {1, 2, 3, 4, 5, 6};
0226             QList<int> out = {6, 8, 10};
0227             QCOMPARE(Views::range(in.begin() + 2, in.begin() + 5) | Views::transform([](int i) {
0228                          return i * 2;
0229                      }) | Actions::toQList,
0230                      out);
0231         }
0232     }
0233 
0234     void testTransformType()
0235     {
0236         {
0237             QStringList in = {QStringLiteral("foo"), QStringLiteral("foobar"), QStringLiteral("foob")};
0238             QList<int> out = {3, 6, 4};
0239             QCOMPARE(in | Views::transform([](const auto &str) {
0240                          return (int)str.size();
0241                      }) | Actions::toQList,
0242                      out);
0243         }
0244     }
0245 
0246     void testFilter()
0247     {
0248         {
0249             QList<int> in = {1, 2, 3, 4, 5, 6, 7, 8};
0250             QList<int> out = {2, 4, 6, 8};
0251             QCOMPARE(in | Views::filter([](int i) {
0252                          return i % 2 == 0;
0253                      }) | Actions::toQList,
0254                      out);
0255             QCOMPARE(in | Views::filter(filterFreeFunc) | Actions::toQList, out);
0256             QCOMPARE(in | Views::filter(&FilterHelper::filter) | Actions::toQList, out);
0257             QCOMPARE(in | Views::filter(FilterHelper()) | Actions::toQList, out);
0258         }
0259     }
0260 
0261     void testFilterTransform()
0262     {
0263         {
0264             QStringList in = {QStringLiteral("foo"), QStringLiteral("foobar"), QStringLiteral("foob")};
0265             QList<qsizetype> out = {6};
0266             QCOMPARE(in | Views::transform(&QString::size) | Views::filter([](int i) {
0267                          return i > 5;
0268                      }) | Actions::toQList,
0269                      out);
0270             QCOMPARE(in | Views::filter([](const auto &str) {
0271                          return str.size() > 5;
0272                      }) | Views::transform(&QString::size)
0273                          | Actions::toQList,
0274                      out);
0275         }
0276     }
0277 
0278     void testTemporaryContainer()
0279     {
0280         const auto func = [] {
0281             QStringList rv;
0282             for (int i = 0; i < 5; i++) {
0283                 rv.push_back(QString::number(i));
0284             }
0285             return rv;
0286         };
0287         {
0288             QList<int> out = {0, 2, 4};
0289             QCOMPARE(func() | Views::transform([](const auto &str) {
0290                          return str.toInt();
0291                      }) | Views::filter([](int i) {
0292                          return i % 2 == 0;
0293                      }) | Actions::toQList,
0294                      out);
0295         }
0296         {
0297             QList<int> out = {0, 2, 4};
0298             QCOMPARE(func() | Views::filter([](const auto &v) {
0299                          return v.toInt() % 2 == 0;
0300                      }) | Views::transform([](const auto &str) {
0301                          return str.toInt();
0302                      }) | Actions::toQList,
0303                      out);
0304         }
0305     }
0306 
0307     void testTemporaryRange()
0308     {
0309         const auto func = [] {
0310             QStringList rv;
0311             for (int i = 0; i < 5; ++i) {
0312                 rv.push_back(QString::number(i));
0313             }
0314             return rv | Views::transform([](const auto &str) {
0315                        return str.toInt();
0316                    });
0317         };
0318         QList<int> out = {1, 3};
0319         QCOMPARE(func() | Views::filter([](int i) {
0320                      return i % 2 == 1;
0321                  }) | Actions::toQList,
0322                  out);
0323     }
0324 
0325 private:
0326     struct ForEachCallable {
0327     public:
0328         explicit ForEachCallable(QList<int> &out)
0329             : mOut(out)
0330         {
0331         }
0332 
0333         void operator()(int i)
0334         {
0335             mOut.push_back(i);
0336         }
0337 
0338         static void append(int i)
0339         {
0340             sOut.push_back(i);
0341         }
0342         static void clear()
0343         {
0344             sOut.clear();
0345         }
0346         static QList<int> sOut;
0347 
0348     private:
0349         QList<int> &mOut;
0350     };
0351 
0352 private Q_SLOTS:
0353     void testForEach()
0354     {
0355         const QList<int> in = {1, 2, 3, 4, 5, 6};
0356         {
0357             QList<int> out;
0358             in | Actions::forEach([&out](int v) {
0359                 out.push_back(v);
0360             });
0361             QCOMPARE(out, in);
0362         }
0363         {
0364             QList<int> out;
0365             in | Actions::forEach(ForEachCallable(out));
0366             QCOMPARE(out, in);
0367         }
0368         {
0369             ForEachCallable::clear();
0370             in | Actions::forEach(&ForEachCallable::append);
0371             QCOMPARE(ForEachCallable::sOut, in);
0372         }
0373         {
0374             QList<int> out;
0375             QCOMPARE(in | Actions::forEach([&out](int v) {
0376                          out.push_back(v);
0377                      }) | Views::filter([](int v) {
0378                          return v % 2 == 0;
0379                      }) | Views::transform([](int v) {
0380                          return v * 2;
0381                      }) | Actions::toQList,
0382                      QList<int>({4, 8, 12}));
0383             QCOMPARE(out, in);
0384         }
0385     }
0386 
0387 private:
0388     template<template<typename, typename> class Container>
0389     void testKeysValuesHelper()
0390     {
0391         const Container<int, QString> in = {{1, QStringLiteral("1")}, {2, QStringLiteral("2")}, {3, QStringLiteral("3")}};
0392 
0393         QCOMPARE(in | Views::keys | Actions::toQList, in.keys());
0394         QCOMPARE(in | Views::values | Actions::toQList, in.values());
0395     }
0396 
0397 private Q_SLOTS:
0398     void testKeysValuesQMap()
0399     {
0400         testKeysValuesHelper<QMap>();
0401     }
0402 
0403     void testKeysValuesQHash()
0404     {
0405         testKeysValuesHelper<QHash>();
0406     }
0407 
0408     void testAll()
0409     {
0410         const QList<int> vals = {2, 4, 6, 8, 10};
0411         QVERIFY(vals | Actions::all([](int v) {
0412                     return v % 2 == 0;
0413                 }));
0414         QVERIFY(!(vals | Actions::all([](int v) {
0415                       return v % 2 == 1;
0416                   })));
0417     }
0418 
0419     void testAny()
0420     {
0421         const QList<int> vals = {1, 3, 5, 7, 9};
0422         QVERIFY(vals | Actions::any([](int v) {
0423                     return v % 2 == 1;
0424                 }));
0425         QVERIFY(!(vals | Actions::any([](int v) {
0426                       return v % 2 == 0;
0427                   })));
0428     }
0429 
0430     void testNone()
0431     {
0432         const QList<int> vals = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
0433         QVERIFY(vals | Views::filter([](int i) {
0434                     return i % 2 == 0;
0435                 })
0436                 | Actions::none([](int i) {
0437                       return i % 2 == 1;
0438                   }));
0439         QVERIFY(!(vals | Views::filter([](int i) {
0440                       return i % 2 == 0;
0441                   })
0442                   | Actions::none([](int i) {
0443                         return i % 2 == 0;
0444                     })));
0445     }
0446 
0447     void testEnumerate()
0448     {
0449         const QList<int> vals = {2, 4, 6, 8, 10};
0450         for (const auto [idx, val] : vals | Views::enumerate()) {
0451             QCOMPARE(val, (idx + 1) * 2);
0452         }
0453         for (const auto [idx, val] : vals | Views::enumerate(1)) {
0454             QCOMPARE(val, idx * 2);
0455         }
0456     }
0457 };
0458 
0459 QList<int> AkRangesTest::ForEachCallable::sOut;
0460 
0461 QTEST_GUILESS_MAIN(AkRangesTest)
0462 
0463 #include "akrangestest.moc"