File indexing completed on 2025-02-02 14:22:24
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/Format> 0012 #include <KSyntaxHighlighting/Repository> 0013 #include <KSyntaxHighlighting/State> 0014 #include <KSyntaxHighlighting/Theme> 0015 0016 #include <QDir> 0017 #include <QFile> 0018 #include <QObject> 0019 #include <QTest> 0020 #include <QTextStream> 0021 0022 #include <map> 0023 0024 using namespace KSyntaxHighlighting; 0025 0026 class TestHighlighter : 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 while (!in.atEnd()) { 0053 m_currentLine = in.readLine(); 0054 state = highlightLine(m_currentLine, state); 0055 m_out << "<br/>\n"; 0056 } 0057 0058 m_out.flush(); 0059 } 0060 0061 protected: 0062 void applyFormat(int offset, int length, const Format &format) override 0063 { 0064 if (format.name().isEmpty()) { 0065 m_out << "<dsNormal>" << QStringView(m_currentLine).mid(offset, length) << "</dsNormal>"; 0066 } else { 0067 m_out << "<" << format.name() << ">" << QStringView(m_currentLine).mid(offset, length) << "</" << format.name() << ">"; 0068 } 0069 } 0070 0071 private: 0072 QTextStream m_out; 0073 QString m_currentLine; 0074 }; 0075 0076 class TestHighlighterTest : public QObject 0077 { 0078 Q_OBJECT 0079 public: 0080 explicit TestHighlighterTest(QObject *parent = nullptr) 0081 : QObject(parent) 0082 , m_repo(nullptr) 0083 { 0084 } 0085 0086 private: 0087 Repository *m_repo; 0088 std::map<QString, QStringList> m_coveredDefinitions; 0089 0090 private Q_SLOTS: 0091 void initTestCase() 0092 { 0093 QStandardPaths::setTestModeEnabled(true); 0094 m_repo = new Repository; 0095 initRepositorySearchPaths(*m_repo); 0096 } 0097 0098 void cleanupTestCase() 0099 { 0100 QFile coveredList(QLatin1String(TESTBUILDDIR "/covered-definitions.txt")); 0101 QFile uncoveredList(QLatin1String(TESTBUILDDIR "/uncovered-definition.txt")); 0102 QVERIFY(coveredList.open(QFile::WriteOnly)); 0103 QVERIFY(uncoveredList.open(QFile::WriteOnly)); 0104 0105 int count = 0; 0106 for (const auto &def : m_repo->definitions()) { 0107 if (!def.isValid()) { 0108 continue; 0109 } 0110 ++count; 0111 if (m_coveredDefinitions.find(def.name()) != m_coveredDefinitions.end()) { 0112 coveredList.write(def.name().toUtf8() + '\n'); 0113 } else { 0114 uncoveredList.write(def.name().toUtf8() + '\n'); 0115 } 0116 } 0117 0118 qDebug() << "Syntax definitions with test coverage:" << ((float)m_coveredDefinitions.size() * 100.0f / (float)count) << "%"; 0119 0120 // we don't want multiple tests for the same highlighting 0121 // tests should be consolidated into one useful file per highlighting 0122 // the update script for https://kate-editor.org/syntax/ will check that no duplicated output is there, too 0123 bool duplicates = false; 0124 for (const auto &entry : std::as_const(m_coveredDefinitions)) { 0125 if (entry.second.size() <= 1) { 0126 continue; 0127 } 0128 0129 // abort and tell about duplicated test cases! 0130 qWarning() << "Multiple unit tests for the language " << entry.first; 0131 for (const auto &testCase : entry.second) { 0132 qWarning() << " - " << testCase; 0133 } 0134 duplicates = true; 0135 } 0136 if (duplicates) { 0137 QFAIL("Multiple unit tests for the same language found, see for details the output above!"); 0138 } 0139 0140 delete m_repo; 0141 m_repo = nullptr; 0142 } 0143 0144 void testHighlight_data() 0145 { 0146 QTest::addColumn<QString>("inFile"); 0147 QTest::addColumn<QString>("outFile"); 0148 QTest::addColumn<QString>("refFile"); 0149 QTest::addColumn<QString>("syntax"); 0150 0151 const QDir dir(QStringLiteral(TESTSRCDIR "/input")); 0152 for (const auto &fileName : dir.entryList(QDir::Files | QDir::NoSymLinks | QDir::Readable | QDir::Hidden, QDir::Name)) { 0153 // skip .clang-format file we use to avoid formatting test files 0154 if (fileName == QLatin1String(".clang-format")) { 0155 continue; 0156 } 0157 0158 const auto inFile = dir.absoluteFilePath(fileName); 0159 if (inFile.endsWith(QLatin1String(".syntax"))) { 0160 continue; 0161 } 0162 0163 QString syntax; 0164 QFile syntaxOverride(inFile + QStringLiteral(".syntax")); 0165 if (syntaxOverride.exists() && syntaxOverride.open(QFile::ReadOnly)) { 0166 syntax = QString::fromUtf8(syntaxOverride.readAll()).trimmed(); 0167 } 0168 0169 QTest::newRow(fileName.toUtf8().constData()) << inFile << (QStringLiteral(TESTBUILDDIR "/output/") + fileName + QStringLiteral(".ref")) 0170 << (QStringLiteral(TESTSRCDIR "/reference/") + fileName + QStringLiteral(".ref")) << syntax; 0171 } 0172 0173 // cleanup before we test 0174 QDir(QStringLiteral(TESTBUILDDIR "/output/")).removeRecursively(); 0175 QDir().mkpath(QStringLiteral(TESTBUILDDIR "/output/")); 0176 } 0177 0178 void testHighlight() 0179 { 0180 QFETCH(QString, inFile); 0181 QFETCH(QString, outFile); 0182 QFETCH(QString, refFile); 0183 QFETCH(QString, syntax); 0184 QVERIFY(m_repo); 0185 0186 auto def = m_repo->definitionForFileName(inFile); 0187 if (!syntax.isEmpty()) { 0188 def = m_repo->definitionForName(syntax); 0189 } 0190 0191 TestHighlighter highlighter; 0192 highlighter.setTheme(m_repo->defaultTheme()); 0193 QVERIFY(highlighter.theme().isValid()); 0194 0195 QVERIFY(def.isValid()); 0196 qDebug() << "Using syntax" << def.name(); 0197 m_coveredDefinitions[def.name()].push_back(inFile); 0198 highlighter.setDefinition(def); 0199 highlighter.highlightFile(inFile, outFile); 0200 0201 /** 0202 * compare results 0203 */ 0204 compareFiles(refFile, outFile); 0205 } 0206 }; 0207 0208 QTEST_GUILESS_MAIN(TestHighlighterTest) 0209 0210 #include "testhighlighter.moc"