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