diff --git a/kdevplatform/language/duchain/navigation/quickopenembeddedwidgetcombiner.h b/kdevplatform/language/duchain/navigation/quickopenembeddedwidgetcombiner.h index c79481650e..ac0acded52 100644 --- a/kdevplatform/language/duchain/navigation/quickopenembeddedwidgetcombiner.h +++ b/kdevplatform/language/duchain/navigation/quickopenembeddedwidgetcombiner.h @@ -1,57 +1,59 @@ /* * This file is part of KDevelop * * Copyright 2019 Milian Wolff * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library 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 KDEVPLATFORM_QUICKOPENEMBEDDEDWIDGETCOMBINER_H #define KDEVPLATFORM_QUICKOPENEMBEDDEDWIDGETCOMBINER_H #include #include namespace KDevelop { class QuickOpenEmbeddedWidgetCombinerPrivate; /** * A widget that implements the QuickOpenEmbeddedWidgetInterface by asking its direct children. * * I.e. add widgets into the combiner's layout. If the widgets support the QuickOpenEmbeddedWidgetInterface, * then they will be used to implement the keyboard navigation features. */ class KDEVPLATFORMLANGUAGE_EXPORT QuickOpenEmbeddedWidgetCombiner : public QWidget, public QuickOpenEmbeddedWidgetInterface { + Q_OBJECT + public: explicit QuickOpenEmbeddedWidgetCombiner(QWidget* parent = nullptr); ~QuickOpenEmbeddedWidgetCombiner() override; bool next() override; bool previous() override; bool up() override; bool down() override; void back() override; void accept() override; void resetNavigationState() override; private: const QScopedPointer d_ptr; Q_DECLARE_PRIVATE(QuickOpenEmbeddedWidgetCombiner) }; } #endif // KDEVPLATFORM_QUICKOPENEMBEDDEDWIDGETCOMBINER_H diff --git a/plugins/clang/duchain/headerguardassistant.cpp b/plugins/clang/duchain/headerguardassistant.cpp index 61831b9f22..911529faf1 100644 --- a/plugins/clang/duchain/headerguardassistant.cpp +++ b/plugins/clang/duchain/headerguardassistant.cpp @@ -1,128 +1,132 @@ /* * Copyright 2018, 2019 Amish K. Naidu * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * */ #include "headerguardassistant.h" #include "util/clangutils.h" #include "util/clangtypes.h" #include #include #include #include #include #include enum class GuardType { Pragma, Macro }; class AddHeaderGuardAction : public KDevelop::IAssistantAction { + Q_OBJECT + public: AddHeaderGuardAction(const GuardType type, const int startLine, const KDevelop::IndexedString& path) : m_type(type) , m_startLine(startLine) , m_path(path) { } virtual ~AddHeaderGuardAction() override = default; QString description() const override { switch (m_type) { case GuardType::Pragma: return i18n("Add #pragma once"); case GuardType::Macro: return i18n("Add macro-based #ifndef/#define/#endif header guard"); } return {}; } void execute() override { KDevelop::DocumentChangeSet changes; switch (m_type) { case GuardType::Pragma: { KDevelop::DocumentChange change(m_path, KTextEditor::Range(m_startLine, 0, m_startLine, 0), QString(), QStringLiteral("#pragma once\n\n")); changes.addChange(change); break; } case GuardType::Macro: { const QString macro = m_path.toUrl() .fileName(QUrl::PrettyDecoded) .replace(QRegularExpression(QStringLiteral("[^a-zA-Z0-9]")), QLatin1String(" ")) .simplified() .toUpper() .replace(QLatin1Char(' '), QLatin1Char('_')) .append(QLatin1String("_INCLUDED")); const auto representation = KDevelop::createCodeRepresentation(m_path); const auto lastLine = representation->lines() - 1; const auto lastColumn = representation->line(lastLine).length(); // Add the #endif change before so that it applies correctly in case lastLine == m_startline changes.addChange(KDevelop::DocumentChange(m_path, KTextEditor::Range(lastLine, lastColumn, lastLine, lastColumn), QString(), QStringLiteral("\n#endif // %1").arg(macro))); changes.addChange(KDevelop::DocumentChange(m_path, KTextEditor::Range(m_startLine, 0, m_startLine, 0), QString(), QStringLiteral("#ifndef %1\n#define %1\n\n").arg(macro))); break; } } KDevelop::DUChainReadLocker lock; changes.setReplacementPolicy(KDevelop::DocumentChangeSet::WarnOnFailedChange); changes.applyAllChanges(); emit executed(this); } private: const GuardType m_type; const int m_startLine; const KDevelop::IndexedString m_path; }; HeaderGuardAssistant::HeaderGuardAssistant(const CXTranslationUnit unit, const CXFile file) : m_line(std::max(ClangUtils::skipTopCommentBlock(unit, file), 1u) - 1) // skip license etc , m_path(QDir(ClangString(clang_getFileName(file)).toString()).canonicalPath()) { } QString HeaderGuardAssistant::title() const { return QStringLiteral("Fix-Header"); } void HeaderGuardAssistant::createActions() { addAction(KDevelop::IAssistantAction::Ptr{new AddHeaderGuardAction(GuardType::Pragma, m_line, m_path)}); addAction(KDevelop::IAssistantAction::Ptr{new AddHeaderGuardAction(GuardType::Macro, m_line, m_path)}); } + +#include "headerguardassistant.moc" diff --git a/plugins/clang/duchain/headerguardassistant.h b/plugins/clang/duchain/headerguardassistant.h index a62d2f83c7..904aff2407 100644 --- a/plugins/clang/duchain/headerguardassistant.h +++ b/plugins/clang/duchain/headerguardassistant.h @@ -1,45 +1,47 @@ /* * Copyright 2018 Amish K. Naidu * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * */ #ifndef HEADERGUARDASSISTANT_H #define HEADERGUARDASSISTANT_H #include #include #include class HeaderGuardAssistant : public KDevelop::IAssistant { + Q_OBJECT + public: HeaderGuardAssistant(const CXTranslationUnit unit, const CXFile file); virtual ~HeaderGuardAssistant() override = default; QString title() const override; void createActions() override; private: const int m_line; const KDevelop::IndexedString m_path; }; #endif // HEADERGUARDASSISTANT_H diff --git a/plugins/clangtidy/tests/test_clangtidyjob.cpp b/plugins/clangtidy/tests/test_clangtidyjob.cpp index 7213a7266a..b9e325ea18 100644 --- a/plugins/clangtidy/tests/test_clangtidyjob.cpp +++ b/plugins/clangtidy/tests/test_clangtidyjob.cpp @@ -1,103 +1,107 @@ /* * This file is part of KDevelop * * Copyright 2016 Carlos Nihelton * * 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 "test_clangtidyjob.h" // SUT #include "job.h" // KDevPlatform #include #include #include // Qt #include #include using namespace KDevelop; using namespace ClangTidy; class JobTester : public Job { + Q_OBJECT + public: JobTester(Job::Parameters params) : Job(params) { } using Job::processStdoutLines; using Job::processStderrLines; using Job::childProcessExited; QString standardOutput() const { return m_standardOutput.join('\n'); } }; void TestClangTidyJob::initTestCase() { AutoTestShell::init({ "kdevclangtidy" }); TestCore::initialize(Core::NoUi); } void TestClangTidyJob::cleanupTestCase() { TestCore::shutdown(); } void TestClangTidyJob::testJob() { QFile output_example_file("data/output_example"); output_example_file.open(QIODevice::ReadOnly); QTextStream ios(&output_example_file); QStringList stdoutOutput; QString line; while (ios.readLineInto(&line)) { stdoutOutput << line; } QVERIFY(!stdoutOutput.isEmpty()); Job::Parameters jobParams; JobTester jobTester(jobParams); qRegisterMetaType>(); QSignalSpy problemsSpy(&jobTester, &JobTester::problemsDetected); jobTester.processStdoutLines(stdoutOutput); QCOMPARE(jobTester.standardOutput(), stdoutOutput.join('\n')); jobTester.childProcessExited(0, QProcess::NormalExit); QCOMPARE(problemsSpy.count(), 1); const auto problems = qvariant_cast>(problemsSpy.at(0).at(0)); QVERIFY(problems[0]->finalLocation().document.str().contains(QStringLiteral("/kdev-clang-tidy/src/plugin.cpp"))); QVERIFY( problems[0]->explanation().startsWith(QStringLiteral("[cppcoreguidelines-pro-bounds-array-to-pointer-decay]"))); QVERIFY(problems[1]->finalLocation().document.str().contains(QStringLiteral("/kdev-clang-tidy/src/plugin.cpp"))); QVERIFY( problems[1]->explanation().startsWith(QStringLiteral("[cppcoreguidelines-pro-bounds-array-to-pointer-decay]"))); QVERIFY(problems[2]->finalLocation().document.str().contains(QStringLiteral("/kdev-clang-tidy/src/plugin.cpp"))); QVERIFY( problems[2]->explanation().startsWith(QStringLiteral("[cppcoreguidelines-pro-bounds-array-to-pointer-decay]"))); } QTEST_GUILESS_MAIN(TestClangTidyJob) + +#include "test_clangtidyjob.moc" diff --git a/plugins/cmake/cmakedoc.h b/plugins/cmake/cmakedoc.h index 90287b13a6..b3f852d6e1 100644 --- a/plugins/cmake/cmakedoc.h +++ b/plugins/cmake/cmakedoc.h @@ -1,44 +1,46 @@ /* KDevelop CMake Support * * Copyright 2009 Aleix Pol * * 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 CMAKEHELPDOC_H #define CMAKEHELPDOC_H #include class CMakeDocumentation; class CMakeDoc : public KDevelop::IDocumentation { + Q_OBJECT + public: CMakeDoc(const QString& name, const QString& desc) : mName(name), mDesc(desc) {} QString description() const override { return mDesc; } QString name() const override { return mName; } KDevelop::IDocumentationProvider* provider() const override; QWidget* documentationWidget(KDevelop::DocumentationFindWidget* findWidget, QWidget* parent = nullptr) override; static CMakeDocumentation* s_provider; private: QString mName, mDesc; }; #endif diff --git a/plugins/cmake/cmakedocumentation.cpp b/plugins/cmake/cmakedocumentation.cpp index c46976bb2b..6471178ad2 100644 --- a/plugins/cmake/cmakedocumentation.cpp +++ b/plugins/cmake/cmakedocumentation.cpp @@ -1,147 +1,148 @@ /* KDevelop CMake Support * * Copyright 2009 Aleix Pol * * 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 "cmakedocumentation.h" #include "cmakeutils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cmakemanager.h" #include "cmakehelpdocumentation.h" #include "cmakebuilderconfig.h" #include "cmakedoc.h" #include "cmakecommandscontents.h" #include "debug.h" K_PLUGIN_FACTORY_WITH_JSON(CMakeSupportDocFactory, "kdevcmakedocumentation.json", registerPlugin(); ) CMakeDocumentation* CMakeDoc::s_provider=nullptr; KDevelop::IDocumentationProvider* CMakeDoc::provider() const { return s_provider; } CMakeDocumentation::CMakeDocumentation(QObject* parent, const QVariantList&) : KDevelop::IPlugin( QStringLiteral("kdevcmakedocumentation"), parent ) , m_index(new CMakeCommandsContents(this)) , m_flatIndex(new KDescendantsProxyModel(m_index)) { m_flatIndex->setSourceModel(m_index); if (CMakeBuilderSettings::self()->cmakeExecutable().isEmpty()) { setErrorDescription(i18n("Unable to find a CMake executable. Is one installed on the system?")); return; } CMakeDoc::s_provider=this; } QVector CMakeDocumentation::names(CMakeDocumentation::Type t) const { return m_index->names(t); } QString CMakeDocumentation::descriptionForIdentifier(const QString& id, Type t) const { return m_index->descriptionForIdentifier(id, t); } KDevelop::IDocumentation::Ptr CMakeDocumentation::description(const QString& identifier, const QUrl &file) const { if (!file.isEmpty() && !QMimeDatabase().mimeTypeForUrl(file).inherits(QStringLiteral("text/x-cmake"))) { return KDevelop::IDocumentation::Ptr(); } Type t = m_index->typeFor(identifier); QString desc=descriptionForIdentifier(identifier, t); KDevelop::IProject* p=KDevelop::ICore::self()->projectController()->findProjectForUrl(file); ICMakeManager* m=nullptr; if(p) m=p->managerPlugin()->extension(); if(m) { QPair entry = m->cacheValue(p, identifier); if(!entry.first.isEmpty()) desc += i18n("
Cache Value: %1\n", entry.first); if(!entry.second.isEmpty()) desc += i18n("
Cache Documentation: %1\n", entry.second); } if(desc.isEmpty()) return KDevelop::IDocumentation::Ptr(); else return KDevelop::IDocumentation::Ptr(new CMakeDoc(identifier, desc)); } KDevelop::IDocumentation::Ptr CMakeDocumentation::documentationForDeclaration(KDevelop::Declaration* decl) const { return description(decl->identifier().toString(), decl->url().toUrl()); } KDevelop::IDocumentation::Ptr CMakeDocumentation::documentationForIndex(const QModelIndex& idx) const { return description(idx.data().toString(), QUrl()); } QAbstractItemModel* CMakeDocumentation::indexModel() const { return m_flatIndex; } QIcon CMakeDocumentation::icon() const { return QIcon::fromTheme(QStringLiteral("cmake")); } QString CMakeDocumentation::name() const { return QStringLiteral("CMake"); } KDevelop::IDocumentation::Ptr CMakeDocumentation::homePage() const { return KDevelop::IDocumentation::Ptr(new CMakeHomeDocumentation); } //////////CMakeDoc QWidget* CMakeDoc::documentationWidget(KDevelop::DocumentationFindWidget* findWidget, QWidget* parent) { auto* view = new KDevelop::StandardDocumentationView(findWidget, parent); view->initZoom(provider()->name()); view->setHtml(mDesc); return view; } #include "cmakedocumentation.moc" +#include "moc_cmakedoc.cpp" diff --git a/plugins/cppcheck/tests/test_cppcheckjob.cpp b/plugins/cppcheck/tests/test_cppcheckjob.cpp index 5b50b6b321..8d8f3644c8 100644 --- a/plugins/cppcheck/tests/test_cppcheckjob.cpp +++ b/plugins/cppcheck/tests/test_cppcheckjob.cpp @@ -1,91 +1,95 @@ /************************************************************************************* * Copyright 2016 (C) Anton Anikin * * * * 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 "test_cppcheckjob.h" #include #include #include #include "job.h" #include "parameters.h" using namespace KDevelop; using namespace cppcheck; class JobTester : public Job { + Q_OBJECT + public: explicit JobTester(const Parameters& params) : Job(params) {} using Job::postProcessStdout; using Job::postProcessStderr; QString standardOutput() const { return m_standardOutput.join('\n'); } QString xmlOutput() const { return m_xmlOutput.join('\n'); } }; void TestCppcheckJob::initTestCase() { AutoTestShell::init({"kdevcppcheck"}); TestCore::initialize(Core::NoUi); } void TestCppcheckJob::cleanupTestCase() { TestCore::shutdown(); } void TestCppcheckJob::testJob() { QStringList stdoutOutput = { "Checking source1.cpp...", "1/2 files checked 50% done", "Checking source2.cpp...", "2/2 files checked 50% done" }; QStringList stderrOutput = { "(information) Couldn't find path given by -I '/missing_include_dir_1/'", "(information) Couldn't find path given by -I '/missing_include_dir_2/'", "", "", " ", " ", " ", " ", "" }; Parameters jobParams; JobTester jobTester(jobParams); jobTester.postProcessStderr(stderrOutput); jobTester.postProcessStdout(stdoutOutput); // move non-XML elements from stderrOutput stdoutOutput.push_front(stderrOutput[1]); stdoutOutput.push_front(stderrOutput[0]); stderrOutput.pop_front(); stderrOutput.pop_front(); QCOMPARE(jobTester.standardOutput(), stdoutOutput.join('\n')); QCOMPARE(jobTester.xmlOutput(), stderrOutput.join('\n')); } QTEST_GUILESS_MAIN(TestCppcheckJob) + +#include "test_cppcheckjob.moc" diff --git a/plugins/debuggercommon/tests/debuggees/debugeethreads.cpp b/plugins/debuggercommon/tests/debuggees/debugeethreads.cpp index f3a327a731..cfdca086d9 100644 --- a/plugins/debuggercommon/tests/debuggees/debugeethreads.cpp +++ b/plugins/debuggercommon/tests/debuggees/debugeethreads.cpp @@ -1,44 +1,48 @@ /* Copyright 2009 Niko Sams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #ifndef _MSC_VER #include #endif class TestThread : public QThread { + Q_OBJECT + public: void run() override { sleep(1); std::cout << "Hello, world!" << std::endl; } }; int main(int /*argc*/, char** /*argv*/) { TestThread t1; TestThread t2; TestThread t3; t1.start(); t2.start(); t3.start(); QThread::usleep(500000); QThread::usleep(600000); return 0; } + +#include "debugeethreads.moc" diff --git a/plugins/gdb/unittests/test_gdb.cpp b/plugins/gdb/unittests/test_gdb.cpp index e2052fbb04..6f5173fc41 100644 --- a/plugins/gdb/unittests/test_gdb.cpp +++ b/plugins/gdb/unittests/test_gdb.cpp @@ -1,2120 +1,2122 @@ /* Copyright 2009 Niko Sams Copyright 2013 Vlas Puhov This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "test_gdb.h" #include "debugsession.h" #include "gdbframestackmodel.h" #include "mi/micommand.h" #include "mi/milexer.h" #include "mi/miparser.h" #include "tests/debuggers-tests-config.h" #include "tests/testhelper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SKIP_IF_ATTACH_FORBIDDEN() \ do { \ if (KDevMI::isAttachForbidden(__FILE__, __LINE__)) \ return; \ } while(0) using KDevelop::AutoTestShell; using KDevMI::findExecutable; using KDevMI::findSourceFile; using KDevMI::findFile; namespace KDevMI { namespace GDB { void GdbTest::initTestCase() { AutoTestShell::init(); KDevelop::TestCore::initialize(KDevelop::Core::NoUi); m_iface = KDevelop::ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IExecutePlugin"), QStringLiteral("kdevexecute"))->extension(); Q_ASSERT(m_iface); } void GdbTest::cleanupTestCase() { KDevelop::TestCore::shutdown(); } void GdbTest::init() { //remove all breakpoints - so we can set our own in the test KConfigGroup breakpoints = KSharedConfig::openConfig()->group("breakpoints"); breakpoints.writeEntry("number", 0); breakpoints.sync(); 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(); } class WritableEnvironmentProfileList : public KDevelop::EnvironmentProfileList { public: explicit WritableEnvironmentProfileList(KConfig* config) : EnvironmentProfileList(config) {} using EnvironmentProfileList::variables; using EnvironmentProfileList::saveSettings; using EnvironmentProfileList::removeProfile; }; class TestLaunchConfiguration : public KDevelop::ILaunchConfiguration { public: explicit TestLaunchConfiguration(const QUrl& executable = findExecutable(QStringLiteral("debuggee_debugee")), const QUrl& workingDirectory = QUrl()) { qDebug() << "FIND" << executable; c = KSharedConfig::openConfig(); c->deleteGroup("launch"); cfg = c->group("launch"); cfg.writeEntry("isExecutable", true); cfg.writeEntry("Executable", executable); cfg.writeEntry("Working Directory", workingDirectory); } const KConfigGroup config() const override { return cfg; } KConfigGroup config() override { return cfg; }; QString name() const override { return QStringLiteral("Test-Launch"); } KDevelop::IProject* project() const override { return nullptr; } KDevelop::LaunchConfigurationType* type() const override { return nullptr; } KConfig* rootConfig() { return c.data(); } private: KConfigGroup cfg; KSharedConfigPtr c; }; class TestFrameStackModel : public GdbFrameStackModel { + Q_OBJECT + public: explicit TestFrameStackModel(DebugSession* session) : GdbFrameStackModel(session), fetchFramesCalled(0), fetchThreadsCalled(0) {} int fetchFramesCalled; int fetchThreadsCalled; void fetchFrames(int threadNumber, int from, int to) override { fetchFramesCalled++; GdbFrameStackModel::fetchFrames(threadNumber, from, to); } void fetchThreads() override { fetchThreadsCalled++; GdbFrameStackModel::fetchThreads(); } }; class TestDebugSession : public DebugSession { Q_OBJECT public: TestDebugSession() : DebugSession() { setSourceInitFile(false); setAutoDisableASLR(false); m_frameStackModel = new TestFrameStackModel(this); KDevelop::ICore::self()->debugController()->addSession(this); } QUrl url() { return currentUrl(); } int line() { return currentLine(); } TestFrameStackModel* frameStackModel() const override { return m_frameStackModel; } private: TestFrameStackModel* m_frameStackModel; }; class TestWaiter { public: TestWaiter(DebugSession * session_, const char * condition_, const char * file_, int line_) : session(session_) , condition(condition_) , file(file_) , line(line_) { stopWatch.start(); } bool waitUnless(bool ok) { if (ok) { qDebug() << "Condition " << condition << " reached in " << file << ':' << line; return false; } if (stopWatch.elapsed() > 5000) { QTest::qFail(qPrintable(QString("Timeout before reaching condition %0").arg(condition)), file, line); return false; } QTest::qWait(100); if (!session) { QTest::qFail(qPrintable(QString("Session ended without reaching condition %0").arg(condition)), file, line); return false; } return true; } private: QTime stopWatch; QPointer session; const char * condition; const char * file; int line; }; #define WAIT_FOR_STATE(session, state) \ do { if (!waitForState((session), (state), __FILE__, __LINE__)) return; } while (0) #define WAIT_FOR_STATE_AND_IDLE(session, state) \ do { if (!waitForState((session), (state), __FILE__, __LINE__, true)) return; } while (0) #define WAIT_FOR(session, condition) \ do { \ TestWaiter w((session), #condition, __FILE__, __LINE__); \ while (w.waitUnless((condition))) /* nothing */ ; \ } while(0) #define COMPARE_DATA(index, expected) \ do { if(!compareData((index), (expected), __FILE__, __LINE__)) return; } while (0) bool compareData(const QModelIndex& index, const QString& expected, const char *file, int line) { QString s = index.model()->data(index, Qt::DisplayRole).toString(); if (s != expected) { QTest::qFail(qPrintable(QString("'%0' didn't match expected '%1' in %2:%3") .arg(s, expected, file).arg(line)), file, line); return false; } return true; } static const QString debugeeFileName = findSourceFile(QStringLiteral("debugee.cpp")); KDevelop::BreakpointModel* breakpoints() { return KDevelop::ICore::self()->debugController()->breakpointModel(); } void GdbTest::testStdOut() { auto *session = new TestDebugSession; QSignalSpy outputSpy(session, &TestDebugSession::inferiorStdoutLines); TestLaunchConfiguration cfg; session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, KDevelop::IDebugSession::EndedState); { QCOMPARE(outputSpy.count(), 1); QList arguments = outputSpy.takeFirst(); QCOMPARE(arguments.count(), 1); QCOMPARE(arguments.first().toStringList(), QStringList() << "Hello, world!" << "Hello"); } } void GdbTest::testEnvironmentSet() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeeechoenv"))); cfg.config().writeEntry("EnvironmentGroup", "GdbTestGroup"); WritableEnvironmentProfileList envProfiles(cfg.rootConfig()); envProfiles.removeProfile(QStringLiteral("GdbTestGroup")); auto &envs = envProfiles.variables(QStringLiteral("GdbTestGroup")); envs[QStringLiteral("VariableA")] = QStringLiteral("-A' \" complex --value"); envs[QStringLiteral("VariableB")] = QStringLiteral("-B' \" complex --value"); envProfiles.saveSettings(cfg.rootConfig()); QSignalSpy outputSpy(session, &TestDebugSession::inferiorStdoutLines); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, KDevelop::IDebugSession::EndedState); QVERIFY(outputSpy.count() > 0); QStringList outputLines; while (outputSpy.count() > 0) { QList arguments = outputSpy.takeFirst(); for (const auto &item : arguments) { outputLines.append(item.toStringList()); } } QCOMPARE(outputLines, QStringList() << "-A' \" complex --value" << "-B' \" complex --value"); } void GdbTest::testBreakpoint() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); } void GdbTest::testDisableBreakpoint() { //Description: We must stop only on the third breakpoint int firstBreakLine=28; int secondBreakLine=23; int thirdBreakLine=24; int fourthBreakLine=31; auto *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint *b; b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), firstBreakLine); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //this is needed to emulate debug from GUI. If we are in edit mode, the debugSession doesn't exist. KDevelop::ICore::self()->debugController()->breakpointModel()->blockSignals(true); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), secondBreakLine); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //all disabled breakpoints were added KDevelop::Breakpoint * thirdBreak = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), thirdBreakLine); KDevelop::ICore::self()->debugController()->breakpointModel()->blockSignals(false); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), thirdBreak->line()); //disable existing breakpoint thirdBreak->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //add another disabled breakpoint b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), fourthBreakLine); QTest::qWait(300); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); QTest::qWait(300); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testChangeLocationBreakpoint() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 27); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 27); QTest::qWait(100); b->setLine(28); QTest::qWait(100); session->run(); QTest::qWait(100); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 28); QTest::qWait(500); breakpoints()->setData(breakpoints()->index(0, KDevelop::Breakpoint::LocationColumn), QString(debugeeFileName+":30")); QCOMPARE(b->line(), 29); QTest::qWait(100); QCOMPARE(b->line(), 29); session->run(); QTest::qWait(100); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testDeleteBreakpoint() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; QCOMPARE(breakpoints()->rowCount(), 0); //add breakpoint before startDebugging breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 21); QCOMPARE(breakpoints()->rowCount(), 1); breakpoints()->removeRow(0); QCOMPARE(breakpoints()->rowCount(), 0); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 22); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testPendingBreakpoint() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile(QStringLiteral("debugeeqt.cpp"))), 10); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::PendingState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testUpdateBreakpoint() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; // breakpoint 1: real line 29: foo(); KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); QCOMPARE(breakpoints()->rowCount(), 1); session->startDebugging(&cfg, m_iface); // breakpoint 2: real line 32: const char *x = "Hello"; //insert custom command as user might do it using GDB console session->addCommand(new MI::UserCommand(MI::NonMI, "break "+debugeeFileName+":32")); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // stop at breakpoint 1, with custom command handled QCOMPARE(session->currentLine(), 28); // check breakpoint 2 got picked up QCOMPARE(breakpoints()->rowCount(), 2); b = breakpoints()->breakpoint(1); QCOMPARE(b->url(), QUrl::fromLocalFile(debugeeFileName)); QCOMPARE(b->line(), 31); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); // stop at breakpoint 2 QCOMPARE(session->currentLine(), 31); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testIgnoreHitsBreakpoint() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint * b1 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 21); b1->setIgnoreHits(1); KDevelop::Breakpoint * b2 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 22); session->startDebugging(&cfg, m_iface); //WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR(session, session->state() == DebugSession::PausedState && b2->hitCount() == 1); b2->setIgnoreHits(1); session->run(); //WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR(session, session->state() == DebugSession::PausedState && b1->hitCount() == 1); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testConditionBreakpoint() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 39); b->setCondition(QStringLiteral("x[0] == 'H'")); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 23); b->setCondition(QStringLiteral("i==2")); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startDebugging(&cfg, m_iface); WAIT_FOR(session, session->state() == DebugSession::PausedState && session->line() == 24); b->setCondition(QStringLiteral("i == 0")); QTest::qWait(100); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 39); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakOnWriteBreakpoint() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); breakpoints()->addWatchpoint(QStringLiteral("i")); QTest::qWait(100); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 22); // line 23: ++i; int j = i; session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakOnWriteWithConditionBreakpoint() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); KDevelop::Breakpoint *b = breakpoints()->addWatchpoint(QStringLiteral("i")); b->setCondition(QStringLiteral("i==2")); QTest::qWait(100); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 22); // line 23: ++i; int j = i; session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakOnReadBreakpoint() { /* test disabled because of gdb bug: http://sourceware.org/bugzilla/show_bug.cgi?id=10136 TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint *b = breakpoints()->addReadWatchpoint("foo::i"); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); */ } void GdbTest::testBreakOnReadBreakpoint2() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); breakpoints()->addReadWatchpoint(QStringLiteral("i")); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 22); // ++i session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 22); // int j = i session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); if(session->line() == 22) { // some GDB versions break 3 times on this line session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); } QCOMPARE(session->line(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakOnAccessBreakpoint() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); breakpoints()->addAccessWatchpoint(QStringLiteral("i")); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 22); // line 23: ++i (read) session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 22); // line 23: ++i (write) session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 22); // line 23: int j = i (read) session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testInsertBreakpointWhileRunning() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeeslow"))); QString fileName = findSourceFile(QStringLiteral("debugeeslow.cpp")); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::ActiveState); QTest::qWait(2000); qDebug() << "adding breakpoint"; KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 30); // ++i; QTest::qWait(500); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); QCOMPARE(session->line(), 30); // ++i; b->setDeleted(); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testInsertBreakpointWhileRunningMultiple() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeeslow"))); QString fileName = findSourceFile(QStringLiteral("debugeeslow.cpp")); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::ActiveState); QTest::qWait(2000); qDebug() << "adding breakpoint"; KDevelop::Breakpoint *b1 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 29); // static int i=0; KDevelop::Breakpoint *b2 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 30); // ++i; QTest::qWait(500); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); QCOMPARE(session->line(), 29); // static int i=0; session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); QCOMPARE(session->line(), 30); // ++i; b1->setDeleted(); b2->setDeleted(); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testInsertBreakpointFunctionName() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QStringLiteral("main")); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 27); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testManualBreakpoint() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QStringLiteral("main")); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 27); breakpoints()->removeRows(0, 1); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->rowCount(), 0); session->addCommand(MI::NonMI, QStringLiteral("break debugee.cpp:23")); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->rowCount(), 1); KDevelop::Breakpoint* b = breakpoints()->breakpoint(0); QCOMPARE(b->line(), 22); session->addCommand(MI::NonMI, QStringLiteral("disable 2")); session->addCommand(MI::NonMI, QStringLiteral("condition 2 i == 1")); session->addCommand(MI::NonMI, QStringLiteral("ignore 2 1")); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(b->enabled(), false); QCOMPARE(b->condition(), QString("i == 1")); QCOMPARE(b->ignoreHits(), 1); session->addCommand(MI::NonMI, QStringLiteral("delete 2")); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->rowCount(), 0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testShowStepInSource() { auto *session = new TestDebugSession; QSignalSpy showStepInSourceSpy(session, &TestDebugSession::showStepInSource); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 29); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); { QCOMPARE(showStepInSourceSpy.count(), 3); QList arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().toUrl(), QUrl::fromLocalFile(debugeeFileName)); QCOMPARE(arguments.at(1).toInt(), 29); arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().toUrl(), QUrl::fromLocalFile(debugeeFileName)); QCOMPARE(arguments.at(1).toInt(), 22); arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().toUrl(), QUrl::fromLocalFile(debugeeFileName)); QCOMPARE(arguments.at(1).toInt(), 23); } } void GdbTest::testStack() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 21); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo"); QCOMPARE(stackModel->rowCount(tIdx), 2); QCOMPARE(stackModel->columnCount(tIdx), 3); COMPARE_DATA(stackModel->index(0, 0, tIdx), "0"); COMPARE_DATA(stackModel->index(0, 1, tIdx), "foo"); COMPARE_DATA(stackModel->index(0, 2, tIdx), debugeeFileName+":23"); COMPARE_DATA(stackModel->index(1, 0, tIdx), "1"); COMPARE_DATA(stackModel->index(1, 1, tIdx), "main"); COMPARE_DATA(stackModel->index(1, 2, tIdx), debugeeFileName+":29"); session->stepOut(); WAIT_FOR_STATE(session, DebugSession::PausedState); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 1); COMPARE_DATA(stackModel->index(0, 0, tIdx), "0"); COMPARE_DATA(stackModel->index(0, 1, tIdx), "main"); COMPARE_DATA(stackModel->index(0, 2, tIdx), debugeeFileName+":30"); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testStackFetchMore() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeerecursion"))); QString fileName = findSourceFile(QStringLiteral("debugeerecursion.cpp")); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->frameStackModel()->fetchFramesCalled, 1); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo"); QCOMPARE(stackModel->rowCount(tIdx), 21); COMPARE_DATA(stackModel->index(0, 0, tIdx), "0"); COMPARE_DATA(stackModel->index(0, 1, tIdx), "foo"); COMPARE_DATA(stackModel->index(0, 2, tIdx), fileName+":26"); COMPARE_DATA(stackModel->index(1, 0, tIdx), "1"); COMPARE_DATA(stackModel->index(1, 1, tIdx), "foo"); COMPARE_DATA(stackModel->index(1, 2, tIdx), fileName+":24"); COMPARE_DATA(stackModel->index(2, 0, tIdx), "2"); COMPARE_DATA(stackModel->index(2, 1, tIdx), "foo"); COMPARE_DATA(stackModel->index(2, 2, tIdx), fileName+":24"); COMPARE_DATA(stackModel->index(19, 0, tIdx), "19"); COMPARE_DATA(stackModel->index(20, 0, tIdx), "20"); stackModel->fetchMoreFrames(); QTest::qWait(200); QCOMPARE(stackModel->fetchFramesCalled, 2); QCOMPARE(stackModel->rowCount(tIdx), 41); COMPARE_DATA(stackModel->index(20, 0, tIdx), "20"); COMPARE_DATA(stackModel->index(21, 0, tIdx), "21"); COMPARE_DATA(stackModel->index(22, 0, tIdx), "22"); COMPARE_DATA(stackModel->index(39, 0, tIdx), "39"); COMPARE_DATA(stackModel->index(40, 0, tIdx), "40"); stackModel->fetchMoreFrames(); QTest::qWait(200); QCOMPARE(stackModel->fetchFramesCalled, 3); QCOMPARE(stackModel->rowCount(tIdx), 121); COMPARE_DATA(stackModel->index(40, 0, tIdx), "40"); COMPARE_DATA(stackModel->index(41, 0, tIdx), "41"); COMPARE_DATA(stackModel->index(42, 0, tIdx), "42"); COMPARE_DATA(stackModel->index(119, 0, tIdx), "119"); COMPARE_DATA(stackModel->index(120, 0, tIdx), "120"); stackModel->fetchMoreFrames(); QTest::qWait(200); QCOMPARE(stackModel->fetchFramesCalled, 4); QCOMPARE(stackModel->rowCount(tIdx), 299); COMPARE_DATA(stackModel->index(120, 0, tIdx), "120"); COMPARE_DATA(stackModel->index(121, 0, tIdx), "121"); COMPARE_DATA(stackModel->index(122, 0, tIdx), "122"); COMPARE_DATA(stackModel->index(298, 0, tIdx), "298"); COMPARE_DATA(stackModel->index(298, 1, tIdx), "main"); COMPARE_DATA(stackModel->index(298, 2, tIdx), fileName+":30"); stackModel->fetchMoreFrames(); //nothing to fetch, we are at the end QTest::qWait(200); QCOMPARE(stackModel->fetchFramesCalled, 4); QCOMPARE(stackModel->rowCount(tIdx), 299); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testStackDeactivateAndActive() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 21); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QModelIndex tIdx = stackModel->index(0,0); session->stepOut(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(200); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 1); COMPARE_DATA(stackModel->index(0, 0, tIdx), "0"); COMPARE_DATA(stackModel->index(0, 1, tIdx), "main"); COMPARE_DATA(stackModel->index(0, 2, tIdx), debugeeFileName+":30"); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testStackSwitchThread() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeethreads"))); QString fileName = findSourceFile(QStringLiteral("debugeethreads.cpp")); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 40); // t3.start(); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QVERIFY(stackModel->rowCount() > 2); QModelIndex tIdx = stackModel->index(0,0); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 1); COMPARE_DATA(stackModel->index(0, 0, tIdx), "0"); COMPARE_DATA(stackModel->index(0, 1, tIdx), "main"); COMPARE_DATA(stackModel->index(0, 2, tIdx), fileName+":41"); // QThread::usleep(500000); tIdx = stackModel->index(1,0); QVERIFY(stackModel->data(tIdx).toString().startsWith("#2 at ")); stackModel->setCurrentThread(2); QTest::qWait(200); int rows = stackModel->rowCount(tIdx); QVERIFY(rows > 3); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testAttach() { SKIP_IF_ATTACH_FORBIDDEN(); #ifdef Q_OS_FREEBSD // Despite successful attach GDB MI spits out a error message "Can't allocate registers". This gets caught by KDevMI layer and gets interpreted as error. // Upstream PR: https://sourceware.org/bugzilla/show_bug.cgi?id=23464 QSKIP("GDB on FreeBSD produces an unexpected error message, on which KDevelop chokes"); #endif QString fileName = findSourceFile(QStringLiteral("debugeeslow.cpp")); KProcess debugeeProcess; debugeeProcess << QStringLiteral("nice") << findExecutable(QStringLiteral("debuggee_debugeeslow")).toLocalFile(); debugeeProcess.start(); QVERIFY(debugeeProcess.waitForStarted()); QTest::qWait(100); auto *session = new TestDebugSession; session->attachToProcess(debugeeProcess.pid()); WAIT_FOR_STATE(session, DebugSession::PausedState); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 39); // } after foo(); QTest::qWait(100); session->run(); QTest::qWait(2000); WAIT_FOR_STATE(session, DebugSession::PausedState); if (session->line() < 39 || session->line() < 40) { QCOMPARE(session->line(), 39); } session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testManualAttach() { SKIP_IF_ATTACH_FORBIDDEN(); #ifdef Q_OS_FREEBSD // Despite successful attach GDB MI spits out a error message "Can't allocate registers". This gets caught by KDevMI layer and gets interpreted as error. // Upstream PR: https://sourceware.org/bugzilla/show_bug.cgi?id=23464 QSKIP("GDB on FreeBSD produces an unexpected error message, on which KDevelop chokes"); #endif KProcess debugeeProcess; debugeeProcess << QStringLiteral("nice") << findExecutable(QStringLiteral("debuggee_debugeeslow")).toLocalFile(); debugeeProcess.start(); QVERIFY(debugeeProcess.waitForStarted()); auto *session = new TestDebugSession; TestLaunchConfiguration cfg; cfg.config().writeEntry(Config::RemoteGdbRunEntry, QUrl::fromLocalFile(findFile(GDB_SRC_DIR, QStringLiteral("unittests/gdb_script_empty")))); QVERIFY(session->startDebugging(&cfg, m_iface)); session->addCommand(MI::NonMI, QStringLiteral("attach %0").arg(debugeeProcess.pid())); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); QTest::qWait(2000); // give the slow inferior some extra time to run WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testCoreFile() { QFileInfo f(QStringLiteral("core")); f.setCaching(false); // don't cache information if (f.exists()) { QVERIFY(QFile::remove(f.canonicalFilePath())); } KProcess debugeeProcess; debugeeProcess.setOutputChannelMode(KProcess::MergedChannels); debugeeProcess << QStringLiteral("bash") << QStringLiteral("-c") << "ulimit -c unlimited; " + findExecutable(QStringLiteral("debuggee_crash")).toLocalFile(); debugeeProcess.start(); debugeeProcess.waitForFinished(); qDebug() << "Debuggee output:\n" << debugeeProcess.readAll(); bool coreFileFound = f.exists(); if (!coreFileFound) { // Try to use coredumpctl auto coredumpctl = QStandardPaths::findExecutable(QStringLiteral("coredumpctl")); if (!coredumpctl.isEmpty()) { KProcess::execute(coredumpctl, {"-1", "-o", f.absoluteFilePath(), "dump", "debuggee_crash"}, 5000); // coredumpctl seems to create an empty file "core" even if no cores can be delivered // (like when run inside docker containers as on KDE CI or with kernel.core_pattern=|/dev/null) // so also check for size != 0 coreFileFound = f.exists() && (f.size() > 0); } } if (!coreFileFound) QSKIP("no core dump found, check your system configuration (see /proc/sys/kernel/core_pattern).", SkipSingle); auto *session = new TestDebugSession; session->examineCoreFile(findExecutable(QStringLiteral("debuggee_crash")), QUrl::fromLocalFile(f.canonicalFilePath())); TestFrameStackModel *stackModel = session->frameStackModel(); WAIT_FOR_STATE(session, DebugSession::StoppedState); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo"); session->stopDebugger(); WAIT_FOR_STATE(session, DebugSession::EndedState); } KDevelop::VariableCollection *variableCollection() { return KDevelop::ICore::self()->debugController()->variableCollection(); } void GdbTest::testVariablesLocals() { auto *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 22); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); QCOMPARE(variableCollection()->rowCount(), 2); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 2); COMPARE_DATA(variableCollection()->index(0, 0, i), "i"); COMPARE_DATA(variableCollection()->index(0, 1, i), "0"); COMPARE_DATA(variableCollection()->index(1, 0, i), "j"); // COMPARE_DATA(variableCollection()->index(1, 1, i), "1"); // j is not initialized yet session->run(); QTest::qWait(1000); WAIT_FOR_STATE(session, DebugSession::PausedState); COMPARE_DATA(variableCollection()->index(0, 0, i), "i"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); COMPARE_DATA(variableCollection()->index(1, 0, i), "j"); COMPARE_DATA(variableCollection()->index(1, 1, i), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testVariablesLocalsStruct() { auto *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); QModelIndex i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); int structIndex = 0; for(int j=0; j<3; ++j) { if (variableCollection()->index(j, 0, i).data().toString() == QLatin1String("ts")) { structIndex = j; } } COMPARE_DATA(variableCollection()->index(structIndex, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(structIndex, 1, i), "{...}"); QModelIndex ts = variableCollection()->index(structIndex, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, ts), "..."); variableCollection()->expanded(ts); QTest::qWait(100); COMPARE_DATA(variableCollection()->index(0, 0, ts), "a"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "0"); COMPARE_DATA(variableCollection()->index(1, 0, ts), "b"); COMPARE_DATA(variableCollection()->index(1, 1, ts), "1"); COMPARE_DATA(variableCollection()->index(2, 0, ts), "c"); COMPARE_DATA(variableCollection()->index(2, 1, ts), "2"); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); COMPARE_DATA(variableCollection()->index(structIndex, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(structIndex, 1, i), "{...}"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testVariablesWatches() { auto *session = new TestDebugSession; KDevelop::ICore::self()->debugController()->variableCollection()->variableWidgetShown(); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add(QStringLiteral("ts")); QTest::qWait(300); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(0, 1, i), "{...}"); QModelIndex ts = variableCollection()->index(0, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, ts), "..."); variableCollection()->expanded(ts); QTest::qWait(100); COMPARE_DATA(variableCollection()->index(0, 0, ts), "a"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "0"); COMPARE_DATA(variableCollection()->index(1, 0, ts), "b"); COMPARE_DATA(variableCollection()->index(1, 1, ts), "1"); COMPARE_DATA(variableCollection()->index(2, 0, ts), "c"); COMPARE_DATA(variableCollection()->index(2, 1, ts), "2"); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(100); COMPARE_DATA(variableCollection()->index(0, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(0, 1, i), "{...}"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testVariablesWatchesQuotes() { auto *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); TestLaunchConfiguration cfg; // the unquoted string (the actual content): t\"t // quoted string (what we would write as a c string): "t\\\"t" // written in source file: R"("t\\\"t")" const QString testString(QStringLiteral("t\\\"t")); // the actual content const QString quotedTestString(QStringLiteral(R"("t\\\"t")")); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); variableCollection()->watches()->add(quotedTestString); //just a constant string QTest::qWait(300); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), quotedTestString); COMPARE_DATA(variableCollection()->index(0, 1, i), "[" + QString::number(testString.length() + 1) + "]"); QModelIndex testStr = variableCollection()->index(0, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, testStr), "..."); variableCollection()->expanded(testStr); QTest::qWait(100); int len = testString.length(); for (int ind = 0; ind < len; ind++) { COMPARE_DATA(variableCollection()->index(ind, 0, testStr), QString::number(ind)); QChar c = testString.at(ind); QString value = QString::number(c.toLatin1()) + " '"; if (c == '\\') value += QLatin1String("\\\\"); else if (c == '\'') value += QLatin1String("\\'"); else value += c; value += QLatin1String("'"); COMPARE_DATA(variableCollection()->index(ind, 1, testStr), value); } COMPARE_DATA(variableCollection()->index(len, 0, testStr), QString::number(len)); COMPARE_DATA(variableCollection()->index(len, 1, testStr), "0 '\\000'"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testVariablesWatchesTwoSessions() { auto *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add(QStringLiteral("ts")); QTest::qWait(300); QModelIndex ts = variableCollection()->index(0, 0, variableCollection()->index(0, 0)); variableCollection()->expanded(ts); QTest::qWait(100); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //check if variable is marked as out-of-scope QCOMPARE(variableCollection()->watches()->childCount(), 1); auto* v = qobject_cast(variableCollection()->watches()->child(0)); QVERIFY(v); QVERIFY(!v->inScope()); QCOMPARE(v->childCount(), 3); v = qobject_cast(v->child(0)); QVERIFY(!v->inScope()); //start a second debug session session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(300); QCOMPARE(variableCollection()->watches()->childCount(), 1); ts = variableCollection()->index(0, 0, variableCollection()->index(0, 0)); v = qobject_cast(variableCollection()->watches()->child(0)); QVERIFY(v); QVERIFY(v->inScope()); QCOMPARE(v->childCount(), 3); v = qobject_cast(v->child(0)); QVERIFY(v->inScope()); QCOMPARE(v->data(1, Qt::DisplayRole).toString(), QString::number(0)); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //check if variable is marked as out-of-scope v = qobject_cast(variableCollection()->watches()->child(0)); QVERIFY(!v->inScope()); QVERIFY(!dynamic_cast(v->child(0))->inScope()); } void GdbTest::testVariablesStopDebugger() { auto *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); session->stopDebugger(); QTest::qWait(300); } void GdbTest::testVariablesStartSecondSession() { QPointer session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QPointer session2 = new TestDebugSession; session2->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session2->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session2, DebugSession::PausedState); session2->run(); WAIT_FOR_STATE(session2, DebugSession::EndedState); } void GdbTest::testVariablesSwitchFrame() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 2); COMPARE_DATA(variableCollection()->index(0, 0, i), "i"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); COMPARE_DATA(variableCollection()->index(1, 0, i), "j"); stackModel->setCurrentFrame(1); QTest::qWait(200); i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); COMPARE_DATA(variableCollection()->index(2, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(2, 1, i), "1"); COMPARE_DATA(variableCollection()->index(3, 0, i), "argv"); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testVariablesQuicklySwitchFrame() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 2); COMPARE_DATA(variableCollection()->index(0, 0, i), "i"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); COMPARE_DATA(variableCollection()->index(1, 0, i), "j"); stackModel->setCurrentFrame(1); QTest::qWait(300); stackModel->setCurrentFrame(0); QTest::qWait(1); stackModel->setCurrentFrame(1); QTest::qWait(1); stackModel->setCurrentFrame(0); QTest::qWait(1); stackModel->setCurrentFrame(1); QTest::qWait(500); i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); QStringList locs; for (int j = 0; j < variableCollection()->rowCount(i); ++j) { locs << variableCollection()->index(j, 0, i).data().toString(); } QVERIFY(locs.contains("argc")); QVERIFY(locs.contains("argv")); QVERIFY(locs.contains("x")); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testSegfaultDebugee() { auto *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_crash"))); QString fileName = findSourceFile(QStringLiteral("debugeecrash.cpp")); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 23); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); session->stopDebugger(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testSwitchFrameGdbConsole() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(stackModel->currentFrame(), 0); stackModel->setCurrentFrame(1); QCOMPARE(stackModel->currentFrame(), 1); QTest::qWait(500); QCOMPARE(stackModel->currentFrame(), 1); session->addUserCommand(QStringLiteral("print x")); QTest::qWait(500); //currentFrame must not reset to 0; Bug 222882 QCOMPARE(stackModel->currentFrame(), 1); } //Bug 201771 void GdbTest::testInsertAndRemoveBreakpointWhileRunning() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeeslow"))); QString fileName = findSourceFile(QStringLiteral("debugeeslow.cpp")); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::ActiveState); QTest::qWait(2000); qDebug() << "adding breakpoint"; KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 30); // ++i; b->setDeleted(); WAIT_FOR_STATE(session, DebugSession::EndedState); } //Bug 274390 void GdbTest::testCommandOrderFastStepping() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeeqt"))); breakpoints()->addCodeBreakpoint(QStringLiteral("main")); QVERIFY(session->startDebugging(&cfg, m_iface)); for(int i=0; i<20; i++) { session->stepInto(); } WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testPickupManuallyInsertedBreakpoint() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QStringLiteral("main")); QVERIFY(session->startDebugging(&cfg, m_iface)); session->addCommand(MI::NonMI, QStringLiteral("break debugee.cpp:32")); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); //wait for breakpoints update QCOMPARE(breakpoints()->breakpoints().count(), 2); QCOMPARE(breakpoints()->rowCount(), 2); KDevelop::Breakpoint *b = breakpoints()->breakpoint(1); QVERIFY(b); QCOMPARE(b->line(), 31); //we start with 0, gdb with 1 QCOMPARE(b->url().fileName(), QString("debugee.cpp")); } //Bug 270970 void GdbTest::testPickupManuallyInsertedBreakpointOnlyOnce() { auto *session = new TestDebugSession; //inject here, so it behaves similar like a command from .gdbinit QTemporaryFile configScript; configScript.open(); configScript.write(QStringLiteral("file %0\n").arg(findExecutable(QStringLiteral("debuggee_debugee")).toLocalFile()).toLocal8Bit()); configScript.write("break debugee.cpp:32\n"); configScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(Config::RemoteGdbConfigEntry, QUrl::fromLocalFile(configScript.fileName())); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(QStringLiteral("debugee.cpp")), 31); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 1); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testPickupCatchThrowOnlyOnce() { QTemporaryFile configScript; configScript.open(); configScript.write("catch throw\n"); configScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(Config::RemoteGdbConfigEntry, QUrl::fromLocalFile(configScript.fileName())); for (int i = 0; i < 2; ++i) { auto* session = new TestDebugSession; QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::EndedState); } QCOMPARE(breakpoints()->rowCount(), 1); //one from kdevelop, one from runScript } void GdbTest::testRunGdbScript() { auto *session = new TestDebugSession; QTemporaryFile runScript; runScript.open(); runScript.write("file " + KShell::quoteArg(findExecutable(QStringLiteral("debuggee_debugee")).toLocalFile()).toUtf8() + "\n"); runScript.write("break main\n"); runScript.write("run\n"); runScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(Config::RemoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 27); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testRemoteDebug() { const QString gdbserverExecutable = QStandardPaths::findExecutable(QStringLiteral("gdbserver")); if (gdbserverExecutable.isEmpty()) { QSKIP("Skipping, gdbserver not available", SkipSingle); } auto *session = new TestDebugSession; QTemporaryFile shellScript(QDir::currentPath()+"/shellscript"); shellScript.open(); shellScript.write("gdbserver localhost:2345 " + findExecutable(QStringLiteral("debuggee_debugee")).toLocalFile().toUtf8() + "\n"); shellScript.close(); shellScript.setPermissions(shellScript.permissions() | QFile::ExeUser); QFile::copy(shellScript.fileName(), shellScript.fileName()+"-copy"); //to avoid "Text file busy" on executing (why?) QTemporaryFile runScript(QDir::currentPath()+"/runscript"); runScript.open(); runScript.write("file " + findExecutable(QStringLiteral("debuggee_debugee")).toLocalFile().toUtf8() + "\n"); runScript.write("target remote localhost:2345\n"); runScript.write("break debugee.cpp:30\n"); runScript.write("continue\n"); runScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(Config::RemoteGdbShellEntry, QUrl::fromLocalFile((shellScript.fileName()+"-copy"))); grp.writeEntry(Config::RemoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); QFile::remove(shellScript.fileName()+"-copy"); } void GdbTest::testRemoteDebugInsertBreakpoint() { const QString gdbserverExecutable = QStandardPaths::findExecutable(QStringLiteral("gdbserver")); if (gdbserverExecutable.isEmpty()) { QSKIP("Skipping, gdbserver not available", SkipSingle); } auto *session = new TestDebugSession; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 29); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 35); QTemporaryFile shellScript(QDir::currentPath()+"/shellscript"); shellScript.open(); shellScript.write("gdbserver localhost:2345 " + findExecutable(QStringLiteral("debuggee_debugee")).toLocalFile().toUtf8() + "\n"); shellScript.close(); shellScript.setPermissions(shellScript.permissions() | QFile::ExeUser); QFile::copy(shellScript.fileName(), shellScript.fileName()+"-copy"); //to avoid "Text file busy" on executing (why?) QTemporaryFile runScript(QDir::currentPath()+"/runscript"); runScript.open(); runScript.write("file " + findExecutable(QStringLiteral("debuggee_debugee")).toLocalFile().toUtf8() + '\n'); runScript.write("target remote localhost:2345\n"); runScript.write("break debugee.cpp:30\n"); runScript.write("continue\n"); runScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(Config::RemoteGdbShellEntry, QUrl::fromLocalFile(shellScript.fileName()+"-copy")); grp.writeEntry(Config::RemoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); QCOMPARE(breakpoints()->breakpoints().count(), 2); //one from kdevelop, one from runScript session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 35); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); QFile::remove(shellScript.fileName()+"-copy"); } void GdbTest::testRemoteDebugInsertBreakpointPickupOnlyOnce() { const QString gdbserverExecutable = QStandardPaths::findExecutable(QStringLiteral("gdbserver")); if (gdbserverExecutable.isEmpty()) { QSKIP("Skipping, gdbserver not available", SkipSingle); } auto *session = new TestDebugSession; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 35); QTemporaryFile shellScript(QDir::currentPath()+"/shellscript"); shellScript.open(); shellScript.write("gdbserver localhost:2345 "+findExecutable(QStringLiteral("debuggee_debugee")).toLocalFile().toLatin1()+"\n"); shellScript.close(); shellScript.setPermissions(shellScript.permissions() | QFile::ExeUser); QFile::copy(shellScript.fileName(), shellScript.fileName()+"-copy"); //to avoid "Text file busy" on executing (why?) QTemporaryFile runScript(QDir::currentPath()+"/runscript"); runScript.open(); runScript.write("file "+findExecutable(QStringLiteral("debuggee_debugee")).toLocalFile().toLatin1()+"\n"); runScript.write("target remote localhost:2345\n"); runScript.write("break debugee.cpp:30\n"); runScript.write("continue\n"); runScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(Config::RemoteGdbShellEntry, QUrl::fromLocalFile((shellScript.fileName()+"-copy"))); grp.writeEntry(Config::RemoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); QCOMPARE(breakpoints()->breakpoints().count(), 2); //one from kdevelop, one from runScript session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 35); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //************************** second session session = new TestDebugSession; QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); QCOMPARE(breakpoints()->breakpoints().count(), 2); //one from kdevelop, one from runScript session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 35); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); QFile::remove(shellScript.fileName()+"-copy"); } void GdbTest::testBreakpointWithSpaceInPath() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeespace"))); KConfigGroup grp = cfg.config(); QString fileName = findSourceFile(QStringLiteral("debugee space.cpp")); KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 20); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 20); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakpointDisabledOnStart() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28) ->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 29); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 31); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Checked); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 31); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testCatchpoint() { auto *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeeexception"))); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile(QStringLiteral("debugeeexception.cpp"))), 29); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); TestFrameStackModel* fsModel = session->frameStackModel(); QCOMPARE(fsModel->currentFrame(), 0); QCOMPARE(session->line(), 29); session->addCommand(MI::NonMI, QStringLiteral("catch throw")); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QTest::qWait(1000); const QVector frames = fsModel->frames(fsModel->currentThread()); QVERIFY(frames.size() >= 2); // frame 0 is somewhere inside libstdc++ QCOMPARE(frames[1].file, QUrl::fromLocalFile(findSourceFile("debugeeexception.cpp"))); QCOMPARE(frames[1].line, 22); QCOMPARE(breakpoints()->rowCount(),2); QVERIFY(!breakpoints()->breakpoint(0)->location().isEmpty()); QVERIFY(!breakpoints()->breakpoint(1)->location().isEmpty()); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testThreadAndFrameInfo() { // Check if --thread is added to user commands auto *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeethreads"))); QString fileName = findSourceFile(QStringLiteral("debugeethreads.cpp")); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QSignalSpy outputSpy(session, &TestDebugSession::debuggerUserCommandOutput); session->addCommand(new MI::UserCommand(MI::ThreadInfo, QString())); session->addCommand(new MI::UserCommand(MI::StackListLocals, QStringLiteral("0"))); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // wait for command finish // outputs should be // 1. -thread-info // 2. ^done for thread-info // 3. -stack-list-locals // 4. ^done for -stack-list-locals QCOMPARE(outputSpy.count(), 4); QVERIFY(outputSpy.at(2).at(0).toString().contains(QLatin1String("--thread 1"))); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::parseBug304730() { MI::FileSymbol file; file.contents = QByteArray("^done,bkpt={" "number=\"1\",type=\"breakpoint\",disp=\"keep\",enabled=\"y\",addr=\"\",times=\"0\"," "original-location=\"/media/portable/Projects/BDSInpainting/PatchMatch/PatchMatch.hpp:231\"}," "{number=\"1.1\",enabled=\"y\",addr=\"0x081d84aa\"," "func=\"PatchMatch, 2u> >" "::Propagation(ForwardPropagationNeighbors)\"," "file=\"/media/portable/Projects/BDSInpainting/Drivers/../PatchMatch/PatchMatch.hpp\"," "fullname=\"/media/portable/Projects/BDSInpainting/PatchMatch/PatchMatch.hpp\",line=\"231\"}," "{number=\"1.2\",enabled=\"y\",addr=\"0x081d8ae2\"," "func=\"PatchMatch, 2u> >" "::Propagation(BackwardPropagationNeighbors)\"," "file=\"/media/portable/Projects/BDSInpainting/Drivers/../PatchMatch/PatchMatch.hpp\"," "fullname=\"/media/portable/Projects/BDSInpainting/PatchMatch/PatchMatch.hpp\",line=\"231\"}," "{number=\"1.3\",enabled=\"y\",addr=\"0x081d911a\"," "func=\"PatchMatch, 2u> >" "::Propagation(AllowedPropagationNeighbors)\"," "file=\"/media/portable/Projects/BDSInpainting/Drivers/../PatchMatch/PatchMatch.hpp\"," "fullname=\"/media/portable/Projects/BDSInpainting/PatchMatch/PatchMatch.hpp\",line=\"231\"}"); MI::MIParser parser; std::unique_ptr record(parser.parse(&file)); QVERIFY(record.get() != nullptr); } void GdbTest::testMultipleLocationsBreakpoint() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeemultilocbreakpoint"))); breakpoints()->addCodeBreakpoint(QStringLiteral("aPlusB")); //TODO check if the additional location breakpoint is added session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 19); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBug301287() { auto *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add(QStringLiteral("argc")); QTest::qWait(300); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //start second debug session (same cfg) session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(300); i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testMultipleBreakpoint() { auto *session = new TestDebugSession; //there'll be about 3-4 breakpoints, but we treat it like one. TestLaunchConfiguration c(findExecutable(QStringLiteral("debuggee_debugeemultiplebreakpoint"))); KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QStringLiteral("debugeemultiplebreakpoint.cpp:52")); session->startDebugging(&c, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 1); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testRegularExpressionBreakpoint() { auto *session = new TestDebugSession; TestLaunchConfiguration c(findExecutable(QStringLiteral("debuggee_debugeemultilocbreakpoint"))); breakpoints()->addCodeBreakpoint(QStringLiteral("main")); session->startDebugging(&c, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); session->addCommand(MI::NonMI, QStringLiteral("rbreak .*aPl.*B")); QTest::qWait(100); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 3); session->addCommand(MI::BreakDelete, QString()); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testChangeBreakpointWhileRunning() { auto *session = new TestDebugSession; TestLaunchConfiguration c(findExecutable(QStringLiteral("debuggee_debugeeslow"))); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint(QStringLiteral("debugeeslow.cpp:30")); session->startDebugging(&c, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QVERIFY(session->currentLine() >= 29 && session->currentLine() <= 31 ); session->run(); WAIT_FOR_STATE(session, DebugSession::ActiveState); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //to make one loop QTest::qWait(2000); WAIT_FOR_STATE(session, DebugSession::ActiveState); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Checked); QTest::qWait(100); WAIT_FOR_STATE(session, DebugSession::PausedState); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); session->run(); QTest::qWait(100); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testDebugInExternalTerminal() { TestLaunchConfiguration cfg; const QStringList consoles { "konsole", "xterm", "xfce4-terminal", "gnome-terminal" }; for (const QString& console : consoles) { TestDebugSession* session = nullptr; if (QStandardPaths::findExecutable(console).isEmpty()) { continue; } session = new TestDebugSession(); cfg.config().writeEntry("External Terminal"/*ExecutePlugin::terminalEntry*/, console); cfg.config().writeEntry("Use External Terminal"/*ExecutePlugin::useTerminalEntry*/, true); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } } // see: https://bugs.kde.org/show_bug.cgi?id=339231 void GdbTest::testPathWithSpace() { auto* session = new TestDebugSession; auto debugee = findExecutable(QStringLiteral("path with space/debuggee_spacedebugee")); TestLaunchConfiguration c(debugee, KIO::upUrl(debugee)); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint(QStringLiteral("spacedebugee.cpp:30")); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); session->startDebugging(&c, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } bool GdbTest::waitForState(DebugSession *session, DebugSession::DebuggerState state, const char *file, int line, bool waitForIdle) { QPointer s(session); //session can get deleted in DebugController QTime stopWatch; stopWatch.start(); // legacy behavior for tests that implicitly may require waiting for idle, // but which were written before waitForIdle was added waitForIdle = waitForIdle || state != MIDebugSession::EndedState; while (s && (s->state() != state || (waitForIdle && s->debuggerStateIsOn(s_dbgBusy)))) { if (stopWatch.elapsed() > 5000) { qWarning() << "current state" << s->state() << "waiting for" << state; QTest::qFail(qPrintable(QString("Timeout before reaching state %0").arg(state)), file, line); return false; } QTest::qWait(20); } // NOTE: don't wait anymore after leaving the loop. Waiting re-enters event loop and // may change session state. if (!s && state != MIDebugSession::EndedState) { QTest::qFail(qPrintable(QString("Session ended before reaching state %0").arg(state)), file, line); return false; } qDebug() << "Reached state " << state << " in " << file << ':' << line; return true; } } // end of namespace GDB } // end of namespace KDevMI QTEST_MAIN(KDevMI::GDB::GdbTest) #include "test_gdb.moc" #include "moc_test_gdb.cpp" diff --git a/plugins/gdb/unittests/test_gdbprinters.cpp b/plugins/gdb/unittests/test_gdbprinters.cpp index 89a9fd73f3..7555a8c50a 100644 --- a/plugins/gdb/unittests/test_gdbprinters.cpp +++ b/plugins/gdb/unittests/test_gdbprinters.cpp @@ -1,472 +1,476 @@ /* Copyright 2010 Niko Sams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "test_gdbprinters.h" #include "tests/debuggers-tests-config.h" #include #include #include #include #include const QString BINARY_PATH(DEBUGGEE_BIN_DIR); class GdbProcess : private QProcess { + Q_OBJECT + public: explicit GdbProcess(const QString &program) : QProcess() { setProcessChannelMode(MergedChannels); // don't attempt to load .gdbinit in home (may cause unexpected results) QProcess::start(QStringLiteral("gdb"), (QStringList() << QStringLiteral("-nx") << (BINARY_PATH + '/' + program))); const bool started = waitForStarted(); if (!started) { qDebug() << "Failed to start 'gdb' executable:" << errorString(); Q_ASSERT(false); return; } QByteArray prompt = waitForPrompt(); QVERIFY(!prompt.contains("No such file or directory")); execute("set confirm off"); execute("set print pretty on"); execute("set disable-randomization off"); // see https://phabricator.kde.org/D2188 QList p; QDir printersDir = QFileInfo(__FILE__).dir(); printersDir.cdUp(); // go up to get to the main printers directory QVERIFY(printersDir.cd("printers")); p << "python" << "import sys" << "sys.path.insert(0, '"+printersDir.path().toLatin1()+"')" << "from qt import register_qt_printers" << "register_qt_printers (None)" << "from kde import register_kde_printers" << "register_kde_printers (None)" << "end"; for (const QByteArray& i : qAsConst(p)) { write(i + "\n"); } waitForPrompt(); } ~GdbProcess() override { write("q\n"); waitForFinished(); } QByteArray waitForPrompt() { QByteArray output; while (!output.endsWith("(gdb) ")) { Q_ASSERT(state() == QProcess::Running); waitForReadyRead(); QByteArray l = readAll(); //qDebug() << l; output.append(l); } output.chop(7); //remove (gdb) prompt if (output.contains("Traceback") || output.contains("Exception")) { qDebug() << output; QTest::qFail("Unexpected Python Exception", __FILE__, __LINE__); } return output; } QByteArray execute(const QByteArray &cmd) { write(cmd + "\n"); auto out = waitForPrompt(); qDebug() << cmd << " = " << out; return out; } }; void QtPrintersTest::testQString() { GdbProcess gdb(QStringLiteral("debuggee_qstring")); gdb.execute("break qstring.cpp:5"); gdb.execute("run"); QVERIFY(gdb.execute("print s").contains("\"test最后一个不是特殊字符'\\\"\\\\u6211\"")); gdb.execute("next"); QVERIFY(gdb.execute("print s").contains("\"test最后一个不是特殊字符'\\\"\\\\u6211x\"")); } void QtPrintersTest::testQByteArray() { GdbProcess gdb(QStringLiteral("debuggee_qbytearray")); gdb.execute("break qbytearray.cpp:5"); gdb.execute("run"); QByteArray out = gdb.execute("print ba"); qDebug() << out; QVERIFY(out.contains("\"\xE6\x98\xAF'\\\"\\\\u6211\"")); QVERIFY(out.contains("[0] = -26 '\\346'")); QVERIFY(out.contains("[3] = 39 '\\''")); QVERIFY(out.contains("[4] = 34 '\"'")); gdb.execute("next"); out = gdb.execute("print ba"); QVERIFY(out.contains("\"\xE6\x98\xAF'\\\"\\\\u6211x\"")); } void QtPrintersTest::testQListContainer_data() { QTest::addColumn("container"); QTest::newRow("QList") << "QList"; QTest::newRow("QQueue") << "QQueue"; QTest::newRow("QVector") << "QVector"; QTest::newRow("QStack") << "QStack"; QTest::newRow("QLinkedList") << "QLinkedList"; QTest::newRow("QSet") << "QSet"; } void QtPrintersTest::testQListContainer() { QFETCH(QString, container); GdbProcess gdb(QStringLiteral("debuggee_qlistcontainer")); gdb.execute("break main"); gdb.execute("run"); gdb.execute(QStringLiteral("break 'doStuff<%1>()'").arg(container).toLocal8Bit()); gdb.execute("cont"); { // gdb.execute("break qlistcontainer.cpp:34"); gdb.execute("cont"); QByteArray out = gdb.execute("print intList"); qWarning() << "FOO" << out; QVERIFY(out.contains(QString("%1 (size = 0)").arg(container).toLocal8Bit())); gdb.execute("next"); out = gdb.execute("print intList"); QVERIFY(out.contains(QString("%1 (size = 2)").arg(container).toLocal8Bit())); if (container != QLatin1String("QSet")) { QVERIFY(out.contains("[0] = 10")); QVERIFY(out.contains("[1] = 20")); QVERIFY(!out.contains("[2] = 30")); } else { // QSet order is undefined QVERIFY(out.contains("] = 10")); QVERIFY(out.contains("] = 20")); QVERIFY(!out.contains("] = 30")); } gdb.execute("next"); out = gdb.execute("print intList"); QVERIFY(out.contains(QString("%1 (size = 3)").arg(container).toLocal8Bit())); if (container != QLatin1String("QSet")) { QVERIFY(out.contains("[0] = 10")); QVERIFY(out.contains("[1] = 20")); QVERIFY(out.contains("[2] = 30")); } else { // QSet order is undefined QVERIFY(out.contains("] = 10")); QVERIFY(out.contains("] = 20")); QVERIFY(out.contains("] = 30")); } } { // gdb.execute("next"); QByteArray out = gdb.execute("print stringList"); QVERIFY(out.contains(QString("%1 (size = 0)").arg(container).toLocal8Bit())); gdb.execute("next"); out = gdb.execute("print stringList"); QVERIFY(out.contains(QString("%1 (size = 2)").arg(container).toLocal8Bit())); if (container != QLatin1String("QSet")) { QVERIFY(out.contains("[0] = \"a\"")); QVERIFY(out.contains("[1] = \"bc\"")); QVERIFY(!out.contains("[2] = \"d\"")); } else { // QSet order is undefined QVERIFY(out.contains("] = \"a\"")); QVERIFY(out.contains("] = \"bc\"")); QVERIFY(!out.contains("] = \"d\"")); } gdb.execute("next"); out = gdb.execute("print stringList"); QVERIFY(out.contains(QString("%1 (size = 3)").arg(container).toLocal8Bit())); if (container != QLatin1String("QSet")) { QVERIFY(out.contains("[0] = \"a\"")); QVERIFY(out.contains("[1] = \"bc\"")); QVERIFY(out.contains("[2] = \"d\"")); } else { // QSet order is undefined QVERIFY(out.contains("] = \"a\"")); QVERIFY(out.contains("] = \"bc\"")); QVERIFY(out.contains("] = \"d\"")); } } { // gdb.execute("next"); QByteArray out = gdb.execute("print structList"); QVERIFY(out.contains(QString("%1 (size = 0)").arg(container).toLocal8Bit())); gdb.execute("next"); out = gdb.execute("print structList"); QVERIFY(out.contains(QString("%1 (size = 1)").arg(container).toLocal8Bit())); QVERIFY(out.contains("[0] = {")); QVERIFY(out.contains("a = \"a\"")); QVERIFY(out.contains("b = \"b\"")); QVERIFY(out.contains("c = 100")); QVERIFY(out.contains("d = -200")); gdb.execute("next"); out = gdb.execute("print structList"); QVERIFY(out.contains(QString("%1 (size = 2)").arg(container).toLocal8Bit())); QVERIFY(out.contains("[1] = {")); } { // gdb.execute("next"); QByteArray out = gdb.execute("print pointerList"); QVERIFY(out.contains(QString("%1 (size = 0)").arg(container).toLocal8Bit())); gdb.execute("next"); out = gdb.execute("print pointerList"); QVERIFY(out.contains(QString("%1 (size = 2)").arg(container).toLocal8Bit())); QVERIFY(out.contains("[0] = 0x")); QVERIFY(out.contains("[1] = 0x")); QVERIFY(!out.contains("[2] = 0x")); gdb.execute("next"); out = gdb.execute("print pointerList"); QVERIFY(out.contains(QString("%1 (size = 3)").arg(container).toLocal8Bit())); QVERIFY(out.contains("[0] = 0x")); QVERIFY(out.contains("[1] = 0x")); QVERIFY(out.contains("[2] = 0x")); gdb.execute("next"); } { // > gdb.execute("next"); QByteArray out = gdb.execute("print pairList"); QVERIFY(out.contains(QString("%1> (size = 0)").arg(container).toLocal8Bit())); gdb.execute("next"); out = gdb.execute("print pairList"); QVERIFY(out.contains(QString("%1> (size = 2)").arg(container).toLocal8Bit())); if (container != QLatin1String("QSet")) { QVERIFY(out.contains("[0] = {\n first = 1, \n second = 2\n }")); QVERIFY(out.contains("[1] = {\n first = 2, \n second = 3\n }")); } else { // order is undefined in QSet QVERIFY(out.contains("] = {\n first = 1, \n second = 2\n }")); QVERIFY(out.contains("] = {\n first = 2, \n second = 3\n }")); } QVERIFY(!out.contains("[2] = ")); gdb.execute("next"); out = gdb.execute("print pairList"); QVERIFY(out.contains(QString("%1> (size = 3)").arg(container).toLocal8Bit())); if (container != QLatin1String("QSet")) { QVERIFY(out.contains("[0] = {\n first = 1, \n second = 2\n }")); QVERIFY(out.contains("[1] = {\n first = 2, \n second = 3\n }")); QVERIFY(out.contains("[2] = {\n first = 4, \n second = 5\n }")); } else { // order is undefined in QSet QVERIFY(out.contains("] = {\n first = 1, \n second = 2\n }")); QVERIFY(out.contains("] = {\n first = 2, \n second = 3\n }")); QVERIFY(out.contains("] = {\n first = 4, \n second = 5\n }")); } } } void QtPrintersTest::testQMapInt() { GdbProcess gdb(QStringLiteral("debuggee_qmapint")); gdb.execute("break qmapint.cpp:7"); gdb.execute("run"); QByteArray out = gdb.execute("print m"); QVERIFY(out.contains("QMap (size = 2)")); QVERIFY(out.contains("[10] = 100")); QVERIFY(out.contains("[20] = 200")); gdb.execute("next"); out = gdb.execute("print m"); QVERIFY(out.contains("QMap (size = 3)")); QVERIFY(out.contains("[30] = 300")); } void QtPrintersTest::testQMapString() { GdbProcess gdb(QStringLiteral("debuggee_qmapstring")); gdb.execute("break qmapstring.cpp:8"); gdb.execute("run"); QByteArray out = gdb.execute("print m"); QVERIFY(out.contains("QMap (size = 2)")); QVERIFY(out.contains("[\"10\"] = \"100\"")); QVERIFY(out.contains("[\"20\"] = \"200\"")); gdb.execute("next"); out = gdb.execute("print m"); QVERIFY(out.contains("QMap (size = 3)")); QVERIFY(out.contains("[\"30\"] = \"300\"")); } void QtPrintersTest::testQMapStringBool() { GdbProcess gdb(QStringLiteral("debuggee_qmapstringbool")); gdb.execute("break qmapstringbool.cpp:8"); gdb.execute("run"); QByteArray out = gdb.execute("print m"); QVERIFY(out.contains("QMap (size = 2)")); QVERIFY(out.contains("[\"10\"] = true")); QVERIFY(out.contains("[\"20\"] = false")); gdb.execute("next"); out = gdb.execute("print m"); QVERIFY(out.contains("QMap (size = 3)")); QVERIFY(out.contains("[\"30\"] = true")); } void QtPrintersTest::testQDate() { GdbProcess gdb(QStringLiteral("debuggee_qdate")); gdb.execute("break qdate.cpp:6"); gdb.execute("run"); QByteArray out = gdb.execute("print d"); QVERIFY(out.contains("2010-01-20")); } void QtPrintersTest::testQTime() { GdbProcess gdb(QStringLiteral("debuggee_qtime")); gdb.execute("break qtime.cpp:6"); gdb.execute("run"); QByteArray out = gdb.execute("print t"); QVERIFY(out.contains("15:30:10.123")); } void QtPrintersTest::testQDateTime() { GdbProcess gdb(QStringLiteral("debuggee_qdatetime")); gdb.execute("break qdatetime.cpp:5"); gdb.execute("run"); QByteArray out = gdb.execute("print dt"); QVERIFY(out.contains("Wed Jan 20 15:31:13 2010")); } void QtPrintersTest::testQUrl() { GdbProcess gdb(QStringLiteral("debuggee_qurl")); gdb.execute("break qurl.cpp:5"); gdb.execute("run"); QByteArray out = gdb.execute("print u"); QVERIFY(out.contains("http://user@www.kdevelop.org:12345/foo?xyz=bar#asdf")); } void QtPrintersTest::testQHashInt() { GdbProcess gdb(QStringLiteral("debuggee_qhashint")); gdb.execute("break qhashint.cpp:7"); gdb.execute("run"); QByteArray out = gdb.execute("print h"); QVERIFY(out.contains("QHash (size = 2)")); QVERIFY(out.contains("[10] = 100")); QVERIFY(out.contains("[20] = 200")); gdb.execute("next"); out = gdb.execute("print h"); QVERIFY(out.contains("QHash (size = 3)")); QVERIFY(out.contains("[30] = 300")); } void QtPrintersTest::testQHashString() { GdbProcess gdb(QStringLiteral("debuggee_qhashstring")); gdb.execute("break qhashstring.cpp:8"); gdb.execute("run"); QByteArray out = gdb.execute("print h"); QVERIFY(out.contains("QHash (size = 2)")); QVERIFY(out.contains("[\"10\"] = \"100\"")); QVERIFY(out.contains("[\"20\"] = \"200\"")); gdb.execute("next"); out = gdb.execute("print h"); QVERIFY(out.contains("QHash (size = 3)")); QVERIFY(out.contains("[\"30\"] = \"300\"")); } void QtPrintersTest::testQSetInt() { GdbProcess gdb(QStringLiteral("debuggee_qsetint")); gdb.execute("break qsetint.cpp:7"); gdb.execute("run"); QByteArray out = gdb.execute("print s"); QVERIFY(out.contains("QSet (size = 2)")); QVERIFY(out.contains("] = 10")); QVERIFY(out.contains("] = 20")); gdb.execute("next"); out = gdb.execute("print s"); QVERIFY(out.contains("QSet (size = 3)")); QVERIFY(out.contains("] = 30")); } void QtPrintersTest::testQSetString() { GdbProcess gdb(QStringLiteral("debuggee_qsetstring")); gdb.execute("break qsetstring.cpp:8"); gdb.execute("run"); QByteArray out = gdb.execute("print s"); QVERIFY(out.contains("QSet (size = 2)")); QVERIFY(out.contains("] = \"10\"")); QVERIFY(out.contains("] = \"20\"")); gdb.execute("next"); out = gdb.execute("print s"); QVERIFY(out.contains("QSet (size = 3)")); QVERIFY(out.contains("] = \"30\"")); } void QtPrintersTest::testQChar() { GdbProcess gdb(QStringLiteral("debuggee_qchar")); gdb.execute("break qchar.cpp:5"); gdb.execute("run"); QVERIFY(gdb.execute("print c").contains("\"k\"")); } void QtPrintersTest::testQListPOD() { GdbProcess gdb(QStringLiteral("debuggee_qlistpod")); gdb.execute("break qlistpod.cpp:31"); gdb.execute("run"); QVERIFY(gdb.execute("print b").contains("false")); QVERIFY(gdb.execute("print c").contains("50")); QVERIFY(gdb.execute("print uc").contains("50")); QVERIFY(gdb.execute("print s").contains("50")); QVERIFY(gdb.execute("print us").contains("50")); QVERIFY(gdb.execute("print i").contains("50")); QVERIFY(gdb.execute("print ui").contains("50")); QVERIFY(gdb.execute("print l").contains("50")); QVERIFY(gdb.execute("print ul").contains("50")); QVERIFY(gdb.execute("print i64").contains("50")); QVERIFY(gdb.execute("print ui64").contains("50")); QVERIFY(gdb.execute("print f").contains("50")); QVERIFY(gdb.execute("print d").contains("50")); } void QtPrintersTest::testQUuid() { GdbProcess gdb(QStringLiteral("debuggee_quuid")); gdb.execute("break quuid.cpp:4"); gdb.execute("run"); QByteArray data = gdb.execute("print id"); QVERIFY(data.contains("{9ec3b70b-d105-42bf-b3b4-656e44d2e223}")); } void QtPrintersTest::testKTextEditorTypes() { GdbProcess gdb(QStringLiteral("debuggee_ktexteditortypes")); gdb.execute("break ktexteditortypes.cpp:9"); gdb.execute("run"); QByteArray data = gdb.execute("print cursor"); QCOMPARE(data, QByteArray("$1 = [1, 1]")); data = gdb.execute("print range"); QCOMPARE(data, QByteArray("$2 = [(1, 1) -> (2, 2)]")); } void QtPrintersTest::testKDevelopTypes() { GdbProcess gdb(QStringLiteral("debuggee_kdeveloptypes")); gdb.execute("break kdeveloptypes.cpp:12"); gdb.execute("run"); QVERIFY(gdb.execute("print path1").contains("(\"tmp\", \"foo\")")); QVERIFY(gdb.execute("print path2").contains("(\"http://www.test.com\", \"tmp\", \"asdf.txt\")")); } QTEST_MAIN(QtPrintersTest) + +#include "test_gdbprinters.moc" diff --git a/plugins/lldb/unittests/test_lldb.cpp b/plugins/lldb/unittests/test_lldb.cpp index 109a6bac70..51242cd95b 100644 --- a/plugins/lldb/unittests/test_lldb.cpp +++ b/plugins/lldb/unittests/test_lldb.cpp @@ -1,1904 +1,1906 @@ /* * Unit tests for LLDB debugger plugin Copyright 2009 Niko Sams Copyright 2013 Vlas Puhov * Copyright 2016 Aetf * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * */ #include "test_lldb.h" #include "controllers/framestackmodel.h" #include "debugsession.h" #include "tests/debuggers-tests-config.h" #include "tests/testhelper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define WAIT_FOR_STATE(session, state) \ do { if (!KDevMI::waitForState((session), (state), __FILE__, __LINE__)) return; } while (0) #define WAIT_FOR_STATE_AND_IDLE(session, state) \ do { if (!KDevMI::waitForState((session), (state), __FILE__, __LINE__, true)) return; } while (0) #define WAIT_FOR_A_WHILE(session, ms) \ do { if (!KDevMI::waitForAWhile((session), (ms), __FILE__, __LINE__)) return; } while (0) #define WAIT_FOR(session, condition) \ do { \ KDevMI::TestWaiter w((session), #condition, __FILE__, __LINE__); \ while (w.waitUnless((condition))) /* nothing */ ; \ } while(0) #define COMPARE_DATA(index, expected) \ do { if (!KDevMI::compareData((index), (expected), __FILE__, __LINE__)) return; } while (0) #define SKIP_IF_ATTACH_FORBIDDEN() \ do { \ if (KDevMI::isAttachForbidden(__FILE__, __LINE__)) \ return; \ } while(0) using namespace KDevelop; using namespace KDevMI::LLDB; using KDevMI::findExecutable; using KDevMI::findFile; using KDevMI::findSourceFile; namespace { class WritableEnvironmentProfileList : public EnvironmentProfileList { public: explicit WritableEnvironmentProfileList(KConfig* config) : EnvironmentProfileList(config) {} using EnvironmentProfileList::variables; using EnvironmentProfileList::saveSettings; using EnvironmentProfileList::removeProfile; }; class TestLaunchConfiguration : public ILaunchConfiguration { public: explicit TestLaunchConfiguration(const QUrl& executable = findExecutable(QStringLiteral("debuggee_debugee")), const QUrl& workingDirectory = QUrl()) { qDebug() << "FIND" << executable; c = KSharedConfig::openConfig(); c->deleteGroup("launch"); cfg = c->group("launch"); cfg.writeEntry("isExecutable", true); cfg.writeEntry("Executable", executable); cfg.writeEntry("Working Directory", workingDirectory); } const KConfigGroup config() const override { return cfg; } KConfigGroup config() override { return cfg; }; QString name() const override { return QStringLiteral("Test-Launch"); } KDevelop::IProject* project() const override { return nullptr; } KDevelop::LaunchConfigurationType* type() const override { return nullptr; } KConfig* rootConfig() { return c.data(); } private: KConfigGroup cfg; KSharedConfigPtr c; }; class TestFrameStackModel : public LldbFrameStackModel { + Q_OBJECT + public: explicit TestFrameStackModel(DebugSession* session) : LldbFrameStackModel(session), fetchFramesCalled(0), fetchThreadsCalled(0) {} void fetchFrames(int threadNumber, int from, int to) override { fetchFramesCalled++; LldbFrameStackModel::fetchFrames(threadNumber, from, to); } void fetchThreads() override { fetchThreadsCalled++; LldbFrameStackModel::fetchThreads(); } int fetchFramesCalled; int fetchThreadsCalled; }; class TestDebugSession : public DebugSession { Q_OBJECT public: TestDebugSession() : DebugSession() { // explicit set formatter path to force use in-tree formatters, not the one installed in system. auto formatter = findFile(LLDB_SRC_DIR, "formatters/all.py"); setFormatterPath(formatter); setSourceInitFile(false); m_frameStackModel = new TestFrameStackModel(this); KDevelop::ICore::self()->debugController()->addSession(this); } TestFrameStackModel* frameStackModel() const override { return m_frameStackModel; } private: TestFrameStackModel* m_frameStackModel; }; } // end of anonymous namespace BreakpointModel* LldbTest::breakpoints() { return m_core->debugController()->breakpointModel(); } VariableCollection *LldbTest::variableCollection() { return m_core->debugController()->variableCollection(); } Variable *LldbTest::watchVariableAt(int i) { auto watchRoot = variableCollection()->indexForItem(variableCollection()->watches(), 0); auto idx = variableCollection()->index(i, 0, watchRoot); return qobject_cast(variableCollection()->itemForIndex(idx)); } QModelIndex LldbTest::localVariableIndexAt(int i, int col) { auto localRoot = variableCollection()->indexForItem(variableCollection()->locals(), 0); return variableCollection()->index(i, col, localRoot); } // Called before the first testfunction is executed void LldbTest::initTestCase() { AutoTestShell::init(); m_core = TestCore::initialize(Core::NoUi); m_iface = m_core->pluginController() ->pluginForExtension(QStringLiteral("org.kdevelop.IExecutePlugin"), QStringLiteral("kdevexecute")) ->extension(); Q_ASSERT(m_iface); m_debugeeFileName = findSourceFile("debugee.cpp"); const QString lldbMiExecutable = QStandardPaths::findExecutable(QStringLiteral("lldb-mi")); if (lldbMiExecutable.isEmpty()) { QSKIP("Skipping, lldb-mi not available"); } } // Called after the last testfunction was executed void LldbTest::cleanupTestCase() { TestCore::shutdown(); } // Called before each testfunction is executed void LldbTest::init() { //remove all breakpoints - so we can set our own in the test KConfigGroup bpCfg = KSharedConfig::openConfig()->group("breakpoints"); bpCfg.writeEntry("number", 0); bpCfg.sync(); breakpoints()->removeRows(0, breakpoints()->rowCount()); while (variableCollection()->watches()->childCount() > 0) { auto var = watchVariableAt(0); if (!var) break; var->die(); } } void LldbTest::cleanup() { // Called after every testfunction } void LldbTest::testStdout() { auto *session = new TestDebugSession; QSignalSpy outputSpy(session, &TestDebugSession::inferiorStdoutLines); TestLaunchConfiguration cfg; QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, KDevelop::IDebugSession::EndedState); QVERIFY(outputSpy.count() > 0); QStringList outputLines; while (outputSpy.count() > 0) { QList arguments = outputSpy.takeFirst(); for (const auto &item : arguments) { outputLines.append(item.toStringList()); } } QCOMPARE(outputLines, QStringList() << "Hello, world!" << "Hello"); } void LldbTest::testEnvironmentSet() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeeechoenv"))); cfg.config().writeEntry("EnvironmentGroup", "LldbTestGroup"); WritableEnvironmentProfileList envProfiles(cfg.rootConfig()); envProfiles.removeProfile(QStringLiteral("LldbTestGroup")); auto &envs = envProfiles.variables(QStringLiteral("LldbTestGroup")); envs[QStringLiteral("VariableA")] = QStringLiteral("-A' \" complex --value"); envs[QStringLiteral("VariableB")] = QStringLiteral("-B' \" complex --value"); envProfiles.saveSettings(cfg.rootConfig()); QSignalSpy outputSpy(session, &TestDebugSession::inferiorStdoutLines); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, KDevelop::IDebugSession::EndedState); QVERIFY(outputSpy.count() > 0); QStringList outputLines; while (outputSpy.count() > 0) { QList arguments = outputSpy.takeFirst(); for (const auto &item : arguments) { outputLines.append(item.toStringList()); } } QCOMPARE(outputLines, QStringList() << "-A' \" complex --value" << "-B' \" complex --value"); } void LldbTest::testBreakpoint() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 29); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); QCOMPARE(session->currentLine(), 29); session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); } void LldbTest::testBreakOnStart() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; cfg.config().writeEntry(KDevMI::Config::BreakOnStartEntry, true); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); // line 28 is the start of main function in debugee.cpp QCOMPARE(session->currentLine(), 27); // currentLine is zero-based session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testDisableBreakpoint() { QSKIP("Skipping... In lldb-mi -d flag has no effect when mixed with -f"); //Description: We must stop only on the third breakpoint int firstBreakLine=28; int secondBreakLine=23; int thirdBreakLine=24; int fourthBreakLine=31; auto *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint *b; b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), firstBreakLine); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //this is needed to emulate debug from GUI. If we are in edit mode, the debugSession doesn't exist. m_core->debugController()->breakpointModel()->blockSignals(true); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), secondBreakLine); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //all disabled breakpoints were added auto *thirdBreak = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), thirdBreakLine); m_core->debugController()->breakpointModel()->blockSignals(false); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), thirdBreak->line()); //disable existing breakpoint thirdBreak->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //add another disabled breakpoint b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), fourthBreakLine); WAIT_FOR_A_WHILE(session, 300); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); WAIT_FOR_A_WHILE(session, 300); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testChangeLocationBreakpoint() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; auto *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 27); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 27); WAIT_FOR_A_WHILE(session, 100); b->setLine(28); WAIT_FOR_A_WHILE(session, 100); session->run(); WAIT_FOR_A_WHILE(session, 100); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 28); WAIT_FOR_A_WHILE(session, 500); breakpoints()->setData(breakpoints()->index(0, KDevelop::Breakpoint::LocationColumn), QString(m_debugeeFileName+":30")); QCOMPARE(b->line(), 29); WAIT_FOR_A_WHILE(session, 100); QCOMPARE(b->line(), 29); session->run(); WAIT_FOR_A_WHILE(session, 100); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 29); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testDeleteBreakpoint() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; QCOMPARE(breakpoints()->rowCount(), 0); //add breakpoint before startDebugging breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 21); QCOMPARE(breakpoints()->rowCount(), 1); breakpoints()->removeRow(0); QCOMPARE(breakpoints()->rowCount(), 0); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 22); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testPendingBreakpoint() { QSKIP("Skipping... Pending breakpoint not work on lldb-mi"); auto *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 28); auto * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("test_lldb.cpp")), 10); QCOMPARE(b->state(), Breakpoint::NotStartedState); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(b->state(), Breakpoint::PendingState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testUpdateBreakpoint() { // Description: user might insert breakpoints using lldb console. model should // pick up the manually set breakpoint auto *session = new TestDebugSession; TestLaunchConfiguration cfg; // break at line 29 auto b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 28); QCOMPARE(breakpoints()->rowCount(), 1); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // stop at line 29 session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // stop after step QCOMPARE(session->currentLine(), 23-1); // at the beginning of foo():23: ++i; session->addUserCommand(QStringLiteral("break set --file %1 --line %2").arg(m_debugeeFileName).arg(33)); WAIT_FOR_A_WHILE(session, 20); QCOMPARE(breakpoints()->rowCount(), 2); b = breakpoints()->breakpoint(1); QCOMPARE(b->url(), QUrl::fromLocalFile(m_debugeeFileName)); QCOMPARE(b->line(), 33-1); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // stop at line 25 QCOMPARE(session->currentLine(), 33-1); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testIgnoreHitsBreakpoint() { QSKIP("Skipping... lldb-mi doesn't provide breakpoint hit count update"); auto *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint * b1 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 21); b1->setIgnoreHits(1); KDevelop::Breakpoint * b2 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 22); QVERIFY(session->startDebugging(&cfg, m_iface)); //WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR(session, session->state() == DebugSession::PausedState && b2->hitCount() == 1); b2->setIgnoreHits(1); session->run(); //WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR(session, session->state() == DebugSession::PausedState && b1->hitCount() == 1); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testConditionBreakpoint() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; auto b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 39); b->setCondition(QStringLiteral("x[0] == 'H'")); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 23); b->setCondition(QStringLiteral("i==2")); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR(session, session->state() == DebugSession::PausedState && session->currentLine() == 24); b->setCondition(QStringLiteral("i == 0")); WAIT_FOR_A_WHILE(session, 100); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 39); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBreakOnWriteBreakpoint() { QSKIP("Skipping... lldb-mi doesn't have proper watchpoint support"); auto *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); breakpoints()->addWatchpoint(QStringLiteral("i")); WAIT_FOR_A_WHILE(session, 100); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBreakOnWriteWithConditionBreakpoint() { QSKIP("Skipping... lldb-mi doesn't have proper watchpoint support"); auto *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); KDevelop::Breakpoint *b = breakpoints()->addWatchpoint(QStringLiteral("i")); b->setCondition(QStringLiteral("i==2")); QTest::qWait(100); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBreakOnReadBreakpoint() { QSKIP("Skipping... lldb-mi doesn't have proper watchpoint support"); auto *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addReadWatchpoint(QStringLiteral("foo::i")); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBreakOnReadBreakpoint2() { QSKIP("Skipping... lldb-mi doesn't have proper watchpoint support"); auto *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); breakpoints()->addReadWatchpoint(QStringLiteral("i")); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 22); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBreakOnAccessBreakpoint() { QSKIP("Skipping... lldb-mi doesn't have proper watchpoint support"); auto *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); breakpoints()->addAccessWatchpoint(QStringLiteral("i")); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 22); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testInsertBreakpointWhileRunning() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeeslow"))); QString fileName = findSourceFile("debugeeslow.cpp"); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::ActiveState); WAIT_FOR_A_WHILE(session, 2000); qDebug() << "adding breakpoint"; KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); WAIT_FOR_A_WHILE(session, 500); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); WAIT_FOR_A_WHILE(session, 500); QCOMPARE(session->currentLine(), 25); b->setDeleted(); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testInsertBreakpointWhileRunningMultiple() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeeslow"))); QString fileName = findSourceFile("debugeeslow.cpp"); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::ActiveState); WAIT_FOR_A_WHILE(session, 2000); qDebug() << "adding breakpoint"; auto b1 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 24); auto b2 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); WAIT_FOR_A_WHILE(session, 500); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); WAIT_FOR_A_WHILE(session, 500); QCOMPARE(session->currentLine(), 24); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); WAIT_FOR_A_WHILE(session, 500); QCOMPARE(session->currentLine(), 25); b1->setDeleted(); b2->setDeleted(); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testInsertBreakpointFunctionName() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QStringLiteral("main")); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 27); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testManualBreakpoint() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QStringLiteral("main")); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 27); breakpoints()->removeRows(0, 1); WAIT_FOR_A_WHILE(session, 100); QCOMPARE(breakpoints()->rowCount(), 0); session->addCommand(MI::NonMI, QStringLiteral("break set --file debugee.cpp --line 23")); WAIT_FOR_A_WHILE(session, 100); QCOMPARE(breakpoints()->rowCount(), 1); auto b = breakpoints()->breakpoint(0); QCOMPARE(b->line(), 22); session->addCommand(MI::NonMI, QStringLiteral("break disable 2")); session->addCommand(MI::NonMI, QStringLiteral("break modify -c 'i == 1' 2")); session->addCommand(MI::NonMI, QStringLiteral("break modify -i 1 2")); WAIT_FOR_A_WHILE(session, 1000); QCOMPARE(b->enabled(), false); QEXPECT_FAIL("", "LLDB 4.0 does not report condition in mi response", Continue); QCOMPARE(b->condition(), QString("i == 1")); QEXPECT_FAIL("", "LLDB 4.0 does not report ignore hits in mi response", Continue); QCOMPARE(b->ignoreHits(), 1); session->addCommand(MI::NonMI, QStringLiteral("break delete 2")); WAIT_FOR_A_WHILE(session, 100); QEXPECT_FAIL("", "LLDB 4.0 does not report breakpoint deletion as mi notification", Continue); QCOMPARE(breakpoints()->rowCount(), 0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } //Bug 201771 void LldbTest::testInsertAndRemoveBreakpointWhileRunning() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeeslow"))); QString fileName = findSourceFile("debugeeslow.cpp"); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::ActiveState); WAIT_FOR_A_WHILE(session, 1000); KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); WAIT_FOR_A_WHILE(session, 200); // wait for feedback notification from lldb-mi b->setDeleted(); WAIT_FOR_A_WHILE(session, 3000); // give slow debugee extra time to run WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testPickupManuallyInsertedBreakpoint() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QStringLiteral("main")); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->addCommand(MI::NonMI, QStringLiteral("break set --file debugee.cpp --line 32")); session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 2); QCOMPARE(breakpoints()->rowCount(), 2); KDevelop::Breakpoint *b = breakpoints()->breakpoint(1); QVERIFY(b); QCOMPARE(b->line(), 31); //we start with 0, gdb with 1 QCOMPARE(b->url().fileName(), QString("debugee.cpp")); } //Bug 270970 void LldbTest::testPickupManuallyInsertedBreakpointOnlyOnce() { auto *session = new TestDebugSession; QString sourceFile = findSourceFile("debugee.cpp"); //inject here, so it behaves similar like a command from .lldbinit QTemporaryFile configScript; configScript.open(); configScript.write(QStringLiteral("break set --file %0 --line 32\n").arg(sourceFile).toLocal8Bit()); configScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(Config::LldbConfigScriptEntry, QUrl::fromLocalFile(configScript.fileName())); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(QStringLiteral("debugee.cpp")), 31); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 1); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBreakpointWithSpaceInPath() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeespace"))); KConfigGroup grp = cfg.config(); QString fileName = findSourceFile("debugee space.cpp"); KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 20); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 20); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBreakpointDisabledOnStart() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; auto b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 23); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 29); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 34); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QEXPECT_FAIL("", "See LLDB bug 28703: -d flag has no effect", Abort); QCOMPARE(session->currentLine(), 29); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Checked); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 34); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testMultipleLocationsBreakpoint() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeemultilocbreakpoint"))); breakpoints()->addCodeBreakpoint(QStringLiteral("aPlusB")); //TODO check if the additional location breakpoint is added QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 19); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testMultipleBreakpoint() { auto *session = new TestDebugSession; //there'll be about 3-4 breakpoints, but we treat it like one. TestLaunchConfiguration c(findExecutable(QStringLiteral("debuggee_debugeemultiplebreakpoint"))); auto b = breakpoints()->addCodeBreakpoint(QStringLiteral("debugeemultiplebreakpoint.cpp:52")); QVERIFY(session->startDebugging(&c, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 1); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testRegularExpressionBreakpoint() { QSKIP("Skipping... lldb has only one breakpoint for multiple locations" " (and lldb-mi returns the first one), not support this yet"); auto *session = new TestDebugSession; TestLaunchConfiguration c(findExecutable(QStringLiteral("debuggee_debugeemultilocbreakpoint"))); breakpoints()->addCodeBreakpoint(QStringLiteral("main")); QVERIFY(session->startDebugging(&c, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->addCommand(MI::NonMI, QStringLiteral("break set --func-regex .*aPl.*B")); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 3); session->addCommand(MI::BreakDelete, QString()); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testChangeBreakpointWhileRunning() { QSKIP("Skipping... lldb-mi command -break-enable doesn't enable breakpoint"); auto *session = new TestDebugSession; TestLaunchConfiguration c(findExecutable(QStringLiteral("debuggee_debugeeslow"))); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint(QStringLiteral("debugeeslow.cpp:25")); QVERIFY(session->startDebugging(&c, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QVERIFY(session->currentLine() >= 24 && session->currentLine() <= 26 ); session->run(); WAIT_FOR_STATE(session, DebugSession::ActiveState); qDebug() << "Disabling breakpoint"; b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //to make one loop WAIT_FOR_A_WHILE(session, 2500); qDebug() << "Waiting for active"; WAIT_FOR_STATE(session, DebugSession::ActiveState); qDebug() << "Enabling breakpoint"; // Use native user command works, but not through -break-enable, which is triggered by setData session->addCommand(MI::NonMI, QStringLiteral("break enable")); //b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Checked); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testCatchpoint() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeeexception"))); session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestFrameStackModel* fsModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("debugeeexception.cpp")), 29); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(fsModel->currentFrame(), 0); QCOMPARE(session->currentLine(), 29); session->addCommand(MI::NonMI, QStringLiteral("break set -E c++")); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); const auto frames = fsModel->frames(fsModel->currentThread()); QVERIFY(frames.size() >= 2); // frame 0 is somewhere inside libstdc++ QCOMPARE(frames[1].file, QUrl::fromLocalFile(findSourceFile("debugeeexception.cpp"))); QCOMPARE(frames[1].line, 22); QCOMPARE(breakpoints()->rowCount(),2); QVERIFY(!breakpoints()->breakpoint(0)->location().isEmpty()); QVERIFY(!breakpoints()->breakpoint(1)->location().isEmpty()); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testShowStepInSource() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; QSignalSpy showStepInSourceSpy(session, &TestDebugSession::showStepInSource); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 29); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); { QCOMPARE(showStepInSourceSpy.count(), 3); QList arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().toUrl(), QUrl::fromLocalFile(m_debugeeFileName)); QCOMPARE(arguments.at(1).toInt(), 29); arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().toUrl(), QUrl::fromLocalFile(m_debugeeFileName)); QCOMPARE(arguments.at(1).toInt(), 22); arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().toUrl(), QUrl::fromLocalFile(m_debugeeFileName)); QCOMPARE(arguments.at(1).toInt(), 23); } } void LldbTest::testStack() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 21); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo()"); QCOMPARE(stackModel->rowCount(tIdx), 4); QCOMPARE(stackModel->columnCount(tIdx), 3); COMPARE_DATA(stackModel->index(0, 0, tIdx), "0"); COMPARE_DATA(stackModel->index(0, 1, tIdx), "foo()"); COMPARE_DATA(stackModel->index(0, 2, tIdx), m_debugeeFileName+":23"); COMPARE_DATA(stackModel->index(1, 0, tIdx), "1"); COMPARE_DATA(stackModel->index(1, 1, tIdx), "main"); COMPARE_DATA(stackModel->index(1, 2, tIdx), m_debugeeFileName+":29"); COMPARE_DATA(stackModel->index(2, 0, tIdx), "2"); COMPARE_DATA(stackModel->index(2, 1, tIdx), "__libc_start_main"); COMPARE_DATA(stackModel->index(3, 0, tIdx), "3"); COMPARE_DATA(stackModel->index(3, 1, tIdx), "_start"); session->stepOut(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 3); COMPARE_DATA(stackModel->index(0, 0, tIdx), "0"); COMPARE_DATA(stackModel->index(0, 1, tIdx), "main"); COMPARE_DATA(stackModel->index(0, 2, tIdx), m_debugeeFileName+":30"); COMPARE_DATA(stackModel->index(1, 0, tIdx), "1"); COMPARE_DATA(stackModel->index(1, 1, tIdx), "__libc_start_main"); COMPARE_DATA(stackModel->index(2, 0, tIdx), "2"); COMPARE_DATA(stackModel->index(2, 1, tIdx), "_start"); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testStackFetchMore() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeerecursion"))); QString fileName = findSourceFile("debugeerecursion.cpp"); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->frameStackModel()->fetchFramesCalled, 1); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo()"); QCOMPARE(stackModel->rowCount(tIdx), 21); COMPARE_DATA(stackModel->index(0, 0, tIdx), "0"); COMPARE_DATA(stackModel->index(0, 1, tIdx), "foo()"); COMPARE_DATA(stackModel->index(0, 2, tIdx), fileName+":26"); COMPARE_DATA(stackModel->index(1, 0, tIdx), "1"); COMPARE_DATA(stackModel->index(1, 1, tIdx), "foo()"); COMPARE_DATA(stackModel->index(1, 2, tIdx), fileName+":24"); COMPARE_DATA(stackModel->index(2, 0, tIdx), "2"); COMPARE_DATA(stackModel->index(2, 1, tIdx), "foo()"); COMPARE_DATA(stackModel->index(2, 2, tIdx), fileName+":24"); COMPARE_DATA(stackModel->index(19, 0, tIdx), "19"); COMPARE_DATA(stackModel->index(20, 0, tIdx), "20"); stackModel->fetchMoreFrames(); WAIT_FOR_A_WHILE(session, 200); QCOMPARE(stackModel->fetchFramesCalled, 2); QCOMPARE(stackModel->rowCount(tIdx), 41); COMPARE_DATA(stackModel->index(20, 0, tIdx), "20"); COMPARE_DATA(stackModel->index(21, 0, tIdx), "21"); COMPARE_DATA(stackModel->index(22, 0, tIdx), "22"); COMPARE_DATA(stackModel->index(39, 0, tIdx), "39"); COMPARE_DATA(stackModel->index(40, 0, tIdx), "40"); stackModel->fetchMoreFrames(); WAIT_FOR_A_WHILE(session, 200); QCOMPARE(stackModel->fetchFramesCalled, 3); QCOMPARE(stackModel->rowCount(tIdx), 121); COMPARE_DATA(stackModel->index(40, 0, tIdx), "40"); COMPARE_DATA(stackModel->index(41, 0, tIdx), "41"); COMPARE_DATA(stackModel->index(42, 0, tIdx), "42"); COMPARE_DATA(stackModel->index(119, 0, tIdx), "119"); COMPARE_DATA(stackModel->index(120, 0, tIdx), "120"); stackModel->fetchMoreFrames(); WAIT_FOR_A_WHILE(session, 200); QCOMPARE(stackModel->fetchFramesCalled, 4); QCOMPARE(stackModel->rowCount(tIdx), 301); COMPARE_DATA(stackModel->index(120, 0, tIdx), "120"); COMPARE_DATA(stackModel->index(121, 0, tIdx), "121"); COMPARE_DATA(stackModel->index(122, 0, tIdx), "122"); COMPARE_DATA(stackModel->index(298, 0, tIdx), "298"); COMPARE_DATA(stackModel->index(298, 1, tIdx), "main"); COMPARE_DATA(stackModel->index(298, 2, tIdx), fileName+":30"); COMPARE_DATA(stackModel->index(299, 0, tIdx), "299"); COMPARE_DATA(stackModel->index(299, 1, tIdx), "__libc_start_main"); COMPARE_DATA(stackModel->index(300, 0, tIdx), "300"); COMPARE_DATA(stackModel->index(300, 1, tIdx), "_start"); stackModel->fetchMoreFrames(); //nothing to fetch, we are at the end WAIT_FOR_A_WHILE(session, 200); QCOMPARE(stackModel->fetchFramesCalled, 4); QCOMPARE(stackModel->rowCount(tIdx), 301); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testStackDeactivateAndActive() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 21); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QModelIndex tIdx = stackModel->index(0,0); session->stepOut(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 3); COMPARE_DATA(stackModel->index(0, 0, tIdx), "0"); COMPARE_DATA(stackModel->index(0, 1, tIdx), "main"); COMPARE_DATA(stackModel->index(0, 2, tIdx), m_debugeeFileName+":30"); COMPARE_DATA(stackModel->index(1, 0, tIdx), "1"); COMPARE_DATA(stackModel->index(1, 1, tIdx), "__libc_start_main"); COMPARE_DATA(stackModel->index(2, 0, tIdx), "2"); COMPARE_DATA(stackModel->index(2, 1, tIdx), "_start"); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testStackSwitchThread() { QSKIP("Skipping... lldb-mi crashes when break at a location with multiple threads running"); auto *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeethreads"))); QString fileName = findSourceFile("debugeethreads.cpp"); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(stackModel->rowCount(), 4); QModelIndex tIdx = stackModel->index(0,0); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 1); COMPARE_DATA(stackModel->index(0, 0, tIdx), "0"); COMPARE_DATA(stackModel->index(0, 1, tIdx), "main"); COMPARE_DATA(stackModel->index(0, 2, tIdx), fileName+":39"); tIdx = stackModel->index(1,0); QVERIFY(stackModel->data(tIdx).toString().startsWith("#2 at ")); stackModel->setCurrentThread(2); WAIT_FOR_A_WHILE(session, 200); int rows = stackModel->rowCount(tIdx); QVERIFY(rows > 3); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testAttach() { SKIP_IF_ATTACH_FORBIDDEN(); QString fileName = findSourceFile("debugeeslow.cpp"); KProcess debugeeProcess; debugeeProcess << QStringLiteral("nice") << findExecutable(QStringLiteral("debuggee_debugeeslow")).toLocalFile(); debugeeProcess.start(); QVERIFY(debugeeProcess.waitForStarted()); QTest::qWait(100); auto *session = new TestDebugSession; session->attachToProcess(debugeeProcess.pid()); WAIT_FOR_A_WHILE(session, 100); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 35); // lldb-mi silently stops when attaching to a process. Force it continue to run. session->addCommand(MI::ExecContinue, QString(), MI::CmdMaybeStartsRunning); WAIT_FOR_A_WHILE(session, 2000); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 35); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testRemoteDebugging() { KProcess gdbServer; gdbServer << QStringLiteral("lldb-server") << QStringLiteral("gdbserver") << QStringLiteral("*:1234"); gdbServer.start(); QVERIFY(gdbServer.waitForStarted()); auto *session = new TestDebugSession; TestLaunchConfiguration cfg; cfg.config().writeEntry(Config::LldbRemoteDebuggingEntry, true); cfg.config().writeEntry(Config::LldbRemoteServerEntry, "localhost:1234"); cfg.config().writeEntry(Config::LldbRemotePathEntry, "/tmp"); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 34); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testCoreFile() { QFileInfo f(QStringLiteral("core")); f.setCaching(false); // don't cache information if (f.exists()) { QVERIFY(QFile::remove(f.canonicalFilePath())); } KProcess debugeeProcess; debugeeProcess.setOutputChannelMode(KProcess::MergedChannels); debugeeProcess << QStringLiteral("bash") << QStringLiteral("-c") << "ulimit -c unlimited; " + findExecutable(QStringLiteral("debuggee_crash")).toLocalFile(); debugeeProcess.start(); debugeeProcess.waitForFinished(); qDebug() << debugeeProcess.readAll(); bool coreFileFound = f.exists(); if (!coreFileFound) { // Try to use coredumpctl qDebug() << "try to use coredumpctl"; auto coredumpctl = QStandardPaths::findExecutable(QStringLiteral("coredumpctl")); if (!coredumpctl.isEmpty()) { KProcess::execute(coredumpctl, {"-1", "-o", f.absoluteFilePath(), "dump", "debuggee_crash"}); // coredumpctl seems to create an empty file "core" even if no cores can be delivered // (like when run inside docker containers as on KDE CI or with kernel.core_pattern=|/dev/null) // so also check for size != 0 coreFileFound = f.exists() && (f.size() > 0); } } if (!coreFileFound) QSKIP("no core dump found, check your system configuration (see /proc/sys/kernel/core_pattern).", SkipSingle); auto *session = new TestDebugSession; session->examineCoreFile(findExecutable(QStringLiteral("debuggee_crash")), QUrl::fromLocalFile(f.canonicalFilePath())); TestFrameStackModel *stackModel = session->frameStackModel(); WAIT_FOR_STATE(session, DebugSession::StoppedState); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo()"); session->stopDebugger(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesLocals() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(IVariableController::UpdateLocals); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(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), "j"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); COMPARE_DATA(variableCollection()->index(0, 0, i), "j"); COMPARE_DATA(variableCollection()->index(0, 1, i), "2"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesLocalsStruct() { auto *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR_A_WHILE(session, 1000); QModelIndex i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); int structIndex = 0; for(int j=0; j<3; ++j) { if (variableCollection()->index(j, 0, i).data().toString() == QLatin1String("ts")) { structIndex = j; } } COMPARE_DATA(variableCollection()->index(structIndex, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(structIndex, 1, i), "{...}"); QModelIndex ts = variableCollection()->index(structIndex, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, ts), "..."); variableCollection()->expanded(ts); WAIT_FOR_A_WHILE(session, 100); COMPARE_DATA(variableCollection()->index(0, 0, ts), "a"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "0"); COMPARE_DATA(variableCollection()->index(1, 0, ts), "b"); COMPARE_DATA(variableCollection()->index(1, 1, ts), "1"); COMPARE_DATA(variableCollection()->index(2, 0, ts), "c"); COMPARE_DATA(variableCollection()->index(2, 1, ts), "2"); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR_A_WHILE(session, 1000); COMPARE_DATA(variableCollection()->index(structIndex, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(structIndex, 1, i), "{...}"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesWatches() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; m_core->debugController()->variableCollection()->variableWidgetShown(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add(QStringLiteral("ts")); WAIT_FOR_A_WHILE(session, 300); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(0, 1, i), "{...}"); QModelIndex ts = variableCollection()->index(0, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, ts), "..."); variableCollection()->expanded(ts); WAIT_FOR_A_WHILE(session, 100); COMPARE_DATA(variableCollection()->index(0, 0, ts), "a"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "0"); COMPARE_DATA(variableCollection()->index(1, 0, ts), "b"); COMPARE_DATA(variableCollection()->index(1, 1, ts), "1"); COMPARE_DATA(variableCollection()->index(2, 0, ts), "c"); COMPARE_DATA(variableCollection()->index(2, 1, ts), "2"); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR_A_WHILE(session, 100); COMPARE_DATA(variableCollection()->index(0, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(0, 1, i), "{...}"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesWatchesQuotes() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); // the unquoted string (the actual content): t\"t // quoted string (what we would write as a c string): "t\\\"t" // written in source file: R"("t\\\"t")" const QString testString(QStringLiteral("t\\\"t")); // the actual content const QString quotedTestString(QStringLiteral(R"("t\\\"t")")); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add(quotedTestString); //just a constant string WAIT_FOR_A_WHILE(session, 3000); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), quotedTestString); QEXPECT_FAIL("", "LLDB 4.0 cannot deal with string literal in expression when debugging, causing memory access error", Abort); COMPARE_DATA(variableCollection()->index(0, 1, i), quotedTestString); QModelIndex testStr = variableCollection()->index(0, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, testStr), "..."); variableCollection()->expanded(testStr); WAIT_FOR_A_WHILE(session, 100); int len = testString.length(); for (int ind = 0; ind < len; ind++) { COMPARE_DATA(variableCollection()->index(ind, 0, testStr), QStringLiteral("[%0]").arg(ind)); QChar c = testString.at(ind); QString value = QString::number(c.toLatin1()) + " '" + c + "'"; COMPARE_DATA(variableCollection()->index(ind, 1, testStr), value); } COMPARE_DATA(variableCollection()->index(len, 0, testStr), QStringLiteral("[%0]").arg(len)); COMPARE_DATA(variableCollection()->index(len, 1, testStr), "0 '\\0'"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesWatchesTwoSessions() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); variableCollection()->watches()->add(QStringLiteral("ts")); WAIT_FOR_A_WHILE(session, 300); QModelIndex ts = variableCollection()->index(0, 0, variableCollection()->index(0, 0)); variableCollection()->expanded(ts); WAIT_FOR_A_WHILE(session, 100); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //check if variable is marked as out-of-scope QCOMPARE(variableCollection()->watches()->childCount(), 1); auto v = qobject_cast(watchVariableAt(0)); QVERIFY(v); QVERIFY(!v->inScope()); QCOMPARE(v->childCount(), 3); v = qobject_cast(v->child(0)); QVERIFY(!v->inScope()); //start a second debug session session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(variableCollection()->watches()->childCount(), 1); ts = variableCollection()->index(0, 0, variableCollection()->index(0, 0)); v = qobject_cast(watchVariableAt(0)); QVERIFY(v); QVERIFY(v->inScope()); QCOMPARE(v->childCount(), 3); v = qobject_cast(v->child(0)); QVERIFY(v->inScope()); COMPARE_DATA(variableCollection()->indexForItem(v, 1), QString::number(0)); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //check if variable is marked as out-of-scope v = qobject_cast(watchVariableAt(0)); QVERIFY(!v->inScope()); QVERIFY(!qobject_cast(v->child(0))->inScope()); } void LldbTest::testVariablesStopDebugger() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->stopDebugger(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesStartSecondSession() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesSwitchFrame() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "j"); // only non-static variable works COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); stackModel->setCurrentFrame(1); WAIT_FOR_A_WHILE(session, 200); i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); COMPARE_DATA(variableCollection()->index(2, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(2, 1, i), "1"); COMPARE_DATA(variableCollection()->index(3, 0, i), "argv"); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesQuicklySwitchFrame() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "j"); // only non-static variable works COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); stackModel->setCurrentFrame(1); WAIT_FOR_A_WHILE(session, 300); stackModel->setCurrentFrame(0); WAIT_FOR_A_WHILE(session, 1); stackModel->setCurrentFrame(1); WAIT_FOR_A_WHILE(session, 1); stackModel->setCurrentFrame(0); WAIT_FOR_A_WHILE(session, 1); stackModel->setCurrentFrame(1); WAIT_FOR_A_WHILE(session, 500); i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); QStringList locs; for (int j = 0; j < variableCollection()->rowCount(i); ++j) { locs << variableCollection()->index(j, 0, i).data().toString(); } QVERIFY(locs.contains("argc")); QVERIFY(locs.contains("argv")); QVERIFY(locs.contains("x")); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testSwitchFrameLldbConsole() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(stackModel->currentFrame(), 0); stackModel->setCurrentFrame(1); QCOMPARE(stackModel->currentFrame(), 1); WAIT_FOR_A_WHILE(session, 500); QCOMPARE(stackModel->currentFrame(), 1); session->addUserCommand(QStringLiteral("print i")); WAIT_FOR_A_WHILE(session, 500); //currentFrame must not reset to 0; Bug 222882 QCOMPARE(stackModel->currentFrame(), 1); } void LldbTest::testSegfaultDebugee() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_crash"))); session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); QString fileName = findSourceFile("debugeecrash.cpp"); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 23); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); session->stopDebugger(); WAIT_FOR_STATE(session, DebugSession::EndedState); } //Bug 274390 void LldbTest::testCommandOrderFastStepping() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeeqt"))); breakpoints()->addCodeBreakpoint(QStringLiteral("main")); QVERIFY(session->startDebugging(&cfg, m_iface)); for(int i=0; i<20; i++) { session->stepInto(); } WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testRunLldbScript() { auto *session = new TestDebugSession; QTemporaryFile runScript; runScript.open(); runScript.write(QStringLiteral("break set --file %1 --line 35\n").arg(findSourceFile("debugee.cpp")).toUtf8()); runScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(Config::LldbConfigScriptEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 35); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBug301287() { auto *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 28); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); variableCollection()->watches()->add(QStringLiteral("argc")); WAIT_FOR_A_WHILE(session, 300); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //start second debug session (same cfg) session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testDebugInExternalTerminal() { TestLaunchConfiguration cfg; const QStringList consoles { "konsole", "xterm", "xfce4-terminal", "gnome-terminal" }; for (const QString& console : consoles) { if (QStandardPaths::findExecutable(console).isEmpty()) { continue; } auto* session = new TestDebugSession(); cfg.config().writeEntry("External Terminal"/*ExecutePlugin::terminalEntry*/, console); cfg.config().writeEntry("Use External Terminal"/*ExecutePlugin::useTerminalEntry*/, true); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 28); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } } void LldbTest::testSpecialPath() { QSKIP("Skipping... lldb-mi itself can't handle path with space in application dir"); auto* session = new TestDebugSession; auto debugee = findExecutable(QStringLiteral("path with space/debuggee_spacedebugee")); TestLaunchConfiguration c(debugee, KIO::upUrl(debugee)); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint(QStringLiteral("spacedebugee.cpp:30")); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); QVERIFY(session->startDebugging(&c, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void KDevMI::LLDB::LldbTest::testEnvironmentCd() { auto *session = new TestDebugSession; QSignalSpy outputSpy(session, &TestDebugSession::inferiorStdoutLines); auto path = KIO::upUrl(findExecutable(QStringLiteral("path with space/debuggee_spacedebugee"))); TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeepath")), path); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, KDevelop::IDebugSession::EndedState); QVERIFY(outputSpy.count() > 0); QStringList outputLines; while (outputSpy.count() > 0) { QList arguments = outputSpy.takeFirst(); for (const auto &item : arguments) { outputLines.append(item.toStringList()); } } QCOMPARE(outputLines, QStringList() << path.toLocalFile()); } QTEST_MAIN(KDevMI::LLDB::LldbTest) #include "test_lldb.moc" diff --git a/plugins/scratchpad/emptymessagelistview.h b/plugins/scratchpad/emptymessagelistview.h index 6c8ee45f6b..f41478b2d5 100644 --- a/plugins/scratchpad/emptymessagelistview.h +++ b/plugins/scratchpad/emptymessagelistview.h @@ -1,38 +1,40 @@ /* This file is part of KDevelop * * Copyright 2018 Amish K. Naidu * * 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 EMPTYMESSAGELISTVIEW_H #define EMPTYMESSAGELISTVIEW_H #include // subclass to show a message when the list is empty class EmptyMessageListView : public QListView { + Q_OBJECT + public: EmptyMessageListView(QWidget* parent); void setEmptyMessage(const QString& message); protected: void paintEvent(QPaintEvent* event) override; private: QString m_message; }; #endif // EMPTYMESSAGELISTVIEW_H diff --git a/plugins/scratchpad/scratchpadview.cpp b/plugins/scratchpad/scratchpadview.cpp index 77fc975302..99c84160e4 100644 --- a/plugins/scratchpad/scratchpadview.cpp +++ b/plugins/scratchpad/scratchpadview.cpp @@ -1,230 +1,235 @@ /* This file is part of KDevelop * * Copyright 2018 Amish K. Naidu * * 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 "scratchpadview.h" #include "scratchpad.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Use a delegate because the dataChanged signal doesn't tell us the previous name class FileRenameDelegate : public QStyledItemDelegate { + Q_OBJECT + public: FileRenameDelegate(QObject* parent, Scratchpad* scratchpad) : QStyledItemDelegate(parent) , m_scratchpad(scratchpad) { } void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override { const QString previousName = index.data().toString(); QStyledItemDelegate::setModelData(editor, model, index); const auto* proxyModel = static_cast(model); m_scratchpad->renameScratch(proxyModel->mapToSource(index), previousName); } private: Scratchpad* m_scratchpad; }; EmptyMessageListView::EmptyMessageListView(QWidget* parent) : QListView(parent) { } void EmptyMessageListView::paintEvent(QPaintEvent* event) { if (model() && model()->rowCount(rootIndex()) > 0) { QListView::paintEvent(event); } else { QPainter painter(viewport()); const auto margin = QMargins(parentWidget()->style()->pixelMetric(QStyle::PM_LayoutLeftMargin), 0, parentWidget()->style()->pixelMetric(QStyle::PM_LayoutRightMargin), 0); painter.drawText(rect() - margin, Qt::AlignCenter | Qt::TextWordWrap, m_message); } } void EmptyMessageListView::setEmptyMessage(const QString& message) { m_message = message; } ScratchpadView::ScratchpadView(QWidget* parent, Scratchpad* scratchpad) : QWidget(parent) , m_scratchpad(scratchpad) { setupUi(this); setupActions(); setWindowTitle(i18n("Scratchpad")); setWindowIcon(QIcon::fromTheme(QStringLiteral("note"))); auto* const modelProxy = new QSortFilterProxyModel(this); modelProxy->setSourceModel(m_scratchpad->model()); modelProxy->setFilterCaseSensitivity(Qt::CaseInsensitive); modelProxy->setSortCaseSensitivity(Qt::CaseInsensitive); modelProxy->setSortRole(Qt::DisplayRole); connect(m_filter, &QLineEdit::textEdited, modelProxy, &QSortFilterProxyModel::setFilterWildcard); scratchView->setModel(modelProxy); scratchView->setItemDelegate(new FileRenameDelegate(this, m_scratchpad)); scratchView->setEmptyMessage(i18n("Scratchpad lets you quickly run and experiment with code without a full project, and even store todos. Create a new scratch to start.")); connect(scratchView, &QListView::activated, this, &ScratchpadView::scratchActivated); connect(m_scratchpad, &Scratchpad::actionFailed, [this](const QString& message) { KMessageBox::sorry(this, message); }); connect(commandWidget, &QLineEdit::returnPressed, this, &ScratchpadView::runSelectedScratch); connect(commandWidget, &QLineEdit::returnPressed, [this] { m_scratchpad->setCommand(proxyModel()->mapToSource(currentIndex()), commandWidget->text()); }); commandWidget->setToolTip(i18n("Command to run this scratch. $f will expand to the scratch path")); commandWidget->setPlaceholderText(commandWidget->toolTip()); // change active scratch when changing document connect(KDevelop::ICore::self()->documentController(), &KDevelop::IDocumentController::documentActivated, [this](const KDevelop::IDocument* document) { if (document->url().isLocalFile()) { const auto* model = scratchView->model(); const auto index = model->match(model->index(0, 0), Scratchpad::FullPathRole, document->url().toLocalFile()).value({}); if (index.isValid()) { scratchView->setCurrentIndex(index); } } }); connect(scratchView, &QAbstractItemView::pressed, this, &ScratchpadView::validateItemActions); validateItemActions(); } void ScratchpadView::setupActions() { QAction* action = new QAction(QIcon::fromTheme(QStringLiteral("list-add")), i18n("New Scratch"), this); connect(action, &QAction::triggered, this, &ScratchpadView::createScratch); addAction(action); action = new QAction(QIcon::fromTheme(QStringLiteral("list-remove")), i18n("Remove Scratch"), this); connect(action, &QAction::triggered, [this] { m_scratchpad->removeScratch(proxyModel()->mapToSource(currentIndex())); validateItemActions(); }); addAction(action); m_itemActions.push_back(action); action = new QAction(QIcon::fromTheme(QStringLiteral("edit-rename")), i18n("Rename Scratch"), this); connect(action, &QAction::triggered, [this] { scratchView->edit(scratchView->currentIndex()); }); addAction(action); m_itemActions.push_back(action); action = m_scratchpad->runAction(); action->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start"))); action->setText(i18n("Run Scratch")); connect(action, &QAction::triggered, this, &ScratchpadView::runSelectedScratch); addAction(action); m_itemActions.push_back(action); m_filter = new QLineEdit(this); m_filter->setPlaceholderText(i18n("Filter...")); auto filterAction = new QWidgetAction(this); filterAction->setDefaultWidget(m_filter); addAction(filterAction); } void ScratchpadView::validateItemActions() { bool enable = currentIndex().isValid(); for (auto* action : qAsConst(m_itemActions)) { action->setEnabled(enable); } commandWidget->setReadOnly(!enable); if (!enable) { commandWidget->clear(); } commandWidget->setText(currentIndex().data(Scratchpad::RunCommandRole).toString()); } void ScratchpadView::runSelectedScratch() { const auto sourceIndex = proxyModel()->mapToSource(currentIndex()); if (auto* document = KDevelop::ICore::self()->documentController()->documentForUrl( QUrl::fromLocalFile(sourceIndex.data(Scratchpad::FullPathRole).toString()))) { document->save(); } m_scratchpad->setCommand(sourceIndex, commandWidget->text()); m_scratchpad->runScratch(sourceIndex); } void ScratchpadView::scratchActivated(const QModelIndex& index) { validateItemActions(); m_scratchpad->openScratch(proxyModel()->mapToSource(index)); } void ScratchpadView::createScratch() { QString name = QInputDialog::getText(this, i18n("Create New Scratch"), i18n("Enter name for scratch file:"), QLineEdit::Normal, QStringLiteral("example.cpp")); if (!name.isEmpty()) { m_scratchpad->createScratch(name); } } QAbstractProxyModel* ScratchpadView::proxyModel() const { return static_cast(scratchView->model()); } QModelIndex ScratchpadView::currentIndex() const { return scratchView->currentIndex(); } + +#include "scratchpadview.moc" +#include "moc_emptymessagelistview.cpp"