File indexing completed on 2024-10-06 06:43:59

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 2004 Arend van Beelen jr. <arend@auton.nl>
0004     SPDX-FileCopyrightText: 2010 David Faure <faure@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-only
0007 */
0008 
0009 #include "kfindtest.h"
0010 
0011 #include <kfind.h>
0012 
0013 #include <QRegularExpression>
0014 #include <QTest>
0015 
0016 #include <assert.h>
0017 
0018 void KFindRecorder::changeText(int line, const QString &text)
0019 {
0020     Q_ASSERT(line < m_text.count());
0021     Q_ASSERT(m_find != nullptr);
0022 
0023     m_line = line;
0024     m_text[line] = text;
0025     m_find->setData(line, text);
0026 }
0027 
0028 KFindRecorder::KFindRecorder(const QStringList &text)
0029     : QObject(nullptr)
0030     , m_text(text)
0031     , m_line(0)
0032 {
0033 }
0034 
0035 KFindRecorder::~KFindRecorder()
0036 {
0037 }
0038 
0039 void KFindRecorder::find(const QString &pattern, long options)
0040 {
0041     m_find.reset(new KFind(pattern, options, nullptr));
0042     // Prevent dialogs from popping up
0043     m_find->closeFindNextDialog();
0044 
0045     connect(m_find.get(), &KFind::textFound, this, [this](const QString &text, int matchingIndex, int matchedLength) {
0046         slotHighlight(text, matchingIndex, matchedLength);
0047     });
0048 
0049     connect(m_find.get(), &KFind::textFoundAtId, this, [this](int id, int matchingIndex, int matchedLength) {
0050         slotHighlight(id, matchingIndex, matchedLength);
0051     });
0052 
0053     m_line = 0;
0054     KFind::Result result = KFind::NoMatch;
0055     do {
0056         if (options & KFind::FindIncremental) {
0057             m_find->setData(m_line, m_text[m_line]);
0058         } else {
0059             m_find->setData(m_text[m_line]);
0060         }
0061 
0062         m_line++;
0063 
0064         result = m_find->find();
0065     } while (result == KFind::NoMatch && m_line < m_text.count());
0066 }
0067 
0068 bool KFindRecorder::findNext(const QString &pattern)
0069 {
0070     Q_ASSERT(m_find != nullptr);
0071 
0072     if (!pattern.isNull()) {
0073         m_find->setPattern(pattern);
0074     }
0075 
0076     KFind::Result result = KFind::NoMatch;
0077     do {
0078         // qDebug() << "m_line: " << m_line;
0079 
0080         result = m_find->find();
0081 
0082         if (result == KFind::NoMatch && m_line < m_text.count()) {
0083             // qDebug() << "incrementing m_line...";
0084             if (m_find->options() & KFind::FindIncremental) {
0085                 m_find->setData(m_line, m_text[m_line]);
0086             } else {
0087                 m_find->setData(m_text[m_line]);
0088             }
0089 
0090             m_line++;
0091         }
0092     } while (result == KFind::NoMatch && m_line < m_text.count());
0093     // qDebug() << "find next completed" << m_line;
0094 
0095     return result != KFind::NoMatch;
0096 }
0097 
0098 void KFindRecorder::slotHighlight(const QString &text, int index, int matchedLength)
0099 {
0100     m_hits.append(QLatin1String("line: \"") + text + QLatin1String("\", index: ") + QString::number(index) + QLatin1String(", length: ")
0101                   + QString::number(matchedLength) + QLatin1Char('\n'));
0102 }
0103 
0104 void KFindRecorder::slotHighlight(int id, int index, int matchedLength)
0105 {
0106     m_hits.append(QLatin1String("line: \"") + m_text[id] + QLatin1String("\", index: ") + QString::number(index) + QLatin1String(", length: ")
0107                   + QString::number(matchedLength) + QLatin1Char('\n'));
0108 }
0109 
0110 ////
0111 
0112 TestKFind::TestKFind()
0113     : QObject()
0114 {
0115     m_text = QLatin1String("This file is part of the KDE project.\n") + QLatin1String("This library is free software; you can redistribute it and/or\n")
0116         + QLatin1String("modify it under the terms of the GNU Library General Public\n")
0117         + QLatin1String("License version 2, as published by the Free Software Foundation.\n") + QLatin1Char('\n')
0118         + QLatin1String("    This library is distributed in the hope that it will be useful,\n")
0119         + QLatin1String("    but WITHOUT ANY WARRANTY; without even the implied warranty of\n")
0120         + QLatin1String("    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n")
0121         + QLatin1String("    Library General Public License for more details.\n") + QLatin1Char('\n')
0122         + QLatin1String("    You should have received a copy of the GNU Library General Public License\n")
0123         + QLatin1String("    along with this library; see the file COPYING.LIB.  If not, write to\n")
0124         + QLatin1String("    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,\n") + QLatin1String("    Boston, MA 02110-1301, USA.\n");
0125 }
0126 
0127 void TestKFind::testStaticFindRegexp_data()
0128 {
0129     // Tests for the core method "static KFind::find"
0130     QTest::addColumn<QString>("text");
0131     QTest::addColumn<QString>("pattern");
0132     QTest::addColumn<int>("startIndex");
0133     QTest::addColumn<int>("options");
0134     QTest::addColumn<int>("expectedResult");
0135     QTest::addColumn<int>("expectedMatchedLength");
0136 
0137     /* clang-format off */
0138     QTest::newRow("simple (0)") << "abc" << "a" << 0 << 0 << 0 << 1;
0139     QTest::newRow("simple (1)") << "abc" << "b" << 0 << 0 << 1 << 1;
0140     QTest::newRow("not found") << "abca" << "ba" << 0 << 0 << -1 << 0;
0141     QTest::newRow("from index") << "abc bc" << "b" << 3 << 0 << 4 << 1;
0142     QTest::newRow("from exact index") << "abc bc" << "b" << 4 << 0 << 4 << 1;
0143     QTest::newRow("past index (not found)") << "abc bc" << "b" << 5 << 0 << -1 << 0;
0144     QTest::newRow("dot") << "abc" << "b." << 0 << 0 << 1 << 2;
0145     QTest::newRow("^simple") << "text" << "^tex" << 0 << 0 << 0 << 3;
0146     QTest::newRow("^multiline first") << "foo\nbar" << "^f" << 0 << 0 << 0 << 1;
0147     QTest::newRow("^multiline last") << "foo\nbar" << "^bar" << 0 << 0 << 4 << 3;
0148     QTest::newRow("^multiline with index") << "boo\nbar" << "^b" << 1 << 0 << 4 << 1;
0149     QTest::newRow("simple$") << "text" << "xt$" << 0 << 0 << 2 << 2;
0150     QTest::newRow("$ backwards") << "text" << "xt$" << 4 << int(KFind::FindBackwards) << 2 << 2;
0151     QTest::newRow("multiline$") << "foo\nbar" << "oo$" << 0 << 0 << 1 << 2;
0152     QTest::newRow("multiline$ intermediary line") << "foo\nbar\nagain bar" << "r$" << 0 << 0 << 6 << 1;
0153     QTest::newRow("multiline$ with index, last line") << "foo\nbar\nagain bar" << "r$" << 7 << 0 << 16 << 1;
0154     QTest::newRow("multiline$ backwards") << "foo\nbar" << "oo$" << 7 << int(KFind::FindBackwards) << 1 << 2;
0155     QTest::newRow("multiline with \\n") << "foo\nbar" << "o\nb" << 0 << 0 << 2 << 3;
0156     QTest::newRow("whole words ok") << "abc bcbc bc bmore be" << "b." << 0 << int(KFind::WholeWordsOnly) << 9 << 2;
0157     QTest::newRow("whole words not found") << "abab abx" << "ab" << 0 << int(KFind::WholeWordsOnly) << -1 << 0;
0158     QTest::newRow("whole words not found (_)") << "abab ab_" << "ab" << 0 << int(KFind::WholeWordsOnly) << -1 << 0;
0159     QTest::newRow("whole words ok (.)") << "ab." << "ab" << 0 << int(KFind::WholeWordsOnly) << 0 << 2;
0160     QTest::newRow("backwards") << "abc bcbc bc" << "b." << 10 << int(KFind::FindBackwards) << 9 << 2;
0161     QTest::newRow("empty (0)") << "a" << "" << 0 << int(0) << 0 << 0;
0162     QTest::newRow("empty (1)") << "a" << "" << 1 << int(0) << 1 << 0; // kreplacetest testReplaceBlankSearch relies on this
0163     QTest::newRow("at end, not found") << "a" << "b" << 1 << int(0) << -1 << 0; // just for catching the while(index<text.length()) bug
0164     QTest::newRow("back, not found") << "a" << "b" << 0 << int(KFind::FindBackwards) << -1 << 0;
0165     QTest::newRow("back, at begin, found") << "a" << "a" << 0 << int(KFind::FindBackwards) << 0 << 1;
0166     QTest::newRow("back, at end, found") << "a" << "a" << 1 << int(KFind::FindBackwards) << 0 << 1;
0167     QTest::newRow("back, text shorter than pattern") << "a" << "abcd" << 0 << int(KFind::FindBackwards) << -1 << 0;
0168     /* clang-format on */
0169 }
0170 
0171 void TestKFind::testStaticFindRegexp()
0172 {
0173     // Tests for the core method "static KFind::find(text, regexp)"
0174     QFETCH(QString, text);
0175     QFETCH(QString, pattern);
0176     QFETCH(int, startIndex);
0177     QFETCH(int, options);
0178     QFETCH(int, expectedResult);
0179     QFETCH(int, expectedMatchedLength);
0180 
0181     int matchedLength = 0;
0182     const int result2 = KFind::find(text, pattern, startIndex, options | KFind::RegularExpression, &matchedLength, nullptr);
0183     QCOMPARE(result2, expectedResult);
0184     QCOMPARE(matchedLength, expectedMatchedLength);
0185 }
0186 
0187 void TestKFind::testRegexpUnicode_data()
0188 {
0189     QTest::addColumn<QString>("text");
0190     QTest::addColumn<QString>("pattern");
0191     QTest::addColumn<int>("startIndex");
0192     QTest::addColumn<int>("options");
0193     QTest::addColumn<int>("expectedResult");
0194     QTest::addColumn<int>("expectedMatchedLength");
0195 
0196     /* clang-format off */
0197     // Test matching with Unicode properties in QRegularExpression
0198     QTest::newRow("unicode-word-boundary") << "aoé" << "\\b" << 1 << 0 << 3 << 0;
0199     QTest::newRow("unicode-word-char") << "aoé" << "\\w$" << 0 << 0 << 2 << 1;
0200     QTest::newRow("unicode-non-word-char") << "aoé" << "\\W" << 0 << 0 << -1 << 0;
0201     /* clang-format on */
0202 }
0203 
0204 void TestKFind::testRegexpUnicode()
0205 {
0206     // Tests for the core method "static KFind::find(text, regexp)"
0207     QFETCH(QString, text);
0208     QFETCH(QString, pattern);
0209     QFETCH(int, startIndex);
0210     QFETCH(int, options);
0211     QFETCH(int, expectedResult);
0212     QFETCH(int, expectedMatchedLength);
0213 
0214     int matchedLength = 0;
0215 
0216     const int result = KFind::find(text, pattern, startIndex, options | KFind::RegularExpression, &matchedLength, nullptr);
0217     QCOMPARE(result, expectedResult);
0218     QCOMPARE(matchedLength, expectedMatchedLength);
0219 }
0220 
0221 void TestKFind::testSimpleSearch()
0222 {
0223     // first we do a simple text searching the text and doing a few find nexts
0224     KFindRecorder test(m_text.split(QLatin1Char('\n')));
0225     test.find(QStringLiteral("This"), 0);
0226     while (test.findNext()) { }
0227 
0228     const QString output1 = QLatin1String("line: \"This file is part of the KDE project.\", index: 0, length: 4\n")
0229         + QLatin1String("line: \"This library is free software; you can redistribute it and/or\", index: 0, length: 4\n")
0230         + QLatin1String("line: \"    This library is distributed in the hope that it will be useful,\", index: 4, length: 4\n")
0231         + QLatin1String("line: \"    along with this library; see the file COPYING.LIB.  If not, write to\", index: 15, length: 4\n");
0232 
0233     QCOMPARE(test.hits().join(QString()), output1);
0234 }
0235 
0236 void TestKFind::testSimpleRegexp()
0237 {
0238     KFindRecorder test(m_text.split(QLatin1Char('\n')));
0239     test.find(QStringLiteral("W.R+ANT[YZ]"), KFind::RegularExpression | KFind::CaseSensitive);
0240     while (test.findNext()) { }
0241     const QString output = QStringLiteral("line: \"    but WITHOUT ANY WARRANTY; without even the implied warranty of\", index: 20, length: 8\n");
0242     QCOMPARE(test.hits().join(QString()), output);
0243 }
0244 
0245 void TestKFind::testLineBeginRegularExpression()
0246 {
0247     int matchedLength;
0248     QRegularExpressionMatch match;
0249     KFind::find(m_text, QStringLiteral("^License.+"), 0, KFind::RegularExpression, &matchedLength, &match);
0250     QCOMPARE(match.captured(0), QStringLiteral("License version 2, as published by the Free Software Foundation."));
0251 }
0252 
0253 void TestKFind::testFindIncremental()
0254 {
0255     // FindIncremental with static contents...
0256 
0257     KFindRecorder test(m_text.split(QLatin1Char('\n')));
0258     test.find(QString(), KFind::FindIncremental);
0259     test.findNext(QStringLiteral("i"));
0260     test.findNext(QStringLiteral("is"));
0261     test.findNext(QStringLiteral("ist"));
0262     test.findNext();
0263     test.findNext(QStringLiteral("istri"));
0264     test.findNext(QStringLiteral("istr"));
0265     test.findNext(QStringLiteral("ist"));
0266     test.findNext(QStringLiteral("is"));
0267     test.findNext(QStringLiteral("W"));
0268     test.findNext(QStringLiteral("WA"));
0269     test.findNext(QStringLiteral("WARRANTY"));
0270     test.findNext(QStringLiteral("Free"));
0271     test.findNext(QStringLiteral("Software Foundation"));
0272 
0273     const QString output2 = QLatin1String("line: \"This file is part of the KDE project.\", index: 0, length: 0\n")
0274         + QLatin1String("line: \"This file is part of the KDE project.\", index: 2, length: 1\n")
0275         + QLatin1String("line: \"This file is part of the KDE project.\", index: 2, length: 2\n")
0276         + QLatin1String("line: \"This library is free software; you can redistribute it and/or\", index: 42, length: 3\n")
0277         + QLatin1String("line: \"    This library is distributed in the hope that it will be useful,\", index: 21, length: 3\n")
0278         + QLatin1String("line: \"    This library is distributed in the hope that it will be useful,\", index: 21, length: 5\n")
0279         + QLatin1String("line: \"    This library is distributed in the hope that it will be useful,\", index: 21, length: 4\n")
0280         + QLatin1String("line: \"    This library is distributed in the hope that it will be useful,\", index: 21, length: 3\n")
0281         + QLatin1String("line: \"This file is part of the KDE project.\", index: 2, length: 2\n")
0282         + QLatin1String("line: \"This library is free software; you can redistribute it and/or\", index: 25, length: 1\n")
0283         + QLatin1String("line: \"This library is free software; you can redistribute it and/or\", index: 25, length: 2\n")
0284         + QLatin1String("line: \"    but WITHOUT ANY WARRANTY; without even the implied warranty of\", index: 20, length: 8\n")
0285         + QLatin1String("line: \"This library is free software; you can redistribute it and/or\", index: 16, length: 4\n")
0286         + QLatin1String("line: \"License version 2, as published by the Free Software Foundation.\", index: 44, length: 19\n");
0287 
0288     QCOMPARE(test.hits().join(QString()), output2);
0289 }
0290 
0291 void TestKFind::testFindIncrementalDynamic()
0292 {
0293     // Now do that again but with pages that change between searches
0294     KFindRecorder test(m_text.split(QLatin1Char('\n')));
0295 
0296     test.find(QString(), KFind::FindIncremental);
0297     test.findNext(QStringLiteral("i"));
0298     test.findNext(QStringLiteral("is"));
0299     test.findNext(QStringLiteral("ist"));
0300     test.findNext(QStringLiteral("istr"));
0301     test.findNext();
0302     test.changeText(1, QStringLiteral("The second line now looks a whole lot different."));
0303     test.findNext(QStringLiteral("istri"));
0304     test.findNext(QStringLiteral("istr"));
0305     test.findNext(QStringLiteral("ist"));
0306     test.findNext(QStringLiteral("is"));
0307     test.findNext(QStringLiteral("i"));
0308     test.findNext(QStringLiteral("W"));
0309     test.findNext(QStringLiteral("WA"));
0310     test.findNext(QStringLiteral("WARRANTY"));
0311     test.changeText(6, QStringLiteral("    but WITHOUT ANY xxxx; without even the implied warranty of"));
0312     test.findNext(QStringLiteral("WARRAN"));
0313     test.findNext(QStringLiteral("Free"));
0314     test.findNext(QStringLiteral("Software Foundation"));
0315 
0316     const QString output3 = QLatin1String("line: \"This file is part of the KDE project.\", index: 0, length: 0\n")
0317         + QLatin1String("line: \"This file is part of the KDE project.\", index: 2, length: 1\n")
0318         + QLatin1String("line: \"This file is part of the KDE project.\", index: 2, length: 2\n")
0319         + QLatin1String("line: \"This library is free software; you can redistribute it and/or\", index: 42, length: 3\n")
0320         + QLatin1String("line: \"This library is free software; you can redistribute it and/or\", index: 42, length: 4\n")
0321         + QLatin1String("line: \"    This library is distributed in the hope that it will be useful,\", index: 21, length: 4\n")
0322         + QLatin1String("line: \"    This library is distributed in the hope that it will be useful,\", index: 21, length: 5\n")
0323         + QLatin1String("line: \"    This library is distributed in the hope that it will be useful,\", index: 21, length: 4\n")
0324         + QLatin1String("line: \"    This library is distributed in the hope that it will be useful,\", index: 21, length: 3\n")
0325         + QLatin1String("line: \"This file is part of the KDE project.\", index: 2, length: 2\n")
0326         + QLatin1String("line: \"This file is part of the KDE project.\", index: 2, length: 1\n")
0327         + QLatin1String("line: \"The second line now looks a whole lot different.\", index: 18, length: 1\n")
0328         + QLatin1String("line: \"License version 2, as published by the Free Software Foundation.\", index: 48, length: 2\n")
0329         + QLatin1String("line: \"    but WITHOUT ANY WARRANTY; without even the implied warranty of\", index: 20, length: 8\n")
0330         + QLatin1String("line: \"    but WITHOUT ANY xxxx; without even the implied warranty of\", index: 51, length: 6\n")
0331         + QLatin1String("line: \"License version 2, as published by the Free Software Foundation.\", index: 39, length: 4\n")
0332         + QLatin1String("line: \"License version 2, as published by the Free Software Foundation.\", index: 44, length: 19\n");
0333 
0334     QCOMPARE(test.hits().join(QString()), output3);
0335 }
0336 
0337 QTEST_MAIN(TestKFind)
0338 
0339 #include "moc_kfindtest.cpp"