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"