forked from qt-creator/qt-creator
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 <alessandro.portale@qt.io> Reviewed-by: Christian Stenger <christian.stenger@qt.io> Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
This commit is contained in:
@@ -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
|
||||
)
|
||||
|
17
tests/auto/utils/qtcprocess/processtestapp/CMakeLists.txt
Normal file
17
tests/auto/utils/qtcprocess/processtestapp/CMakeLists.txt
Normal file
@@ -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}"
|
||||
)
|
64
tests/auto/utils/qtcprocess/processtestapp/main.cpp
Normal file
64
tests/auto/utils/qtcprocess/processtestapp/main.cpp
Normal file
@@ -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 <app/app_version.h>
|
||||
|
||||
#include <utils/launcherinterface.h>
|
||||
#include <utils/qtcprocess.h>
|
||||
#include <utils/temporarydirectory.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QScopeGuard>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <crtdbg.h>
|
||||
#include <cstdlib>
|
||||
#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();
|
||||
}
|
208
tests/auto/utils/qtcprocess/processtestapp/processtestapp.cpp
Normal file
208
tests/auto/utils/qtcprocess/processtestapp/processtestapp.cpp
Normal file
@@ -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 <utils/qtcprocess.h>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QHash>
|
||||
#include <QMutex>
|
||||
#include <QScopeGuard>
|
||||
#include <QThread>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#endif
|
||||
|
||||
using namespace Utils;
|
||||
|
||||
static QHash<const char *, ProcessTestApp::SubProcessMain> 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;
|
||||
}
|
120
tests/auto/utils/qtcprocess/processtestapp/processtestapp.h
Normal file
120
tests/auto/utils/qtcprocess/processtestapp/processtestapp.h
Normal file
@@ -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 <functional>
|
||||
|
||||
#include <utils/commandline.h>
|
||||
#include <utils/environment.h>
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
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<int ()>;
|
||||
|
||||
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| <delay> Rebasing (2/10)\r| <delay> ...\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)
|
@@ -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",
|
||||
]
|
||||
}
|
@@ -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"
|
||||
}
|
||||
|
@@ -23,6 +23,8 @@
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "processtestapp/processtestapp.h"
|
||||
|
||||
#include <app/app_version.h>
|
||||
|
||||
#include <utils/environment.h>
|
||||
@@ -42,11 +44,6 @@
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
#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<void ()>;
|
||||
static QMap<const char *, SubProcessMain> 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<QString, QString> 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<int>("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<bool>("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| <delay> Rebasing (2/10)\r| <delay> ...\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<QProcess::ProcessChannelMode>("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>("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);
|
||||
|
||||
|
Reference in New Issue
Block a user