File indexing completed on 2024-05-12 09:56:52
0001 /* 0002 SPDX-FileCopyrightText: 2014 Kurt Hindenburg <kurt.hindenburg@gmail.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 // Own 0008 #include "TerminalInterfaceTest.h" 0009 #include "../profile/Profile.h" 0010 #include "../profile/ProfileManager.h" 0011 #include "config-konsole.h" 0012 0013 // Qt 0014 #include <QDir> 0015 #include <QSignalSpy> 0016 0017 // KDE 0018 #include <KPluginFactory> 0019 #include <kcoreaddons_version.h> 0020 0021 // Others 0022 #if defined(Q_OS_LINUX) 0023 #include <unistd.h> 0024 #elif defined(Q_OS_OPENBSD) 0025 #include <kvm.h> 0026 #include <sys/param.h> 0027 #include <sys/sysctl.h> 0028 #endif 0029 0030 #include <QTest> 0031 0032 using namespace Konsole; 0033 0034 /* TerminalInterface found in KParts/kde_terminal_interface.h 0035 * 0036 * void startProgram(const QString &program, 0037 * const QStringList &args) 0038 * void showShellInDir(const QString &dir) 0039 * void sendInput(const QString &text) 0040 * int terminalProcessId() 0041 * int foregroundProcessId() 0042 * QString foregroundProcessName() 0043 * QString currentWorkingDirectory() const 0044 */ 0045 0046 void TerminalInterfaceTest::initTestCase() 0047 { 0048 /* Try to test against build konsolepart, so move directory containing 0049 executable to front of libraryPaths. KPluginLoader should find the 0050 part first in the build dir over the system installed ones. 0051 I believe the CI installs first and then runs the test so the other 0052 paths can not be removed. 0053 */ 0054 const auto libraryPaths = QCoreApplication::libraryPaths(); 0055 auto buildPath = libraryPaths.last(); 0056 QCoreApplication::removeLibraryPath(buildPath); 0057 // konsolepart.so is in ../autotests/ 0058 if (buildPath.endsWith(QStringLiteral("/autotests"))) { 0059 buildPath.chop(10); 0060 } 0061 QCoreApplication::addLibraryPath(buildPath); 0062 } 0063 0064 // Test with no shell running 0065 void TerminalInterfaceTest::testTerminalInterfaceNoShell() 0066 { 0067 // create a Konsole part and attempt to connect to it 0068 _terminalPart = createPart(); 0069 if (_terminalPart == nullptr) { 0070 QFAIL("konsolepart not found."); 0071 } 0072 0073 TerminalInterface *terminal = qobject_cast<TerminalInterface *>(_terminalPart); 0074 QVERIFY(terminal); 0075 0076 #if !defined(Q_OS_FREEBSD) 0077 // Skip this for now on FreeBSD 0078 // -1 is current foreground process and name for process 0 is "kernel" 0079 0080 // Verify results when no shell running 0081 int terminalProcessId = terminal->terminalProcessId(); 0082 QCOMPARE(terminalProcessId, 0); 0083 int foregroundProcessId = terminal->foregroundProcessId(); 0084 QCOMPARE(foregroundProcessId, -1); 0085 QString foregroundProcessName = terminal->foregroundProcessName(); 0086 QCOMPARE(foregroundProcessName, QString()); 0087 const QString currentWorkingDirectory = terminal->currentWorkingDirectory(); 0088 QCOMPARE(currentWorkingDirectory, QString()); 0089 0090 #endif 0091 delete _terminalPart; 0092 } 0093 0094 // Test with default shell running 0095 void TerminalInterfaceTest::testTerminalInterface() 0096 { 0097 // Maybe https://bugreports.qt.io/browse/QTBUG-82351 ??? 0098 QSKIP("Skipping on CI suse_tumbelweed_qt64", SkipSingle); 0099 return; 0100 0101 QString currentDirectory; 0102 0103 // create a Konsole part and attempt to connect to it 0104 _terminalPart = createPart(); 0105 if (_terminalPart == nullptr) { 0106 QFAIL("konsolepart not found."); 0107 } 0108 0109 TerminalInterface *terminal = qobject_cast<TerminalInterface *>(_terminalPart); 0110 QVERIFY(terminal); 0111 0112 // Start a shell in given directory 0113 terminal->showShellInDir(QDir::home().path()); 0114 0115 // After fa398f56, the CI test failed; also the KF was updated on that build. 0116 // TODO: research this more 0117 #if defined(Q_OS_FREEBSD) 0118 return; 0119 #endif 0120 // Skip this for now on FreeBSD 0121 // -1 is current foreground process and name for process 0 is "kernel" 0122 0123 int foregroundProcessId = terminal->foregroundProcessId(); 0124 QCOMPARE(foregroundProcessId, -1); 0125 QString foregroundProcessName = terminal->foregroundProcessName(); 0126 QCOMPARE(foregroundProcessName, QString()); 0127 0128 // terminalProcessId() is the user's default shell 0129 // FIXME: find a way to verify this 0130 int terminalProcessId = terminal->terminalProcessId(); 0131 #if defined(Q_OS_LINUX) 0132 const int uid = getuid(); 0133 QFile passwdFile(QStringLiteral("/etc/passwd")); 0134 QString defaultExePath; 0135 0136 passwdFile.open(QIODevice::ReadOnly); 0137 0138 do { 0139 const QString userData(QString::fromUtf8(passwdFile.readLine())); 0140 const QStringList dataFields(userData.split(QLatin1Char(':'))); 0141 if (dataFields.at(2).toInt() == uid) { 0142 defaultExePath = dataFields.at(6); 0143 } 0144 } while (defaultExePath.isEmpty()); 0145 0146 passwdFile.close(); 0147 0148 QFile procExeTarget(QStringLiteral("/proc/%1/exe").arg(terminalProcessId)); 0149 if (procExeTarget.exists()) { 0150 QCOMPARE(procExeTarget.symLinkTarget(), defaultExePath.trimmed()); 0151 } 0152 #endif 0153 0154 // Let's try using QSignalSpy 0155 // https://community.kde.org/Guidelines_and_HOWTOs/UnitTests 0156 // QSignalSpy is really a QList of QLists, so we take the first 0157 // list, which corresponds to the arguments for the first signal 0158 // we caught. 0159 0160 QSignalSpy stateSpy(_terminalPart, SIGNAL(currentDirectoryChanged(QString))); 0161 QVERIFY(stateSpy.isValid()); 0162 0163 // Now we check to make sure we don't have any signals already 0164 QCOMPARE(stateSpy.count(), 0); 0165 0166 // Let's trigger some signals 0167 0168 // #1A - Test signal currentDirectoryChanged(QString) 0169 currentDirectory = QStringLiteral("/tmp"); 0170 terminal->sendInput(QStringLiteral("cd ") + currentDirectory + QLatin1Char('\n')); 0171 stateSpy.wait(5000); 0172 QCOMPARE(stateSpy.count(), 1); 0173 0174 // Correct result? 0175 QList<QVariant> firstSignalArgs = stateSpy.takeFirst(); 0176 0177 // Actual: /Users/kurthindenburg 0178 // Expected: /tmp 0179 #if !defined(Q_OS_MACOS) 0180 QString firstSignalState = firstSignalArgs.at(0).toString(); 0181 QCOMPARE(firstSignalState, currentDirectory); 0182 0183 const QString currentWorkingDirectory = terminal->currentWorkingDirectory(); 0184 QCOMPARE(currentWorkingDirectory, currentDirectory); 0185 0186 // #1B - Test signal currentDirectoryChanged(QString) 0187 // Invalid directory - no signal should be emitted 0188 terminal->sendInput(QStringLiteral("cd /usrADADFASDF\n")); 0189 stateSpy.wait(2500); 0190 QCOMPARE(stateSpy.count(), 0); 0191 0192 // Should be no change since the above cd didn't work 0193 const QString currentWorkingDirectory2 = terminal->currentWorkingDirectory(); 0194 QCOMPARE(currentWorkingDirectory2, currentDirectory); 0195 0196 // Test starting a new program 0197 QString command = QStringLiteral("top"); 0198 terminal->sendInput(command + QLatin1Char('\n')); 0199 stateSpy.wait(2500); 0200 foregroundProcessId = terminal->foregroundProcessId(); 0201 QVERIFY(foregroundProcessId != -1); 0202 0203 // Check that pid indeed belongs to a process running 'top' 0204 #if defined(Q_OS_LINUX) 0205 QFile procInfo(QStringLiteral("/proc/%1/stat").arg(foregroundProcessId)); 0206 QVERIFY(procInfo.open(QIODevice::ReadOnly)); 0207 const QString data(QString::fromUtf8(procInfo.readAll())); 0208 const int nameStartIdx = data.indexOf(QLatin1Char('(')) + 1; 0209 const QString name(data.mid(nameStartIdx, data.lastIndexOf(QLatin1Char(')')) - nameStartIdx)); 0210 QCOMPARE(name, command); 0211 #elif defined(Q_OS_SOLARIS) 0212 QFile procExeTarget(QStringLiteral("/proc/%1/execname").arg(foregroundProcessId)); 0213 QVERIFY(procExeTarget.open(QIODevice::ReadOnly)); 0214 QCOMPARE(QStringLiteral(procExeTarget.readAll()), command); 0215 #elif defined(Q_OS_OPENBSD) 0216 kvm_t *kd = kvm_openfiles(NULL, NULL, NULL, O_RD_ONLY, NULL); 0217 int count; 0218 auto kProcStruct = ::kvm_getprocs(kd, KERN_PROC_PID, foregroundProcessId, &count); 0219 QCOMPARE(count, 1); 0220 QCOMPARE(QStringLiteral(kProcStruct->p_comm), command); 0221 kvm_close(kd); 0222 #endif 0223 0224 // check that Terminal::foregroundProcessName outputs name of correct command 0225 foregroundProcessName = terminal->foregroundProcessName(); 0226 QCOMPARE(foregroundProcessName, command); 0227 0228 terminal->sendInput(QStringLiteral("q")); 0229 stateSpy.wait(2500); 0230 0231 // Nothing running in foreground 0232 foregroundProcessId = terminal->foregroundProcessId(); 0233 QCOMPARE(foregroundProcessId, -1); 0234 foregroundProcessName = terminal->foregroundProcessName(); 0235 QCOMPARE(foregroundProcessName, QString()); 0236 #endif 0237 0238 // Test destroyed() 0239 QSignalSpy destroyedSpy(_terminalPart, SIGNAL(destroyed())); 0240 QVERIFY(destroyedSpy.isValid()); 0241 0242 // Now we check to make sure we don't have any signals already 0243 QCOMPARE(destroyedSpy.count(), 0); 0244 0245 delete _terminalPart; 0246 QCOMPARE(destroyedSpy.count(), 1); 0247 } 0248 0249 void TerminalInterfaceTest::testTerminalInterfaceV2() 0250 { 0251 // Use the built-in profile for testing 0252 Profile::Ptr testProfile = ProfileManager::instance()->builtinProfile(); 0253 0254 _terminalPart = createPart(); 0255 if (_terminalPart == nullptr) { 0256 QFAIL("konsolepart not found."); 0257 } 0258 0259 TerminalInterface *terminal = qobject_cast<TerminalInterface *>(_terminalPart); 0260 0261 QVERIFY(terminal); 0262 QVERIFY(terminal->setCurrentProfile(testProfile->name())); 0263 QCOMPARE(terminal->currentProfileName(), testProfile->name()); 0264 0265 QCOMPARE(terminal->profileProperty(QStringLiteral("Path")), testProfile->path()); 0266 QCOMPARE(terminal->profileProperty(QStringLiteral("SilenceSeconds")), testProfile->silenceSeconds()); 0267 QCOMPARE(terminal->profileProperty(QStringLiteral("Icon")), testProfile->icon()); 0268 QCOMPARE(terminal->profileProperty(QStringLiteral("ShowTerminalSizeHint")), testProfile->showTerminalSizeHint()); 0269 QCOMPARE(terminal->profileProperty(QStringLiteral("Environment")), testProfile->environment()); 0270 QCOMPARE(terminal->profileProperty(QStringLiteral("BellMode")), testProfile->property<QVariant>(Profile::Property::BellMode)); 0271 } 0272 0273 KParts::Part *TerminalInterfaceTest::createPart() 0274 { 0275 const KPluginMetaData metaData(QStringLiteral("konsolepart")); 0276 Q_ASSERT(metaData.isValid()); 0277 0278 KPluginFactory::Result<KParts::Part> result = KPluginFactory::instantiatePlugin<KParts::Part>(metaData, this); 0279 Q_ASSERT(result); 0280 0281 return result.plugin; 0282 } 0283 0284 QTEST_MAIN(TerminalInterfaceTest) 0285 0286 #include "moc_TerminalInterfaceTest.cpp"