File indexing completed on 2024-05-12 04:40:20

0001 /*
0002     SPDX-FileCopyrightText: 2013 Milian Wolff <mail@milianw.de>
0003 
0004     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "test_projectfilter.h"
0008 
0009 #include <QTest>
0010 #include <KConfigGroup>
0011 
0012 #include <tests/testcore.h>
0013 #include <tests/autotestshell.h>
0014 #include <tests/testproject.h>
0015 
0016 #include "../projectfilter.h"
0017 
0018 QTEST_GUILESS_MAIN(TestProjectFilter)
0019 
0020 using namespace KDevelop;
0021 
0022 using TestFilter = QSharedPointer<ProjectFilter>;
0023 
0024 Q_DECLARE_METATYPE(TestFilter)
0025 
0026 namespace {
0027 
0028 const bool Invalid = false;
0029 const bool Valid = true;
0030 const bool Folder = true;
0031 const bool File = false;
0032 
0033 struct MatchTest
0034 {
0035     QString path;
0036     bool isFolder;
0037     bool shouldMatch;
0038 };
0039 
0040 void addTests(const char* tag, const TestProject& project, const TestFilter& filter, const MatchTest* tests,
0041               uint numTests)
0042 {
0043     for (uint i = 0; i < numTests; ++i) {
0044         const MatchTest& test = tests[i];
0045         QTest::addRow("%s:%s", tag, qUtf8Printable(test.path))
0046             << filter << Path(project.path(), test.path) << test.isFolder << test.shouldMatch;
0047 
0048         if (test.isFolder) {
0049             // also test folder with trailing slash - should not make a difference
0050             QTest::addRow("%s:%s/", tag, qUtf8Printable(test.path))
0051                 << filter << Path(project.path(), test.path) << test.isFolder << test.shouldMatch;
0052         }
0053     }
0054 }
0055 
0056 template<typename T>
0057 void addTests(const char* tag, const TestProject& project, const TestFilter& filter, const T& tests)
0058 {
0059     addTests(tag, project, filter, tests, std::size(tests));
0060 }
0061 
0062 struct BenchData
0063 {
0064     BenchData(const Path &path = Path(), bool isFolder = false)
0065     : path(path)
0066     , isFolder(isFolder)
0067     {}
0068 
0069     Path path;
0070     bool isFolder;
0071 };
0072 
0073 }
0074 
0075 Q_DECLARE_METATYPE(QVector<BenchData>)
0076 
0077 void TestProjectFilter::initTestCase()
0078 {
0079     AutoTestShell::init();
0080     TestCore::initialize(Core::NoUi);
0081     qRegisterMetaType<TestFilter>();
0082     qRegisterMetaType<Path>();
0083     qRegisterMetaType<QVector<BenchData> >();
0084 }
0085 
0086 void TestProjectFilter::cleanupTestCase()
0087 {
0088     TestCore::shutdown();
0089 }
0090 
0091 void TestProjectFilter::match()
0092 {
0093     QFETCH(TestFilter, filter);
0094     QFETCH(KDevelop::Path, path);
0095     QFETCH(bool, isFolder);
0096     QFETCH(bool, expectedIsValid);
0097 
0098     QCOMPARE(filter->isValid(path, isFolder), expectedIsValid);
0099 }
0100 
0101 void TestProjectFilter::match_data()
0102 {
0103     QTest::addColumn<TestFilter>("filter");
0104     QTest::addColumn<Path>("path");
0105     QTest::addColumn<bool>("isFolder");
0106     QTest::addColumn<bool>("expectedIsValid");
0107 
0108     {
0109         // test default filters
0110         const TestProject project;
0111         TestFilter filter(new ProjectFilter(&project, deserialize(defaultFilters())));
0112 
0113         QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid;
0114         QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid;
0115 
0116         MatchTest tests[] = {
0117             //{path, isFolder, isValid}
0118             {QStringLiteral(".kdev4"), Folder, Invalid},
0119 
0120             {QStringLiteral("folder"), Folder, Valid},
0121             {QStringLiteral("folder/folder"), Folder, Valid},
0122             {QStringLiteral("file"), File, Valid},
0123             {QStringLiteral("folder/file"), File, Valid},
0124             {QStringLiteral(".file"), File, Invalid},
0125             {QStringLiteral(".folder"), Folder, Invalid},
0126             {QStringLiteral("folder/.folder"), Folder, Invalid},
0127             {QStringLiteral("folder/.file"), File, Invalid},
0128 
0129             {QStringLiteral(".git"), Folder, Invalid},
0130             {QStringLiteral(".gitignore"), File, Valid},
0131             {QStringLiteral(".gitmodules"), File, Valid},
0132             {QStringLiteral("_darcs"), Folder, Invalid},
0133             {QStringLiteral("_svn"), Folder, Invalid},
0134             {QStringLiteral(".svn"), Folder, Invalid},
0135             {QStringLiteral("CVS"), Folder, Invalid},
0136             {QStringLiteral("SCCS"), Folder, Invalid},
0137             {QStringLiteral(".hg"), Folder, Invalid},
0138             {QStringLiteral(".bzr"), Folder, Invalid},
0139 
0140             {QStringLiteral("foo.o"), File, Invalid},
0141             {QStringLiteral("foo.so"), File, Invalid},
0142             {QStringLiteral("foo.so.1"), File, Invalid},
0143             {QStringLiteral("foo.a"), File, Invalid},
0144             {QStringLiteral("moc_foo.cpp"), File, Invalid},
0145             {QStringLiteral("ui_foo.h"), File, Invalid},
0146             {QStringLiteral("qrc_foo.cpp"), File, Invalid},
0147             {QStringLiteral("foo.cpp~"), File, Invalid},
0148             {QStringLiteral(".foo.cpp.kate-swp"), File, Invalid},
0149             {QStringLiteral(".foo.cpp.swp"), File, Invalid},
0150 
0151             // Default code quality tools explicitly checked
0152             {QStringLiteral(".bumpversion.cfg"), File, Valid},
0153             {QStringLiteral(".clang-format"), File, Valid},
0154             {QStringLiteral(".clippy.toml"), File, Valid},
0155             {QStringLiteral(".codespellrc"), File, Valid},
0156             {QStringLiteral(".isort.cfg"), File, Valid},
0157             {QStringLiteral(".mypy.ini"), File, Valid},
0158             {QStringLiteral(".pydocstyle"), File, Valid},
0159             {QStringLiteral(".pydocstyle.ini"), File, Valid},
0160             {QStringLiteral(".pydocstylerc"), File, Valid},
0161             {QStringLiteral(".pydocstylerc.ini"), File, Valid},
0162             {QStringLiteral(".pylintrc"), File, Valid},
0163             {QStringLiteral(".readthedocs.yaml"), File, Valid},
0164             {QStringLiteral(".readthedocs.yml"), File, Valid},
0165             {QStringLiteral(".yamllint"), File, Valid},
0166             {QStringLiteral(".yamllint.yaml"), File, Valid},
0167             {QStringLiteral(".yamllint.yml"), File, Valid},
0168 
0169             // Code quality tool similar files that should remain hidden
0170             {QStringLiteral(".mypy_cache"), Folder, Invalid},
0171             {QStringLiteral(".pylint.d"), Folder, Invalid},
0172             {QStringLiteral(".pytest_cache"), File, Invalid},
0173             {QStringLiteral(".tox"), File, Invalid},
0174         };
0175         addTests("default", project, filter, tests);
0176     }
0177     {
0178         // test exclude files, basename
0179         const TestProject project;
0180         const Filters filters = Filters()
0181             << Filter(SerializedFilter(QStringLiteral("*.cpp"), Filter::Files));
0182         TestFilter filter(new ProjectFilter(&project, filters));
0183 
0184         QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid;
0185         QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid;
0186 
0187         MatchTest tests[] = {
0188             //{path, isFolder, isValid}
0189             {QStringLiteral(".kdev4"), Folder, Invalid},
0190 
0191             {QStringLiteral("folder"), Folder, Valid},
0192             {QStringLiteral("file"), File, Valid},
0193             {QStringLiteral("file.cpp"), File, Invalid},
0194             {QStringLiteral("folder.cpp"), Folder, Valid},
0195             {QStringLiteral("folder/file.cpp"), File, Invalid},
0196             {QStringLiteral("folder/folder.cpp"), Folder, Valid}
0197         };
0198         addTests("exclude:*.cpp", project, filter, tests);
0199     }
0200     {
0201         // test excludes on folders
0202         const TestProject project;
0203         const Filters filters = Filters()
0204             << Filter(SerializedFilter(QStringLiteral("foo"), Filter::Folders));
0205         TestFilter filter(new ProjectFilter(&project, filters));
0206 
0207         QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid;
0208         QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid;
0209 
0210         MatchTest tests[] = {
0211             //{path, isFolder, isValid}
0212             {QStringLiteral(".kdev4"), Folder, Invalid},
0213 
0214             {QStringLiteral("folder"), Folder, Valid},
0215             {QStringLiteral("file"), File, Valid},
0216             {QStringLiteral("foo"), Folder, Invalid},
0217             {QStringLiteral("folder/file"), File, Valid},
0218             {QStringLiteral("folder/foo"), Folder, Invalid},
0219             {QStringLiteral("folder/foo"), File, Valid}
0220         };
0221         addTests("exclude:foo", project, filter, tests);
0222     }
0223     {
0224         // test includes
0225         const TestProject project;
0226         const Filters filters = Filters()
0227             << Filter(SerializedFilter(QStringLiteral("*"), Filter::Files))
0228             << Filter(SerializedFilter(QStringLiteral("*.cpp"), Filter::Files, Filter::Inclusive));
0229         TestFilter filter(new ProjectFilter(&project, filters));
0230 
0231         QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid;
0232         QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid;
0233 
0234         MatchTest tests[] = {
0235             //{path, isFolder, isValid}
0236             {QStringLiteral(".kdev4"), Folder, Invalid},
0237 
0238             {QStringLiteral("folder"), Folder, Valid},
0239             {QStringLiteral("file"), File, Invalid},
0240             {QStringLiteral("file.cpp"), File, Valid},
0241             {QStringLiteral(".file.cpp"), File, Valid},
0242             {QStringLiteral("folder/file.cpp"), File, Valid},
0243             {QStringLiteral("folder/.file.cpp"), File, Valid}
0244         };
0245         addTests("include:*.cpp", project, filter, tests);
0246         project.projectConfiguration();
0247     }
0248     {
0249         // test mixed stuff
0250         const TestProject project;
0251         const Filters filters = Filters()
0252             << Filter(SerializedFilter(QStringLiteral("*"), Filter::Files, Filter::Exclusive))
0253             << Filter(SerializedFilter(QStringLiteral("*.inc"), Filter::Files, Filter::Inclusive))
0254             << Filter(SerializedFilter(QStringLiteral("*ex.inc"), Filter::Files, Filter::Exclusive))
0255             << Filter(SerializedFilter(QStringLiteral("bar"), Filter::Folders, Filter::Exclusive));
0256         TestFilter filter(new ProjectFilter(&project, filters));
0257 
0258         QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid;
0259         QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid;
0260 
0261         MatchTest tests[] = {
0262             //{path, isFolder, isValid}
0263             {QStringLiteral(".kdev4"), Folder, Invalid},
0264 
0265             {QStringLiteral("folder"), Folder, Valid},
0266             {QStringLiteral("file"), File, Invalid},
0267             {QStringLiteral("file.inc"), File, Valid},
0268             {QStringLiteral("file.ex.inc"), File, Invalid},
0269             {QStringLiteral("folder/file"), File, Invalid},
0270             {QStringLiteral("folder/file.inc"), File, Valid},
0271             {QStringLiteral("folder/file.ex.inc"), File, Invalid},
0272             {QStringLiteral("bar"), Folder, Invalid},
0273         };
0274         addTests("mixed", project, filter, tests);
0275     }
0276     {
0277         // relative path
0278         const TestProject project;
0279         const Filters filters = Filters()
0280             << Filter(SerializedFilter(QStringLiteral("/foo/*bar"), Filter::Targets(Filter::Files | Filter::Folders)));
0281         TestFilter filter(new ProjectFilter(&project, filters));
0282 
0283         QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid;
0284         QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid;
0285 
0286         MatchTest tests[] = {
0287             //{path, isFolder, isValid}
0288             {QStringLiteral(".kdev4"), Folder, Invalid},
0289 
0290             {QStringLiteral("foo"), Folder, Valid},
0291             {QStringLiteral("bar"), File, Valid},
0292             {QStringLiteral("foo/bar"), Folder, Invalid},
0293             {QStringLiteral("foo/bar"), File, Invalid},
0294             {QStringLiteral("foo/asdf/bar"), Folder, Invalid},
0295             {QStringLiteral("foo/asdf/bar"), File, Invalid},
0296             {QStringLiteral("foo/asdf_bar"), Folder, Invalid},
0297             {QStringLiteral("foo/asdf_bar"), File, Invalid},
0298             {QStringLiteral("asdf/bar"), File, Valid},
0299             {QStringLiteral("asdf/foo/bar"), File, Valid},
0300         };
0301         addTests("relative", project, filter, tests);
0302     }
0303     {
0304         // trailing slash
0305         const TestProject project;
0306         const Filters filters = Filters()
0307             << Filter(SerializedFilter(QStringLiteral("bar/"), Filter::Targets(Filter::Files | Filter::Folders)));
0308         TestFilter filter(new ProjectFilter(&project, filters));
0309 
0310         QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid;
0311         QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid;
0312 
0313         MatchTest tests[] = {
0314             //{path, isFolder, isValid}
0315             {QStringLiteral(".kdev4"), Folder, Invalid},
0316 
0317             {QStringLiteral("foo"), Folder, Valid},
0318             {QStringLiteral("bar"), File, Valid},
0319             {QStringLiteral("bar"), Folder, Invalid},
0320             {QStringLiteral("foo/bar"), File, Valid},
0321             {QStringLiteral("foo/bar"), Folder, Invalid}
0322         };
0323         addTests("trailingslash", project, filter, tests);
0324     }
0325     {
0326         // escaping
0327         const TestProject project;
0328         const Filters filters = Filters()
0329             << Filter(SerializedFilter(QStringLiteral("foo\\*bar"), Filter::Files));
0330         TestFilter filter(new ProjectFilter(&project, filters));
0331 
0332         QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid;
0333         QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid;
0334 
0335         MatchTest tests[] = {
0336             //{path, isFolder, isValid}
0337             {QStringLiteral(".kdev4"), Folder, Invalid},
0338 
0339             {QStringLiteral("foobar"), Folder, Valid},
0340             {QStringLiteral("fooasdfbar"), File, Valid},
0341             {QStringLiteral("foo*bar"), File, Invalid},
0342             {QStringLiteral("foo/bar"), Folder, Valid}
0343         };
0344         addTests("escaping", project, filter, tests);
0345     }
0346 }
0347 
0348 static QVector<BenchData> createBenchData(const Path& base, int folderDepth, int foldersPerFolder, int filesPerFolder)
0349 {
0350     QVector<BenchData> data;
0351     data << BenchData(base, true);
0352 
0353     for(int i = 0; i < filesPerFolder; ++i) {
0354         if (i % 2) {
0355             data << BenchData(Path(base, QStringLiteral("file%1.cpp").arg(i)), false);
0356         } else {
0357             data << BenchData(Path(base, QStringLiteral("file%1.h").arg(i)), true);
0358         }
0359     }
0360     for(int i = 0; i < foldersPerFolder && folderDepth > 0; ++i) {
0361         data += createBenchData(Path(base, QStringLiteral("folder%1").arg(i)), folderDepth - 1, foldersPerFolder, filesPerFolder);
0362     }
0363     return data;
0364 }
0365 
0366 void TestProjectFilter::bench()
0367 {
0368     QFETCH(TestFilter, filter);
0369     QFETCH(const QVector<BenchData>, data);
0370 
0371     QBENCHMARK {
0372         for (const BenchData& bench : data) {
0373             filter->isValid(bench.path, bench.isFolder);
0374         }
0375     }
0376 }
0377 
0378 void TestProjectFilter::bench_data()
0379 {
0380     QTest::addColumn<TestFilter>("filter");
0381     QTest::addColumn<QVector<BenchData> >("data");
0382 
0383     const TestProject project;
0384 
0385     const QVector<QVector<BenchData> > dataSets = QVector<QVector<BenchData> >()
0386         << createBenchData(project.path(), 3, 5, 10)
0387         << createBenchData(project.path(), 3, 5, 20)
0388         << createBenchData(project.path(), 4, 5, 10)
0389         << createBenchData(project.path(), 3, 10, 10);
0390 
0391     {
0392         TestFilter filter(new ProjectFilter(&project, Filters()));
0393         for (const QVector<BenchData>& data : dataSets) {
0394             QTest::newRow(QByteArray("baseline-" + QByteArray::number(data.size()))) << filter << data;
0395         }
0396     }
0397 
0398     {
0399         TestFilter filter(new ProjectFilter(&project, deserialize(defaultFilters())));
0400         for (const QVector<BenchData>& data : dataSets) {
0401             QTest::newRow(QByteArray("defaults-" + QByteArray::number(data.size()))) << filter << data;
0402         }
0403     }
0404 }
0405 
0406 #include "moc_test_projectfilter.cpp"