Advance Wayland and KDE package bring-up
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
|
||||
remove_definitions(-DQT_NO_CAST_FROM_ASCII)
|
||||
|
||||
find_package(Qt6Test ${REQUIRED_QT_VERSION} REQUIRED CONFIG)
|
||||
|
||||
include(ECMAddTests)
|
||||
|
||||
if(UNIX)
|
||||
add_executable(kdbussimpleservice kdbussimpleservice.cpp)
|
||||
target_link_libraries(kdbussimpleservice Qt6::Core KF6::DBusAddons)
|
||||
|
||||
ecm_add_tests(
|
||||
deadservicetest.cpp
|
||||
LINK_LIBRARIES Qt6::Test KF6::DBusAddons
|
||||
)
|
||||
|
||||
add_dependencies(deadservicetest kdbussimpleservice)
|
||||
endif()
|
||||
|
||||
ecm_add_tests(
|
||||
kdbusservicetest.cpp
|
||||
LINK_LIBRARIES Qt6::Test KF6::DBusAddons
|
||||
)
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
This file is part of libkdbus
|
||||
|
||||
SPDX-FileCopyrightText: 2019 Harald Sitter <sitter@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusConnectionInterface>
|
||||
#include <QDBusServiceWatcher>
|
||||
#include <QDebug>
|
||||
#include <QProcess>
|
||||
#include <QTest>
|
||||
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static const QString s_serviceName = QStringLiteral("org.kde.kdbussimpleservice");
|
||||
|
||||
class TestObject : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QList<int> m_danglingPids;
|
||||
private Q_SLOTS:
|
||||
void cleanupTestCase()
|
||||
{
|
||||
// Make sure we don't leave dangling processes even when we had an
|
||||
// error and the process is stopped.
|
||||
for (int pid : m_danglingPids) {
|
||||
kill(pid, SIGKILL);
|
||||
}
|
||||
}
|
||||
|
||||
void testDeadService()
|
||||
{
|
||||
QVERIFY(!QDBusConnection::sessionBus().interface()->isServiceRegistered(s_serviceName).value());
|
||||
|
||||
QProcess proc1;
|
||||
proc1.setProgram(QFINDTESTDATA("kdbussimpleservice"));
|
||||
proc1.setProcessChannelMode(QProcess::ForwardedChannels);
|
||||
proc1.start();
|
||||
QVERIFY(proc1.waitForStarted());
|
||||
m_danglingPids << proc1.processId();
|
||||
|
||||
// Spy isn't very suitable here because we'd be racing with proc1 or
|
||||
// signal blocking since we'd need to unblock before spying but then
|
||||
// there is an ɛ between unblock and spy.
|
||||
qint64 pid1 = proc1.processId(); // store local, in case the proc disappears
|
||||
QVERIFY(pid1 >= 0);
|
||||
bool proc1Registered = QTest::qWaitFor(
|
||||
[&]() {
|
||||
QTest::qSleep(1000);
|
||||
return QDBusConnection::sessionBus().interface()->servicePid(s_serviceName).value() == pid1;
|
||||
},
|
||||
8000);
|
||||
QVERIFY(proc1Registered);
|
||||
|
||||
// suspend proc1, we don't want it responding on dbus anymore, but still
|
||||
// be running so it holds the name.
|
||||
QCOMPARE(kill(proc1.processId(), SIGSTOP), 0);
|
||||
|
||||
// start second instance
|
||||
QProcess proc2;
|
||||
proc2.setProgram(QFINDTESTDATA("kdbussimpleservice"));
|
||||
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
||||
env.insert("KCRASH_AUTO_RESTARTED", "1");
|
||||
proc2.setProcessEnvironment(env);
|
||||
proc2.setProcessChannelMode(QProcess::ForwardedChannels);
|
||||
proc2.start();
|
||||
QVERIFY(proc2.waitForStarted());
|
||||
m_danglingPids << proc2.processId();
|
||||
|
||||
// sleep a bit. fairly awkward. we need proc2 to be waiting on the name
|
||||
// but we can't easily determine when it started waiting. in lieu of
|
||||
// better instrumentation let's just sleep a bit.
|
||||
qDebug() << "sleeping";
|
||||
QTest::qSleep(4000);
|
||||
|
||||
// Let proc1 go up in flames so that dbus-daemon reclaims the name and
|
||||
// gives it to proc2.
|
||||
qDebug() << "murder on the orient express";
|
||||
QCOMPARE(0, kill(proc1.processId(), SIGUSR1));
|
||||
QCOMPARE(0, kill(proc1.processId(), SIGCONT));
|
||||
|
||||
qint64 pid2 = proc2.processId(); // store local, in case the proc disappears
|
||||
QVERIFY(pid2 >= 0);
|
||||
// Wait for service to be owned by proc2.
|
||||
bool proc2Registered = QTest::qWaitFor(
|
||||
[&]() {
|
||||
QTest::qSleep(1000);
|
||||
return QDBusConnection::sessionBus().interface()->servicePid(s_serviceName).value() == pid2;
|
||||
},
|
||||
8000);
|
||||
QVERIFY(proc2Registered);
|
||||
|
||||
proc1.kill();
|
||||
m_danglingPids.removeAll(pid1);
|
||||
proc2.kill();
|
||||
m_danglingPids.removeAll(pid2);
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestObject)
|
||||
|
||||
#include "deadservicetest.moc"
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
This file is part of libkdbus
|
||||
|
||||
SPDX-FileCopyrightText: 1999 Waldo Bastian <bastian@kde.org>
|
||||
SPDX-FileCopyrightText: 2011 David Faure <faure@kde.org>
|
||||
SPDX-FileCopyrightText: 2011 Kevin Ottens <ervin@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QMetaObject>
|
||||
#include <QProcess>
|
||||
#include <QTimer>
|
||||
|
||||
#include <kdbusservice.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
class TestObject : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
TestObject(KDBusService *service)
|
||||
: m_proc(nullptr)
|
||||
, m_callCount(0)
|
||||
, m_service(service)
|
||||
{
|
||||
}
|
||||
|
||||
~TestObject() override
|
||||
{
|
||||
if (m_proc) {
|
||||
m_proc->waitForFinished();
|
||||
}
|
||||
}
|
||||
|
||||
int callCount() const
|
||||
{
|
||||
return m_callCount;
|
||||
}
|
||||
|
||||
void slotActivateRequested(const QStringList &args, const QString &workingDirectory)
|
||||
{
|
||||
Q_UNUSED(workingDirectory);
|
||||
qDebug() << "Application executed with args" << args;
|
||||
|
||||
++m_callCount;
|
||||
|
||||
if (m_callCount == 1) {
|
||||
Q_ASSERT(args.count() == 1);
|
||||
Q_ASSERT(args.at(0) == QLatin1String("dummy call"));
|
||||
} else if (m_callCount == 2) {
|
||||
Q_ASSERT(args.count() == 2);
|
||||
Q_ASSERT(args.at(1) == QLatin1String("bad call"));
|
||||
m_service->setExitValue(4);
|
||||
} else if (m_callCount == 3) {
|
||||
Q_ASSERT(args.count() == 3);
|
||||
Q_ASSERT(args.at(1) == QLatin1String("real call"));
|
||||
Q_ASSERT(args.at(2) == QLatin1String("second arg"));
|
||||
// OK, all done, quit
|
||||
QCoreApplication::instance()->quit();
|
||||
}
|
||||
}
|
||||
|
||||
public Q_SLOTS:
|
||||
void firstCall()
|
||||
{
|
||||
QStringList args;
|
||||
args << QStringLiteral("bad call");
|
||||
executeNewChild(args);
|
||||
}
|
||||
|
||||
private Q_SLOTS:
|
||||
void slotProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
|
||||
{
|
||||
Q_UNUSED(exitStatus)
|
||||
qDebug() << "Process exited with code" << exitCode;
|
||||
m_proc = nullptr;
|
||||
if (m_callCount == 2) {
|
||||
Q_ASSERT(exitCode == 4);
|
||||
secondCall();
|
||||
}
|
||||
}
|
||||
|
||||
void secondCall()
|
||||
{
|
||||
QStringList args;
|
||||
args << QStringLiteral("real call") << QStringLiteral("second arg");
|
||||
executeNewChild(args);
|
||||
}
|
||||
|
||||
private:
|
||||
void executeNewChild(const QStringList &args)
|
||||
{
|
||||
// Duplicated from kglobalsettingstest.cpp - make a shared helper method?
|
||||
m_proc = new QProcess(this);
|
||||
connect(m_proc, &QProcess::finished, this, &TestObject::slotProcessFinished);
|
||||
QString appName = QStringLiteral("kdbusservicetest");
|
||||
#ifdef Q_OS_WIN
|
||||
appName += ".exe";
|
||||
#else
|
||||
if (QFile::exists(appName + ".shell")) {
|
||||
appName = "./" + appName + ".shell";
|
||||
} else {
|
||||
Q_ASSERT(QFile::exists(appName));
|
||||
appName = "./" + appName;
|
||||
}
|
||||
#endif
|
||||
qDebug() << "about to run" << appName << args;
|
||||
m_proc->start(appName, args);
|
||||
}
|
||||
|
||||
QProcess *m_proc;
|
||||
int m_callCount;
|
||||
KDBusService *m_service;
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication a(argc, argv);
|
||||
|
||||
QCoreApplication::setApplicationName(QStringLiteral("kdbusservicetest"));
|
||||
QCoreApplication::setOrganizationDomain(QStringLiteral("kde.org"));
|
||||
|
||||
QDir::setCurrent(QCoreApplication::applicationDirPath());
|
||||
|
||||
KDBusService service(KDBusService::Unique);
|
||||
TestObject testObject(&service);
|
||||
QObject::connect(&service, &KDBusService::activateRequested, &testObject, &TestObject::slotActivateRequested);
|
||||
|
||||
// Testcase for the problem coming from the old fork-on-startup solution:
|
||||
// the "Activate" D-Bus call would time out if the app took too much time
|
||||
// to be ready.
|
||||
// printf("Sleeping.\n");
|
||||
// sleep(200);
|
||||
QStringList args;
|
||||
args << QStringLiteral("dummy call");
|
||||
|
||||
auto activateSignal = [&service, &args]() {
|
||||
service.activateRequested(args, QDir::currentPath());
|
||||
};
|
||||
QMetaObject::invokeMethod(&service, activateSignal, Qt::QueuedConnection);
|
||||
QTimer::singleShot(400, &testObject, &TestObject::firstCall);
|
||||
qDebug() << "Running.";
|
||||
a.exec();
|
||||
qDebug() << "Terminating.";
|
||||
|
||||
Q_ASSERT(testObject.callCount() == 3);
|
||||
const bool ok = testObject.callCount() == 3;
|
||||
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
|
||||
#include "kdbusservicetest.moc"
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
This file is part of libkdbus
|
||||
|
||||
SPDX-FileCopyrightText: 2019 Harald Sitter <sitter@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QThread>
|
||||
#include <kdbusservice.h>
|
||||
|
||||
// sigaction
|
||||
#include <signal.h>
|
||||
// close
|
||||
#include <unistd.h>
|
||||
|
||||
// rlimit
|
||||
#include <sys/resource.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
// USR1.
|
||||
// Close all sockets and eventually ABRT. This mimics behavior during KCrash
|
||||
// handling. Closing all sockets will effectively disconnect us from the bus
|
||||
// and result in the daemon reclaiming all our service names and allowing
|
||||
// other processes to register them instead.
|
||||
void usr1_handler(int signum)
|
||||
{
|
||||
qDebug() << "usr1" << signum << SIGSEGV;
|
||||
|
||||
// Close all remaining file descriptors so we drop off of the bus.
|
||||
struct rlimit rlp;
|
||||
getrlimit(RLIMIT_NOFILE, &rlp);
|
||||
for (int i = 3; i < (int)rlp.rlim_cur; i++) {
|
||||
close(i);
|
||||
}
|
||||
|
||||
// Sleep a bit for good measure. We could actually loop ad infinitum here
|
||||
// as after USR1 we are expected to get killed. In the interest of sane
|
||||
// behavior we'll simply exit on our own as well though.
|
||||
sleep(4);
|
||||
abort();
|
||||
}
|
||||
|
||||
// Simple application under test.
|
||||
// Closes all sockets on USR1 and aborts to simulate a kcrash shutdown behavior
|
||||
// which can result in a service registration race.
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
qDebug() << "hello there!";
|
||||
|
||||
struct sigaction action;
|
||||
|
||||
action.sa_handler = usr1_handler;
|
||||
sigemptyset(&action.sa_mask);
|
||||
sigaddset(&action.sa_mask, SIGUSR1);
|
||||
action.sa_flags = SA_RESTART;
|
||||
|
||||
if (sigaction(SIGUSR1, &action, nullptr) < 0) {
|
||||
qDebug() << "failed to register segv handler";
|
||||
return 1;
|
||||
}
|
||||
|
||||
QCoreApplication app(argc, argv);
|
||||
QCoreApplication::setApplicationName("kdbussimpleservice");
|
||||
QCoreApplication::setOrganizationDomain("kde.org");
|
||||
|
||||
KDBusService service(KDBusService::Unique);
|
||||
if (!service.isRegistered()) {
|
||||
qDebug() << "service not registered => exiting";
|
||||
return 1;
|
||||
}
|
||||
qDebug() << "service registered";
|
||||
|
||||
int ret = app.exec();
|
||||
qDebug() << "exiting deadservice";
|
||||
return ret;
|
||||
}
|
||||
Reference in New Issue
Block a user