File indexing completed on 2024-04-14 14:19:40

0001 /* This file is part of the KDE libraries
0002     Copyright (c) 2009 David Faure <faure@kde.org>
0003 
0004     This library is free software; you can redistribute it and/or modify
0005     it under the terms of the GNU Lesser General Public License as published by
0006     the Free Software Foundation; either version 2 of the License or ( at
0007     your option ) version 3 or, at the discretion of KDE e.V. ( which shall
0008     act as a proxy as in section 14 of the GPLv3 ), any later version.
0009 
0010     This library is distributed in the hope that it will be useful,
0011     but WITHOUT ANY WARRANTY; without even the implied warranty of
0012     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013     Library General Public License for more details.
0014 
0015     You should have received a copy of the GNU Library General Public License
0016     along with this library; see the file COPYING.LIB.  If not, write to
0017     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0018     Boston, MA 02110-1301, USA.
0019 */
0020 
0021 #include "kdebug_unittest.h"
0022 #include <qstandardpaths.h>
0023 #include <kconfig.h>
0024 #include <kconfiggroup.h>
0025 #include <QTest>
0026 #include <kdebug.h>
0027 #include <QProcess>
0028 
0029 // Set up the env before the first qDebug/qWarning (e.g. from kcrash)
0030 void setEnvironmentVariables()
0031 {
0032     qputenv("KDE_DEBUG_TIMESTAMP", "");
0033     qputenv("QT_MESSAGE_PATTERN", "%{appname}(%{pid})/%{category} %{function}: %{message}");
0034 }
0035 Q_CONSTRUCTOR_FUNCTION(setEnvironmentVariables)
0036 
0037 QTEST_MAIN(KDebugTest)
0038 
0039 void KDebugTest::initTestCase()
0040 {
0041     // The source files (kdebugrc and kdebug.areas) are in the "global" config dir:
0042     qputenv("XDG_CONFIG_DIRS", QFile::encodeName(QFileInfo(QFINDTESTDATA("../src/kdebug.areas")).absolutePath()));
0043 
0044     QStandardPaths::setTestModeEnabled(true);
0045 
0046     QString kdebugrc = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + "kdebugrc";
0047     if (!kdebugrc.isEmpty()) {
0048         QFile::remove(kdebugrc);
0049     }
0050     QFile::remove("kdebug.dbg");
0051     QFile::remove("myarea.dbg");
0052 
0053     // Check that we can find kdebugrc and kdebug.areas
0054     QString filename(QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QLatin1String("kdebug.areas")));
0055     QVERIFY2(QFile::exists(filename), filename.toLatin1() + " not found");
0056     QVERIFY(QFile::exists(QFINDTESTDATA("../src/kdebugrc")));
0057 
0058     // Now set up logging to file
0059     KConfig config("kdebugrc");
0060     config.group(QString()).writeEntry("DisableAll", false); // in case of a global kdebugrc with DisableAll=true
0061     config.group("180").writeEntry("InfoOutput", 0 /*FileOutput*/);
0062     config.group("myarea").writeEntry("InfoOutput", 0 /*FileOutput*/);
0063     config.group("myarea").writeEntry("InfoFilename", "myarea.dbg");
0064     config.group("kdebug_unittest").writeEntry("InfoOutput", 0 /*FileOutput*/);
0065     config.group("kdebug_unittest").writeEntry("WarnOutput", 0 /*FileOutput*/);
0066     config.sync();
0067 
0068     //QCOMPARE(KDebug::hasNullOutput(QtDebugMsg, true, 0, false), false);
0069 
0070     // Test for crash that used to happen when using an unknown area after only dynamic areas
0071     KDebug::registerArea("somearea"); // gets number 1
0072     KDebug::registerArea("someotherarea"); // gets number 2
0073     QCOMPARE(KDebug::hasNullOutput(QtDebugMsg, true, 4242, false), false); // unknown area -> area 0 is being used
0074 
0075     kClearDebugConfig();
0076 }
0077 
0078 void KDebugTest::cleanupTestCase()
0079 {
0080     QString kdebugrc = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + "kdebugrc";
0081     if (!kdebugrc.isEmpty()) {
0082         QFile::remove(kdebugrc);
0083     }
0084     // TODO QFile::remove("kdebug.dbg");
0085     QFile::remove("myarea.dbg");
0086 }
0087 
0088 static QList<QByteArray> readLines(const char *fileName = "kdebug.dbg")
0089 {
0090     const QString path = QFile::decodeName(fileName);
0091     Q_ASSERT(!path.isEmpty());
0092     Q_ASSERT(QFile::exists(path));
0093     QFile file(path);
0094     const bool opened = file.open(QIODevice::ReadOnly);
0095     Q_ASSERT(opened);
0096     Q_UNUSED(opened);
0097     QList<QByteArray> lines;
0098     QByteArray line;
0099     do {
0100         line = file.readLine();
0101         if (!line.isEmpty()) {
0102             lines.append(line);
0103         }
0104     } while (!line.isEmpty());
0105     return lines;
0106 }
0107 
0108 void KDebugTest::compareLines(const QList<QByteArray> &expectedLines, const char *fileName)
0109 {
0110     QList<QByteArray> lines = readLines(fileName);
0111     //qDebug() << lines;
0112     QCOMPARE(lines.count(), expectedLines.count());
0113     QVERIFY(lines[0].endsWith('\n'));
0114     for (int i = 0; i < lines.count(); ++i) {
0115         QByteArray line = lines[i];
0116         if (expectedLines[i].contains("[...]")) {
0117             const int pos = line.indexOf('[');
0118             QVERIFY(pos >= 0);
0119             line.truncate(pos);
0120             line.append("[...]\n");
0121         }
0122         //qDebug() << "line" << i << ":" << line << expectedLines[i];
0123         QVERIFY2(line.endsWith(expectedLines[i]), "Got '" + line + "'\nexpected '" + expectedLines[i] + "'");
0124     }
0125 }
0126 
0127 // Test what happens when a operator<< calls a method that itself uses kDebug,
0128 // meaning that two kDebug instances will be active at the same time.
0129 // In this case it works, but technically, if area 180 was configured with
0130 // a different output file than area 0 then the output would currently go
0131 // into the wrong file (because the stream is static) (the "after the call" string
0132 // would go into the file for area 180)
0133 class TestClass
0134 {
0135 public:
0136     TestClass() {}
0137     QString getSomething() const
0138     {
0139         kDebug(180) << "Nested kDebug call";
0140         return "TestClass";
0141     }
0142 };
0143 QDebug operator<<(QDebug s, const TestClass &me)
0144 {
0145     s << me.getSomething() << "after the call";
0146     return s;
0147 }
0148 
0149 void KDebugTest::testDebugToFile()
0150 {
0151     kDebug(180) << "TEST DEBUG 180";
0152     kDebug(0) << "TEST DEBUG 0";
0153     kWarning() << "TEST WARNING 0";
0154     // The calls to kDebug(0) created a dynamic debug area named after the componentdata name
0155     KConfig config("kdebugrc");
0156     QVERIFY(config.hasGroup("kdebug_unittest"));
0157     kDebug(0) << "TEST DEBUG with newline" << endl << "newline";
0158     TestClass tc;
0159     kDebug(0) << "Re-entrance test" << tc << "[ok]";
0160     {
0161         KDebug::Block block("block 1");
0162         {
0163             KDebug::Block block("block 2");
0164         }
0165     }
0166     QVERIFY(QFile::exists("kdebug.dbg"));
0167     QList<QByteArray> expected;
0168     expected << "/kdecore (kdelibs) KDebugTest::testDebugToFile: TEST DEBUG 180\n";
0169     expected << "KDebugTest::testDebugToFile: TEST DEBUG 0\n";
0170     expected << "KDebugTest::testDebugToFile: TEST WARNING 0\n";
0171     expected << "KDebugTest::testDebugToFile: TEST DEBUG with newline\n";
0172     expected << "newline\n";
0173     expected << "/kdecore (kdelibs) TestClass::getSomething: Nested kDebug call\n";
0174     expected << "Re-entrance test \"TestClass\" after the call [ok]\n";
0175     expected << "BEGIN: block 1\n";
0176     expected << "BEGIN: block 2\n";
0177     expected << "END__: block 2 [...]\n";
0178     expected << "END__: block 1 [...]\n";
0179     compareLines(expected);
0180 }
0181 
0182 void KDebugTest::testDisableArea()
0183 {
0184     QFile::remove("kdebug.dbg");
0185     KConfig config("kdebugrc");
0186     config.group("180").writeEntry("InfoOutput", 4 /*NoOutput*/);
0187     config.group("kdebug_unittest").writeEntry("InfoOutput", 4 /*NoOutput*/);
0188     config.sync();
0189     kClearDebugConfig();
0190     kDebug(180) << "TEST DEBUG 180 - SHOULD NOT APPEAR";
0191     kDebug(0) << "TEST DEBUG 0 - SHOULD NOT APPEAR";
0192     {
0193         KDebug::Block block("SHOULD NOT APPEAR");
0194         kDebug(0) << "msg inside the block, should not appear";
0195     }
0196     QVERIFY(!QFile::exists("kdebug.dbg"));
0197 
0198     // Re-enable debug, for further tests
0199     config.group("180").writeEntry("InfoOutput", 0 /*FileOutput*/);
0200     config.group("kdebug_unittest").writeEntry("InfoOutput", 0 /*FileOutput*/);
0201     config.sync();
0202     kClearDebugConfig();
0203 }
0204 
0205 void KDebugTest::testDynamicArea()
0206 {
0207     const int myArea = KDebug::registerArea("myarea"); // gets number 3
0208     QCOMPARE(myArea, 3);
0209     KConfig config("kdebugrc");
0210     QVERIFY(!config.hasGroup(QString::number(myArea)));
0211     QVERIFY(config.hasGroup("myarea"));
0212     kDebug(myArea) << "TEST DEBUG using myArea" << myArea;
0213     QList<QByteArray> expected;
0214     expected << "/myarea KDebugTest::testDynamicArea: TEST DEBUG using myArea 3\n";
0215     compareLines(expected, "myarea.dbg");
0216 }
0217 
0218 void KDebugTest::testDisabledDynamicArea()
0219 {
0220     const int verboseArea = KDebug::registerArea("verbosearea", false);
0221     QVERIFY(verboseArea > 0);
0222     kClearDebugConfig(); // force a sync() of KDebug's own kdebugrc so that it gets written out
0223     KConfig config("kdebugrc");
0224     QVERIFY(config.hasGroup("verbosearea"));
0225     kDebug(verboseArea) << "TEST DEBUG using verboseArea" << verboseArea;
0226 }
0227 
0228 static void disableAll(bool dis)
0229 {
0230     KConfig config("kdebugrc");
0231     config.group(QString()).writeEntry("DisableAll", dis);
0232     config.sync();
0233     kClearDebugConfig();
0234 }
0235 
0236 void KDebugTest::testDisableAll()
0237 {
0238     // Some people really don't like debug output :-)
0239     disableAll(true);
0240     QFile::remove("kdebug.dbg");
0241     kDebug() << "Should not appear";
0242     kDebug(123465) << "Unknown area, should not appear either";
0243     QVERIFY(!QFile::exists("kdebug.dbg"));
0244     // Repair
0245     disableAll(false);
0246 }
0247 
0248 void KDebugTest::testHasNullOutput()
0249 {
0250     // When compiling in debug mode:
0251     QCOMPARE(KDebug::hasNullOutput(QtDebugMsg, true, 0, true), false);
0252     QCOMPARE(KDebug::hasNullOutput(QtDebugMsg, true, 180, true), false);
0253     QCOMPARE(KDebug::hasNullOutput(QtDebugMsg, true, 293, true), false);
0254     QCOMPARE(KDebug::hasNullOutput(QtDebugMsg, true, 4242, true), false);
0255 
0256     kClearDebugConfig(); // force dropping the cache
0257 
0258     // When compiling in release mode:
0259     QCOMPARE(KDebug::hasNullOutput(QtDebugMsg, true, 0, false), false); // controlled by "InfoOutput" key
0260     QCOMPARE(KDebug::hasNullOutput(QtDebugMsg, true, 180, false), false); // controlled by "InfoOutput" key
0261     QCOMPARE(KDebug::hasNullOutput(QtDebugMsg, true, 293, false), true); // no config -> the default is being used
0262     QCOMPARE(KDebug::hasNullOutput(QtDebugMsg, true, 4242, false), false); // unknown area -> area 0 is being used
0263 
0264     // And if we really have no config for area 0 (the app name)
0265     KConfig config("kdebugrc");
0266     config.deleteGroup("kdebug_unittest");
0267     config.sync();
0268     kClearDebugConfig();
0269 
0270     QCOMPARE(KDebug::hasNullOutput(QtDebugMsg, true, 0, false), true);
0271     QCOMPARE(KDebug::hasNullOutput(QtDebugMsg, true, 293, false), true);
0272     QCOMPARE(KDebug::hasNullOutput(QtDebugMsg, true, 4242, false), true);
0273 
0274     // Restore to normal for future tests
0275     config.group("kdebug_unittest").writeEntry("InfoOutput", 0 /*FileOutput*/);
0276     config.sync();
0277     kClearDebugConfig();
0278 }
0279 
0280 void KDebugTest::testNoMainComponentData()
0281 {
0282     // change to the bin dir
0283     QDir::setCurrent(QCoreApplication::applicationDirPath());
0284     // This test runs kdebug_qcoreapptest and checks its output
0285     QProcess proc;
0286     QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
0287     // No process info, to make this easier
0288     environment.insert("QT_MESSAGE_PATTERN", "%{category} %{function}: %{message}");
0289     proc.setProcessEnvironment(environment);
0290     proc.setProcessChannelMode(QProcess::SeparateChannels);
0291 #ifdef Q_OS_WIN
0292     proc.start("kdebug_qcoreapptest.exe");
0293 #else
0294     if (QFile::exists("./kdebug_qcoreapptest.shell")) {
0295         proc.start("./kdebug_qcoreapptest.shell");
0296     } else {
0297         QVERIFY(QFile::exists("./kdebug_qcoreapptest"));
0298         proc.start("./kdebug_qcoreapptest");
0299     }
0300 #endif
0301     //     kDebug() << proc.args();
0302     const bool ok = proc.waitForFinished();
0303     QVERIFY(ok);
0304     const QByteArray allOutput = proc.readAllStandardError();
0305     const QList<QByteArray> receivedLines = allOutput.split('\n');
0306     //qDebug() << receivedLines;
0307     QList<QByteArray> expectedLines;
0308     expectedLines << "qcoreapp_myarea main: Test debug using qcoreapp_myarea 1";
0309     expectedLines << "kdebug_qcoreapptest main: Debug in area 100";
0310     expectedLines << "kdebug_qcoreapptest main: Simple debug";
0311     expectedLines << "kdebug_qcoreapptest main: This should appear, under the kdebug_qcoreapptest area";
0312     expectedLines << "kdebug_qcoreapptest main: Debug in area 100";
0313     expectedLines << ""; // artefact of split, I guess?
0314     for (int i = 0; i < qMin(expectedLines.count(), receivedLines.count()); ++i) {
0315         QCOMPARE(QString::fromLatin1(receivedLines[i]), QString::fromLatin1(expectedLines[i]));
0316     }
0317     QCOMPARE(receivedLines.count(), expectedLines.count());
0318     QCOMPARE(receivedLines, expectedLines);
0319 }
0320 
0321 #include <QThreadPool>
0322 #include <QFutureSynchronizer>
0323 #include <qtconcurrentrun.h>
0324 
0325 class KDebugThreadTester
0326 {
0327 public:
0328     void doDebugs()
0329     {
0330         KDEBUG_BLOCK
0331         for (int i = 0; i < 10; ++i) {
0332             kDebug() << "A kdebug statement in a thread:" << i;
0333         }
0334     }
0335 };
0336 
0337 void KDebugTest::testMultipleThreads()
0338 {
0339     kDebug() << "kDebug works";
0340     QVERIFY(QFile::exists("kdebug.dbg"));
0341     QFile::remove("kdebug.dbg");
0342 
0343     KDebugThreadTester tester;
0344     QThreadPool::globalInstance()->setMaxThreadCount(10);
0345     QFutureSynchronizer<void> sync;
0346     for (int threadNum = 0; threadNum < 10; ++threadNum) {
0347         sync.addFuture(QtConcurrent::run(&tester, &KDebugThreadTester::doDebugs));
0348     }
0349     sync.waitForFinished();
0350 
0351     QVERIFY(QFile::exists("kdebug.dbg"));
0352 
0353     //QFile f("kdebug.dbg"); f.open(QIODevice::ReadOnly);
0354     //qDebug() << QString::fromLatin1(f.readAll());
0355 
0356     // We have no guarantee that the debug lines are issued one after the other.
0357     // The \n comes from the destruction of the temp kDebug, and that's not mutexed,
0358     // so we can get msg1 + msg2 + \n + \n.
0359     // So this test is basically only good for running in helgrind.
0360 
0361 #if 0
0362     // Check that the lines are whole
0363     QList<QByteArray> lines = readLines();
0364     Q_FOREACH (const QByteArray &line, lines) {
0365         qDebug() << line;
0366         QCOMPARE(line.count("doDebugs"), 1);
0367         QCOMPARE(line.count('\n'), 1);
0368         QVERIFY(!line.startsWith("   ")); // more than 2 spaces? indentString messed up
0369     }
0370 #endif
0371 }
0372 
0373 #include "moc_kdebug_unittest.cpp"