1 /*
2     SPDX-FileCopyrightText: 2014 Kurt Hindenburg <kurt.hindenburg@gmail.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 // Own
8 #include "TerminalInterfaceTest.h"
9 #include "../profile/Profile.h"
10 #include "../profile/ProfileManager.h"
11 #include "config-konsole.h"
12 
13 // Qt
14 #include <QDebug>
15 #include <QDir>
16 #include <QSignalSpy>
17 
18 // KDE
19 #include <KPluginFactory>
20 #include <KPluginLoader>
21 #include <qtest.h>
22 
23 using namespace Konsole;
24 
25 /* TerminalInterface found in KParts/kde_terminal_interface.h
26  *
27  *  void startProgram(const QString &program,
28  *                    const QStringList &args)
29  *  void showShellInDir(const QString &dir)
30  *  void sendInput(const QString &text)
31  *  int terminalProcessId()
32  *  int foregroundProcessId()
33  *  QString foregroundProcessName()
34  *  QString currentWorkingDirectory() const
35  */
36 
initTestCase()37 void TerminalInterfaceTest::initTestCase()
38 {
39     /* Try to test against build konsolepart, so move directory containing
40       executable to front of libraryPaths.  KPluginLoader should find the
41       part first in the build dir over the system installed ones.
42       I believe the CI installs first and then runs the test so the other
43       paths can not be removed.
44     */
45     const auto libraryPaths = QCoreApplication::libraryPaths();
46     auto buildPath = libraryPaths.last();
47     QCoreApplication::removeLibraryPath(buildPath);
48     // konsolepart.so is in ../autotests/
49     if (buildPath.endsWith(QStringLiteral("/autotests"))) {
50         buildPath.chop(10);
51     }
52     QCoreApplication::addLibraryPath(buildPath);
53 }
54 
55 // Test with no shell running
testTerminalInterfaceNoShell()56 void TerminalInterfaceTest::testTerminalInterfaceNoShell()
57 {
58     // create a Konsole part and attempt to connect to it
59     _terminalPart = createPart();
60     if (_terminalPart == nullptr) {
61         QFAIL("konsolepart not found.");
62     }
63 
64     TerminalInterface *terminal = qobject_cast<TerminalInterface *>(_terminalPart);
65     QVERIFY(terminal);
66 
67 #if !defined(Q_OS_FREEBSD)
68     // Skip this for now on FreeBSD
69     // -1 is current foreground process and name for process 0 is "kernel"
70 
71     // Verify results when no shell running
72     int terminalProcessId = terminal->terminalProcessId();
73     QCOMPARE(terminalProcessId, 0);
74     int foregroundProcessId = terminal->foregroundProcessId();
75     QCOMPARE(foregroundProcessId, -1);
76     QString foregroundProcessName = terminal->foregroundProcessName();
77     QCOMPARE(foregroundProcessName, QString());
78     const QString currentWorkingDirectory = terminal->currentWorkingDirectory();
79     QCOMPARE(currentWorkingDirectory, QString());
80 
81 #endif
82     delete _terminalPart;
83 }
84 
85 // Test with default shell running
testTerminalInterface()86 void TerminalInterfaceTest::testTerminalInterface()
87 {
88     QString currentDirectory;
89 
90     // create a Konsole part and attempt to connect to it
91     _terminalPart = createPart();
92     if (_terminalPart == nullptr) {
93         QFAIL("konsolepart not found.");
94     }
95 
96     TerminalInterface *terminal = qobject_cast<TerminalInterface *>(_terminalPart);
97     QVERIFY(terminal);
98 
99     // Start a shell in given directory
100     terminal->showShellInDir(QDir::home().path());
101 
102 // After fa398f56, the CI test failed; also the KF was updated on that build.
103 // TODO: research this more
104 #if defined(Q_OS_FREEBSD)
105     return;
106 #endif
107     // Skip this for now on FreeBSD
108     // -1 is current foreground process and name for process 0 is "kernel"
109 
110     int foregroundProcessId = terminal->foregroundProcessId();
111     QCOMPARE(foregroundProcessId, -1);
112     QString foregroundProcessName = terminal->foregroundProcessName();
113     QCOMPARE(foregroundProcessName, QString());
114 
115     // terminalProcessId() is the user's default shell
116     // FIXME: find a way to verify this
117     // int terminalProcessId  = terminal->terminalProcessId();
118 
119     // Let's try using QSignalSpy
120     // https://community.kde.org/Guidelines_and_HOWTOs/UnitTests
121     // QSignalSpy is really a QList of QLists, so we take the first
122     // list, which corresponds to the arguments for the first signal
123     // we caught.
124 
125     QSignalSpy stateSpy(_terminalPart, SIGNAL(currentDirectoryChanged(QString)));
126     QVERIFY(stateSpy.isValid());
127 
128     // Now we check to make sure we don't have any signals already
129     QCOMPARE(stateSpy.count(), 0);
130 
131     // Let's trigger some signals
132 
133     // #1A - Test signal currentDirectoryChanged(QString)
134     currentDirectory = QStringLiteral("/tmp");
135     terminal->sendInput(QStringLiteral("cd ") + currentDirectory + QLatin1Char('\n'));
136     stateSpy.wait(2000);
137     QCOMPARE(stateSpy.count(), 1);
138 
139     // Correct result?
140     QList<QVariant> firstSignalArgs = stateSpy.takeFirst();
141 
142     // Actual: /Users/kurthindenburg
143     // Expected: /tmp
144 #if !defined(Q_OS_MACOS)
145     QString firstSignalState = firstSignalArgs.at(0).toString();
146     QCOMPARE(firstSignalState, currentDirectory);
147 
148     const QString currentWorkingDirectory = terminal->currentWorkingDirectory();
149     QCOMPARE(currentWorkingDirectory, currentDirectory);
150 
151     // #1B - Test signal currentDirectoryChanged(QString)
152     // Invalid directory - no signal should be emitted
153     terminal->sendInput(QStringLiteral("cd /usrADADFASDF\n"));
154     stateSpy.wait(2000);
155     QCOMPARE(stateSpy.count(), 0);
156 
157     // Should be no change since the above cd didn't work
158     const QString currentWorkingDirectory2 = terminal->currentWorkingDirectory();
159     QCOMPARE(currentWorkingDirectory2, currentDirectory);
160 
161     // Test starting a new program
162     QString command = QStringLiteral("top");
163     terminal->sendInput(command + QLatin1Char('\n'));
164     stateSpy.wait(2000);
165     // FIXME: find a good way to validate process id of 'top'
166     foregroundProcessId = terminal->foregroundProcessId();
167     QVERIFY(foregroundProcessId != -1);
168     foregroundProcessName = terminal->foregroundProcessName();
169     QCOMPARE(foregroundProcessName, command);
170 
171     terminal->sendInput(QStringLiteral("q"));
172     stateSpy.wait(2000);
173 
174     // Nothing running in foreground
175     foregroundProcessId = terminal->foregroundProcessId();
176     QCOMPARE(foregroundProcessId, -1);
177     foregroundProcessName = terminal->foregroundProcessName();
178     QCOMPARE(foregroundProcessName, QString());
179 #endif
180 
181     // Test destroyed()
182     QSignalSpy destroyedSpy(_terminalPart, SIGNAL(destroyed()));
183     QVERIFY(destroyedSpy.isValid());
184 
185     // Now we check to make sure we don't have any signals already
186     QCOMPARE(destroyedSpy.count(), 0);
187 
188     delete _terminalPart;
189     QCOMPARE(destroyedSpy.count(), 1);
190 }
191 
testTerminalInterfaceV2()192 void TerminalInterfaceTest::testTerminalInterfaceV2()
193 {
194 #ifdef USE_TERMINALINTERFACEV2
195     Profile::Ptr testProfile(new Profile);
196     testProfile->useFallback();
197     ProfileManager::instance()->addProfile(testProfile);
198 
199     _terminalPart = createPart();
200     if (_terminalPart == nullptr) {
201         QFAIL("konsolepart not found.");
202     }
203 
204     TerminalInterfaceV2 *terminal = qobject_cast<TerminalInterfaceV2 *>(_terminalPart);
205 
206     QVERIFY(terminal);
207     QVERIFY(terminal->setCurrentProfile(testProfile->name()));
208     QCOMPARE(terminal->currentProfileName(), testProfile->name());
209 
210     QCOMPARE(terminal->profileProperty(QStringLiteral("Path")), testProfile->path());
211     QCOMPARE(terminal->profileProperty(QStringLiteral("SilenceSeconds")), testProfile->silenceSeconds());
212     QCOMPARE(terminal->profileProperty(QStringLiteral("Icon")), testProfile->icon());
213     QCOMPARE(terminal->profileProperty(QStringLiteral("ShowTerminalSizeHint")), testProfile->showTerminalSizeHint());
214     QCOMPARE(terminal->profileProperty(QStringLiteral("Environment")), testProfile->environment());
215     QCOMPARE(terminal->profileProperty(QStringLiteral("BellMode")), testProfile->property<QVariant>(Profile::Property::BellMode));
216 #else
217     QSKIP("TerminalInterfaceV2 not enabled", SkipSingle);
218 #endif
219 }
220 
createPart()221 KParts::Part *TerminalInterfaceTest::createPart()
222 {
223     auto konsolePartPlugin = KPluginLoader::findPlugin(QStringLiteral("konsolepart"));
224     if (konsolePartPlugin.isNull()) {
225         return nullptr;
226     }
227 
228     KPluginFactory *factory = KPluginLoader(konsolePartPlugin).factory();
229     if (factory == nullptr) { // not found
230         return nullptr;
231     }
232 
233     auto *terminalPart = factory->create<KParts::Part>(this);
234 
235     return terminalPart;
236 }
237 
238 QTEST_MAIN(TerminalInterfaceTest)
239