diff --git a/project/path.cpp b/project/path.cpp index 532904fe67..55f40801e5 100644 --- a/project/path.cpp +++ b/project/path.cpp @@ -1,413 +1,417 @@ /* * This file is part of KDevelop * Copyright 2012 Milian Wolff * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "path.h" #include #include #include +#include using namespace KDevelop; Path::Path() { } Path::Path(const QString& pathOrUrl) { init(KUrl(pathOrUrl)); } Path::Path(const KUrl& url) { init(url); } void Path::init(KUrl url) { // we do not support urls with: // - fragments // - sub urls // - query // nor do we support relative urls if (!url.isValid() || url.hasFragment() || url.hasQuery() || url.hasSubUrl() || url.isRelative() || !url.hasPath()) { // invalid qWarning("Path::init: invalid/unsupported Path encountered: \"%s\"", qPrintable(url.pathOrUrl())); return; } // remove /../ parts url.cleanPath(); // get the path as segmented list QStringList path = url.path(KUrl::RemoveTrailingSlash).split('/', QString::SkipEmptyParts); if (!url.isLocalFile()) { // handle remote urls QString urlPrefix; urlPrefix += url.protocol(); urlPrefix += "://"; if (url.hasUser()) { urlPrefix += url.user(); urlPrefix += '@'; } urlPrefix += url.host(); if (url.port() != -1) { urlPrefix += ':' + QString::number(url.port()); } path.prepend(urlPrefix); } m_data = path.toVector(); // support for root paths, they are valid but don't really contain any data if (m_data.isEmpty() || (isRemote() && m_data.size() == 1)) { m_data << QString(); } } Path::Path(const Path& other, const QString& child) : m_data(other.m_data) { if (child.startsWith('/')) { // absolute path: only share the remote part of @p other m_data.resize(isRemote() ? 1 : 0); } addPath(child); } static QString generatePathOrUrl(bool onlyPath, bool isLocalFile, const QVector& data) { // more or less a copy of QtPrivate::QStringList_join const int size = data.size(); if (size == 0) { return QString(); } int totalLength = 0; // separators: '/' totalLength += size; // skip Path segment if we only want the path const int start = (onlyPath && !isLocalFile) ? 1 : 0; // path and url prefix for (int i = start; i < size; ++i) { totalLength += data.at(i).size(); } // build string representation QString res; res.reserve(totalLength); for (int i = start; i < size; ++i) { if (i || isLocalFile) { res += '/'; } res += data.at(i); } return res; } QString Path::pathOrUrl() const { return generatePathOrUrl(false, isLocalFile(), m_data); } QString Path::path() const { return generatePathOrUrl(true, isLocalFile(), m_data); } QString Path::toLocalFile() const { return isLocalFile() ? path() : QString(); } QString Path::relativePath(const Path& path) const { if (!path.isValid()) { return QString(); } if (!isValid() || remotePrefix() != path.remotePrefix()) { // different remote destinations or we are invalid, return input as-is return path.pathOrUrl(); } // while I'd love to use KUrl::relativePath here, it seems to behave pretty // strangely, and adds unexpected "./" at the start for example // so instead, do it on our own based on _relativePath in kurl.cpp // this should also be more performant I think // Find where they meet int level = isRemote() ? 1 : 0; const int maxLevel = qMin(m_data.count(), path.m_data.count()); while(level < maxLevel && m_data.at(level) == path.m_data.at(level)) { ++level; } // Need to go down out of our path to the common branch. // but keep in mind that e.g. '/' paths have an empty name int backwardSegments = m_data.count() - level; if (backwardSegments && level < maxLevel && m_data.at(level).isEmpty()) { --backwardSegments; } // Now up up from the common branch to the second path. int forwardSegmentsLength = 0; for (int i = level; i < path.m_data.count(); ++i) { forwardSegmentsLength += path.m_data.at(i).length(); // slashes if (i + 1 != path.m_data.count()) { forwardSegmentsLength += 1; } } QString relativePath; relativePath.reserve((backwardSegments * 3) + forwardSegmentsLength); for(int i = 0; i < backwardSegments; ++i) { relativePath.append(QLatin1String("../")); } for (int i = level; i < path.m_data.count(); ++i) { relativePath.append(path.m_data.at(i)); if (i + 1 != path.m_data.count()) { relativePath.append(QLatin1Char('/')); } } Q_ASSERT(relativePath.length() == ((backwardSegments * 3) + forwardSegmentsLength)); return relativePath; } static bool isParentPath(const QVector& parent, const QVector& child, bool direct) { if (direct && child.size() != parent.size() + 1) { return false; } else if (!direct && child.size() <= parent.size()) { return false; } for (int i = 0; i < parent.size(); ++i) { if (child.at(i) != parent.at(i)) { // support for trailing '/' if (i + 1 == parent.size() && parent.at(i).isEmpty()) { return true; } // otherwise we take a different branch here return false; } } return true; } bool Path::isParentOf(const Path& path) const { if (!isValid() || !path.isValid() || remotePrefix() != path.remotePrefix()) { return false; } return isParentPath(m_data, path.m_data, false); } bool Path::isDirectParentOf(const Path& path) const { if (!isValid() || !path.isValid() || remotePrefix() != path.remotePrefix()) { return false; } return isParentPath(m_data, path.m_data, true); } QString Path::remotePrefix() const { return isRemote() ? m_data.first() : QString(); } bool Path::operator<(const Path& other) const { const int size = m_data.size(); const int otherSize = other.m_data.size(); const int toCompare = qMin(size, otherSize); // compare each Path segment in turn and try to return early for (int i = 0; i < toCompare; ++i) { int comparison = m_data.at(i).compare(other.m_data.at(i)); if (comparison == 0) { // equal, try next segment continue; } else { // return whether our segment is less then the other one return comparison < 0; } } // when we reach this point, all elements that we compared where equal // thus return whether we have less items than the other Path return size < otherSize; } // NOTE: If we'd introduce an IndexedPath this could maybe be optimized IndexedString Path::toIndexed() const { return IndexedString(pathOrUrl()); } KUrl Path::toUrl() const { return KUrl(pathOrUrl()); } QString Path::lastPathSegment() const { // remote Paths are offset by one, thus never return the first item of them as file name if (m_data.isEmpty() || (!isLocalFile() && m_data.size() == 1)) { return QString(); } return m_data.last(); } void Path::setLastPathSegment(const QString& name) { // remote Paths are offset by one, thus never return the first item of them as file name if (m_data.isEmpty() || (!isLocalFile() && m_data.size() == 1)) { // append the name to empty Paths or remote Paths only containing the Path prefix m_data.append(name); } else { // overwrite the last data member m_data.last() = name; } } static void cleanPath(QVector* data, const bool isRemote) { if (data->isEmpty()) { return; } QString* it = data->begin(); const int startOffset = isRemote ? 1 : 0; it += startOffset; while(it != data->end()) { if (*it == QLatin1String("..")) { if (it == (data->begin() + startOffset)) { it = data->erase(it); } else { it = data->erase(it - 1, it + 1); } } else if (*it == QLatin1String(".")) { it = data->erase(it); } else { ++it; } } if (data->count() == startOffset) { data->append(QString()); } } void Path::addPath(const QString& path) { if (path.isEmpty()) { return; } QStringList newData = path.split('/', QString::SkipEmptyParts); if (newData.isEmpty()) { if (m_data.size() == (isRemote() ? 1 : 0)) { // this represents the root path, we just turned an invalid path into it m_data << QString(); } return; } if (!m_data.isEmpty() && m_data.last().isEmpty()) { // the root item is empty, set its contents and continue appending m_data.last() = newData.takeFirst(); } m_data += newData.toVector(); cleanPath(&m_data, isRemote()); } Path Path::parent() const { if (m_data.isEmpty()) { return Path(); } Path ret(*this); if (m_data.size() == (1 + (isRemote() ? 1 : 0))) { // keep the root item, but clear it, otherwise we'd make the path invalid // or a URL a local path ret.m_data.last().clear(); } else { ret.m_data.pop_back(); } return ret; } void Path::clear() { m_data.clear(); } Path Path::cd(const QString& dir) const { if (!isValid()) { return Path(); } return Path(*this, dir); } namespace KDevelop { uint qHash(const Path& path) { - // TODO: optimize using RunningHash - return qHash(path.pathOrUrl()); + KDevHash hash; + foreach (const QString& segment, path.segments()) { + hash << qHash(segment); + } + return hash; } Path::List toPathList(const KUrl::List& list) { Path::List ret; ret.reserve(list.size()); foreach(const KUrl& url, list) { Path path(url); if (path.isValid()) { ret << path; } } return ret; } } QDebug operator<<(QDebug s, const Path& string) { s.nospace() << string.pathOrUrl(); return s.space(); } namespace QTest { template<> char *toString(const Path &path) { return qstrdup(qPrintable(path.pathOrUrl())); } } diff --git a/project/tests/testpath.cpp b/project/tests/testpath.cpp index 727e2ef94c..d54a541134 100644 --- a/project/tests/testpath.cpp +++ b/project/tests/testpath.cpp @@ -1,531 +1,540 @@ /* * This file is part of KDevelop * Copyright 2012 Milian Wolff * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "testpath.h" #include #include #include #include #include #include QTEST_MAIN(TestPath); using namespace KDevelop; static const int FILES_PER_FOLDER = 10; static const int FOLDERS_PER_FOLDER = 5; static const int TREE_DEPTH = 5; namespace QTest { template<> char *toString(const KUrl &url) { return qstrdup(qPrintable(url.pathOrUrl())); } } template T stringToUrl(const QString& path) { return T(path); } template<> QStringList stringToUrl(const QString& path) { return path.split('/'); } template T childUrl(const T& parent, const QString& child) { return T(parent, child); } template<> QStringList childUrl(const QStringList& parent, const QString& child) { QStringList ret = parent; ret << child; return ret; } template<> QUrl childUrl(const QUrl& parent, const QString& child) { QUrl ret = parent; ret.setPath(ret.path() + '/' + child); return ret; } template<> KUrl childUrl(const KUrl& parent, const QString& child) { KUrl ret = parent; ret.addPath(child); return ret; } template QVector generateData(const T& parent, int level) { QVector ret; // files per folder for (int i = 0; i < FILES_PER_FOLDER; ++i) { const QString fileName = QString("file%1.txt").arg(i); const T file = childUrl(parent, fileName); Q_ASSERT(!ret.contains(file)); ret << file; } // nesting depth if (level < TREE_DEPTH) { // folders per folder for (int i = 0; i < FOLDERS_PER_FOLDER; ++i) { const QString folderName = QString("folder%1").arg(i); const T folder = childUrl(parent, folderName); Q_ASSERT(!ret.contains(folder)); ret << folder; ret += generateData(folder, level + 1); } } return ret; } template void runBenchmark() { QBENCHMARK { const T base = stringToUrl("/tmp/foo/bar"); generateData(base, 0); } } void TestPath::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); } void TestPath::cleanupTestCase() { TestCore::shutdown(); } void TestPath::bench_kurl() { runBenchmark(); } void TestPath::bench_qurl() { runBenchmark(); } void TestPath::bench_qstringlist() { runBenchmark(); } void TestPath::bench_path() { runBenchmark(); } void TestPath::bench_fromLocalPath() { QFETCH(int, variant); const QString input("/foo/bar/asdf/bla/blub.h"); const int repeat = 1000; if (variant == 1) { QBENCHMARK { for(int i = 0; i < repeat; ++i) { Path path = Path(KUrl(input)); Q_UNUSED(path); } } } else if (variant == 2) { QBENCHMARK { for(int i = 0; i < repeat; ++i) { Path path = Path(KUrl::fromPath(input)); Q_UNUSED(path); } } } else if (variant == 3) { QBENCHMARK { for(int i = 0; i < repeat; ++i) { Path path = Path(QUrl::fromLocalFile(input)); Q_UNUSED(path); } } } else { QFAIL("unexpected variant"); } } void TestPath::bench_fromLocalPath_data() { QTest::addColumn("variant"); QTest::newRow("KUrl::KUrl") << 1; QTest::newRow("KUrl::fromPath") << 2; QTest::newRow("QUrl::fromLocalFile") << 3; } +void TestPath::bench_hash() +{ + const Path path("/my/very/long/path/to/a/file.cpp"); + QBENCHMARK { + auto hash = qHash(path); + Q_UNUSED(hash); + } +} + KUrl comparableUpUrl(const KUrl& url) { KUrl ret = url.upUrl(); ret.adjustPath(KUrl::RemoveTrailingSlash); if (ret.hasPass()) { ret.setPass(QString()); } return ret; } void TestPath::testPath() { QFETCH(QString, input); KUrl url(input); url.cleanPath(); url.adjustPath(KUrl::RemoveTrailingSlash); Path optUrl(input); if (url.hasPass()) { KUrl urlNoPass = url; urlNoPass.setPass(QString()); QCOMPARE(optUrl.toUrl(), urlNoPass); } else { QCOMPARE(optUrl.toUrl(), url); } QCOMPARE(optUrl.isLocalFile(), url.isLocalFile()); QCOMPARE(optUrl.pathOrUrl(), url.pathOrUrl()); QCOMPARE(optUrl.isValid(), url.isValid()); QCOMPARE(optUrl.lastPathSegment(), url.fileName()); QCOMPARE(optUrl.path(), url.path()); QCOMPARE(optUrl.parent().toUrl(), comparableUpUrl(url)); QCOMPARE(optUrl.toLocalFile(), url.toLocalFile()); QCOMPARE(optUrl, Path(input)); QCOMPARE(optUrl, Path(optUrl)); QVERIFY(optUrl != Path(input + "/asdf")); if (url.isLocalFile() && !input.startsWith("file://")) { QCOMPARE(optUrl, Path(KUrl::fromPath(input))); } QCOMPARE(optUrl.toIndexed(), IndexedString(url)); QCOMPARE(optUrl, Path(url)); if (url.isValid()) { QVERIFY(optUrl.relativePath(optUrl).isEmpty()); Path relativePath(optUrl, "foo/bar"); QCOMPARE(optUrl.relativePath(relativePath), QLatin1String("foo/bar")); QCOMPARE(relativePath.relativePath(optUrl), QLatin1String("../../")); QVERIFY(optUrl.isParentOf(relativePath)); QVERIFY(!relativePath.isParentOf(optUrl)); Path absolutePath(optUrl, "/laa/loo"); QCOMPARE(absolutePath.path(), QLatin1String("/laa/loo")); QCOMPARE(KUrl(url, "/laa/loo").path(), QLatin1String("/laa/loo")); Path absolutePath2(optUrl, "/"); QCOMPARE(absolutePath2.path(), QLatin1String("/")); QCOMPARE(KUrl(url, "/").path(), QLatin1String("/")); Path unrelatedPath("https://test@blubasdf.com:12345/"); QCOMPARE(optUrl.relativePath(unrelatedPath), unrelatedPath.pathOrUrl()); QCOMPARE(unrelatedPath.relativePath(optUrl), optUrl.pathOrUrl()); QVERIFY(!unrelatedPath.isParentOf(optUrl)); QVERIFY(!optUrl.isParentOf(unrelatedPath)); } QCOMPARE(Path().relativePath(optUrl), optUrl.pathOrUrl()); QVERIFY(optUrl.relativePath(Path()).isEmpty()); QVERIFY(Path().relativePath(Path()).isEmpty()); QVERIFY(!optUrl.isParentOf(Path())); QVERIFY(!Path().isParentOf(optUrl)); QVERIFY(!Path().isParentOf(Path())); QVERIFY(!optUrl.isParentOf(optUrl)); QCOMPARE(optUrl.isRemote(), optUrl.isValid() && !optUrl.isLocalFile()); QCOMPARE(optUrl.isRemote(), optUrl.isValid() && !optUrl.remotePrefix().isEmpty()); url.addPath("test/foo/bar"); optUrl.addPath("test/foo/bar"); QCOMPARE(optUrl.lastPathSegment(), url.fileName()); QCOMPARE(optUrl.path(), url.path()); url.setFileName("lalalala_adsf.txt"); optUrl.setLastPathSegment("lalalala_adsf.txt"); QCOMPARE(optUrl.lastPathSegment(), url.fileName()); QCOMPARE(optUrl.path(), url.path()); QCOMPARE(optUrl.parent().toUrl(), comparableUpUrl(url)); QVERIFY(optUrl.parent().isDirectParentOf(optUrl)); QVERIFY(!optUrl.parent().parent().isDirectParentOf(optUrl)); Path a("/foo/bar/asdf/"); Path b("/foo/bar/"); QVERIFY(b.isDirectParentOf(a)); Path c("/foo/bar"); QVERIFY(c.isDirectParentOf(a)); optUrl.clear(); url.clear(); QCOMPARE(optUrl.toUrl(), url); } void TestPath::testPath_data() { QTest::addColumn("input"); QTest::ignoreMessage(QtWarningMsg, "Path::init: invalid/unsupported Path encountered: \"\""); QTest::ignoreMessage(QtWarningMsg, "Path::init: invalid/unsupported Path encountered: \"\""); QTest::ignoreMessage(QtWarningMsg, "Path::init: invalid/unsupported Path encountered: \"\""); QTest::newRow("invalid") << ""; QTest::newRow("path") << "/tmp/foo/asdf.txt"; QTest::newRow("path-folder") << "/tmp/foo/asdf/"; QTest::newRow("root") << "/"; QTest::newRow("clean-path") << "/tmp/..///asdf/"; QTest::newRow("remote-root") << "http://www.test.com/"; QTest::newRow("http") << "http://www.test.com/tmp/asdf.txt"; QTest::newRow("file") << "file:///tmp/foo/asdf.txt"; QTest::newRow("file-folder") << "file:///tmp/foo/bar/"; QTest::newRow("ftps") << "ftps://user@host.com/tmp/foo/asdf.txt"; QTest::newRow("password") << "ftps://user:password@host.com/tmp/asdf.txt"; QTest::newRow("port") << "http://localhost:8080/foo/bar/test.txt"; } void TestPath::testPathInvalid() { QFETCH(QString, input); Path url(input); QVERIFY(!url.isValid()); } void TestPath::testPathInvalid_data() { QTest::addColumn("input"); QTest::ignoreMessage(QtWarningMsg, "Path::init: invalid/unsupported Path encountered: \"\""); QTest::newRow("empty") << ""; QTest::ignoreMessage(QtWarningMsg, "Path::init: invalid/unsupported Path encountered: \"http://test.com/#hello\""); QTest::newRow("fragment") << "http://test.com/#hello"; QTest::ignoreMessage(QtWarningMsg, "Path::init: invalid/unsupported Path encountered: \"http://test.com/?hello\""); QTest::newRow("query") << "http://test.com/?hello"; QTest::ignoreMessage(QtWarningMsg, "Path::init: invalid/unsupported Path encountered: \"file:///home/weis/kde.tgz#gzip:/%23tar:/kdebase\""); QTest::newRow("suburl") << "file:///home/weis/kde.tgz#gzip:/#tar:/kdebase"; QTest::ignoreMessage(QtWarningMsg, "Path::init: invalid/unsupported Path encountered: \"../foo/bar\""); QTest::newRow("relative") << "../foo/bar"; QTest::ignoreMessage(QtWarningMsg, "Path::init: invalid/unsupported Path encountered: \"asdfasdf\""); QTest::newRow("name") << "asdfasdf"; QTest::ignoreMessage(QtWarningMsg, "Path::init: invalid/unsupported Path encountered: \"http://www.test.com\""); QTest::newRow("remote-nopath") << "http://www.test.com"; } void TestPath::testPathOperators() { QFETCH(Path, left); QFETCH(Path, right); QFETCH(bool, equal); QFETCH(bool, less); bool greater = !equal && !less; QVERIFY(left == left); QVERIFY(right == right); QCOMPARE(left == right, equal); QCOMPARE(right == left, equal); QCOMPARE(left < right, less); QCOMPARE(left <= right, less || equal); QCOMPARE(left > right, greater); QCOMPARE(left >= right, greater || equal); QCOMPARE(right < left, greater); QCOMPARE(right <= left, greater || equal); QCOMPARE(right > left, less); QCOMPARE(right >= left, less || equal); } void TestPath::testPathOperators_data() { QTest::addColumn("left"); QTest::addColumn("right"); QTest::addColumn("equal"); QTest::addColumn("less"); Path a("/tmp/a"); Path b("/tmp/b"); Path c("/tmp/ac"); Path d("/d"); Path e("/tmp"); Path f("/tmp/"); Path invalid; QTest::newRow("a-b") << a << b << false << true; QTest::newRow("a-copy") << a << Path(a) << true << false; QTest::newRow("c-a") << c << a << false << false; QTest::newRow("c-invalid") << c << invalid << false << false; QTest::newRow("c-d") << c << d << false << false; QTest::newRow("e-f") << e << f << true << false; } void TestPath::testPathAddData() { QFETCH(QString, pathToAdd); const QStringList bases = QStringList() << "/foo/bar/asdf/" << "file:///foo/bar/asdf/" << "http://www.asdf.com/foo/bar/asdf/" << "/" ; foreach(const QString& base, bases) { KUrl baseUrl(base); baseUrl.addPath(pathToAdd); baseUrl.cleanPath(); baseUrl.adjustPath(KUrl::RemoveTrailingSlash); Path basePath(base); basePath.addPath(pathToAdd); QCOMPARE(basePath.toUrl(), baseUrl); QCOMPARE(basePath.pathOrUrl(), baseUrl.pathOrUrl()); } } void TestPath::testPathAddData_data() { QTest::addColumn("pathToAdd"); const QStringList paths = QStringList() << "file.txt" << "path/file.txt" << "path//file.txt" << "/absolute" << "../" << ".." << "../../../" << "./foo" << "../relative" << "../../relative" << "../foo/../bar" << "../foo/./bar" << "../../../../../../../invalid"; foreach(const QString &path, paths) { QTest::newRow(qstrdup(path.toUtf8().constData())) << path; } } void TestPath::testPathBaseCtor() { QFETCH(QString, base); QFETCH(QString, subPath); const Path basePath(base); const Path path(basePath, subPath); KUrl url(base); if (KUrl(subPath).isRelative()) { url.addPath(subPath); } else { url.setPath(subPath); } url.cleanPath(); QCOMPARE(path.pathOrUrl(), url.pathOrUrl(KUrl::RemoveTrailingSlash)); } void TestPath::testPathBaseCtor_data() { QTest::addColumn("base"); QTest::addColumn("subPath"); QTest::newRow("empty") << "" << ""; QTest::newRow("root-empty") << "/" << ""; QTest::newRow("root-root") << "/" << "/"; QTest::newRow("root-relative") << "/" << "bar"; QTest::newRow("root-relative-dirty") << "/" << "bar//foo/a/.."; QTest::newRow("empty-relative") << "" << "bar/foo/"; QTest::newRow("path-relative") << "/foo/bar" << "bar/foo"; QTest::newRow("path-absolute") << "/foo/bar" << "/bar/foo"; QTest::newRow("remote-path-absolute") << "http://foo.com/foo/bar" << "/bar/foo"; QTest::newRow("remote-path-relative") << "http://foo.com/foo/bar" << "bar/foo"; } void TestPath::testPathCd() { QFETCH(QString, base); QFETCH(QString, change); Path path = base.isEmpty() ? Path() : Path(base); KUrl url(base); Path changed = path.cd(change); if (url.cd(change)) { QVERIFY(changed.isValid()); } url.cleanPath(); QCOMPARE(changed.pathOrUrl(), url.pathOrUrl(KUrl::RemoveTrailingSlash)); } void TestPath::testPathCd_data() { QTest::addColumn("base"); QTest::addColumn("change"); const QVector bases{"", "/foo", "/foo/bar/asdf", "http://foo.com/", "http://foo.com/foo", "http://foo.com/foo/bar/asdf"}; foreach (const QString& base, bases) { QTest::newRow(qstrdup(qPrintable(base + "-"))) << base << ""; QTest::newRow(qstrdup(qPrintable(base + "-.."))) << base << ".."; QTest::newRow(qstrdup(qPrintable(base + "-../"))) << base << "../"; QTest::newRow(qstrdup(qPrintable(base + "v../foo"))) << base << "../foo"; QTest::newRow(qstrdup(qPrintable(base + "-."))) << base << "."; QTest::newRow(qstrdup(qPrintable(base + "-./"))) << base << "./"; QTest::newRow(qstrdup(qPrintable(base + "-./foo"))) << base << "./foo"; QTest::newRow(qstrdup(qPrintable(base + "-./foo/bar"))) << base << "./foo/bar"; QTest::newRow(qstrdup(qPrintable(base + "-foo/.."))) << base << "foo/.."; QTest::newRow(qstrdup(qPrintable(base + "-foo/"))) << base << "foo/"; QTest::newRow(qstrdup(qPrintable(base + "-foo/../bar"))) << base << "foo/../bar"; QTest::newRow(qstrdup(qPrintable(base + "-/foo"))) << base << "/foo"; QTest::newRow(qstrdup(qPrintable(base + "-/foo/../bar"))) << base << "/foo/../bar"; } } #include "testpath.moc" diff --git a/project/tests/testpath.h b/project/tests/testpath.h index 68009103e2..95bc7de964 100644 --- a/project/tests/testpath.h +++ b/project/tests/testpath.h @@ -1,55 +1,56 @@ /* * This file is part of KDevelop * Copyright 2012 Milian Wolff * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef TESTPATH_H #define TESTPATH_H #include class TestPath : public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void bench_kurl(); void bench_qurl(); void bench_qstringlist(); void bench_path(); void bench_fromLocalPath(); void bench_fromLocalPath_data(); + void bench_hash(); void testPath(); void testPath_data(); void testPathInvalid(); void testPathInvalid_data(); void testPathOperators(); void testPathOperators_data(); void testPathAddData(); void testPathAddData_data(); void testPathBaseCtor(); void testPathBaseCtor_data(); void testPathCd(); void testPathCd_data(); }; #endif // TESTPATH_H