From 8f427c1c959e9500287a8315eedc4e2d4dcdac7d Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Fri, 1 Apr 2022 14:15:30 +0200 Subject: [PATCH] tst_QtcProcess: Move subprocesses into a separate executable Don't invoke subcreator test in order to run a custom process. Run a separate executable instead. The advantages are: 1. Faster test running (no need for QTest specific code path when running subprocess). 2. Don't use std::exit() from subprocesses as it doesn't clean up properly. Use qApp->exit() instead. 3. This should support returning NormalExit and proper exit code of subprocess in case the code wasn't 0. Change-Id: I1395bd8a7873c95a594c3e054087f00c55a15376 Reviewed-by: Alessandro Portale Reviewed-by: Christian Stenger Reviewed-by: --- tests/auto/utils/qtcprocess/CMakeLists.txt | 5 + .../qtcprocess/processtestapp/CMakeLists.txt | 17 ++ .../utils/qtcprocess/processtestapp/main.cpp | 64 +++++ .../processtestapp/processtestapp.cpp | 208 ++++++++++++++ .../processtestapp/processtestapp.h | 120 +++++++++ .../processtestapp/processtestapp.qbs | 30 +++ tests/auto/utils/qtcprocess/qtcprocess.qbs | 38 ++- .../auto/utils/qtcprocess/tst_qtcprocess.cpp | 255 ++---------------- 8 files changed, 486 insertions(+), 251 deletions(-) create mode 100644 tests/auto/utils/qtcprocess/processtestapp/CMakeLists.txt create mode 100644 tests/auto/utils/qtcprocess/processtestapp/main.cpp create mode 100644 tests/auto/utils/qtcprocess/processtestapp/processtestapp.cpp create mode 100644 tests/auto/utils/qtcprocess/processtestapp/processtestapp.h create mode 100644 tests/auto/utils/qtcprocess/processtestapp/processtestapp.qbs diff --git a/tests/auto/utils/qtcprocess/CMakeLists.txt b/tests/auto/utils/qtcprocess/CMakeLists.txt index 5d7985a2822..51720bbeb5b 100644 --- a/tests/auto/utils/qtcprocess/CMakeLists.txt +++ b/tests/auto/utils/qtcprocess/CMakeLists.txt @@ -1,8 +1,13 @@ +add_subdirectory(processtestapp) + file(RELATIVE_PATH RELATIVE_TEST_PATH "${PROJECT_BINARY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}") file(RELATIVE_PATH TEST_RELATIVE_LIBEXEC_PATH "/${RELATIVE_TEST_PATH}" "/${IDE_LIBEXEC_PATH}") add_qtc_test(tst_qtcprocess DEFINES "TEST_RELATIVE_LIBEXEC_PATH=\"${TEST_RELATIVE_LIBEXEC_PATH}\"" + "PROCESS_TESTAPP=\"${CMAKE_CURRENT_BINARY_DIR}/processtestapp\"" DEPENDS Utils app_version SOURCES tst_qtcprocess.cpp + processtestapp/processtestapp.h + processtestapp/processtestapp.cpp ) diff --git a/tests/auto/utils/qtcprocess/processtestapp/CMakeLists.txt b/tests/auto/utils/qtcprocess/processtestapp/CMakeLists.txt new file mode 100644 index 00000000000..7372cda14a7 --- /dev/null +++ b/tests/auto/utils/qtcprocess/processtestapp/CMakeLists.txt @@ -0,0 +1,17 @@ +file(RELATIVE_PATH RELATIVE_TEST_PATH "${PROJECT_BINARY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}") +file(RELATIVE_PATH TEST_RELATIVE_LIBEXEC_PATH "/${RELATIVE_TEST_PATH}" "/${IDE_LIBEXEC_PATH}") + +add_qtc_executable(processtestapp + DEFINES "TEST_RELATIVE_LIBEXEC_PATH=\"${TEST_RELATIVE_LIBEXEC_PATH}\"" + "PROCESS_TESTAPP=\"${CMAKE_CURRENT_BINARY_DIR}\"" + DEPENDS Utils app_version + SOURCES main.cpp processtestapp.h processtestapp.cpp + SKIP_INSTALL + INTERNAL_ONLY +) + +set_target_properties(processtestapp PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" +) diff --git a/tests/auto/utils/qtcprocess/processtestapp/main.cpp b/tests/auto/utils/qtcprocess/processtestapp/main.cpp new file mode 100644 index 00000000000..25d5d7aafa7 --- /dev/null +++ b/tests/auto/utils/qtcprocess/processtestapp/main.cpp @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "processtestapp.h" + +#include + +#include +#include +#include + +#include +#include + +#ifdef Q_OS_WIN +#include +#include +#endif + +using namespace Utils; + +int main(int argc, char **argv) +{ +#ifdef Q_OS_WIN + // avoid crash reporter dialog + _set_error_mode(_OUT_TO_STDERR); + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG); +#endif + QCoreApplication app(argc, argv); + + auto cleanup = qScopeGuard([] { Singleton::deleteAll(); }); + + TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath() + "/" + + Core::Constants::IDE_CASED_ID + "-XXXXXX"); + const QString libExecPath(qApp->applicationDirPath() + '/' + + QLatin1String(TEST_RELATIVE_LIBEXEC_PATH)); + LauncherInterface::setPathToLauncher(libExecPath); + SubProcessConfig::setPathToProcessTestApp(QLatin1String(PROCESS_TESTAPP)); + + QMetaObject::invokeMethod(&app, [] { ProcessTestApp::invokeSubProcess(); }, Qt::QueuedConnection); + return app.exec(); +} diff --git a/tests/auto/utils/qtcprocess/processtestapp/processtestapp.cpp b/tests/auto/utils/qtcprocess/processtestapp/processtestapp.cpp new file mode 100644 index 00000000000..4ea6d5f26f2 --- /dev/null +++ b/tests/auto/utils/qtcprocess/processtestapp/processtestapp.cpp @@ -0,0 +1,208 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "processtestapp.h" + +#include + +#include +#include +#include +#include +#include + +#include + +#ifdef Q_OS_WIN +#include +#include +#endif + +using namespace Utils; + +static QHash s_subProcesses = {}; + +ProcessTestApp::ProcessTestApp() = default; + +void ProcessTestApp::invokeSubProcess() +{ + ProcessTestApp processTestApp; + int returnValue = 1; + auto cleanup = qScopeGuard([&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(QtcProcess *subProcess) +{ + subProcess->setEnvironment(m_environment); + const FilePath filePath = FilePath::fromString(s_pathToProcessTestApp + + QLatin1String("/processtestapp")).withExecutableSuffix(); + subProcess->setCommand(CommandLine(filePath, {})); +} + +static void doCrash() +{ + qFatal("The application has crashed purposefully!"); +} + +int ProcessTestApp::SimpleTest::main() +{ + std::cout << s_simpleTestData << std::endl; + 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; + QThread::msleep(300); + 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(5000); + 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(), {}); + QtcProcess process; + subConfig.setupSubProcess(&process); + + process.setProcessChannelMode(channelMode); + process.start(); + process.waitForFinished(); + return 0; +} + +int ProcessTestApp::KillBlockingProcess::main() +{ + std::cout << "Blocking process successfully executed." << std::endl; + const BlockType blockType = BlockType(qEnvironmentVariableIntValue(envVar())); + switch (blockType) { + case BlockType::EndlessLoop: + while (true) + ; + 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::EmitOneErrorOnCrash::main() +{ + doCrash(); + return 1; +} + +int ProcessTestApp::CrashAfterOneSecond::main() +{ + QThread::sleep(1); + doCrash(); + return 1; +} diff --git a/tests/auto/utils/qtcprocess/processtestapp/processtestapp.h b/tests/auto/utils/qtcprocess/processtestapp/processtestapp.h new file mode 100644 index 00000000000..6523f8c8bca --- /dev/null +++ b/tests/auto/utils/qtcprocess/processtestapp/processtestapp.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include + +#include +#include +#include + +namespace Utils { class QtcProcess; } + +#define SUB_PROCESS(SubProcessClass)\ +class SubProcessClass\ +{\ +public:\ + static const char *envVar() { return m_envVar; }\ +private:\ + SubProcessClass() { registerSubProcess(envVar(), &SubProcessClass::main); }\ + ~SubProcessClass() { unregisterSubProcess(envVar()); }\ + static int main();\ + static constexpr char m_envVar[] = "TST_QTC_PROCESS_" QTC_ASSERT_STRINGIFY(SubProcessClass);\ + friend class ProcessTestApp;\ +};\ +\ +SubProcessClass m_ ## SubProcessClass + +class ProcessTestApp +{ +public: + using SubProcessMain = std::function; + + static void invokeSubProcess(); + + // Many tests inside tst_qtcprocess need to start a new subprocess with custom code. + // In order to simplify things we produce just one separate executable - processtestapp. + // We embed all our custom subprocesses in processtestapp and enclose them in separate + // classes. We select desired process to run by setting the relevant environment variable. + // Test classes are defined by the SUB_PROCESS macro. The macro defines a class + // alongside of the corresponding environment variable which is set prior to the execution + // of the subprocess. The following subprocess classes are defined: + + SUB_PROCESS(SimpleTest); + SUB_PROCESS(ExitCode); + SUB_PROCESS(RunBlockingStdOut); + SUB_PROCESS(LineCallback); + SUB_PROCESS(StandardOutputAndErrorWriter); + SUB_PROCESS(ChannelForwarding); + SUB_PROCESS(KillBlockingProcess); + SUB_PROCESS(EmitOneErrorOnCrash); + SUB_PROCESS(CrashAfterOneSecond); + + // In order to get a value associated with the certain subprocess use SubProcessClass::envVar(). + // The classes above define different custom executables. Inside invokeSubProcess(), called + // by processtestapp, we are detecting if one of these variables is set and invoke a respective + // custom executable code directly. The exit code of the process is reported to the caller + // by the return value of SubProcessClass::main(). + +private: + ProcessTestApp(); + + static void registerSubProcess(const char *envVar, const SubProcessMain &main); + static void unregisterSubProcess(const char *envVar); +}; + +class SubProcessConfig +{ +public: + SubProcessConfig(const char *envVar, const QString &envVal); + void setupSubProcess(Utils::QtcProcess *subProcess); + + static void setPathToProcessTestApp(const QString &path); + +private: + const Utils::Environment m_environment; +}; + +static const char s_simpleTestData[] = "Test process successfully executed."; +static const char s_runBlockingStdOutSubProcessMagicWord[] = "42"; + +// Expect ending lines detected at '|': +const char s_lineCallbackData[] = + "This is the first line\r\n|" + "Here comes the second one\r\n|" + "And a line without LF\n|" + "Rebasing (1/10)\r| Rebasing (2/10)\r| ...\r\n|" + "And no end"; + +static const char s_outputData[] = "This is the output message."; +static const char s_errorData[] = "This is the error message."; + +enum class BlockType { + EndlessLoop, + InfiniteSleep, + MutexDeadlock, + EventLoop +}; + +Q_DECLARE_METATYPE(BlockType) diff --git a/tests/auto/utils/qtcprocess/processtestapp/processtestapp.qbs b/tests/auto/utils/qtcprocess/processtestapp/processtestapp.qbs new file mode 100644 index 00000000000..a27c4e0da0b --- /dev/null +++ b/tests/auto/utils/qtcprocess/processtestapp/processtestapp.qbs @@ -0,0 +1,30 @@ +import qbs.FileInfo + +QtApplication { + name: "processtestapp" + Depends { name: "app_version_header" } + Depends { name: "qtc" } + Depends { name: "Utils" } + + consoleApplication: true + cpp.cxxLanguageVersion: "c++17" + cpp.defines: { + var defines = base; + var absLibExecPath = FileInfo.joinPaths(qbs.installRoot, qbs.installPrefix, + qtc.ide_libexec_path); + var relLibExecPath = FileInfo.relativePath(destinationDirectory, absLibExecPath); + defines.push('TEST_RELATIVE_LIBEXEC_PATH="' + relLibExecPath + '"'); + defines.push('PROCESS_TESTAPP="' + destinationDirectory + '"'); + return defines; + } + + install: false + destinationDirectory: project.buildDirectory + '/' + + FileInfo.relativePath(project.ide_source_tree, sourceDirectory) + + files: [ + "main.cpp", + "processtestapp.cpp", + "processtestapp.h", + ] +} diff --git a/tests/auto/utils/qtcprocess/qtcprocess.qbs b/tests/auto/utils/qtcprocess/qtcprocess.qbs index 04ffafef1c7..cc37a0124fc 100644 --- a/tests/auto/utils/qtcprocess/qtcprocess.qbs +++ b/tests/auto/utils/qtcprocess/qtcprocess.qbs @@ -1,19 +1,29 @@ import qbs.FileInfo -QtcAutotest { - name: "QtcProcess autotest" - Depends { name: "Utils" } - Depends { name: "app_version_header" } +Project { + QtcAutotest { + name: "QtcProcess autotest" - files: "tst_qtcprocess.cpp" - cpp.defines: { - var defines = base; - if (qbs.targetOS === "windows") - defines.push("_CRT_SECURE_NO_WARNINGS"); - var absLibExecPath = FileInfo.joinPaths(qbs.installRoot, qbs.installPrefix, - qtc.ide_libexec_path); - var relLibExecPath = FileInfo.relativePath(destinationDirectory, absLibExecPath); - defines.push('TEST_RELATIVE_LIBEXEC_PATH="' + relLibExecPath + '"'); - return defines; + Depends { name: "Utils" } + Depends { name: "app_version_header" } + + files: [ + "processtestapp/processtestapp.cpp", + "processtestapp/processtestapp.h", + "tst_qtcprocess.cpp", + ] + cpp.defines: { + var defines = base; + if (qbs.targetOS === "windows") + defines.push("_CRT_SECURE_NO_WARNINGS"); + var absLibExecPath = FileInfo.joinPaths(qbs.installRoot, qbs.installPrefix, + qtc.ide_libexec_path); + var relLibExecPath = FileInfo.relativePath(destinationDirectory, absLibExecPath); + defines.push('TEST_RELATIVE_LIBEXEC_PATH="' + relLibExecPath + '"'); + defines.push('PROCESS_TESTAPP="' + + FileInfo.joinPaths(destinationDirectory, "processtestapp") + '"'); + return defines; + } } + references: "processtestapp/processtestapp.qbs" } diff --git a/tests/auto/utils/qtcprocess/tst_qtcprocess.cpp b/tests/auto/utils/qtcprocess/tst_qtcprocess.cpp index 87efd924e47..2197c9b7950 100644 --- a/tests/auto/utils/qtcprocess/tst_qtcprocess.cpp +++ b/tests/auto/utils/qtcprocess/tst_qtcprocess.cpp @@ -23,6 +23,8 @@ ** ****************************************************************************/ +#include "processtestapp/processtestapp.h" + #include #include @@ -42,11 +44,6 @@ #include #include -#ifdef Q_OS_WIN - #include - #include -#endif - using namespace Utils; // This handler is inspired by the one used in qtbase/tests/auto/corelib/io/qfile/tst_qfile.cpp @@ -86,70 +83,6 @@ protected: int MessageHandler::s_destroyCount = 0; QtMessageHandler MessageHandler::s_oldMessageHandler = 0; -using SubProcessMain = std::function; -static QMap s_subProcesses = {}; - -static void invokeSubProcessIfRequired() -{ - for (auto it = s_subProcesses.constBegin(); it != s_subProcesses.constEnd(); ++it) { - if (qEnvironmentVariableIsSet(it.key())) - it.value()(); - } -} - -static void registerSubProcess(const char *envVar, const SubProcessMain &main) -{ - s_subProcesses.insert(envVar, main); -} - -#define SUB_CREATOR_PROCESS(SubProcessClass)\ -class SubProcessClass\ -{\ -public:\ - SubProcessClass()\ - {\ - registerSubProcess(envVar(), &SubProcessClass::main);\ - }\ - static const char *envVar() { return m_envVar; }\ -private:\ - static void main();\ - static constexpr char m_envVar[] = "TST_QTC_PROCESS_" QTC_ASSERT_STRINGIFY(SubProcessClass);\ -};\ -\ -SubProcessClass m_ ## SubProcessClass - -static Environment subEnvironment(const char *envVar, const QString &envVal) -{ - Environment env = Environment::systemEnvironment(); - env.set(envVar, envVal); - return env; -} - -static CommandLine subCommandLine() -{ - QStringList args = QCoreApplication::arguments(); - const QString binary = args.takeFirst(); - const FilePath filePath = FilePath::fromString(QDir::currentPath()).resolvePath(binary); - return CommandLine(filePath, args); -} - -class SubCreatorConfig -{ -public: - SubCreatorConfig(const char *envVar, const QString &envVal) - : m_environment(subEnvironment(envVar, envVal)) - , m_commandLine(subCommandLine()) {} - - void setupSubProcess(QtcProcess *subProcess) - { - subProcess->setEnvironment(m_environment); - subProcess->setCommand(m_commandLine); - } -private: - const Environment m_environment; - const CommandLine m_commandLine; -}; - static std::atomic_int s_processCounter = 0; static const bool s_removeSingletonsOnLastProcess = false; // The use of this class (when s_removeSingletonsOnLastProcess = true) guarantees that if all @@ -193,11 +126,6 @@ private: QHash m_map; }; -static void doCrash() -{ - qFatal("The application has crashed purposefully!"); -} - class tst_QtcProcess : public QObject { Q_OBJECT @@ -238,31 +166,6 @@ private slots: void cleanupTestCase(); private: - // Many tests in this file need to start a new subprocess with custom code. - // In order to simplify things we don't produce separate executables, but invoke - // the same test process recursively. These tests are embedded in classes, - // defined by the SUB_CREATOR_PROCESS macro. The macro defines a class alongside of the - // corresponding environment variable which is set prior to the execution of the subprocess. - // The following subprocess classes are defined: - - SUB_CREATOR_PROCESS(SimpleTest); - SUB_CREATOR_PROCESS(ExitCode); - SUB_CREATOR_PROCESS(RunBlockingStdOut); - SUB_CREATOR_PROCESS(LineCallback); - SUB_CREATOR_PROCESS(ChannelForwarding); - SUB_CREATOR_PROCESS(StandardOutputAndErrorWriter); - SUB_CREATOR_PROCESS(KillBlockingProcess); - SUB_CREATOR_PROCESS(EmitOneErrorOnCrash); - SUB_CREATOR_PROCESS(CrashAfterOneSecond); - - // In order to get a value associated with the certain subprocess use SubProcessClass::envVar(). - // The classes above define different custom executables. Inside initTestCase() - // we are detecting if one of these variables is set (by a call to - // invokeSubProcessIfRequired()) and invoke directly a respective custom - // executable code, which always ends up with a call to exit(). In this way (by calling exit() - // by the end of each subprocess executable) we stop the recursion, as from the test point of - // view we meant to execute only our custom code without further execution of the test itself. - void iteratorEditsHelper(OsType osType); Environment envWindows; @@ -276,23 +179,15 @@ private: MessageHandler *msgHandler = nullptr; }; -static const char s_simpleTestData[] = "Test process successfully executed."; - -void tst_QtcProcess::SimpleTest::main() -{ - std::cout << s_simpleTestData << std::endl; - exit(0); -} - void tst_QtcProcess::initTestCase() { msgHandler = new MessageHandler; Utils::TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath() + "/" + Core::Constants::IDE_CASED_ID + "-XXXXXX"); - Utils::LauncherInterface::setPathToLauncher(qApp->applicationDirPath() + '/' - + QLatin1String(TEST_RELATIVE_LIBEXEC_PATH)); - - invokeSubProcessIfRequired(); + const QString libExecPath(qApp->applicationDirPath() + '/' + + QLatin1String(TEST_RELATIVE_LIBEXEC_PATH)); + LauncherInterface::setPathToLauncher(libExecPath); + SubProcessConfig::setPathToProcessTestApp(QLatin1String(PROCESS_TESTAPP)); homeStr = QLatin1String("@HOME@"); home = QDir::homePath(); @@ -959,13 +854,6 @@ void tst_QtcProcess::iteratorEditsLinux() iteratorEditsHelper(OsTypeLinux); } -void tst_QtcProcess::ExitCode::main() -{ - const int exitCode = qEnvironmentVariableIntValue(envVar()); - std::cout << "Exiting with code:" << exitCode << std::endl; - exit(exitCode); -} - void tst_QtcProcess::exitCode_data() { QTest::addColumn("exitCode"); @@ -984,7 +872,7 @@ void tst_QtcProcess::exitCode() { QFETCH(int, exitCode); - SubCreatorConfig subConfig(ExitCode::envVar(), QString::number(exitCode)); + SubProcessConfig subConfig(ProcessTestApp::ExitCode::envVar(), QString::number(exitCode)); { TestProcess process; subConfig.setupSubProcess(&process); @@ -1005,22 +893,6 @@ void tst_QtcProcess::exitCode() } } -static const char s_runBlockingStdOutSubProcessMagicWord[] = "42"; - -void tst_QtcProcess::RunBlockingStdOut::main() -{ - std::cout << "Wait for the Answer to the Ultimate Question of Life, " - "The Universe, and Everything..." << std::endl; - QThread::msleep(300); - 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(5000); - exit(0); -} - void tst_QtcProcess::runBlockingStdOut_data() { QTest::addColumn("withEndl"); @@ -1048,7 +920,7 @@ void tst_QtcProcess::runBlockingStdOut() QFETCH(int, timeOutS); QFETCH(ProcessResult, expectedResult); - SubCreatorConfig subConfig(RunBlockingStdOut::envVar(), withEndl ? "true" : "false"); + SubProcessConfig subConfig(ProcessTestApp::RunBlockingStdOut::envVar(), withEndl ? "true" : "false"); TestProcess process; subConfig.setupSubProcess(&process); @@ -1068,28 +940,9 @@ void tst_QtcProcess::runBlockingStdOut() QVERIFY2(readLastLine, "Last line was read."); } -// Expect ending lines detected at '|': -const char s_lineCallbackData[] = - "This is the first line\r\n|" - "Here comes the second one\r\n|" - "And a line without LF\n|" - "Rebasing (1/10)\r| Rebasing (2/10)\r| ...\r\n|" - "And no end"; - - -void tst_QtcProcess::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()); - exit(0); -} - void tst_QtcProcess::lineCallback() { - SubCreatorConfig subConfig(LineCallback::envVar(), {}); + SubProcessConfig subConfig(ProcessTestApp::LineCallback::envVar(), {}); TestProcess process; subConfig.setupSubProcess(&process); @@ -1133,7 +986,7 @@ void tst_QtcProcess::lineCallbackIntern() void tst_QtcProcess::waitForStartedAndFinished() { - SubCreatorConfig subConfig(SimpleTest::envVar(), {}); + SubProcessConfig subConfig(ProcessTestApp::SimpleTest::envVar(), {}); TestProcess process; subConfig.setupSubProcess(&process); @@ -1187,32 +1040,6 @@ void tst_QtcProcess::notRunningAfterStartingNonExistingProgram() // and the error channels. Then ChannelForwarding::main() either forwards these channels // or not - we check it in the outer channelForwarding() test. -static const char s_outputData[] = "This is the output message."; -static const char s_errorData[] = "This is the error message."; - -void tst_QtcProcess::StandardOutputAndErrorWriter::main() -{ - std::cout << s_outputData << std::endl; - std::cerr << s_errorData << std::endl; - exit(0); -} - -void tst_QtcProcess::ChannelForwarding::main() -{ - const QProcess::ProcessChannelMode channelMode - = QProcess::ProcessChannelMode(qEnvironmentVariableIntValue(envVar())); - qunsetenv(envVar()); - - SubCreatorConfig subConfig(StandardOutputAndErrorWriter::envVar(), {}); - TestProcess process; - subConfig.setupSubProcess(&process); - - process.setProcessChannelMode(channelMode); - process.start(); - process.waitForFinished(); - exit(0); -} - void tst_QtcProcess::channelForwarding_data() { QTest::addColumn("channelMode"); @@ -1232,7 +1059,8 @@ void tst_QtcProcess::channelForwarding() QFETCH(bool, outputForwarded); QFETCH(bool, errorForwarded); - SubCreatorConfig subConfig(ChannelForwarding::envVar(), QString::number(int(channelMode))); + SubProcessConfig subConfig(ProcessTestApp::ChannelForwarding::envVar(), + QString::number(int(channelMode))); TestProcess process; subConfig.setupSubProcess(&process); @@ -1275,7 +1103,7 @@ void tst_QtcProcess::mergedChannels() QFETCH(bool, errorOnOutput); QFETCH(bool, errorOnError); - SubCreatorConfig subConfig(StandardOutputAndErrorWriter::envVar(), {}); + SubProcessConfig subConfig(ProcessTestApp::StandardOutputAndErrorWriter::envVar(), {}); TestProcess process; subConfig.setupSubProcess(&process); @@ -1292,43 +1120,6 @@ void tst_QtcProcess::mergedChannels() QCOMPARE(error.contains(QByteArray(s_errorData)), errorOnError); } -enum class BlockType { - EndlessLoop, - InfiniteSleep, - MutexDeadlock, - EventLoop -}; - -Q_DECLARE_METATYPE(BlockType) - -void tst_QtcProcess::KillBlockingProcess::main() -{ - std::cout << "Blocking process successfully executed." << std::endl; - const BlockType blockType = BlockType(qEnvironmentVariableIntValue(envVar())); - switch (blockType) { - case BlockType::EndlessLoop: - while (true) - ; - 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; - } - - } - exit(1); -} - void tst_QtcProcess::killBlockingProcess_data() { QTest::addColumn("blockType"); @@ -1343,7 +1134,8 @@ void tst_QtcProcess::killBlockingProcess() { QFETCH(BlockType, blockType); - SubCreatorConfig subConfig(KillBlockingProcess::envVar(), QString::number(int(blockType))); + SubProcessConfig subConfig(ProcessTestApp::KillBlockingProcess::envVar(), + QString::number(int(blockType))); TestProcess process; subConfig.setupSubProcess(&process); @@ -1354,7 +1146,7 @@ void tst_QtcProcess::killBlockingProcess() void tst_QtcProcess::flushFinishedWhileWaitingForReadyRead() { - SubCreatorConfig subConfig(SimpleTest::envVar(), {}); + SubProcessConfig subConfig(ProcessTestApp::SimpleTest::envVar(), {}); TestProcess process; subConfig.setupSubProcess(&process); @@ -1377,14 +1169,9 @@ void tst_QtcProcess::flushFinishedWhileWaitingForReadyRead() QVERIFY(reply.contains(s_simpleTestData)); } -void tst_QtcProcess::EmitOneErrorOnCrash::main() -{ - doCrash(); -} - void tst_QtcProcess::emitOneErrorOnCrash() { - SubCreatorConfig subConfig(EmitOneErrorOnCrash::envVar(), {}); + SubProcessConfig subConfig(ProcessTestApp::EmitOneErrorOnCrash::envVar(), {}); TestProcess process; subConfig.setupSubProcess(&process); @@ -1401,15 +1188,9 @@ void tst_QtcProcess::emitOneErrorOnCrash() QCOMPARE(process.error(), QProcess::Crashed); } -void tst_QtcProcess::CrashAfterOneSecond::main() -{ - QThread::sleep(1); - doCrash(); -} - void tst_QtcProcess::crashAfterOneSecond() { - SubCreatorConfig subConfig(CrashAfterOneSecond::envVar(), {}); + SubProcessConfig subConfig(ProcessTestApp::CrashAfterOneSecond::envVar(), {}); TestProcess process; subConfig.setupSubProcess(&process);