/ test / tst_shell.cpp
tst_shell.cpp
  1  #include "tst_shell.h"
  2  
  3  #include <gui/mainwindow.h>
  4  #include <msgpackrequest.h>
  5  #include <QClipboard>
  6  #include <QFontDatabase>
  7  #include <QLocalSocket>
  8  #include <QtGlobal>
  9  #include <QtTest/QtTest>
 10  
 11  #include "common.h"
 12  #include "common_gui.h"
 13  
 14  #if defined(Q_OS_WIN) && defined(USE_STATIC_QT)
 15  #include <QtPlugin>
 16  Q_IMPORT_PLUGIN (QWindowsIntegrationPlugin);
 17  #endif
 18  
 19  namespace NeovimQt {
 20  
 21  class TestShell : public QObject
 22  {
 23  	Q_OBJECT
 24  
 25  private slots:
 26  	void initTestCase() noexcept;
 27  	void benchStart() noexcept;
 28  	void startVarsShellWidget() noexcept;
 29  	void startVarsMainWindow() noexcept;
 30  	void gviminit() noexcept;
 31  	void guiShimCommands() noexcept;
 32  	void CloseEvent_data() noexcept;
 33  	void CloseEvent() noexcept;
 34  	void GetClipboard_data() noexcept;
 35  	void GetClipboard() noexcept;
 36  	void SetClipboard_data() noexcept;
 37  	void SetClipboard() noexcept;
 38  
 39  protected:
 40  	void checkStartVars(NeovimQt::NeovimConnector* conn) noexcept;
 41  	void grabShellScreenshot(Shell& s, const QString& filename) noexcept;
 42  };
 43  
 44  static void SignalPrintError(QString msg, const QVariant& err) noexcept
 45  {
 46  	qWarning() << "Error Signal!" << msg << err;
 47  }
 48  
 49  void TestShell::initTestCase() noexcept
 50  {
 51  	const QStringList fonts{
 52  		QStringLiteral("third-party/DejaVuSansMono.ttf"),
 53  		QStringLiteral("third-party/DejaVuSansMono-Bold.ttf"),
 54  		QStringLiteral("third-party/DejaVuSansMono-BoldOblique.ttf") };
 55  
 56  	for (const auto& path : fonts) {
 57  		QString abs_path_to_font(CMAKE_SOURCE_DIR);
 58  		abs_path_to_font.append("/").append(path);
 59  		QFontDatabase::addApplicationFont(abs_path_to_font);
 60  	}
 61  }
 62  
 63  void TestShell::benchStart() noexcept
 64  {
 65  	QBENCHMARK
 66  	{
 67  		auto s = CreateShellWidget();
 68  
 69  		QSignalSpy onResize(s.get(), &Shell::neovimResized);
 70  		QVERIFY(onResize.isValid());
 71  		QVERIFY(SPYWAIT(onResize));
 72  	}
 73  }
 74  
 75  void TestShell::startVarsShellWidget() noexcept
 76  {
 77  	auto s = CreateShellWidget();
 78  
 79  	checkStartVars(s->nvim());
 80  }
 81  
 82  void TestShell::startVarsMainWindow() noexcept
 83  {
 84  	auto w = CreateMainWindow();
 85  	checkStartVars(w->shell()->nvim());
 86  }
 87  
 88  void TestShell::gviminit() noexcept
 89  {
 90  	qputenv("GVIMINIT", "let g:test_gviminit = 1");
 91  	auto s = CreateShellWidget();
 92  	NeovimConnector* c = s->nvim();
 93  
 94  	MsgpackRequest* req{ c->api0()->vim_command_output(c->encode("echo g:test_gviminit")) };
 95  	QSignalSpy cmd{ req, &MsgpackRequest::finished };
 96  	QVERIFY(cmd.isValid());
 97  	QVERIFY(SPYWAIT(cmd));
 98  	QCOMPARE(cmd.at(0).at(2).toByteArray(), QByteArray("1"));
 99  }
100  
101  void TestShell::guiShimCommands() noexcept
102  {
103  	auto w = CreateMainWindowWithRuntime();
104  	auto c = w->shell()->nvim();
105  
106  	QObject::connect(c->neovimObject(), &NeovimApi1::err_vim_command_output, SignalPrintError);
107  
108  	QSignalSpy cmd_font(
109  		c->neovimObject()->vim_command_output(c->encode("GuiFont!")), &MsgpackRequest::finished);
110  	QVERIFY(cmd_font.isValid());
111  	QVERIFY2(SPYWAIT(cmd_font), "Waiting for GuiFont");
112  
113  	QSignalSpy cmd_ls(
114  		c->neovimObject()->vim_command_output(c->encode("GuiLinespace")),
115  		&MsgpackRequest::finished);
116  	QVERIFY(cmd_ls.isValid());
117  	QVERIFY2(SPYWAIT(cmd_ls), "Waiting for GuiLinespace");
118  
119  	// Test font attributes
120  	const QString cmdFontSize14{ QStringLiteral("GuiFont! %1:h14").arg(GetPlatformTestFont()) };
121  	const QString expectedFontSize14{ QStringLiteral("%1:h14").arg(GetPlatformTestFont()) };
122  	QSignalSpy cmd_gf{ c->neovimObject()->vim_command_output(c->encode(cmdFontSize14)),
123  		&MsgpackRequest::finished };
124  	QVERIFY(cmd_gf.isValid());
125  	QVERIFY(SPYWAIT(cmd_gf));
126  
127  	QSignalSpy spy_fontchange(w->shell(), &ShellWidget::shellFontChanged);
128  
129  	// Test Performance: timeout occurs often, set value carefully.
130  	SPYWAIT(spy_fontchange, 2500 /*msec*/);
131  
132  	QCOMPARE(w->shell()->fontDesc(), expectedFontSize14);
133  
134  	// Normalization removes the :b attribute
135  	const QString cmdFontBoldRemoved{
136  		QStringLiteral("GuiFont! %1:h16:b:l").arg(GetPlatformTestFont())
137  	};
138  	const QString expectedFontBoldRemoved{ QStringLiteral("%1:h16:l").arg(GetPlatformTestFont()) };
139  	QSignalSpy spy_fontchange2(w->shell(), &ShellWidget::shellFontChanged);
140  	QSignalSpy cmd_gf2{ c->neovimObject()->vim_command_output(c->encode(cmdFontBoldRemoved)),
141  						&MsgpackRequest::finished };
142  	QVERIFY(cmd_gf2.isValid());
143  	QVERIFY(SPYWAIT(cmd_gf2, 5000));
144  
145  	// Test Performance: timeout occurs often, set value carefully.
146  	SPYWAIT(spy_fontchange2, 5000 /*msec*/);
147  
148  	QCOMPARE(w->shell()->fontDesc(), expectedFontBoldRemoved);
149  
150  	// GuiRenderFontAttr
151  	QCOMPARE(w->shell()->renderFontAttr(), true);
152  
153  	QSignalSpy spy_fontattr_change(w->shell(), &ShellWidget::renderFontAttrChanged);
154  	QVERIFY(spy_fontattr_change.isValid());
155  	QSignalSpy cmd_fontattr(
156  		c->neovimObject()->vim_command_output(c->encode("GuiRenderFontAttr 0")),
157  		&MsgpackRequest::finished);
158  	QVERIFY(cmd_fontattr.isValid());
159  
160  	QVERIFY2(SPYWAIT(cmd_fontattr), "Waiting for GuiRenderFontAttr cmd");
161  	QVERIFY2(SPYWAIT(spy_fontattr_change), "Waiting for renderFontAttrChanged");
162  	QCOMPARE(w->shell()->renderFontAttr(), false);
163  
164  	QSignalSpy spy_fontattr_change2(w->shell(), &ShellWidget::renderFontAttrChanged);
165  	QVERIFY(spy_fontattr_change2.isValid());
166  	QSignalSpy cmd_fontattr2(
167  		c->neovimObject()->vim_command_output(c->encode("GuiRenderFontAttr 1")),
168  		&MsgpackRequest::finished);
169  	QVERIFY(cmd_fontattr2.isValid());
170  
171  	QVERIFY2(SPYWAIT(cmd_fontattr2), "Waiting for GuiRenderFontAttr cmd");
172  	QVERIFY2(SPYWAIT(spy_fontattr_change2), "Waiting for renderFontAttrChanged");
173  	QCOMPARE(w->shell()->renderFontAttr(), true);
174  
175  }
176  
177  void TestShell::CloseEvent_data() noexcept
178  {
179  	QTest::addColumn<int>("msgpack_status");
180  	QTest::addColumn<int>("exit_status");
181  	QTest::addColumn<QByteArray>("command");
182  
183  	QTest::newRow("Normal Exit: q") << 0 << 0 << QByteArray("q");
184  
185  	QTest::newRow("Exit with Code 1: cq") << 1 << 1 << QByteArray("cq");
186  
187  	QTest::newRow("Exit with Code 2: 2cq") << 2 << 2 << QByteArray("2cq");
188  
189  	QTest::newRow("Exit with Code 255: 255cq") << 255 << 255 << QByteArray("255cq");
190  
191  	// Some exit-status scenarios are platform dependent.
192  	// Ex) Overflow on UNIX-like operating systems.
193  	AddPlatformSpecificExitCodeCases();
194  }
195  
196  void TestShell::CloseEvent() noexcept
197  {
198  	auto w = CreateMainWindowWithRuntime();
199  	auto c = w->shell()->nvim();
200  
201  	QFETCH(int, msgpack_status);
202  	QFETCH(int, exit_status);
203  	QFETCH(QByteArray, command);
204  
205  	// GUI shim Close event
206  	QSignalSpy onClose(w->shell(), &Shell::neovimGuiCloseRequest);
207  	QVERIFY(onClose.isValid());
208  
209  	QSignalSpy onWindowClosing(w.get(), &MainWindow::closing);
210  	QVERIFY(onWindowClosing.isValid());
211  
212  	c->api0()->vim_command(c->encode(command));
213  
214  	QVERIFY(SPYWAIT(onClose));
215  	QCOMPARE(onClose.takeFirst().at(0).toInt(), msgpack_status);
216  
217  	QVERIFY(SPYWAIT(onWindowClosing));
218  	QCOMPARE(onWindowClosing.takeFirst().at(0).toInt(), msgpack_status);
219  
220  	// and finally a call to nvim-qt
221  	QProcess p;
222  	p.setProgram(NVIM_QT_BINARY);
223  	QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
224  	env.insert("NVIM_QT_RUNTIME_PATH", GetRuntimeAbsolutePath());
225  	p.setProcessEnvironment(env);
226  	p.setArguments(BinaryAndArgumentsNoForkWithCommand(command));
227  	p.start();
228  	p.waitForFinished(-1);
229  	QCOMPARE(p.exitStatus(), QProcess::NormalExit);
230  	int actual_exit_status{ p.exitCode() };
231  
232  	QCOMPARE(actual_exit_status, exit_status);
233  }
234  
235  void TestShell::GetClipboard_data() noexcept
236  {
237  	// * or +
238  	QTest::addColumn<char>("reg");
239  	// data set in the register when starting test, this is set
240  	// externaly (i.e. without going through the provider)
241  	QTest::addColumn<QByteArray>("register_data");
242  
243  	QTest::newRow("empty *") << '*' << QByteArray();
244  	QTest::newRow("set *") << '*' << QByteArray("something");
245  	QTest::newRow("empty +") << '+' << QByteArray();
246  	QTest::newRow("set +") << '+' << QByteArray("something");
247  	QTest::newRow("paste *") << '*' << QByteArray("something");
248  	QTest::newRow("paste +") << '+' << QByteArray("something");
249  }
250  
251  void TestShell::GetClipboard() noexcept
252  {
253  	auto w = CreateMainWindowWithRuntime();
254  	NeovimConnector* c = w->shell()->nvim();
255  
256  	QFETCH(char, reg);
257  	QFETCH(QByteArray, register_data);
258  
259  	QObject::connect(c->neovimObject(), &NeovimApi1::err_vim_command_output, SignalPrintError);
260  
261  	// provided by the GUI shim
262  	c->api0()->vim_command(c->encode("call GuiClipboard()"));
263  
264  	QGuiApplication::clipboard()->setText(register_data, GetClipboardMode(reg));
265  
266  	QString getreg_cmd = QStringLiteral("getreg('%1')").arg(reg);
267  	QSignalSpy cmd_clip(c->api1()->nvim_eval(c->encode(getreg_cmd)), &MsgpackRequest::finished);
268  	QVERIFY(cmd_clip.isValid());
269  	QVERIFY(SPYWAIT(cmd_clip));
270  	QCOMPARE(cmd_clip.takeFirst().at(2), QVariant(register_data));
271  }
272  
273  void TestShell::SetClipboard_data() noexcept
274  {
275  	// * or +
276  	QTest::addColumn<char>("reg");
277  	// data set in the register when starting test, this is set
278  	// externaly (i.e. without going through the provider)
279  	QTest::addColumn<QByteArray>("register_data");
280  
281  	QTest::newRow("empty *") << '*' << QByteArray();
282  	QTest::newRow("set *") << '*' << QByteArray("something");
283  	QTest::newRow("empty +") << '+' << QByteArray();
284  	QTest::newRow("set +") << '+' << QByteArray("something");
285  	QTest::newRow("paste *") << '*' << QByteArray("something");
286  	QTest::newRow("paste +") << '+' << QByteArray("something");
287  }
288  
289  void TestShell::SetClipboard() noexcept
290  {
291  	auto w = CreateMainWindowWithRuntime();
292  	NeovimConnector* c = w->shell()->nvim();
293  
294  	QFETCH(char, reg);
295  	QFETCH(QByteArray, register_data);
296  
297  	QObject::connect(c->neovimObject(), &NeovimApi1::err_vim_command_output, SignalPrintError);
298  
299  	// provided by the GUI shim
300  	c->api0()->vim_command(c->encode("call GuiClipboard()"));
301  
302  	QString setreg_cmd =
303  		QStringLiteral("setreg('%1', '%2')\n").arg(reg).arg(QString::fromUtf8(register_data));
304  	c->neovimObject()->vim_command(c->encode(setreg_cmd));
305  	QSignalSpy spy_sync(c->neovimObject()->vim_feedkeys("", "", false), &MsgpackRequest::finished);
306  	SPYWAIT(spy_sync);
307  	QVERIFY(spy_sync.isValid());
308  	QVERIFY(SPYWAIT(spy_sync));
309  
310  	QGuiApplication::clipboard()->setText(register_data, GetClipboardMode(reg));
311  }
312  
313  void TestShell::checkStartVars(NeovimQt::NeovimConnector* conn) noexcept
314  {
315  	auto* nvim = conn->api1();
316  	connect(nvim, &NeovimQt::NeovimApi1::err_vim_get_var, SignalPrintError);
317  
318  	const QStringList vars{ "GuiWindowId", "GuiWindowMaximized", "GuiWindowFullScreen", "GuiFont", "GuiWindowFrameless" };
319  	for (const auto& var : vars) {
320  		QSignalSpy onVar(nvim, SIGNAL(on_vim_get_var(QVariant)));
321  		QVERIFY(onVar.isValid());
322  		nvim->vim_get_var(conn->encode(var));
323  		QVERIFY(SPYWAIT(onVar));
324  	}
325  
326  	// v:windowid
327  	QSignalSpy onVarWindowId(nvim, SIGNAL(on_vim_get_vvar(QVariant)));
328  	QVERIFY(onVarWindowId.isValid());
329  	nvim->vim_get_vvar(conn->encode("windowid"));
330  	QVERIFY(SPYWAIT(onVarWindowId));
331  }
332  
333  void TestShell::grabShellScreenshot(Shell& s, const QString& filename) noexcept
334  {
335  	s.repaint();
336  	QPixmap p{ s.grab() };
337  	p.save(filename);
338  }
339  
340  } // Namespace NeovimQt
341  
342  QTEST_MAIN(NeovimQt::TestShell)
343  #include "tst_shell.moc"