File indexing completed on 2024-05-12 04:39:28

0001 /*
0002     SPDX-FileCopyrightText: 2012 Miha Čančula <miha@noughmad.eu>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "ctestrunjob.h"
0008 #include "ctestsuite.h"
0009 #include "qttestdelegate.h"
0010 #include <debug_testing.h>
0011 
0012 #include <algorithm>
0013 #include <interfaces/ilaunchconfiguration.h>
0014 #include <interfaces/icore.h>
0015 #include <interfaces/itestcontroller.h>
0016 #include <interfaces/iruncontroller.h>
0017 #include <interfaces/ilauncher.h>
0018 #include <interfaces/launchconfigurationtype.h>
0019 #include <interfaces/ilaunchmode.h>
0020 #include <util/executecompositejob.h>
0021 #include <outputview/outputmodel.h>
0022 
0023 #include <KConfigGroup>
0024 #include <KLocalizedString>
0025 
0026 using namespace KDevelop;
0027 
0028 CTestRunJob::CTestRunJob(CTestSuite* suite, const QStringList& cases, OutputJob::OutputJobVerbosity verbosity, QObject* parent)
0029 : KJob(parent)
0030 , m_suite(suite)
0031 , m_cases(cases)
0032 , m_job(nullptr)
0033 , m_outputModel(nullptr)
0034 , m_verbosity(verbosity)
0035 {
0036     for (const QString& testCase : cases) {
0037         m_caseResults[testCase] = TestResult::NotRun;
0038     }
0039 
0040     setCapabilities(Killable);
0041 }
0042 
0043 
0044 static KJob* createTestJob(const QString& launchModeId, const QStringList& arguments, const QString &workingDirectory)
0045 {
0046     LaunchConfigurationType* type = ICore::self()->runController()->launchConfigurationTypeForId( QStringLiteral("Native Application") );
0047     ILaunchMode* mode = ICore::self()->runController()->launchModeForId( launchModeId );
0048 
0049     qCDebug(CMAKE_TESTING) << "got mode and type:" << type << type->id() << mode << mode->id();
0050     Q_ASSERT(type && mode);
0051 
0052     ILauncher* launcher = [type, mode]() {
0053         const auto launchers = type->launchers();
0054         auto it = std::find_if(launchers.begin(), launchers.end(), [mode](ILauncher *l) {
0055             return l->supportedModes().contains(mode->id());
0056         });
0057         Q_ASSERT(it != launchers.end());
0058         return *it;
0059     }();
0060     Q_ASSERT(launcher);
0061 
0062     auto ilaunch = [type]() {
0063         const auto launchConfigurations = ICore::self()->runController()->launchConfigurations();
0064         auto it = std::find_if(launchConfigurations.begin(), launchConfigurations.end(),
0065                             [type](ILaunchConfiguration* l) {
0066                                 return (l->type() == type && l->config().readEntry("ConfiguredByCTest", false));
0067                             });
0068         return it == launchConfigurations.end() ? nullptr : *it;
0069     }();
0070 
0071     if (!ilaunch) {
0072         ilaunch = ICore::self()->runController()->createLaunchConfiguration( type,
0073                                                 qMakePair( mode->id(), launcher->id() ),
0074                                                 nullptr, //TODO add project
0075                                                 i18n("CTest") );
0076         ilaunch->config().writeEntry("ConfiguredByCTest", true);
0077         //qCDebug(CMAKE_TESTING) << "created config, launching";
0078     } else {
0079         //qCDebug(CMAKE_TESTING) << "reusing generated config, launching";
0080     }
0081     if (!workingDirectory.isEmpty())
0082         ilaunch->config().writeEntry( "Working Directory", QUrl::fromLocalFile( workingDirectory ) );
0083     type->configureLaunchFromCmdLineArguments( ilaunch->config(), arguments );
0084     return ICore::self()->runController()->execute(launchModeId, ilaunch);
0085 }
0086 
0087 void CTestRunJob::start()
0088 {
0089 //     if (!m_suite->cases().isEmpty())
0090 //     {
0091         // TODO: Find a better way of determining whether QTestLib is used by this test
0092 //         qCDebug(CMAKE_TESTING) << "Setting a QtTestDelegate";
0093 //         setDelegate(new QtTestDelegate);
0094 //     }
0095 //     setStandardToolView(IOutputView::RunView);
0096 
0097     QStringList arguments = m_cases;
0098     if (m_cases.isEmpty() && !m_suite->arguments().isEmpty())
0099     {
0100         arguments = m_suite->arguments();
0101     }
0102 
0103     QStringList cases_selected = arguments;
0104     arguments.prepend(m_suite->executable().toLocalFile());
0105     const QString workingDirectory = m_suite->properties().value(QStringLiteral("WORKING_DIRECTORY"), QString());
0106 
0107     m_job = createTestJob(QStringLiteral("execute"), arguments, workingDirectory);
0108 
0109     if (auto* cjob = qobject_cast<ExecuteCompositeJob*>(m_job)) {
0110         auto* outputJob = cjob->findChild<OutputJob*>();
0111         if (outputJob) {
0112             outputJob->setVerbosity(m_verbosity);
0113 
0114             QString testName = m_suite->name();
0115             QString title;
0116             if (cases_selected.count() == 1)
0117                 title = i18nc("running test %1, %2 test case", "CTest %1: %2", testName, cases_selected.value(0));
0118             else
0119                 title = i18ncp("running test %1, %2 number of test cases", "CTest %2 (%1)", "CTest %2 (%1)",
0120                                cases_selected.count(), testName);
0121 
0122             outputJob->setTitle(title);
0123 
0124             m_outputModel = qobject_cast<OutputModel*>(outputJob->model());
0125             connect(m_outputModel, &QAbstractItemModel::rowsInserted, this, &CTestRunJob::rowsInserted);
0126         }
0127     }
0128     connect(m_job, &KJob::finished, this, &CTestRunJob::processFinished);
0129 
0130     ICore::self()->testController()->notifyTestRunStarted(m_suite, cases_selected);
0131 }
0132 
0133 bool CTestRunJob::doKill()
0134 {
0135     if (m_job)
0136     {
0137         m_job->kill();
0138     }
0139     return true;
0140 }
0141 
0142 void CTestRunJob::processFinished(KJob* job)
0143 {
0144     int error = job->error();
0145     auto finished = [this,error]() {
0146         TestResult result;
0147         result.testCaseResults = m_caseResults;
0148         if (error == OutputJob::FailedShownError) {
0149             result.suiteResult = TestResult::Failed;
0150         } else if (error == KJob::NoError) {
0151             result.suiteResult = TestResult::Passed;
0152         } else {
0153             result.suiteResult = TestResult::Error;
0154         }
0155 
0156         // in case the job was killed, mark this job as killed as well
0157         if (error == KJob::KilledJobError) {
0158             setError(KJob::KilledJobError);
0159             setErrorText(QStringLiteral("Child job was killed."));
0160         }
0161 
0162         qCDebug(CMAKE_TESTING) << result.suiteResult << result.testCaseResults;
0163         ICore::self()->testController()->notifyTestRunFinished(m_suite, result);
0164         emitResult();
0165     };
0166 
0167     if (m_outputModel)
0168     {
0169         connect(m_outputModel, &OutputModel::allDone, this, finished, Qt::QueuedConnection);
0170         m_outputModel->ensureAllDone();
0171     }
0172     else
0173     {
0174         finished();
0175     }
0176 }
0177 
0178 void CTestRunJob::rowsInserted(const QModelIndex &parent, int startRow, int endRow)
0179 {
0180     // This regular expression matches the name of the testcase (whatever between the last "::" and "(", indeed )
0181     // For example, from:
0182     //      PASS   : ExpTest::testExp(sum)
0183     // matches "testExp"
0184     static QRegExp caseRx(QStringLiteral("::([^:]*)\\("), Qt::CaseSensitive, QRegExp::RegExp2);
0185     for (int row = startRow; row <= endRow; ++row)
0186     {
0187         QString line = m_outputModel->data(m_outputModel->index(row, 0, parent), Qt::DisplayRole).toString();
0188 
0189         QString testCase;
0190         if (caseRx.indexIn(line) >= 0) {
0191             testCase = caseRx.cap(1);
0192         }
0193 
0194         TestResult::TestCaseResult prevResult = m_caseResults.value(testCase, TestResult::NotRun);
0195         if (prevResult == TestResult::Passed || prevResult == TestResult::NotRun)
0196         {
0197             TestResult::TestCaseResult result = TestResult::NotRun;
0198             const bool expectFail = m_suite->properties().value(QStringLiteral("WILL_FAIL"), QStringLiteral("FALSE")) == QLatin1String("TRUE");
0199             if (line.startsWith(QLatin1String("PASS   :")))
0200             {
0201                 result = expectFail ? TestResult::UnexpectedPass : TestResult::Passed;
0202             }
0203             else if (line.startsWith(QLatin1String("FAIL!  :")))
0204             {
0205                 result = expectFail ? TestResult::ExpectedFail : TestResult::Failed;
0206             }
0207             else if (line.startsWith(QLatin1String("XFAIL  :")))
0208             {
0209                 result = TestResult::ExpectedFail;
0210             }
0211             else if (line.startsWith(QLatin1String("XPASS  :")))
0212             {
0213                 result = TestResult::UnexpectedPass;
0214             }
0215             else if (line.startsWith(QLatin1String("SKIP   :")))
0216             {
0217                 result = TestResult::Skipped;
0218             }
0219 
0220             if (result != TestResult::NotRun)
0221             {
0222                 m_caseResults[testCase] = result;
0223             }
0224         }
0225     }
0226 }
0227 
0228 #include "moc_ctestrunjob.cpp"