mirror of
https://invent.kde.org/marcoa/a-la-karte.git
synced 2026-03-26 17:03:08 +00:00
438 lines
15 KiB
C++
438 lines
15 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 <QProcess>
|
|
#include <QProcessEnvironment>
|
|
#include <QStandardPaths>
|
|
#include <QStringList>
|
|
#include <QUuid>
|
|
#include <QtTest>
|
|
|
|
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.metaType() == QMetaType::fromType<QDBusVariant>()) {
|
|
v = v.value<QDBusVariant>().variant();
|
|
}
|
|
return v;
|
|
}
|
|
|
|
static QVariantMap unwrapVariantMap(QVariant v)
|
|
{
|
|
v = unwrapDbusVariant(v);
|
|
if (v.metaType() == QMetaType::fromType<QDBusArgument>()) {
|
|
const QDBusArgument arg = v.value<QDBusArgument>();
|
|
return qdbus_cast<QVariantMap>(arg);
|
|
}
|
|
if (v.canConvert<QVariantMap>()) {
|
|
return v.toMap();
|
|
}
|
|
return {};
|
|
}
|
|
}
|
|
|
|
class DbusSmokeTest : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
private Q_SLOTS:
|
|
void initTestCase();
|
|
void cleanupTestCase();
|
|
|
|
void pingGameCenter();
|
|
void pingRunner();
|
|
void pingInput();
|
|
void runnerResolveLaunchNative();
|
|
void runnerGameProfiles();
|
|
|
|
private:
|
|
QString m_gamecenterPath;
|
|
QString m_runnerdPath;
|
|
QString m_inputdPath;
|
|
|
|
QString m_dbusAddress;
|
|
QProcess m_dbusDaemon;
|
|
QProcess m_gamecenter;
|
|
QProcess m_runnerd;
|
|
QProcess m_inputd;
|
|
|
|
QString m_testXdgBase;
|
|
|
|
QDBusConnection m_bus = QDBusConnection::connectToBus(QString(), QStringLiteral("alakarte_test"));
|
|
|
|
bool startPrivateBus();
|
|
bool haveDbusDaemon() const;
|
|
void startDaemon(QProcess &p, const QString &program);
|
|
void stopProcess(QProcess &p);
|
|
};
|
|
|
|
bool DbusSmokeTest::haveDbusDaemon() const
|
|
{
|
|
return !QStandardPaths::findExecutable(QStringLiteral("dbus-daemon")).isEmpty();
|
|
}
|
|
|
|
bool DbusSmokeTest::startPrivateBus()
|
|
{
|
|
const QString dbusDaemon = QStandardPaths::findExecutable(QStringLiteral("dbus-daemon"));
|
|
if (dbusDaemon.isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
m_dbusDaemon.setProgram(dbusDaemon);
|
|
m_dbusDaemon.setArguments({QStringLiteral("--session"), QStringLiteral("--nofork"), QStringLiteral("--print-address=1")});
|
|
m_dbusDaemon.setProcessChannelMode(QProcess::MergedChannels);
|
|
m_dbusDaemon.start();
|
|
if (!m_dbusDaemon.waitForStarted(5000)) {
|
|
return false;
|
|
}
|
|
|
|
if (!m_dbusDaemon.waitForReadyRead(5000)) {
|
|
return false;
|
|
}
|
|
|
|
m_dbusAddress = QString::fromUtf8(m_dbusDaemon.readLine()).trimmed();
|
|
if (m_dbusAddress.isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
QDBusConnection::disconnectFromBus(QStringLiteral("alakarte_test"));
|
|
m_bus = QDBusConnection::connectToBus(m_dbusAddress, QStringLiteral("alakarte_test"));
|
|
return m_bus.isConnected();
|
|
}
|
|
|
|
void DbusSmokeTest::startDaemon(QProcess &p, const QString &program)
|
|
{
|
|
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
|
env.insert(QStringLiteral("DBUS_SESSION_BUS_ADDRESS"), m_dbusAddress);
|
|
if (!m_testXdgBase.isEmpty()) {
|
|
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"));
|
|
}
|
|
p.setProcessEnvironment(env);
|
|
p.setProgram(program);
|
|
p.setArguments({});
|
|
p.setProcessChannelMode(QProcess::MergedChannels);
|
|
p.start();
|
|
}
|
|
|
|
void DbusSmokeTest::stopProcess(QProcess &p)
|
|
{
|
|
if (p.state() == QProcess::NotRunning) {
|
|
return;
|
|
}
|
|
|
|
p.terminate();
|
|
if (!p.waitForFinished(3000)) {
|
|
p.kill();
|
|
p.waitForFinished(3000);
|
|
}
|
|
}
|
|
|
|
void DbusSmokeTest::initTestCase()
|
|
{
|
|
m_gamecenterPath = qApp ? qApp->property("alakarte_test_gamecenter").toString() : QString();
|
|
m_runnerdPath = qApp ? qApp->property("alakarte_test_runnerd").toString() : QString();
|
|
m_inputdPath = qApp ? qApp->property("alakarte_test_inputd").toString() : QString();
|
|
|
|
if (m_gamecenterPath.isEmpty() || m_runnerdPath.isEmpty() || m_inputdPath.isEmpty()) {
|
|
const QStringList args = QCoreApplication::arguments();
|
|
if (m_gamecenterPath.isEmpty()) {
|
|
m_gamecenterPath = takeArgValue(args, QStringLiteral("--gamecenter"));
|
|
}
|
|
if (m_runnerdPath.isEmpty()) {
|
|
m_runnerdPath = takeArgValue(args, QStringLiteral("--runnerd"));
|
|
}
|
|
if (m_inputdPath.isEmpty()) {
|
|
m_inputdPath = takeArgValue(args, QStringLiteral("--inputd"));
|
|
}
|
|
}
|
|
|
|
QVERIFY2(!m_gamecenterPath.isEmpty(), "--gamecenter is required");
|
|
QVERIFY2(!m_runnerdPath.isEmpty(), "--runnerd is required");
|
|
QVERIFY2(!m_inputdPath.isEmpty(), "--inputd is required");
|
|
|
|
if (!haveDbusDaemon()) {
|
|
QSKIP("dbus-daemon not found");
|
|
}
|
|
|
|
if (!startPrivateBus()) {
|
|
QSKIP("failed to start private dbus session");
|
|
}
|
|
|
|
m_testXdgBase = QDir::tempPath() + QStringLiteral("/alakarte-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")));
|
|
|
|
startDaemon(m_runnerd, m_runnerdPath);
|
|
QVERIFY(m_runnerd.waitForStarted(5000));
|
|
|
|
startDaemon(m_inputd, m_inputdPath);
|
|
QVERIFY(m_inputd.waitForStarted(5000));
|
|
|
|
startDaemon(m_gamecenter, m_gamecenterPath);
|
|
QVERIFY(m_gamecenter.waitForStarted(5000));
|
|
|
|
QVERIFY2(waitForService(m_bus, QStringLiteral("org.kde.ALaKarte.Runner1"), 5000), "Runner1 service did not appear on the bus");
|
|
QVERIFY2(waitForService(m_bus, QStringLiteral("org.kde.ALaKarte.Input1"), 5000), "Input1 service did not appear on the bus");
|
|
QVERIFY2(waitForService(m_bus, QStringLiteral("org.kde.GameCenter1"), 5000), "GameCenter1 service did not appear on the bus");
|
|
}
|
|
|
|
void DbusSmokeTest::cleanupTestCase()
|
|
{
|
|
stopProcess(m_gamecenter);
|
|
stopProcess(m_inputd);
|
|
stopProcess(m_runnerd);
|
|
stopProcess(m_dbusDaemon);
|
|
QDBusConnection::disconnectFromBus(QStringLiteral("alakarte_test"));
|
|
|
|
if (!m_testXdgBase.isEmpty()) {
|
|
QDir(m_testXdgBase).removeRecursively();
|
|
m_testXdgBase.clear();
|
|
}
|
|
}
|
|
|
|
void DbusSmokeTest::pingGameCenter()
|
|
{
|
|
QDBusInterface iface(QStringLiteral("org.kde.GameCenter1"), QStringLiteral("/org/kde/GameCenter1"), QStringLiteral("org.kde.GameCenter1"), m_bus);
|
|
QVERIFY(iface.isValid());
|
|
iface.setTimeout(2000);
|
|
|
|
const QDBusReply<QString> reply = iface.call(QStringLiteral("Ping"));
|
|
QVERIFY(reply.isValid());
|
|
QCOMPARE(reply.value(), QStringLiteral("ok"));
|
|
}
|
|
|
|
void DbusSmokeTest::pingRunner()
|
|
{
|
|
QDBusInterface iface(QStringLiteral("org.kde.ALaKarte.Runner1"),
|
|
QStringLiteral("/org/kde/ALaKarte/Runner1"),
|
|
QStringLiteral("org.kde.ALaKarte.Runner1"),
|
|
m_bus);
|
|
QVERIFY(iface.isValid());
|
|
iface.setTimeout(2000);
|
|
|
|
const QDBusReply<QString> reply = iface.call(QStringLiteral("Ping"));
|
|
QVERIFY(reply.isValid());
|
|
QCOMPARE(reply.value(), QStringLiteral("ok"));
|
|
}
|
|
|
|
void DbusSmokeTest::pingInput()
|
|
{
|
|
QDBusInterface iface(QStringLiteral("org.kde.ALaKarte.Input1"),
|
|
QStringLiteral("/org/kde/ALaKarte/Input1"),
|
|
QStringLiteral("org.kde.ALaKarte.Input1"),
|
|
m_bus);
|
|
QVERIFY(iface.isValid());
|
|
iface.setTimeout(2000);
|
|
|
|
const QDBusReply<QString> reply = iface.call(QStringLiteral("Ping"));
|
|
QVERIFY(reply.isValid());
|
|
QCOMPARE(reply.value(), QStringLiteral("ok"));
|
|
}
|
|
|
|
void DbusSmokeTest::runnerResolveLaunchNative()
|
|
{
|
|
QDBusInterface iface(QStringLiteral("org.kde.ALaKarte.Runner1"),
|
|
QStringLiteral("/org/kde/ALaKarte/Runner1"),
|
|
QStringLiteral("org.kde.ALaKarte.Runner1"),
|
|
m_bus);
|
|
QVERIFY(iface.isValid());
|
|
iface.setTimeout(2000);
|
|
|
|
QVariantMap spec;
|
|
spec.insert(QStringLiteral("program"), QStringLiteral("/bin/true"));
|
|
spec.insert(QStringLiteral("args"), QStringList{});
|
|
|
|
const QDBusReply<QVariantMap> reply = iface.call(QStringLiteral("ResolveLaunch"), spec);
|
|
QVERIFY(reply.isValid());
|
|
|
|
const QVariantMap out = reply.value();
|
|
QVERIFY(out.value(QStringLiteral("ok")).toBool());
|
|
QCOMPARE(out.value(QStringLiteral("finalProgram")).toString(), QStringLiteral("/bin/true"));
|
|
QCOMPARE(out.value(QStringLiteral("finalArgs")).toStringList(), QStringList{});
|
|
}
|
|
|
|
void DbusSmokeTest::runnerGameProfiles()
|
|
{
|
|
QDBusInterface iface(QStringLiteral("org.kde.ALaKarte.Runner1"),
|
|
QStringLiteral("/org/kde/ALaKarte/Runner1"),
|
|
QStringLiteral("org.kde.ALaKarte.Runner1"),
|
|
m_bus);
|
|
QVERIFY(iface.isValid());
|
|
iface.setTimeout(2000);
|
|
|
|
const QString gameId = QStringLiteral("alakarte-test-game");
|
|
|
|
{
|
|
const QDBusReply<QVariantMap> reply = iface.call(QStringLiteral("ClearGameProfile"), gameId);
|
|
QVERIFY(reply.isValid());
|
|
QVERIFY(reply.value().value(QStringLiteral("ok")).toBool());
|
|
}
|
|
|
|
{
|
|
QVariantMap spec;
|
|
spec.insert(QStringLiteral("gameId"), gameId);
|
|
spec.insert(QStringLiteral("runner"), QStringLiteral("wine"));
|
|
spec.insert(QStringLiteral("runnerPath"), QStringLiteral("/bin/true"));
|
|
spec.insert(QStringLiteral("envOverrides"), QVariantMap{{QStringLiteral("ALAKARTE_TEST_VAR"), QStringLiteral("1")}});
|
|
spec.insert(QStringLiteral("extraArgs"), QStringList{QStringLiteral("--profile-arg")});
|
|
spec.insert(QStringLiteral("dllOverrides"),
|
|
QVariantMap{{QStringLiteral("d3d11"), QStringLiteral("native,builtin")}, {QStringLiteral("dxgi"), QStringLiteral("native,builtin")}});
|
|
|
|
const QDBusReply<QVariantMap> reply = iface.call(QStringLiteral("SetGameProfile"), spec);
|
|
QVERIFY(reply.isValid());
|
|
const QVariantMap out = reply.value();
|
|
QVERIFY(out.value(QStringLiteral("ok")).toBool());
|
|
|
|
const QVariantMap profile = unwrapVariantMap(out.value(QStringLiteral("profile")));
|
|
QCOMPARE(profile.value(QStringLiteral("gameId")).toString(), gameId);
|
|
const QVariantMap env = unwrapVariantMap(profile.value(QStringLiteral("envOverrides")));
|
|
QCOMPARE(env.value(QStringLiteral("ALAKARTE_TEST_VAR")).toString(), QStringLiteral("1"));
|
|
|
|
QCOMPARE(profile.value(QStringLiteral("extraArgs")).toStringList(), QStringList{QStringLiteral("--profile-arg")});
|
|
|
|
const QVariantMap dll = unwrapVariantMap(profile.value(QStringLiteral("dllOverrides")));
|
|
QCOMPARE(dll.value(QStringLiteral("d3d11")).toString(), QStringLiteral("native,builtin"));
|
|
QCOMPARE(dll.value(QStringLiteral("dxgi")).toString(), QStringLiteral("native,builtin"));
|
|
}
|
|
|
|
{
|
|
const QDBusReply<QVariantMap> reply = iface.call(QStringLiteral("GetGameProfile"), gameId);
|
|
QVERIFY(reply.isValid());
|
|
const QVariantMap out = reply.value();
|
|
QVERIFY(out.value(QStringLiteral("ok")).toBool());
|
|
const QVariantMap profile = unwrapVariantMap(out.value(QStringLiteral("profile")));
|
|
QCOMPARE(profile.value(QStringLiteral("gameId")).toString(), gameId);
|
|
|
|
QCOMPARE(profile.value(QStringLiteral("extraArgs")).toStringList(), QStringList{QStringLiteral("--profile-arg")});
|
|
|
|
const QVariantMap dll = unwrapVariantMap(profile.value(QStringLiteral("dllOverrides")));
|
|
QCOMPARE(dll.value(QStringLiteral("d3d11")).toString(), QStringLiteral("native,builtin"));
|
|
QCOMPARE(dll.value(QStringLiteral("dxgi")).toString(), QStringLiteral("native,builtin"));
|
|
}
|
|
|
|
{
|
|
QVariantMap spec;
|
|
spec.insert(QStringLiteral("gameId"), gameId);
|
|
spec.insert(QStringLiteral("useGameProfile"), true);
|
|
spec.insert(QStringLiteral("program"), QStringLiteral("/bin/true"));
|
|
spec.insert(QStringLiteral("args"), QStringList{QStringLiteral("--base-arg")});
|
|
spec.insert(QStringLiteral("extraArgs"), QStringList{QStringLiteral("--spec-arg")});
|
|
|
|
const QDBusReply<QVariantMap> reply = iface.call(QStringLiteral("ResolveLaunch"), spec);
|
|
QVERIFY(reply.isValid());
|
|
const QVariantMap out = reply.value();
|
|
QVERIFY(out.value(QStringLiteral("ok")).toBool());
|
|
QCOMPARE(out.value(QStringLiteral("finalProgram")).toString(), QStringLiteral("/bin/true"));
|
|
const QStringList expectedFinalArgs = QStringList({
|
|
QStringLiteral("/bin/true"),
|
|
QStringLiteral("--base-arg"),
|
|
QStringLiteral("--profile-arg"),
|
|
QStringLiteral("--spec-arg"),
|
|
});
|
|
QCOMPARE(out.value(QStringLiteral("finalArgs")).toStringList(), expectedFinalArgs);
|
|
|
|
const QVariantMap env = unwrapVariantMap(out.value(QStringLiteral("effectiveEnv")));
|
|
QCOMPARE(env.value(QStringLiteral("ALAKARTE_TEST_VAR")).toString(), QStringLiteral("1"));
|
|
QCOMPARE(env.value(QStringLiteral("WINEDLLOVERRIDES")).toString(), QStringLiteral("d3d11=native,builtin;dxgi=native,builtin"));
|
|
}
|
|
|
|
{
|
|
const QDBusReply<QVariantList> reply = iface.call(QStringLiteral("ListGameProfiles"));
|
|
QVERIFY(reply.isValid());
|
|
const QVariantList list = reply.value();
|
|
bool found = false;
|
|
for (const QVariant &v : list) {
|
|
const QVariantMap m = unwrapVariantMap(v);
|
|
if (m.value(QStringLiteral("gameId")).toString() == gameId) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
QVERIFY(found);
|
|
}
|
|
|
|
{
|
|
const QDBusReply<QVariantMap> reply = iface.call(QStringLiteral("ClearGameProfile"), gameId);
|
|
QVERIFY(reply.isValid());
|
|
QVERIFY(reply.value().value(QStringLiteral("ok")).toBool());
|
|
}
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
QCoreApplication app(argc, argv);
|
|
|
|
const QStringList args = app.arguments();
|
|
const QString gamecenterPath = takeArgValue(args, QStringLiteral("--gamecenter"));
|
|
const QString runnerdPath = takeArgValue(args, QStringLiteral("--runnerd"));
|
|
const QString inputdPath = takeArgValue(args, QStringLiteral("--inputd"));
|
|
|
|
app.setProperty("alakarte_test_gamecenter", gamecenterPath);
|
|
app.setProperty("alakarte_test_runnerd", runnerdPath);
|
|
app.setProperty("alakarte_test_inputd", inputdPath);
|
|
|
|
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") || a == QLatin1String("--runnerd")) {
|
|
++i;
|
|
continue;
|
|
}
|
|
if (a == QLatin1String("--inputd")) {
|
|
++i;
|
|
continue;
|
|
}
|
|
filtered.push_back(a);
|
|
}
|
|
|
|
DbusSmokeTest tc;
|
|
return QTest::qExec(&tc, filtered);
|
|
}
|
|
|
|
#include "dbus_smoketest.moc"
|