// 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 #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include #include #else #include #include #include #endif using namespace Utils; using namespace std::chrono_literals; static QHash 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; }