summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew Christian <andrew.christian@nokia.com>2012-02-23 10:00:28 -0500
committerChris Craig <ext-chris.craig@nokia.com>2012-02-27 12:14:59 +0100
commitd996e4d26539a0a80a63d03fc75ea37d916733d2 (patch)
tree1d9fdcb1ca41c93147f1b5beef3565a20340ae86
parent3668383c67e6fab36ce377835ea160a868061b24 (diff)
Added forklauncher code and test cases.
Change-Id: I2e21b7635169464e229a36d02cc728a5f5bab443 Reviewed-by: Chris Craig <ext-chris.craig@nokia.com>
-rw-r--r--src/core/core-lib.pri6
-rw-r--r--src/core/forklauncher.cpp671
-rw-r--r--src/core/forklauncher.h52
-rw-r--r--src/core/pipeprocessbackendfactory.cpp3
-rw-r--r--src/core/remoteprocessbackendfactory.cpp3
-rw-r--r--src/core/remoteprotocol.h2
-rw-r--r--src/declarative/declarativesocketlauncher.h3
-rw-r--r--tests/auto/processmanager/processmanager.pro2
-rw-r--r--tests/auto/processmanager/testClient/main.cpp19
-rw-r--r--tests/auto/processmanager/testForkLauncher/.gitignore1
-rw-r--r--tests/auto/processmanager/testForkLauncher/main.cpp142
-rw-r--r--tests/auto/processmanager/testForkLauncher/testForkLauncher.pro11
-rw-r--r--tests/auto/processmanager/testPrelaunch/main.cpp21
-rw-r--r--tests/auto/processmanager/tst_processmanager.cpp140
14 files changed, 1040 insertions, 36 deletions
diff --git a/src/core/core-lib.pri b/src/core/core-lib.pri
index 77ecb38..26a9423 100644
--- a/src/core/core-lib.pri
+++ b/src/core/core-lib.pri
@@ -29,7 +29,8 @@ PUBLIC_HEADERS += \
$$PWD/pipelauncher.h \
$$PWD/socketlauncher.h \
$$PWD/procutils.h \
- $$PWD/remoteprotocol.h
+ $$PWD/remoteprotocol.h \
+ $$PWD/forklauncher.h
HEADERS += \
$$PUBLIC_HEADERS \
@@ -61,4 +62,5 @@ SOURCES += \
$$PWD/launcherclient.cpp \
$$PWD/pipelauncher.cpp \
$$PWD/socketlauncher.cpp \
- $$PWD/procutils.cpp
+ $$PWD/procutils.cpp \
+ $$PWD/forklauncher.cpp
diff --git a/src/core/forklauncher.cpp b/src/core/forklauncher.cpp
new file mode 100644
index 0000000..46894a5
--- /dev/null
+++ b/src/core/forklauncher.cpp
@@ -0,0 +1,671 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QDebug>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QByteArray>
+#include <QMap>
+#include <QtEndian>
+#include <QFile>
+#include <QElapsedTimer>
+#include <QProcess>
+
+#include <signal.h>
+#include <sys/select.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/resource.h>
+#include <fcntl.h>
+
+// Linux only?
+#include <sys/wait.h>
+#include <grp.h>
+
+#include "forklauncher.h"
+#include "remoteprotocol.h"
+#include "processinfo.h"
+#include "procutils.h"
+
+#if defined(Q_OS_MAC) && !defined(QT_NO_CORESERVICES)
+// Shared libraries don't have direct access to environ until runtime
+# include <crt_externs.h>
+# define environ (*_NSGetEnviron())
+#else
+ extern char **environ;
+#endif
+
+
+QT_BEGIN_NAMESPACE_PROCESSMANAGER
+
+static int sig_child_pipe[2];
+static struct sigaction old_sig_child_handler;
+
+static void sig_child_handler(int sig)
+{
+ ::write(sig_child_pipe[1], "@", 1);
+
+ // Complicated way of calling the old child handler
+ void (*oldAction)(int) = ((volatile struct sigaction *)&old_sig_child_handler)->sa_handler;
+ if (oldAction && oldAction != SIG_IGN)
+ oldAction(sig);
+}
+
+static void readToBuffer(int fd, QByteArray& buffer)
+{
+ const int bufsize = 1024;
+ uint oldSize = buffer.size();
+ buffer.resize(oldSize + bufsize);
+ int n = ::read(fd, buffer.data()+oldSize, bufsize);
+ if (n > 0)
+ buffer.resize(oldSize+n);
+ else
+ buffer.resize(oldSize);
+}
+
+static void writeFromBuffer(int fd, QByteArray& buffer)
+{
+ int n = ::write(fd, buffer.data(), buffer.size());
+ if (n == -1) {
+ qDebug() << "Failed to write to " << fd;
+ exit(-1);
+ }
+ if (n < buffer.size())
+ buffer = buffer.mid(n);
+ else
+ buffer.clear();
+}
+
+static void copyToOutgoing(QByteArray& outgoing, const QString& channel, QByteArray& buf, int id)
+{
+ QJsonObject message;
+ message.insert(RemoteProtocol::event(), RemoteProtocol::output());
+ message.insert(RemoteProtocol::id(), id);
+ message.insert(channel, QString::fromLocal8Bit(buf.data(), buf.size()));
+
+ outgoing.append(QJsonDocument(message).toBinaryData());
+ buf.clear();
+}
+
+/*
+ Update the current process to match the information in info
+ */
+
+static void fixProcessState(const ProcessInfo& info, int *argc_ptr, char ***argv_ptr)
+{
+ // Fix the UID & GID values
+ ::setpgid(0,0);
+ if (info.contains(ProcessInfoConstants::Gid))
+ ::setgid(info.gid());
+ if (info.contains(ProcessInfoConstants::Uid))
+ ::setuid(info.uid());
+ ::umask(S_IWGRP | S_IWOTH);
+ struct passwd *pw = getpwent();
+ if (pw)
+ ::initgroups(pw->pw_name, pw->pw_gid);
+ else
+ ::setgroups(0,0);
+
+ if (info.contains(ProcessInfoConstants::Priority)) {
+ int priority = info.priority();
+ if (::setpriority(PRIO_PROCESS, ::getpid(), priority) == -1)
+ qWarning("Unable to set priority of pid=%d to %d", ::getpid(), priority);
+ }
+
+ if (info.contains(ProcessInfoConstants::OomAdjustment)) {
+ int adj = info.oomAdjustment();
+ if (!ProcUtils::setOomAdjustment(::getpid(), adj))
+ qWarning("Unable to set oom adjustment of pid=%d to %d", ::getpid(), adj);
+ }
+
+ if (info.contains(ProcessInfoConstants::WorkingDirectory)) {
+ QByteArray wd = QFile::encodeName(info.workingDirectory());
+ if (!::chdir(wd.constData()))
+ qWarning("Unable to chdir to %s", wd.constData());
+ }
+
+ // Fix the environment
+ if (info.contains(ProcessInfoConstants::Environment)) {
+ const char *entry;
+ QList<QByteArray> envlist;
+ for (int count = 0 ; (entry = environ[count]) ; ++count) {
+ const char *equal = strchr(entry, '=');
+ if (!equal)
+ envlist.append(QByteArray(entry));
+ else
+ envlist.append(QByteArray(entry, equal - entry));
+ }
+
+ QVariantMap env = info.environment();
+ foreach (const QByteArray& ba, envlist) {
+ QString key = QString::fromLocal8Bit(ba.constData(), ba.size());
+ if (!env.contains(key)) // Remove only keys that don't exist in the planned environment
+ ::unsetenv(ba.constData());
+ }
+
+ QMapIterator<QString, QVariant> iter(env);
+ while (iter.hasNext()) {
+ iter.next();
+ QByteArray key = iter.key().toLocal8Bit();
+ QByteArray value = iter.value().toByteArray();
+ ::setenv(key.constData(), value.constData(), 1);
+ }
+ }
+
+ // Fix up the argument list
+ if (info.contains(ProcessInfoConstants::Arguments)) {
+ const int argc = info.arguments().size() + 1;
+ char **argv = new char *[argc + 1];
+ if (info.contains(ProcessInfoConstants::Program))
+ argv[0] = strdup(info.program().toLocal8Bit().constData());
+ else
+ argv[0] = (*argv_ptr)[0];
+ for (int i = 0 ; i < argc ; i++ )
+ argv[i+1] = strdup(info.arguments().at(0).toLocal8Bit().constData());
+ argv[argc] = 0;
+ *argc_ptr = argc;
+ *argv_ptr = argv;
+ }
+ else if (info.contains(ProcessInfoConstants::Program)) {
+ // No new arguments; just copy in the new program name
+ (*argv_ptr)[0] = strdup(info.program().toLocal8Bit().constData());
+ }
+}
+
+
+/**************************************************************************/
+
+// ### TODO: Should we watch for 'startOutputPattern'???
+
+class ChildProcess {
+public:
+ ChildProcess(int id);
+ ~ChildProcess();
+
+ int updateFdSet(int n, fd_set& rfds, fd_set& wfds);
+ void processFdSet(QByteArray& outgoing, fd_set& rfds, fd_set& wfds);
+ void stop(int timeout);
+ void setPriority(int priority);
+ void setOomAdjustment(int oomAdjustment);
+ bool doFork();
+
+ void write(const QByteArray& buf) { m_inbuf.append(buf); }
+ pid_t pid() const { return m_pid; }
+ int id() const { return m_id; }
+ bool needTimeout() const { return m_state == SentSigTerm; }
+
+ void sendStateChanged(QByteArray& outgoing, QProcess::ProcessState state);
+ void sendStarted(QByteArray& outgoing);
+ void sendFinished(QByteArray& outgoing, int exitCode, QProcess::ExitStatus);
+ void sendError(QByteArray& outgoing, QProcess::ProcessError err, const QString& errString);
+
+ enum ProcessState {
+ NotRunning,
+ Running,
+ SentSigTerm,
+ SentSigKill,
+ Finished
+ };
+private:
+ ProcessState m_state;
+ pid_t m_pid;
+ int m_id; // Unique id for this process
+ QElapsedTimer m_timer;
+ int m_timeout;
+ int m_stdin, m_stdout, m_stderr;
+ QByteArray m_inbuf; // Data being written
+ QByteArray m_outbuf; // Data being read
+ QByteArray m_errbuf; // Data being read
+};
+
+ChildProcess::ChildProcess(int id)
+ : m_state(NotRunning)
+ , m_pid(-1)
+ , m_id(id)
+ , m_stdin(-1)
+ , m_stdout(-1)
+ , m_stderr(-1)
+{
+}
+
+ChildProcess::~ChildProcess()
+{
+ if (m_stdin > 0) close(m_stdin);
+ if (m_stdout > 0) close(m_stdout);
+ if (m_stderr > 0) close(m_stderr);
+}
+
+int ChildProcess::updateFdSet(int n, fd_set& rfds, fd_set& wfds)
+{
+ FD_SET(m_stdout, &rfds);
+ int n2 = qMax(n, m_stdout);
+ FD_SET(m_stderr, &rfds);
+ n2 = qMax(n2, m_stderr);
+ if (m_inbuf.size()) {
+ FD_SET(m_stdin, &wfds);
+ n2 = qMax(n2, m_stdin);
+ }
+ return n2;
+}
+
+void ChildProcess::processFdSet(QByteArray& outgoing, fd_set& rfds, fd_set& wfds)
+{
+ if (FD_ISSET(m_stdin, &wfds)) { // Data to write
+ writeFromBuffer(m_stdin, m_inbuf);
+ }
+ if (FD_ISSET(m_stdout, &rfds)) { // Data to read
+ readToBuffer(m_stdout, m_outbuf);
+ copyToOutgoing(outgoing, RemoteProtocol::stdout(), m_outbuf, m_id);
+ }
+ if (FD_ISSET(m_stderr, &rfds)) { // Data to read
+ readToBuffer(m_stderr, m_errbuf);
+ copyToOutgoing(outgoing, RemoteProtocol::stderr(), m_errbuf, m_id);
+ }
+ if (m_state == SentSigTerm && m_timer.hasExpired(m_timeout)) {
+ m_state = SentSigKill;
+ ::kill(m_pid, SIGKILL);
+ }
+}
+
+/*
+ Stop the child process from running. Pass in a timeout value in milliseconds.
+ */
+
+void ChildProcess::stop(int timeout)
+{
+ if (m_state == Running) {
+ // ### TODO: Kill by progress group...
+ if (timeout > 0) {
+ m_state = SentSigTerm;
+ m_timeout = timeout;
+ m_timer.start();
+ ::kill(m_pid, SIGTERM);
+ }
+ else {
+ m_state = SentSigKill;
+ ::kill(m_pid, SIGKILL);
+ }
+ }
+}
+
+void ChildProcess::setPriority(int priority)
+{
+ if (::setpriority(PRIO_PROCESS, m_pid, priority) == -1)
+ qWarning("Unable to set priority of pid=%d to %d", m_pid, priority);
+}
+
+void ChildProcess::setOomAdjustment(int oomAdjustment)
+{
+ if (!ProcUtils::setOomAdjustment(m_pid, oomAdjustment))
+ qWarning("Unable to set oom adjustment of pid=%d to %d", m_pid, oomAdjustment);
+}
+
+static void makePipe(int fd[])
+{
+ if (::pipe(fd) == -1)
+ qFatal("Unable to create pipe: %s", strerror(errno));
+ if (::fcntl(fd[0], F_SETFL, O_NONBLOCK) == -1) // Set non-block on read end
+ qFatal("Unable to set nonblocking: %s", strerror(errno));
+}
+
+bool ChildProcess::doFork()
+{
+ m_state = Running;
+
+ int fd1[2]; // Stdin of the child
+ int fd2[2]; // Stdout of the child
+ int fd3[2]; // Stderr of the child
+ makePipe(fd1);
+ makePipe(fd2);
+ makePipe(fd3);
+
+ m_pid = fork();
+ if (m_pid < 0) // failed to fork
+ qFatal("Failed to fork: %s", strerror(errno));
+
+ if (m_pid == 0) { // child
+ dup2(fd1[0], STDIN_FILENO); // Duplicate input side of pipe to stdin
+ dup2(fd2[1], STDOUT_FILENO); // Duplicate output side of the pipe to stdout
+ dup2(fd3[1], STDERR_FILENO); // Duplicate output side of the pipe to stderr
+ // Close all of the original pipes
+ close(fd1[0]);
+ close(fd1[1]);
+ close(fd2[0]);
+ close(fd2[1]);
+ close(fd3[0]);
+ close(fd3[1]);
+ return true;
+ }
+
+ // Execute parent code here....
+ m_stdin = fd1[1];
+ m_stdout = fd2[0];
+ m_stderr = fd3[0];
+ close(fd1[0]);
+ close(fd2[1]);
+ close(fd3[1]);
+ return false; // Parent returns false
+}
+
+void ChildProcess::sendStateChanged(QByteArray& outgoing, QProcess::ProcessState state)
+{
+ QJsonObject msg;
+ msg.insert(RemoteProtocol::event(), RemoteProtocol::stateChanged());
+ msg.insert(RemoteProtocol::id(), m_id);
+ msg.insert(RemoteProtocol::stateChanged(), state);
+ outgoing.append(QJsonDocument(msg).toBinaryData());
+}
+
+void ChildProcess::sendStarted(QByteArray& outgoing)
+{
+ QJsonObject msg;
+ msg.insert(RemoteProtocol::event(), RemoteProtocol::started());
+ msg.insert(RemoteProtocol::id(), m_id);
+ msg.insert(RemoteProtocol::pid(), m_pid);
+ outgoing.append(QJsonDocument(msg).toBinaryData());
+}
+
+void ChildProcess::sendFinished(QByteArray& outgoing, int exitCode, QProcess::ExitStatus exitStatus)
+{
+ QJsonObject msg;
+ msg.insert(RemoteProtocol::event(), RemoteProtocol::finished());
+ msg.insert(RemoteProtocol::id(), m_id);
+ msg.insert(RemoteProtocol::exitCode(), exitCode);
+ msg.insert(RemoteProtocol::exitStatus(), exitStatus);
+ outgoing.append(QJsonDocument(msg).toBinaryData());
+}
+
+void ChildProcess::sendError(QByteArray& outgoing, QProcess::ProcessError err, const QString& errString)
+{
+ QJsonObject msg;
+ msg.insert(RemoteProtocol::event(), RemoteProtocol::error());
+ msg.insert(RemoteProtocol::id(), m_id);
+ msg.insert(RemoteProtocol::error(), err);
+ msg.insert(RemoteProtocol::errorString(), errString);
+ outgoing.append(QJsonDocument(msg).toBinaryData());
+}
+
+
+/**************************************************************************/
+
+class ParentProcess {
+public:
+ ParentProcess(int *argc, char ***argv);
+ ~ParentProcess();
+
+ ChildProcess *childFromPid(pid_t pid);
+
+ int updateFdSet(fd_set& rfds, fd_set& wfds);
+ bool processFdSet(fd_set& rfds, fd_set& wfds);
+ void waitForChildren();
+ bool handleMessage(QJsonObject& message);
+ bool needTimeout() const;
+
+private:
+ int *m_argc_ptr;
+ char ***m_argv_ptr;
+ QMap<int, ChildProcess *> m_children;
+ QByteArray m_sendbuf;
+ QByteArray m_recvbuf;
+};
+
+
+ParentProcess::ParentProcess(int *argc, char ***argv)
+ : m_argc_ptr(argc), m_argv_ptr(argv)
+{
+ // Set up a signal handler for child events
+ makePipe(sig_child_pipe);
+
+ struct sigaction action;
+ memset(&action, 0, sizeof(action));
+ action.sa_handler = sig_child_handler;
+ action.sa_flags = SA_NOCLDSTOP;
+ ::sigaction(SIGCHLD, &action, &old_sig_child_handler);
+}
+
+ParentProcess::~ParentProcess()
+{
+ // The normal destructor is only executed by a child process, so all
+ // we do is close down the extra file descriptors
+ foreach (ChildProcess *child, m_children)
+ delete child; // This closes the file descriptors included in the child processes
+
+ ::sigaction(SIGCHLD, &old_sig_child_handler, 0);
+ ::close(sig_child_pipe[0]);
+ ::close(sig_child_pipe[1]);
+}
+
+int ParentProcess::updateFdSet(fd_set& rfds, fd_set& wfds)
+{
+ FD_SET(0, &rfds); // Always read from stdin
+ FD_SET(sig_child_pipe[0], &rfds); // Watch for signals
+ if (m_sendbuf.size() > 0)
+ FD_SET(1, &wfds);
+
+ int n = sig_child_pipe[0]; // We're pretty sure this is the largest so far
+ foreach (ChildProcess *child, m_children)
+ n = child->updateFdSet(n, rfds, wfds);
+ return n;
+}
+
+ChildProcess *ParentProcess::childFromPid(pid_t pid)
+{
+ foreach (ChildProcess *child, m_children)
+ if (child->pid() == pid)
+ return child;
+ return NULL;
+}
+
+void ParentProcess::waitForChildren()
+{
+ int status;
+ while (1) {
+ pid_t pid = ::waitpid(-1, &status, WNOHANG);
+ if (pid == -1 && errno == ECHILD)
+ return;
+ if (pid < 0)
+ qFatal("Error in wait %s", strerror(errno));
+ bool crashed = !WIFEXITED(status);
+ int exitCode = WEXITSTATUS(status);
+ ChildProcess *child = childFromPid(pid);
+ if (child) {
+ m_children.take(child->id());
+ if (crashed)
+ child->sendError(m_sendbuf, QProcess::Crashed, QStringLiteral("Process crashed"));
+ child->sendStateChanged(m_sendbuf, QProcess::NotRunning);
+ child->sendFinished(m_sendbuf, exitCode,
+ (crashed ? QProcess::CrashExit : QProcess::NormalExit));
+ delete child;
+ }
+ }
+}
+
+// Return 'true' if we're a new child process
+bool ParentProcess::processFdSet(fd_set& rfds, fd_set& wfds)
+{
+ // Handle the children first because other messages may change the child list
+ foreach (ChildProcess *child, m_children)
+ child->processFdSet(m_sendbuf, rfds, wfds);
+
+ if (FD_ISSET(sig_child_pipe[0], &rfds)) { // A child process died
+ char c;
+ if (::read(sig_child_pipe[0], &c, 1) == 1)
+ waitForChildren();
+ else
+ qDebug() << "############################ READ SIG PROBLEM";
+ }
+ if (FD_ISSET(0, &rfds)) { // Data available on stdin
+ readToBuffer(0, m_recvbuf);
+ // Process messages here
+ while (m_recvbuf.size() >= 12) {
+ qint32 message_size = qFromLittleEndian(((qint32 *)m_recvbuf.data())[2]) + 8;
+ if (m_recvbuf.size() < message_size)
+ break;
+ QByteArray msg = m_recvbuf.left(message_size);
+ m_recvbuf = m_recvbuf.mid(message_size);
+ QJsonObject object = QJsonDocument::fromBinaryData(msg).object();
+ if (handleMessage(object))
+ return true;
+ }
+ }
+ if (FD_ISSET(1, &wfds)) // Write to stdout
+ writeFromBuffer(1, m_sendbuf);
+ return false;
+}
+
+// Return 'true' if this is a child process
+bool ParentProcess::handleMessage(QJsonObject& message)
+{
+ if (message.value(RemoteProtocol::remote()).toString() == RemoteProtocol::stop()) {
+ // Force all children to stop
+ foreach (ChildProcess *child, m_children)
+ child->stop(0);
+ exit(0);
+ }
+ else {
+ QString command = message.value(RemoteProtocol::command()).toString();
+ int id = message.value(RemoteProtocol::id()).toDouble();
+ if (command == RemoteProtocol::stop()) {
+ ChildProcess *child = m_children.value(id);
+ if (child) {
+ int timeout = message.value(RemoteProtocol::timeout()).toDouble();
+ child->stop(timeout);
+ }
+ } else if (command == RemoteProtocol::set()) {
+ ChildProcess *child = m_children.value(id);
+ if (child) {
+ QString key = message.value(RemoteProtocol::key()).toString();
+ int value = message.value(RemoteProtocol::value()).toDouble();
+ if (key == RemoteProtocol::priority())
+ child->setPriority(value);
+ else if (key == RemoteProtocol::oomAdjustment())
+ child->setOomAdjustment(value);
+ }
+ } else if (command == RemoteProtocol::start()) {
+ ProcessInfo info(message.value(RemoteProtocol::info()).toObject().toVariantMap());
+ ChildProcess *child = new ChildProcess(id);
+ if (child->doFork()) {
+ delete child;
+ fixProcessState(info, m_argc_ptr, m_argv_ptr);
+ return true;
+ }
+ else {
+ m_children.insert(id, child);
+ child->sendStateChanged(m_sendbuf, QProcess::Starting);
+ child->sendStateChanged(m_sendbuf, QProcess::Running);
+ child->sendStarted(m_sendbuf);
+ }
+ } else if (command == RemoteProtocol::write()) {
+ ChildProcess *child = m_children.value(id);
+ if (child)
+ child->write(message.value(RemoteProtocol::data()).toString().toLocal8Bit());
+ }
+ }
+ return false;
+}
+
+/*!
+ Return true if some child is in a "needs a timeout" phase
+ */
+
+bool ParentProcess::needTimeout() const
+{
+ foreach (ChildProcess *child, m_children)
+ if (child->needTimeout())
+ return true;
+ return false;
+}
+
+/**************************************************************************/
+
+void forklauncher(int *argc, char ***argv )
+{
+ ParentProcess parent(argc, argv);
+
+ while (1) {
+ fd_set rfds, wfds;
+ FD_ZERO(&rfds);
+ FD_ZERO(&wfds);
+ int n = parent.updateFdSet(rfds, wfds);
+
+ struct timeval timeout;
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 100000;
+ struct timeval *tptr = (parent.needTimeout() ? &timeout : NULL);
+
+ // Select on the inputs
+ int retval = ::select(n+1, &rfds, &wfds, NULL, tptr);
+ if (retval == -1 && errno == EINTR)
+ continue;
+ if (retval < 0)
+ qFatal("select: %s", strerror(errno));
+
+ if (parent.processFdSet(rfds, wfds))
+ return;
+ }
+}
+
+void displayFileDescriptors(int argc, char **argv)
+{
+ QList<QByteArray> arglist;
+ for (int i = 0 ; i < argc ; i++)
+ arglist << argv[i];
+
+ QList<QByteArray> envlist;
+ const char *entry;
+ for (int count = 0 ; (entry = environ[count]) ; count++)
+ envlist.append(entry);
+
+ struct rlimit data;
+ if (::getrlimit(RLIMIT_NOFILE, &data) < 0)
+ qFatal("Unable to read rlimit");
+ QList<int> fdlist;
+ for (unsigned int i=0 ; i < data.rlim_cur ; i++) {
+ if (::fcntl(i, F_GETFD, 0) != -1)
+ fdlist << i;
+ }
+
+ qDebug() << "##### " << arglist;
+ qDebug() << "##### " << envlist;
+ qDebug() << "##### FD " << fdlist;
+}
+
+QT_END_NAMESPACE_PROCESSMANAGER
diff --git a/src/core/forklauncher.h b/src/core/forklauncher.h
new file mode 100644
index 0000000..8cb74ea
--- /dev/null
+++ b/src/core/forklauncher.h
@@ -0,0 +1,52 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef FORK_LAUNCHER_H
+#define FORK_LAUNCHER_H
+
+#include "processmanager-global.h"
+
+QT_BEGIN_NAMESPACE_PROCESSMANAGER
+
+Q_CORE_EXPORT void forklauncher(int *argc, char ***argv);
+Q_CORE_EXPORT void displayFileDescriptors(int argc, char **argv);
+
+QT_END_NAMESPACE_PROCESSMANAGER
+
+#endif // FORK_LAUNCHER_H
diff --git a/src/core/pipeprocessbackendfactory.cpp b/src/core/pipeprocessbackendfactory.cpp
index da867f5..d69fd46 100644
--- a/src/core/pipeprocessbackendfactory.cpp
+++ b/src/core/pipeprocessbackendfactory.cpp
@@ -129,7 +129,6 @@ QList<Q_PID> PipeProcessBackendFactory::internalProcesses()
*/
bool PipeProcessBackendFactory::send(const QJsonObject& message)
{
- // qDebug() << Q_FUNC_INFO << message;
if (m_process->state() != QProcess::Running) {
qCritical("Pipe process not running");
return false;
@@ -169,7 +168,6 @@ void PipeProcessBackendFactory::pipeReadyReadStandardError()
void PipeProcessBackendFactory::pipeStarted()
{
-// qDebug() << "Pipe process started";
}
void PipeProcessBackendFactory::pipeError(QProcess::ProcessError error)
@@ -187,7 +185,6 @@ void PipeProcessBackendFactory::pipeFinished(int exitCode, QProcess::ExitStatus
void PipeProcessBackendFactory::pipeStateChanged(QProcess::ProcessState state)
{
Q_UNUSED(state);
-// qDebug() << "Pipe process state change" << state;
}
#include "moc_pipeprocessbackendfactory.cpp"
diff --git a/src/core/remoteprocessbackendfactory.cpp b/src/core/remoteprocessbackendfactory.cpp
index 41ec4af..7c1f252 100644
--- a/src/core/remoteprocessbackendfactory.cpp
+++ b/src/core/remoteprocessbackendfactory.cpp
@@ -40,8 +40,6 @@
#include "remoteprocessbackendfactory.h"
#include "remoteprocessbackend.h"
-#include <QDebug>
-
QT_BEGIN_NAMESPACE_PROCESSMANAGER
const int kRemoteTimerInterval = 1000;
@@ -146,7 +144,6 @@ ProcessBackend * RemoteProcessBackendFactory::create(const ProcessInfo& info, QO
void RemoteProcessBackendFactory::receive(const QJsonObject& message)
{
- // qDebug() << Q_FUNC_INFO << message;
int id = message.value(QLatin1String("id")).toDouble();
if (m_backendMap.contains(id))
m_backendMap.value(id)->receive(message);
diff --git a/src/core/remoteprotocol.h b/src/core/remoteprotocol.h
index 3a31613..99f7131 100644
--- a/src/core/remoteprotocol.h
+++ b/src/core/remoteprotocol.h
@@ -40,6 +40,8 @@
#ifndef _REMOTE_PROTOCOL_H
#define _REMOTE_PROTOCOL_H
+#include <QString>
+
#include "processmanager-global.h"
QT_BEGIN_NAMESPACE_PROCESSMANAGER
diff --git a/src/declarative/declarativesocketlauncher.h b/src/declarative/declarativesocketlauncher.h
index 494b4bc..64036bd 100644
--- a/src/declarative/declarativesocketlauncher.h
+++ b/src/declarative/declarativesocketlauncher.h
@@ -43,9 +43,6 @@
#include <QtDeclarative>
#include "socketlauncher.h"
-class JsonAuthority;
-class QtAddOn::JsonStream::JsonAuthority;
-
QT_BEGIN_NAMESPACE_PROCESSMANAGER
class Q_ADDON_PROCESSMANAGER_EXPORT DeclarativeSocketLauncher : public SocketLauncher,
diff --git a/tests/auto/processmanager/processmanager.pro b/tests/auto/processmanager/processmanager.pro
index 864fbf9..2049057 100644
--- a/tests/auto/processmanager/processmanager.pro
+++ b/tests/auto/processmanager/processmanager.pro
@@ -1,3 +1,3 @@
TEMPLATE = subdirs
-SUBDIRS = testClient testPrelaunch testPipeLauncher testSocketLauncher test
+SUBDIRS = testClient testPrelaunch testPipeLauncher testSocketLauncher testForkLauncher test
diff --git a/tests/auto/processmanager/testClient/main.cpp b/tests/auto/processmanager/testClient/main.cpp
index bb5875c..17cde87 100644
--- a/tests/auto/processmanager/testClient/main.cpp
+++ b/tests/auto/processmanager/testClient/main.cpp
@@ -41,6 +41,8 @@
#include <sys/uio.h>
#include <unistd.h>
#include <string.h>
+#include <signal.h>
+#include <stdio.h>
const int kBufSize = 100;
@@ -72,9 +74,24 @@ ssize_t readline(char *buffer, int max_len)
return len;
}
+static char tough[] = "tough\n";
+
int
-main(int /*argc*/, char ** /*argv*/)
+main(int argc, char **argv)
{
+ for (int i = 1 ; i < argc ; i++) {
+ if (!strcmp(argv[i], "-noterm")) {
+ struct sigaction action;
+ memset(&action, 0, sizeof(action));
+ action.sa_handler=SIG_IGN;
+ if (sigaction(SIGTERM, &action, NULL) < 0) {
+ perror("Unable ignore SIGTERM");
+ return 1;
+ }
+ if (writeline(tough, strlen(tough)) < 0)
+ return 3;
+ }
+ }
char buffer[kBufSize+1];
while (1) {
diff --git a/tests/auto/processmanager/testForkLauncher/.gitignore b/tests/auto/processmanager/testForkLauncher/.gitignore
new file mode 100644
index 0000000..65b56dd
--- /dev/null
+++ b/tests/auto/processmanager/testForkLauncher/.gitignore
@@ -0,0 +1 @@
+testForkLauncher
diff --git a/tests/auto/processmanager/testForkLauncher/main.cpp b/tests/auto/processmanager/testForkLauncher/main.cpp
new file mode 100644
index 0000000..ca25645
--- /dev/null
+++ b/tests/auto/processmanager/testForkLauncher/main.cpp
@@ -0,0 +1,142 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QCoreApplication>
+#include <QSocketNotifier>
+#include <QTimer>
+#include <QDebug>
+#include <QtEndian>
+#include <QJsonDocument>
+#include <QJsonObject>
+
+#include "forklauncher.h"
+
+#if defined(Q_OS_LINUX)
+#include <sys/types.h>
+#include <unistd.h>
+#include <grp.h>
+#endif
+#include <pwd.h>
+
+QT_USE_NAMESPACE_PROCESSMANAGER
+
+class Container : public QObject
+{
+ Q_OBJECT
+
+public:
+ Container() {
+ m_in = new QSocketNotifier( STDIN_FILENO, QSocketNotifier::Read, this );
+ connect(m_in, SIGNAL(activated(int)), SLOT(inReady(int)));
+ m_in->setEnabled(true);
+ m_out = new QSocketNotifier( STDOUT_FILENO, QSocketNotifier::Write, this );
+ connect(m_out, SIGNAL(activated(int)), SLOT(outReady(int)));
+ m_out->setEnabled(false);
+ }
+
+ void handleMessage(const QString& cmd) {
+ if (cmd == QLatin1String("stop")) {
+ qDebug() << "Stopping";
+ exit(0);
+ }
+ else if (cmd == QLatin1String("crash")) {
+ qDebug() << "Crashing";
+ exit(2);
+ }
+ else {
+ m_outbuf.append(cmd.toLatin1());
+ m_outbuf.append('\n');
+ m_out->setEnabled(true);
+ }
+ }
+
+public slots:
+ void inReady(int fd) {
+ m_in->setEnabled(false);
+ const int bufsize = 1024;
+ uint oldSize = m_inbuf.size();
+ m_inbuf.resize(oldSize + bufsize);
+ int n = ::read(fd, m_inbuf.data()+oldSize, bufsize);
+ if (n > 0)
+ m_inbuf.resize(oldSize+n);
+ else
+ m_inbuf.resize(oldSize);
+
+ int offset;
+ while ((offset=m_inbuf.indexOf('\n')) != -1) {
+ QByteArray msg = m_inbuf.left(offset);
+ m_inbuf = m_inbuf.mid(offset + 1);
+ if (msg.size() > 0)
+ handleMessage(QString::fromLocal8Bit(msg));
+ }
+ m_in->setEnabled(true);
+ }
+
+ void outReady(int fd) {
+ m_out->setEnabled(false);
+ if (m_outbuf.size()) {
+ int n = ::write(fd, m_outbuf.data(), m_outbuf.size());
+ if (n == -1) {
+ qDebug() << "Failed to write to stdout";
+ exit(-1);
+ }
+ if (n < m_outbuf.size())
+ m_outbuf = m_outbuf.mid(n);
+ else
+ m_outbuf.clear();
+ }
+ if (m_outbuf.size())
+ m_out->setEnabled(true);
+ }
+
+private:
+ QSocketNotifier *m_in, *m_out;
+ QByteArray m_inbuf, m_outbuf;
+};
+
+int
+main(int argc, char **argv)
+{
+ forklauncher(&argc, &argv);
+ QCoreApplication app(argc, argv);
+ Container c;
+ return app.exec();
+}
+
+#include "main.moc"
diff --git a/tests/auto/processmanager/testForkLauncher/testForkLauncher.pro b/tests/auto/processmanager/testForkLauncher/testForkLauncher.pro
new file mode 100644
index 0000000..bb5184a
--- /dev/null
+++ b/tests/auto/processmanager/testForkLauncher/testForkLauncher.pro
@@ -0,0 +1,11 @@
+CONFIG -= app_bundle
+QT += processmanager
+
+include(../processmanager.pri)
+
+DESTDIR = ./
+SOURCES += main.cpp
+TARGET = testForkLauncher
+
+target.path = $$[QT_INSTALL_TESTS]/$$TESTCASE_NAME/testForkLauncher
+INSTALLS += target
diff --git a/tests/auto/processmanager/testPrelaunch/main.cpp b/tests/auto/processmanager/testPrelaunch/main.cpp
index de83b1a..c53a258 100644
--- a/tests/auto/processmanager/testPrelaunch/main.cpp
+++ b/tests/auto/processmanager/testPrelaunch/main.cpp
@@ -45,6 +45,8 @@
#include <QJsonDocument>
#include <QJsonObject>
#include "processinfo.h"
+#include <signal.h>
+#include <stdio.h>
#if defined(Q_OS_LINUX)
#include <sys/types.h>
@@ -158,6 +160,25 @@ int
main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
+ QStringList args = QCoreApplication::arguments();
+ QString progname = args.takeFirst();
+ while (args.size()) {
+ QString arg = args.at(0);
+ if (!arg.startsWith('-'))
+ break;
+ args.removeFirst();
+ if (arg == QStringLiteral("-noterm")) {
+ struct sigaction action;
+ memset(&action, 0, sizeof(action));
+ action.sa_handler=SIG_IGN;
+ if (sigaction(SIGTERM, &action, NULL) < 0) {
+ perror("Unable ignore SIGTERM");
+ return 1;
+ }
+ qDebug() << "tough";
+ }
+ }
+
Container c;
qDebug() << "testPrelaunch running";
return app.exec();
diff --git a/tests/auto/processmanager/tst_processmanager.cpp b/tests/auto/processmanager/tst_processmanager.cpp
index f47d87c..6b7db25 100644
--- a/tests/auto/processmanager/tst_processmanager.cpp
+++ b/tests/auto/processmanager/tst_processmanager.cpp
@@ -219,10 +219,10 @@ public:
, stderrSpy(process, SIGNAL(standardError(const QByteArray&))) {}
void check( int startCount, int errorCount, int finishedCount, int stateCount ) {
- QVERIFY(startSpy.count() == startCount);
- QVERIFY(errorSpy.count() == errorCount);
- QVERIFY(finishedSpy.count() == finishedCount);
- QVERIFY(stateSpy.count() == stateCount);
+ QCOMPARE(startSpy.count(), startCount);
+ QCOMPARE(errorSpy.count(), errorCount);
+ QCOMPARE(finishedSpy.count(), finishedCount);
+ QCOMPARE(stateSpy.count(), stateCount);
bool failedToStart = false;
for (int i = 0 ; i < errorSpy.count() ; i++)
if (errorSpy.at(i) == QProcess::FailedToStart)
@@ -400,6 +400,47 @@ static void startAndStopClient(ProcessBackendManager *manager, ProcessInfo info,
cleanupProcess(process);
}
+const int kProcessCount = 20;
+
+static void startAndStopMultiple(ProcessBackendManager *manager, ProcessInfo info, CommandFunc func)
+{
+ ProcessBackend *plist[kProcessCount];
+ Spy *slist[kProcessCount];
+
+ for (int i = 0 ; i < kProcessCount ; i++) {
+ ProcessBackend *process = manager->create(info);
+ QVERIFY(process);
+ QVERIFY(process->state() == QProcess::NotRunning);
+ Spy *spy = new Spy(process);
+ plist[i] = process;
+ slist[i] = spy;
+ }
+
+ for (int i = 0 ; i < kProcessCount ; i++ )
+ plist[i]->start();
+
+ for (int i = 0 ; i < kProcessCount ; i++ ) {
+ slist[i]->waitStart();
+ verifyRunning(plist[i]);
+ slist[i]->check(1,0,0,2);
+ }
+
+ for (int i = 0 ; i < kProcessCount ; i++ )
+ func(plist[i], "stop");
+
+ for (int i = 0 ; i < kProcessCount ; i++ ) {
+ slist[i]->waitFinished();
+ slist[i]->check(1,0,1,3);
+ slist[i]->checkExitCode(0);
+ slist[i]->checkExitStatus(QProcess::NormalExit);
+ }
+
+ for (int i = 0 ; i < kProcessCount ; i++) {
+ cleanupProcess(plist[i]);
+ delete slist[i];
+ }
+}
+
static void startAndKillClient(ProcessBackendManager *manager, ProcessInfo info, CommandFunc func)
{
Q_UNUSED(func);
@@ -424,6 +465,15 @@ static void startAndKillClient(ProcessBackendManager *manager, ProcessInfo info,
cleanupProcess(process);
}
+static void startAndKillTough(ProcessBackendManager *manager, ProcessInfo info, CommandFunc func)
+{
+ Q_UNUSED(func);
+ QStringList args = info.arguments();
+ args << "-noterm";
+ info.setArguments(args);
+ startAndKillClient(manager, info, func);
+}
+
static void startAndCrashClient(ProcessBackendManager *manager, ProcessInfo info, CommandFunc func)
{
ProcessBackend *process = manager->create(info);
@@ -608,7 +658,7 @@ static void fixUidGid(ProcessInfo& info)
info.setGid(gidString.toLongLong());
}
-static void standardFactoryTest( clientFunc func )
+static void standardTest( clientFunc func )
{
ProcessBackendManager *manager = new ProcessBackendManager;
@@ -621,7 +671,7 @@ static void standardFactoryTest( clientFunc func )
delete manager;
}
-static void prelaunchFactoryTest( clientFunc func )
+static void prelaunchTest( clientFunc func )
{
ProcessBackendManager *manager = new ProcessBackendManager;
@@ -699,6 +749,25 @@ static void socketLauncherTest( clientFunc func, QStringList args=QStringList()
delete remote;
}
+#if defined(Q_OS_LINUX)
+static void forkLauncherTest( clientFunc func )
+{
+ ProcessBackendManager *manager = new ProcessBackendManager;
+ ProcessInfo info;
+ info.setValue("program", "testForkLauncher/testForkLauncher");
+ manager->addFactory(new PipeProcessBackendFactory(info));
+
+ // Wait for the factory to have launched a pipe
+ waitForInternalProcess(manager);
+ QVERIFY(manager->internalProcesses().count() == 1);
+
+ ProcessInfo info2;
+ fixUidGid(info2);
+ func(manager, info2, writeLine);
+ delete manager;
+}
+#endif
+
static void socketSchemaTest( clientFunc func )
{
QStringList args;
@@ -718,31 +787,37 @@ class tst_ProcessManager : public QObject
private slots:
void initTestCase();
- void standardStartAndStop() { standardFactoryTest(startAndStopClient); }
- void standardStartAndKill() { standardFactoryTest(startAndKillClient); }
- void standardStartAndCrash() { standardFactoryTest(startAndCrashClient); }
- void standardFailToStart() { standardFactoryTest(failToStartClient); }
- void standardEcho() { standardFactoryTest(echoClient); }
- void standardPriorityChangeBefore() { standardFactoryTest(priorityChangeBeforeClient); }
- void standardPriorityChangeAfter() { standardFactoryTest(priorityChangeAfterClient); }
+ void standardStartAndStop() { standardTest(startAndStopClient); }
+ void standardStartAndStopMultiple() { standardTest(startAndStopMultiple); }
+ void standardStartAndKill() { standardTest(startAndKillClient); }
+ void standardStartAndKillTough() { standardTest(startAndKillTough); }
+ void standardStartAndCrash() { standardTest(startAndCrashClient); }
+ void standardFailToStart() { standardTest(failToStartClient); }
+ void standardEcho() { standardTest(echoClient); }
+ void standardPriorityChangeBefore() { standardTest(priorityChangeBeforeClient); }
+ void standardPriorityChangeAfter() { standardTest(priorityChangeAfterClient); }
#if defined(Q_OS_LINUX)
- void standardOomChangeBefore() { standardFactoryTest(oomChangeBeforeClient); }
- void standardOomChangeAfter() { standardFactoryTest(oomChangeAfterClient); }
+ void standardOomChangeBefore() { standardTest(oomChangeBeforeClient); }
+ void standardOomChangeAfter() { standardTest(oomChangeAfterClient); }
#endif
- void prelaunchStartAndStop() { prelaunchFactoryTest(startAndStopClient); }
- void prelaunchStartAndKill() { prelaunchFactoryTest(startAndKillClient); }
- void prelaunchStartAndCrash() { prelaunchFactoryTest(startAndCrashClient); }
- void prelaunchEcho() { prelaunchFactoryTest(echoClient); }
- void prelaunchPriorityChangeBefore() { prelaunchFactoryTest(priorityChangeBeforeClient); }
- void prelaunchPriorityChangeAfter() { prelaunchFactoryTest(priorityChangeAfterClient); }
+ void prelaunchStartAndStop() { prelaunchTest(startAndStopClient); }
+ void prelaunchStartAndStopMultiple() { prelaunchTest(startAndStopMultiple); }
+ void prelaunchStartAndKill() { prelaunchTest(startAndKillClient); }
+ void prelaunchStartAndKillTough() { prelaunchTest(startAndKillTough); }
+ void prelaunchStartAndCrash() { prelaunchTest(startAndCrashClient); }
+ void prelaunchEcho() { prelaunchTest(echoClient); }
+ void prelaunchPriorityChangeBefore() { prelaunchTest(priorityChangeBeforeClient); }
+ void prelaunchPriorityChangeAfter() { prelaunchTest(priorityChangeAfterClient); }
#if defined(Q_OS_LINUX)
- void prelaunchOomChangeBefore() { prelaunchFactoryTest(oomChangeBeforeClient); }
- void prelaunchOomChangeAfter() { prelaunchFactoryTest(oomChangeAfterClient); }
+ void prelaunchOomChangeBefore() { prelaunchTest(oomChangeBeforeClient); }
+ void prelaunchOomChangeAfter() { prelaunchTest(oomChangeAfterClient); }
#endif
void prelaunchRestrictedStartAndStop() { prelaunchRestrictedTest(startAndStopClient); }
+ void prelaunchRestrictedStartAndStopMultiple() { prelaunchRestrictedTest(startAndStopMultiple); }
void prelaunchRestrictedStartAndKill() { prelaunchRestrictedTest(startAndKillClient); }
+ void prelaunchRestrictedStartAndKillTough() { prelaunchRestrictedTest(startAndKillTough); }
void prelaunchRestrictedStartAndCrash() { prelaunchRestrictedTest(startAndCrashClient); }
void prelaunchRestrictedEcho() { prelaunchRestrictedTest(echoClient); }
void prelaunchRestrictedPriorityChangeBefore() { prelaunchRestrictedTest(priorityChangeBeforeClient); }
@@ -753,7 +828,9 @@ private slots:
#endif
void pipeLauncherStartAndStop() { pipeLauncherTest(startAndStopClient); }
+ void pipeLauncherStartAndStopMultiple() { pipeLauncherTest(startAndStopMultiple); }
void pipeLauncherStartAndKill() { pipeLauncherTest(startAndKillClient); }
+ void pipeLauncherStartAndKillTough() { pipeLauncherTest(startAndKillTough); }
void pipeLauncherStartAndCrash() { pipeLauncherTest(startAndCrashClient); }
void pipeLauncherEcho() { pipeLauncherTest(echoClient); }
void pipeLauncherPriorityChangeBefore() { pipeLauncherTest(priorityChangeBeforeClient); }
@@ -764,7 +841,9 @@ private slots:
#endif
void socketLauncherStartAndStop() { socketLauncherTest(startAndStopClient); }
+ void socketLauncherStartAndStopMultiple() { socketLauncherTest(startAndStopMultiple); }
void socketLauncherStartAndKill() { socketLauncherTest(startAndKillClient); }
+ void socketLauncherStartAndKillTough() { socketLauncherTest(startAndKillTough); }
void socketLauncherStartAndCrash() { socketLauncherTest(startAndCrashClient); }
void socketLauncherEcho() { socketLauncherTest(echoClient); }
void socketLauncherPriorityChangeBefore() { socketLauncherTest(priorityChangeBeforeClient); }
@@ -775,7 +854,9 @@ private slots:
#endif
void socketSchemaStartAndStop() { socketSchemaTest(startAndStopClient); }
+ void socketSchemaStartAndStopMultiple() { socketSchemaTest(startAndStopMultiple); }
void socketSchemaStartAndKill() { socketSchemaTest(startAndKillClient); }
+ void socketSchemaStartAndKillTough() { socketSchemaTest(startAndKillTough); }
void socketSchemaStartAndCrash() { socketSchemaTest(startAndCrashClient); }
void socketSchemaEcho() { socketSchemaTest(echoClient); }
void socketSchemaPriorityChangeBefore() { socketSchemaTest(priorityChangeBeforeClient); }
@@ -785,6 +866,19 @@ private slots:
void socketSchemaOomChangeAfter() { socketSchemaTest(oomChangeAfterClient); }
#endif
+#if defined(Q_OS_LINUX)
+ void forkLauncherStartAndStop() { forkLauncherTest(startAndStopClient); }
+ void forkLauncherStartAndStopMultiple() { forkLauncherTest(startAndStopMultiple); }
+ void forkLauncherStartAndKill() { forkLauncherTest(startAndKillClient); }
+ void forkLauncherStartAndKillTough() { forkLauncherTest(startAndKillTough); }
+ void forkLauncherStartAndCrash() { forkLauncherTest(startAndCrashClient); }
+ void forkLauncherEcho() { forkLauncherTest(echoClient); }
+ void forkLauncherPriorityChangeBefore() { forkLauncherTest(priorityChangeBeforeClient); }
+ void forkLauncherPriorityChangeAfter() { forkLauncherTest(priorityChangeAfterClient); }
+ void forkLauncherOomChangeBefore() { forkLauncherTest(oomChangeBeforeClient); }
+ void forkLauncherOomChangeAfter() { forkLauncherTest(oomChangeAfterClient); }
+#endif
+
void prelaunchChildAbort();
void frontend();