File indexing completed on 2024-05-12 04:39:45

0001 /*
0002     SPDX-FileCopyrightText: 2018 Friedrich W. H. Kossebau <kossebau@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "test_miparser.h"
0008 
0009 // SUT
0010 #include <mi/miparser.h>
0011 // Qt
0012 #include <QTest>
0013 #include <QStandardPaths>
0014 
0015 struct ResultData
0016 {
0017     QString name;
0018     QVariant value;
0019 };
0020 
0021 struct ResultRecordData
0022 {
0023     uint32_t token;
0024     QString reason;
0025     QVector<ResultData> results;
0026 
0027     QVariant toVariant() { return QVariant::fromValue<ResultRecordData>(*this); }
0028 };
0029 Q_DECLARE_METATYPE(ResultRecordData)
0030 
0031 struct AsyncRecordData
0032 {
0033     int subkind;
0034     QString reason;
0035     QVector<ResultData> results;
0036 
0037     QVariant toVariant() { return QVariant::fromValue<AsyncRecordData>(*this); }
0038 };
0039 Q_DECLARE_METATYPE(AsyncRecordData)
0040 
0041 struct StreamRecordData
0042 {
0043     int subkind;
0044     QString message;
0045 
0046     QVariant toVariant() { return QVariant::fromValue<StreamRecordData>(*this); }
0047 };
0048 Q_DECLARE_METATYPE(StreamRecordData)
0049 
0050 void TestMIParser::initTestCase()
0051 {
0052     QStandardPaths::setTestModeEnabled(true);
0053 }
0054 
0055 void TestMIParser::testParseLine_data()
0056 {
0057     QTest::addColumn<QByteArray>("line");
0058     QTest::addColumn<int>("recordKind");
0059     QTest::addColumn<QVariant>("recordData");
0060 
0061     // prompt
0062     QTest::newRow("gdpprompt")
0063         << QByteArray("(gdb)")
0064         << (int)KDevMI::MI::Record::Prompt
0065         << QVariant();
0066 
0067     // result records
0068     QTest::newRow("done")
0069         << QByteArray("^done")
0070         << (int)KDevMI::MI::Record::Result
0071         << ResultRecordData{0, "done", {}}.toVariant();
0072     QTest::newRow("doneWToken")
0073         << QByteArray("11^done")
0074         << (int)KDevMI::MI::Record::Result
0075         << ResultRecordData{11, "done", {}}.toVariant();
0076     QTest::newRow("runningWToken")
0077         << QByteArray("25^running")
0078         << (int)KDevMI::MI::Record::Result
0079         << ResultRecordData{25, "running", {}}.toVariant();
0080 
0081     // Out-of-band records
0082     QTest::newRow("stopreply")
0083         << QByteArray("*stop,reason=\"stop\",address=\"0x123\",source=\"a.c:123\"")
0084         << (int)KDevMI::MI::Record::Async
0085         << AsyncRecordData{KDevMI::MI::AsyncRecord::Exec, "stop",
0086                            {{"reason", "stop"}, {"address", "0x123"}, {"source", "a.c:123"}}}.toVariant();
0087     QTest::newRow("threadgroupadded")
0088         << QByteArray("=thread-group-added,id=\"i1\"")
0089         << (int)KDevMI::MI::Record::Async
0090         << AsyncRecordData{KDevMI::MI::AsyncRecord::Notify, "thread-group-added",
0091                            {{"id", "i1"}}}.toVariant();
0092     QTest::newRow("symbolfilereply") << QByteArray("*breakpoint,nr=\"3\",address=\"0x123\",source=\"a.c:123\"")
0093         << (int)KDevMI::MI::Record::Async
0094         << AsyncRecordData{KDevMI::MI::AsyncRecord::Exec, "breakpoint",
0095                            {{"nr", "3"}, {"address", "0x123"}, {"source", "a.c:123"}}}.toVariant();
0096 
0097     // breakpoint creation records
0098     QTest::newRow("breakreply")
0099         << QByteArray("&\"break /path/to/some/file.cpp:28\\n\"")
0100         << (int)KDevMI::MI::Record::Stream
0101         << StreamRecordData{KDevMI::MI::StreamRecord::Log, "break /path/to/some/file.cpp:28\n"}.toVariant();
0102     QTest::newRow("breakreply2")
0103         << QByteArray("~\"Breakpoint 1 at 0x400ab0: file /path/to/some/file.cpp, line 28.\\n\"")
0104         << (int)KDevMI::MI::Record::Stream
0105         << StreamRecordData{KDevMI::MI::StreamRecord::Console, "Breakpoint 1 at 0x400ab0: file /path/to/some/file.cpp, line 28.\n"}.toVariant();
0106     QTest::newRow("breakreply3")
0107         << 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\"}")
0108         << (int)KDevMI::MI::Record::Async
0109         << AsyncRecordData{KDevMI::MI::AsyncRecord::Notify, "breakpoint-created",
0110                            {{"bkpt", QVariantMap{
0111                                {"number", "1"},
0112                                {"type", "breakpoint"},
0113                                {"disp", "keep"},
0114                                {"enabled", "y"},
0115                                {"addr", "0x0000000000400ab0"},
0116                                {"func", "main(int, char**)"},
0117                                {"file", "/path/to/some/file.cpp"},
0118                                {"fullname", "/path/to/some/file.cpp"},
0119                                {"line", "28"},
0120                                {"thread-groups", QVariantList{"i1"}},
0121                                {"times", "0"},
0122                                {"original-location", "/path/to/some/file.cpp:28"}}}}}.toVariant();
0123 }
0124 
0125 
0126 void TestMIParser::doTestResult(const KDevMI::MI::Value& actualValue, const QVariant& expectedValue)
0127 {
0128     if (expectedValue.type() == QVariant::String) {
0129         QCOMPARE((int)actualValue.kind, (int)KDevMI::MI::Value::StringLiteral);
0130         QCOMPARE(actualValue.literal(), expectedValue.toString());
0131     } else if (expectedValue.type() == QVariant::List) {
0132         QCOMPARE(actualValue.kind, KDevMI::MI::Value::List);
0133 
0134         const auto expectedList = expectedValue.toList();
0135         QCOMPARE(actualValue.size(), expectedList.size());
0136         for (int i = 0; i < expectedList.size(); ++i) {
0137             doTestResult(actualValue[i], expectedList[i]);
0138         }
0139     } else if (expectedValue.type() == QVariant::Map) {
0140         QCOMPARE(actualValue.kind, KDevMI::MI::Value::Tuple);
0141 
0142         const auto expectedMap = expectedValue.toMap();
0143 
0144         for (auto it = expectedMap.begin(), end = expectedMap.end(); it != end; ++it) {
0145             const auto& expectedMapField = it.key();
0146             QVERIFY(actualValue.hasField(expectedMapField));
0147             const auto& expectedMapValue = it.value();
0148             doTestResult(actualValue[expectedMapField], expectedMapValue);
0149         }
0150     } else {
0151         QFAIL("Using unexpected QVariant type");
0152     }
0153 }
0154 
0155 
0156 void TestMIParser::testParseLine()
0157 {
0158     QFETCH(QByteArray, line);
0159     QFETCH(int, recordKind);
0160     QFETCH(QVariant, recordData);
0161 
0162     KDevMI::MI::MIParser m_parser;
0163 
0164     KDevMI::MI::FileSymbol file;
0165     file.contents = line;
0166 
0167     std::unique_ptr<KDevMI::MI::Record> record(m_parser.parse(&file));
0168     QVERIFY(record != nullptr);
0169     QCOMPARE((int)record->kind, recordKind);
0170 
0171     switch(recordKind) {
0172     case KDevMI::MI::Record::Result: {
0173         const auto& resultRecord = static_cast<KDevMI::MI::ResultRecord&>(*record);
0174         const auto resultData = recordData.value<ResultRecordData>();
0175 
0176         QCOMPARE(resultRecord.token, resultData.token);
0177         QCOMPARE(resultRecord.reason, resultData.reason);
0178 
0179         for (const auto& result : resultData.results) {
0180             QVERIFY(resultRecord.hasField(result.name));
0181             doTestResult(resultRecord[result.name], result.value);
0182         }
0183         break;
0184     }
0185     case KDevMI::MI::Record::Async: {
0186         const auto& asyncRecord = static_cast<KDevMI::MI::AsyncRecord&>(*record);
0187         const auto asyncData = recordData.value<AsyncRecordData>();
0188 
0189         QCOMPARE((int)asyncRecord.subkind, asyncData.subkind);
0190         QCOMPARE(asyncRecord.reason, asyncData.reason);
0191 
0192         for (const auto& result : asyncData.results) {
0193             QVERIFY(asyncRecord.hasField(result.name));
0194             doTestResult(asyncRecord[result.name], result.value);
0195         }
0196         break;
0197     }
0198     case KDevMI::MI::Record::Stream: {
0199         const auto& streamRecord = static_cast<KDevMI::MI::StreamRecord&>(*record);
0200         const auto streamData = recordData.value<StreamRecordData>();
0201 
0202         QCOMPARE((int)streamRecord.subkind, streamData.subkind);
0203         QCOMPARE(streamRecord.message, streamData.message);
0204         break;
0205     }
0206     case KDevMI::MI::Record::Prompt:
0207         break;
0208     }
0209 
0210 }
0211 
0212 QTEST_GUILESS_MAIN(TestMIParser)
0213 
0214 #include "moc_test_miparser.cpp"