File indexing completed on 2024-04-28 15:25:14

0001 /**
0002  * This file is part of the KDE project
0003  *
0004  * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org>
0005  *
0006  * This library is free software; you can redistribute it and/or
0007  * modify it under the terms of the GNU Library General Public
0008  * License as published by the Free Software Foundation; either
0009  * version 2 of the License, or (at your option) any later version.
0010  *
0011  * This library 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 GNU
0014  * Library General Public License for more details.
0015  *
0016  * You should have received a copy of the GNU Library General Public License
0017  * along with this library; see the file COPYING.LIB.  If not, write to
0018  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0019  * Boston, MA 02110-1301, USA.
0020  *
0021  */
0022 
0023 #include <assert.h>
0024 #include <signal.h>
0025 
0026 #include <QFile>
0027 #include <QTimer>
0028 #include <QFileInfo>
0029 #include <QTextStream>
0030 #include <QFileDialog>
0031 #include <QMainWindow>
0032 
0033 #include <kiconloader.h>
0034 #include <kmessagebox.h>
0035 #include <kconfig.h>
0036 
0037 // Taken from QUrl
0038 #define Q_HAS_FLAG(a, b)    ( ((a) & (b)) == (b) )
0039 #define Q_SET_FLAG(a, b)    { (a) |= (b); }
0040 #define Q_UNSET_FLAG(a, b)  { (a) &= ~(b); }
0041 
0042 TestRegressionWindow::TestRegressionWindow(QWidget *parent)
0043     : QMainWindow(parent), m_flags(None), m_runCounter(0), m_testCounter(0), m_totalTests(0),
0044       m_totalTestsJS(0), m_totalTestsDOMTS(0), m_lastResult(Unknown),
0045       m_browserPart(0), m_activeProcess(0), m_activeTreeItem(0),
0046       m_suspended(false), m_justProcessingQueue(false)
0047 {
0048     m_ui.setupUi(this);
0049 
0050     // Setup actions/connections
0051     connect(m_ui.actionOnly_run_JS_tests, SIGNAL(toggled(bool)), SLOT(toggleJSTests(bool)));
0052     connect(m_ui.actionOnly_run_HTML_tests, SIGNAL(toggled(bool)), SLOT(toggleHTMLTests(bool)));
0053     connect(m_ui.actionDo_not_suppress_debug_output, SIGNAL(toggled(bool)), SLOT(toggleDebugOutput(bool)));
0054     connect(m_ui.actionDo_not_use_Xvfb, SIGNAL(toggled(bool)), SLOT(toggleNoXvfbUse(bool)));
0055     connect(m_ui.actionSpecify_tests_directory, SIGNAL(triggered(bool)), SLOT(setTestsDirectory()));
0056     connect(m_ui.actionSpecify_khtml_directory, SIGNAL(triggered(bool)), SLOT(setKHTMLDirectory()));
0057     connect(m_ui.actionSpecify_output_directory, SIGNAL(triggered(bool)), SLOT(setOutputDirectory()));
0058     connect(m_ui.actionRun_tests, SIGNAL(triggered(bool)), SLOT(runTests()));
0059 
0060     connect(m_ui.pauseContinueButton, SIGNAL(clicked(bool)), SLOT(pauseContinueButtonClicked()));
0061     connect(m_ui.saveLogButton, SIGNAL(clicked(bool)), SLOT(saveLogButtonClicked()));
0062 
0063     connect(m_ui.treeWidget, SIGNAL(customContextMenuRequested(QPoint)),
0064             this, SLOT(treeWidgetContextMenuRequested(QPoint)));
0065 
0066     // Setup actions' default state
0067     m_ui.progressBar->setValue(0);
0068     m_ui.textEdit->setReadOnly(true);
0069     m_ui.actionRun_tests->setEnabled(false);
0070     m_ui.pauseContinueButton->setEnabled(false);
0071 
0072     m_ui.treeWidget->headerItem()->setTextAlignment(0, Qt::AlignLeft);
0073     m_ui.treeWidget->headerItem()->setText(0, i18n("Available Tests: 0"));
0074 
0075     // Load default values for tests directory/khtml directory...
0076     KConfig config("testregressiongui", KConfig::SimpleConfig);
0077     KConfigGroup grp = config.group("<default>");
0078 
0079     m_testsUrl = QUrl::fromLocalFile(grp.readPathEntry("TestsDirectory", QString()));
0080     m_khtmlUrl = QUrl::fromLocalFile(grp.readPathEntry("KHTMLDirectory", QString()));
0081 
0082     initTestsDirectory();
0083 
0084     // Init early visible items in the text edit...
0085     initLegend();
0086     initOutputBrowser();
0087 }
0088 
0089 TestRegressionWindow::~TestRegressionWindow()
0090 {
0091     if (m_activeProcess) {
0092         m_activeProcess->kill();
0093 
0094         /* This leads to:
0095          * QProcess object destroyed while process is still running.
0096          * Any idea why??
0097         delete m_activeProcess;
0098         */
0099 
0100         m_activeProcess = 0;
0101     }
0102 }
0103 
0104 void TestRegressionWindow::toggleJSTests(bool checked)
0105 {
0106     if (checked) {
0107         Q_SET_FLAG(m_flags, JSTests)
0108         Q_UNSET_FLAG(m_flags, HTMLTests)
0109 
0110         m_ui.actionOnly_run_HTML_tests->setChecked(false);
0111     } else
0112         Q_UNSET_FLAG(m_flags, JSTests)
0113 
0114         // Eventually update progress bar range...
0115         updateProgressBarRange();
0116 }
0117 
0118 void TestRegressionWindow::toggleHTMLTests(bool checked)
0119 {
0120     if (checked) {
0121         Q_SET_FLAG(m_flags, HTMLTests)
0122         Q_UNSET_FLAG(m_flags, JSTests)
0123 
0124         m_ui.actionOnly_run_JS_tests->setChecked(false);
0125     } else
0126         Q_UNSET_FLAG(m_flags, HTMLTests)
0127 
0128         // Eventually update progress bar range...
0129         updateProgressBarRange();
0130 }
0131 
0132 void TestRegressionWindow::toggleDebugOutput(bool checked)
0133 {
0134     if (checked)
0135         Q_SET_FLAG(m_flags, DebugOutput)
0136         else
0137             Q_UNSET_FLAG(m_flags, DebugOutput)
0138         }
0139 
0140 void TestRegressionWindow::toggleNoXvfbUse(bool checked)
0141 {
0142     if (checked)
0143         Q_SET_FLAG(m_flags, NoXvfbUse)
0144         else
0145             Q_UNSET_FLAG(m_flags, NoXvfbUse)
0146         }
0147 
0148 void TestRegressionWindow::setTestsDirectory()
0149 {
0150     m_testsUrl = QFileDialog::getExistingDirectory();
0151 
0152     initTestsDirectory();
0153     loadOutputHTML();
0154 }
0155 
0156 void TestRegressionWindow::setOutputDirectory()
0157 {
0158     m_outputUrl = QFileDialog::getExistingDirectory();
0159     loadOutputHTML();
0160 }
0161 
0162 void TestRegressionWindow::initTestsDirectory()
0163 {
0164     bool okay = !m_testsUrl.isEmpty();
0165     if (okay) {
0166         const char *subdirs[] = { "tests", "baseline", "output", "resources" };
0167         for (int i = 0; i <= 3; i++) {
0168             QFileInfo sourceDir(m_testsUrl.path() + "/" + subdirs[i]);  //krazy:exclude=duoblequote_chars DOM demands chars
0169             if (!sourceDir.exists() || !sourceDir.isDir()) {
0170                 KMessageBox::error(0, i18n("Please choose a valid 'khtmltests/regression/' directory."));
0171 
0172                 okay = false;
0173                 m_testsUrl = QUrl();
0174                 break;
0175             }
0176         }
0177     }
0178 
0179     if (okay) {
0180         // Clean up...
0181         m_itemMap.clear();
0182         m_ignoreMap.clear();
0183         m_failureMap.clear();
0184         m_directoryMap.clear();
0185 
0186         m_ui.treeWidget->clear();
0187 
0188         if (!m_khtmlUrl.isEmpty()) {
0189             m_ui.actionRun_tests->setEnabled(true);
0190         }
0191 
0192         // Initialize map (to prevent assert below)...
0193         m_directoryMap.insert(QString(), QStringList());
0194 
0195         // Setup root tree widget item...
0196         (void) new QTreeWidgetItem(m_ui.treeWidget, QStringList(m_testsUrl.path() + "/tests"));
0197 
0198         // Check for ignore & failure file in root directory...
0199         QString ignoreFile = m_testsUrl.path() + "/tests/ignore";
0200         QString failureFile = m_testsUrl.path() + "/tests/KNOWN_FAILURES";
0201 
0202         QStringList ignoreFileList = readListFile(ignoreFile);
0203         QStringList failureFileList = readListFile(failureFile);
0204 
0205         if (!ignoreFileList.isEmpty()) {
0206             m_ignoreMap.insert(QString(), ignoreFileList);
0207         }
0208 
0209         if (!failureFileList.isEmpty()) {
0210             m_failureMap.insert(QString(), failureFileList);
0211         }
0212 
0213         // Remember directory...
0214         KConfig config("testregressiongui", KConfig::SimpleConfig);
0215         KConfigGroup grp = config.group("<default>");
0216         grp.writePathEntry("TestsDirectory", m_testsUrl.path());
0217 
0218         // Start listing directory...
0219         QUrl listUrl(m_testsUrl);
0220         listUrl.setPath(listUrl.path() + "/tests");
0221         KIO::ListJob *job = KIO::listRecursive(listUrl, KIO::HideProgressInfo, false /* no hidden files */);
0222 
0223         connect(job, SIGNAL(result(KJob*)), SLOT(directoryListingFinished(KJob*)));
0224 
0225         connect(job, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)),
0226                 this, SLOT(directoryListingResult(KIO::Job*,KIO::UDSEntryList)));
0227     }
0228 }
0229 
0230 void TestRegressionWindow::setKHTMLDirectory()
0231 {
0232     m_khtmlUrl = QFileDialog::getExistingDirectory();
0233 
0234     if (!m_khtmlUrl.isEmpty()) {
0235         const char *subdirs[] = { "css", "dom", "xml", "html" }; // That's enough ;-)
0236         for (int i = 0; i <= 3; i++) {
0237             QFileInfo sourceDir(m_khtmlUrl.path() + "/" + subdirs[i]);  //krazy:exclude=duoblequote_chars DOM demands chars
0238             if (!sourceDir.exists() || !sourceDir.isDir()) {
0239                 KMessageBox::error(0, i18n("Please choose a valid 'khtml/' build directory."));
0240 
0241                 m_khtmlUrl = QUrl();
0242                 break;
0243             }
0244         }
0245 
0246         // Remember directory...
0247         KConfig config("testregressiongui", KConfig::SimpleConfig);
0248         KConfigGroup grp = config.group("<default>");
0249         grp.writePathEntry("KHTMLDirectory", m_khtmlUrl.path());
0250 
0251         if (!m_testsUrl.isEmpty() && !m_khtmlUrl.isEmpty()) {
0252             m_ui.actionRun_tests->setEnabled(true);
0253         }
0254     }
0255 }
0256 
0257 void TestRegressionWindow::directoryListingResult(KIO::Job *, const KIO::UDSEntryList &list)
0258 {
0259     KIO::UDSEntryList::ConstIterator it = list.constBegin();
0260     const KIO::UDSEntryList::ConstIterator end = list.constEnd();
0261 
0262     for (; it != end; ++it) {
0263         const KIO::UDSEntry &entry = *it;
0264 
0265         QString name = entry.stringValue(KIO::UDSEntry::UDS_NAME);
0266         if (entry.isDir()) { // Create new map entry...
0267             assert(m_directoryMap.constFind(name) == m_directoryMap.constEnd());
0268             m_directoryMap.insert(name, QStringList());
0269 
0270             QString ignoreFile = m_testsUrl.path() + "/tests/" + name + "/ignore";
0271             QString failureFile = m_testsUrl.path() + "/tests/" + name + "/KNOWN_FAILURES";
0272 
0273             QStringList ignoreFileList = readListFile(ignoreFile);
0274             QStringList failureFileList = readListFile(failureFile);
0275 
0276             if (!ignoreFileList.isEmpty()) {
0277                 m_ignoreMap.insert(name, ignoreFileList);
0278             }
0279 
0280             if (!failureFileList.isEmpty()) {
0281                 m_failureMap.insert(name, failureFileList);
0282             }
0283         } else if (name.endsWith(".html") || name.endsWith(".htm") ||
0284                    name.endsWith(".xhtml") || name.endsWith(".xml") || name.endsWith(".js")) {
0285             int lastSlashPos = name.lastIndexOf('/');
0286 
0287             QString cachedDirectory = (lastSlashPos > 0 ? name.mid(0, lastSlashPos) : QString());
0288             QString cachedFilename = name.mid(lastSlashPos + 1);
0289 
0290             assert(m_directoryMap.constFind(cachedDirectory) != m_directoryMap.constEnd());
0291             m_directoryMap[cachedDirectory].append(cachedFilename);
0292         }
0293     }
0294 }
0295 
0296 void TestRegressionWindow::directoryListingFinished(KJob *)
0297 {
0298     QTreeWidgetItem *topLevelItem = m_ui.treeWidget->topLevelItem(0);
0299 
0300     // Gather a lot of statistics...
0301     unsigned long availableDomFiles = 0;
0302     unsigned long availableDumpFiles = 0;
0303     unsigned long availableRenderFiles = 0;
0304 
0305     unsigned long ignoredJSTests = 0;
0306     unsigned long availableJSTests = 0;
0307 
0308     unsigned long ignoredXMLTests = 0;
0309     unsigned long availableXMLTests = 0;
0310 
0311     unsigned long ignoredHTMLTests = 0;
0312     unsigned long availableHTMLTests = 0;
0313 
0314     unsigned long ignoredDOMTSTests = 0;
0315     unsigned long availableDOMTSTests = 0;
0316 
0317     // Start the actual data processing...
0318     QMap<QString, QStringList>::const_iterator it = m_directoryMap.constBegin();
0319     const QMap<QString, QStringList>::const_iterator end = m_directoryMap.constEnd();
0320 
0321     for (; it != end; ++it) {
0322         QString directory = it.key();
0323         QStringList filenames = it.value();
0324 
0325         if (filenames.isEmpty()) { // Do not add empty directories at all...
0326             continue;
0327         }
0328 
0329         bool hasIgnores = (m_ignoreMap.constFind(directory) != m_directoryMap.constEnd());
0330         bool hasFailures = (m_failureMap.constFind(directory) != m_failureMap.constEnd());
0331 
0332         // Extract parent directory...
0333         int position = directory.lastIndexOf('/');
0334 
0335         QString parentDirectory = directory.mid(0, (position == -1 ? 0 : position));
0336         QString parentDirectoryItem = directory.mid(position + 1);
0337 
0338         bool hasParentIgnores = (m_ignoreMap.constFind(parentDirectory) != m_directoryMap.constEnd());
0339         bool hasParentFailures = (m_failureMap.constFind(parentDirectory) != m_failureMap.constEnd());
0340 
0341         // Sort in ascending order...
0342         filenames.sort();
0343 
0344         QStringList::const_iterator it2 = filenames.constBegin();
0345         const QStringList::const_iterator end2 = filenames.constEnd();
0346 
0347         // Create new tree widget item for the active directory...
0348         QTreeWidgetItem *parent = topLevelItem;
0349 
0350         if (!directory.isEmpty()) {
0351             parent = new QTreeWidgetItem(topLevelItem, QStringList(directory));
0352 
0353             // Directory is completely ignored, mark it 'yellow'...
0354             if (hasParentIgnores && m_ignoreMap[parentDirectory].contains(parentDirectoryItem)) {
0355                 parent->setIcon(0, m_ignorePixmap);
0356             }
0357 
0358             // Directory is completely known to fail, mark it 'red'...
0359             if (hasParentFailures && m_failureMap[parentDirectory].contains(parentDirectoryItem)) {
0360                 parent->setIcon(0, m_failKnownPixmap);
0361             }
0362         }
0363 
0364         // Add all contained files as new items below 'parent'...
0365         for (; it2 != end2; ++it2) {
0366             QString test = (*it2);
0367             QString cacheName = directory + "/" + test; //krazy:exclude=duoblequote_chars DOM demands chars
0368 
0369             QTreeWidgetItem *testItem = new QTreeWidgetItem(parent, QStringList(QUrl(test).path()));
0370 
0371             // Remember name <-> item pair...
0372             assert(m_itemMap.contains(cacheName));
0373             m_itemMap.insert(cacheName, testItem);
0374 
0375             bool ignore = (hasIgnores && m_ignoreMap[directory].contains(test));
0376             bool ignoreParent = (hasParentIgnores && m_ignoreMap[parentDirectory].contains(parentDirectoryItem));
0377 
0378             bool failure = (hasFailures && m_failureMap[directory].contains(test));
0379 
0380             // Check baseline directory for this test...
0381             QString baseLinePath = m_testsUrl.path() + "/baseline/" + cacheName;
0382 
0383             bool dom[9], render[9];
0384             for (unsigned int i = 0; i < 9; ++i) {
0385                 if (i == 0) {
0386                     dom[i] = (QFileInfo(baseLinePath + "-dom").exists());
0387                     render[i] = (QFileInfo(baseLinePath + "-render").exists());
0388                 } else {
0389                     dom[i] = (QFileInfo(baseLinePath + "-" + QString::number(i) + "-dom").exists());    //krazy:exclude=duoblequote_chars DOM demands chars
0390                     render[i] = (QFileInfo(baseLinePath + "-" + QString::number(i) + "-render").exists());  //krazy:exclude=duoblequote_chars DOM demands chars
0391                 }
0392             }
0393 
0394             bool dump = (QFileInfo(baseLinePath + "-dump.png").exists());
0395 
0396             // Ignored tests are marked 'yellow'...
0397             if (ignore) {
0398                 testItem->setIcon(0, m_ignorePixmap);
0399             }
0400 
0401             // Tests, known to fail, are marked 'red'...
0402             if (failure) {
0403                 testItem->setIcon(0, m_failKnownPixmap);
0404             }
0405 
0406             // Detect whether the tests has no corresponding baseline items...
0407             if (!ignore && !failure) {
0408                 if (!dom[0] && !dump && !render && !cacheName.endsWith(".js") && !cacheName.startsWith("domts")) {
0409                     // See if parent directory is completely ignored...
0410                     if (!ignoreParent) {
0411                         testItem->setIcon(0, m_noBaselinePixmap);
0412                     }
0413                 }
0414             }
0415 
0416             // Update statistics...
0417             if (dump) {
0418                 availableDumpFiles++;
0419             }
0420 
0421             for (unsigned i = 0; i < 9; ++i) {
0422                 if (dom[i]) {
0423                     availableDomFiles++;
0424                 }
0425 
0426                 if (render[i]) {
0427                     availableRenderFiles++;
0428                 }
0429             }
0430 
0431             // Count DOM Testsuite files separated... (these have no baseline items!)
0432             if (cacheName.startsWith("domts")) {
0433                 // See if parent directory is completely ignored...
0434                 if (ignore || ignoreParent) {
0435                     ignoredDOMTSTests++;
0436                 } else {
0437                     availableDOMTSTests++;
0438                 }
0439             }
0440 
0441             if (cacheName.endsWith(".html") || cacheName.endsWith(".htm") || cacheName.endsWith(".xhtml")) {
0442                 if (ignore || ignoreParent) {
0443                     ignoredHTMLTests++;
0444                 } else {
0445                     availableHTMLTests++;
0446                 }
0447             } else if (cacheName.endsWith(".xml")) {
0448                 if (ignore || ignoreParent) {
0449                     ignoredXMLTests++;
0450                 } else {
0451                     availableXMLTests++;
0452                 }
0453             } else if (cacheName.endsWith(".js")) {
0454                 unsigned long containedTests = 0;
0455 
0456                 // Try hard to _ESTIMATE_ the number of tests...
0457                 // I really meant estimate, no way to calculate it perfectly.
0458 
0459                 QString jsFilePath = m_testsUrl.path() + "/tests/" + cacheName;
0460                 assert(QFileInfo(jsFilePath).exists() == true);
0461 
0462                 QStringList fileList = readListFile(jsFilePath);
0463                 QString fileContent = fileList.join("");
0464 
0465                 // #1 -> Check js file for the 'reportResult' calls...
0466                 containedTests = fileContent.count("reportResult");
0467 
0468                 // #2 -> Check js file for 'openPage' calls...
0469                 containedTests += fileContent.count("openPage");
0470 
0471                 // #3 -> Check js file for 'checkOutput' calls...
0472                 containedTests += fileContent.count("checkOutput");
0473 
0474                 // #4 -> Fallback for ie. mozilla/ecma files...
0475                 if (containedTests == 0) { // Doesn't use 'reportResult' scheme...
0476                     containedTests++;
0477                 }
0478 
0479                 if (ignore || ignoreParent) {
0480                     ignoredJSTests += containedTests;
0481                 } else {
0482                     availableJSTests += containedTests;
0483                 }
0484             }
0485         }
0486     }
0487 
0488     // Now we can calculate all ignored/available tests...
0489     unsigned long ignoredTests = ignoredJSTests + ignoredXMLTests + ignoredHTMLTests;
0490     unsigned long availableTests = availableJSTests + availableXMLTests + availableHTMLTests;
0491 
0492     // This estimates the number of total tests, depending on the mode...
0493     m_totalTests = availableDomFiles + availableDumpFiles + availableRenderFiles +
0494                    availableDOMTSTests + availableJSTests;
0495 
0496     m_totalTestsJS = availableJSTests;
0497     m_totalTestsDOMTS = availableDOMTSTests;
0498 
0499     // Update progress bar range...
0500     updateProgressBarRange();
0501 
0502     QString statistics = QString("<body><table border='0' align='center' cellspacing='15'>") +
0503                          QString("<tr valign='top'><td colspan='3'><center><b>Statistics</b></center></td></tr>") +
0504                          QString("<tr valign='middle'><td>JS Tests</td><td>" + QString::number(availableJSTests) + "</td><td>(" + QString::number(ignoredJSTests) + " ignored)</td></tr>") +
0505                          QString("<tr valign='middle'><td>XML Tests</td><td>" + QString::number(availableXMLTests) + "</td><td>(" + QString::number(ignoredXMLTests) + " ignored)</td></tr>") +
0506                          QString("<tr valign='middle'><td>HTML Tests</td><td>" + QString::number(availableHTMLTests) + "</td><td>(" + QString::number(ignoredHTMLTests) + " ignored)</td></tr>") +
0507                          QString("</table></body>");
0508 
0509     // Go to end...
0510     QTextCursor cursor = m_ui.textEdit->textCursor();
0511     cursor.movePosition(QTextCursor::End);
0512     m_ui.textEdit->setTextCursor(cursor);
0513 
0514     // Insert statistics...
0515     m_ui.textEdit->insertHtml(statistics);
0516 
0517     // Update treeview...
0518     m_ui.treeWidget->headerItem()->setText(0, i18n("Available Tests: %1 (ignored: %2)", availableTests, ignoredTests));
0519 }
0520 
0521 void TestRegressionWindow::updateProgressBarRange() const
0522 {
0523     if (m_totalTests != 0 && m_totalTestsJS != 0) {
0524         unsigned long totalTests = m_totalTests;
0525 
0526         if (Q_HAS_FLAG(m_flags, JSTests)) {
0527             totalTests = m_totalTestsJS;
0528         } else if (Q_HAS_FLAG(m_flags, HTMLTests)) {
0529             totalTests -= m_totalTestsJS;
0530             totalTests -= m_totalTestsDOMTS;
0531         }
0532 
0533         m_ui.progressBar->setRange(0, totalTests);
0534     }
0535 }
0536 
0537 void TestRegressionWindow::pauseContinueButtonClicked()
0538 {
0539     assert(m_activeProcess != 0);
0540 
0541     if (!m_suspended) {
0542         // Suspend process
0543         kill(m_activeProcess->pid(), SIGSTOP);
0544 
0545         m_suspended = true;
0546         m_ui.pauseContinueButton->setText(i18n("Continue"));
0547     } else {
0548         // Continue process
0549         kill(m_activeProcess->pid(), SIGCONT);
0550 
0551         m_suspended = false;
0552         m_ui.pauseContinueButton->setText(i18n("Pause"));
0553     }
0554 }
0555 
0556 void TestRegressionWindow::saveLogButtonClicked()
0557 {
0558     assert(m_activeProcess == 0);
0559     m_saveLogUrl = QFileDialog::getExistingDirectory();
0560 
0561     QString fileName = m_saveLogUrl.path() + "/logOutput.html";
0562     if (QFileInfo(fileName).exists()) {
0563         // Remove file if already existent...
0564         QFile file(fileName);
0565         if (!file.remove()) {
0566             kError() << " Can't remove " << fileName;
0567             exit(1);
0568         }
0569     }
0570 }
0571 
0572 void TestRegressionWindow::runTests()
0573 {
0574     // Run in all-in-one mode...
0575     m_runCounter = 0;
0576     m_testCounter = 0;
0577 
0578     initRegressionTesting(QString());
0579 }
0580 
0581 void TestRegressionWindow::runSingleTest()
0582 {
0583     assert(m_activeTreeItem != 0);
0584 
0585     QString testFileName = pathFromItem(m_activeTreeItem);
0586 
0587     // Run in single-test mode...
0588     m_runCounter = 0;
0589     m_testCounter = -1;
0590 
0591     initRegressionTesting(testFileName);
0592 }
0593 
0594 void TestRegressionWindow::initRegressionTesting(const QString &testFileName)
0595 {
0596     assert(m_activeProcess == 0);
0597 
0598     m_activeProcess = new QProcess();
0599     m_activeProcess->setReadChannelMode(QProcess::MergedChannels);
0600 
0601     QStringList environment = QProcess::systemEnvironment();
0602     environment << "KDE_DEBUG=false"; // No Dr. Konqi please!
0603 
0604     QString program = m_khtmlUrl.path() + "/.libs/testregression";
0605     QString program2 = m_khtmlUrl.path() + "/testregression"; // with CMake, it's in $buildir/bin
0606 
0607     if (!QFileInfo(program).exists()) {
0608         if (!QFileInfo(program2).exists()) {
0609             KMessageBox::error(0, i18n("Cannot find testregression executable."));
0610             return;
0611         } else {
0612             program = program2;
0613         }
0614     }
0615 
0616     QStringList arguments;
0617     arguments << "--base" << m_testsUrl.path();
0618 
0619     if (!m_outputUrl.isEmpty()) {
0620         arguments << "--output" << m_outputUrl.path();
0621     }
0622     if (!testFileName.isEmpty()) {
0623         arguments << "--test" << testFileName;
0624     }
0625 
0626     if (Q_HAS_FLAG(m_flags, JSTests)) {
0627         arguments << "--js";
0628     }
0629     if (Q_HAS_FLAG(m_flags, HTMLTests)) {
0630         arguments << "--html";
0631     }
0632     if (Q_HAS_FLAG(m_flags, DebugOutput)) {
0633         arguments << "--debug";
0634     }
0635     if (Q_HAS_FLAG(m_flags, NoXvfbUse)) {
0636         arguments << "--noxvfb";
0637     }
0638 
0639     connect(m_activeProcess, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(testerExited(int,QProcess::ExitStatus)));
0640     connect(m_activeProcess, SIGNAL(readyReadStandardOutput()), SLOT(testerReceivedData()));
0641 
0642     // Clear processing queue before starting...
0643     m_processingQueue.clear();
0644 
0645     // Clean up gui...
0646     m_ui.textEdit->clear();
0647     m_ui.progressBar->reset();
0648     m_ui.saveLogButton->setEnabled(false);
0649     m_ui.actionRun_tests->setEnabled(false);
0650     m_ui.pauseContinueButton->setEnabled(true);
0651 
0652     // Start regression testing process...
0653     m_activeProcess->setEnvironment(environment);
0654     m_activeProcess->start(program, arguments, QIODevice::ReadOnly);
0655 }
0656 
0657 void TestRegressionWindow::initOutputBrowser()
0658 {
0659     assert(m_browserPart == 0);
0660     m_browserPart = new KHTMLPart(m_ui.secondTab, m_ui.secondTab, KHTMLPart::BrowserViewGUI);
0661 
0662     // Setup vertical layout for the browser widget...
0663     QVBoxLayout *layout = new QVBoxLayout();
0664     layout->addWidget(m_browserPart->widget());
0665     m_ui.secondTab->setLayout(layout);
0666 
0667     m_browserPart->setJavaEnabled(true);
0668     m_browserPart->setJScriptEnabled(true);
0669     m_browserPart->setPluginsEnabled(true);
0670     m_browserPart->setURLCursor(QCursor(Qt::PointingHandCursor));
0671 
0672     m_browserPart->widget()->show();
0673 
0674     // Check if there is already an output/index.html present...
0675     loadOutputHTML();
0676 }
0677 
0678 void TestRegressionWindow::loadOutputHTML() const
0679 {
0680     if (m_testsUrl.isEmpty()) {
0681         return;
0682     }
0683 
0684     QString fileName = m_testsUrl.path() + "/output/index.html";
0685     if (!m_outputUrl.isEmpty()) {
0686         fileName = m_outputUrl.path() + "/index.html";
0687     }
0688 
0689     QFileInfo indexHtml(fileName);
0690     if (indexHtml.exists()) {
0691         m_browserPart->openUrl(QUrl::fromLocalFile(fileName));
0692         m_ui.tabWidget->setTabEnabled(1, true);
0693     } else {
0694         m_ui.tabWidget->setTabEnabled(1, false);
0695     }
0696 }
0697 
0698 void TestRegressionWindow::updateItemStatus(TestResult result, QTreeWidgetItem *item, const QString &testFileName)
0699 {
0700     if (!item) {
0701         return;
0702     }
0703 
0704     // Ensure item is visible...
0705     QTreeWidgetItem *parent = item;
0706     while (parent != 0) {
0707         m_ui.treeWidget->setItemExpanded(parent, true);
0708         parent = parent->parent();
0709     }
0710 
0711     m_ui.treeWidget->scrollToItem(item);
0712 
0713     bool updateIcon = true;
0714     if (m_lastName == testFileName && !m_lastName.isEmpty()) {
0715         if (m_lastResult == result) {
0716             updateIcon = false;
0717         } else if ((m_lastResult == Pass || m_lastResult == PassUnexpected) &&
0718                    (result == Fail || result == FailKnown || result == Crash)) {
0719             // If one part of the test (render/dom/paint) passed,
0720             // and the current part fails, update to 'failed' icon...
0721             updateIcon = true;
0722         } else if ((m_lastResult == Fail || m_lastResult == FailKnown || m_lastResult == Crash) &&
0723                    (result == Pass || result == PassUnexpected)) {
0724             // If one part of the test (render/dom/paint) failed,
0725             // and the current part passes, don't update to 'passed' icon...
0726             updateIcon = false;
0727         }
0728     }
0729 
0730     // Update icon, if necessary...
0731     if (updateIcon) {
0732         if (result == Crash) {
0733             item->setIcon(0, m_crashPixmap);
0734         } else if (result == Fail) {
0735             item->setIcon(0, m_failPixmap);
0736         } else if (result == FailKnown) {
0737             item->setIcon(0, m_failKnownPixmap);
0738         } else if (result == Pass) {
0739             item->setIcon(0, m_passPixmap);
0740         } else if (result == PassUnexpected) {
0741             item->setIcon(0, m_passUnexpectedPixmap);
0742         } else { // Unhandled state...
0743             assert(false);
0744         }
0745     }
0746 
0747     // Remember test & result...
0748     m_lastResult = result;
0749     m_lastName = testFileName;
0750     m_activeTreeItem = item;
0751 }
0752 
0753 void TestRegressionWindow::initLegend()
0754 {
0755     // Init pixmaps...
0756     m_failPixmap = QPixmap(":/test/pics/fail.xpm");
0757     m_failKnownPixmap = QPixmap(":/test/pics/failKnown.xpm");
0758     m_passPixmap = QPixmap(":/test/pics/pass.xpm");
0759     m_passUnexpectedPixmap = QPixmap(":/test/pics/passUnexpected.xpm");
0760     m_ignorePixmap = QPixmap(":/test/pics/ignore.xpm");
0761     m_crashPixmap = QPixmap(":/test/pics/crash.xpm");
0762     m_noBaselinePixmap = QPixmap(":/test/pics/noBaseline.xpm");
0763 
0764     QString legend = QLatin1String("<body><center><font size='8'>Welcome to the khtml<br/>") +
0765                      QLatin1String("regression testing tool!</font></center><br/><br/>") +
0766                      QLatin1String("<table border='0' align='center' cellspacing='15'>") +
0767                      QLatin1String("<tr valign='top'><td colspan='2'><center><b>Legend</b></center></td></tr>") +
0768                      QLatin1String("<tr valign='middle'><td>Pass</td><td><img src=':/test/pics/pass.xpm'></td></tr>") +
0769                      QLatin1String("<tr valign='middle'><td>Pass unexpected</td><td><img src=':/test/pics/passUnexpected.xpm'></td></tr>") +
0770                      QLatin1String("<tr valign='middle'><td>Fail</td><td><img src=':/test/pics/fail.xpm'></td></tr>") +
0771                      QLatin1String("<tr valign='middle'><td>Fail known</td><td><img src=':/test/pics/failKnown.xpm'></td></tr>") +
0772                      QLatin1String("<tr valign='middle'><td>Ignore</td><td><img src=':/test/pics/ignore.xpm'></td></tr>") +
0773                      QLatin1String("<tr valign='middle'><td>Baseline missing</td><td><img src=':/test/pics/noBaseline.xpm'></td></tr>") +
0774                      QLatin1String("<tr valign='middle'><td>Crash</td><td><img src=':/test/pics/crash.xpm'></td></tr>") +
0775                      QLatin1String("</table></body>");
0776 
0777     m_ui.textEdit->setHtml(legend);
0778 }
0779 
0780 void TestRegressionWindow::testerExited(int /* exitCode */, QProcess::ExitStatus exitStatus)
0781 {
0782     assert(m_activeProcess != 0);
0783     assert(m_activeTreeItem != 0);
0784 
0785     if (exitStatus == QProcess::CrashExit) { // Special case: crash!
0786         QTreeWidgetItem *useItem = m_activeTreeItem;
0787 
0788         if (m_testCounter >= 0 || m_runCounter > 0) { // Single-tests mode invoked on a directory OR All-test-mode
0789             QTreeWidgetItem *parent = useItem->parent();
0790             assert(parent != 0);
0791 
0792             useItem = parent->child(parent->indexOfChild(useItem) + 1);
0793             assert(useItem != 0);
0794         }
0795 
0796         // Reflect crashed test...
0797         updateItemStatus(Crash, useItem, QString());
0798     }
0799 
0800     if (m_testCounter >= 0) { // All-tests mode
0801         m_ui.progressBar->setValue(m_ui.progressBar->maximum());
0802     }
0803 
0804     // Eventually save log output...
0805     if (!m_saveLogUrl.isEmpty()) {
0806         // We should close our written log with </body></html>.
0807         m_processingQueue.enqueue(QString::fromLatin1("\n</body>\n</html>"));
0808 
0809         if (!m_justProcessingQueue) {
0810             m_justProcessingQueue = true;
0811             QTimer::singleShot(50, this, SLOT(processQueue()));
0812         }
0813     }
0814 
0815     // Cleanup gui...
0816     m_ui.saveLogButton->setEnabled(true);
0817     m_ui.actionRun_tests->setEnabled(true);
0818     m_ui.pauseContinueButton->setEnabled(false);
0819 
0820     // Check if there is already an output/index.html present...
0821     loadOutputHTML();
0822 
0823     // Cleanup data..
0824     delete m_activeProcess;
0825     m_activeProcess = 0;
0826 
0827     m_runCounter = 0;
0828     m_testCounter = 0;
0829     m_activeTreeItem = 0;
0830 }
0831 
0832 void TestRegressionWindow::testerReceivedData()
0833 {
0834     assert(m_activeProcess != 0);
0835 
0836     QString data(m_activeProcess->readAllStandardOutput());
0837     QStringList list = data.split('\n');
0838 
0839     QStringList::const_iterator it = list.constBegin();
0840     const QStringList::const_iterator end = list.constEnd();
0841 
0842     for (; it != end; ++it) {
0843         QString temp = *it;
0844         if (!temp.isEmpty()) {
0845             m_processingQueue.enqueue(temp);
0846         }
0847     }
0848 
0849     if (!m_justProcessingQueue) {
0850         m_justProcessingQueue = true;
0851         QTimer::singleShot(50, this, SLOT(processQueue()));
0852     }
0853 }
0854 
0855 void TestRegressionWindow::processQueue()
0856 {
0857     while (!m_processingQueue.isEmpty()) {
0858         QString data = m_processingQueue.dequeue();
0859         TestResult result = Unknown;
0860 
0861         QString cacheName = extractTestNameFromData(data, result);
0862 
0863         if (result != Unknown) { // Yes, we're dealing with a test result...
0864             if (cacheName.isEmpty()) { // Make sure everything is alright!
0865                 kError() << "Couldn't extract cacheName from data=\"" << data << "\"! Ignoring!";
0866                 continue;
0867             }
0868         }
0869 
0870         parseRegressionTestingOutput(data, result, cacheName);
0871     }
0872 
0873     m_justProcessingQueue = false;
0874 }
0875 
0876 void TestRegressionWindow::addToIgnores()
0877 {
0878     assert(m_activeTreeItem != 0);
0879 
0880     QString treeItemText = pathFromItem(m_activeTreeItem);
0881 
0882     // Extract directory/file name...
0883     int position = treeItemText.lastIndexOf('/');
0884 
0885     QString directory = treeItemText.mid(0, (position == -1 ? 0 : position));
0886     QString fileName = treeItemText.mid(position + 1);
0887 
0888     // Read corresponding ignore file..
0889     QString ignoreFile = m_testsUrl.path() + "/tests/" + directory + "/ignore";
0890     QStringList ignoreFileList = readListFile(ignoreFile);
0891 
0892     if (!ignoreFileList.contains(fileName)) {
0893         ignoreFileList.append(fileName);
0894     }
0895 
0896     // Commit changes...
0897     writeListFile(ignoreFile, ignoreFileList);
0898 
0899     // Reset icon status...
0900     m_activeTreeItem->setIcon(0, m_ignorePixmap);
0901 }
0902 
0903 void TestRegressionWindow::removeFromIgnores()
0904 {
0905     assert(m_activeTreeItem != 0);
0906 
0907     QString treeItemText = pathFromItem(m_activeTreeItem);
0908 
0909     // Extract directory/file name...
0910     int position = treeItemText.lastIndexOf('/');
0911 
0912     QString directory = treeItemText.mid(0, (position == -1 ? 0 : position));
0913     QString fileName = treeItemText.mid(position + 1);
0914 
0915     // Read corresponding ignore file..
0916     QString ignoreFile = m_testsUrl.path() + "/tests/" + directory + "/ignore";
0917     QStringList ignoreFileList = readListFile(ignoreFile);
0918 
0919     if (ignoreFileList.contains(fileName)) {
0920         ignoreFileList.removeAll(fileName);
0921     }
0922 
0923     // Commit changes...
0924     writeListFile(ignoreFile, ignoreFileList);
0925 
0926     // Reset icon status...
0927     m_activeTreeItem->setIcon(0, QPixmap());
0928 }
0929 
0930 QString TestRegressionWindow::pathFromItem(const QTreeWidgetItem *item) const
0931 {
0932     QString path = item->text(0);
0933 
0934     QTreeWidgetItem *parent = item->parent();
0935     while (parent != 0) {
0936         if (parent->parent() != 0) {
0937             path.prepend(parent->text(0) + "/");    //krazy:exclude=duoblequote_chars DOM demands chars
0938         }
0939 
0940         parent = parent->parent();
0941     }
0942 
0943     return path;
0944 }
0945 
0946 QString TestRegressionWindow::extractTestNameFromData(QString &data, TestResult &result) const
0947 {
0948     if (data.indexOf("PASS") >= 0 || data.indexOf("FAIL") >= 0) {
0949         // Name extraction regexps...
0950         QString bracesSelector("[0-9a-zA-Z-_<>\\* +-,.:!?$'\"=/\\[\\]\\(\\)]*");
0951 
0952         QRegExp expPass("PASS: (" + bracesSelector + ")");  //krazy:exclude=duoblequote_chars DOM demands chars
0953         QRegExp expPassUnexpected("PASS \\(unexpected!\\): (" + bracesSelector + ")");  //krazy:exclude=duoblequote_chars DOM demands chars
0954 
0955         QRegExp expFail("FAIL: (" + bracesSelector + ")");  //krazy:exclude=duoblequote_chars DOM demands chars
0956         QRegExp expFailKnown("FAIL \\(known\\): (" + bracesSelector + ")"); //krazy:exclude=duoblequote_chars DOM demands chars
0957 
0958         // Extract name of test... (while using regexps as rare as possible!)
0959         int pos = -1;
0960         QString test;
0961 
0962         QRegExp cleanTest(" \\[" + bracesSelector + "\\]");
0963 
0964         pos = expPass.indexIn(data);
0965         if (pos > -1) {
0966             test = expPass.cap(1);
0967             result = Pass;
0968         }
0969 
0970         if (result == Unknown) {
0971             pos = expPassUnexpected.indexIn(data);
0972             if (pos > -1) {
0973                 test = expPassUnexpected.cap(1);
0974                 result = PassUnexpected;
0975             }
0976         }
0977 
0978         if (result == Unknown) {
0979             pos = expFail.indexIn(data);
0980             if (pos > -1) {
0981                 test = expFail.cap(1);
0982                 result = Fail;
0983             }
0984         }
0985 
0986         if (result == Unknown) {
0987             pos = expFailKnown.indexIn(data);
0988             if (pos > -1) {
0989                 test = expFailKnown.cap(1);
0990                 result = FailKnown;
0991             }
0992         }
0993 
0994         if (!test.isEmpty() && result != Unknown) { // Got information about test...
0995             // Clean up first, so we only get the file name...
0996             test.replace(cleanTest, QString());
0997 
0998             // Extract cached directory/filename pair...
0999             int lastSlashPos = test.lastIndexOf('/');
1000 
1001             QString cachedDirectory = (lastSlashPos > 0 ? test.mid(0, lastSlashPos) : QString());
1002             QString cachedFilename = test.mid(lastSlashPos + 1);
1003 
1004             if (cachedDirectory == ".") { // Handle cases like "./empty.html"
1005                 cachedDirectory.clear();
1006             }
1007 
1008             assert(m_directoryMap.constFind(cachedDirectory) != m_directoryMap.constEnd());
1009 
1010             QString cacheName = cachedDirectory + "/" + cachedFilename; //krazy:exclude=duoblequote_chars DOM demands chars
1011             if (m_itemMap.constFind(cacheName) != m_itemMap.constEnd()) {
1012                 // Highlight test...
1013                 data.replace(expPass, "<b><font color='green'>PASS:\t\\1</font></b>");
1014                 data.replace(expPassUnexpected, "<b><font color='green'>PASS (unexpected!):\t\\1</font></b>");
1015                 data.replace(expFail, "<b><font color='red'>FAIL:\t\\1</font></b>");
1016                 data.replace(expFailKnown, "<b><font color='red'>FAIL (known):\t\\1</font></b>");
1017 
1018                 return cacheName;
1019             }
1020         }
1021     }
1022 
1023     return QString();
1024 }
1025 
1026 void TestRegressionWindow::parseRegressionTestingOutput(QString data, TestResult result, const QString &cacheName)
1027 {
1028     if (!cacheName.isEmpty()) {
1029         if (m_testCounter >= 0) { // Only increment in all-tests mode...
1030             m_testCounter++;
1031         }
1032 
1033         m_runCounter++; // Always increment...
1034 
1035         // Update the icon...
1036         updateItemStatus(result, m_itemMap[cacheName], cacheName);
1037     }
1038 
1039     // Apply some nice formatting for the statistics...
1040     if (data.indexOf("Total") >= 0 || data.indexOf("Passes") >= 0 || data.indexOf("Tests completed") >= 0 ||
1041             data.indexOf("Errors:") >= 0 || data.indexOf("Failures:") >= 0) {
1042         QRegExp expTotal("Total:    ([0-9]*)");
1043         QRegExp expPasses("Passes:   ([0-9 a-z\\(\\)]*)");
1044         QRegExp expErrors("Errors:   ([0-9 a-z]*)");
1045         QRegExp expFailures("Failures: ([0-9 a-z\\(\\)]*)");
1046 
1047         data.replace("Tests completed.", "<br><center><h2>Tests completed.</h2></center>");
1048         data.replace(expTotal, "<b><font size='4'>Total:&nbsp;\\1</font></b>");
1049         data.replace(expPasses, "<b><font size='4' color='green'>Passes:&nbsp;\\1</font></b>");
1050         data.replace(expErrors, "<b><font size='4' color='blue'>Errors:&nbsp;\\1</font></b>");
1051         data.replace(expFailures, "<b><font size='4' color='red'>Failures:&nbsp;\\1</font></b>");
1052     }
1053 
1054     if (!data.contains("</body>\n</html>")) { // Don't put <br> behind </html>!
1055         data.append("<br>");
1056     }
1057 
1058     // Update text edit...
1059     updateLogOutput(data);
1060 
1061     // Update progressbar...
1062     if (m_testCounter > 0) {
1063         m_ui.progressBar->setValue(m_testCounter);
1064     }
1065 }
1066 
1067 void TestRegressionWindow::treeWidgetContextMenuRequested(const QPoint &pos)
1068 {
1069     if ((m_testCounter == -1 && m_activeProcess) || (m_testCounter > 0 && m_activeProcess) ||
1070             m_testsUrl.isEmpty() || m_khtmlUrl.isEmpty()) { // Still processing/not ready yet...
1071         return;
1072     }
1073 
1074     QTreeWidgetItem *item = m_ui.treeWidget->itemAt(pos);
1075     if (item && item != m_ui.treeWidget->topLevelItem(0)) {
1076         m_activeTreeItem = item;
1077 
1078         // Build & show popup menu...
1079         QMenu menu(m_ui.treeWidget);
1080 
1081         menu.addAction(SmallIcon("media-playback-start"), i18n("Run test..."), this, SLOT(runSingleTest()));
1082         menu.addSeparator();
1083         menu.addAction(SmallIcon("list-add"), i18n("Add to ignores..."), this, SLOT(addToIgnores()));
1084         menu.addAction(SmallIcon("dialog-cancel"), i18n("Remove from ignores..."), this, SLOT(removeFromIgnores()));
1085 
1086         if (!menu.exec(m_ui.treeWidget->mapToGlobal(pos))) {
1087             m_activeTreeItem = 0;    // Needs reset...
1088         }
1089     }
1090 }
1091 
1092 void TestRegressionWindow::updateLogOutput(const QString &data)
1093 {
1094     QTextCursor cursor = m_ui.textEdit->textCursor();
1095 
1096     // Append 'data'...
1097     m_ui.textEdit->insertHtml(data);
1098 
1099     // Keep a maximum of 100 lines in the log...
1100     const int maxLogLines = 100;
1101 
1102     long logLines = countLogLines();
1103     if (logLines > maxLogLines) {
1104         cursor.movePosition(QTextCursor::Start);
1105         cursor.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor, logLines - maxLogLines);
1106         cursor.removeSelectedText();
1107     }
1108 
1109     // Go to end...
1110     cursor.movePosition(QTextCursor::End);
1111     m_ui.textEdit->setTextCursor(cursor);
1112 
1113     // Eventually save log output...
1114     if (!m_saveLogUrl.isEmpty()) {
1115         QString fileName = m_saveLogUrl.path() + "/logOutput.html";
1116         QIODevice::OpenMode fileFlags = QIODevice::WriteOnly;
1117 
1118         bool fileExists = QFileInfo(fileName).exists();
1119         if (fileExists) {
1120             fileFlags |= QIODevice::Append;
1121         }
1122 
1123         QFile file(fileName);
1124         if (!file.open(fileFlags)) {
1125             kError() << " Can't open " << fileName;
1126             exit(1);
1127         }
1128 
1129         if (!fileExists) {
1130             file.write(QString::fromLatin1("<html>\n<body>\n").toLatin1());
1131         }
1132 
1133         file.write(QString(data + "\n").toLatin1());    //krazy:exclude=duoblequote_chars DOM demands chars
1134         file.close();
1135 
1136         // Reset save log url, if we reached the end...
1137         if (data.contains("</body>\n</html>")) {
1138             m_saveLogUrl = QUrl();
1139         }
1140     }
1141 }
1142 
1143 unsigned long TestRegressionWindow::countLogLines() const
1144 {
1145     QTextCursor cursor = m_ui.textEdit->textCursor();
1146     cursor.movePosition(QTextCursor::Start);
1147 
1148     unsigned long lines = 0;
1149     while (cursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor)) {
1150         lines++;
1151     }
1152 
1153     return lines;
1154 }
1155 
1156 QStringList TestRegressionWindow::readListFile(const QString &fileName) const
1157 {
1158     QStringList files;
1159 
1160     QFileInfo fileInfo(fileName);
1161     if (fileInfo.exists()) {
1162         QFile file(fileName);
1163         if (!file.open(QIODevice::ReadOnly)) {
1164             kError() << " Can't open " << fileName;
1165             exit(1);
1166         }
1167 
1168         QString line;
1169 
1170         QTextStream fileStream(&file);
1171         while (!(line = fileStream.readLine()).isNull()) {
1172             files.append(line);
1173         }
1174 
1175         file.close();
1176     }
1177 
1178     return files;
1179 }
1180 
1181 void TestRegressionWindow::writeListFile(const QString &fileName, const QStringList &content) const
1182 {
1183     QFile file(fileName);
1184     if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
1185         kError() << " Can't open " << fileName;
1186         exit(1);
1187     }
1188 
1189     file.write(content.join("\n").toLatin1());
1190     file.close();
1191 }
1192 
1193 #include "moc_test_regression_gui_window.cpp"