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

0001 /*
0002     SPDX-FileCopyrightText: 2016 Anton Anikin <anton.anikin@htower.ru>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "test_kdevformatsource.h"
0008 #include "../kdevformatfile.h"
0009 #include "../filesystemhelpers.h"
0010 
0011 #include <QTest>
0012 #include <QByteArray>
0013 #include <QByteArrayList>
0014 #include <QDebug>
0015 #include <QDir>
0016 #include <QFile>
0017 #include <QFileInfo>
0018 #include <QString>
0019 #include <QStringList>
0020 #include <QTemporaryDir>
0021 #include <QTextStream>
0022 #include <QStandardPaths>
0023 
0024 #include <vector>
0025 
0026 QTEST_MAIN(KDevelop::TestKdevFormatSource)
0027 
0028 using namespace KDevelop;
0029 
0030 namespace {
0031 QString applyFormatting(const QString& path, bool expectedFormattingResult)
0032 {
0033     KDevFormatFile formatFile(path, path);
0034     if (!formatFile.find()) {
0035         return "found no format_sources file for " + path;
0036     }
0037     if (!formatFile.read()) {
0038         return "reading format_sources file failed for " + path;
0039     }
0040     if (formatFile.apply() != expectedFormattingResult) {
0041         if (expectedFormattingResult) {
0042             return "formatting was expected to succeed but actually failed for " + path;
0043         } else {
0044             return "formatting was expected to fail but actually succeeded for " + path;
0045         }
0046     }
0047     return QString{};
0048 }
0049 }
0050 
0051 TestKdevFormatSource::TestKdevFormatSource()
0052 {
0053 }
0054 
0055 TestKdevFormatSource::~TestKdevFormatSource()
0056 {
0057 }
0058 
0059 void TestKdevFormatSource::initTestCase()
0060 {
0061     QStandardPaths::setTestModeEnabled(true);
0062 }
0063 
0064 void TestKdevFormatSource::testNotFound_data()
0065 {
0066     static const QStringList formatFileData = {};
0067 
0068     QCOMPARE(initTest(formatFileData), true);
0069 
0070     for (const Source& source : qAsConst(m_sources)) {
0071         QTest::newRow(source.path.toUtf8()) << source.path << false << false << false << source.lines;
0072     }
0073 }
0074 
0075 void TestKdevFormatSource::testNotFound()
0076 {
0077     runTest();
0078 }
0079 
0080 void TestKdevFormatSource::testNoCommands_data()
0081 {
0082     static const QStringList formatFileData = {QStringLiteral("# some comment")};
0083 
0084     QCOMPARE(initTest(formatFileData), true);
0085 
0086     for (const Source& source : qAsConst(m_sources)) {
0087         QTest::newRow(source.path.toUtf8()) << source.path << true << false << false << source.lines;
0088     }
0089 }
0090 
0091 void TestKdevFormatSource::testNoCommands()
0092 {
0093     runTest();
0094 }
0095 
0096 void TestKdevFormatSource::testNotMatch_data()
0097 {
0098     static const QStringList formatFileData = {QStringLiteral("notmatched.cpp : unused_command")};
0099 
0100     QCOMPARE(initTest(formatFileData), true);
0101 
0102     for (const Source& source : qAsConst(m_sources)) {
0103         QTest::newRow(source.path.toUtf8()) << source.path << true << true << false << source.lines;
0104     }
0105 }
0106 
0107 void TestKdevFormatSource::testNotMatch()
0108 {
0109     runTest();
0110 }
0111 
0112 void TestKdevFormatSource::testMatch1_data()
0113 {
0114     static const QStringList formatFileData({
0115         QStringLiteral("src1/source_1.cpp : cat $ORIGFILE | sed 's/foo/FOO/' > tmp && mv tmp $ORIGFILE"),
0116         QStringLiteral("src2/source_2.cpp : cat $ORIGFILE | sed 's/sqrt/std::sqrt/' > tmp && mv tmp $ORIGFILE"),
0117         QStringLiteral("*.cpp : cat $ORIGFILE | sed 's/z/Z/' > tmp && mv tmp $ORIGFILE"),
0118         QStringLiteral("notmatched.cpp : unused_command"),
0119     });
0120 
0121     QCOMPARE(initTest(formatFileData), true);
0122 
0123     m_sources[0].lines.replaceInStrings(QStringLiteral("foo"), QStringLiteral("FOO"));
0124     m_sources[1].lines.replaceInStrings(QStringLiteral("sqrt"), QStringLiteral("std::sqrt"));
0125     m_sources[2].lines.replaceInStrings(QStringLiteral("z"), QStringLiteral("Z"));
0126 
0127     for (const Source& source : qAsConst(m_sources)) {
0128         QTest::newRow(source.path.toUtf8()) << source.path << true << true << true << source.lines;
0129     }
0130 }
0131 
0132 void TestKdevFormatSource::testMatch1()
0133 {
0134     runTest();
0135 }
0136 
0137 void TestKdevFormatSource::testMatch2_data()
0138 {
0139     static const QStringList formatFileData({QStringLiteral("cat $ORIGFILE | sed 's/;/;;/' > tmp && mv tmp $ORIGFILE")});
0140 
0141     QCOMPARE(initTest(formatFileData), true);
0142 
0143     for (Source& source : m_sources) {
0144         source.lines.replaceInStrings(QStringLiteral(";"), QStringLiteral(";;"));
0145         QTest::newRow(source.path.toUtf8()) << source.path << true << true << true << source.lines;
0146     }
0147 }
0148 
0149 void TestKdevFormatSource::testMatch2()
0150 {
0151     runTest();
0152 }
0153 
0154 void TestKdevFormatSource::testWildcardPathMatching_data()
0155 {
0156     struct FormatInfo{ const char* dir; const char* contents; };
0157     struct Row{
0158         const char* dataTag;
0159         std::vector<FormatInfo> formatInfos;
0160         std::vector<const char*> unmatchedPaths;
0161         std::vector<const char*> matchedPaths;
0162     };
0163 
0164     const std::vector<Row> dataRows{
0165         Row{"format_sources without wildcards (simple syntax)",
0166         {FormatInfo{"", "true"}},
0167         {},
0168         {"x", "a/b", "exclude", "x.c", "p q\tr", "v/l/p/a/t/h.x"}
0169     }, Row{"Single root format_sources with a single command",
0170         {FormatInfo{"", "rd/* *include* *.h : true"}},
0171         {"x", "r", "r.d", "includ", "includh", "rdh", "rd.h/x", "a/b.hh", "rc/x.h/y"},
0172         {"x.h", "rd/x", "rd/x.h", "aincludeb", "include", "include.h", "rd/a/b/c", "a/b/c.h", "a/include"}
0173     }, Row{"Single root format_sources with different commands",
0174         {FormatInfo{"", "*inc/*:\n q/* *x?z:true \n dd/*: \n *.c:false \n *ab*:true"}},
0175         {"q", "a.b", "xz", "xyzc", "c", "ac", "inc", "inc-/x", "ayz", "xy", "add/s", "incc", "a./c", "x/yz", "a-b", "minc"},
0176         {"xyz", "x.c", "incxyz", "ainc/b.c", "a/b/.c", "a/.c", "x/z", "a/x-z", "p/x.z", "asinc/v", "a/b/cab/d/e", "dd/d", "dd/.c"}
0177     }, Row{"Multiple format_sources files",
0178         {FormatInfo{"a/b/", "q/* *x?z : false"}, FormatInfo{"", "*.c *cab* : true"}},
0179         {"a/q", "a/xyz", "q/x", "xz", "a/b/qu", "a/bu/xyz", "ab/q/x", "a/b/qt/x", "a/bxyz", "a/x/z", "a/b/xz", "a/b/.c", "a/b/x-z.c"},
0180         {"a/b/xyz", "x.c", "a/b/cdxyz", "a/b/cd/xyz", "a/b/q/x", "a/.c", "a/b/x/z", "exclude.c", "a/bcab/d/e"}
0181     }, Row{"Case sensitivity",
0182         {FormatInfo{"", "pQ* *RS* : true"}},
0183         {"a/b/CDE", "cdpq", "a/b/.e", "a/b/cDe", "prcpQ.Eqs"},
0184         {"a/b/pQrs", "a/b/c/d/pq/rs", "a/b/RSPQ", "pq", "uvrs", "PQa/b"}
0185     }};
0186 
0187     QTest::addColumn<QStringList>("formatDirs");
0188     QTest::addColumn<QByteArrayList>("formatContents");
0189     QTest::addColumn<QStringList>("unmatchedPaths");
0190     QTest::addColumn<QStringList>("matchedPaths");
0191 
0192     for (const Row& row : dataRows) {
0193         QStringList formatDirs;
0194         QByteArrayList formatContents;
0195         for (const FormatInfo& info : row.formatInfos) {
0196             formatDirs.push_back(info.dir);
0197             formatContents.push_back(info.contents);
0198         }
0199         const QStringList unmatchedPaths(row.unmatchedPaths.cbegin(), row.unmatchedPaths.cend());
0200         const QStringList matchedPaths(row.matchedPaths.cbegin(), row.matchedPaths.cend());
0201         QTest::newRow(row.dataTag) << formatDirs << formatContents << unmatchedPaths << matchedPaths;
0202     }
0203 }
0204 
0205 void TestKdevFormatSource::testWildcardPathMatching()
0206 {
0207     QFETCH(QStringList, formatDirs);
0208     QFETCH(QByteArrayList, formatContents);
0209     QFETCH(QStringList, unmatchedPaths);
0210     QFETCH(QStringList, matchedPaths);
0211 
0212     QTemporaryDir tmpDir;
0213     QVERIFY2(tmpDir.isValid(), qPrintable("couldn't create temporary directory: " + tmpDir.errorString()));
0214 
0215     using FilesystemHelpers::makeAbsoluteCreateAndWrite;
0216 
0217     for (auto& dir : formatDirs) {
0218         dir = QFileInfo{QDir{dir}, "format_sources"}.filePath();
0219     }
0220     QString errorPath = makeAbsoluteCreateAndWrite(tmpDir.path(), formatDirs, formatContents);
0221     QVERIFY2(errorPath.isEmpty(), qPrintable("couldn't create or write to temporary file or directory " + errorPath));
0222 
0223     errorPath = makeAbsoluteCreateAndWrite(tmpDir.path(), unmatchedPaths);
0224     if (errorPath.isEmpty()) {
0225         errorPath = makeAbsoluteCreateAndWrite(tmpDir.path(), matchedPaths);
0226     }
0227     QVERIFY2(errorPath.isEmpty(), qPrintable("couldn't create temporary file or directory " + errorPath));
0228 
0229     bool expectedFormattingResult = false; // for unmatchedPaths
0230     for (const auto& paths : { unmatchedPaths, matchedPaths }) {
0231         for (const auto& path : paths) {
0232             QVERIFY2(QFileInfo{path}.isFile(), qPrintable(path + ": file was not created or was deleted"));
0233             const QString error = applyFormatting(path, expectedFormattingResult);
0234             QVERIFY2(error.isEmpty(), qPrintable(error));
0235         }
0236         expectedFormattingResult = true; // for matchedPaths
0237     }
0238 }
0239 
0240 bool TestKdevFormatSource::initTest(const QStringList& formatFileData)
0241 {
0242     QTest::addColumn<QString>("path");
0243     QTest::addColumn<bool>("isFound");
0244     QTest::addColumn<bool>("isRead");
0245     QTest::addColumn<bool>("isApplied");
0246     QTest::addColumn<QStringList>("lines");
0247 
0248     m_temporaryDir.reset(new QTemporaryDir);
0249     const QString workPath = m_temporaryDir->path();
0250     qDebug() << "Using temporary dir:" << workPath;
0251 
0252     if (!mkPath(workPath + "/src1"))
0253         return false;
0254 
0255     if (!mkPath(workPath + "/src2"))
0256         return false;
0257 
0258     if (!QDir::setCurrent(workPath)) {
0259         qDebug() << "unable to set current directory to" << workPath;
0260         return false;
0261     }
0262 
0263     m_sources.resize(3);
0264 
0265     m_sources[0].path = workPath + "/src1/source_1.cpp";
0266     m_sources[0].lines = QStringList({
0267         QStringLiteral("void foo(int x) {"),
0268         QStringLiteral("  printf(\"squared x = %d\\n\", x * x);"),
0269         QStringLiteral("}")
0270     });
0271 
0272     m_sources[1].path = workPath + "/src2/source_2.cpp";
0273     m_sources[1].lines = QStringList({
0274         QStringLiteral("void bar(double x) {"),
0275         QStringLiteral("  x = sqrt(x);"),
0276         QStringLiteral("  printf(\"sqrt(x) = %e\\n\", x);"),
0277         QStringLiteral("}")
0278     });
0279 
0280     m_sources[2].path = workPath + "/source_3.cpp";
0281     m_sources[2].lines = QStringList({
0282         QStringLiteral("void baz(double x, double y) {"),
0283         QStringLiteral("  double z = pow(x, y);"),
0284         QStringLiteral("  printf(\"x^y = %e\\n\", z);"),
0285         QStringLiteral("}")
0286     });
0287 
0288     for (const Source& source : qAsConst(m_sources)) {
0289         if (!writeLines(source.path, source.lines))
0290             return false;
0291     }
0292 
0293     if (!formatFileData.isEmpty() && !writeLines(QStringLiteral("format_sources"), formatFileData))
0294         return false;
0295 
0296     return true;
0297 }
0298 
0299 void TestKdevFormatSource::runTest() const
0300 {
0301     QFETCH(QString, path);
0302     QFETCH(bool, isFound);
0303     QFETCH(bool, isRead);
0304     QFETCH(bool, isApplied);
0305     QFETCH(QStringList, lines);
0306 
0307     KDevFormatFile formatFile(path, path);
0308 
0309     QCOMPARE(formatFile.find(), isFound);
0310 
0311     if (isFound)
0312         QCOMPARE(formatFile.read(), isRead);
0313 
0314     if (isRead)
0315         QCOMPARE(formatFile.apply(), isApplied);
0316 
0317     QStringList processedLines;
0318     QCOMPARE(readLines(path, processedLines), true);
0319 
0320     QCOMPARE(processedLines, lines);
0321 }
0322 
0323 bool TestKdevFormatSource::mkPath(const QString& path) const
0324 {
0325     if (!QDir().exists(path) && !QDir().mkpath(path)) {
0326         qDebug() << "unable to create directory" << path;
0327         return false;
0328     }
0329 
0330     return true;
0331 }
0332 
0333 bool TestKdevFormatSource::writeLines(const QString& path, const QStringList& lines) const
0334 {
0335     QFile outFile(path);
0336     if (!outFile.open(QIODevice::WriteOnly)) {
0337         qDebug() << "unable to open file" << path << "for writing";
0338         return false;
0339     }
0340 
0341     QTextStream outStream(&outFile);
0342     for (const QString& line : lines) {
0343         outStream << line << "\n";
0344     }
0345 
0346     outStream.flush();
0347     outFile.close();
0348 
0349     return true;
0350 }
0351 
0352 bool TestKdevFormatSource::readLines(const QString& path, QStringList& lines) const
0353 {
0354     QFile inFile(path);
0355     if (!inFile.open(QIODevice::ReadOnly)) {
0356         qDebug() << "unable to open file" << path << "for reading";
0357         return false;
0358     }
0359 
0360     lines.clear();
0361 
0362     QTextStream inStream(&inFile);
0363     while (!inStream.atEnd()) {
0364         lines += inStream.readLine();
0365     }
0366     inFile.close();
0367 
0368     return true;
0369 }
0370 
0371 #include "moc_test_kdevformatsource.cpp"