mirror of
https://invent.kde.org/marcoa/a-la-karte.git
synced 2026-03-26 17:03:08 +00:00
630 lines
24 KiB
C++
630 lines
24 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
// SPDX-FileCopyrightText: 2026 A-La-Karte Contributors
|
|
|
|
#include <QCoreApplication>
|
|
#include <QDBusConnection>
|
|
#include <QDBusConnectionInterface>
|
|
#include <QDBusInterface>
|
|
#include <QDBusReply>
|
|
#include <QDBusVariant>
|
|
#include <QDir>
|
|
#include <QElapsedTimer>
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
#include <QProcess>
|
|
#include <QProcessEnvironment>
|
|
#include <QStandardPaths>
|
|
#include <QStringList>
|
|
#include <QTemporaryDir>
|
|
#include <QUuid>
|
|
#include <QtTest>
|
|
|
|
#include <signal.h>
|
|
#include <sys/types.h>
|
|
|
|
namespace
|
|
{
|
|
static QString takeArgValue(const QStringList &args, const QString &key)
|
|
{
|
|
const int idx = args.indexOf(key);
|
|
if (idx < 0) {
|
|
return {};
|
|
}
|
|
if (idx + 1 >= args.size()) {
|
|
return {};
|
|
}
|
|
return args.at(idx + 1);
|
|
}
|
|
|
|
static bool waitForService(QDBusConnection &bus, const QString &service, int timeoutMs)
|
|
{
|
|
if (!bus.isConnected() || !bus.interface()) {
|
|
return false;
|
|
}
|
|
|
|
QElapsedTimer t;
|
|
t.start();
|
|
while (t.elapsed() < timeoutMs) {
|
|
if (bus.interface()->isServiceRegistered(service)) {
|
|
return true;
|
|
}
|
|
QTest::qWait(50);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static QByteArray readProcFile(const QString &path, qint64 maxSize = 65536)
|
|
{
|
|
QFile f(path);
|
|
if (!f.open(QIODevice::ReadOnly)) {
|
|
return {};
|
|
}
|
|
return f.read(maxSize);
|
|
}
|
|
|
|
static bool containsNullSeparatedEntry(const QByteArray &blob, const QByteArray &needle)
|
|
{
|
|
if (blob.isEmpty() || needle.isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
int pos = -1;
|
|
while (true) {
|
|
pos = blob.indexOf(needle, pos + 1);
|
|
if (pos < 0) {
|
|
return false;
|
|
}
|
|
|
|
const bool startOk = (pos == 0) || (blob.at(pos - 1) == '\0');
|
|
const int endPos = pos + needle.size();
|
|
const bool endOk = (endPos == blob.size()) || (blob.at(endPos) == '\0');
|
|
if (startOk && endOk) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
static QList<uint> pidsWithEnvironEntry(const QByteArray &needle)
|
|
{
|
|
QList<uint> out;
|
|
const QDir proc(QStringLiteral("/proc"));
|
|
const QStringList entries = proc.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
|
for (const QString &entry : entries) {
|
|
bool ok = false;
|
|
const uint pid = entry.toUInt(&ok);
|
|
if (!ok || pid <= 1) {
|
|
continue;
|
|
}
|
|
|
|
const QByteArray env = readProcFile(QStringLiteral("/proc/%1/environ").arg(pid));
|
|
if (containsNullSeparatedEntry(env, needle)) {
|
|
out.push_back(pid);
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
static bool waitForAnyEnvironEntry(const QByteArray &needle, int timeoutMs)
|
|
{
|
|
QElapsedTimer t;
|
|
t.start();
|
|
while (t.elapsed() < timeoutMs) {
|
|
if (!pidsWithEnvironEntry(needle).isEmpty()) {
|
|
return true;
|
|
}
|
|
QTest::qWait(50);
|
|
}
|
|
return !pidsWithEnvironEntry(needle).isEmpty();
|
|
}
|
|
|
|
static bool waitForNoEnvironEntry(const QByteArray &needle, int timeoutMs)
|
|
{
|
|
QElapsedTimer t;
|
|
t.start();
|
|
while (t.elapsed() < timeoutMs) {
|
|
if (pidsWithEnvironEntry(needle).isEmpty()) {
|
|
return true;
|
|
}
|
|
QTest::qWait(150);
|
|
}
|
|
return pidsWithEnvironEntry(needle).isEmpty();
|
|
}
|
|
|
|
static void killPids(const QList<uint> &pids)
|
|
{
|
|
for (uint pid : pids) {
|
|
if (pid > 1) {
|
|
::kill(static_cast<pid_t>(pid), SIGKILL);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void stopProcess(QProcess &p)
|
|
{
|
|
if (p.state() == QProcess::NotRunning) {
|
|
return;
|
|
}
|
|
|
|
p.terminate();
|
|
if (!p.waitForFinished(3000)) {
|
|
p.kill();
|
|
p.waitForFinished(3000);
|
|
}
|
|
}
|
|
|
|
static bool waitForUnitNotActive(const QString &unitName, int timeoutMs)
|
|
{
|
|
QDBusConnection bus = QDBusConnection::sessionBus();
|
|
if (!bus.isConnected()) {
|
|
return false;
|
|
}
|
|
|
|
QDBusInterface manager(QStringLiteral("org.freedesktop.systemd1"),
|
|
QStringLiteral("/org/freedesktop/systemd1"),
|
|
QStringLiteral("org.freedesktop.systemd1.Manager"),
|
|
bus);
|
|
if (!manager.isValid()) {
|
|
return false;
|
|
}
|
|
|
|
QElapsedTimer t;
|
|
t.start();
|
|
while (t.elapsed() < timeoutMs) {
|
|
const QDBusReply<QDBusObjectPath> unitReply = manager.call(QStringLiteral("GetUnit"), unitName);
|
|
if (!unitReply.isValid()) {
|
|
if (unitReply.error().name() == QLatin1String("org.freedesktop.systemd1.NoSuchUnit")) {
|
|
return true;
|
|
}
|
|
QTest::qWait(100);
|
|
continue;
|
|
}
|
|
|
|
QDBusInterface props(QStringLiteral("org.freedesktop.systemd1"), unitReply.value().path(), QStringLiteral("org.freedesktop.DBus.Properties"), bus);
|
|
if (!props.isValid()) {
|
|
return true;
|
|
}
|
|
|
|
const QDBusReply<QVariant> stateReply =
|
|
props.call(QStringLiteral("Get"), QStringLiteral("org.freedesktop.systemd1.Unit"), QStringLiteral("ActiveState"));
|
|
if (!stateReply.isValid()) {
|
|
return true;
|
|
}
|
|
|
|
QVariant v = stateReply.value();
|
|
if (v.canConvert<QDBusVariant>()) {
|
|
v = v.value<QDBusVariant>().variant();
|
|
}
|
|
const QString state = v.toString();
|
|
if (state != QLatin1String("active") && state != QLatin1String("activating") && state != QLatin1String("deactivating")) {
|
|
return true;
|
|
}
|
|
|
|
QTest::qWait(100);
|
|
}
|
|
|
|
const QDBusReply<QDBusObjectPath> unitReply = manager.call(QStringLiteral("GetUnit"), unitName);
|
|
if (!unitReply.isValid() && unitReply.error().name() == QLatin1String("org.freedesktop.systemd1.NoSuchUnit")) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
class StopLaunchingRegressionTest : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
private Q_SLOTS:
|
|
void initTestCase();
|
|
void cleanupTestCase();
|
|
|
|
void stopWhileLaunchingSteam();
|
|
void stopByGameIdWhileLaunchingSteam();
|
|
void stopWhileLaunchingLutris();
|
|
void stopByGameIdWhileLaunchingLutris();
|
|
|
|
void stopDirectLaunch();
|
|
void stopByGameIdDirectLaunch();
|
|
|
|
private:
|
|
QString m_gamecenterPath;
|
|
QProcess m_gamecenter;
|
|
QString m_testXdgBase;
|
|
QTemporaryDir m_stubBin;
|
|
QString m_markerValue;
|
|
};
|
|
|
|
void StopLaunchingRegressionTest::initTestCase()
|
|
{
|
|
m_gamecenterPath = qApp->property("alakarte_test_gamecenter").toString();
|
|
if (m_gamecenterPath.isEmpty()) {
|
|
const QStringList args = QCoreApplication::arguments();
|
|
m_gamecenterPath = takeArgValue(args, QStringLiteral("--gamecenter"));
|
|
}
|
|
QVERIFY2(!m_gamecenterPath.isEmpty(), "--gamecenter is required");
|
|
|
|
QDBusConnection bus = QDBusConnection::sessionBus();
|
|
if (!bus.isConnected() || !bus.interface()) {
|
|
QSKIP("session bus not available");
|
|
}
|
|
|
|
QDBusInterface systemd(QStringLiteral("org.freedesktop.systemd1"),
|
|
QStringLiteral("/org/freedesktop/systemd1"),
|
|
QStringLiteral("org.freedesktop.systemd1.Manager"),
|
|
bus);
|
|
if (!systemd.isValid()) {
|
|
QSKIP("systemd user manager not available on session bus");
|
|
}
|
|
|
|
if (bus.interface()->isServiceRegistered(QStringLiteral("org.kde.GameCenter1"))) {
|
|
QSKIP("org.kde.GameCenter1 already registered on the session bus");
|
|
}
|
|
|
|
if (!m_stubBin.isValid()) {
|
|
QSKIP("failed to create temporary directory for stub executables");
|
|
}
|
|
|
|
m_markerValue = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
|
const QByteArray markerNeedle = (QStringLiteral("ALAKARTE_TEST_MARKER=") + m_markerValue).toUtf8();
|
|
killPids(pidsWithEnvironEntry(markerNeedle));
|
|
|
|
m_testXdgBase = QDir::tempPath() + QStringLiteral("/alakarte-stop-launching-test-xdg-") + QUuid::createUuid().toString(QUuid::WithoutBraces);
|
|
QVERIFY(QDir().mkpath(m_testXdgBase + QStringLiteral("/data")));
|
|
QVERIFY(QDir().mkpath(m_testXdgBase + QStringLiteral("/config")));
|
|
QVERIFY(QDir().mkpath(m_testXdgBase + QStringLiteral("/cache")));
|
|
|
|
const QString steamPath = m_stubBin.path() + QStringLiteral("/steam");
|
|
{
|
|
QFile steam(steamPath);
|
|
QVERIFY(steam.open(QIODevice::WriteOnly | QIODevice::Truncate));
|
|
const QByteArray script =
|
|
"#!/bin/sh\n"
|
|
"appid=\"\"\n"
|
|
"while [ $# -gt 0 ]; do\n"
|
|
" if [ \"$1\" = \"-applaunch\" ] && [ $# -ge 2 ]; then appid=\"$2\"; break; fi\n"
|
|
" shift\n"
|
|
"done\n"
|
|
"if [ -z \"$appid\" ]; then appid=\"123456\"; fi\n"
|
|
"marker=\"${ALAKARTE_TEST_MARKER_VALUE}\"\n"
|
|
"nohup sh -c \"sleep 1; env SteamAppId=$appid SteamGameId=$appid ALAKARTE_TEST_MARKER=$marker /bin/sleep 60\" >/dev/null 2>&1 &\n"
|
|
"exit 0\n";
|
|
QVERIFY(steam.write(script) == script.size());
|
|
}
|
|
QVERIFY(QFile::setPermissions(steamPath,
|
|
QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner | QFileDevice::ReadGroup | QFileDevice::ExeGroup
|
|
| QFileDevice::ReadOther | QFileDevice::ExeOther));
|
|
|
|
const QString lutrisPath = m_stubBin.path() + QStringLiteral("/lutris");
|
|
{
|
|
QFile lutris(lutrisPath);
|
|
QVERIFY(lutris.open(QIODevice::WriteOnly | QIODevice::Truncate));
|
|
const QByteArray script =
|
|
"#!/bin/sh\n"
|
|
"id=\"\"\n"
|
|
"if [ $# -ge 1 ]; then\n"
|
|
" id=\"$1\"\n"
|
|
"fi\n"
|
|
"case \"$id\" in\n"
|
|
" lutris:rungameid/*) id=\"${id##*/}\" ;;\n"
|
|
" lutris:rungame/*) id=\"${id##*/}\" ;;\n"
|
|
"esac\n"
|
|
"if [ -z \"$id\" ]; then id=\"test-lutris\"; fi\n"
|
|
"marker=\"${ALAKARTE_TEST_MARKER_VALUE}\"\n"
|
|
"nohup sh -c \"sleep 1; env LUTRIS_GAME_SLUG=$id ALAKARTE_TEST_MARKER=$marker /bin/sleep 60\" >/dev/null 2>&1 &\n"
|
|
"exit 0\n";
|
|
QVERIFY(lutris.write(script) == script.size());
|
|
}
|
|
QVERIFY(QFile::setPermissions(lutrisPath,
|
|
QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner | QFileDevice::ReadGroup | QFileDevice::ExeGroup
|
|
| QFileDevice::ReadOther | QFileDevice::ExeOther));
|
|
|
|
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
|
env.insert(QStringLiteral("PATH"), m_stubBin.path() + QStringLiteral(":") + env.value(QStringLiteral("PATH")));
|
|
env.insert(QStringLiteral("ALAKARTE_TEST_MARKER_VALUE"), m_markerValue);
|
|
env.insert(QStringLiteral("XDG_DATA_HOME"), m_testXdgBase + QStringLiteral("/data"));
|
|
env.insert(QStringLiteral("XDG_CONFIG_HOME"), m_testXdgBase + QStringLiteral("/config"));
|
|
env.insert(QStringLiteral("XDG_CACHE_HOME"), m_testXdgBase + QStringLiteral("/cache"));
|
|
|
|
m_gamecenter.setProcessEnvironment(env);
|
|
m_gamecenter.setProgram(m_gamecenterPath);
|
|
m_gamecenter.setArguments({});
|
|
m_gamecenter.setProcessChannelMode(QProcess::MergedChannels);
|
|
m_gamecenter.start();
|
|
QVERIFY(m_gamecenter.waitForStarted(5000));
|
|
|
|
QVERIFY2(waitForService(bus, QStringLiteral("org.kde.GameCenter1"), 5000), "GameCenter1 service did not appear on the session bus");
|
|
}
|
|
|
|
void StopLaunchingRegressionTest::cleanupTestCase()
|
|
{
|
|
stopProcess(m_gamecenter);
|
|
|
|
if (!m_markerValue.isEmpty()) {
|
|
const QByteArray markerNeedle = (QStringLiteral("ALAKARTE_TEST_MARKER=") + m_markerValue).toUtf8();
|
|
killPids(pidsWithEnvironEntry(markerNeedle));
|
|
m_markerValue.clear();
|
|
}
|
|
|
|
if (!m_testXdgBase.isEmpty()) {
|
|
QDir(m_testXdgBase).removeRecursively();
|
|
m_testXdgBase.clear();
|
|
}
|
|
}
|
|
|
|
void StopLaunchingRegressionTest::stopWhileLaunchingSteam()
|
|
{
|
|
QDBusConnection bus = QDBusConnection::sessionBus();
|
|
if (!bus.isConnected()) {
|
|
QSKIP("session bus not available");
|
|
}
|
|
|
|
QDBusInterface iface(QStringLiteral("org.kde.GameCenter1"), QStringLiteral("/org/kde/GameCenter1"), QStringLiteral("org.kde.GameCenter1"), bus);
|
|
QVERIFY2(iface.isValid(), "GameCenter1 DBus interface not valid");
|
|
|
|
const QString appId = QStringLiteral("987654");
|
|
QVariantMap spec;
|
|
spec.insert(QStringLiteral("provider"), QStringLiteral("steam"));
|
|
spec.insert(QStringLiteral("command"), QStringLiteral("steam -applaunch %1").arg(appId));
|
|
spec.insert(QStringLiteral("gameId"), QStringLiteral("test-steam-%1").arg(appId));
|
|
|
|
QDBusReply<QString> launchReply = iface.call(QStringLiteral("Launch"), spec);
|
|
QVERIFY2(launchReply.isValid(), qPrintable(launchReply.error().message()));
|
|
const QString sessionId = launchReply.value();
|
|
QVERIFY2(!sessionId.isEmpty(), "Launch returned empty sessionId");
|
|
const QString unitName = QStringLiteral("alakarte-game-%1.scope").arg(sessionId);
|
|
|
|
QDBusReply<void> stopReply = iface.call(QStringLiteral("Stop"), sessionId);
|
|
QVERIFY2(stopReply.isValid(), qPrintable(stopReply.error().message()));
|
|
|
|
const QByteArray markerNeedle = (QStringLiteral("ALAKARTE_TEST_MARKER=") + m_markerValue).toUtf8();
|
|
waitForAnyEnvironEntry(markerNeedle, 5000);
|
|
|
|
const bool cleaned = waitForNoEnvironEntry(markerNeedle, 15000);
|
|
if (!cleaned) {
|
|
const QList<uint> pids = pidsWithEnvironEntry(markerNeedle);
|
|
killPids(pids);
|
|
}
|
|
QVERIFY2(cleaned, "marker process still alive after Stop() during Launching");
|
|
|
|
QVERIFY2(waitForUnitNotActive(unitName, 15000), "systemd unit still active after Stop() during Launching");
|
|
}
|
|
|
|
void StopLaunchingRegressionTest::stopByGameIdWhileLaunchingSteam()
|
|
{
|
|
QDBusConnection bus = QDBusConnection::sessionBus();
|
|
if (!bus.isConnected()) {
|
|
QSKIP("session bus not available");
|
|
}
|
|
|
|
QDBusInterface iface(QStringLiteral("org.kde.GameCenter1"), QStringLiteral("/org/kde/GameCenter1"), QStringLiteral("org.kde.GameCenter1"), bus);
|
|
QVERIFY2(iface.isValid(), "GameCenter1 DBus interface not valid");
|
|
|
|
const QByteArray markerNeedle = (QStringLiteral("ALAKARTE_TEST_MARKER=") + m_markerValue).toUtf8();
|
|
killPids(pidsWithEnvironEntry(markerNeedle));
|
|
|
|
const QString appId = QStringLiteral("876543");
|
|
const QString gameId = QStringLiteral("test-steam-stop-by-gameid-%1").arg(QUuid::createUuid().toString(QUuid::WithoutBraces));
|
|
|
|
QVariantMap spec;
|
|
spec.insert(QStringLiteral("provider"), QStringLiteral("steam"));
|
|
spec.insert(QStringLiteral("command"), QStringLiteral("steam -applaunch %1").arg(appId));
|
|
spec.insert(QStringLiteral("gameId"), gameId);
|
|
|
|
QDBusReply<QString> launchReply = iface.call(QStringLiteral("Launch"), spec);
|
|
QVERIFY2(launchReply.isValid(), qPrintable(launchReply.error().message()));
|
|
const QString sessionId = launchReply.value();
|
|
QVERIFY2(!sessionId.isEmpty(), "Launch returned empty sessionId");
|
|
const QString unitName = QStringLiteral("alakarte-game-%1.scope").arg(sessionId);
|
|
|
|
QDBusReply<void> stopReply = iface.call(QStringLiteral("StopByGameId"), gameId);
|
|
QVERIFY2(stopReply.isValid(), qPrintable(stopReply.error().message()));
|
|
|
|
waitForAnyEnvironEntry(markerNeedle, 5000);
|
|
|
|
const bool cleaned = waitForNoEnvironEntry(markerNeedle, 15000);
|
|
if (!cleaned) {
|
|
const QList<uint> pids = pidsWithEnvironEntry(markerNeedle);
|
|
killPids(pids);
|
|
}
|
|
QVERIFY2(cleaned, "marker process still alive after StopByGameId() during Launching");
|
|
|
|
QVERIFY2(waitForUnitNotActive(unitName, 15000), "systemd unit still active after StopByGameId() during Launching");
|
|
}
|
|
|
|
void StopLaunchingRegressionTest::stopWhileLaunchingLutris()
|
|
{
|
|
QDBusConnection bus = QDBusConnection::sessionBus();
|
|
if (!bus.isConnected()) {
|
|
QSKIP("session bus not available");
|
|
}
|
|
|
|
QDBusInterface iface(QStringLiteral("org.kde.GameCenter1"), QStringLiteral("/org/kde/GameCenter1"), QStringLiteral("org.kde.GameCenter1"), bus);
|
|
QVERIFY2(iface.isValid(), "GameCenter1 DBus interface not valid");
|
|
|
|
const QByteArray markerNeedle = (QStringLiteral("ALAKARTE_TEST_MARKER=") + m_markerValue).toUtf8();
|
|
killPids(pidsWithEnvironEntry(markerNeedle));
|
|
|
|
const QString lutrisId = QStringLiteral("rungameid/%1").arg(QStringLiteral("lutris-test-1"));
|
|
QVariantMap spec;
|
|
spec.insert(QStringLiteral("provider"), QStringLiteral("lutris"));
|
|
spec.insert(QStringLiteral("command"), QStringLiteral("lutris lutris:%1").arg(lutrisId));
|
|
spec.insert(QStringLiteral("gameId"), QStringLiteral("test-lutris-%1").arg(QUuid::createUuid().toString(QUuid::WithoutBraces)));
|
|
|
|
QDBusReply<QString> launchReply = iface.call(QStringLiteral("Launch"), spec);
|
|
QVERIFY2(launchReply.isValid(), qPrintable(launchReply.error().message()));
|
|
const QString sessionId = launchReply.value();
|
|
QVERIFY2(!sessionId.isEmpty(), "Launch returned empty sessionId");
|
|
const QString unitName = QStringLiteral("alakarte-game-%1.scope").arg(sessionId);
|
|
|
|
QDBusReply<void> stopReply = iface.call(QStringLiteral("Stop"), sessionId);
|
|
QVERIFY2(stopReply.isValid(), qPrintable(stopReply.error().message()));
|
|
|
|
waitForAnyEnvironEntry(markerNeedle, 5000);
|
|
const bool cleaned = waitForNoEnvironEntry(markerNeedle, 15000);
|
|
if (!cleaned) {
|
|
killPids(pidsWithEnvironEntry(markerNeedle));
|
|
}
|
|
QVERIFY2(cleaned, "marker process still alive after Stop() during Launching (lutris)");
|
|
|
|
QVERIFY2(waitForUnitNotActive(unitName, 15000), "systemd unit still active after Stop() during Launching (lutris)");
|
|
}
|
|
|
|
void StopLaunchingRegressionTest::stopByGameIdWhileLaunchingLutris()
|
|
{
|
|
QDBusConnection bus = QDBusConnection::sessionBus();
|
|
if (!bus.isConnected()) {
|
|
QSKIP("session bus not available");
|
|
}
|
|
|
|
QDBusInterface iface(QStringLiteral("org.kde.GameCenter1"), QStringLiteral("/org/kde/GameCenter1"), QStringLiteral("org.kde.GameCenter1"), bus);
|
|
QVERIFY2(iface.isValid(), "GameCenter1 DBus interface not valid");
|
|
|
|
const QByteArray markerNeedle = (QStringLiteral("ALAKARTE_TEST_MARKER=") + m_markerValue).toUtf8();
|
|
killPids(pidsWithEnvironEntry(markerNeedle));
|
|
|
|
const QString gameId = QStringLiteral("test-lutris-stop-by-gameid-%1").arg(QUuid::createUuid().toString(QUuid::WithoutBraces));
|
|
QVariantMap spec;
|
|
spec.insert(QStringLiteral("provider"), QStringLiteral("lutris"));
|
|
spec.insert(QStringLiteral("command"), QStringLiteral("lutris lutris:rungameid/%1").arg(QStringLiteral("lutris-test-2")));
|
|
spec.insert(QStringLiteral("gameId"), gameId);
|
|
|
|
QDBusReply<QString> launchReply = iface.call(QStringLiteral("Launch"), spec);
|
|
QVERIFY2(launchReply.isValid(), qPrintable(launchReply.error().message()));
|
|
const QString sessionId = launchReply.value();
|
|
QVERIFY2(!sessionId.isEmpty(), "Launch returned empty sessionId");
|
|
const QString unitName = QStringLiteral("alakarte-game-%1.scope").arg(sessionId);
|
|
|
|
QDBusReply<void> stopReply = iface.call(QStringLiteral("StopByGameId"), gameId);
|
|
QVERIFY2(stopReply.isValid(), qPrintable(stopReply.error().message()));
|
|
|
|
waitForAnyEnvironEntry(markerNeedle, 5000);
|
|
const bool cleaned = waitForNoEnvironEntry(markerNeedle, 15000);
|
|
if (!cleaned) {
|
|
killPids(pidsWithEnvironEntry(markerNeedle));
|
|
}
|
|
QVERIFY2(cleaned, "marker process still alive after StopByGameId() during Launching (lutris)");
|
|
|
|
QVERIFY2(waitForUnitNotActive(unitName, 15000), "systemd unit still active after StopByGameId() during Launching (lutris)");
|
|
}
|
|
|
|
void StopLaunchingRegressionTest::stopDirectLaunch()
|
|
{
|
|
QDBusConnection bus = QDBusConnection::sessionBus();
|
|
if (!bus.isConnected()) {
|
|
QSKIP("session bus not available");
|
|
}
|
|
|
|
QDBusInterface iface(QStringLiteral("org.kde.GameCenter1"), QStringLiteral("/org/kde/GameCenter1"), QStringLiteral("org.kde.GameCenter1"), bus);
|
|
QVERIFY2(iface.isValid(), "GameCenter1 DBus interface not valid");
|
|
|
|
const QByteArray markerNeedle = (QStringLiteral("ALAKARTE_TEST_MARKER=") + m_markerValue).toUtf8();
|
|
killPids(pidsWithEnvironEntry(markerNeedle));
|
|
|
|
const QString gameId = QStringLiteral("test-direct-stop-%1").arg(QUuid::createUuid().toString(QUuid::WithoutBraces));
|
|
|
|
const QString envExe = QStandardPaths::findExecutable(QStringLiteral("env"));
|
|
if (envExe.isEmpty()) {
|
|
QSKIP("env executable not found");
|
|
}
|
|
|
|
QVariantMap spec;
|
|
spec.insert(QStringLiteral("provider"), QStringLiteral("manual"));
|
|
spec.insert(QStringLiteral("program"), envExe);
|
|
spec.insert(QStringLiteral("args"),
|
|
QStringList{QStringLiteral("ALAKARTE_TEST_MARKER=%1").arg(m_markerValue), QStringLiteral("/bin/sleep"), QStringLiteral("60")});
|
|
spec.insert(QStringLiteral("gameId"), gameId);
|
|
|
|
QDBusReply<QString> launchReply = iface.call(QStringLiteral("Launch"), spec);
|
|
QVERIFY2(launchReply.isValid(), qPrintable(launchReply.error().message()));
|
|
const QString sessionId = launchReply.value();
|
|
QVERIFY2(!sessionId.isEmpty(), "Launch returned empty sessionId");
|
|
const QString unitName = QStringLiteral("alakarte-game-%1.scope").arg(sessionId);
|
|
|
|
QVERIFY2(waitForAnyEnvironEntry(markerNeedle, 5000), "direct launch marker process did not appear");
|
|
|
|
QDBusReply<void> stopReply = iface.call(QStringLiteral("Stop"), sessionId);
|
|
QVERIFY2(stopReply.isValid(), qPrintable(stopReply.error().message()));
|
|
|
|
const bool cleaned = waitForNoEnvironEntry(markerNeedle, 15000);
|
|
if (!cleaned) {
|
|
killPids(pidsWithEnvironEntry(markerNeedle));
|
|
}
|
|
QVERIFY2(cleaned, "marker process still alive after Stop() (direct launch)");
|
|
|
|
QVERIFY2(waitForUnitNotActive(unitName, 15000), "systemd unit still active after Stop() (direct launch)");
|
|
}
|
|
|
|
void StopLaunchingRegressionTest::stopByGameIdDirectLaunch()
|
|
{
|
|
QDBusConnection bus = QDBusConnection::sessionBus();
|
|
if (!bus.isConnected()) {
|
|
QSKIP("session bus not available");
|
|
}
|
|
|
|
QDBusInterface iface(QStringLiteral("org.kde.GameCenter1"), QStringLiteral("/org/kde/GameCenter1"), QStringLiteral("org.kde.GameCenter1"), bus);
|
|
QVERIFY2(iface.isValid(), "GameCenter1 DBus interface not valid");
|
|
|
|
const QByteArray markerNeedle = (QStringLiteral("ALAKARTE_TEST_MARKER=") + m_markerValue).toUtf8();
|
|
killPids(pidsWithEnvironEntry(markerNeedle));
|
|
|
|
const QString gameId = QStringLiteral("test-direct-stop-by-gameid-%1").arg(QUuid::createUuid().toString(QUuid::WithoutBraces));
|
|
|
|
const QString envExe = QStandardPaths::findExecutable(QStringLiteral("env"));
|
|
if (envExe.isEmpty()) {
|
|
QSKIP("env executable not found");
|
|
}
|
|
|
|
QVariantMap spec;
|
|
spec.insert(QStringLiteral("provider"), QStringLiteral("manual"));
|
|
spec.insert(QStringLiteral("program"), envExe);
|
|
spec.insert(QStringLiteral("args"),
|
|
QStringList{QStringLiteral("ALAKARTE_TEST_MARKER=%1").arg(m_markerValue), QStringLiteral("/bin/sleep"), QStringLiteral("60")});
|
|
spec.insert(QStringLiteral("gameId"), gameId);
|
|
|
|
QDBusReply<QString> launchReply = iface.call(QStringLiteral("Launch"), spec);
|
|
QVERIFY2(launchReply.isValid(), qPrintable(launchReply.error().message()));
|
|
const QString sessionId = launchReply.value();
|
|
QVERIFY2(!sessionId.isEmpty(), "Launch returned empty sessionId");
|
|
const QString unitName = QStringLiteral("alakarte-game-%1.scope").arg(sessionId);
|
|
|
|
QVERIFY2(waitForAnyEnvironEntry(markerNeedle, 5000), "direct launch marker process did not appear");
|
|
|
|
QDBusReply<void> stopReply = iface.call(QStringLiteral("StopByGameId"), gameId);
|
|
QVERIFY2(stopReply.isValid(), qPrintable(stopReply.error().message()));
|
|
|
|
const bool cleaned = waitForNoEnvironEntry(markerNeedle, 15000);
|
|
if (!cleaned) {
|
|
killPids(pidsWithEnvironEntry(markerNeedle));
|
|
}
|
|
QVERIFY2(cleaned, "marker process still alive after StopByGameId() (direct launch)");
|
|
|
|
QVERIFY2(waitForUnitNotActive(unitName, 15000), "systemd unit still active after StopByGameId() (direct launch)");
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
QCoreApplication app(argc, argv);
|
|
|
|
const QStringList args = app.arguments();
|
|
const QString gamecenterPath = takeArgValue(args, QStringLiteral("--gamecenter"));
|
|
app.setProperty("alakarte_test_gamecenter", gamecenterPath);
|
|
|
|
QStringList filtered;
|
|
filtered.reserve(args.size());
|
|
if (!args.isEmpty()) {
|
|
filtered.push_back(args.first());
|
|
}
|
|
for (int i = 1; i < args.size(); ++i) {
|
|
const QString a = args.at(i);
|
|
if (a == QLatin1String("--gamecenter")) {
|
|
++i;
|
|
continue;
|
|
}
|
|
filtered.push_back(a);
|
|
}
|
|
|
|
StopLaunchingRegressionTest tc;
|
|
return QTest::qExec(&tc, filtered);
|
|
}
|
|
|
|
#include "stop_launching_regression_test.moc"
|