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"