1 /*
2     SPDX-FileCopyrightText: 2012 Miha Čančula <miha@noughmad.eu>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "phpunitrunjob.h"
8 #include "phpunittestsuite.h"
9 #include "testdoxdelegate.h"
10 #include "testproviderdebug.h"
11 
12 #include <QStandardPaths>
13 
14 #include <util/processlinemaker.h>
15 #include <util/executecompositejob.h>
16 #include <outputview/outputmodel.h>
17 #include <interfaces/itestcontroller.h>
18 #include <interfaces/iruncontroller.h>
19 #include <interfaces/icore.h>
20 #include <interfaces/ilauncher.h>
21 #include <interfaces/ilaunchconfiguration.h>
22 #include <interfaces/launchconfigurationtype.h>
23 #include <interfaces/ilaunchmode.h>
24 
25 #include <KProcess>
26 #include <KLocalizedString>
27 #include <KConfigGroup>
28 
PhpUnitRunJob(PhpUnitTestSuite * suite,const QStringList & cases,KDevelop::OutputJob::OutputJobVerbosity verbosity,QObject * parent)29 PhpUnitRunJob::PhpUnitRunJob(PhpUnitTestSuite* suite, const QStringList& cases, KDevelop::OutputJob::OutputJobVerbosity verbosity, QObject* parent)
30 : KJob(parent)
31 , m_process(nullptr)
32 , m_suite(suite)
33 , m_cases(cases)
34 , m_job(nullptr)
35 , m_outputJob(nullptr)
36 , m_verbosity(verbosity)
37 {
38 }
39 
createTestJob(QString launchModeId,QStringList arguments)40 KJob* createTestJob(QString launchModeId, QStringList arguments )
41 {
42     KDevelop::LaunchConfigurationType* type = KDevelop::ICore::self()->runController()->launchConfigurationTypeForId( QStringLiteral("Script Application") );
43     KDevelop::ILaunchMode* mode = KDevelop::ICore::self()->runController()->launchModeForId( launchModeId );
44 
45     qCDebug(TESTPROVIDER) << "got mode and type:" << type << type->id() << mode << mode->id();
46     Q_ASSERT(type && mode);
47 
48     KDevelop::ILauncher* launcher = nullptr;
49     foreach (KDevelop::ILauncher *l, type->launchers())
50     {
51         //qCDebug(TESTPROVIDER) << "available launcher" << l << l->id() << l->supportedModes();
52         if (l->supportedModes().contains(mode->id())) {
53             launcher = l;
54             break;
55         }
56     }
57     Q_ASSERT(launcher);
58 
59     KDevelop::ILaunchConfiguration* ilaunch = nullptr;
60     QList<KDevelop::ILaunchConfiguration*> launchConfigurations = KDevelop::ICore::self()->runController()->launchConfigurations();
61     foreach (KDevelop::ILaunchConfiguration *l, launchConfigurations) {
62         if (l->type() == type && l->config().readEntry("ConfiguredByPhpUnit", false)) {
63             ilaunch = l;
64             break;
65         }
66     }
67     if (!ilaunch) {
68         ilaunch = KDevelop::ICore::self()->runController()->createLaunchConfiguration( type,
69                                                 qMakePair( mode->id(), launcher->id() ),
70                                                 nullptr, //TODO add project
71                                                 i18n("PHPUnit") );
72         ilaunch->config().writeEntry("ConfiguredByPhpUnit", true);
73         //qCDebug(TESTPROVIDER) << "created config, launching";
74     } else {
75         //qCDebug(TESTPROVIDER) << "reusing generated config, launching";
76     }
77     type->configureLaunchFromCmdLineArguments( ilaunch->config(), arguments );
78     return KDevelop::ICore::self()->runController()->execute(launchModeId, ilaunch);
79 }
80 
start()81 void PhpUnitRunJob::start()
82 {
83     m_process = new KProcess(this);
84     // TODO: Arguments from test cases
85 
86     QStringList args;
87 
88     if (m_cases != m_suite->cases())
89     {
90         args << QStringLiteral("--filter");
91         args << '"' + m_cases.join(QStringLiteral("|")) + '"';
92     }
93 
94     args << QStringLiteral("--testdox") << m_suite->name() << m_suite->url().toLocalFile();
95 
96     const QString exe = QStandardPaths::findExecutable(QStringLiteral("phpunit"));
97     if (exe.isEmpty()) {
98         KDevelop::ITestController* tc = KDevelop::ICore::self()->testController();
99         tc->notifyTestRunFinished(m_suite, m_result);
100         emitResult();
101         return;
102     }
103 
104     args.prepend(exe);
105     args.prepend(QStringLiteral("php"));
106 
107     m_job = createTestJob(QStringLiteral("execute"), args);
108 
109     m_outputJob = qobject_cast<KDevelop::OutputJob*>(m_job);
110     if (!m_outputJob) {
111         if (UnprotectedExecuteCompositeJob* cjob = qobject_cast<UnprotectedExecuteCompositeJob*>(m_job)) {
112             m_outputJob = qobject_cast<KDevelop::OutputJob*>(cjob->subjobs().last());
113         }
114     }
115     Q_ASSERT(m_outputJob);
116     if (m_outputJob) {
117         m_outputJob->setVerbosity(m_verbosity);
118         connect(m_outputJob->model(), &QAbstractItemModel::rowsInserted, this, &PhpUnitRunJob::rowsInserted);
119     }
120 
121     connect(m_job, &KJob::finished, this, &PhpUnitRunJob::processFinished);
122 }
123 
doKill()124 bool PhpUnitRunJob::doKill()
125 {
126     if (m_job)
127     {
128         m_job->kill();
129     }
130     return true;
131 }
132 
processFinished(KJob * job)133 void PhpUnitRunJob::processFinished(KJob* job)
134 {
135     if (job->error() == 1) {
136         m_result.suiteResult = KDevelop::TestResult::Failed;
137     } else if (job->error() == 0) {
138         m_result.suiteResult = KDevelop::TestResult::Passed;
139         foreach (KDevelop::TestResult::TestCaseResult result, m_result.testCaseResults)
140         {
141             if (result == KDevelop::TestResult::Failed)
142             {
143                 m_result.suiteResult = KDevelop::TestResult::Failed;
144                 break;
145             }
146         }
147     } else {
148         m_result.suiteResult = KDevelop::TestResult::Error;
149     }
150 
151     qCDebug(TESTPROVIDER) << m_result.suiteResult << m_result.testCaseResults;
152     KDevelop::ICore::self()->testController()->notifyTestRunFinished(m_suite, m_result);
153     emitResult();
154 }
155 
rowsInserted(const QModelIndex & parent,int startRow,int endRow)156 void PhpUnitRunJob::rowsInserted(const QModelIndex &parent, int startRow, int endRow)
157 {
158     Q_ASSERT(m_outputJob);
159     static QRegExp testResultLineExp = QRegExp("\\[([x\\s])\\]");
160     for (int row = startRow; row <= endRow; ++row)
161     {
162         QString line = m_outputJob->model()->data(m_outputJob->model()->index(row, 0, parent), Qt::DisplayRole).toString();
163 
164         int i = testResultLineExp.indexIn(line);
165         if (i > -1)
166         {
167             bool passed = testResultLineExp.cap(1) == QLatin1String("x");
168             QString testCase = "test" + line.mid(i+4).toLower().remove(' ');
169             qCDebug(TESTPROVIDER) << "Got result in " << line << " for " << testCase;
170             if (m_cases.contains(testCase, Qt::CaseInsensitive))
171             {
172                 foreach (const QString& realCaseName, m_cases)
173                 {
174                     if (QString::compare(testCase, realCaseName, Qt::CaseInsensitive) == 0)
175                     {
176                         m_result.testCaseResults[testCase] = (passed ? KDevelop::TestResult::Passed : KDevelop::TestResult::Failed);
177                         break;
178                     }
179                 }
180             }
181         }
182         else
183         {
184             qCDebug(TESTPROVIDER) << line << testResultLineExp.pattern() << i;
185         }
186     }
187 }
188