forked from qt-creator/qt-creator
307 lines
8.1 KiB
C++
307 lines
8.1 KiB
C++
// Copyright (C) 2022 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
|
|
#include "processtestapp.h"
|
|
|
|
#include <utils/qtcprocess.h>
|
|
|
|
#include <QCoreApplication>
|
|
#include <QDebug>
|
|
#include <QHash>
|
|
#include <QMutex>
|
|
#include <QScopeGuard>
|
|
#include <QThread>
|
|
|
|
#include <iostream>
|
|
|
|
#ifdef Q_OS_WIN
|
|
#include <fcntl.h>
|
|
#include <io.h>
|
|
#else
|
|
#include <atomic>
|
|
#include <signal.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
using namespace Utils;
|
|
|
|
using namespace std::chrono_literals;
|
|
|
|
static QHash<const char *, ProcessTestApp::SubProcessMain> s_subProcesses = {};
|
|
|
|
ProcessTestApp::ProcessTestApp() = default;
|
|
|
|
void ProcessTestApp::invokeSubProcess()
|
|
{
|
|
ProcessTestApp processTestApp;
|
|
int returnValue = 1;
|
|
const QScopeGuard cleanup([&returnValue] {
|
|
QMetaObject::invokeMethod(qApp, [returnValue] {
|
|
qApp->exit(returnValue);
|
|
}, Qt::QueuedConnection);
|
|
});
|
|
|
|
for (auto it = s_subProcesses.constBegin(); it != s_subProcesses.constEnd(); ++it) {
|
|
if (qEnvironmentVariableIsSet(it.key())) {
|
|
returnValue = it.value()();
|
|
return;
|
|
}
|
|
}
|
|
qWarning() << "No test was run!";
|
|
}
|
|
|
|
void ProcessTestApp::registerSubProcess(const char *envVar, const SubProcessMain &main)
|
|
{
|
|
s_subProcesses.insert(envVar, main);
|
|
}
|
|
|
|
void ProcessTestApp::unregisterSubProcess(const char *envVar)
|
|
{
|
|
s_subProcesses.remove(envVar);
|
|
}
|
|
|
|
static QString s_pathToProcessTestApp;
|
|
|
|
static Environment subEnvironment(const char *envVar, const QString &envVal)
|
|
{
|
|
Environment env = Environment::systemEnvironment();
|
|
env.set(QString::fromLatin1(envVar), envVal);
|
|
return env;
|
|
}
|
|
|
|
void SubProcessConfig::setPathToProcessTestApp(const QString &path)
|
|
{
|
|
s_pathToProcessTestApp = path;
|
|
}
|
|
|
|
SubProcessConfig::SubProcessConfig(const char *envVar, const QString &envVal)
|
|
: m_environment(subEnvironment(envVar, envVal))
|
|
{
|
|
}
|
|
|
|
void SubProcessConfig::setupSubProcess(Process *subProcess) const
|
|
{
|
|
subProcess->setEnvironment(m_environment);
|
|
const FilePath filePath = FilePath::fromString(s_pathToProcessTestApp
|
|
+ QLatin1String("/processtestapp")).withExecutableSuffix();
|
|
subProcess->setCommand(CommandLine{filePath});
|
|
}
|
|
|
|
void SubProcessConfig::setupSubProcess(QProcess *subProcess) const
|
|
{
|
|
subProcess->setProcessEnvironment(m_environment.toProcessEnvironment());
|
|
subProcess->setProgram(FilePath::fromString(s_pathToProcessTestApp
|
|
+ QLatin1String("/processtestapp")).withExecutableSuffix().toString());
|
|
}
|
|
|
|
static void doCrash()
|
|
{
|
|
qFatal("The application has crashed purposefully!");
|
|
}
|
|
|
|
static int envVarIntWithDefault(const char *varName, int defaultValue)
|
|
{
|
|
bool ok = false;
|
|
const int result = qEnvironmentVariableIntValue(varName, &ok);
|
|
return ok ? result : defaultValue;
|
|
}
|
|
|
|
int ProcessTestApp::SimpleTest::main()
|
|
{
|
|
const QProcess::ProcessChannel processChannel
|
|
= QProcess::ProcessChannel(envVarIntWithDefault(envVar(), 0));
|
|
if (processChannel == QProcess::StandardOutput)
|
|
std::cout << s_outputData << std::endl;
|
|
else
|
|
std::cerr << s_errorData << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
int ProcessTestApp::ChannelEchoer::main()
|
|
{
|
|
const QProcess::ProcessChannel processChannel
|
|
= QProcess::ProcessChannel(envVarIntWithDefault(envVar(), 0));
|
|
while (true) {
|
|
std::string input;
|
|
std::cin >> input;
|
|
if (input == "exit")
|
|
return 0;
|
|
if (processChannel == QProcess::StandardOutput)
|
|
std::cout << input << std::flush;
|
|
else
|
|
std::cerr << input << std::flush;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int ProcessTestApp::ExitCode::main()
|
|
{
|
|
const int exitCode = qEnvironmentVariableIntValue(envVar());
|
|
std::cout << "Exiting with code:" << exitCode << std::endl;
|
|
return exitCode;
|
|
}
|
|
|
|
int ProcessTestApp::RunBlockingStdOut::main()
|
|
{
|
|
std::cout << "Wait for the Answer to the Ultimate Question of Life, "
|
|
"The Universe, and Everything..." << std::endl;
|
|
std::cout << s_runBlockingStdOutSubProcessMagicWord << "...Now wait for the question...";
|
|
if (qEnvironmentVariable(envVar()) == "true")
|
|
std::cout << std::endl;
|
|
else
|
|
std::cout << std::flush; // otherwise it won't reach the original process (will be buffered)
|
|
QThread::msleep(3000);
|
|
return 0;
|
|
}
|
|
|
|
int ProcessTestApp::LineCallback::main()
|
|
{
|
|
#ifdef Q_OS_WIN
|
|
// Prevent \r\n -> \r\r\n translation.
|
|
_setmode(_fileno(stderr), O_BINARY);
|
|
#endif
|
|
fprintf(stderr, "%s", QByteArray(s_lineCallbackData).replace('|', "").data());
|
|
return 0;
|
|
}
|
|
|
|
int ProcessTestApp::StandardOutputAndErrorWriter::main()
|
|
{
|
|
std::cout << s_outputData << std::endl;
|
|
std::cerr << s_errorData << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
int ProcessTestApp::ChannelForwarding::main()
|
|
{
|
|
const QProcess::ProcessChannelMode channelMode
|
|
= QProcess::ProcessChannelMode(qEnvironmentVariableIntValue(envVar()));
|
|
qunsetenv(envVar());
|
|
|
|
SubProcessConfig subConfig(StandardOutputAndErrorWriter::envVar(), {});
|
|
Process process;
|
|
subConfig.setupSubProcess(&process);
|
|
|
|
process.setProcessChannelMode(channelMode);
|
|
process.start();
|
|
process.waitForFinished();
|
|
return 0;
|
|
}
|
|
|
|
int ProcessTestApp::BlockingProcess::main()
|
|
{
|
|
std::cout << "Blocking process successfully executed." << std::endl;
|
|
const BlockType blockType = BlockType(qEnvironmentVariableIntValue(envVar()));
|
|
bool dummy = true;
|
|
switch (blockType) {
|
|
case BlockType::EndlessLoop:
|
|
while (true) {
|
|
if (dummy) {
|
|
// Note: Keep these lines, otherwise the compiler may optimize out the empty loop.
|
|
std::cout << "EndlessLoop started" << std::endl;
|
|
dummy = false;
|
|
}
|
|
}
|
|
break;
|
|
case BlockType::InfiniteSleep:
|
|
QThread::sleep(INT_MAX);
|
|
break;
|
|
case BlockType::MutexDeadlock: {
|
|
QMutex mutex;
|
|
mutex.lock();
|
|
mutex.lock();
|
|
break;
|
|
}
|
|
case BlockType::EventLoop: {
|
|
QEventLoop loop;
|
|
loop.exec();
|
|
break;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int ProcessTestApp::Crash::main()
|
|
{
|
|
doCrash();
|
|
return 1;
|
|
}
|
|
|
|
int ProcessTestApp::CrashAfterOneSecond::main()
|
|
{
|
|
QThread::sleep(1);
|
|
doCrash();
|
|
return 1;
|
|
}
|
|
|
|
int ProcessTestApp::RecursiveCrashingProcess::main()
|
|
{
|
|
const int currentDepth = qEnvironmentVariableIntValue(envVar());
|
|
if (currentDepth == 1) {
|
|
QThread::sleep(1);
|
|
doCrash();
|
|
return 1;
|
|
}
|
|
SubProcessConfig subConfig(envVar(), QString::number(currentDepth - 1));
|
|
Process process;
|
|
subConfig.setupSubProcess(&process);
|
|
process.start();
|
|
process.waitForFinished();
|
|
if (process.exitStatus() == QProcess::NormalExit)
|
|
return process.exitCode();
|
|
return s_crashCode;
|
|
}
|
|
|
|
#ifndef Q_OS_WIN
|
|
static std::atomic_bool s_terminate = false;
|
|
|
|
void terminate(int signum)
|
|
{
|
|
Q_UNUSED(signum)
|
|
s_terminate.store(true);
|
|
}
|
|
#endif
|
|
|
|
int ProcessTestApp::RecursiveBlockingProcess::main()
|
|
{
|
|
#ifndef Q_OS_WIN
|
|
struct sigaction action;
|
|
memset(&action, 0, sizeof(struct sigaction));
|
|
action.sa_handler = terminate;
|
|
sigaction(SIGTERM, &action, NULL);
|
|
#endif
|
|
|
|
const int currentDepth = qEnvironmentVariableIntValue(envVar());
|
|
if (currentDepth == 1) {
|
|
std::cout << s_leafProcessStarted << std::flush;
|
|
while (true) {
|
|
// TODO: make it configurable so that we could test the reaper timeout
|
|
QThread::msleep(10);
|
|
#ifndef Q_OS_WIN
|
|
if (s_terminate.load()) {
|
|
std::cout << s_leafProcessTerminated << std::flush;
|
|
return s_crashCode;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
SubProcessConfig subConfig(envVar(), QString::number(currentDepth - 1));
|
|
Process process;
|
|
subConfig.setupSubProcess(&process);
|
|
process.setProcessChannelMode(QProcess::ForwardedChannels);
|
|
process.start();
|
|
while (true) {
|
|
if (process.waitForFinished(10ms))
|
|
return 0;
|
|
#ifndef Q_OS_WIN
|
|
if (s_terminate.load()) {
|
|
process.terminate();
|
|
process.waitForFinished(QDeadlineTimer::Forever);
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
if (process.exitStatus() == QProcess::NormalExit)
|
|
return process.exitCode();
|
|
return s_crashCode;
|
|
}
|