diff --git a/debugjob.cpp b/debugjob.cpp index d4f1cc8..d0cfb8f 100644 --- a/debugjob.cpp +++ b/debugjob.cpp @@ -1,396 +1,387 @@ /* * XDebug Debugger Support * * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2009 Andreas Pakulat * Copyright 2009 Niko Sams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "debugjob.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if KDEVPLATFORM_VERSION < QT_VERSION_CHECK(5,1,40) #include #else #include #endif #include "debugsession.h" #include "xdebugplugin.h" #include "connection.h" #include "debuggerdebug.h" namespace XDebug { XDebugJob::XDebugJob(DebugSession* session, KDevelop::ILaunchConfiguration* cfg, QObject* parent) : KDevelop::OutputJob(parent) , m_proc(nullptr) , m_session(session) { setCapabilities(Killable); session->setLaunchConfiguration(cfg); setObjectName(cfg->name()); IExecuteScriptPlugin* iface = KDevelop::ICore::self()->pluginController()->pluginForExtension("org.kdevelop.IExecuteScriptPlugin")->extension(); Q_ASSERT(iface); #if KDEVPLATFORM_VERSION < QT_VERSION_CHECK(5,1,40) KDevelop::EnvironmentGroupList l(KSharedConfig::openConfig()); QString envgrp = iface->environmentGroup(cfg); #else KDevelop::EnvironmentProfileList l(KSharedConfig::openConfig()); QString envgrp = iface->environmentProfileName(cfg); #endif QString err; QString interpreter = iface->interpreter(cfg, err); if (!err.isEmpty()) { setError(-1); setErrorText(err); return; } QUrl script = iface->script(cfg, err); if (!err.isEmpty()) { setError(-3); setErrorText(err); return; } QString remoteHost = iface->remoteHost(cfg, err); if (!err.isEmpty()) { setError(-4); setErrorText(err); return; } if (envgrp.isEmpty()) { qCWarning(KDEV_PHP_DEBUGGER) << "Launch Configuration:" << cfg->name() << i18n("No environment group specified, looks like a broken " "configuration, please check run configuration '%1'. " "Using default environment group.", cfg->name()); #if KDEVPLATFORM_VERSION < QT_VERSION_CHECK(5,1,40) envgrp = l.defaultGroup(); #else envgrp = l.defaultProfileName(); #endif } QStringList arguments = iface->arguments(cfg, err); if (!err.isEmpty()) { setError(-2); setErrorText(err); } if (error() != 0) { qCWarning(KDEV_PHP_DEBUGGER) << "Launch Configuration:" << cfg->name() << "oops, problem" << errorText(); return; } QString ideKey = "kdev" + QString::number(qrand()); //TODO NIKO set ideKey to session? m_proc = new KProcess(this); m_lineMaker = new KDevelop::ProcessLineMaker(m_proc, this); setStandardToolView(KDevelop::IOutputView::RunView); setBehaviours(KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll); KDevelop::OutputModel* m = new KDevelop::OutputModel(); m->setFilteringStrategy(KDevelop::OutputModel::ScriptErrorFilter); setModel(m); connect(m_lineMaker, &KDevelop::ProcessLineMaker::receivedStdoutLines, model(),&KDevelop::OutputModel::appendLines); #if QT_VERSION < 0x050600 connect(m_proc, static_cast(&QProcess::error), #else connect(m_proc, &QProcess::errorOccurred, #endif this, &XDebugJob::processError); connect(m_proc, static_cast (&QProcess::finished), this, &XDebugJob::processFinished); QStringList env = l.createEnvironment(envgrp, m_proc->systemEnvironment()); env << "XDEBUG_CONFIG=\"remote_enable=1 \""; m_proc->setEnvironment(env); QUrl wc = iface->workingDirectory(cfg); if (!wc.isValid() || wc.isEmpty()) { wc = QUrl(QFileInfo(script.toLocalFile()).absolutePath()); } m_proc->setWorkingDirectory(wc.toLocalFile()); m_proc->setProperty("executable", interpreter); QStringList program; if (!remoteHost.isEmpty()) { program << "ssh"; QStringList parts = remoteHost.split(":"); program << parts.first(); if (parts.length() > 1) { program << "-p " + parts.at(1); } program << "XDEBUG_CONFIG=\"remote_enable=1 \""; } qCDebug(KDEV_PHP_DEBUGGER) << program; program << interpreter; program << "-d xdebug.remote_enable=1"; QString remoteHostSetting = cfg->config().readEntry("RemoteHost", QString()); int remotePortSetting = cfg->config().readEntry("RemotePort", 9000); if (!remoteHostSetting.isEmpty()) { program << "-d xdebug.remote_host=" + remoteHostSetting; } program << "-d xdebug.remote_port=" + QString::number(remotePortSetting); program << "-d xdebug.idekey=" + ideKey; program << script.toLocalFile(); program << arguments; qCDebug(KDEV_PHP_DEBUGGER) << "setting app:" << program; m_proc->setOutputChannelMode(KProcess::MergedChannels); m_proc->setProgram(program); setTitle(cfg->name()); setStandardToolView(KDevelop::IOutputView::DebugView); } void XDebugJob::start() { qCDebug(KDEV_PHP_DEBUGGER) << "launching?" << m_proc; if (m_proc) { QString err; if (!m_session->listenForConnection(err)) { qCWarning(KDEV_PHP_DEBUGGER) << "listening for connection failed"; setError(-1); setErrorText(err); emitResult(); + m_session->stopDebugger(); return; } startOutput(); qCDebug(KDEV_PHP_DEBUGGER) << "starting" << m_proc->program().join(" "); appendLine(i18n("Starting: %1", m_proc->program().join(" "))); m_proc->start(); } else { qCWarning(KDEV_PHP_DEBUGGER) << "No process, something went wrong when creating the job"; // No process means we've returned early on from the constructor, some bad error happened emitResult(); + m_session->stopDebugger(); } } KProcess* XDebugJob::process() const { return m_proc; } bool XDebugJob::doKill() { qCDebug(KDEV_PHP_DEBUGGER); if (m_session) { m_session->stopDebugger(); } return true; } void XDebugJob::processFinished(int exitCode, QProcess::ExitStatus status) { m_lineMaker->flushBuffers(); if (exitCode == 0 && status == QProcess::NormalExit) { appendLine(i18n("*** Exited normally ***")); } else if (status == QProcess::NormalExit) { appendLine(i18n("*** Exited with return code: %1 ***", QString::number(exitCode))); } else if (error() == KJob::KilledJobError) { appendLine(i18n("*** Process aborted ***")); } else { appendLine(i18n("*** Crashed with return code: %1 ***", QString::number(exitCode))); } qCDebug(KDEV_PHP_DEBUGGER) << "Process done"; emitResult(); - - if (m_session && m_session->connection()) { - m_session->connection()->setState(DebugSession::EndedState); - } } void XDebugJob::processError(QProcess::ProcessError error) { if (error == QProcess::FailedToStart) { setError(-1); QString errmsg = i18n("Could not start program '%1'. Make sure that the " "path is specified correctly.", m_proc->property("executable").toString()); setErrorText(errmsg); + m_session->stopDebugger(); emitResult(); } qCDebug(KDEV_PHP_DEBUGGER) << "Process error"; - - if (m_session && m_session->connection()) { - m_session->connection()->setState(DebugSession::EndedState); - } - else if(m_session) - { - m_session->stateChanged(DebugSession::EndedState); - m_session->stopDebugger(); - } } void XDebugJob::appendLine(const QString& l) { if (KDevelop::OutputModel* m = model()) { m->appendLine(l); } } KDevelop::OutputModel* XDebugJob::model() { return dynamic_cast(KDevelop::OutputJob::model()); } XDebugBrowserJob::XDebugBrowserJob(DebugSession* session, KDevelop::ILaunchConfiguration* cfg, QObject* parent) : KJob(parent) , m_session(session) { setCapabilities(Killable); session->setLaunchConfiguration(cfg); setObjectName(cfg->name()); IExecuteBrowserPlugin* iface = KDevelop::ICore::self()->pluginController() ->pluginForExtension("org.kdevelop.IExecuteBrowserPlugin")->extension(); Q_ASSERT(iface); QString err; m_url = iface->url(cfg, err); if (!err.isEmpty()) { m_url.clear(); setError(-1); setErrorText(err); return; } m_browser = iface->browser(cfg); setObjectName(cfg->name()); connect(m_session, &KDevelop::IDebugSession::finished,this, &XDebugBrowserJob::sessionFinished); m_session->setAcceptMultipleConnections(true); } void XDebugBrowserJob::start() { qCDebug(KDEV_PHP_DEBUGGER) << "launching?" << m_url; if (!m_url.isValid()) { emitResult(); return; } QString err; if (!m_session->listenForConnection(err)) { qCWarning(KDEV_PHP_DEBUGGER) << "listening for connection failed"; setError(-1); setErrorText(err); emitResult(); + m_session->stopDebugger(); return; } QUrl url = m_url; url.setQuery("XDEBUG_SESSION_START=kdev"); - if (m_browser.isEmpty()) { - if (!QDesktopServices::openUrl(url)) { - qCWarning(KDEV_PHP_DEBUGGER) << "openUrl failed, something went wrong when creating the job"; - emitResult(); - } - } else { - KProcess proc(this); - proc.setProgram(QStringList() << m_browser << url.url()); - if( !proc.startDetached() ) - { - processFailedToStart(); - } - } + launchBrowser(url); } void XDebugBrowserJob::processFailedToStart() { qCWarning(KDEV_PHP_DEBUGGER) << "Cannot start application" << m_browser; setError(-1); QString errmsg = i18n("Could not start program '%1'. Make sure that the " "path is specified correctly.", m_browser); setErrorText(errmsg); emitResult(); qCDebug(KDEV_PHP_DEBUGGER) << "Process error"; + m_session->stopDebugger(); +} - if (m_session && m_session->connection()) { - m_session->connection()->setState(DebugSession::EndedState); - } - else if(m_session) - { - m_session->stateChanged(DebugSession::EndedState); - m_session->stopDebugger(); + +void XDebugBrowserJob::launchBrowser(QUrl &url) +{ + if (m_browser.isEmpty()) { + if (!QDesktopServices::openUrl(url)) { + qCWarning(KDEV_PHP_DEBUGGER) << "openUrl failed, something went wrong when creating the job"; + emitResult(); + m_session->stopDebugger(); + } + } else { + KProcess proc(this); + + proc.setProgram(QStringList() << m_browser << url.url()); + if( !proc.startDetached() ) + { + processFailedToStart(); + } } } bool XDebugBrowserJob::doKill() { qCDebug(KDEV_PHP_DEBUGGER); m_session->stopDebugger(); QUrl url = m_url; url.setQuery("XDEBUG_SESSION_STOP_NO_EXEC=kdev"); - QDesktopServices::openUrl(url); + launchBrowser(url); return true; } void XDebugBrowserJob::sessionFinished() { emitResult(); } } diff --git a/debugjob.h b/debugjob.h index a8271b6..b5b212c 100644 --- a/debugjob.h +++ b/debugjob.h @@ -1,94 +1,95 @@ /* * XDebug Debugger Support * * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2009 Andreas Pakulat * Copyright 2009 Niko Sams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef XDEBUGDEBUGJOB #define XDEBUGDEBUGJOB #include #include #include #include class KProcess; namespace KDevelop { class OutputModel; class ILaunchConfiguration; class ProcessLineMaker; } namespace XDebug { class DebugSession; class XDebugJob : public KDevelop::OutputJob { Q_OBJECT public: XDebugJob(DebugSession* session, KDevelop::ILaunchConfiguration*, QObject* parent = nullptr); void start() override; KProcess* process() const; protected: bool doKill() override; private: KDevelop::OutputModel* model(); private slots: void processError(QProcess::ProcessError); void processFinished(int, QProcess::ExitStatus); private: void appendLine(const QString& l); KProcess* m_proc; KDevelop::ProcessLineMaker* m_lineMaker; QPointer m_session; }; class XDebugBrowserJob : public KJob { Q_OBJECT public: XDebugBrowserJob(DebugSession* session, KDevelop::ILaunchConfiguration*, QObject* parent = nullptr); void start() override; protected: bool doKill() override; + void launchBrowser(QUrl &url); private slots: void sessionFinished(); private: void processFailedToStart(); QUrl m_url; QString m_browser; DebugSession* m_session; }; } #endif diff --git a/debugsession.cpp b/debugsession.cpp index 81e088d..1295df8 100644 --- a/debugsession.cpp +++ b/debugsession.cpp @@ -1,329 +1,335 @@ /* * XDebug Debugger Support * * Copyright 2009 Niko Sams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "debugsession.h" #include #include #include #include #include #include #include #include #include "connection.h" #include "breakpointcontroller.h" #include "framestackmodel.h" #include "variablecontroller.h" #include "launchconfig.h" #include "debuggerdebug.h" namespace XDebug { DebugSession::DebugSession() : KDevelop::IDebugSession() , m_breakpointController(new BreakpointController(this)) , m_variableController(new VariableController(this)) , m_frameStackModel(new FrameStackModel(this)) , m_server(nullptr) , m_connection(nullptr) , m_launchConfiguration(nullptr) , m_acceptMultipleConnections(false) { } DebugSession::~DebugSession() { emit finished(); } void DebugSession::setLaunchConfiguration(KDevelop::ILaunchConfiguration* cfg) { m_launchConfiguration = cfg; } void DebugSession::setAcceptMultipleConnections(bool v) { m_acceptMultipleConnections = v; } bool DebugSession::listenForConnection(QString& error) { Q_ASSERT(!m_server); m_server = new QTcpServer(this); qCDebug(KDEV_PHP_DEBUGGER); int remotePortSetting = m_launchConfiguration->config().readEntry("RemotePort", 9000); if (m_server->listen(QHostAddress::Any, remotePortSetting)) { connect(m_server, &QTcpServer::newConnection, this, &DebugSession::incomingConnection); + // avoid 'debug launch' button + stateChanged(ActiveState); } else { error = i18n("Opening port %1 failed: %2.", remotePortSetting, m_server->errorString()); qCWarning(KDEV_PHP_DEBUGGER) << "Error" << m_server->errorString(); delete m_server; m_server = nullptr; return false; } return m_server->isListening(); } void DebugSession::closeServer() { if (m_server) { m_server->close(); m_server->deleteLater(); m_server = nullptr; } } void DebugSession::incomingConnection() { qCDebug(KDEV_PHP_DEBUGGER); QTcpSocket* client = m_server->nextPendingConnection(); if (m_connection) { m_connection->disconnect(); m_connection->deleteLater(); m_connection = nullptr; } m_connection = new Connection(client, this); connect(m_connection, &Connection::output, this, &DebugSession::output); connect(m_connection, &Connection::outputLine, this, &DebugSession::outputLine); connect(m_connection, &Connection::stateChanged, this, &DebugSession::stateChanged); connect(m_connection, &Connection::stateChanged, this, &DebugSession::_stateChanged); connect(m_connection, &Connection::currentPositionChanged,this, &DebugSession::currentPositionChanged); connect(m_connection, &Connection::closed, this, &DebugSession::connectionClosed); if (!m_acceptMultipleConnections) { closeServer(); } } void DebugSession::connectionClosed() { Q_ASSERT(sender() == m_connection); if (m_acceptMultipleConnections && m_server && m_server->isListening() - && m_server->hasPendingConnections() ) { - m_connection->setState(DebugSession::NotStartedState); + // clear variable widget + emit stateChanged(NotStartedState); + // avoid 'debug launch' button + emit stateChanged(ActiveState); } else { m_connection->setState(DebugSession::EndedState); } m_connection->deleteLater(); m_connection = nullptr; } void DebugSession::_stateChanged(KDevelop::IDebugSession::DebuggerState state) { qCDebug(KDEV_PHP_DEBUGGER) << state; if (state == StartingState) { run(); } else if (state == PausedState) { raiseEvent(program_state_changed); } else if (state == StoppingState) { m_connection->sendCommand("stop"); } else if (state == EndedState) { raiseEvent(debugger_exited); emit finished(); } } DebugSession::DebuggerState DebugSession::state() const { if (!m_connection) { return NotStartedState; } return m_connection->currentState(); } void DebugSession::run() { Q_ASSERT(m_connection); m_connection->sendCommand("run"); m_connection->setState(ActiveState); } void DebugSession::stepOut() { Q_ASSERT(m_connection); m_connection->sendCommand("step_out"); m_connection->setState(ActiveState); } void DebugSession::stepOverInstruction() { } void DebugSession::stepInto() { Q_ASSERT(m_connection); m_connection->sendCommand("step_into"); m_connection->setState(ActiveState); } void DebugSession::stepIntoInstruction() { } void DebugSession::stepOver() { Q_ASSERT(m_connection); m_connection->sendCommand("step_over"); m_connection->setState(ActiveState); } void DebugSession::jumpToCursor() { } void DebugSession::runToCursor() { } void DebugSession::interruptDebugger() { } void DebugSession::stopDebugger() { closeServer(); - if (!m_connection || m_connection->currentState() == DebugSession::StoppedState || !m_connection->socket()) { + // finish debugger when no connection active + if ( state() == DebugSession::NotStartedState && !m_connection ) { + stateChanged(DebugSession::EndedState); emit finished(); } else { m_connection->sendCommand("stop"); } } void DebugSession::restartDebugger() { } void DebugSession::eval(QByteArray source) { Q_ASSERT(m_connection); m_connection->sendCommand("eval", QStringList(), source); } bool DebugSession::waitForFinished(int msecs) { QTime stopWatch; stopWatch.start(); if (!waitForState(DebugSession::StoppingState, msecs)) { return false; } if (msecs != -1) { msecs = msecs - stopWatch.elapsed(); } return true; } bool DebugSession::waitForState(KDevelop::IDebugSession::DebuggerState state, int msecs) { if (!m_connection) { return false; } if (m_connection->currentState() == state) { return true; } QTime stopWatch; stopWatch.start(); if (!waitForConnected(msecs)) { return false; } while (m_connection->currentState() != state) { if (!m_connection) { return false; } if (!m_connection->socket()) { return false; } if (!m_connection->socket()->isOpen()) { return false; } m_connection->socket()->waitForReadyRead(100); if (msecs != -1 && stopWatch.elapsed() > msecs) { return false; } } return true; } bool DebugSession::waitForConnected(int msecs) { if (!m_connection) { Q_ASSERT(m_server); if (!m_server->waitForNewConnection(msecs)) { return false; } } Q_ASSERT(m_connection); Q_ASSERT(m_connection->socket()); return m_connection->socket()->waitForConnected(msecs); } Connection* DebugSession::connection() { return m_connection; } bool DebugSession::restartAvaliable() const { //not supported at all by xdebug return false; } KDevelop::IBreakpointController* DebugSession::breakpointController() const { return m_breakpointController; } KDevelop::IVariableController* DebugSession::variableController() const { return m_variableController; } KDevelop::IFrameStackModel* DebugSession::frameStackModel() const { return m_frameStackModel; } QPair DebugSession::convertToLocalUrl(const QPair& remoteUrl) const { Q_ASSERT(m_launchConfiguration); QPair ret = remoteUrl; ret.first = KDevelop::PathMappings::convertToLocalUrl(m_launchConfiguration->config(), remoteUrl.first); return ret; } QPair DebugSession::convertToRemoteUrl(const QPair& localUrl) const { Q_ASSERT(m_launchConfiguration); QPair ret = localUrl; ret.first = KDevelop::PathMappings::convertToRemoteUrl(m_launchConfiguration->config(), localUrl.first); return ret; } void DebugSession::currentPositionChanged(const QUrl& url, int line) { setCurrentPosition(url, line, QString()); } } diff --git a/tests/connectiontest.cpp b/tests/connectiontest.cpp index 9b75395..ce2e64d 100644 --- a/tests/connectiontest.cpp +++ b/tests/connectiontest.cpp @@ -1,1011 +1,1004 @@ /* * XDebug Debugger Support * * Copyright 2009 Niko Sams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "connectiontest.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "connection.h" #include "debugsession.h" #include "launchconfig.h" #include "debugjob.h" #include "debuggerdebug.h" using namespace XDebug; namespace KParts { class MainWindow; } namespace Sublime { class Controller; } namespace KDevelop { class IToolViewFactory; } namespace { inline QString phpExecutable() { return QStandardPaths::findExecutable("php"); } } class TestLaunchConfiguration : public KDevelop::ILaunchConfiguration { public: TestLaunchConfiguration(const QUrl& script) { c = new KConfig(); cfg = c->group("launch"); cfg.writeEntry("isExecutable", true); cfg.writeEntry("Executable", script); cfg.writeEntry("Interpreter", phpExecutable()); } ~TestLaunchConfiguration() override { delete c; } const KConfigGroup config() const override { return cfg; } KConfigGroup config() override { return cfg; } QString name() const override { return QString("Test-Launch"); } KDevelop::IProject* project() const override { return nullptr; } KDevelop::LaunchConfigurationType* type() const override { return nullptr; } private: KConfigGroup cfg; KConfig* c; }; #define COMPARE_DATA(index, expected) \ QCOMPARE(index.model()->data(index, Qt::DisplayRole).toString(), QString(expected)) void ConnectionTest::initTestCase() { // check whether the php-xdebug module is installed at all const int rc = QProcess::execute(phpExecutable(), {"-r", "exit(extension_loaded('xdebug') ? 0 : 1);"}); if (rc != 0) { QSKIP("This test requires the php-xdebug module to be installed!"); } } void ConnectionTest::init() { qRegisterMetaType("DebugSession*"); KDevelop::AutoTestShell::init({ QStringLiteral("kdevxdebug"), QStringLiteral("kdevexecutebrowser"), QStringLiteral("kdevexecutescript") }); m_core = new KDevelop::TestCore(); m_core->initialize(KDevelop::Core::NoUi); //remove all breakpoints - so we can set our own in the test KDevelop::BreakpointModel* m = KDevelop::ICore::self()->debugController()->breakpointModel(); m->removeRows(0, m->rowCount()); /* KDevelop::VariableCollection *vc = KDevelop::ICore::self()->debugController()->variableCollection(); for (int i=0; i < vc->watches()->childCount(); ++i) { delete vc->watches()->child(i); } vc->watches()->clear(); */ } void ConnectionTest::cleanup() { m_core->cleanup(); delete m_core; } void ConnectionTest::testStdOutput() { QStringList contents; contents << "debugController()->addSession(session); TestLaunchConfiguration cfg(url); XDebugJob job(session, &cfg); QSignalSpy outputLineSpy(session, SIGNAL(outputLine(QString))); QSignalSpy outputSpy(session, SIGNAL(output(QString))); job.start(); session->waitForConnected(); session->waitForFinished(); { QCOMPARE(outputSpy.count(), 4); QList arguments = outputSpy.takeFirst(); QCOMPARE(arguments.count(), 1); QCOMPARE(arguments.first().toString(), QString("foo\n")); } { QCOMPARE(outputLineSpy.count(), 2); QList arguments = outputLineSpy.takeFirst(); QCOMPARE(arguments.count(), 1); QCOMPARE(arguments.at(0).toString(), QString("foo")); arguments = outputLineSpy.takeFirst(); QCOMPARE(arguments.count(), 1); QCOMPARE(arguments.at(0).toString(), QString("foobar")); } } void ConnectionTest::testShowStepInSource() { QStringList contents; contents << "debugController()->addSession(session); TestLaunchConfiguration cfg(url); XDebugJob job(session, &cfg); KDevelop::ICore::self()->debugController()->breakpointModel()->addCodeBreakpoint(url, 1); QSignalSpy showStepInSourceSpy(session, SIGNAL(showStepInSource(QUrl,int,QString))); qCDebug(KDEV_PHP_DEBUGGER) << "************************************************************************************"; job.start(); session->waitForConnected(); session->waitForState(DebugSession::PausedState); QTest::qWait(100); session->stepInto(); session->waitForState(DebugSession::PausedState); QTest::qWait(100); session->run(); session->waitForFinished(); { QCOMPARE(showStepInSourceSpy.count(), 2); QList arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().value(), url); QCOMPARE(arguments.at(1).toInt(), 1); arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().value(), url); QCOMPARE(arguments.at(1).toInt(), 2); } } void ConnectionTest::testMultipleSessions() { QStringList contents; contents << "debugController()->addSession(session); TestLaunchConfiguration cfg(url); XDebugJob job(session, &cfg); job.start(); session->waitForConnected(); session->waitForFinished(); } } void ConnectionTest::testStackModel() { QStringList contents; contents << "debugController()->addSession(session); TestLaunchConfiguration cfg(url); XDebugJob job(session, &cfg); KDevelop::ICore::self()->debugController()->breakpointModel()->addCodeBreakpoint(url, 1); job.start(); session->waitForConnected(); session->waitForState(DebugSession::PausedState); //step into function for (int i = 0; i < 2; ++i) { session->stepInto(); session->waitForState(DebugSession::PausedState); } QTest::qWait(100); KDevelop::IFrameStackModel* stackModel = session->frameStackModel(); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); //one fake thread QModelIndex tIdx = stackModel->index(0, 0); QCOMPARE(stackModel->rowCount(tIdx), 2); QCOMPARE(stackModel->columnCount(tIdx), 3); COMPARE_DATA(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "x"); COMPARE_DATA(tIdx.child(0, 2), url.toLocalFile() + ":3"); COMPARE_DATA(tIdx.child(1, 0), "1"); COMPARE_DATA(tIdx.child(1, 1), "{main}"); COMPARE_DATA(tIdx.child(1, 2), url.toLocalFile() + ":5"); session->stepInto(); session->waitForState(DebugSession::PausedState); session->stepInto(); session->waitForState(DebugSession::PausedState); QTest::qWait(100); QCOMPARE(stackModel->rowCount(tIdx), 1); session->run(); session->waitForFinished(); } void ConnectionTest::testBreakpoint() { QStringList contents; contents << "debugController()->addSession(session); TestLaunchConfiguration cfg(url); XDebugJob job(session, &cfg); KDevelop::ICore::self()->debugController()->breakpointModel()->addCodeBreakpoint(url, 3); job.start(); session->waitForConnected(); QSignalSpy showStepInSourceSpy(session, SIGNAL(showStepInSource(QUrl,int,QString))); session->waitForState(DebugSession::PausedState); QTest::qWait(100); session->run(); session->waitForFinished(); { QCOMPARE(showStepInSourceSpy.count(), 1); QList arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().value(), url); QCOMPARE(arguments.at(1).toInt(), 3); } } void ConnectionTest::testDisableBreakpoint() { QStringList contents; contents << "debugController()->addSession(session); TestLaunchConfiguration cfg(url); XDebugJob job(session, &cfg); KDevelop::BreakpointModel* breakpoints = KDevelop::ICore::self()->debugController()->breakpointModel(); KDevelop::Breakpoint* b; //add disabled breakpoint before startProgram b = breakpoints->addCodeBreakpoint(url, 2); b->setData(KDevelop::Breakpoint::EnableColumn, false); b = breakpoints->addCodeBreakpoint(url, 3); job.start(); session->waitForConnected(); session->waitForState(DebugSession::PausedState); //disable existing breakpoint b->setData(KDevelop::Breakpoint::EnableColumn, false); //add another disabled breakpoint b = breakpoints->addCodeBreakpoint(url, 7); QTest::qWait(300); b->setData(KDevelop::Breakpoint::EnableColumn, false); session->run(); session->waitForFinished(); } void ConnectionTest::testChangeLocationBreakpoint() { QStringList contents; contents << "debugController()->addSession(session); TestLaunchConfiguration cfg(url); XDebugJob job(session, &cfg); KDevelop::BreakpointModel* breakpoints = KDevelop::ICore::self()->debugController()->breakpointModel(); KDevelop::Breakpoint* b = breakpoints->addCodeBreakpoint(url, 5); job.start(); session->waitForConnected(); session->waitForState(DebugSession::PausedState); b->setLine(7); QTest::qWait(100); session->run(); QTest::qWait(100); session->waitForState(DebugSession::PausedState); session->run(); session->waitForFinished(); } void ConnectionTest::testDeleteBreakpoint() { QStringList contents; contents << "debugController()->addSession(session); TestLaunchConfiguration cfg(url); XDebugJob job(session, &cfg); KDevelop::BreakpointModel* breakpoints = KDevelop::ICore::self()->debugController()->breakpointModel(); QCOMPARE(KDevelop::ICore::self()->debugController()->breakpointModel()->rowCount(), 0); //add breakpoint before startProgram breakpoints->addCodeBreakpoint(url, 5); QCOMPARE(KDevelop::ICore::self()->debugController()->breakpointModel()->rowCount(), 1); breakpoints->removeRow(0); QCOMPARE(KDevelop::ICore::self()->debugController()->breakpointModel()->rowCount(), 0); breakpoints->addCodeBreakpoint(url, 2); QCOMPARE(KDevelop::ICore::self()->debugController()->breakpointModel()->rowCount(), 1); job.start(); session->waitForConnected(); session->waitForState(DebugSession::PausedState); breakpoints->removeRow(0); QTest::qWait(100); session->run(); session->waitForFinished(); } void ConnectionTest::testConditionalBreakpoint() { QStringList contents; contents << "debugController()->addSession(session); TestLaunchConfiguration cfg(url); XDebugJob job(session, &cfg); KDevelop::ICore::self()->debugController()->breakpointModel()->addCodeBreakpoint(url, 2) ->setCondition("1==2"); KDevelop::ICore::self()->debugController()->breakpointModel()->addCodeBreakpoint(url, 5) ->setCondition("1==1"); job.start(); session->waitForConnected(); QSignalSpy showStepInSourceSpy(session, SIGNAL(showStepInSource(QUrl,int,QString))); session->waitForState(DebugSession::PausedState); QTest::qWait(100); session->run(); session->waitForFinished(); { QCOMPARE(showStepInSourceSpy.count(), 1); QList arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().value(), url); QCOMPARE(arguments.at(1).toInt(), 5); } } void ConnectionTest::testBreakpointError() { QStringList contents; contents << "debugController()->addSession(session); TestLaunchConfiguration cfg(url); XDebugJob job(session, &cfg); KDevelop::ICore::self()->debugController()->breakpointModel()->addCodeBreakpoint(url, 2); KDevelop::Breakpoint* b = KDevelop::ICore::self()->debugController()->breakpointModel() ->addCodeBreakpoint(QUrl(""), 2); QVERIFY(b->errorText().isEmpty()); job.start(); session->waitForConnected(); session->waitForState(DebugSession::PausedState); qCDebug(KDEV_PHP_DEBUGGER) << b->errorText(); QVERIFY(!b->errorText().isEmpty()); session->run(); session->waitForFinished(); } KDevelop::VariableCollection* variableCollection() { return KDevelop::ICore::self()->debugController()->variableCollection(); } void ConnectionTest::testVariablesLocals() { QStringList contents; contents << " */ QTemporaryFile file("xdebugtest"); file.open(); const auto url = QUrl::fromLocalFile(file.fileName()); file.write(contents.join("\n").toUtf8()); file.close(); auto session = new DebugSession; KDevelop::ICore::self()->debugController()->addSession(session); TestLaunchConfiguration cfg(url); XDebugJob job(session, &cfg); session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); KDevelop::ICore::self()->debugController()->breakpointModel()->addCodeBreakpoint(url, 4); job.start(); session->waitForConnected(); session->waitForState(DebugSession::PausedState); QTest::qWait(1000); QVERIFY(variableCollection()->rowCount() >= 2); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 3); COMPARE_DATA(variableCollection()->index(2, 0, i), "$foo"); COMPARE_DATA(variableCollection()->index(2, 1, i), "foo"); COMPARE_DATA(variableCollection()->index(0, 0, i), "$bar"); COMPARE_DATA(variableCollection()->index(0, 1, i), "123"); COMPARE_DATA(variableCollection()->index(1, 0, i), "$baz"); COMPARE_DATA(variableCollection()->index(1, 1, i), ""); i = variableCollection()->index(1, 0, i); QCOMPARE(variableCollection()->rowCount(i), 3); COMPARE_DATA(variableCollection()->index(0, 0, i), "0"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); COMPARE_DATA(variableCollection()->index(2, 0, i), "2"); COMPARE_DATA(variableCollection()->index(2, 1, i), "5"); session->run(); session->waitForFinished(); } void ConnectionTest::testVariablesSuperglobals() { QStringList contents; contents << "debugController()->addSession(session); TestLaunchConfiguration cfg(url); XDebugJob job(session, &cfg); session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); KDevelop::ICore::self()->debugController()->breakpointModel()->addCodeBreakpoint(url, 1); job.start(); session->waitForConnected(); session->waitForState(DebugSession::PausedState); QTest::qWait(1000); QVERIFY(variableCollection()->rowCount() >= 3); QModelIndex i = variableCollection()->index(2, 0); COMPARE_DATA(i, "Superglobals"); QVERIFY(variableCollection()->rowCount(i) > 4); COMPARE_DATA(variableCollection()->index(0, 0, i), "$_COOKIE"); COMPARE_DATA(variableCollection()->index(0, 1, i), ""); COMPARE_DATA(variableCollection()->index(1, 0, i), "$_ENV"); COMPARE_DATA(variableCollection()->index(1, 1, i), ""); COMPARE_DATA(variableCollection()->index(6, 0, i), "$_SERVER"); COMPARE_DATA(variableCollection()->index(6, 1, i), ""); i = variableCollection()->index(6, 0, i); QVERIFY(variableCollection()->rowCount(i) > 5); session->run(); session->waitForFinished(); } void ConnectionTest::testVariableExpanding() { QStringList contents; contents << "debugController()->addSession(session); TestLaunchConfiguration cfg(url); XDebugJob job(session, &cfg); session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); KDevelop::ICore::self()->debugController()->breakpointModel()->addCodeBreakpoint(url, 2); job.start(); session->waitForConnected(); session->waitForState(DebugSession::PausedState); QTest::qWait(1000); QVERIFY(variableCollection()->rowCount() >= 2); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "$foo"); COMPARE_DATA(variableCollection()->index(0, 1, i), ""); i = i.child(0, 0); variableCollection()->expanded(i); QTest::qWait(1000); QCOMPARE(variableCollection()->rowCount(i), 1); i = i.child(0, 0); variableCollection()->expanded(i); QTest::qWait(1000); QCOMPARE(variableCollection()->rowCount(i), 1); i = i.child(0, 0); variableCollection()->expanded(i); QTest::qWait(1000); QCOMPARE(variableCollection()->rowCount(i), 3); COMPARE_DATA(variableCollection()->index(0, 0, i), "0"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); COMPARE_DATA(variableCollection()->index(2, 0, i), "2"); COMPARE_DATA(variableCollection()->index(2, 1, i), "5"); session->run(); session->waitForFinished(); } class TestTooltipRoot : public KDevelop::TreeItem { public: TestTooltipRoot(KDevelop::TreeModel* model) : KDevelop::TreeItem(model) {} void init(KDevelop::Variable* var) { appendChild(var); } void fetchMoreChildren() override {} }; void ConnectionTest::testTooltipVariable() { QStringList contents; contents << "debugController()->addSession(session); TestLaunchConfiguration cfg(url); XDebugJob job(session, &cfg); KDevelop::ICore::self()->debugController()->breakpointModel()->addCodeBreakpoint(url, 2); job.start(); session->waitForConnected(); session->waitForState(DebugSession::PausedState); QTest::qWait(1000); KDevelop::TreeModel* model = new KDevelop::TreeModel(QVector() << "Name" << "Value", this); TestTooltipRoot* tr = new TestTooltipRoot(model); model->setRootItem(tr); KDevelop::Variable* var = session->variableController()->createVariable(model, tr, "$foo"); tr->init(var); var->attachMaybe(); QTest::qWait(1000); QCOMPARE(model->rowCount(), 1); COMPARE_DATA(model->index(0, 0), "$foo"); COMPARE_DATA(model->index(0, 1), "123"); session->run(); session->waitForFinished(); } void ConnectionTest::testInvalidTooltipVariable() { QStringList contents; contents << "debugController()->addSession(session); TestLaunchConfiguration cfg(url); XDebugJob job(session, &cfg); KDevelop::ICore::self()->debugController()->breakpointModel()->addCodeBreakpoint(url, 2); job.start(); session->waitForConnected(); session->waitForState(DebugSession::PausedState); QTest::qWait(1000); KDevelop::TreeModel* model = new KDevelop::TreeModel(QVector() << "Name" << "Value", this); TestTooltipRoot* tr = new TestTooltipRoot(model); model->setRootItem(tr); KDevelop::Variable* var = session->variableController()->createVariable(model, tr, "blah"); tr->init(var); var->attachMaybe(); QTest::qWait(1000); QCOMPARE(model->rowCount(), 1); COMPARE_DATA(model->index(0, 0), "blah"); COMPARE_DATA(model->index(0, 1), ""); session->run(); session->waitForFinished(); } void ConnectionTest::testPhpCrash() { QStringList contents; contents << " session(new DebugSession); KDevelop::ICore::self()->debugController()->addSession(session); TestLaunchConfiguration cfg(url); XDebugJob* job = new XDebugJob(session, &cfg); KDevelop::ICore::self()->debugController()->breakpointModel()->addCodeBreakpoint(url, 1); job->start(); session->waitForConnected(); session->waitForState(DebugSession::PausedState); job->process()->kill(); //simulate crash QTest::qWait(1000); QVERIFY(!session); //job seems to gets deleted automatically } void ConnectionTest::testConnectionClosed() { QStringList contents; contents << " session(new DebugSession); KDevelop::ICore::self()->debugController()->addSession(session); TestLaunchConfiguration cfg(url); XDebugJob* job = new XDebugJob(session, &cfg); KDevelop::ICore::self()->debugController()->breakpointModel()->addCodeBreakpoint(url, 1); job->start(); session->waitForConnected(); session->waitForState(DebugSession::PausedState); session->connection()->close(); //simulate eg webserver restart QTest::qWait(1000); QVERIFY(!session); //job seems to gets deleted automatically } //see bug 235061 void ConnectionTest::testMultipleConnectionsClosed() { QStringList contents; contents << " session(new DebugSession); KDevelop::ICore::self()->debugController()->addSession(session); session->setAcceptMultipleConnections(true); TestLaunchConfiguration cfg(url); XDebugJob* job = new XDebugJob(session, &cfg); KDevelop::ICore::self()->debugController()->breakpointModel()->addCodeBreakpoint(url, 1); job->start(); session->waitForConnected(); Connection* firstConnection = session->connection(); session->waitForState(DebugSession::PausedState); //start a second process, as XDebugBrowserJob does KProcess secondProcess; secondProcess.setProgram(job->process()->program()); secondProcess.setEnvironment(job->process()->environment()); secondProcess.setOutputChannelMode(KProcess::ForwardedChannels); secondProcess.start(); QTest::qWait(1000); - // FIXME: Is this being handled correctly? - QVERIFY(!session); - return; - QVERIFY(session->connection() != firstConnection); //must be a different connection session->connection()->close(); //close second connection QTest::qWait(1000); -// firstConnection->close(); //close first connection _after_ second - - QTest::qWait(1000); QCOMPARE(session->state(), DebugSession::NotStartedState); //well, it should be EndedState in reality, but this works too //job seems to gets deleted automatically } void ConnectionTest::testVariableUpdates() { QStringList contents; contents << "debugController()->addSession(session); TestLaunchConfiguration cfg(url); XDebugJob job(session, &cfg); session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); KDevelop::ICore::self()->debugController()->breakpointModel()->addCodeBreakpoint(url, 2); job.start(); session->waitForConnected(); session->waitForState(DebugSession::PausedState); QTest::qWait(1000); QVERIFY(variableCollection()->rowCount() >= 2); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "$foo"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->stepInto(); QTest::qWait(1000); QVERIFY(variableCollection()->rowCount() >= 2); i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "$foo"); COMPARE_DATA(variableCollection()->index(0, 1, i), "2"); session->run(); session->waitForFinished(); } // controller.connection()->sendCommand("eval -i 124", QStringList(), "eval(\"function test124() { return rand(); } return test124();\")"); // controller.connection()->sendCommand("eval -i 126", QStringList(), "test124();"); QTEST_MAIN(ConnectionTest)