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