File indexing completed on 2024-05-05 04:35:28

0001 /*
0002  * This file is part of KDevelop Krazy2 Plugin.
0003  *
0004  * Copyright 2012 Daniel Calviño Sánchez <danxuliu@gmail.com>
0005  *
0006  * This program is free software; you can redistribute it and/or
0007  * modify it under the terms of the GNU General Public License
0008  * as published by the Free Software Foundation; either version 2
0009  * of the License, or (at your option) any later version.
0010  *
0011  * This program is distributed in the hope that it will be useful,
0012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014  * GNU General Public License for more details.
0015  *
0016  * You should have received a copy of the GNU General Public License
0017  * along with this program. If not, see <http://www.gnu.org/licenses/>.
0018  */
0019 
0020 #include <QTest>
0021 
0022 #include <QSignalSpy>
0023 #include <KLocalizedString>
0024 #include <KSharedConfig>
0025 #include <QTimer>
0026 
0027 #include <KConfigGroup>
0028 
0029 #include "../checkerlistjob.h"
0030 #include "../checker.h"
0031 
0032 //Needed for qRegisterMetaType
0033 Q_DECLARE_METATYPE(KJob*)
0034 
0035 /**
0036  * Modified version of private KSignalSpy found in
0037  * kdelibs/kdecore/util/qtest_kde.cpp.
0038  * Original KDESignalSpy, accessed through
0039  * QTest::kWaitForSignal(QObject*, const char*, int), can miss a signal if it is
0040  * emitted too quickly (that is, before the connect is reached). This modified
0041  * version, instead of starting the wait in the constructor, has a specific
0042  * method for it. So the object can be created before executing the call that
0043  * emits the signal, enabling it to register the signal before starting to wait
0044  * and thus ensuring that no signal will be missed.
0045  */
0046 class SignalSpy: public QObject {
0047 Q_OBJECT
0048 public:
0049     SignalSpy(QObject* object, const char* signal): QObject(nullptr),
0050         m_signalSpy(object, signal) {
0051         connect(object, signal, this, SLOT(signalEmitted()));
0052     }
0053 
0054     bool waitForSignal(int timeout = 0) {
0055         if (m_signalSpy.count() == 0) {
0056             if (timeout > 0) {
0057                 QObject::connect(&m_timer, SIGNAL(timeout()), &m_loop, SLOT(quit()));
0058                 m_timer.setSingleShot(true);
0059                 m_timer.start(timeout);
0060             }
0061             m_loop.exec(); //krazy:exclude=crashy
0062         }
0063 
0064         if (m_signalSpy.count() >= 0) {
0065             return true;
0066         }
0067         return false;
0068     }
0069 private Q_SLOTS:
0070     void signalEmitted() {
0071         m_timer.stop();
0072         m_loop.quit();
0073     }
0074 private:
0075     QSignalSpy m_signalSpy;
0076     QEventLoop m_loop;
0077     QTimer m_timer;
0078 };
0079 
0080 class CheckerListJobTest: public QObject {
0081 Q_OBJECT
0082 private slots:
0083 
0084     void initTestCase();
0085     void init();
0086     void cleanup();
0087 
0088     void testConstructor();
0089 
0090     void testRun();
0091     void testRunWithInvalidPaths();
0092 
0093     void testKill();
0094 
0095 private:
0096 
0097     QList<const Checker*>* m_checkerList;
0098 
0099     bool krazy2InPath() const;
0100 
0101     const Checker* findChecker(const QList<const Checker*>* checkerList,
0102                                const QString& checkerName, const QString& fileType) const;
0103 
0104 };
0105 
0106 void CheckerListJobTest::initTestCase() {
0107     //Needed for SignalSpy
0108     qRegisterMetaType<KJob*>();
0109 }
0110 
0111 void CheckerListJobTest::init() {
0112     m_checkerList = new QList<const Checker*>();
0113 }
0114 
0115 void CheckerListJobTest::cleanup() {
0116     qDeleteAll(*m_checkerList);
0117     delete m_checkerList;
0118 }
0119 
0120 void CheckerListJobTest::testConstructor() {
0121     QObject parent;
0122     auto  checkerListJob = new CheckerListJob(&parent);
0123 
0124     QCOMPARE(checkerListJob->parent(), &parent);
0125     QCOMPARE(checkerListJob->capabilities(), KJob::Killable);
0126 }
0127 
0128 void CheckerListJobTest::testRun() {
0129     if (!krazy2InPath()) {
0130         QSKIP("krazy2 is not in the execution path", SkipAll);
0131     }
0132 
0133     CheckerListJob checkerListJob;
0134     checkerListJob.setAutoDelete(false);
0135     checkerListJob.setCheckerList(m_checkerList);
0136 
0137     KConfigGroup krazy2Configuration = KSharedConfig::openConfig()->group("Krazy2");
0138     krazy2Configuration.writeEntry("krazy2 Path", "krazy2");
0139 
0140     SignalSpy resultSpy(&checkerListJob, SIGNAL(result(KJob*)));
0141 
0142     checkerListJob.start();
0143 
0144     resultSpy.waitForSignal();
0145 
0146     QCOMPARE(checkerListJob.error(), (int)KJob::NoError);
0147 
0148     //The full list of checkers may change between Krazy2 versions (maybe even
0149     //between the systems where it is installed), so just some checkers are
0150     //verified
0151     QVERIFY(m_checkerList->count() > 0);
0152 
0153     const Checker* checker = findChecker(m_checkerList, "doublequote_chars", "c++");
0154     QVERIFY(checker);
0155     QCOMPARE(checker->description(),
0156              QString("Check single-char QString operations for efficiency"));
0157     QVERIFY(checker->explanation().startsWith(
0158                 "Adding single characters to a QString is faster"));
0159     QCOMPARE(checker->isExtra(), false);
0160 
0161     const Checker* checker2 = findChecker(m_checkerList, "endswithnewline", "c++");
0162     QVERIFY(checker2);
0163     QCOMPARE(checker2->description(),
0164              QString("Check that file ends with a newline"));
0165     QVERIFY(checker2->explanation().startsWith(
0166                 "Files that do not end with a newline character"));
0167     QCOMPARE(checker2->isExtra(), false);
0168 
0169     const Checker* checker3 = findChecker(m_checkerList, "endswithnewline", "designer");
0170     QVERIFY(checker3);
0171     QCOMPARE(checker3->description(),
0172              QString("Check that file ends with a newline"));
0173     QVERIFY(checker3->explanation().startsWith(
0174                 "Files that do not end with a newline character"));
0175     QCOMPARE(checker3->isExtra(), false);
0176     QVERIFY(checker3 != checker2);
0177 
0178     const Checker* checker4 = findChecker(m_checkerList, "contractions", "c++");
0179     QVERIFY(checker4);
0180     QCOMPARE(checker4->description(),
0181              QString("Check for contractions in strings"));
0182     QVERIFY(checker4->explanation().startsWith(
0183                 "The KDE Style Guide recommends not using contractions"));
0184     QCOMPARE(checker4->isExtra(), true);
0185 
0186     const Checker* checker5 = findChecker(m_checkerList, "contractions", "desktop");
0187     QVERIFY(checker5);
0188     QCOMPARE(checker5->description(),
0189              QString("Check for contractions in strings"));
0190     QVERIFY(checker5->explanation().startsWith(
0191                 "The KDE Style Guide recommends not using contractions"));
0192     QCOMPARE(checker5->isExtra(), true);
0193     QVERIFY(checker5 != checker4);
0194 }
0195 
0196 void CheckerListJobTest::testRunWithInvalidPaths() {
0197     CheckerListJob checkerListJob;
0198     checkerListJob.setAutoDelete(false);
0199     checkerListJob.setCheckerList(m_checkerList);
0200 
0201     KConfigGroup krazy2Configuration = KSharedConfig::openConfig()->group("Krazy2");
0202     krazy2Configuration.writeEntry("krazy2 Path", "invalid/krazy2/path");
0203 
0204     SignalSpy resultSpy(&checkerListJob, SIGNAL(result(KJob*)));
0205 
0206     checkerListJob.start();
0207 
0208     resultSpy.waitForSignal();
0209 
0210     QCOMPARE(checkerListJob.error(), (int)KJob::UserDefinedError);
0211     QCOMPARE(checkerListJob.errorString(),
0212              xi18nc("@info", "<para><command>krazy2</command> failed to start "
0213                              "using the path "
0214                              "(<filename>%1</filename>).</para>", "invalid/krazy2/path"));
0215     QCOMPARE(m_checkerList->count(), 0);
0216 }
0217 
0218 void CheckerListJobTest::testKill() {
0219     if (!krazy2InPath()) {
0220         QSKIP("krazy2 is not in the execution path", SkipAll);
0221     }
0222 
0223     CheckerListJob checkerListJob;
0224     checkerListJob.setAutoDelete(false);
0225     checkerListJob.setCheckerList(m_checkerList);
0226 
0227     KConfigGroup krazy2Configuration = KSharedConfig::openConfig()->group("Krazy2");
0228     krazy2Configuration.writeEntry("krazy2 Path", "krazy2");
0229 
0230     SignalSpy resultSpy(&checkerListJob, SIGNAL(result(KJob*)));
0231 
0232     checkerListJob.start();
0233 
0234     QTest::qWait(500);
0235 
0236     checkerListJob.kill(KJob::EmitResult);
0237 
0238     resultSpy.waitForSignal();
0239 
0240     QCOMPARE(checkerListJob.error(), (int)KJob::KilledJobError);
0241     QCOMPARE(m_checkerList->count(), 0);
0242 }
0243 
0244 ///////////////////////////////// Helpers //////////////////////////////////////
0245 
0246 bool CheckerListJobTest::krazy2InPath() const {
0247     //QProcess::exec is not used, as the static method uses ForwardedChannels
0248     QProcess process;
0249     process.start("krazy2 --version");
0250     process.waitForFinished();
0251 
0252     if (process.error() == QProcess::FailedToStart) {
0253         return false;
0254     }
0255 
0256     return true;
0257 }
0258 
0259 const Checker* CheckerListJobTest::findChecker(const QList<const Checker*>* checkerList,
0260                                                const QString& checkerName, const QString& fileType) const {
0261     foreach (const Checker* checker, *checkerList) {
0262         if (checker->name() == checkerName &&
0263             checker->fileType() == fileType) {
0264             return checker;
0265         }
0266     }
0267 
0268     return nullptr;
0269 }
0270 
0271 QTEST_GUILESS_MAIN(CheckerListJobTest)
0272 
0273 #include "checkerlistjobtest.moc"