File indexing completed on 2024-05-05 16:22:32

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