a-la-karte/tests/stop_launching_regression_test.cpp

726 lines
28 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2026 A-La-Karte Contributors
#include <QCoreApplication>
#include <QDBusArgument>
#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 QVariant unwrapDbusVariant(QVariant v)
{
if (v.canConvert<QDBusVariant>()) {
v = v.value<QDBusVariant>().variant();
}
return v;
}
static QVariantMap unwrapVariantMap(QVariant v)
{
v = unwrapDbusVariant(v);
if (v.canConvert<QDBusArgument>()) {
const QDBusArgument arg = v.value<QDBusArgument>();
return qdbus_cast<QVariantMap>(arg);
}
if (v.canConvert<QVariantMap>()) {
return v.toMap();
}
return {};
}
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();
void recoverSessionsAfterDaemonRestart();
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::recoverSessionsAfterDaemonRestart()
{
QDBusConnection bus = QDBusConnection::sessionBus();
if (!bus.isConnected()) {
QSKIP("session bus not available");
}
QDBusInterface iface(QStringLiteral("org.kde.GameCenter1"), QStringLiteral("/org/kde/ALaKarte/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 envExe = QStandardPaths::findExecutable(QStringLiteral("env"));
if (envExe.isEmpty()) {
QSKIP("env executable not found");
}
const QString gameId = QStringLiteral("test-reattach-%1").arg(QUuid::createUuid().toString(QUuid::WithoutBraces));
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);
const 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");
m_gamecenter.kill();
QVERIFY(m_gamecenter.waitForFinished(5000));
QVERIFY2(waitForNoEnvironEntry(markerNeedle, 1000) == false, "marker process unexpectedly disappeared after daemon kill");
m_gamecenter.start();
QVERIFY(m_gamecenter.waitForStarted(5000));
QVERIFY2(waitForService(bus, QStringLiteral("org.kde.GameCenter1"), 5000), "GameCenter1 service did not re-appear after restart");
QDBusInterface iface2(QStringLiteral("org.kde.GameCenter1"), QStringLiteral("/org/kde/ALaKarte/GameCenter1"), QStringLiteral("org.kde.GameCenter1"), bus);
QVERIFY2(iface2.isValid(), "GameCenter1 DBus interface not valid after restart");
const QDBusReply<QVariantList> listReply = iface2.call(QStringLiteral("ListSessions"));
QVERIFY2(listReply.isValid(), qPrintable(listReply.error().message()));
bool found = false;
for (const QVariant &v : listReply.value()) {
const QVariantMap m = unwrapVariantMap(v);
if (m.value(QStringLiteral("sessionId")).toString() == sessionId) {
found = true;
QCOMPARE(m.value(QStringLiteral("gameId")).toString(), gameId);
QCOMPARE(m.value(QStringLiteral("state")).toString(), QStringLiteral("Running"));
break;
}
}
QVERIFY2(found, "expected recovered session not found in ListSessions() after restart");
const QDBusReply<void> stopReply = iface2.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() on recovered session");
QVERIFY2(waitForUnitNotActive(unitName, 15000), "systemd unit still active after Stop() on recovered session");
}
void StopLaunchingRegressionTest::stopWhileLaunchingSteam()
{
QDBusConnection bus = QDBusConnection::sessionBus();
if (!bus.isConnected()) {
QSKIP("session bus not available");
}
QDBusInterface iface(QStringLiteral("org.kde.GameCenter1"), QStringLiteral("/org/kde/ALaKarte/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/ALaKarte/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/ALaKarte/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/ALaKarte/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/ALaKarte/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/ALaKarte/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"