Introduce a test server
Summary: The idea of shipping a test server is to have something like Xvfb for Wayland. To be able to run a test application with a fake Wayland server. To make this super easy the test server binary is installed into libexec directory and provides a cmake function to run a test application with the test server. The test server takes full control over the process. It's a guiless application and starts the passed test application once it is fully set up. The environment is setup to have the test application connect to the fake server (WAYLAND_SOCKET env variable and QT_QPA_PLATFORM). When the started application finishes the test server goes down and exits with the exit value of the test application. This allows a good integration with ctest. The test server is a virtual server which supports the following interfaces: * Shm * Compositor * Shell * Seat * DataDeviceManager * Idle * SubCompositor * Output (1280x1024 at 60 Hz with 96 dpi) * FakeInput This is sufficient to bring up a QtWayland based application and allows some basic interactions from a test application (e.g. fake input). So far the server fakes a repaint every 16 msec, but does not yet pass events to the test applications. To integrate this into an application for testing use: find_package(KF5Wayland CONFIG) add_executable(myTest myTest.cpp) target_link_libraries(myTest Qt5::Gui Qt5::Test) kwaylandtest(myTest) When now running ctest in the build directory the test server gets started and will start the myTest binary and report the passed/failed in the expected and normal way. This way a test case can easily be run against both X11 and Wayland. Reviewers: #plasma Subscribers: plasma-devel Tags: #plasma Differential Revision: https://phabricator.kde.org/D1726 Changelog: Virtual framebuffer server for auto tests
This commit is contained in:
parent
151a910f4c
commit
682652714d
6 changed files with 316 additions and 0 deletions
|
@ -3,3 +3,10 @@
|
|||
find_dependency(Qt5Gui @REQUIRED_QT_VERSION@)
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/KF5WaylandTargets.cmake")
|
||||
|
||||
function(kwaylandtest testBinaryName)
|
||||
|
||||
add_test(NAME ${testBinaryName}-kwayland-test COMMAND
|
||||
@CMAKE_INSTALL_FULL_LIBEXECDIR@/org-kde-kf5-kwayland-testserver ${CMAKE_CURRENT_BINARY_DIR}/${testBinaryName}
|
||||
)
|
||||
endfunction()
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
add_subdirectory(testserver)
|
||||
|
||||
include(ECMMarkAsTest)
|
||||
|
||||
find_package(Qt5Concurrent ${QT_MIN_VERSION} CONFIG QUIET)
|
||||
|
|
3
src/wayland/tools/testserver/CMakeLists.txt
Normal file
3
src/wayland/tools/testserver/CMakeLists.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
add_executable(org-kde-kf5-kwayland-testserver main.cpp testserver.cpp)
|
||||
target_link_libraries(org-kde-kf5-kwayland-testserver Qt5::Core KF5::WaylandServer)
|
||||
install(TARGETS org-kde-kf5-kwayland-testserver DESTINATION ${LIBEXEC_INSTALL_DIR} )
|
39
src/wayland/tools/testserver/main.cpp
Normal file
39
src/wayland/tools/testserver/main.cpp
Normal file
|
@ -0,0 +1,39 @@
|
|||
/********************************************************************
|
||||
Copyright 2016 Martin Gräßlin <mgraesslin@kde.org>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) version 3, or any
|
||||
later version accepted by the membership of KDE e.V. (or its
|
||||
successor approved by the membership of KDE e.V.), which shall
|
||||
act as a proxy defined in Section 6 of version 3 of the license.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************/
|
||||
#include "testserver.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication a(argc, argv);
|
||||
auto arguments = QCoreApplication::arguments();
|
||||
// get rid of our own application path
|
||||
arguments.removeFirst();
|
||||
if (arguments.size() < 1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
TestServer *server = new TestServer(&a);
|
||||
server->init();
|
||||
server->startTestApp(arguments.takeFirst(), arguments);
|
||||
|
||||
return a.exec();
|
||||
}
|
200
src/wayland/tools/testserver/testserver.cpp
Normal file
200
src/wayland/tools/testserver/testserver.cpp
Normal file
|
@ -0,0 +1,200 @@
|
|||
/********************************************************************
|
||||
Copyright 2016 Martin Gräßlin <mgraesslin@kde.org>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) version 3, or any
|
||||
later version accepted by the membership of KDE e.V. (or its
|
||||
successor approved by the membership of KDE e.V.), which shall
|
||||
act as a proxy defined in Section 6 of version 3 of the license.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************/
|
||||
#include "testserver.h"
|
||||
#include "../../server/display.h"
|
||||
#include "../../server/compositor_interface.h"
|
||||
#include "../../server/datadevicemanager_interface.h"
|
||||
#include "../../server/idle_interface.h"
|
||||
#include "../../server/fakeinput_interface.h"
|
||||
#include "../../server/seat_interface.h"
|
||||
#include "../../server/shell_interface.h"
|
||||
#include "../../server/surface_interface.h"
|
||||
#include "../../server/subcompositor_interface.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QElapsedTimer>
|
||||
#include <QProcess>
|
||||
#include <QTimer>
|
||||
// system
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
using namespace KWayland::Server;
|
||||
|
||||
TestServer::TestServer(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_repaintTimer(new QTimer(this))
|
||||
, m_timeSinceStart(new QElapsedTimer)
|
||||
, m_cursorPos(QPointF(0, 0))
|
||||
{
|
||||
}
|
||||
|
||||
TestServer::~TestServer() = default;
|
||||
|
||||
void TestServer::init()
|
||||
{
|
||||
Q_ASSERT(!m_display);
|
||||
m_display = new Display(this);
|
||||
m_display->start(Display::StartMode::ConnectClientsOnly);
|
||||
m_display->createShm();
|
||||
m_display->createCompositor()->create();
|
||||
m_shell = m_display->createShell(m_display);
|
||||
connect(m_shell, &ShellInterface::surfaceCreated, this,
|
||||
[this] (ShellSurfaceInterface *surface) {
|
||||
m_shellSurfaces << surface;
|
||||
// TODO: pass keyboard/pointer/touch focus on mapped
|
||||
connect(surface, &QObject::destroyed, this,
|
||||
[this, surface] {
|
||||
m_shellSurfaces.removeOne(surface);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
m_shell->create();
|
||||
m_seat = m_display->createSeat(m_display);
|
||||
m_seat->setHasKeyboard(true);
|
||||
m_seat->setHasPointer(true);
|
||||
m_seat->setHasTouch(true);
|
||||
m_seat->create();
|
||||
m_display->createDataDeviceManager(m_display)->create();
|
||||
m_display->createIdle(m_display)->create();
|
||||
m_display->createSubCompositor(m_display)->create();
|
||||
// output
|
||||
auto output = m_display->createOutput(m_display);
|
||||
const QSize size(1280, 1024);
|
||||
output->setGlobalPosition(QPoint(0, 0));
|
||||
output->setPhysicalSize(size / 3.8);
|
||||
output->addMode(size);
|
||||
output->create();
|
||||
|
||||
auto fakeInput = m_display->createFakeInput(m_display);
|
||||
fakeInput->create();
|
||||
connect(fakeInput, &FakeInputInterface::deviceCreated, this,
|
||||
[this] (FakeInputDevice *device) {
|
||||
device->setAuthentication(true);
|
||||
connect(device, &FakeInputDevice::pointerMotionRequested, this,
|
||||
[this] (const QSizeF &delta) {
|
||||
m_seat->setTimestamp(m_timeSinceStart->elapsed());
|
||||
m_cursorPos = m_cursorPos + QPointF(delta.width(), delta.height());
|
||||
m_seat->setPointerPos(m_cursorPos);
|
||||
}
|
||||
);
|
||||
connect(device, &FakeInputDevice::pointerButtonPressRequested, this,
|
||||
[this] (quint32 button) {
|
||||
m_seat->setTimestamp(m_timeSinceStart->elapsed());
|
||||
m_seat->pointerButtonPressed(button);
|
||||
}
|
||||
);
|
||||
connect(device, &FakeInputDevice::pointerButtonReleaseRequested, this,
|
||||
[this] (quint32 button) {
|
||||
m_seat->setTimestamp(m_timeSinceStart->elapsed());
|
||||
m_seat->pointerButtonReleased(button);
|
||||
}
|
||||
);
|
||||
connect(device, &FakeInputDevice::pointerAxisRequested, this,
|
||||
[this] (Qt::Orientation orientation, qreal delta) {
|
||||
m_seat->setTimestamp(m_timeSinceStart->elapsed());
|
||||
m_seat->pointerAxis(orientation, delta);
|
||||
}
|
||||
);
|
||||
connect(device, &FakeInputDevice::touchDownRequested, this,
|
||||
[this] (quint32 id, const QPointF &pos) {
|
||||
m_seat->setTimestamp(m_timeSinceStart->elapsed());
|
||||
m_touchIdMapper.insert(id, m_seat->touchDown(pos));
|
||||
}
|
||||
);
|
||||
connect(device, &FakeInputDevice::touchMotionRequested, this,
|
||||
[this] (quint32 id, const QPointF &pos) {
|
||||
m_seat->setTimestamp(m_timeSinceStart->elapsed());
|
||||
const auto it = m_touchIdMapper.constFind(id);
|
||||
if (it != m_touchIdMapper.constEnd()) {
|
||||
m_seat->touchMove(it.value(), pos);
|
||||
}
|
||||
}
|
||||
);
|
||||
connect(device, &FakeInputDevice::touchUpRequested, this,
|
||||
[this] (quint32 id) {
|
||||
m_seat->setTimestamp(m_timeSinceStart->elapsed());
|
||||
const auto it = m_touchIdMapper.find(id);
|
||||
if (it != m_touchIdMapper.end()) {
|
||||
m_seat->touchUp(it.value());
|
||||
m_touchIdMapper.erase(it);
|
||||
}
|
||||
}
|
||||
);
|
||||
connect(device, &FakeInputDevice::touchCancelRequested, this,
|
||||
[this] {
|
||||
m_seat->setTimestamp(m_timeSinceStart->elapsed());
|
||||
m_seat->cancelTouchSequence();
|
||||
}
|
||||
);
|
||||
connect(device, &FakeInputDevice::touchFrameRequested, this,
|
||||
[this] {
|
||||
m_seat->setTimestamp(m_timeSinceStart->elapsed());
|
||||
m_seat->touchFrame();
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
m_repaintTimer->setInterval(1000 / 60);
|
||||
connect(m_repaintTimer, &QTimer::timeout, this, &TestServer::repaint);
|
||||
m_repaintTimer->start();
|
||||
m_timeSinceStart->start();
|
||||
}
|
||||
|
||||
void TestServer::startTestApp(const QString &app, const QStringList &arguments)
|
||||
{
|
||||
int sx[2];
|
||||
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) {
|
||||
QCoreApplication::instance()->exit(1);
|
||||
return;
|
||||
}
|
||||
m_display->createClient(sx[0]);
|
||||
int socket = dup(sx[1]);
|
||||
if (socket == -1) {
|
||||
QCoreApplication::instance()->exit(1);
|
||||
return;
|
||||
}
|
||||
QProcess *p = new QProcess(this);
|
||||
p->setProcessChannelMode(QProcess::ForwardedChannels);
|
||||
QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
|
||||
environment.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("wayland"));
|
||||
environment.insert(QStringLiteral("WAYLAND_SOCKET"), QString::fromUtf8(QByteArray::number(socket)));
|
||||
p->setProcessEnvironment(environment);
|
||||
auto finishedSignal = static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished);
|
||||
connect(p, finishedSignal, QCoreApplication::instance(), &QCoreApplication::exit);
|
||||
auto errorSignal = static_cast<void (QProcess::*)(QProcess::ProcessError)>(&QProcess::error);
|
||||
connect(p, errorSignal, this,
|
||||
[] {
|
||||
QCoreApplication::instance()->exit(1);
|
||||
}
|
||||
);
|
||||
p->start(app, arguments);
|
||||
}
|
||||
|
||||
void TestServer::repaint()
|
||||
{
|
||||
for (auto it = m_shellSurfaces.constBegin(), end = m_shellSurfaces.constEnd(); it != end; ++it) {
|
||||
(*it)->surface()->frameRendered(m_timeSinceStart->elapsed());
|
||||
}
|
||||
}
|
65
src/wayland/tools/testserver/testserver.h
Normal file
65
src/wayland/tools/testserver/testserver.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
/********************************************************************
|
||||
Copyright 2016 Martin Gräßlin <mgraesslin@kde.org>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) version 3, or any
|
||||
later version accepted by the membership of KDE e.V. (or its
|
||||
successor approved by the membership of KDE e.V.), which shall
|
||||
act as a proxy defined in Section 6 of version 3 of the license.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************/
|
||||
#ifndef TESTSERVER_H
|
||||
#define TESTSERVER_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
#include <QPointF>
|
||||
#include <QVector>
|
||||
|
||||
class QElapsedTimer;
|
||||
class QTimer;
|
||||
|
||||
namespace KWayland
|
||||
{
|
||||
namespace Server
|
||||
{
|
||||
class Display;
|
||||
class SeatInterface;
|
||||
class ShellInterface;
|
||||
class ShellSurfaceInterface;
|
||||
}
|
||||
}
|
||||
|
||||
class TestServer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit TestServer(QObject *parent);
|
||||
virtual ~TestServer();
|
||||
|
||||
void init();
|
||||
void startTestApp(const QString &app, const QStringList &arguments);
|
||||
|
||||
private:
|
||||
void repaint();
|
||||
|
||||
KWayland::Server::Display *m_display = nullptr;
|
||||
KWayland::Server::ShellInterface *m_shell = nullptr;
|
||||
KWayland::Server::SeatInterface *m_seat = nullptr;
|
||||
QVector<KWayland::Server::ShellSurfaceInterface*> m_shellSurfaces;
|
||||
QTimer *m_repaintTimer;
|
||||
QScopedPointer<QElapsedTimer> m_timeSinceStart;
|
||||
QPointF m_cursorPos;
|
||||
QHash<qint32, qint32> m_touchIdMapper;
|
||||
};
|
||||
|
||||
#endif
|
Loading…
Reference in a new issue