diff --git a/tests/auto/utils/qtcprocess/tst_qtcprocess.cpp b/tests/auto/utils/qtcprocess/tst_qtcprocess.cpp index f8422c053d4..2b9924ffa06 100644 --- a/tests/auto/utils/qtcprocess/tst_qtcprocess.cpp +++ b/tests/auto/utils/qtcprocess/tst_qtcprocess.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -46,21 +47,24 @@ #include #endif - using namespace Utils; +#define SUB_CREATOR_PROCESS(var)\ +const char var[] = "TST_QTC_PROCESS_" QTC_ASSERT_STRINGIFY(var);\ +static void var ## Main(); + // 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 and prior to the execution we set one of the // following environment variables: -const char kExitCodeSubProcessCode[] = "QTC_TST_QTCPROCESS_EXITCODE_CODE"; -const char kRunBlockingStdOutSubProcessWithEndl[] = "QTC_TST_QTCPROCESS_RUNBLOCKINGSTDOUT_WITHENDL"; -const char kLineCallback[] = "QTC_TST_QTCPROCESS_LINECALLBACK"; -const char kTestProcess[] = "QTC_TST_TEST_PROCESS"; -const char kForwardProcess[] = "QTC_TST_FORWARD_PROCESS"; -const char kForwardSubProcess[] = "QTC_TST_FORWARD_SUB_PROCESS"; -const char kBlockingProcess[] = "QTC_TST_BLOCKING_PROCESS"; +SUB_CREATOR_PROCESS(subExitCode); +SUB_CREATOR_PROCESS(subRunBlockingStdOut); +SUB_CREATOR_PROCESS(subLineCallback); +SUB_CREATOR_PROCESS(subSimpleTest); +SUB_CREATOR_PROCESS(subProcessChannelForwarding); +SUB_CREATOR_PROCESS(subSubProcessChannelForwarding); +SUB_CREATOR_PROCESS(subKillBlockingProcess); // The variables above are meant to be different custom executables. Inside initTestCase() // we are detecting if one of these variables is set and invoke directly a respective custom @@ -68,7 +72,7 @@ const char kBlockingProcess[] = "QTC_TST_BLOCKING_PROCESS"; // the recursion, as from the test point of view we meant to execute only our custom code // without further execution of the test itself. -const char testProcessData[] = "Test process successfully executed."; +const char simpleTestData[] = "Test process successfully executed."; const char forwardedOutputData[] = "This is the output message."; const char forwardedErrorData[] = "This is the error message."; const char runBlockingStdOutSubProcessMagicWord[] = "42"; @@ -81,82 +85,62 @@ const char lineCallbackData[] = "Rebasing (1/10)\r| Rebasing (2/10)\r| ...\r\n|" "And no end"; -static void exitCodeSubProcessMain() -{ - const int exitCode = qEnvironmentVariableIntValue(kExitCodeSubProcessCode); - std::cout << "Exiting with code:" << exitCode << std::endl; - exit(exitCode); -} - -static void blockingStdOutSubProcessMain() -{ - std::cout << "Wait for the Answer to the Ultimate Question of Life, " - "The Universe, and Everything..." << std::endl; - QThread::msleep(300); - std::cout << runBlockingStdOutSubProcessMagicWord << "...Now wait for the question..."; - if (qEnvironmentVariable(kRunBlockingStdOutSubProcessWithEndl) == "true") - std::cout << std::endl; - QThread::msleep(5000); - exit(0); -} - -static void lineCallbackMain() -{ -#ifdef Q_OS_WIN - // Prevent \r\n -> \r\r\n translation. - setmode(fileno(stderr), O_BINARY); -#endif - fprintf(stderr, "%s", QByteArray(lineCallbackData).replace('|', "").data()); - exit(0); -} - -static void testProcessSubProcessMain() -{ - std::cout << testProcessData << std::endl; - exit(0); -} - -// Since we want to test whether the process forwards its channels or not, we can't just create -// a process and start it, because in this case there is no way on how to check whether something -// went into out output channels or not. - -// So we start two processes in chain instead. On the beginning the processChannelForwarding() -// test starts the "testForwardProcessMain" - this one will start another process -// "testForwardSubProcessMain" with forwarding options. The "testForwardSubProcessMain" -// is very simple - it just puts something to the output and the error channels. -// Then "testForwardProcessMain" either forwards these channels or not - we check it in the outer -// processChannelForwarding() test. -static void testForwardProcessMain() +static Environment subEnvironment(const char *envVar, const QString &envVal) { Environment env = Environment::systemEnvironment(); - env.set(kForwardSubProcess, {}); + env.set(envVar, envVal); + return env; +} + +static CommandLine subCommandLine() +{ QStringList args = QCoreApplication::arguments(); const QString binary = args.takeFirst(); - const CommandLine command(FilePath::fromString(binary), args); - - QtcProcess process; - const QProcess::ProcessChannelMode channelMode - = QProcess::ProcessChannelMode(qEnvironmentVariableIntValue(kForwardProcess)); - process.setProcessChannelMode(channelMode); - process.setCommand(command); - process.setEnvironment(env); - process.start(); - process.waitForFinished(); - exit(0); + return CommandLine(FilePath::fromString(binary), args); } -static void testForwardSubProcessMain() +class SubCreatorConfig { - std::cout << forwardedOutputData << std::endl; - std::cerr << forwardedErrorData << std::endl; - exit(0); -} +public: + SubCreatorConfig(const char *envVar, const QString &envVal) + : m_environment(subEnvironment(envVar, envVal)) + , m_commandLine(subCommandLine()) {} -static void blockingProcessSubProcessMain() + 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 +// instances of QtcProcess are destructed at some point (i.e. after each test function ends) +// we also run process reaper's and process launcher's destructors. Otherwise +// (when s_removeSingletonsOnLastProcess = true) this is a no-op wrapper around QtcProcess. +class TestProcess : public QtcProcess { - std::cout << "Blocking process successfully executed." << std::endl; - while (true) - ; +public: + class TestProcessDeleter : public QObject + { + public: + TestProcessDeleter(QObject *parent) : QObject(parent) { s_processCounter.fetch_add(1); } + ~TestProcessDeleter() { + if ((s_processCounter.fetch_sub(1) == 1) && s_removeSingletonsOnLastProcess) + Utils::Singleton::deleteAll(); + } + }; + TestProcess() { new TestProcessDeleter(this); } +}; + +static void subSimpleTestMain() +{ + std::cout << simpleTestData << std::endl; + exit(0); } class MacroMapExpander : public AbstractMacroExpander { @@ -233,20 +217,20 @@ void tst_QtcProcess::initTestCase() + Core::Constants::IDE_CASED_ID + "-XXXXXX"); Utils::LauncherInterface::setPathToLauncher(qApp->applicationDirPath() + '/' + QLatin1String(TEST_RELATIVE_LIBEXEC_PATH)); - if (qEnvironmentVariableIsSet(kExitCodeSubProcessCode)) - exitCodeSubProcessMain(); - if (qEnvironmentVariableIsSet(kRunBlockingStdOutSubProcessWithEndl)) - blockingStdOutSubProcessMain(); - if (qEnvironmentVariableIsSet(kLineCallback)) - lineCallbackMain(); - if (qEnvironmentVariableIsSet(kTestProcess)) - testProcessSubProcessMain(); - if (qEnvironmentVariableIsSet(kForwardSubProcess)) - testForwardSubProcessMain(); - else if (qEnvironmentVariableIsSet(kForwardProcess)) - testForwardProcessMain(); - if (qEnvironmentVariableIsSet(kBlockingProcess)) - blockingProcessSubProcessMain(); + if (qEnvironmentVariableIsSet(subExitCode)) + subExitCodeMain(); + if (qEnvironmentVariableIsSet(subRunBlockingStdOut)) + subRunBlockingStdOutMain(); + if (qEnvironmentVariableIsSet(subLineCallback)) + subLineCallbackMain(); + if (qEnvironmentVariableIsSet(subSimpleTest)) + subSimpleTestMain(); + if (qEnvironmentVariableIsSet(subProcessChannelForwarding)) + subProcessChannelForwardingMain(); + if (qEnvironmentVariableIsSet(subSubProcessChannelForwarding)) + subSubProcessChannelForwardingMain(); + if (qEnvironmentVariableIsSet(subKillBlockingProcess)) + subKillBlockingProcessMain(); homeStr = QLatin1String("@HOME@"); home = QDir::homePath(); @@ -907,6 +891,13 @@ void tst_QtcProcess::iteratorEditsLinux() iteratorEditsHelper(OsTypeLinux); } +static void subExitCodeMain() +{ + const int exitCode = qEnvironmentVariableIntValue(subExitCode); + std::cout << "Exiting with code:" << exitCode << std::endl; + exit(exitCode); +} + void tst_QtcProcess::exitCode_data() { QTest::addColumn("exitCode"); @@ -925,34 +916,39 @@ void tst_QtcProcess::exitCode() { QFETCH(int, exitCode); - Environment env = Environment::systemEnvironment(); - env.set(kExitCodeSubProcessCode, QString::number(exitCode)); - QStringList args = QCoreApplication::arguments(); - const QString binary = args.takeFirst(); - const CommandLine command(FilePath::fromString(binary), args); - + SubCreatorConfig subConfig(subExitCode, QString::number(exitCode)); { - QtcProcess qtcP; - qtcP.setCommand(command); - qtcP.setEnvironment(env); - qtcP.start(); - const bool finished = qtcP.waitForFinished(); + TestProcess process; + subConfig.setupSubProcess(&process); + process.start(); + const bool finished = process.waitForFinished(); QVERIFY(finished); - QCOMPARE(qtcP.exitCode(), exitCode); - QCOMPARE(qtcP.exitCode() == 0, qtcP.result() == ProcessResult::FinishedWithSuccess); + QCOMPARE(process.exitCode(), exitCode); + QCOMPARE(process.exitCode() == 0, process.result() == ProcessResult::FinishedWithSuccess); } { - QtcProcess sP; - sP.setCommand(command); - sP.setEnvironment(env); - sP.runBlocking(); + TestProcess process; + subConfig.setupSubProcess(&process); + process.runBlocking(); - QCOMPARE(sP.exitCode(), exitCode); - QCOMPARE(sP.exitCode() == 0, sP.result() == ProcessResult::FinishedWithSuccess); + QCOMPARE(process.exitCode(), exitCode); + QCOMPARE(process.exitCode() == 0, process.result() == ProcessResult::FinishedWithSuccess); } } +static void subRunBlockingStdOutMain() +{ + std::cout << "Wait for the Answer to the Ultimate Question of Life, " + "The Universe, and Everything..." << std::endl; + QThread::msleep(300); + std::cout << runBlockingStdOutSubProcessMagicWord << "...Now wait for the question..."; + if (qEnvironmentVariable(subRunBlockingStdOut) == "true") + std::cout << std::endl; + QThread::msleep(5000); + exit(0); +} + void tst_QtcProcess::runBlockingStdOut_data() { QTest::addColumn("withEndl"); @@ -974,40 +970,44 @@ void tst_QtcProcess::runBlockingStdOut() QFETCH(bool, withEndl); QFETCH(int, timeOutS); - QtcProcess sp; - QStringList args = QCoreApplication::arguments(); - const QString binary = args.takeFirst(); - sp.setCommand(CommandLine(FilePath::fromString(binary), args)); - Environment env = Environment::systemEnvironment(); - env.set(kRunBlockingStdOutSubProcessWithEndl, withEndl ? "true" : "false"); - sp.setEnvironment(env); - sp.setTimeoutS(timeOutS); + SubCreatorConfig subConfig(subRunBlockingStdOut, withEndl ? "true" : "false"); + TestProcess process; + subConfig.setupSubProcess(&process); + + process.setTimeoutS(timeOutS); bool readLastLine = false; - sp.setStdOutCallback([&readLastLine, &sp](const QString &out) { + process.setStdOutCallback([&readLastLine, &process](const QString &out) { if (out.startsWith(runBlockingStdOutSubProcessMagicWord)) { readLastLine = true; - sp.kill(); + process.kill(); } }); - sp.runBlocking(); + process.runBlocking(); // See also QTCREATORBUG-25667 for why it is a bad idea to use QtcProcess::runBlocking // with interactive cli tools. QEXPECT_FAIL("Unterminated stdout lost: early timeout", "", Continue); - QVERIFY2(sp.result() != ProcessResult::Hang, "Process run did not time out."); + QVERIFY2(process.result() != ProcessResult::Hang, "Process run did not time out."); QEXPECT_FAIL("Unterminated stdout lost: early timeout", "", Continue); QVERIFY2(readLastLine, "Last line was read."); } +static void subLineCallbackMain() +{ +#ifdef Q_OS_WIN + // Prevent \r\n -> \r\r\n translation. + setmode(fileno(stderr), O_BINARY); +#endif + fprintf(stderr, "%s", QByteArray(lineCallbackData).replace('|', "").data()); + exit(0); +} + void tst_QtcProcess::lineCallback() { - QtcProcess process; - QStringList args = QCoreApplication::arguments(); - const QString binary = args.takeFirst(); - process.setCommand(CommandLine(FilePath::fromString(binary), args)); - Environment env = Environment::systemEnvironment(); - env.set(kLineCallback, "Yes"); - process.setEnvironment(env); + SubCreatorConfig subConfig(subLineCallback, {}); + TestProcess process; + subConfig.setupSubProcess(&process); + QStringList lines = QString(lineCallbackData).split('|'); int lineNumber = 0; process.setStdErrLineCallback([lines, &lineNumber](const QString &actual) { @@ -1027,7 +1027,7 @@ void tst_QtcProcess::lineCallback() void tst_QtcProcess::lineCallbackIntern() { - QtcProcess process; + TestProcess process; QStringList lines = QString(lineCallbackData).split('|'); int lineNumber = 0; process.setStdOutLineCallback([lines, &lineNumber](const QString &actual) { @@ -1048,15 +1048,10 @@ void tst_QtcProcess::lineCallbackIntern() void tst_QtcProcess::waitForStartedAndFinished() { - Environment env = Environment::systemEnvironment(); - env.set(kTestProcess, {}); - QStringList args = QCoreApplication::arguments(); - const QString binary = args.takeFirst(); - const CommandLine command(FilePath::fromString(binary), args); + SubCreatorConfig subConfig(subSimpleTest, {}); + TestProcess process; + subConfig.setupSubProcess(&process); - QtcProcess process; - process.setCommand(command); - process.setEnvironment(env); process.start(); QThread::msleep(1000); // long enough for process to finish QVERIFY(process.waitForStarted()); @@ -1067,7 +1062,7 @@ void tst_QtcProcess::waitForStartedAndFinished() void tst_QtcProcess::notRunningAfterStartingNonExistingProgram() { - QtcProcess process; + TestProcess process; process.setCommand({ FilePath::fromString( "there_is_a_big_chance_that_executable_with_that_name_does_not_exists"), {} }); @@ -1096,6 +1091,40 @@ void tst_QtcProcess::notRunningAfterStartingNonExistingProgram() } } +// Since we want to test whether the process forwards its channels or not, we can't just create +// a process and start it, because in this case there is no way on how to check whether something +// went into our output channels or not. + +// So we start two processes in chain instead. On the beginning the processChannelForwarding() +// test starts the "subProcessChannelForwardingMain" - this one will start another process +// "subSubProcessChannelForwardingMain" with forwarding options. +// The "subSubProcessChannelForwardingMain" is very simple - it just puts something to the output +// and the error channels. Then "subProcessChannelForwardingMain" either forwards these channels +// or not - we check it in the outer processChannelForwarding() test. + +static void subSubProcessChannelForwardingMain() +{ + std::cout << forwardedOutputData << std::endl; + std::cerr << forwardedErrorData << std::endl; + exit(0); +} + +static void subProcessChannelForwardingMain() +{ + const QProcess::ProcessChannelMode channelMode + = QProcess::ProcessChannelMode(qEnvironmentVariableIntValue(subProcessChannelForwarding)); + qunsetenv(subProcessChannelForwarding); + + SubCreatorConfig subConfig(subSubProcessChannelForwarding, {}); + TestProcess process; + subConfig.setupSubProcess(&process); + + process.setProcessChannelMode(channelMode); + process.start(); + process.waitForFinished(); + exit(0); +} + void tst_QtcProcess::processChannelForwarding_data() { QTest::addColumn("channelMode"); @@ -1114,15 +1143,10 @@ void tst_QtcProcess::processChannelForwarding() QFETCH(bool, outputForwarded); QFETCH(bool, errorForwarded); - Environment env = Environment::systemEnvironment(); - env.set(kForwardProcess, QString::number(int(channelMode))); - QStringList args = QCoreApplication::arguments(); - const QString binary = args.takeFirst(); - const CommandLine command(FilePath::fromString(binary), args); + SubCreatorConfig subConfig(subProcessChannelForwarding, QString::number(int(channelMode))); + TestProcess process; + subConfig.setupSubProcess(&process); - QtcProcess process; - process.setCommand(command); - process.setEnvironment(env); process.start(); QVERIFY(process.waitForFinished()); @@ -1171,19 +1195,21 @@ protected: bool MessageHandler::ok = true; QtMessageHandler MessageHandler::oldMessageHandler = 0; +static void subKillBlockingProcessMain() +{ + std::cout << "Blocking process successfully executed." << std::endl; + while (true) + ; +} + void tst_QtcProcess::killBlockingProcess() { - Environment env = Environment::systemEnvironment(); - env.set(kBlockingProcess, {}); - QStringList args = QCoreApplication::arguments(); - const QString binary = args.takeFirst(); - const CommandLine command(FilePath::fromString(binary), args); + SubCreatorConfig subConfig(subKillBlockingProcess, {}); MessageHandler msgHandler; { - QtcProcess process; - process.setCommand(command); - process.setEnvironment(env); + TestProcess process; + subConfig.setupSubProcess(&process); process.start(); QVERIFY(process.waitForStarted()); QVERIFY(!process.waitForFinished(1000)); @@ -1194,15 +1220,10 @@ void tst_QtcProcess::killBlockingProcess() void tst_QtcProcess::flushFinishedWhileWaitingForReadyRead() { - Environment env = Environment::systemEnvironment(); - env.set(kTestProcess, {}); - QStringList args = QCoreApplication::arguments(); - const QString binary = args.takeFirst(); - const CommandLine command(FilePath::fromString(binary), args); + SubCreatorConfig subConfig(subSimpleTest, {}); + TestProcess process; + subConfig.setupSubProcess(&process); - QtcProcess process; - process.setCommand(command); - process.setEnvironment(env); process.start(); QVERIFY(process.waitForStarted()); @@ -1219,7 +1240,7 @@ void tst_QtcProcess::flushFinishedWhileWaitingForReadyRead() QCOMPARE(process.state(), QProcess::NotRunning); QVERIFY(!timer.hasExpired()); - QVERIFY(reply.contains(testProcessData)); + QVERIFY(reply.contains(simpleTestData)); } QTEST_MAIN(tst_QtcProcess)