File indexing completed on 2024-05-19 04:39:59

0001 /*
0002     SPDX-FileCopyrightText: 2012 Milian Wolff <mail@milianw.de>
0003     SPDX-FileCopyrightText: 2015 Kevin Funk <kfunk@kde.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006 */
0007 
0008 #include "test_path.h"
0009 
0010 #include <util/path.h>
0011 
0012 #include <KIO/Global>
0013 
0014 #include <QTest>
0015 #include <QStandardPaths>
0016 
0017 #include <type_traits>
0018 #include <utility>
0019 
0020 QTEST_MAIN(TestPath)
0021 
0022 using namespace KDevelop;
0023 
0024 static_assert(std::is_nothrow_move_assignable<Path>(), "Why would a move assignment operator throw?");
0025 static_assert(std::is_nothrow_move_constructible<Path>(), "Why would a move constructor throw?");
0026 
0027 static const int FILES_PER_FOLDER = 10;
0028 static const int FOLDERS_PER_FOLDER = 5;
0029 static const int TREE_DEPTH = 5;
0030 
0031 template<typename T>
0032 T stringToUrl(const QString& path)
0033 {
0034     return T(path);
0035 }
0036 
0037 template<>
0038 QStringList stringToUrl(const QString& path)
0039 {
0040     return path.split('/');
0041 }
0042 
0043 template<typename T>
0044 T childUrl(const T& parent, const QString& child)
0045 {
0046     return T(parent, child);
0047 }
0048 
0049 template<>
0050 QStringList childUrl(const QStringList& parent, const QString& child)
0051 {
0052     QStringList ret = parent;
0053     ret << child;
0054     return ret;
0055 }
0056 
0057 template<>
0058 QUrl childUrl(const QUrl& parent, const QString& child)
0059 {
0060     QUrl ret = parent;
0061     ret.setPath(ret.path() + '/' + child);
0062     return ret;
0063 }
0064 
0065 template<typename T>
0066 QVector<T> generateData(const T& parent, int level)
0067 {
0068     QVector<T> ret;
0069     // files per folder
0070     for (int i = 0; i < FILES_PER_FOLDER; ++i) {
0071         const QString fileName = QStringLiteral("file%1.txt").arg(i);
0072         const T file = childUrl<T>(parent, fileName);
0073         Q_ASSERT(!ret.contains(file));
0074         ret << file;
0075     }
0076 
0077     // nesting depth
0078     if (level < TREE_DEPTH) {
0079         // folders per folder
0080         for (int i = 0; i < FOLDERS_PER_FOLDER; ++i) {
0081             const QString folderName = QStringLiteral("folder%1").arg(i);
0082             const T folder = childUrl<T>(parent, folderName);
0083             Q_ASSERT(!ret.contains(folder));
0084             ret << folder;
0085             ret += generateData<T>(folder, level + 1);
0086         }
0087     }
0088     return ret;
0089 }
0090 
0091 template<typename T>
0092 void runBenchmark()
0093 {
0094     QBENCHMARK {
0095         const T base = stringToUrl<T>("/tmp/foo/bar");
0096         generateData(base, 0);
0097     }
0098 }
0099 
0100 void TestPath::initTestCase()
0101 {
0102     QStandardPaths::setTestModeEnabled(true);
0103 }
0104 
0105 void TestPath::bench_qurl()
0106 {
0107     runBenchmark<QUrl>();
0108 }
0109 
0110 void TestPath::bench_qstringlist()
0111 {
0112     runBenchmark<QStringList>();
0113 }
0114 
0115 void TestPath::bench_path()
0116 {
0117     runBenchmark<Path>();
0118 }
0119 
0120 void TestPath::bench_fromLocalPath()
0121 {
0122     QFETCH(int, variant);
0123 
0124     const QString input(QStringLiteral("/foo/bar/asdf/bla/blub.h"));
0125     const int repeat = 1000;
0126     if (variant == 1) {
0127         QBENCHMARK {
0128             for (int i = 0; i < repeat; ++i) {
0129                 Path path = Path(QUrl::fromLocalFile(input));
0130                 Q_UNUSED(path);
0131             }
0132         }
0133     } else if (variant == 2) {
0134         QBENCHMARK {
0135             for (int i = 0; i < repeat; ++i) {
0136                 Path path = Path(input);
0137                 Q_UNUSED(path);
0138             }
0139         }
0140     } else {
0141         QFAIL("unexpected variant");
0142     }
0143 }
0144 
0145 void TestPath::bench_fromLocalPath_data()
0146 {
0147     QTest::addColumn<int>("variant");
0148 
0149     QTest::newRow("QUrl::fromLocalFile") << 1;
0150     QTest::newRow("QString") << 2;
0151 }
0152 
0153 void TestPath::bench_swap()
0154 {
0155     Path a(QStringLiteral("/foo/a.cpp"));
0156     Path b(QStringLiteral("/bar/b.cc"));
0157     QBENCHMARK {
0158         using std::swap;
0159         swap(a, b);
0160     }
0161 }
0162 
0163 void TestPath::bench_hash()
0164 {
0165     const Path path(QStringLiteral("/my/very/long/path/to/a/file.cpp"));
0166     QBENCHMARK {
0167         auto hash = qHash(path);
0168         Q_UNUSED(hash);
0169     }
0170 }
0171 
0172 /// Invoke @p op on URL @p base, but preserve drive letter if @p op removes it
0173 template<typename Func>
0174 QUrl preserveWindowsDriveLetter(const QUrl& base, Func op)
0175 {
0176 #ifndef Q_OS_WIN
0177     return op(base);
0178 #else
0179     // only apply to local files
0180     if (!base.isLocalFile()) {
0181         return op(base);
0182     }
0183 
0184     // save drive letter
0185     const QString windowsDriveLetter = base.toLocalFile().mid(0, 2);
0186     QUrl url = op(base);
0187     // restore drive letter
0188     if (url.toLocalFile().startsWith('/')) {
0189         url = QUrl::fromLocalFile(windowsDriveLetter + url.toLocalFile());
0190     }
0191     return url;
0192 #endif
0193 }
0194 
0195 QUrl resolvedUrl(const QUrl& base, const QUrl& relative)
0196 {
0197     return preserveWindowsDriveLetter(base, [&](const QUrl& url) {
0198         return url.resolved(relative);
0199     });
0200 }
0201 
0202 QUrl comparableUpUrl(const QUrl& url)
0203 {
0204     QUrl ret = preserveWindowsDriveLetter(url, [&](const QUrl& url) {
0205         return KIO::upUrl(url).adjusted(QUrl::RemovePassword);
0206     });
0207     return ret.adjusted(QUrl::StripTrailingSlash);
0208 }
0209 
0210 void TestPath::testPath()
0211 {
0212     QFETCH(QString, input);
0213 
0214     QUrl url = QUrl::fromUserInput(input);
0215     url = url.adjusted(QUrl::StripTrailingSlash | QUrl::NormalizePathSegments);
0216 
0217     Path optUrl(input);
0218 
0219     if (!url.password().isEmpty()) {
0220         QUrl urlNoPass = url.adjusted(QUrl::RemovePassword);
0221         QCOMPARE(optUrl.toUrl(), urlNoPass);
0222     } else {
0223         QCOMPARE(optUrl.toUrl(), url);
0224     }
0225     QCOMPARE(optUrl.isLocalFile(), url.isLocalFile());
0226     QCOMPARE(optUrl.pathOrUrl(), toUrlOrLocalFile(url, QUrl::RemovePassword));
0227     QCOMPARE(optUrl.isValid(), url.isValid());
0228     QCOMPARE(optUrl.isEmpty(), url.isEmpty());
0229     QCOMPARE(optUrl.lastPathSegment(), url.fileName());
0230     QCOMPARE(optUrl.path(), url.isLocalFile() ? url.toLocalFile() : url.path());
0231     QCOMPARE(optUrl.parent().toUrl(), comparableUpUrl(url));
0232     QCOMPARE(optUrl.toLocalFile(), url.toLocalFile());
0233 
0234     QCOMPARE(optUrl, Path(input));
0235     QCOMPARE(optUrl, Path(optUrl));
0236     QVERIFY(optUrl != Path(input + "/asdf"));
0237 
0238     if (url.isLocalFile() && !input.startsWith(QLatin1String("file://"))) {
0239         QCOMPARE(optUrl, Path(QUrl::fromLocalFile(input)));
0240     }
0241 
0242     QCOMPARE(optUrl, Path(url));
0243 
0244     if (url.isValid()) {
0245         QVERIFY(optUrl.relativePath(optUrl).isEmpty());
0246 
0247         Path relativePath(optUrl, QStringLiteral("foo/bar"));
0248         QCOMPARE(optUrl.relativePath(relativePath), QLatin1String("foo/bar"));
0249         QCOMPARE(relativePath.relativePath(optUrl), QLatin1String("../../"));
0250         QVERIFY(optUrl.isParentOf(relativePath));
0251         QVERIFY(!relativePath.isParentOf(optUrl));
0252 
0253 #ifndef Q_OS_WIN
0254         Path absolutePath(optUrl, QStringLiteral("/laa/loo"));
0255         QCOMPARE(absolutePath.path(), QLatin1String("/laa/loo"));
0256         QCOMPARE(url.resolved(QUrl(QStringLiteral("/laa/loo"))).path(), QLatin1String("/laa/loo"));
0257 
0258         Path absolutePath2(optUrl, QStringLiteral("/"));
0259         QCOMPARE(absolutePath2.path(), QLatin1String("/"));
0260         QCOMPARE(url.resolved(QUrl(QStringLiteral("/"))).path(), QLatin1String("/"));
0261 #endif
0262 
0263         Path unrelatedPath(QStringLiteral("https://test@blubasdf.com:12345/"));
0264         QCOMPARE(optUrl.relativePath(unrelatedPath), unrelatedPath.pathOrUrl());
0265         QCOMPARE(unrelatedPath.relativePath(optUrl), optUrl.pathOrUrl());
0266         QVERIFY(!unrelatedPath.isParentOf(optUrl));
0267         QVERIFY(!optUrl.isParentOf(unrelatedPath));
0268     }
0269 
0270     QCOMPARE(Path().relativePath(optUrl), optUrl.pathOrUrl());
0271     QVERIFY(optUrl.relativePath(Path()).isEmpty());
0272     QVERIFY(Path().relativePath(Path()).isEmpty());
0273     QVERIFY(!optUrl.isParentOf(Path()));
0274     QVERIFY(!Path().isParentOf(optUrl));
0275     QVERIFY(!Path().isParentOf(Path()));
0276     QVERIFY(!optUrl.isParentOf(optUrl));
0277 
0278     QCOMPARE(optUrl.isRemote(), optUrl.isValid() && !optUrl.isLocalFile());
0279     QCOMPARE(optUrl.isRemote(), optUrl.isValid() && !optUrl.remotePrefix().isEmpty());
0280 
0281     if (url.path() == QLatin1String("/")) {
0282         url.setPath("/test/foo/bar");
0283     } else {
0284         url.setPath(url.path() + "/test/foo/bar");
0285     }
0286     if (url.scheme().isEmpty()) {
0287         url.setScheme(QStringLiteral("file"));
0288     }
0289     optUrl.addPath(QStringLiteral("test/foo/bar"));
0290     QCOMPARE(optUrl.lastPathSegment(), url.fileName());
0291     QCOMPARE(optUrl.path(), url.isLocalFile() ? url.toLocalFile() : url.path());
0292 
0293     url = url.adjusted(QUrl::RemoveFilename);
0294     url.setPath(url.path() + "lalalala_adsf.txt");
0295     optUrl.setLastPathSegment(QStringLiteral("lalalala_adsf.txt"));
0296     QCOMPARE(optUrl.lastPathSegment(), url.fileName());
0297     QCOMPARE(optUrl.path(), url.isLocalFile() ? url.toLocalFile() : url.path());
0298 
0299     QCOMPARE(optUrl.parent().toUrl(), comparableUpUrl(url));
0300 
0301     QVERIFY(optUrl.parent().isDirectParentOf(optUrl));
0302     QVERIFY(!optUrl.parent().parent().isDirectParentOf(optUrl));
0303 
0304 #ifndef Q_OS_WIN
0305     Path a(QStringLiteral("/foo/bar/asdf/"));
0306     Path b(QStringLiteral("/foo/bar/"));
0307     QVERIFY(b.isDirectParentOf(a));
0308     Path c(QStringLiteral("/foo/bar"));
0309     QVERIFY(c.isDirectParentOf(a));
0310 #endif
0311 
0312     optUrl = Path{};
0313     url.clear();
0314     QCOMPARE(optUrl.toUrl(), url);
0315 }
0316 
0317 void TestPath::testPath_data()
0318 {
0319     QTest::addColumn<QString>("input");
0320 
0321 #ifndef Q_OS_WIN
0322     QTest::newRow("invalid") << "";
0323     QTest::newRow("path") << "/tmp/foo/asdf.txt";
0324     QTest::newRow("path-folder") << "/tmp/foo/asdf/";
0325     QTest::newRow("root") << "/";
0326     QTest::newRow("clean-path") << "/tmp/..///asdf/";
0327     QTest::newRow("file") << "file:///tmp/foo/asdf.txt";
0328     QTest::newRow("file-folder") << "file:///tmp/foo/bar/";
0329 #else
0330     QTest::newRow("path") << "C:/tmp/foo/asdf.txt";
0331     QTest::newRow("path-folder") << "C:/tmp/foo/asdf/";
0332     QTest::newRow("root") << "C:/";
0333     QTest::newRow("clean-path") << "C:/tmp/..///asdf/";
0334     QTest::newRow("file") << "file:///C:/tmp/foo/asdf.txt";
0335     QTest::newRow("file-folder") << "file:///C:/tmp/foo/bar/";
0336 #endif
0337     QTest::newRow("remote-root") << "http://www.test.com/";
0338     QTest::newRow("http") << "http://www.test.com/tmp/asdf.txt";
0339     QTest::newRow("ftps") << "ftps://user@host.com/tmp/foo/asdf.txt";
0340     QTest::newRow("password") << "ftps://user:password@host.com/tmp/asdf.txt";
0341     QTest::newRow("port") << "http://localhost:8080/foo/bar/test.txt";
0342 }
0343 
0344 void TestPath::testPathInvalid()
0345 {
0346     QFETCH(QString, input);
0347     Path url(input);
0348     QVERIFY(!url.isValid());
0349     QVERIFY(url.isEmpty());
0350 }
0351 
0352 void TestPath::testPathInvalid_data()
0353 {
0354     QTest::addColumn<QString>("input");
0355 
0356     QTest::newRow("empty") << "";
0357     QTest::newRow("fragment") << "http://test.com/#hello";
0358     QTest::newRow("query") << "http://test.com/?hello";
0359     QTest::newRow("suburl") << "file:///home/weis/kde.tgz#gzip:/#tar:/kdebase";
0360     QTest::newRow("relative") << "../foo/bar";
0361     QTest::newRow("name") << "asdfasdf";
0362     QTest::newRow("remote-nopath") << "http://www.test.com";
0363 }
0364 
0365 void TestPath::testPathComparison()
0366 {
0367     QFETCH(Path, left);
0368     QFETCH(Path, right);
0369     QFETCH(int, leftCompareRight);
0370     QFETCH(int, leftCompareRightCi);
0371 
0372     const bool equal = leftCompareRight == 0;
0373     const bool less = leftCompareRight < 0;
0374     const bool greater = leftCompareRight > 0;
0375 
0376     QVERIFY(left == left);
0377     QVERIFY(right == right);
0378     QCOMPARE(left == right, equal);
0379     QCOMPARE(right == left, equal);
0380 
0381     QVERIFY(!(left != left));
0382     QVERIFY(!(right != right));
0383     QCOMPARE(left != right, !equal);
0384     QCOMPARE(right != left, !equal);
0385 
0386     QCOMPARE(left < right, less);
0387     QCOMPARE(left <= right, less || equal);
0388     QCOMPARE(left > right, greater);
0389     QCOMPARE(left >= right, greater || equal);
0390 
0391     QCOMPARE(right < left, greater);
0392     QCOMPARE(right <= left, greater || equal);
0393     QCOMPARE(right > left, less);
0394     QCOMPARE(right >= left, less || equal);
0395 
0396     QCOMPARE(left.compare(left), 0);
0397     QCOMPARE(right.compare(right), 0);
0398     QCOMPARE(left.compare(right) < 0, leftCompareRight < 0);
0399     QCOMPARE(right.compare(left) < 0, leftCompareRight > 0);
0400 
0401     QCOMPARE(left.compare(left, Qt::CaseSensitive), 0);
0402     QCOMPARE(right.compare(right, Qt::CaseSensitive), 0);
0403     QCOMPARE(left.compare(right, Qt::CaseSensitive) < 0, leftCompareRight < 0);
0404     QCOMPARE(right.compare(left, Qt::CaseSensitive) < 0, leftCompareRight > 0);
0405 
0406     QCOMPARE(left.compare(left, Qt::CaseInsensitive), 0);
0407     QCOMPARE(right.compare(right, Qt::CaseInsensitive), 0);
0408     QCOMPARE(left.compare(right, Qt::CaseInsensitive) < 0, leftCompareRightCi < 0);
0409     QCOMPARE(right.compare(left, Qt::CaseInsensitive) < 0, leftCompareRightCi > 0);
0410 }
0411 
0412 void TestPath::testPathComparison_data()
0413 {
0414     QTest::addColumn<Path>("left");
0415     QTest::addColumn<Path>("right");
0416     QTest::addColumn<int>("leftCompareRight");
0417     QTest::addColumn<int>("leftCompareRightCi");
0418 
0419     Path a(QStringLiteral("/tmp/a"));
0420     Path b(QStringLiteral("/tmp/b"));
0421     Path c(QStringLiteral("/tmp/ac"));
0422     Path d(QStringLiteral("/d"));
0423     Path e(QStringLiteral("/tmp"));
0424     Path f(QStringLiteral("/tmp/"));
0425     Path invalid;
0426 
0427     QTest::newRow("a-b") << a << b << -1 << -1;
0428     QTest::newRow("a-copy") << a << Path(a) << 0 << 0;
0429     QTest::newRow("c-a") << c << a << 1 << 1;
0430     QTest::newRow("c-invalid") << c << invalid << 1 << 1;
0431     QTest::newRow("c-d") << c << d << 1 << 1;
0432     QTest::newRow("e-f") << e << f << 0 << 0;
0433 
0434     Path A(QStringLiteral("/tmp/A"));
0435     Path B(QStringLiteral("/tmp/B"));
0436     Path C(QStringLiteral("/tmp/aC"));
0437     Path D(QStringLiteral("/D"));
0438     Path E(QStringLiteral("/TMP"));
0439     Path F(QStringLiteral("/TmP/F"));
0440 
0441     QTest::newRow("a-A") << a << A << 1 << 0;
0442     QTest::newRow("a-B") << a << B << 1 << -1;
0443     QTest::newRow("A-b") << A << b << -1 << -1;
0444     QTest::newRow("A-C") << A << C << -1 << -1;
0445     QTest::newRow("c-C") << c << C << 1 << 0;
0446     QTest::newRow("d-D") << d << D << 1 << 0;
0447     QTest::newRow("F-A") << F << A << -1 << 1;
0448     QTest::newRow("f-E") << f << E << 1 << 0;
0449     QTest::newRow("E-F") << E << F << -1 << -1;
0450 }
0451 
0452 void TestPath::testPathSwap()
0453 {
0454     Path a(QStringLiteral("/foo/22/x.C"));
0455     Path b(QStringLiteral("/home/bar/at.7z"));
0456     QVERIFY(!(a == b));
0457 
0458     const auto aCopy = a;
0459     const auto bCopy = b;
0460     QCOMPARE(aCopy, a);
0461     QCOMPARE(bCopy, b);
0462 
0463     using std::swap;
0464     swap(a, b);
0465     QCOMPARE(a, bCopy);
0466     QCOMPARE(b, aCopy);
0467 }
0468 
0469 void TestPath::testPathAddData()
0470 {
0471     QFETCH(QString, pathToAdd);
0472 
0473     const QStringList bases = {
0474 #ifndef Q_OS_WIN
0475         QStringLiteral("/"),
0476         QStringLiteral("/foo/bar/asdf/"),
0477         QStringLiteral("file:///foo/bar/asdf/"),
0478 #else
0479         "C:/",
0480         "C:/foo/bar/asdf/",
0481         "file:///C:/foo/bar/asdf/",
0482 #endif
0483         QStringLiteral("http://www.asdf.com/foo/bar/asdf/"),
0484     };
0485 
0486     for (const QString& base : bases) {
0487         QUrl baseUrl = QUrl::fromUserInput(base);
0488         if (QDir::isRelativePath(pathToAdd)) {
0489             baseUrl = resolvedUrl(baseUrl, QUrl(pathToAdd));
0490         } else if (QDir::isRelativePath(pathToAdd) || baseUrl.path() != QLatin1String("/")) {
0491             // if pathToAdd == /absolute && baseUrl == "/", below call would lead to an invalid QUrl
0492             // with qtbase.git/f62768d046528636789f901ac79e2cfa1843a7b7
0493             baseUrl.setPath(baseUrl.path() + pathToAdd);
0494         } else {
0495             baseUrl.setPath(pathToAdd);
0496         }
0497 
0498         baseUrl = baseUrl.adjusted(QUrl::NormalizePathSegments);
0499         if (baseUrl.path().contains(QLatin1String("//"))) {
0500             // odd, this should have been normalized, no?
0501             auto path = baseUrl.path();
0502             path.replace(QLatin1String("//"), QLatin1String("/"));
0503             baseUrl.setPath(path);
0504         }
0505         // QUrl::StripTrailingSlash converts file:/// to file: which is not what we want
0506         if (baseUrl.path() != QLatin1String("/")) {
0507             baseUrl = baseUrl.adjusted(QUrl::StripTrailingSlash);
0508         }
0509 
0510         Path basePath(base);
0511         basePath.addPath(pathToAdd);
0512 
0513         QCOMPARE(basePath.pathOrUrl(), toUrlOrLocalFile(baseUrl));
0514         QCOMPARE(basePath.toUrl(), baseUrl);
0515     }
0516 }
0517 
0518 void TestPath::testPathAddData_data()
0519 {
0520     QTest::addColumn<QString>("pathToAdd");
0521 
0522     const QStringList paths = QStringList()
0523                               << QStringLiteral("file.txt")
0524                               << QStringLiteral("path/file.txt")
0525                               << QStringLiteral("path//file.txt")
0526                               << QStringLiteral("/absolute")
0527                               << QStringLiteral("../")
0528                               << QStringLiteral("..")
0529                               << QStringLiteral("../../../")
0530                               << QStringLiteral("./foo")
0531                               << QStringLiteral("../relative")
0532                               << QStringLiteral("../../relative")
0533                               << QStringLiteral("../foo/../bar")
0534                               << QStringLiteral("../foo/./bar")
0535                               << QStringLiteral("../../../../../../../invalid");
0536     for (const QString& path : paths) {
0537         QTest::addRow("%s", qPrintable(path)) << path;
0538     }
0539 }
0540 
0541 void TestPath::testPathBaseCtor()
0542 {
0543     QFETCH(QString, base);
0544     QFETCH(QString, subPath);
0545     QFETCH(QString, expected);
0546 
0547     const Path basePath(base);
0548 
0549     const Path path(basePath, subPath);
0550 
0551     QCOMPARE(path.pathOrUrl(), expected);
0552 }
0553 
0554 void TestPath::testPathBaseCtor_data()
0555 {
0556     QTest::addColumn<QString>("base");
0557     QTest::addColumn<QString>("subPath");
0558     QTest::addColumn<QString>("expected");
0559 
0560     QTest::newRow("empty") << "" << "" << "";
0561     QTest::newRow("empty-relative") << "" << "foo" << "";
0562 #ifndef Q_OS_WIN
0563     QTest::newRow("root-empty") << "/" << "" << "/";
0564     QTest::newRow("root-root") << "/" << "/" << "/";
0565     QTest::newRow("root-relative") << "/" << "bar" << "/bar";
0566     QTest::newRow("root-relative-dirty") << "/" << "bar//foo/a/.." << "/bar/foo";
0567     QTest::newRow("path-relative") << "/foo/bar" << "bar/foo" << "/foo/bar/bar/foo";
0568     QTest::newRow("path-absolute") << "/foo/bar" << "/bar/foo" << "/bar/foo";
0569 #else
0570     QTest::newRow("root-empty") << "C:/" << "" << "C:";
0571     QTest::newRow("root1-empty") << "C:" << "" << "C:";
0572     QTest::newRow("root-root") << "C:/" << "C:/" << "C:";
0573     QTest::newRow("root-relative") << "C:/" << "bar" << "C:/bar";
0574     QTest::newRow("root1-relative") << "C:" << "bar" << "C:/bar";
0575     QTest::newRow("root-relative-dirty") << "C:/" << "bar//foo/a/.." << "C:/bar/foo";
0576     QTest::newRow("path-relative") << "C:/foo/bar" << "bar/foo" << "C:/foo/bar/bar/foo";
0577     QTest::newRow("path-absolute") << "C:/foo/bar" << "C:/bar/foo" << "C:/bar/foo";
0578 #endif
0579     QTest::newRow("remote-path-absolute") << "http://foo.com/foo/bar" << "/bar/foo" << "http://foo.com/bar/foo";
0580     QTest::newRow("remote-path-relative") << "http://foo.com/foo/bar" << "bar/foo" << "http://foo.com/foo/bar/bar/foo";
0581 }
0582 
0583 // there is no cd() in QUrl, emulate what KUrl did
0584 static bool cdQUrl(QUrl& url, const QString& path)
0585 {
0586     if (path.isEmpty() || !url.isValid()) {
0587         return false;
0588     }
0589     // have to append slash otherwise last segment is treated as a file name and not a directory
0590     if (!url.path().endsWith('/')) {
0591         url.setPath(url.path() + '/');
0592     }
0593     url = url.resolved(QUrl(path)).adjusted(QUrl::RemoveFragment | QUrl::RemoveQuery);
0594     return true;
0595 }
0596 
0597 void TestPath::testPathCd()
0598 {
0599     QFETCH(QString, base);
0600     QFETCH(QString, change);
0601 
0602     Path path = base.isEmpty() ? Path() : Path(base);
0603     QUrl url = QUrl::fromUserInput(base);
0604 
0605     Path changed = path.cd(change);
0606     if (cdQUrl(url, change)) {
0607         QVERIFY(changed.isValid());
0608     }
0609     url = url.adjusted(QUrl::NormalizePathSegments);
0610 
0611     QCOMPARE(changed.pathOrUrl(), toUrlOrLocalFile(url, QUrl::StripTrailingSlash));
0612 }
0613 
0614 void TestPath::testPathCd_data()
0615 {
0616     QTest::addColumn<QString>("base");
0617     QTest::addColumn<QString>("change");
0618 
0619     const QVector<QString> bases{
0620         QString(),
0621 #ifndef Q_OS_WIN
0622         QStringLiteral("/foo"), QStringLiteral("/foo/bar/asdf"),
0623 #else
0624         "C:/foo", "C:/foo/bar/asdf",
0625 #endif
0626         QStringLiteral("http://foo.com/"), QStringLiteral("http://foo.com/foo"), QStringLiteral(
0627             "http://foo.com/foo/bar/asdf")
0628     };
0629 
0630     const QVector<QString> suffixes {
0631         QString(),
0632         QStringLiteral(".."),
0633         QStringLiteral("../"),
0634         QStringLiteral("../foo"),
0635         QStringLiteral("."),
0636         QStringLiteral("./"),
0637         QStringLiteral("./foo"),
0638         QStringLiteral("./foo/bar"),
0639         QStringLiteral("./foo/.."),
0640         QStringLiteral("./foo/"),
0641         QStringLiteral("./foo/../bar"),
0642     };
0643 
0644     const QVector<QString> extraSuffixes {
0645         QStringLiteral("/foo"),
0646         QStringLiteral("/foo/../bar"),
0647     };
0648 
0649     for (const QString& base : bases) {
0650         for (const auto& suffix : suffixes) {
0651             QTest::addRow("%s-%s", qPrintable(base), qPrintable(suffix)) << base << suffix;
0652         }
0653 #ifdef Q_OS_WIN
0654         // only add next rows for remote URLs on Windows
0655         if (base.startsWith("C:/")) {
0656             continue;
0657         }
0658 #endif
0659         for (const auto& suffix : extraSuffixes) {
0660             QTest::addRow("%s-%s", qPrintable(base), qPrintable(suffix)) << base << suffix;
0661         }
0662     }
0663 }
0664 
0665 void TestPath::testHasParent_data()
0666 {
0667     QTest::addColumn<QString>("input");
0668     QTest::addColumn<bool>("hasParent");
0669 
0670     QTest::newRow("empty") << QString() << false;
0671 #ifdef Q_OS_WIN
0672     QTest::newRow("\\") << QStringLiteral("\\") << true; // true b/c parent could be e.g. 'C:'
0673 #else
0674     QTest::newRow("/") << QStringLiteral("/") << false;
0675     QTest::newRow("/foo") << QStringLiteral("/foo") << true;
0676     QTest::newRow("/foo/bar") << QStringLiteral("/foo/bar") << true;
0677     QTest::newRow("//foo/bar") << QStringLiteral("//foo/bar") << true;
0678 #endif
0679     QTest::newRow("http://foo.bar") << QStringLiteral("http://foo.bar") << false;
0680     QTest::newRow("http://foo.bar/") << QStringLiteral("http://foo.bar/") << false;
0681     QTest::newRow("http://foo.bar/asdf") << QStringLiteral("http://foo.bar/asdf") << true;
0682     QTest::newRow("http://foo.bar/asdf/asdf") << QStringLiteral("http://foo.bar/asdf/asdf") << true;
0683 }
0684 
0685 void TestPath::testHasParent()
0686 {
0687     QFETCH(QString, input);
0688     Path path(input);
0689     QTEST(path.hasParent(), "hasParent");
0690 }
0691 
0692 void TestPath::QUrl_acceptance()
0693 {
0694     const QUrl baseLocal = QUrl(QStringLiteral("file:///foo.h"));
0695     QCOMPARE(baseLocal.isValid(), true);
0696     QCOMPARE(baseLocal.isRelative(), false);
0697     QCOMPARE(baseLocal, QUrl::fromLocalFile(QStringLiteral("/foo.h")));
0698     QCOMPARE(baseLocal, QUrl::fromUserInput(QStringLiteral("/foo.h")));
0699 
0700     QUrl relative(QStringLiteral("bar.h"));
0701     QCOMPARE(relative.isRelative(), true);
0702     QCOMPARE(baseLocal.resolved(relative), QUrl(QStringLiteral("file:///bar.h")));
0703     QUrl relative2(QStringLiteral("/foo/bar.h"));
0704     QCOMPARE(relative2.isRelative(), true);
0705     QCOMPARE(baseLocal.resolved(relative2), QUrl(QStringLiteral("file:///foo/bar.h")));
0706 
0707     const QUrl baseRemote = QUrl(QStringLiteral("http://foo.org/asdf/foo/asdf"));
0708     QCOMPARE(baseRemote.resolved(relative), QUrl(QStringLiteral("http://foo.org/asdf/foo/bar.h")));
0709 }
0710 
0711 #include "moc_test_path.cpp"