diff --git a/plugins/debuggercommon/tests/CMakeLists.txt b/plugins/debuggercommon/tests/CMakeLists.txt --- a/plugins/debuggercommon/tests/CMakeLists.txt +++ b/plugins/debuggercommon/tests/CMakeLists.txt @@ -13,3 +13,15 @@ PRIVATE Qt5::Test ) + +ecm_add_test(test_miparser + LINK_LIBRARIES Qt5::Test kdevdbg_testhelper +) + +ecm_add_test(test_micommand + LINK_LIBRARIES Qt5::Test kdevdbg_testhelper +) + +ecm_add_test(test_micommandqueue + LINK_LIBRARIES Qt5::Test kdevdbg_testhelper +) diff --git a/plugins/debuggercommon/tests/test_micommand.h b/plugins/debuggercommon/tests/test_micommand.h new file mode 100644 --- /dev/null +++ b/plugins/debuggercommon/tests/test_micommand.h @@ -0,0 +1,37 @@ +/* This file is part of KDevelop + * + * Copyright 2018 Friedrich W. H. Kossebau + * + * 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 Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef KDEV_TESTMICOMMAND_H +#define KDEV_TESTMICOMMAND_H + +#include + +class TestMICommand : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testUserCommand(); + void testMICommandHandler_data(); + void testMICommandHandler(); + void testQObjectCommandHandler(); +}; + +#endif diff --git a/plugins/debuggercommon/tests/test_micommand.cpp b/plugins/debuggercommon/tests/test_micommand.cpp new file mode 100644 --- /dev/null +++ b/plugins/debuggercommon/tests/test_micommand.cpp @@ -0,0 +1,168 @@ +/* This file is part of KDevelop + * + * Copyright 2018 Friedrich W. H. Kossebau + * + * 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 Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "test_micommand.h" + +// SUT +#include +// Qt +#include + +class TestCommandHandler : public QObject, public KDevMI::MI::MICommandHandler +{ + Q_OBJECT +public: + explicit TestCommandHandler(bool autodelete, int expectedRecordsHandled = -1); + ~TestCommandHandler() override; + int recordsHandled() const { return m_recordsHandled; } +public: // MICommandHandler API + void handle(const KDevMI::MI::ResultRecord& record) override; + bool autoDelete() override { return m_autodelete; } +private: + bool m_autodelete; + int m_expectedRecordsHandled = 0; + int m_recordsHandled = 0; +}; + +TestCommandHandler::TestCommandHandler(bool autodelete, int expectedRecordsHandled) + : m_autodelete(autodelete) + , m_expectedRecordsHandled(expectedRecordsHandled) +{} + +TestCommandHandler::~TestCommandHandler() +{ + QCOMPARE(m_expectedRecordsHandled, m_recordsHandled); +} + +void TestCommandHandler::handle(const KDevMI::MI::ResultRecord& record) +{ + QCOMPARE(record.reason, "reason"); + + ++m_recordsHandled; +} + + + +class TestCommandResultHandler : public QObject +{ + Q_OBJECT +public: + void handleResult(const KDevMI::MI::ResultRecord& record); + int recordsHandled() const { return m_recordsHandled; } +private: + int m_recordsHandled = 0; +}; + +void TestCommandResultHandler::handleResult(const KDevMI::MI::ResultRecord& record) +{ + QCOMPARE(record.reason, "reason"); + + ++m_recordsHandled; +} + +void TestMICommand::testUserCommand() +{ + KDevMI::MI::UserCommand command(KDevMI::MI::NonMI, "command"); + command.setToken(1); + + // check + QCOMPARE(command.token(), 1); + QCOMPARE(command.frame(), -1); + QCOMPARE(command.thread(), -1); + QCOMPARE(command.isUserCommand(), true); + QCOMPARE(command.handlesError(), false); + QCOMPARE(command.flags(), KDevMI::MI::CommandFlags(KDevMI::MI::CmdMaybeStartsRunning)); + QCOMPARE(command.command(), "command"); + QCOMPARE(command.initialString(), "1command"); + QCOMPARE(command.cmdToSend(), "1command\n"); +} + +void TestMICommand::testMICommandHandler_data() +{ + QTest::addColumn("autodelete"); + + QTest::newRow("reusable") << false; + QTest::newRow("burnafterusing") << true; +} + +void TestMICommand::testMICommandHandler() +{ + QFETCH(bool, autodelete); + + KDevMI::MI::UserCommand command(KDevMI::MI::NonMI, "command"); + command.setToken(1); + + // set handle and invoke + QPointer commandHandler = new TestCommandHandler(autodelete, 1); + command.setHandler(commandHandler); + + KDevMI::MI::ResultRecord resultRecord1("reason"); + bool success = command.invokeHandler(resultRecord1); + + // check + QVERIFY(success); + QCOMPARE(commandHandler.isNull(), autodelete); + if (!autodelete) { + QCOMPARE(commandHandler->recordsHandled(), 1); + } + + // try to invoke again without handler + // ensure handler no longer exists + if (!autodelete) { + delete commandHandler; + } + + KDevMI::MI::ResultRecord resultRecord2("reason"); + success = command.invokeHandler(resultRecord2); + + // check + QVERIFY(!success); +} + +void TestMICommand::testQObjectCommandHandler() +{ + KDevMI::MI::UserCommand command(KDevMI::MI::NonMI, "command"); + command.setToken(1); + + // set handle and invoke + auto* resultHandler = new TestCommandResultHandler; + command.setHandler(resultHandler, &TestCommandResultHandler::handleResult); + + KDevMI::MI::ResultRecord resultRecord1("reason"); + bool success = command.invokeHandler(resultRecord1); + + // check + QVERIFY(success); + QCOMPARE(resultHandler->recordsHandled(), 1); + + // delete handler and try to invoke again + delete resultHandler; + + KDevMI::MI::ResultRecord resultRecord2("reason"); + success = command.invokeHandler(resultRecord2); + + // check + QVERIFY(!success); +} + + +QTEST_GUILESS_MAIN(TestMICommand) + +#include "test_micommand.moc" diff --git a/plugins/debuggercommon/tests/test_micommandqueue.h b/plugins/debuggercommon/tests/test_micommandqueue.h new file mode 100644 --- /dev/null +++ b/plugins/debuggercommon/tests/test_micommandqueue.h @@ -0,0 +1,38 @@ +/* This file is part of KDevelop + * + * Copyright 2018 Friedrich W. H. Kossebau + * + * 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 Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef KDEV_TESTMICOMMANDQUEUE_H +#define KDEV_TESTMICOMMANDQUEUE_H + +#include + +class TestMICommandQueue : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testConstructor(); + void testDestructor(); + void addAndTake_data(); + void addAndTake(); + void clearQueue(); +}; + +#endif diff --git a/plugins/debuggercommon/tests/test_micommandqueue.cpp b/plugins/debuggercommon/tests/test_micommandqueue.cpp new file mode 100644 --- /dev/null +++ b/plugins/debuggercommon/tests/test_micommandqueue.cpp @@ -0,0 +1,147 @@ +/* This file is part of KDevelop + * + * Copyright 2018 Friedrich W. H. Kossebau + * + * 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 Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "test_micommandqueue.h" + +// SUT +#include +#include +// Qt +#include +#include + +Q_DECLARE_METATYPE(KDevMI::MI::CommandFlags) + +class TestDummyCommand : public QObject, public KDevMI::MI::MICommand +{ + Q_OBJECT +public: + TestDummyCommand(KDevMI::MI::CommandType type, const QString& args = QString(), + KDevMI::MI::CommandFlags flags = {}); +}; + +TestDummyCommand::TestDummyCommand(KDevMI::MI::CommandType type, const QString& args, + KDevMI::MI::CommandFlags flags) + : KDevMI::MI::MICommand(type, args, flags) +{ +} + +void TestMICommandQueue::testConstructor() +{ + KDevMI::MI::CommandQueue commandQueue; + + // check + QCOMPARE(commandQueue.count(), 0); + QCOMPARE(commandQueue.isEmpty(), true); + QCOMPARE(commandQueue.haveImmediateCommand(), false); +} + +void TestMICommandQueue::testDestructor() +{ + auto* commandQueue = new KDevMI::MI::CommandQueue; + + // prepare + auto* command1 = new TestDummyCommand(KDevMI::MI::NonMI, QString(), KDevMI::MI::CmdImmediately); + auto* command2 = new TestDummyCommand(KDevMI::MI::NonMI, QString(), {}); + + QSignalSpy command1Spy(command1, &QObject::destroyed); + QSignalSpy command2Spy(command2, &QObject::destroyed); + + commandQueue->enqueue(command1); + commandQueue->enqueue(command2); + + // execute + delete commandQueue; + + // check + QCOMPARE(command1Spy.count(), 1); + QCOMPARE(command2Spy.count(), 1); +} + +void TestMICommandQueue::addAndTake_data() +{ + QTest::addColumn("flags"); + QTest::addColumn("isImmediate"); + + QTest::newRow("none") + << KDevMI::MI::CommandFlags() << false; + QTest::newRow("MaybeStartsRunning") + << KDevMI::MI::CommandFlags(KDevMI::MI::CmdMaybeStartsRunning) << false; + QTest::newRow("Immediately") + << KDevMI::MI::CommandFlags(KDevMI::MI::CmdImmediately) << true; + QTest::newRow("Interrupt") + << KDevMI::MI::CommandFlags(KDevMI::MI::CmdInterrupt) << true; +} + +void TestMICommandQueue::addAndTake() +{ + QFETCH(KDevMI::MI::CommandFlags, flags); + QFETCH(bool, isImmediate); + + KDevMI::MI::CommandQueue commandQueue; + + auto* command = new TestDummyCommand(KDevMI::MI::NonMI, QString(), flags); + + // add + commandQueue.enqueue(command); + // check + QVERIFY(command->token() != 0); + QCOMPARE(commandQueue.count(), 1); + QCOMPARE(commandQueue.isEmpty(), false); + QCOMPARE(commandQueue.haveImmediateCommand(), isImmediate); + + // take + auto* nextCommand = commandQueue.nextCommand(); + // check + QCOMPARE(nextCommand, command); + QVERIFY(nextCommand->token() != 0); + QCOMPARE(commandQueue.count(), 0); + QCOMPARE(commandQueue.isEmpty(), true); + QCOMPARE(commandQueue.haveImmediateCommand(), false); +} + +void TestMICommandQueue::clearQueue() +{ + KDevMI::MI::CommandQueue commandQueue; + + // prepare + auto* command1 = new TestDummyCommand(KDevMI::MI::NonMI, QString(), KDevMI::MI::CmdImmediately); + auto* command2 = new TestDummyCommand(KDevMI::MI::NonMI, QString(), {}); + + QSignalSpy command1Spy(command1, &QObject::destroyed); + QSignalSpy command2Spy(command2, &QObject::destroyed); + + commandQueue.enqueue(command1); + commandQueue.enqueue(command2); + + // execute + commandQueue.clear(); + + // check + QCOMPARE(commandQueue.count(), 0); + QCOMPARE(commandQueue.isEmpty(), true); + QCOMPARE(commandQueue.haveImmediateCommand(), false); + QCOMPARE(command1Spy.count(), 1); + QCOMPARE(command2Spy.count(), 1); +} + +QTEST_GUILESS_MAIN(TestMICommandQueue) + +#include "test_micommandqueue.moc" diff --git a/plugins/debuggercommon/tests/test_miparser.h b/plugins/debuggercommon/tests/test_miparser.h new file mode 100644 --- /dev/null +++ b/plugins/debuggercommon/tests/test_miparser.h @@ -0,0 +1,41 @@ +/* This file is part of KDevelop + * + * Copyright 2018 Friedrich W. H. Kossebau + * + * 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 Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef KDEV_TESTMIPARSER_H +#define KDEV_TESTMIPARSER_H + +#include + +namespace KDevMI { namespace MI { struct Value; }} + + +class TestMIParser : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testParseLine_data(); + void testParseLine(); + +private: + void doTestResult(const KDevMI::MI::Value& actualValue, const QVariant& expectedValue); +}; + +#endif diff --git a/plugins/debuggercommon/tests/test_miparser.cpp b/plugins/debuggercommon/tests/test_miparser.cpp new file mode 100644 --- /dev/null +++ b/plugins/debuggercommon/tests/test_miparser.cpp @@ -0,0 +1,221 @@ +/* This file is part of KDevelop + * + * Copyright 2018 Friedrich W. H. Kossebau + * + * 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 Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "test_miparser.h" + +// SUT +#include +// Qt +#include + +struct ResultData +{ + QString name; + QVariant value; +}; + +struct ResultRecordData +{ + uint32_t token; + QString reason; + QVector results; + + QVariant toVariant() { return QVariant::fromValue(*this); } +}; +Q_DECLARE_METATYPE(ResultRecordData) + +struct AsyncRecordData +{ + int subkind; + QString reason; + QVector results; + + QVariant toVariant() { return QVariant::fromValue(*this); } +}; +Q_DECLARE_METATYPE(AsyncRecordData) + +struct StreamRecordData +{ + int subkind; + QString message; + + QVariant toVariant() { return QVariant::fromValue(*this); } +}; +Q_DECLARE_METATYPE(StreamRecordData) + + +void TestMIParser::testParseLine_data() +{ + QTest::addColumn("line"); + QTest::addColumn("recordKind"); + QTest::addColumn("recordData"); + + // prompt + QTest::newRow("gdpprompt") + << QByteArray("(gdb)") + << (int)KDevMI::MI::Record::Prompt + << QVariant(); + + // result records + QTest::newRow("done") + << QByteArray("^done") + << (int)KDevMI::MI::Record::Result + << ResultRecordData{0, "done", {}}.toVariant(); + QTest::newRow("doneWToken") + << QByteArray("11^done") + << (int)KDevMI::MI::Record::Result + << ResultRecordData{11, "done", {}}.toVariant(); + QTest::newRow("runningWToken") + << QByteArray("25^running") + << (int)KDevMI::MI::Record::Result + << ResultRecordData{25, "running", {}}.toVariant(); + + // Out-of-band records + QTest::newRow("stopreply") + << QByteArray("*stop,reason=\"stop\",address=\"0x123\",source=\"a.c:123\"") + << (int)KDevMI::MI::Record::Async + << AsyncRecordData{KDevMI::MI::AsyncRecord::Exec, "stop", + {{"reason", "stop"}, {"address", "0x123"}, {"source", "a.c:123"}}}.toVariant(); + QTest::newRow("threadgroupadded") + << QByteArray("=thread-group-added,id=\"i1\"") + << (int)KDevMI::MI::Record::Async + << AsyncRecordData{KDevMI::MI::AsyncRecord::Notify, "thread-group-added", + {{"id", "i1"}}}.toVariant(); + QTest::newRow("symbolfilereply") << QByteArray("*breakpoint,nr=\"3\",address=\"0x123\",source=\"a.c:123\"") + << (int)KDevMI::MI::Record::Async + << AsyncRecordData{KDevMI::MI::AsyncRecord::Exec, "breakpoint", + {{"nr", "3"}, {"address", "0x123"}, {"source", "a.c:123"}}}.toVariant(); + + // breakpoint creation records + QTest::newRow("breakreply") + << QByteArray("&\"break /path/to/some/file.cpp:28\\n\"") + << (int)KDevMI::MI::Record::Stream + << StreamRecordData{KDevMI::MI::StreamRecord::Log, "break /path/to/some/file.cpp:28\n"}.toVariant(); + QTest::newRow("breakreply2") + << QByteArray("~\"Breakpoint 1 at 0x400ab0: file /path/to/some/file.cpp, line 28.\\n\"") + << (int)KDevMI::MI::Record::Stream + << StreamRecordData{KDevMI::MI::StreamRecord::Console, "Breakpoint 1 at 0x400ab0: file /path/to/some/file.cpp, line 28.\n"}.toVariant(); + QTest::newRow("breakreply3") + << QByteArray("=breakpoint-created,bkpt={number=\"1\",type=\"breakpoint\",disp=\"keep\",enabled=\"y\",addr=\"0x0000000000400ab0\",func=\"main(int, char**)\",file=\"/path/to/some/file.cpp\",fullname=\"/path/to/some/file.cpp\",line=\"28\",thread-groups=[\"i1\"],times=\"0\",original-location=\"/path/to/some/file.cpp:28\"}") + << (int)KDevMI::MI::Record::Async + << AsyncRecordData{KDevMI::MI::AsyncRecord::Notify, "breakpoint-created", + {{"bkpt", QVariantMap{ + {"number", "1"}, + {"type", "breakpoint"}, + {"disp", "keep"}, + {"enabled", "y"}, + {"addr", "0x0000000000400ab0"}, + {"func", "main(int, char**)"}, + {"file", "/path/to/some/file.cpp"}, + {"fullname", "/path/to/some/file.cpp"}, + {"line", "28"}, + {"thread-groups", QVariantList{"i1"}}, + {"times", "0"}, + {"original-location", "/path/to/some/file.cpp:28"}}}}}.toVariant(); +} + + +void TestMIParser::doTestResult(const KDevMI::MI::Value& actualValue, const QVariant& expectedValue) +{ + if (expectedValue.type() == QVariant::String) { + QCOMPARE((int)actualValue.kind, (int)KDevMI::MI::Value::StringLiteral); + QCOMPARE(actualValue.literal(), expectedValue.toString()); + } else if (expectedValue.type() == QVariant::List) { + QCOMPARE(actualValue.kind, KDevMI::MI::Value::List); + + const auto expectedList = expectedValue.toList(); + QCOMPARE(actualValue.size(), expectedList.size()); + for (int i = 0; i < expectedList.size(); ++i) { + doTestResult(actualValue[i], expectedList[i]); + } + } else if (expectedValue.type() == QVariant::Map) { + QCOMPARE(actualValue.kind, KDevMI::MI::Value::Tuple); + + const auto expectedMap = expectedValue.toMap(); + + for (auto it = expectedMap.begin(), end = expectedMap.end(); it != end; ++it) { + const auto& expectedMapField = it.key(); + QVERIFY(actualValue.hasField(expectedMapField)); + const auto& expectedMapValue = it.value(); + doTestResult(actualValue[expectedMapField], expectedMapValue); + } + } else { + QFAIL("Using unexpected QVariant type"); + } +} + + +void TestMIParser::testParseLine() +{ + QFETCH(QByteArray, line); + QFETCH(int, recordKind); + QFETCH(QVariant, recordData); + + KDevMI::MI::MIParser m_parser; + + KDevMI::MI::FileSymbol file; + file.contents = line; + + std::unique_ptr record(m_parser.parse(&file)); + QVERIFY(record); + QCOMPARE((int)record->kind, recordKind); + + switch(recordKind) { + case KDevMI::MI::Record::Result: { + const auto& resultRecord = static_cast(*record); + const auto resultData = recordData.value(); + + QCOMPARE(resultRecord.token, resultData.token); + QCOMPARE(resultRecord.reason, resultData.reason); + + for (const auto& result : resultData.results) { + QVERIFY(resultRecord.hasField(result.name)); + doTestResult(resultRecord[result.name], result.value); + } + break; + } + case KDevMI::MI::Record::Async: { + const auto& asyncRecord = static_cast(*record); + const auto asyncData = recordData.value(); + + QCOMPARE((int)asyncRecord.subkind, asyncData.subkind); + QCOMPARE(asyncRecord.reason, asyncData.reason); + + for (const auto& result : asyncData.results) { + QVERIFY(asyncRecord.hasField(result.name)); + doTestResult(asyncRecord[result.name], result.value); + } + break; + } + case KDevMI::MI::Record::Stream: { + const auto& streamRecord = static_cast(*record); + const auto streamData = recordData.value(); + + QCOMPARE((int)streamRecord.subkind, streamData.subkind); + QCOMPARE(streamRecord.message, streamData.message); + break; + } + case KDevMI::MI::Record::Prompt: + break; + } + +} + +QTEST_GUILESS_MAIN(TestMIParser)