File indexing completed on 2024-05-05 04:01:40

0001 /*
0002     SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: MIT
0005 */
0006 
0007 #include "test-config.h"
0008 
0009 #include <KSyntaxHighlighting/AbstractHighlighter>
0010 #include <KSyntaxHighlighting/Definition>
0011 #include <KSyntaxHighlighting/FoldingRegion>
0012 #include <KSyntaxHighlighting/Repository>
0013 #include <KSyntaxHighlighting/State>
0014 
0015 #include <QDir>
0016 #include <QFile>
0017 #include <QObject>
0018 #include <QStandardPaths>
0019 #include <QTest>
0020 #include <QTextStream>
0021 
0022 #include <unordered_map>
0023 
0024 using namespace KSyntaxHighlighting;
0025 
0026 class FoldingHighlighter : public AbstractHighlighter
0027 {
0028 public:
0029     void highlightFile(const QString &inFileName, const QString &outFileName)
0030     {
0031         QFile outFile(outFileName);
0032         if (!outFile.open(QFile::WriteOnly | QFile::Truncate)) {
0033             qWarning() << "Failed to open output file" << outFileName << ":" << outFile.errorString();
0034             return;
0035         }
0036         m_out.setDevice(&outFile);
0037 
0038         QFile f(inFileName);
0039         if (!f.open(QFile::ReadOnly)) {
0040             qWarning() << "Failed to open input file" << inFileName << ":" << f.errorString();
0041             return;
0042         }
0043 
0044         QTextStream in(&f);
0045         State state;
0046         bool indentationFoldEnabled = definition().indentationBasedFoldingEnabled();
0047         if (indentationFoldEnabled) {
0048             m_out << "<indentfold>";
0049         }
0050         while (!in.atEnd()) {
0051             const auto currentLine = in.readLine();
0052             state = highlightLine(currentLine, state);
0053 
0054             if (indentationFoldEnabled != state.indentationBasedFoldingEnabled()) {
0055                 indentationFoldEnabled = state.indentationBasedFoldingEnabled();
0056                 if (indentationFoldEnabled) {
0057                     m_out << "<indentfold>";
0058                 } else {
0059                     m_out << "</indentfold>";
0060                 }
0061             }
0062 
0063             int offset = 0;
0064             for (const auto &fold : std::as_const(m_folds)) {
0065                 // use stable ids for output, see below docs for m_stableFoldingIds
0066                 const auto stableId = m_stableFoldingIds[fold.region.id()];
0067                 m_out << currentLine.mid(offset, fold.offset - offset);
0068                 if (fold.region.type() == FoldingRegion::Begin) {
0069                     m_out << "<beginfold id='" << stableId << "'>";
0070                 } else {
0071                     m_out << "<endfold id='" << stableId << "'>";
0072                 }
0073                 m_out << currentLine.mid(fold.offset, fold.length);
0074                 if (fold.region.type() == FoldingRegion::Begin) {
0075                     m_out << "</beginfold id='" << stableId << "'>";
0076                 } else {
0077                     m_out << "</endfold id='" << stableId << "'>";
0078                 }
0079                 offset = fold.offset + fold.length;
0080             }
0081             m_out << currentLine.mid(offset) << '\n';
0082             m_folds.clear();
0083         }
0084 
0085         m_out.flush();
0086     }
0087 
0088 protected:
0089     void applyFormat(int offset, int length, const Format &format) override
0090     {
0091         Q_UNUSED(offset);
0092         Q_UNUSED(length);
0093         Q_UNUSED(format);
0094     }
0095 
0096     void applyFolding(int offset, int length, FoldingRegion region) override
0097     {
0098         Q_ASSERT(region.isValid());
0099         m_folds.push_back({offset, length, region});
0100 
0101         // create stable id if needed, see below m_stableFoldingIds docs for details
0102         // start with 1
0103         m_stableFoldingIds.emplace(region.id(), m_stableFoldingIds.size() + 1);
0104     }
0105 
0106 private:
0107     QTextStream m_out;
0108     struct Fold {
0109         int offset;
0110         int length;
0111         FoldingRegion region;
0112     };
0113     QList<Fold> m_folds;
0114 
0115     // we use one repository for all tests
0116     // => the folding ids might change even if just unrelated highlighings are added
0117     // => construct some stable id per test based on occurrence of id
0118     std::unordered_map<uint32_t, size_t> m_stableFoldingIds;
0119 };
0120 
0121 class FoldingTest : public QObject
0122 {
0123     Q_OBJECT
0124 public:
0125     explicit FoldingTest(QObject *parent = nullptr)
0126         : QObject(parent)
0127         , m_repo(nullptr)
0128     {
0129     }
0130 
0131 private:
0132     Repository *m_repo;
0133 
0134 private Q_SLOTS:
0135     void initTestCase()
0136     {
0137         QStandardPaths::setTestModeEnabled(true);
0138         m_repo = new Repository;
0139         initRepositorySearchPaths(*m_repo);
0140     }
0141 
0142     void cleanupTestCase()
0143     {
0144         delete m_repo;
0145         m_repo = nullptr;
0146     }
0147 
0148     void testFolding_data()
0149     {
0150         QTest::addColumn<QString>("inFile");
0151         QTest::addColumn<QString>("outFile");
0152         QTest::addColumn<QString>("refFile");
0153         QTest::addColumn<QString>("syntax");
0154 
0155         const QDir dir(QStringLiteral(TESTSRCDIR "/input"));
0156         for (const auto &fileName : dir.entryList(QDir::Files | QDir::NoSymLinks | QDir::Readable | QDir::Hidden, QDir::Name)) {
0157             // skip .clang-format file we use to avoid formatting test files
0158             if (fileName == QLatin1String(".clang-format")) {
0159                 continue;
0160             }
0161 
0162             const auto inFile = dir.absoluteFilePath(fileName);
0163             if (inFile.endsWith(QLatin1String(".syntax"))) {
0164                 continue;
0165             }
0166 
0167             QString syntax;
0168             QFile syntaxOverride(inFile + QStringLiteral(".syntax"));
0169             if (syntaxOverride.exists() && syntaxOverride.open(QFile::ReadOnly)) {
0170                 syntax = QString::fromUtf8(syntaxOverride.readAll()).trimmed();
0171             }
0172 
0173             QTest::newRow(fileName.toUtf8().constData()) << inFile << (QStringLiteral(TESTBUILDDIR "/folding.out/") + fileName + QStringLiteral(".fold"))
0174                                                          << (QStringLiteral(TESTSRCDIR "/folding/") + fileName + QStringLiteral(".fold")) << syntax;
0175         }
0176 
0177         // cleanup before we test
0178         QDir(QStringLiteral(TESTBUILDDIR "/folding.out/")).removeRecursively();
0179         QDir().mkpath(QStringLiteral(TESTBUILDDIR "/folding.out/"));
0180     }
0181 
0182     void testFolding()
0183     {
0184         QFETCH(QString, inFile);
0185         QFETCH(QString, outFile);
0186         QFETCH(QString, refFile);
0187         QFETCH(QString, syntax);
0188         QVERIFY(m_repo);
0189 
0190         auto def = m_repo->definitionForFileName(inFile);
0191         if (!syntax.isEmpty()) {
0192             def = m_repo->definitionForName(syntax);
0193         }
0194 
0195         FoldingHighlighter highlighter;
0196         QVERIFY(def.isValid());
0197         highlighter.setDefinition(def);
0198         highlighter.highlightFile(inFile, outFile);
0199 
0200         /**
0201          * compare results
0202          */
0203         compareFiles(refFile, outFile);
0204     }
0205 };
0206 
0207 QTEST_GUILESS_MAIN(FoldingTest)
0208 
0209 #include "foldingtest.moc"