diff --git a/autotests/client/test_remote_access.cpp b/autotests/client/test_remote_access.cpp index 155fa83..1dcc35f 100644 --- a/autotests/client/test_remote_access.cpp +++ b/autotests/client/test_remote_access.cpp @@ -1,474 +1,484 @@ /* SPDX-FileCopyrightText: 2016 Oleg Chernovskiy SPDX-FileCopyrightText: 2018 Roman Gilg SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ // Qt #include // client #include "../../src/client/connection_thread.h" #include "../../src/client/event_queue.h" #include "../../src/client/remote_access.h" #include "../../src/client/registry.h" #include "../../src/client/output.h" // server #include "../../src/server/display.h" #include "../../src/server/output_interface.h" #include "../../src/server/remote_access_interface.h" #include using namespace KWayland::Client; using namespace KWayland::Server; Q_DECLARE_METATYPE(const BufferHandle *) Q_DECLARE_METATYPE(const RemoteBuffer *) Q_DECLARE_METATYPE(const void *) class RemoteAccessTest : public QObject { Q_OBJECT private Q_SLOTS: void init(); void cleanup(); void testSendReleaseSingle(); void testSendReleaseMultiple(); void testSendReleaseCrossScreen(); void testSendClientGone(); void testSendReceiveClientGone(); private: Display *m_display = nullptr; OutputInterface *m_outputInterface[2] = {nullptr}; RemoteAccessManagerInterface *m_remoteAccessInterface = nullptr; }; class MockupClient : public QObject { Q_OBJECT public: MockupClient(QObject *parent = nullptr); ~MockupClient(); void bindOutput(int index); ConnectionThread *connection = nullptr; QThread *thread = nullptr; EventQueue *queue = nullptr; Registry *registry = nullptr; RemoteAccessManager *remoteAccess = nullptr; Output *outputs[2] = {nullptr}; }; static const QString s_socketName = QStringLiteral("kwayland-test-remote-access-0"); MockupClient::MockupClient(QObject *parent) : QObject(parent) { // setup connection connection = new KWayland::Client::ConnectionThread; QSignalSpy connectedSpy(connection, &ConnectionThread::connected); QVERIFY(connectedSpy.isValid()); connection->setSocketName(s_socketName); thread = new QThread(this); connection->moveToThread(thread); thread->start(); connection->initConnection(); QVERIFY(connectedSpy.wait()); queue = new EventQueue(this); queue->setup(connection); registry = new Registry(this); QSignalSpy interfacesAnnouncedSpy(registry, &Registry::interfacesAnnounced); QVERIFY(interfacesAnnouncedSpy.isValid()); registry->setEventQueue(queue); registry->create(connection); QVERIFY(registry->isValid()); registry->setup(); QVERIFY(interfacesAnnouncedSpy.wait()); remoteAccess = registry->createRemoteAccessManager( registry->interface(Registry::Interface::RemoteAccessManager).name, registry->interface(Registry::Interface::RemoteAccessManager).version, this); QVERIFY(remoteAccess->isValid()); connection->flush(); } MockupClient::~MockupClient() { #define CLEANUP(variable) \ if (variable) { \ delete variable; \ variable = nullptr; \ } CLEANUP(outputs[0]) CLEANUP(outputs[1]) CLEANUP(remoteAccess) CLEANUP(queue) CLEANUP(registry) if (thread) { if (connection) { connection->flush(); connection->deleteLater(); connection = nullptr; } thread->quit(); thread->wait(); delete thread; thread = nullptr; } #undef CLEANUP } void MockupClient::bindOutput(int index) { // client-bound output outputs[index] = registry->createOutput(registry->interfaces(Registry::Interface::Output)[index].name, registry->interfaces(Registry::Interface::Output)[index].version, this); QVERIFY(outputs[index]->isValid()); connection->flush(); } void RemoteAccessTest::init() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); delete m_display; m_display = new Display(this); m_display->setSocketName(s_socketName); m_display->start(); QVERIFY(m_display->isRunning()); m_display->createShm(); auto initOutputIface = [this](int i) { m_outputInterface[i] = m_display->createOutput(); m_outputInterface[i]->create(); }; initOutputIface(0); initOutputIface(1); m_remoteAccessInterface = m_display->createRemoteAccessManager(); m_remoteAccessInterface->create(); QSignalSpy bufferReleasedSpy(m_remoteAccessInterface, &RemoteAccessManagerInterface::bufferReleased); QVERIFY(bufferReleasedSpy.isValid()); } void RemoteAccessTest::cleanup() { #define CLEANUP(variable) \ if (variable) { \ delete variable; \ variable = nullptr; \ } CLEANUP(m_outputInterface[0]) CLEANUP(m_outputInterface[1]) CLEANUP(m_remoteAccessInterface) CLEANUP(m_display) #undef CLEANUP } void RemoteAccessTest::testSendReleaseSingle() { // this test verifies that a buffer is sent to client and returned back // setup QVERIFY(!m_remoteAccessInterface->isBound()); auto *client = new MockupClient(this); client->bindOutput(0); m_display->dispatchEvents(); QVERIFY(m_remoteAccessInterface->isBound()); // we have one client now QSignalSpy bufferReadySpy(client->remoteAccess, &RemoteAccessManager::bufferReady); QVERIFY(bufferReadySpy.isValid()); BufferHandle *buf = new BufferHandle(); QTemporaryFile *tmpFile = new QTemporaryFile(this); tmpFile->open(); buf->setFd(tmpFile->handle()); buf->setSize(50, 50); buf->setFormat(100500); buf->setStride(7800); m_remoteAccessInterface->sendBufferReady(m_outputInterface[0], buf); // receive buffer QVERIFY(bufferReadySpy.wait()); auto rbuf = bufferReadySpy.takeFirst()[1].value(); QSignalSpy paramsObtainedSpy(rbuf, &RemoteBuffer::parametersObtained); QVERIFY(paramsObtainedSpy.isValid()); // wait for params QVERIFY(paramsObtainedSpy.wait()); // client fd is different, not subject to check QCOMPARE(rbuf->width(), 50u); QCOMPARE(rbuf->height(), 50u); QCOMPARE(rbuf->format(), 100500u); QCOMPARE(rbuf->stride(), 7800u); // release QSignalSpy bufferReleasedSpy(m_remoteAccessInterface, &RemoteAccessManagerInterface::bufferReleased); QVERIFY(bufferReleasedSpy.isValid()); delete rbuf; QVERIFY(bufferReleasedSpy.wait()); // cleanup delete buf; delete client; m_display->dispatchEvents(); QVERIFY(!m_remoteAccessInterface->isBound()); } void RemoteAccessTest::testSendReleaseMultiple() { // this test verifies that a buffer is sent to 2 clients and returned back // setup QVERIFY(!m_remoteAccessInterface->isBound()); auto *client1 = new MockupClient(this); auto *client2 = new MockupClient(this); client1->bindOutput(0); client2->bindOutput(0); m_display->dispatchEvents(); QVERIFY(m_remoteAccessInterface->isBound()); // now we have 2 clients - QSignalSpy bufferReadySpy1(client1->remoteAccess, &RemoteAccessManager::bufferReady); - QVERIFY(bufferReadySpy1.isValid()); - QSignalSpy bufferReadySpy2(client2->remoteAccess, &RemoteAccessManager::bufferReady); - QVERIFY(bufferReadySpy2.isValid()); - BufferHandle *buf = new BufferHandle(); QTemporaryFile *tmpFile = new QTemporaryFile(this); tmpFile->open(); buf->setFd(tmpFile->handle()); buf->setSize(50, 50); buf->setFormat(100500); buf->setStride(7800); - m_remoteAccessInterface->sendBufferReady(m_outputInterface[0], buf); - - // wait for event loop - QVERIFY(bufferReadySpy1.wait()); - // receive buffer at client 1 - QCOMPARE(bufferReadySpy1.size(), 1); - auto rbuf1 = bufferReadySpy1.takeFirst()[1].value(); - QSignalSpy paramsObtainedSpy1(rbuf1, &RemoteBuffer::parametersObtained); - QVERIFY(paramsObtainedSpy1.isValid()); - if (bufferReadySpy2.size() == 0) { - QVERIFY(bufferReadySpy2.wait()); + bool buffer1ParametersReceived = false; + bool buffer2ParametersReceived = false; + + const RemoteBuffer *rbuf1 = nullptr; + const RemoteBuffer *rbuf2 = nullptr; + + client1->connection->flush(); + client2->connection->flush(); + + { + QEventLoop e; + QTimer::singleShot(1000, &e, &QEventLoop::quit); //safety timer + connect(client1->remoteAccess, &RemoteAccessManager::bufferReady, [&] (const void*, const RemoteBuffer *buffer) { + rbuf1 = buffer; + connect(rbuf1, &RemoteBuffer::parametersObtained, [&]() { + buffer1ParametersReceived = true; + if (buffer1ParametersReceived && buffer2ParametersReceived) { + e.quit(); + } + }); + }); + + connect(client2->remoteAccess, &RemoteAccessManager::bufferReady, [&] (const void*, const RemoteBuffer *buffer) { + rbuf2 = buffer; + connect(rbuf2, &RemoteBuffer::parametersObtained, [&]() { + buffer2ParametersReceived = true; + if (buffer1ParametersReceived && buffer2ParametersReceived) { + e.quit(); + } + }); + }); + m_remoteAccessInterface->sendBufferReady(m_outputInterface[0], buf); + + e.exec(); } - // receive buffer at client 2 - QCOMPARE(bufferReadySpy2.size(), 1); - auto rbuf2 = bufferReadySpy2.takeFirst()[1].value(); - QSignalSpy paramsObtainedSpy2(rbuf2, &RemoteBuffer::parametersObtained); - QVERIFY(paramsObtainedSpy2.isValid()); - // wait for event loop - QVERIFY(paramsObtainedSpy1.size() == 1 || paramsObtainedSpy1.wait()); - QCOMPARE(paramsObtainedSpy1.size(), 1); - if (paramsObtainedSpy2.size() == 0) { - QVERIFY(paramsObtainedSpy2.wait()); - } - QCOMPARE(paramsObtainedSpy2.size(), 1); + + QVERIFY(rbuf1); + QVERIFY(rbuf2); + QVERIFY(buffer1ParametersReceived); + QVERIFY(buffer2ParametersReceived); // release QSignalSpy bufferReleasedSpy(m_remoteAccessInterface, &RemoteAccessManagerInterface::bufferReleased); - QVERIFY(bufferReleasedSpy.isValid()); delete rbuf1; QVERIFY(!bufferReleasedSpy.wait(1000)); // one client released, second still holds buffer! delete rbuf2; QVERIFY(bufferReleasedSpy.wait()); // all clients released, buffer should be freed // cleanup delete buf; delete client1; delete client2; m_display->dispatchEvents(); QVERIFY(!m_remoteAccessInterface->isBound()); } void RemoteAccessTest::testSendReleaseCrossScreen() { // this test verifies that multiple buffers for multiple screens are sent to // multiple clients and returned back // setup QVERIFY(!m_remoteAccessInterface->isBound()); auto *client1 = new MockupClient(this); auto *client2 = new MockupClient(this); client1->bindOutput(1); client2->bindOutput(0); m_display->dispatchEvents(); QVERIFY(m_remoteAccessInterface->isBound()); // now we have 2 clients QSignalSpy bufferReadySpy1(client1->remoteAccess, &RemoteAccessManager::bufferReady); QVERIFY(bufferReadySpy1.isValid()); QSignalSpy bufferReadySpy2(client2->remoteAccess, &RemoteAccessManager::bufferReady); QVERIFY(bufferReadySpy2.isValid()); BufferHandle *buf1 = new BufferHandle(); QTemporaryFile *tmpFile1 = new QTemporaryFile(this); tmpFile1->open(); BufferHandle *buf2 = new BufferHandle(); QTemporaryFile *tmpFile2 = new QTemporaryFile(this); tmpFile2->open(); buf1->setFd(tmpFile1->handle()); buf1->setSize(50, 50); buf1->setFormat(100500); buf1->setStride(7800); buf2->setFd(tmpFile2->handle()); buf2->setSize(100, 100); buf2->setFormat(100500); buf2->setStride(7800); m_remoteAccessInterface->sendBufferReady(m_outputInterface[0], buf1); m_remoteAccessInterface->sendBufferReady(m_outputInterface[1], buf2); // wait for event loop QVERIFY(bufferReadySpy1.wait()); if (bufferReadySpy2.size() == 0) { QVERIFY(bufferReadySpy2.wait()); } // receive buffer at client 1 QCOMPARE(bufferReadySpy1.size(), 1); auto rbuf1 = bufferReadySpy1.takeFirst()[1].value(); QSignalSpy paramsObtainedSpy1(rbuf1, &RemoteBuffer::parametersObtained); QVERIFY(paramsObtainedSpy1.isValid()); // receive buffer at client 2 QCOMPARE(bufferReadySpy2.size(), 1); auto rbuf2 = bufferReadySpy2.takeFirst()[1].value(); QSignalSpy paramsObtainedSpy2(rbuf2, &RemoteBuffer::parametersObtained); QVERIFY(paramsObtainedSpy2.isValid()); // wait for event loop if (paramsObtainedSpy1.isEmpty()) { QVERIFY(paramsObtainedSpy1.wait()); } if (paramsObtainedSpy2.isEmpty()) { QVERIFY(paramsObtainedSpy2.wait()); } QCOMPARE(paramsObtainedSpy1.size(), 1); QCOMPARE(paramsObtainedSpy2.size(), 1); // release QSignalSpy bufferReleasedSpy(m_remoteAccessInterface, &RemoteAccessManagerInterface::bufferReleased); QVERIFY(bufferReleasedSpy.isValid()); delete rbuf1; QVERIFY(bufferReleasedSpy.wait()); delete rbuf2; QVERIFY(bufferReleasedSpy.wait()); QCOMPARE(bufferReleasedSpy.size(), 2); // cleanup delete buf1; delete buf2; delete client1; delete client2; m_display->dispatchEvents(); QVERIFY(!m_remoteAccessInterface->isBound()); } void RemoteAccessTest::testSendClientGone() { // this test verifies that when buffer is sent and client is gone, server will release buffer correctly QVERIFY(!m_remoteAccessInterface->isBound()); auto *client = new MockupClient(this); client->bindOutput(0); m_display->dispatchEvents(); QVERIFY(m_remoteAccessInterface->isBound()); // we have one client now QSignalSpy bufferReadySpy(client->remoteAccess, &RemoteAccessManager::bufferReady); QVERIFY(bufferReadySpy.isValid()); BufferHandle *buf = new BufferHandle(); QTemporaryFile *tmpFile = new QTemporaryFile(this); tmpFile->open(); buf->setFd(tmpFile->handle()); buf->setSize(50, 50); buf->setFormat(100500); buf->setStride(7800); m_remoteAccessInterface->sendBufferReady(m_outputInterface[0], buf); // release forcefully QSignalSpy bufferReleasedSpy(m_remoteAccessInterface, &RemoteAccessManagerInterface::bufferReleased); QVERIFY(bufferReleasedSpy.isValid()); delete client; QVERIFY(bufferReleasedSpy.wait()); // cleanup delete buf; m_display->dispatchEvents(); QVERIFY(!m_remoteAccessInterface->isBound()); } void RemoteAccessTest::testSendReceiveClientGone() { // this test verifies that when buffer is sent, received and client is gone, // both client and server will release buffer correctly QVERIFY(!m_remoteAccessInterface->isBound()); auto *client = new MockupClient(this); client->bindOutput(0); m_display->dispatchEvents(); QVERIFY(m_remoteAccessInterface->isBound()); // we have one client now QSignalSpy bufferReadySpy(client->remoteAccess, &RemoteAccessManager::bufferReady); QVERIFY(bufferReadySpy.isValid()); BufferHandle *buf = new BufferHandle(); QTemporaryFile *tmpFile = new QTemporaryFile(this); tmpFile->open(); buf->setFd(tmpFile->handle()); buf->setSize(50, 50); buf->setFormat(100500); buf->setStride(7800); m_remoteAccessInterface->sendBufferReady(m_outputInterface[0], buf); // receive buffer QVERIFY(bufferReadySpy.wait()); auto rbuf = bufferReadySpy.takeFirst()[1].value(); QSignalSpy paramsObtainedSpy(rbuf, &RemoteBuffer::parametersObtained); QVERIFY(paramsObtainedSpy.isValid()); // wait for params QVERIFY(paramsObtainedSpy.wait()); // client fd is different, not subject to check QCOMPARE(rbuf->width(), 50u); QCOMPARE(rbuf->height(), 50u); QCOMPARE(rbuf->format(), 100500u); QCOMPARE(rbuf->stride(), 7800u); // release forcefully QSignalSpy bufferReleasedSpy(m_remoteAccessInterface, &RemoteAccessManagerInterface::bufferReleased); QVERIFY(bufferReleasedSpy.isValid()); delete client; QVERIFY(bufferReleasedSpy.wait()); // cleanup delete buf; m_display->dispatchEvents(); QVERIFY(!m_remoteAccessInterface->isBound()); } QTEST_GUILESS_MAIN(RemoteAccessTest) #include "test_remote_access.moc"