File indexing completed on 2024-05-12 04:39:45
0001 /* 0002 SPDX-FileCopyrightText: 2009 Niko Sams <niko.sams@gmail.com> 0003 SPDX-FileCopyrightText: 2016 Aetf <aetf@unlimitedcodeworks.xyz> 0004 0005 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0006 */ 0007 0008 #include "testhelper.h" 0009 0010 #include "debuggers-tests-config.h" 0011 #include "midebugsession.h" 0012 0013 #include <execute/iexecuteplugin.h> 0014 #include <util/environmentprofilelist.h> 0015 0016 #include <QAbstractItemModel> 0017 #include <QDebug> 0018 #include <QFile> 0019 #include <QFileInfo> 0020 #include <QModelIndex> 0021 #include <QRegularExpression> 0022 #include <QSignalSpy> 0023 #include <QStringList> 0024 #include <QTest> 0025 #include <QVariant> 0026 0027 namespace KDevMI { 0028 0029 QUrl findExecutable(const QString& name) 0030 { 0031 QString exeExtension; 0032 #ifdef Q_OS_WIN 0033 exeExtension = QStringLiteral(".exe"); 0034 #endif 0035 QFileInfo info(QString::fromLocal8Bit(DEBUGGEE_BIN_DIR), name + exeExtension); 0036 Q_ASSERT_X(info.exists(), "findExecutable", info.filePath().toLocal8Bit()); 0037 Q_ASSERT(info.isExecutable()); 0038 return QUrl::fromLocalFile(info.canonicalFilePath()); 0039 } 0040 0041 QString findSourceFile(const QString& name) 0042 { 0043 return findFile(DEBUGGEE_SRC_DIR, name); 0044 } 0045 0046 QString findFile(const char* dir, const QString& name) 0047 { 0048 QFileInfo info(QString::fromLocal8Bit(dir), name); 0049 Q_ASSERT_X(info.exists(), "findFile", info.filePath().toLocal8Bit()); 0050 0051 return info.canonicalFilePath(); 0052 } 0053 0054 bool isAttachForbidden(const char *file, int line) 0055 { 0056 // if on linux, ensure we can actually attach 0057 QFile canRun(QStringLiteral("/proc/sys/kernel/yama/ptrace_scope")); 0058 if (canRun.exists()) { 0059 if (!canRun.open(QIODevice::ReadOnly)) { 0060 QTest::qFail("Something is wrong: /proc/sys/kernel/yama/ptrace_scope exists but cannot be read", file, line); 0061 return true; 0062 } 0063 if (canRun.read(1).toInt() != 0) { 0064 QTest::qSkip("ptrace attaching not allowed, skipping test. To enable it, set /proc/sys/kernel/yama/ptrace_scope to 0.", file, line); 0065 return true; 0066 } 0067 } 0068 0069 return false; 0070 } 0071 0072 bool compareData(const QModelIndex& index, const QString& expected, const char *file, int line, bool useRE) 0073 { 0074 QString s = index.model()->data(index, Qt::DisplayRole).toString(); 0075 bool matched = true; 0076 if (useRE) { 0077 QRegularExpression re(expected); 0078 matched = re.match(s).hasMatch(); 0079 } else { 0080 matched = s == expected; 0081 } 0082 return QTest::qVerify(matched, "Comparison of data", qPrintable(QString("'%0' didn't match expected '%1' in %2:%3") 0083 .arg(s, expected, file).arg(line)), 0084 file, line); 0085 } 0086 0087 bool waitForAWhile(MIDebugSession *session, int ms, const char *file, int line) 0088 { 0089 QPointer<MIDebugSession> s(session); //session can get deleted in DebugController 0090 QTest::qWait(ms); 0091 if (!s) { 0092 QTest::qFail("Session ended while waiting", file, line); 0093 return false; 0094 } 0095 return true; 0096 } 0097 0098 bool waitForState(MIDebugSession *session, KDevelop::IDebugSession::DebuggerState state, 0099 const char *file, int line, bool waitForIdle) 0100 { 0101 QPointer<MIDebugSession> s(session); //session can get deleted in DebugController 0102 QElapsedTimer stopWatch; 0103 stopWatch.start(); 0104 0105 // legacy behavior for tests that implicitly may require waiting for idle, 0106 // but which were written before waitForIdle was added 0107 waitForIdle = waitForIdle || state != MIDebugSession::EndedState; 0108 0109 while (s && (s->state() != state 0110 || (waitForIdle && s->debuggerStateIsOn(s_dbgBusy)))) { 0111 if (stopWatch.elapsed() > 50000) { 0112 qWarning() << "current state" << s->state() << "waiting for" << state; 0113 QTest::qFail(qPrintable(QString("Timeout before reaching state %0").arg(state)), 0114 file, line); 0115 return false; 0116 } 0117 QTest::qWait(20); 0118 } 0119 0120 // NOTE: don't wait anymore after leaving the loop. Waiting reenters event loop and 0121 // may change session state. 0122 0123 if (!s && state != MIDebugSession::EndedState) { 0124 QTest::qFail(qPrintable(QString("Session ended before reaching state %0").arg(state)), 0125 file, line); 0126 return false; 0127 } 0128 0129 qDebug() << "Reached state " << state << " in " << file << ':' << line; 0130 return true; 0131 } 0132 0133 TestWaiter::TestWaiter(MIDebugSession * session_, const char * condition_, const char * file_, int line_) 0134 : session(session_) 0135 , condition(condition_) 0136 , file(file_) 0137 , line(line_) 0138 { 0139 stopWatch.start(); 0140 } 0141 0142 bool TestWaiter::waitUnless(bool ok) 0143 { 0144 if (ok) { 0145 qDebug() << "Condition " << condition << " reached in " << file << ':' << line; 0146 return false; 0147 } 0148 0149 if (stopWatch.elapsed() > 5000) { 0150 QTest::qFail(qPrintable(QString("Timeout before reaching condition %0").arg(condition)), 0151 file, line); 0152 return false; 0153 } 0154 0155 QTest::qWait(100); 0156 0157 if (!session) { 0158 QTest::qFail(qPrintable(QString("Session ended without reaching condition %0").arg(condition)), 0159 file, line); 0160 return false; 0161 } 0162 0163 return true; 0164 } 0165 0166 TestLaunchConfiguration::TestLaunchConfiguration(const QUrl& executable, const QUrl& workingDirectory) 0167 { 0168 qDebug() << "FIND" << executable; 0169 c = KSharedConfig::openConfig(); 0170 static constexpr const char* groupName = "launch"; 0171 c->deleteGroup(groupName); 0172 cfg = c->group(groupName); 0173 cfg.writeEntry(IExecutePlugin::isExecutableEntry, true); 0174 cfg.writeEntry(IExecutePlugin::executableEntry, executable); 0175 cfg.writeEntry(IExecutePlugin::workingDirEntry, workingDirectory); 0176 } 0177 0178 namespace { 0179 class WritableEnvironmentProfileList : public KDevelop::EnvironmentProfileList 0180 { 0181 public: 0182 explicit WritableEnvironmentProfileList(KConfig* config) : EnvironmentProfileList(config) {} 0183 0184 using EnvironmentProfileList::variables; 0185 using EnvironmentProfileList::saveSettings; 0186 using EnvironmentProfileList::removeProfile; 0187 }; 0188 } // end of namespace 0189 0190 void testEnvironmentSet(MIDebugSession* session, const QString& profileName, 0191 IExecutePlugin* executePlugin) 0192 { 0193 TestLaunchConfiguration cfg(QStringLiteral("debuggee_debugeeechoenv")); 0194 0195 cfg.config().writeEntry(IExecutePlugin::environmentProfileEntry, profileName); 0196 0197 WritableEnvironmentProfileList envProfiles(cfg.rootConfig()); 0198 envProfiles.removeProfile(profileName); 0199 auto& envs = envProfiles.variables(profileName); 0200 envs[QStringLiteral("VariableA")] = QStringLiteral("-A' \" complex --value"); 0201 envs[QStringLiteral("VariableB")] = QStringLiteral("-B' \" complex --value"); 0202 envProfiles.saveSettings(cfg.rootConfig()); 0203 0204 QSignalSpy outputSpy(session, &MIDebugSession::inferiorStdoutLines); 0205 0206 QVERIFY(session->startDebugging(&cfg, executePlugin)); 0207 WAIT_FOR_STATE(session, KDevelop::IDebugSession::EndedState); 0208 0209 QVERIFY(outputSpy.count() > 0); 0210 0211 QStringList outputLines; 0212 while (outputSpy.count() > 0) { 0213 const QList<QVariant> arguments = outputSpy.takeFirst(); 0214 for (const auto& item : arguments) { 0215 outputLines.append(item.toStringList()); 0216 } 0217 } 0218 QCOMPARE(outputLines, QStringList() << "-A' \" complex --value" 0219 << "-B' \" complex --value"); 0220 } 0221 0222 } // end of namespace KDevMI