/****************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/


#include <QtTest/QtTest>
#include <QtCore/QProcess>
#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QtCore/QThread>
#include <QtCore/QRegExp>
#include <QtCore/QDebug>
#include <QtCore/QMetaType>
#if !defined(Q_OS_SYMBIAN)
// Network test unnecessary?
#include <QtNetwork/QHostInfo>
#endif
#include <stdlib.h>

#ifdef QT_NO_PROCESS
QTEST_NOOP_MAIN
#else

#if defined(Q_OS_WIN)
#include <windows.h>
#endif

//TESTED_CLASS=
//TESTED_FILES=

Q_DECLARE_METATYPE(QList<QProcess::ExitStatus>);
Q_DECLARE_METATYPE(QProcess::ExitStatus);
Q_DECLARE_METATYPE(QProcess::ProcessState);

#define QPROCESS_VERIFY(Process, Fn) \
{ \
const bool ret = Process.Fn; \
if (ret == false) \
	qWarning("QProcess error: %d: %s", Process.error(), qPrintable(Process.errorString())); \
QVERIFY(ret); \
}

class tst_QProcess : public QObject
{
    Q_OBJECT

public:
    tst_QProcess();
    virtual ~tst_QProcess();

public slots:
    void init();
    void cleanup();

private slots:
    void getSetCheck();
    void constructing();
    void simpleStart();
    void startDetached();
    void crashTest();
    void crashTest2();
    void echoTest_data();
    void echoTest();
    void echoTest2();
    void echoTest_performance();
#if defined Q_OS_WIN
    void echoTestGui();
    void batFiles_data();
    void batFiles();
#endif
    void exitStatus_data();
    void exitStatus();
    void loopBackTest();
    void readTimeoutAndThenCrash();
    void waitForFinished();
    void deadWhileReading();
    void restartProcessDeadlock();
    void closeWriteChannel();
    void closeReadChannel();
    void openModes();
    void emitReadyReadOnlyWhenNewDataArrives();
    void hardExit();
    void softExit();
    void softExitInSlots_data();
    void softExitInSlots();
    void mergedChannels();
    void forwardedChannels();
    void atEnd();
    void atEnd2();
    void processInAThread();
    void processesInMultipleThreads();
    void waitForFinishedWithTimeout();
    void waitForReadyReadInAReadyReadSlot();
    void waitForBytesWrittenInABytesWrittenSlot();
    void spaceArgsTest_data();
    void spaceArgsTest();
    void exitCodeTest();
    void setEnvironment_data();
    void setEnvironment();
    void setProcessEnvironment_data();
    void setProcessEnvironment();
    void systemEnvironment();
    void spaceInName();
    void lockupsInStartDetached();
    void waitForReadyReadForNonexistantProcess();
    void setStandardInputFile();
    void setStandardOutputFile_data();
    void setStandardOutputFile();
    void setStandardOutputProcess_data();
    void setStandardOutputProcess();
    void removeFileWhileProcessIsRunning();
    void fileWriterProcess();
    void detachedWorkingDirectoryAndPid();
    void switchReadChannels();
    void setWorkingDirectory();
    void startFinishStartFinish();
    void invalidProgramString_data();
    void invalidProgramString();

    // keep these at the end, since they use lots of processes and sometimes
    // caused obscure failures to occur in tests that followed them (esp. on the Mac)
    void failToStart();
    void failToStartWithWait();
    void failToStartWithEventLoop();

protected slots:
    void readFromProcess();
    void exitLoopSlot();
    void restartProcess();
    void waitForReadyReadInAReadyReadSlotSlot();
    void waitForBytesWrittenInABytesWrittenSlotSlot();

private:
    QProcess *process;
    qint64 bytesAvailable;
};

// Testing get/set functions
void tst_QProcess::getSetCheck()
{
    QProcess obj1;
    // ProcessChannelMode QProcess::readChannelMode()
    // void QProcess::setReadChannelMode(ProcessChannelMode)
    obj1.setReadChannelMode(QProcess::ProcessChannelMode(QProcess::SeparateChannels));
    QCOMPARE(QProcess::ProcessChannelMode(QProcess::SeparateChannels), obj1.readChannelMode());
    obj1.setReadChannelMode(QProcess::ProcessChannelMode(QProcess::MergedChannels));
    QCOMPARE(QProcess::ProcessChannelMode(QProcess::MergedChannels), obj1.readChannelMode());
    obj1.setReadChannelMode(QProcess::ProcessChannelMode(QProcess::ForwardedChannels));
    QCOMPARE(QProcess::ProcessChannelMode(QProcess::ForwardedChannels), obj1.readChannelMode());

    // ProcessChannel QProcess::readChannel()
    // void QProcess::setReadChannel(ProcessChannel)
    obj1.setReadChannel(QProcess::ProcessChannel(QProcess::StandardOutput));
    QCOMPARE(QProcess::ProcessChannel(QProcess::StandardOutput), obj1.readChannel());
    obj1.setReadChannel(QProcess::ProcessChannel(QProcess::StandardError));
    QCOMPARE(QProcess::ProcessChannel(QProcess::StandardError), obj1.readChannel());
}

tst_QProcess::tst_QProcess()
{
}

tst_QProcess::~tst_QProcess()
{
}

void tst_QProcess::init()
{
#ifdef Q_OS_SYMBIAN
    QString dirStr = QString::fromLatin1("c:\\logs");
    QDir dir;
    if (!dir.exists(dirStr))
        dir.mkpath(dirStr);
#endif
}

void tst_QProcess::cleanup()
{
}

//-----------------------------------------------------------------------------
void tst_QProcess::constructing()
{
    QProcess process;
    QCOMPARE(process.readChannel(), QProcess::StandardOutput);
    QCOMPARE(process.workingDirectory(), QString());
    QCOMPARE(process.environment(), QStringList());
    QCOMPARE(process.error(), QProcess::UnknownError);
    QCOMPARE(process.state(), QProcess::NotRunning);
    QCOMPARE(process.pid(), Q_PID(0));
    QCOMPARE(process.readAllStandardOutput(), QByteArray());
    QCOMPARE(process.readAllStandardError(), QByteArray());
    QCOMPARE(process.canReadLine(), false);

    // QIODevice
    QCOMPARE(process.openMode(), QIODevice::NotOpen);
    QVERIFY(!process.isOpen());
    QVERIFY(!process.isReadable());
    QVERIFY(!process.isWritable());
    QVERIFY(process.isSequential());
    QCOMPARE(process.pos(), qlonglong(0));
    QCOMPARE(process.size(), qlonglong(0));
    QVERIFY(process.atEnd());
    QCOMPARE(process.bytesAvailable(), qlonglong(0));
    QCOMPARE(process.bytesToWrite(), qlonglong(0));
    QVERIFY(!process.errorString().isEmpty());

    char c;
    QCOMPARE(process.read(&c, 1), qlonglong(-1));
    QCOMPARE(process.write(&c, 1), qlonglong(-1));
}

void tst_QProcess::simpleStart()
{
    qRegisterMetaType<QProcess::ProcessState>("QProcess::ProcessState");

    process = new QProcess;
    QSignalSpy spy(process, SIGNAL(stateChanged(QProcess::ProcessState)));
    connect(process, SIGNAL(readyRead()), this, SLOT(readFromProcess()));

    /* valgrind dislike SUID binaries(those that have the `s'-flag set), which
     * makes it fail to start the process. For this reason utilities like `ping' won't
     * start, when the auto test is run through `valgrind'. */
    process->start("testProcessNormal/testProcessNormal");
    if (process->state() != QProcess::Starting)
        QCOMPARE(process->state(), QProcess::Running);
    QVERIFY(process->waitForStarted(5000));
    QCOMPARE(process->state(), QProcess::Running);
#if defined(Q_OS_WINCE)
    // Note: This actually seems incorrect, it will only exit the while loop when finishing fails
    while (process->waitForFinished(5000))
    { }
#elif defined(Q_OS_SYMBIAN)
    QVERIFY(process->waitForFinished(5000));
#else
    while (process->waitForReadyRead(5000))
    { }
#endif
    QCOMPARE(process->state(), QProcess::NotRunning);

    delete process;
    process = 0;

    QCOMPARE(spy.count(), 3);
    QCOMPARE(qVariantValue<QProcess::ProcessState>(spy.at(0).at(0)), QProcess::Starting);
    QCOMPARE(qVariantValue<QProcess::ProcessState>(spy.at(1).at(0)), QProcess::Running);
    QCOMPARE(qVariantValue<QProcess::ProcessState>(spy.at(2).at(0)), QProcess::NotRunning);
}
//-----------------------------------------------------------------------------
void tst_QProcess::startDetached()
{
    QProcess proc;
    QVERIFY(proc.startDetached("testProcessNormal/testProcessNormal",
                               QStringList() << "arg1" << "arg2"));
    QCOMPARE(QProcess::startDetached("nonexistingexe"), false);
}

//-----------------------------------------------------------------------------
void tst_QProcess::readFromProcess()
{
    int lines = 0;
    while (process->canReadLine()) {
        ++lines;
        QByteArray line = process->readLine();
    }
}

//-----------------------------------------------------------------------------
void tst_QProcess::crashTest()
{
    qRegisterMetaType<QProcess::ProcessState>("QProcess::ProcessState");
#ifdef Q_OS_WIN
    QSKIP("This test opens a crash dialog on Windows", SkipSingle);
#endif
    process = new QProcess;
    QSignalSpy stateSpy(process, SIGNAL(stateChanged(QProcess::ProcessState)));
    process->start("testProcessCrash/testProcessCrash");
    QVERIFY(process->waitForStarted(5000));

    qRegisterMetaType<QProcess::ProcessError>("QProcess::ProcessError");
    qRegisterMetaType<QProcess::ProcessError>("QProcess::ExitStatus");

    QSignalSpy spy(process, SIGNAL(error(QProcess::ProcessError)));
    QSignalSpy spy2(process, SIGNAL(finished(int, QProcess::ExitStatus)));

    QVERIFY(process->waitForFinished(5000));

    QCOMPARE(spy.count(), 1);
    QCOMPARE(*static_cast<const QProcess::ProcessError *>(spy.at(0).at(0).constData()), QProcess::Crashed);

    QCOMPARE(spy2.count(), 1);
    QCOMPARE(*static_cast<const QProcess::ExitStatus *>(spy2.at(0).at(1).constData()), QProcess::CrashExit);

    QCOMPARE(process->exitStatus(), QProcess::CrashExit);

    delete process;
    process = 0;

    QCOMPARE(stateSpy.count(), 3);
    QCOMPARE(qVariantValue<QProcess::ProcessState>(stateSpy.at(0).at(0)), QProcess::Starting);
    QCOMPARE(qVariantValue<QProcess::ProcessState>(stateSpy.at(1).at(0)), QProcess::Running);
    QCOMPARE(qVariantValue<QProcess::ProcessState>(stateSpy.at(2).at(0)), QProcess::NotRunning);
}

//-----------------------------------------------------------------------------
void tst_QProcess::crashTest2()
{
#ifdef Q_OS_WIN
    QSKIP("This test opens a crash dialog on Windows", SkipSingle);
#endif
    process = new QProcess;
    process->start("testProcessCrash/testProcessCrash");
    QVERIFY(process->waitForStarted(5000));

    qRegisterMetaType<QProcess::ProcessError>("QProcess::ProcessError");
    qRegisterMetaType<QProcess::ProcessError>("QProcess::ExitStatus");

    QSignalSpy spy(process, SIGNAL(error(QProcess::ProcessError)));
    QSignalSpy spy2(process, SIGNAL(finished(int, QProcess::ExitStatus)));

    QObject::connect(process, SIGNAL(finished(int)), this, SLOT(exitLoopSlot()));

    QTestEventLoop::instance().enterLoop(5);
    if (QTestEventLoop::instance().timeout())
        QFAIL("Failed to detect crash : operation timed out");

    QCOMPARE(spy.count(), 1);
    QCOMPARE(*static_cast<const QProcess::ProcessError *>(spy.at(0).at(0).constData()), QProcess::Crashed);

    QCOMPARE(spy2.count(), 1);
    QCOMPARE(*static_cast<const QProcess::ExitStatus *>(spy2.at(0).at(1).constData()), QProcess::CrashExit);

    QCOMPARE(process->exitStatus(), QProcess::CrashExit);

    delete process;
    process = 0;
}

//-----------------------------------------------------------------------------
void tst_QProcess::echoTest_data()
{
    QTest::addColumn<QByteArray>("input");

    QTest::newRow("1") << QByteArray("H");
    QTest::newRow("2") << QByteArray("He");
    QTest::newRow("3") << QByteArray("Hel");
    QTest::newRow("4") << QByteArray("Hell");
    QTest::newRow("5") << QByteArray("Hello");
    QTest::newRow("100 bytes") << QByteArray(100, '@');
    QTest::newRow("1000 bytes") << QByteArray(1000, '@');
    QTest::newRow("10000 bytes") << QByteArray(10000, '@');
}

//-----------------------------------------------------------------------------
void tst_QProcess::echoTest()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Reading and writing to a process is not supported on Qt/Symbian", SkipAll);
#endif

    QFETCH(QByteArray, input);

    process = new QProcess;
    connect(process, SIGNAL(readyRead()), this, SLOT(exitLoopSlot()));

#ifdef Q_OS_MAC
    process->start("testProcessEcho/testProcessEcho.app");
#else
    process->start("testProcessEcho/testProcessEcho");
#endif
    QVERIFY(process->waitForStarted(5000));

    process->write(input);

    QTime stopWatch;
    stopWatch.start();
    do {
        QVERIFY(process->isOpen());
        QTestEventLoop::instance().enterLoop(2);
    } while (stopWatch.elapsed() < 60000 && process->bytesAvailable() < input.size());
    if (stopWatch.elapsed() >= 60000)
        QFAIL("Timed out");

    QByteArray message = process->readAll();
    QCOMPARE(message.size(), input.size());

    char *c1 = message.data();
    char *c2 = input.data();
    while (*c1 && *c2) {
        if (*c1 != *c2)
            QCOMPARE(*c1, *c2);
        ++c1;
        ++c2;
    }
    QCOMPARE(*c1, *c2);

    process->write("", 1);

    QVERIFY(process->waitForFinished(5000));


    delete process;
    process = 0;
}

//-----------------------------------------------------------------------------
void tst_QProcess::exitLoopSlot()
{
    QTestEventLoop::instance().exitLoop();
}

//-----------------------------------------------------------------------------
void tst_QProcess::echoTest2()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Reading and writing to a process is not supported on Qt/Symbian", SkipAll);
#endif

    process = new QProcess;
    connect(process, SIGNAL(readyRead()), this, SLOT(exitLoopSlot()));

#ifdef Q_OS_MAC
    process->start("testProcessEcho2/testProcessEcho2.app");
#else
    process->start("testProcessEcho2/testProcessEcho2");
#endif
    QVERIFY(process->waitForStarted(5000));
    QVERIFY(!process->waitForReadyRead(250));
    QCOMPARE(process->error(), QProcess::Timedout);

    process->write("Hello");
    QSignalSpy spy1(process, SIGNAL(readyReadStandardOutput()));
    QSignalSpy spy2(process, SIGNAL(readyReadStandardError()));

    QTime stopWatch;
    stopWatch.start();
    forever {
        QTestEventLoop::instance().enterLoop(1);
        if (stopWatch.elapsed() >= 30000)
            QFAIL("Timed out");
        process->setReadChannel(QProcess::StandardOutput);
        qint64 baso = process->bytesAvailable();

        process->setReadChannel(QProcess::StandardError);
        qint64 base = process->bytesAvailable();
        if (baso == 5 && base == 5)
            break;
    }

    QVERIFY(spy1.count() > 0);
    QVERIFY(spy2.count() > 0);

    QCOMPARE(process->readAllStandardOutput(), QByteArray("Hello"));
    QCOMPARE(process->readAllStandardError(), QByteArray("Hello"));

    process->write("", 1);
    QVERIFY(process->waitForFinished(5000));

    delete process;
    process = 0;
}

//-----------------------------------------------------------------------------
void tst_QProcess::echoTest_performance()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Reading and writing to a process is not supported on Qt/Symbian", SkipAll);
#endif

    QProcess process;
#ifdef Q_OS_MAC
    process.start("testProcessLoopback/testProcessLoopback.app");
#else
    process.start("testProcessLoopback/testProcessLoopback");
#endif

    QByteArray array;
    array.resize(1024 * 1024);
    for (int j = 0; j < array.size(); ++j)
        array[j] = 'a' + (j % 20);

    QVERIFY(process.waitForStarted());

    QTime stopWatch;
    stopWatch.start();

    qint64 totalBytes = 0;
    QByteArray dump;
    QSignalSpy readyReadSpy(&process, SIGNAL(readyRead()));
    while (stopWatch.elapsed() < 2000) {
        process.write(array);
        while (process.bytesToWrite() > 0) {
            int readCount = readyReadSpy.count();
            QVERIFY(process.waitForBytesWritten(5000));
            if (readyReadSpy.count() == readCount)
                QVERIFY(process.waitForReadyRead(5000));
        }

        while (process.bytesAvailable() < array.size())
            QVERIFY2(process.waitForReadyRead(5000), qPrintable(process.errorString()));
        dump = process.readAll();
        totalBytes += dump.size();
    }

    qDebug() << "Elapsed time:" << stopWatch.elapsed() << "ms;"
             << "transfer rate:" << totalBytes / (1048.576) / stopWatch.elapsed()
             << "MB/s";

    for (int j = 0; j < array.size(); ++j)
        QCOMPARE(char(dump.at(j)), char('a' + (j % 20)));

    process.closeWriteChannel();
    QVERIFY(process.waitForFinished());
}

#if defined Q_OS_WIN
//-----------------------------------------------------------------------------
void tst_QProcess::echoTestGui()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Reading and writing to a process is not supported on Qt/Symbian", SkipAll);
#endif

    QProcess process;

    process.start("testProcessEchoGui/testProcessEchoGui");


    process.write("Hello");
    process.write("q");

    QVERIFY(process.waitForFinished(50000));

    QCOMPARE(process.readAllStandardOutput(), QByteArray("Hello"));
    QCOMPARE(process.readAllStandardError(), QByteArray("Hello"));
}

//-----------------------------------------------------------------------------
void tst_QProcess::batFiles_data()
{
    QTest::addColumn<QString>("batFile");
    QTest::addColumn<QByteArray>("output");

    QTest::newRow("simple") << QString::fromLatin1("testBatFiles/simple.bat") << QByteArray("Hello");
    QTest::newRow("with space") << QString::fromLatin1("testBatFiles/with space.bat") << QByteArray("Hello");
}

void tst_QProcess::batFiles()
{
#if defined(Q_OS_WINCE)
    QSKIP("Batch files are not supported on Windows CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Batch files are not supported on Symbian", SkipAll);
#endif
    QFETCH(QString, batFile);
    QFETCH(QByteArray, output);

    QProcess proc;

    proc.start(batFile, QStringList());

    QVERIFY(proc.waitForFinished(5000));

    QVERIFY(proc.bytesAvailable() > 0);

    QVERIFY(proc.readAll().startsWith(output));
}

#endif

//-----------------------------------------------------------------------------
void tst_QProcess::exitStatus_data()
{
    QTest::addColumn<QStringList>("processList");
    QTest::addColumn<QList<QProcess::ExitStatus> >("exitStatus");

    QTest::newRow("normal") << (QStringList() << "testProcessNormal/testProcessNormal")
                            << (QList<QProcess::ExitStatus>() << QProcess::NormalExit);
    QTest::newRow("crash") << (QStringList() << "testProcessCrash/testProcessCrash")
                            << (QList<QProcess::ExitStatus>() << QProcess::CrashExit);

    QTest::newRow("normal-crash") << (QStringList()
                                      << "testProcessNormal/testProcessNormal"
                                      << "testProcessCrash/testProcessCrash")
                                  << (QList<QProcess::ExitStatus>()
                                      << QProcess::NormalExit
                                      << QProcess::CrashExit);
    QTest::newRow("crash-normal") << (QStringList()
                                      << "testProcessCrash/testProcessCrash"
                                      << "testProcessNormal/testProcessNormal")
                                  << (QList<QProcess::ExitStatus>()
                                      << QProcess::CrashExit
                                      << QProcess::NormalExit);
}

void tst_QProcess::exitStatus()
{
    process = new QProcess;
    QFETCH(QStringList, processList);
    QFETCH(QList<QProcess::ExitStatus>, exitStatus);

#ifdef Q_OS_WIN
    if (exitStatus.contains(QProcess::CrashExit))
        QSKIP("This test opens a crash dialog on Windows", SkipSingle);
#endif

    Q_ASSERT(processList.count() == exitStatus.count());
    for (int i = 0; i < processList.count(); ++i) {
        process->start(processList.at(i));
        QVERIFY(process->waitForStarted(5000));
        QVERIFY(process->waitForFinished(5000));

        QCOMPARE(process->exitStatus(), exitStatus.at(i));
    }

    process->deleteLater();
    process = 0;
}
//-----------------------------------------------------------------------------
void tst_QProcess::loopBackTest()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Reading and writing to a process is not supported on Qt/Symbian", SkipAll);
#endif

    process = new QProcess;
#ifdef Q_OS_MAC
    process->start("testProcessEcho/testProcessEcho.app");
#else
    process->start("testProcessEcho/testProcessEcho");
#endif
    QVERIFY(process->waitForStarted(5000));

    for (int i = 0; i < 100; ++i) {
        process->write("Hello");
        do {
            QVERIFY(process->waitForReadyRead(5000));
        } while (process->bytesAvailable() < 5);
        QCOMPARE(process->readAll(), QByteArray("Hello"));
    }

    process->write("", 1);
    QVERIFY(process->waitForFinished(5000));

    delete process;
    process = 0;
}

//-----------------------------------------------------------------------------
void tst_QProcess::readTimeoutAndThenCrash()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Reading and writing to a process is not supported on Qt/Symbian", SkipAll);
#endif

    process = new QProcess;
#ifdef Q_OS_MAC
    process->start("testProcessEcho/testProcessEcho.app");
#else
    process->start("testProcessEcho/testProcessEcho");
#endif
    if (process->state() != QProcess::Starting)
        QCOMPARE(process->state(), QProcess::Running);

    QVERIFY(process->waitForStarted(5000));
    QCOMPARE(process->state(), QProcess::Running);

    QVERIFY(!process->waitForReadyRead(5000));
    QCOMPARE(process->error(), QProcess::Timedout);

    qRegisterMetaType<QProcess::ProcessError>("QProcess::ProcessError");
    QSignalSpy spy(process, SIGNAL(error(QProcess::ProcessError)));

    process->kill();

    QVERIFY(process->waitForFinished(5000));
    QCOMPARE(process->state(), QProcess::NotRunning);

    QCOMPARE(spy.count(), 1);
    QCOMPARE(*static_cast<const QProcess::ProcessError *>(spy.at(0).at(0).constData()), QProcess::Crashed);

    delete process;
    process = 0;
}

void tst_QProcess::waitForFinished()
{
    QProcess process;

#ifdef Q_OS_MAC
    process.start("testProcessOutput/testProcessOutput.app");
#else
    process.start("testProcessOutput/testProcessOutput");
#endif

#if !defined(Q_OS_WINCE) && !defined(Q_OS_SYMBIAN)
    QVERIFY(process.waitForFinished(5000));
#else
    QVERIFY(process.waitForFinished(30000));
#endif
    QCOMPARE(process.exitStatus(), QProcess::NormalExit);

#if defined(Q_OS_SYMBIAN)
    // Symbian test outputs to a file, so check that
    FILE* file = fopen("c:\\logs\\qprocess_output_test.txt","r");
    int retval = 0;
    int count = 0;
    while((int)(retval = fgetc(file) )!= EOF)
        if (retval == '\n')
            count++;
    fclose(file);
    QCOMPARE(count, 200);
#else
#  if defined (Q_OS_WINCE)
    QEXPECT_FAIL("", "Reading and writing to a process is not supported on Qt/CE", Continue);
#  endif
    QString output = process.readAll();
    QCOMPARE(output.count("\n"), 10*1024);
#endif

    process.start("blurdybloop");
    QVERIFY(!process.waitForFinished());
    QCOMPARE(process.error(), QProcess::FailedToStart);
}


void tst_QProcess::deadWhileReading()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Reading and writing to a process is not supported on Qt/Symbian", SkipAll);
#endif

    QProcess process;

#ifdef Q_OS_MAC
    process.start("testProcessDeadWhileReading/testProcessDeadWhileReading.app");
#else
    process.start("testProcessDeadWhileReading/testProcessDeadWhileReading");
#endif

    QString output;

    QVERIFY(process.waitForStarted(5000));
    while (process.waitForReadyRead(5000))
        output += process.readAll();

    QCOMPARE(output.count("\n"), 10*1024);
    process.waitForFinished();
}

//-----------------------------------------------------------------------------
void tst_QProcess::restartProcessDeadlock()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Reading and writing to a process is not supported on Qt/Symbian", SkipAll);
#endif

    // The purpose of this test is to detect whether restarting a
    // process in the finished() connected slot causes a deadlock
    // because of the way QProcessManager uses its locks.
    QProcess proc;
    process = &proc;
    connect(process, SIGNAL(finished(int)), this, SLOT(restartProcess()));

#ifdef Q_OS_MAC
    process->start("testProcessEcho/testProcessEcho.app");
#else
    process->start("testProcessEcho/testProcessEcho");
#endif

    QCOMPARE(process->write("", 1), qlonglong(1));
    QVERIFY(process->waitForFinished(5000));

    process->disconnect(SIGNAL(finished(int)));

    QCOMPARE(process->write("", 1), qlonglong(1));
    QVERIFY(process->waitForFinished(5000));
}

void tst_QProcess::restartProcess()
{
#ifdef Q_OS_MAC
    process->start("testProcessEcho/testProcessEcho.app");
#else
    process->start("testProcessEcho/testProcessEcho");
#endif
}

//-----------------------------------------------------------------------------
void tst_QProcess::closeWriteChannel()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Reading and writing to a process is not supported on Qt/Symbian", SkipAll);
#endif

    QProcess more;
    more.start("testProcessEOF/testProcessEOF");

    QVERIFY(more.waitForStarted(5000));
    QVERIFY(!more.waitForReadyRead(250));
    QCOMPARE(more.error(), QProcess::Timedout);

    QVERIFY(more.write("Data to read") != -1);

    QVERIFY(!more.waitForReadyRead(250));
    QCOMPARE(more.error(), QProcess::Timedout);

    more.closeWriteChannel();

    QVERIFY(more.waitForReadyRead(5000));
    QVERIFY(more.readAll().startsWith("Data to read"));

    if (more.state() == QProcess::Running)
        more.write("q");
    QVERIFY(more.waitForFinished(5000));
}

//-----------------------------------------------------------------------------
void tst_QProcess::closeReadChannel()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Reading and writing to a process is not supported on Qt/Symbian", SkipAll);
#endif

    for (int i = 0; i < 10; ++i) {
        QProcess::ProcessChannel channel1 = QProcess::StandardOutput;
        QProcess::ProcessChannel channel2 = QProcess::StandardError;

        QProcess proc;
#ifdef Q_OS_MAC
        proc.start("testProcessEcho2/testProcessEcho2.app");
#else
        proc.start("testProcessEcho2/testProcessEcho2");
#endif
        QVERIFY(proc.waitForStarted(5000));
        proc.closeReadChannel(i&1 ? channel2 : channel1);
        proc.setReadChannel(i&1 ? channel2 : channel1);
        proc.write("Data");

        QVERIFY(!proc.waitForReadyRead(5000));
        QVERIFY(proc.readAll().isEmpty());

        proc.setReadChannel(i&1 ? channel1 : channel2);

        while (proc.bytesAvailable() < 4 && proc.waitForReadyRead(5000))
        { }

        QCOMPARE(proc.readAll(), QByteArray("Data"));

        proc.write("", 1);
        QVERIFY(proc.waitForFinished(5000));
    }
}

//-----------------------------------------------------------------------------
void tst_QProcess::openModes()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Reading and writing to a process is not supported on Qt/Symbian", SkipAll);
#endif

    QProcess proc;
    QVERIFY(!proc.isOpen());
    QVERIFY(proc.openMode() == QProcess::NotOpen);
#ifdef Q_OS_MAC
    proc.start("testProcessEcho3/testProcessEcho3.app");
#else
    proc.start("testProcessEcho3/testProcessEcho3");
#endif
    QVERIFY(proc.waitForStarted(5000));
    QVERIFY(proc.isOpen());
    QVERIFY(proc.openMode() == QProcess::ReadWrite);
    QVERIFY(proc.isReadable());
    QVERIFY(proc.isWritable());

    proc.write("Data");

    proc.closeWriteChannel();

    QVERIFY(proc.isWritable());
    QVERIFY(proc.openMode() == QProcess::ReadWrite);

    while (proc.bytesAvailable() < 4 && proc.waitForReadyRead(5000))
    { }

    QCOMPARE(proc.readAll().constData(), QByteArray("Data").constData());

    proc.closeReadChannel(QProcess::StandardOutput);

    QVERIFY(proc.openMode() == QProcess::ReadWrite);
    QVERIFY(proc.isReadable());

    proc.closeReadChannel(QProcess::StandardError);

    QVERIFY(proc.openMode() == QProcess::ReadWrite);
    QVERIFY(proc.isReadable());

    proc.close();
    QVERIFY(!proc.isOpen());
    QVERIFY(!proc.isReadable());
    QVERIFY(!proc.isWritable());
    QCOMPARE(proc.state(), QProcess::NotRunning);
}

//-----------------------------------------------------------------------------
void tst_QProcess::emitReadyReadOnlyWhenNewDataArrives()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Reading and writing to a process is not supported on Qt/Symbian", SkipAll);
#endif

    QProcess proc;
    connect(&proc, SIGNAL(readyRead()), this, SLOT(exitLoopSlot()));
    QSignalSpy spy(&proc, SIGNAL(readyRead()));

#ifdef Q_OS_MAC
    proc.start("testProcessEcho/testProcessEcho.app");
#else
    proc.start("testProcessEcho/testProcessEcho");
#endif

    QCOMPARE(spy.count(), 0);

    proc.write("A");

    QTestEventLoop::instance().enterLoop(5);
    if (QTestEventLoop::instance().timeout())
        QFAIL("Operation timed out");

    QCOMPARE(spy.count(), 1);

    QTestEventLoop::instance().enterLoop(1);
    QVERIFY(QTestEventLoop::instance().timeout());
    QVERIFY(!proc.waitForReadyRead(250));

    QObject::disconnect(&proc, SIGNAL(readyRead()), 0, 0);
    proc.write("B");
    QVERIFY(proc.waitForReadyRead(5000));

    proc.write("", 1);
    QVERIFY(proc.waitForFinished(5000));
}

//-----------------------------------------------------------------------------
void tst_QProcess::hardExit()
{
#if defined(Q_OS_SYMBIAN)
    QSKIP("Killing started processes is not supported on Qt/Symbian due platform security", SkipAll);
#endif
    QProcess proc;

#if defined(Q_OS_MAC)
    proc.start("testProcessEcho/testProcessEcho.app");
#elif defined(Q_OS_WINCE)
    proc.start("testSoftExit/testSoftExit");
#else
    proc.start("testProcessEcho/testProcessEcho");
#endif

#ifndef Q_OS_WINCE
    QVERIFY(proc.waitForStarted(5000));
#else
    QVERIFY(proc.waitForStarted(10000));
#endif

    proc.kill();

    QVERIFY(proc.waitForFinished(5000));
    QCOMPARE(int(proc.state()), int(QProcess::NotRunning));
    QCOMPARE(int(proc.error()), int(QProcess::Crashed));
}

//-----------------------------------------------------------------------------
void tst_QProcess::softExit()
{
#if defined(Q_OS_SYMBIAN)
    QSKIP("Terminating started processes is not supported on Qt/Symbian due platform security", SkipAll);
#endif
    QProcess proc;

    proc.start("testSoftExit/testSoftExit");

    QVERIFY(proc.waitForStarted(10000));
#if !defined(Q_OS_WINCE)
    QVERIFY(proc.waitForReadyRead(10000));
#endif

    proc.terminate();

    QVERIFY(proc.waitForFinished(10000));
    QCOMPARE(int(proc.state()), int(QProcess::NotRunning));
    QCOMPARE(int(proc.error()), int(QProcess::UnknownError));
}

class SoftExitProcess : public QProcess
{
    Q_OBJECT
public:
    bool waitedForFinished;

    SoftExitProcess(int n) : waitedForFinished(false), n(n), killing(false)
    {
        connect(this, SIGNAL(finished(int, QProcess::ExitStatus)),
                this, SLOT(finishedSlot(int, QProcess::ExitStatus)));

        switch (n) {
        case 0:
            setReadChannelMode(QProcess::MergedChannels);
            connect(this, SIGNAL(readyRead()), this, SLOT(terminateSlot()));
            break;
        case 1:
            connect(this, SIGNAL(readyReadStandardOutput()),
                    this, SLOT(terminateSlot()));
            break;
        case 2:
            connect(this, SIGNAL(readyReadStandardError()),
                    this, SLOT(terminateSlot()));
            break;
        case 3:
            connect(this, SIGNAL(started()),
                    this, SLOT(terminateSlot()));
            break;
        case 4:
        default:
            connect(this, SIGNAL(stateChanged(QProcess::ProcessState)),
                    this, SLOT(terminateSlot()));
            break;
        }
    }

public slots:
    void terminateSlot()
    {
        if (killing || (n == 4 && state() != Running)) {
            // Don't try to kill the process before it is running - that can
            // be hazardous, as the actual child process might not be running
            // yet. Also, don't kill it "recursively".
            return;
        }
        killing = true;
        readAll();
        terminate();
        if ((waitedForFinished = waitForFinished(5000)) == false) {
            kill();
            if (state() != NotRunning)
                waitedForFinished = waitForFinished(5000);
        }
    }

    void finishedSlot(int, QProcess::ExitStatus)
    {
        waitedForFinished = true;
    }

private:
    int n;
    bool killing;
};

//-----------------------------------------------------------------------------
void tst_QProcess::softExitInSlots_data()
{
    QTest::addColumn<QString>("appName");

#ifdef Q_OS_MAC
    QTest::newRow("gui app") << "testGuiProcess/testGuiProcess.app";
#else
    QTest::newRow("gui app") << "testGuiProcess/testGuiProcess";
#endif
#ifdef Q_OS_MAC
    QTest::newRow("console app") << "testProcessEcho2/testProcessEcho2.app";
#else
    QTest::newRow("console app") << "testProcessEcho2/testProcessEcho2";
#endif
}

//-----------------------------------------------------------------------------
void tst_QProcess::softExitInSlots()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Reading and writing to a process is not supported on Qt/Symbian", SkipAll);
#endif

    QFETCH(QString, appName);

    for (int i = 0; i < 5; ++i) {
        SoftExitProcess proc(i);
        proc.start(appName);
        proc.write("OLEBOLE", 8); // include the \0
        QTestEventLoop::instance().enterLoop(10);
        QCOMPARE(proc.state(), QProcess::NotRunning);
        QVERIFY(proc.waitedForFinished);
    }
}

//-----------------------------------------------------------------------------
void tst_QProcess::mergedChannels()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Reading and writing to a process is not supported on Qt/Symbian", SkipAll);
#endif

    QProcess process;
    process.setReadChannelMode(QProcess::MergedChannels);
    QCOMPARE(process.readChannelMode(), QProcess::MergedChannels);

#ifdef Q_OS_MAC
    process.start("testProcessEcho2/testProcessEcho2.app");
#else
    process.start("testProcessEcho2/testProcessEcho2");
#endif

    QVERIFY(process.waitForStarted(5000));

    for (int i = 0; i < 100; ++i) {
        QCOMPARE(process.write("abc"), qlonglong(3));
        while (process.bytesAvailable() < 6)
            QVERIFY(process.waitForReadyRead(5000));
        QCOMPARE(process.readAll(), QByteArray("aabbcc"));
    }

    process.closeWriteChannel();
    QVERIFY(process.waitForFinished(5000));
}

//-----------------------------------------------------------------------------
void tst_QProcess::forwardedChannels()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Reading and writing to a process is not supported on Qt/Symbian", SkipAll);
#endif

    QProcess process;
    process.setReadChannelMode(QProcess::ForwardedChannels);
    QCOMPARE(process.readChannelMode(), QProcess::ForwardedChannels);

#ifdef Q_OS_MAC
    process.start("testProcessEcho2/testProcessEcho2.app");
#else
    process.start("testProcessEcho2/testProcessEcho2");
#endif

    QVERIFY(process.waitForStarted(5000));
    QCOMPARE(process.write("forwarded\n"), qlonglong(10));
    QVERIFY(!process.waitForReadyRead(250));
    QCOMPARE(process.bytesAvailable(), qlonglong(0));

    process.closeWriteChannel();
    QVERIFY(process.waitForFinished(5000));
}


//-----------------------------------------------------------------------------
void tst_QProcess::atEnd()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Reading and writing to a process is not supported on Qt/Symbian", SkipAll);
#endif

    QProcess process;

#ifdef Q_OS_MAC
    process.start("testProcessEcho/testProcessEcho.app");
#else
    process.start("testProcessEcho/testProcessEcho");
#endif
    process.write("abcdefgh\n");

    while (process.bytesAvailable() < 8)
        QVERIFY(process.waitForReadyRead(5000));

    QTextStream stream(&process);
    QVERIFY(!stream.atEnd());
    QString tmp = stream.readLine();
    QVERIFY(stream.atEnd());
    QCOMPARE(tmp, QString::fromLatin1("abcdefgh"));

    process.write("", 1);
    QVERIFY(process.waitForFinished(5000));
}

class TestThread : public QThread
{
    Q_OBJECT
public:
    inline int code()
    {
        return exitCode;
    }

#if defined(Q_OS_SYMBIAN)
    int serial;
#endif

protected:
    inline void run()
    {
        exitCode = 90210;

        QProcess process;
        connect(&process, SIGNAL(finished(int)), this, SLOT(catchExitCode(int)),
                Qt::DirectConnection);

#ifdef Q_OS_MAC
        process.start("testProcessEcho/testProcessEcho.app");
#elif defined(Q_OS_SYMBIAN) && defined(Q_CC_NOKIAX86)
    // WINSCW builds in Symbian do not allow multiple processes to load Qt libraries,
    // so use just a simple process instead of testDetached.
    process.start("testProcessNormal");
#elif defined(Q_OS_SYMBIAN)
        // testDetached used because it does something, but doesn't take too long.
        QFile infoFile(QString("c:\\logs\\detinfo%1").arg(serial));
        QStringList args;
        args << infoFile.fileName();
        process.start("testDetached", args);
#else
        process.start("testProcessEcho/testProcessEcho");
#endif

#if !defined(Q_OS_WINCE) && !defined(Q_OS_SYMBIAN)
        QCOMPARE(process.write("abc\0", 4), qint64(4));
#endif
        exitCode = exec();
    }

protected slots:
    inline void catchExitCode(int exitCode)
    {
        this->exitCode = exitCode;
        exit(exitCode);
    }

private:
    int exitCode;
};

//-----------------------------------------------------------------------------
void tst_QProcess::processInAThread()
{
    for (int i = 0; i < 10; ++i) {
        TestThread thread;
        thread.start();
        QVERIFY(thread.wait(10000));
        QCOMPARE(thread.code(), 0);
    }
}

//-----------------------------------------------------------------------------
void tst_QProcess::processesInMultipleThreads()
{
#if defined(Q_OS_SYMBIAN)
    int serialCounter = 0;
#endif

    for (int i = 0; i < 10; ++i) {
        TestThread thread1;
        TestThread thread2;
        TestThread thread3;

#if defined(Q_OS_SYMBIAN)
        thread1.serial = serialCounter++;
        thread2.serial = serialCounter++;
        thread3.serial = serialCounter++;
#endif
        thread1.start();
        thread2.start();
        thread3.start();

        QVERIFY(thread2.wait(10000));
        QVERIFY(thread3.wait(10000));
        QVERIFY(thread1.wait(10000));

        QCOMPARE(thread1.code(), 0);
        QCOMPARE(thread2.code(), 0);
        QCOMPARE(thread3.code(), 0);
    }
}

//-----------------------------------------------------------------------------
void tst_QProcess::waitForFinishedWithTimeout()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif

    process = new QProcess(this);

#ifdef Q_OS_MAC
    process->start("testProcessEcho/testProcessEcho.app");
#elif defined(Q_OS_SYMBIAN)
    process->start("testProcessOutput");
#else
    process->start("testProcessEcho/testProcessEcho");
#endif

#if defined(Q_OS_SYMBIAN)
    QVERIFY(process->waitForStarted(50));
    QVERIFY(!process->waitForFinished(1));
#else
    QVERIFY(process->waitForStarted(5000));
    QVERIFY(!process->waitForFinished(1));

    process->write("", 1);
#endif

    QVERIFY(process->waitForFinished());

    delete process;
    process = 0;
}

//-----------------------------------------------------------------------------
void tst_QProcess::waitForReadyReadInAReadyReadSlot()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Reading and writing to a process is not supported on Qt/Symbian", SkipAll);
#endif

    process = new QProcess(this);
    connect(process, SIGNAL(readyRead()), this, SLOT(waitForReadyReadInAReadyReadSlotSlot()));
    connect(process, SIGNAL(finished(int)), this, SLOT(exitLoopSlot()));
    bytesAvailable = 0;

#ifdef Q_OS_MAC
    process->start("testProcessEcho/testProcessEcho.app");
#else
    process->start("testProcessEcho/testProcessEcho");
#endif
    QVERIFY(process->waitForStarted(5000));

    QSignalSpy spy(process, SIGNAL(readyRead()));
    process->write("foo");
    QTestEventLoop::instance().enterLoop(30);
    QVERIFY(!QTestEventLoop::instance().timeout());

    QCOMPARE(spy.count(), 1);

    process->disconnect();
    QVERIFY(process->waitForFinished(5000));
    QVERIFY(process->bytesAvailable() > bytesAvailable);
    delete process;
    process = 0;
}

//-----------------------------------------------------------------------------
void tst_QProcess::waitForReadyReadInAReadyReadSlotSlot()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Reading and writing to a process is not supported on Qt/Symbian", SkipAll);
#endif

    bytesAvailable = process->bytesAvailable();
    process->write("bar", 4);
    QVERIFY(process->waitForReadyRead(5000));
    QTestEventLoop::instance().exitLoop();
}

//-----------------------------------------------------------------------------
void tst_QProcess::waitForBytesWrittenInABytesWrittenSlot()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Reading and writing to a process is not supported on Qt/Symbian", SkipAll);
#endif

    process = new QProcess(this);
    connect(process, SIGNAL(bytesWritten(qint64)), this, SLOT(waitForBytesWrittenInABytesWrittenSlotSlot()));
    bytesAvailable = 0;

#ifdef Q_OS_MAC
    process->start("testProcessEcho/testProcessEcho.app");
#else
    process->start("testProcessEcho/testProcessEcho");
#endif
    QVERIFY(process->waitForStarted(5000));

    qRegisterMetaType<qint64>("qint64");
    QSignalSpy spy(process, SIGNAL(bytesWritten(qint64)));
    process->write("f");
    QTestEventLoop::instance().enterLoop(30);
    QVERIFY(!QTestEventLoop::instance().timeout());

    QCOMPARE(spy.count(), 1);
    process->write("", 1);
    process->disconnect();
    QVERIFY(process->waitForFinished());
    delete process;
    process = 0;
}

//-----------------------------------------------------------------------------
void tst_QProcess::waitForBytesWrittenInABytesWrittenSlotSlot()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Reading and writing to a process is not supported on Qt/Symbian", SkipAll);
#endif

    process->write("b");
    QVERIFY(process->waitForBytesWritten(5000));
    QTestEventLoop::instance().exitLoop();
}

//-----------------------------------------------------------------------------
void tst_QProcess::spaceArgsTest_data()
{
    QTest::addColumn<QStringList>("args");
    QTest::addColumn<QString>("stringArgs");

    // arg1 | arg2
    QTest::newRow("arg1 arg2") << (QStringList() << QString::fromLatin1("arg1") << QString::fromLatin1("arg2"))
                               << QString::fromLatin1("arg1 arg2");
    // "arg1" | ar "g2
    QTest::newRow("\"\"\"\"arg1\"\"\"\" \"ar \"\"\"g2\"") << (QStringList() << QString::fromLatin1("\"arg1\"") << QString::fromLatin1("ar \"g2"))
                                                          << QString::fromLatin1("\"\"\"\"arg1\"\"\"\" \"ar \"\"\"g2\"");
    // ar g1 | a rg 2
    QTest::newRow("\"ar g1\" \"a rg 2\"") << (QStringList() << QString::fromLatin1("ar g1") << QString::fromLatin1("a rg 2"))
                                          << QString::fromLatin1("\"ar g1\" \"a rg 2\"");
    // -lar g1 | -l"ar g2"
    QTest::newRow("\"-lar g1\" \"-l\"\"\"ar g2\"\"\"\"") << (QStringList() << QString::fromLatin1("-lar g1") << QString::fromLatin1("-l\"ar g2\""))
                                                         << QString::fromLatin1("\"-lar g1\" \"-l\"\"\"ar g2\"\"\"\"");
    // ar"g1
    QTest::newRow("ar\"\"\"\"g1") << (QStringList() << QString::fromLatin1("ar\"g1"))
                                  << QString::fromLatin1("ar\"\"\"\"g1");
    // ar/g1
    QTest::newRow("ar\\g1") << (QStringList() << QString::fromLatin1("ar\\g1"))
                            << QString::fromLatin1("ar\\g1");
    // ar\g"1
    QTest::newRow("ar\\g\"\"\"\"1") << (QStringList() << QString::fromLatin1("ar\\g\"1"))
                                    << QString::fromLatin1("ar\\g\"\"\"\"1");
    // arg\"1
    QTest::newRow("arg\\\"\"\"1") << (QStringList() << QString::fromLatin1("arg\\\"1"))
                                  << QString::fromLatin1("arg\\\"\"\"1");
    // """"
    QTest::newRow("\"\"\"\"\"\"\"\"\"\"\"\"") << (QStringList() << QString::fromLatin1("\"\"\"\""))
                                              << QString::fromLatin1("\"\"\"\"\"\"\"\"\"\"\"\"");
    // """" | "" ""
    QTest::newRow("\"\"\"\"\"\"\"\"\"\"\"\" \"\"\"\"\"\"\" \"\"\"\"\"\"\"") << (QStringList() << QString::fromLatin1("\"\"\"\"") << QString::fromLatin1("\"\" \"\""))
                                                                            << QString::fromLatin1("\"\"\"\"\"\"\"\"\"\"\"\" \"\"\"\"\"\"\" \"\"\"\"\"\"\"");
    // ""  ""
    QTest::newRow("\"\"\"\"\"\"\" \"\" \"\"\"\"\"\"\" (bogus double quotes)") << (QStringList() << QString::fromLatin1("\"\"  \"\""))
                                                                              << QString::fromLatin1("\"\"\"\"\"\"\" \"\" \"\"\"\"\"\"\"");
    // ""  ""
    QTest::newRow(" \"\"\"\"\"\"\" \"\" \"\"\"\"\"\"\"   (bogus double quotes)") << (QStringList() << QString::fromLatin1("\"\"  \"\""))
                                                                                 << QString::fromLatin1(" \"\"\"\"\"\"\" \"\" \"\"\"\"\"\"\"   ");
}

//-----------------------------------------------------------------------------
void tst_QProcess::spaceArgsTest()
{
    QFETCH(QStringList, args);
    QFETCH(QString, stringArgs);

    QStringList programs;
    programs << QString::fromLatin1("testProcessSpacesArgs/nospace")
#if defined(Q_OS_SYMBIAN)
    ; // Symbian toolchain doesn't like exes with spaces in the name
#else
             << QString::fromLatin1("testProcessSpacesArgs/one space")
             << QString::fromLatin1("testProcessSpacesArgs/two space s");
#endif

    process = new QProcess(this);

    for (int i = 0; i < programs.size(); ++i) {
        QString program = programs.at(i);
        process->start(program, args);

#if !defined(Q_OS_WINCE) && !defined(Q_OS_SYMBIAN)
        QVERIFY(process->waitForStarted(5000));
        QVERIFY(process->waitForFinished(5000));
#else
        QVERIFY(process->waitForStarted(10000));
        QVERIFY(process->waitForFinished(10000));
#endif

#if defined(Q_OS_SYMBIAN)
        // Symbian test outputs to a file, so check that
        FILE* file = fopen("c:\\logs\\qprocess_args_test.txt","r");
        char buf[256];
        fgets(buf, 256, file);
        fclose(file);
        QStringList actual = QString::fromLatin1(buf).split("|");
#elif !defined(Q_OS_WINCE)
        QStringList actual = QString::fromLatin1(process->readAll()).split("|");
#endif
#if !defined(Q_OS_WINCE)
        QVERIFY(!actual.isEmpty());
        // not interested in the program name, it might be different.
        actual.removeFirst();

        QCOMPARE(actual, args);
#endif

        if (program.contains(" "))
            program = "\"" + program + "\"";

        if (!stringArgs.isEmpty())
            program += QString::fromLatin1(" ") + stringArgs;

        process->start(program);

        QVERIFY(process->waitForStarted(5000));
        QVERIFY(process->waitForFinished(5000));

#if defined(Q_OS_SYMBIAN)
        // Symbian test outputs to a file, so check that
        file = fopen("c:\\logs\\qprocess_args_test.txt","r");
        fgets(buf, 256, file);
        fclose(file);
        actual = QString::fromLatin1(buf).split("|");
#elif !defined(Q_OS_WINCE)
        actual = QString::fromLatin1(process->readAll()).split("|");
#endif
#if !defined(Q_OS_WINCE)
        QVERIFY(!actual.isEmpty());
        // not interested in the program name, it might be different.
        actual.removeFirst();

        QCOMPARE(actual, args);
#endif
    }

    delete process;
    process = 0;
}

//-----------------------------------------------------------------------------
void tst_QProcess::exitCodeTest()
{
#if defined(Q_OS_SYMBIAN)
    // Kernel will run out of process handles on some hw, as there is some
    // delay before they are recycled, so limit the amount of processes.
    for (int i = 0; i < 50; ++i) {
#else
    for (int i = 0; i < 255; ++i) {
#endif
        QProcess process;
        process.start("testExitCodes/testExitCodes " + QString::number(i));
        QVERIFY(process.waitForFinished(5000));
        QCOMPARE(process.exitCode(), i);
        QCOMPARE(process.error(), QProcess::UnknownError);
    }
}

//-----------------------------------------------------------------------------
void tst_QProcess::failToStart()
{
    qRegisterMetaType<QProcess::ProcessError>("QProcess::ProcessError");
    qRegisterMetaType<QProcess::ExitStatus>("QProcess::ExitStatus");
    qRegisterMetaType<QProcess::ProcessState>("QProcess::ProcessState");

    QProcess process;
    QSignalSpy stateSpy(&process, SIGNAL(stateChanged(QProcess::ProcessState)));
    QSignalSpy errorSpy(&process, SIGNAL(error(QProcess::ProcessError)));
    QSignalSpy finishedSpy(&process, SIGNAL(finished(int)));
    QSignalSpy finishedSpy2(&process, SIGNAL(finished(int, QProcess::ExitStatus)));

// Mac OS X and HP-UX have a really low default process limit (~100), so spawning
// to many processes here will cause test failures later on.
#if defined Q_OS_HPUX
   const int attempts = 15;
#elif defined Q_OS_MAC
   const int attempts = 15;
#else
   const int attempts = 50;
#endif

    for (int j = 0; j < 8; ++j) {
        for (int i = 0; i < attempts; ++i) {
            QCOMPARE(errorSpy.count(), j * attempts + i);
            process.start("/blurp");

            switch (j) {
            case 0:
            case 1:
                QVERIFY(!process.waitForStarted());
                break;
            case 2:
            case 3:
                QVERIFY(!process.waitForFinished());
                break;
            case 4:
            case 5:
                QVERIFY(!process.waitForReadyRead());
                break;
            case 6:
            case 7:
            default:
                QVERIFY(!process.waitForBytesWritten());
                break;
            }

            QCOMPARE(process.error(), QProcess::FailedToStart);
            QCOMPARE(errorSpy.count(), j * attempts + i + 1);
            QCOMPARE(finishedSpy.count(), 0);
            QCOMPARE(finishedSpy2.count(), 0);

            int it = j * attempts + i + 1;

            QCOMPARE(stateSpy.count(), it * 2);
            QCOMPARE(qVariantValue<QProcess::ProcessState>(stateSpy.at(it * 2 - 2).at(0)), QProcess::Starting);
            QCOMPARE(qVariantValue<QProcess::ProcessState>(stateSpy.at(it * 2 - 1).at(0)), QProcess::NotRunning);
        }
    }
}

//-----------------------------------------------------------------------------
void tst_QProcess::failToStartWithWait()
{
    qRegisterMetaType<QProcess::ProcessError>("QProcess::ProcessError");
    qRegisterMetaType<QProcess::ExitStatus>("QProcess::ExitStatus");

    QProcess process;
    QEventLoop loop;
    QSignalSpy errorSpy(&process, SIGNAL(error(QProcess::ProcessError)));
    QSignalSpy finishedSpy(&process, SIGNAL(finished(int)));
    QSignalSpy finishedSpy2(&process, SIGNAL(finished(int, QProcess::ExitStatus)));

    for (int i = 0; i < 50; ++i) {
        process.start("/blurp", QStringList() << "-v" << "-debug");
        process.waitForStarted();

        QCOMPARE(process.error(), QProcess::FailedToStart);
        QCOMPARE(errorSpy.count(), i + 1);
        QCOMPARE(finishedSpy.count(), 0);
        QCOMPARE(finishedSpy2.count(), 0);
    }
}

//-----------------------------------------------------------------------------
void tst_QProcess::failToStartWithEventLoop()
{
    qRegisterMetaType<QProcess::ProcessError>("QProcess::ProcessError");
    qRegisterMetaType<QProcess::ExitStatus>("QProcess::ExitStatus");

    QProcess process;
    QEventLoop loop;
    QSignalSpy errorSpy(&process, SIGNAL(error(QProcess::ProcessError)));
    QSignalSpy finishedSpy(&process, SIGNAL(finished(int)));
    QSignalSpy finishedSpy2(&process, SIGNAL(finished(int, QProcess::ExitStatus)));

    // The error signal may be emitted before start() returns
    connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit()), Qt::QueuedConnection);


    for (int i = 0; i < 50; ++i) {
        process.start("/blurp", QStringList() << "-v" << "-debug");

        loop.exec();

        QCOMPARE(process.error(), QProcess::FailedToStart);
        QCOMPARE(errorSpy.count(), i + 1);
        QCOMPARE(finishedSpy.count(), 0);
        QCOMPARE(finishedSpy2.count(), 0);
    }
}

//-----------------------------------------------------------------------------
void tst_QProcess::removeFileWhileProcessIsRunning()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Reading and writing to a process is not supported on Qt/Symbian", SkipAll);
#endif

    QFile file("removeFile.txt");
    QVERIFY(file.open(QFile::WriteOnly));

    QProcess process;
#ifdef Q_OS_MAC
    process.start("testProcessEcho/testProcessEcho.app");
#else
    process.start("testProcessEcho/testProcessEcho");
#endif

    QVERIFY(process.waitForStarted(5000));

    QVERIFY(file.remove());

    process.write("", 1);
    QVERIFY(process.waitForFinished(5000));
}

//-----------------------------------------------------------------------------
void tst_QProcess::setEnvironment_data()
{
    QTest::addColumn<QString>("name");
    QTest::addColumn<QString>("value");

    QTest::newRow("setting-empty") << "tst_QProcess" << "";
    QTest::newRow("setting") << "tst_QProcess" << "value";

#ifdef Q_OS_WIN
    QTest::newRow("unsetting") << "PROMPT" << QString();
    QTest::newRow("overriding") << "PROMPT" << "value";
#else
    QTest::newRow("unsetting") << "PATH" << QString();
    QTest::newRow("overriding") << "PATH" << "value";
#endif
}

void tst_QProcess::setEnvironment()
{
#if defined (Q_OS_WINCE) || defined(Q_OS_SYMBIAN)
    QSKIP("OS doesn't support environment variables", SkipAll);
#endif

    // make sure our environment variables are correct
    QVERIFY(qgetenv("tst_QProcess").isEmpty());
    QVERIFY(!qgetenv("PATH").isEmpty());
#ifdef Q_OS_WIN
    QVERIFY(!qgetenv("PROMPT").isEmpty());
#endif

    QFETCH(QString, name);
    QFETCH(QString, value);
    QString executable = QDir::currentPath() + "/testProcessEnvironment/testProcessEnvironment";

    {
        QProcess process;
        QStringList environment = QProcess::systemEnvironment();
        if (value.isNull()) {
            int pos;
            QRegExp rx(name + "=.*");
#ifdef Q_OS_WIN
            rx.setCaseSensitivity(Qt::CaseInsensitive);
#endif
            while ((pos = environment.indexOf(rx)) != -1)
                environment.removeAt(pos);
        } else {
            environment.append(name + '=' + value);
        }
        process.setEnvironment(environment);
        process.start(executable, QStringList() << name);

        QVERIFY(process.waitForFinished());
        if (value.isNull())
            QCOMPARE(process.exitCode(), 1);
        else if (!value.isEmpty())
            QCOMPARE(process.exitCode(), 0);

        QCOMPARE(process.readAll(), value.toLocal8Bit());
    }

    // re-do the test but set the environment twice, to make sure
    // that the latter addition overrides
    // this test doesn't make sense in unsetting
    if (!value.isNull()) {
        QProcess process;
        QStringList environment = QProcess::systemEnvironment();
        environment.prepend(name + "=This is not the right value");
        environment.append(name + '=' + value);
        process.setEnvironment(environment);
        process.start(executable, QStringList() << name);

        QVERIFY(process.waitForFinished());
        if (!value.isEmpty())
            QCOMPARE(process.exitCode(), 0);

        QCOMPARE(process.readAll(), value.toLocal8Bit());
    }
}

//-----------------------------------------------------------------------------
void tst_QProcess::setProcessEnvironment_data()
{
    setEnvironment_data();
}

void tst_QProcess::setProcessEnvironment()
{
#if defined (Q_OS_WINCE) || defined(Q_OS_SYMBIAN)
    QSKIP("OS doesn't support environment variables", SkipAll);
#endif

    // make sure our environment variables are correct
    QVERIFY(qgetenv("tst_QProcess").isEmpty());
    QVERIFY(!qgetenv("PATH").isEmpty());
#ifdef Q_OS_WIN
    QVERIFY(!qgetenv("PROMPT").isEmpty());
#endif

    QFETCH(QString, name);
    QFETCH(QString, value);
    QString executable = QDir::currentPath() + "/testProcessEnvironment/testProcessEnvironment";

    {
        QProcess process;
        QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
        if (value.isNull())
            environment.remove(name);
        else
            environment.insert(name, value);
        process.setProcessEnvironment(environment);
        process.start(executable, QStringList() << name);

        QVERIFY(process.waitForFinished());
        if (value.isNull())
            QCOMPARE(process.exitCode(), 1);
        else if (!value.isEmpty())
            QCOMPARE(process.exitCode(), 0);

        QCOMPARE(process.readAll(), value.toLocal8Bit());
    }
}
//-----------------------------------------------------------------------------
void tst_QProcess::systemEnvironment()
{
#if defined (Q_OS_WINCE) || defined(Q_OS_SYMBIAN)
    // there is no concept of system variables on Windows CE as there is no console
    QVERIFY(QProcess::systemEnvironment().isEmpty());
    QVERIFY(QProcessEnvironment::systemEnvironment().isEmpty());
#else
    QVERIFY(!QProcess::systemEnvironment().isEmpty());
    QVERIFY(!QProcessEnvironment::systemEnvironment().isEmpty());

    QVERIFY(QProcessEnvironment::systemEnvironment().contains("PATH"));
    QVERIFY(!QProcess::systemEnvironment().filter(QRegExp("^PATH=", Qt::CaseInsensitive)).isEmpty());
#endif
}

//-----------------------------------------------------------------------------
void tst_QProcess::spaceInName()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Reading and writing to a process is not supported on Qt/Symbian", SkipAll);
#endif
    QProcess process;
    process.start("test Space In Name/testSpaceInName", QStringList());
    QVERIFY(process.waitForStarted());
    process.write("", 1);
    QVERIFY(process.waitForFinished());
}

//-----------------------------------------------------------------------------
void tst_QProcess::lockupsInStartDetached()
{
#if !defined(Q_OS_SYMBIAN)
    // What exactly is this call supposed to achieve anyway?
    QHostInfo::lookupHost(QString("something.invalid"), 0, 0);
#endif
    QProcess::execute("yjhbrty");
    QProcess::startDetached("yjhbrty");
}

//-----------------------------------------------------------------------------
void tst_QProcess::atEnd2()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Reading and writing to a process is not supported on Qt/Symbian", SkipAll);
#endif

    QProcess process;

#ifdef Q_OS_MAC
    process.start("testProcessEcho/testProcessEcho.app");
#else
    process.start("testProcessEcho/testProcessEcho");
#endif
    process.write("Foo\nBar\nBaz\nBodukon\nHadukan\nTorwukan\nend\n");
    process.putChar('\0');
    QVERIFY(process.waitForFinished());
    QList<QByteArray> lines;
    while (!process.atEnd()) {
        lines << process.readLine();
    }
    QCOMPARE(lines.size(), 7);
}

//-----------------------------------------------------------------------------
void tst_QProcess::waitForReadyReadForNonexistantProcess()
{
    // This comes from task 108968
    // Start a program that doesn't exist, process events and then try to waitForReadyRead
    qRegisterMetaType<QProcess::ProcessError>("QProcess::ProcessError");
    qRegisterMetaType<QProcess::ExitStatus>("QProcess::ExitStatus");

    QProcess process;
    QSignalSpy errorSpy(&process, SIGNAL(error(QProcess::ProcessError)));
    QSignalSpy finishedSpy1(&process, SIGNAL(finished(int)));
    QSignalSpy finishedSpy2(&process, SIGNAL(finished(int, QProcess::ExitStatus)));
    QVERIFY(!process.waitForReadyRead()); // used to crash
    process.start("doesntexist");
    QVERIFY(!process.waitForReadyRead());
    QCOMPARE(errorSpy.count(), 1);
    QCOMPARE(errorSpy.at(0).at(0).toInt(), 0);
    QCOMPARE(finishedSpy1.count(), 0);
    QCOMPARE(finishedSpy2.count(), 0);
}

//-----------------------------------------------------------------------------
void tst_QProcess::setStandardInputFile()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Reading and writing to a process is not supported on Qt/Symbian", SkipAll);
#endif

    static const char data[] = "A bunch\1of\2data\3\4\5\6\7...";
    QProcess process;
    QFile file("data");

    QVERIFY(file.open(QIODevice::WriteOnly));
    file.write(data, sizeof data);
    file.close();

    process.setStandardInputFile("data");
#ifdef Q_OS_MAC
    process.start("testProcessEcho/testProcessEcho.app");
#else
    process.start("testProcessEcho/testProcessEcho");
#endif

    QPROCESS_VERIFY(process, waitForFinished());
	QByteArray all = process.readAll();
    QCOMPARE(all.size(), int(sizeof data) - 1); // testProcessEcho drops the ending \0
    QVERIFY(all == data);
}

//-----------------------------------------------------------------------------
void tst_QProcess::setStandardOutputFile_data()
{
    QTest::addColumn<int>("channelToTest");
    QTest::addColumn<int>("_channelMode");
    QTest::addColumn<bool>("append");

    QTest::newRow("stdout-truncate") << int(QProcess::StandardOutput)
                                     << int(QProcess::SeparateChannels)
                                     << false;
    QTest::newRow("stdout-append") << int(QProcess::StandardOutput)
                                   << int(QProcess::SeparateChannels)
                                   << true;

    QTest::newRow("stderr-truncate") << int(QProcess::StandardError)
                                     << int(QProcess::SeparateChannels)
                                     << false;
    QTest::newRow("stderr-append") << int(QProcess::StandardError)
                                   << int(QProcess::SeparateChannels)
                                   << true;

    QTest::newRow("merged-truncate") << int(QProcess::StandardOutput)
                                     << int(QProcess::MergedChannels)
                                     << false;
    QTest::newRow("merged-append") << int(QProcess::StandardOutput)
                                   << int(QProcess::MergedChannels)
                                   << true;
}

void tst_QProcess::setStandardOutputFile()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Reading and writing to a process is not supported on Qt/Symbian", SkipAll);
#endif

    static const char data[] = "Original data. ";
    static const char testdata[] = "Test data.";

    QFETCH(int, channelToTest);
    QFETCH(int, _channelMode);
    QFETCH(bool, append);

    QProcess::ProcessChannelMode channelMode = QProcess::ProcessChannelMode(_channelMode);
    QIODevice::OpenMode mode = append ? QIODevice::Append : QIODevice::Truncate;

    // create the destination file with data
    QFile file("data");
    QVERIFY(file.open(QIODevice::WriteOnly));
    file.write(data, sizeof data - 1);
    file.close();

    // run the process
    QProcess process;
    process.setReadChannelMode(channelMode);
    if (channelToTest == QProcess::StandardOutput)
        process.setStandardOutputFile("data", mode);
    else
        process.setStandardErrorFile("data", mode);

#ifdef Q_OS_MAC
    process.start("testProcessEcho2/testProcessEcho2.app");
#else
    process.start("testProcessEcho2/testProcessEcho2");
#endif
    process.write(testdata, sizeof testdata);
    QPROCESS_VERIFY(process,waitForFinished());

    // open the file again and verify the data
    QVERIFY(file.open(QIODevice::ReadOnly));
    QByteArray all = file.readAll();
    file.close();

    int expectedsize = sizeof testdata - 1;
    if (mode == QIODevice::Append) {
        QVERIFY(all.startsWith(data));
        expectedsize += sizeof data - 1;
    }
    if (channelMode == QProcess::MergedChannels) {
        expectedsize += sizeof testdata - 1;
    } else {
        QVERIFY(all.endsWith(testdata));
    }

    QCOMPARE(all.size(), expectedsize);
}

//-----------------------------------------------------------------------------
void tst_QProcess::setStandardOutputProcess_data()
{
    QTest::addColumn<bool>("merged");
    QTest::newRow("separate") << false;
    QTest::newRow("merged") << true;
}

void tst_QProcess::setStandardOutputProcess()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Reading and writing to a process is not supported on Qt/Symbian", SkipAll);
#endif

    QProcess source;
    QProcess sink;

    QFETCH(bool, merged);
    source.setReadChannelMode(merged ? QProcess::MergedChannels : QProcess::SeparateChannels);
    source.setStandardOutputProcess(&sink);

#ifdef Q_OS_MAC
    source.start("testProcessEcho2/testProcessEcho2.app");
    sink.start("testProcessEcho2/testProcessEcho2.app");
#else
    source.start("testProcessEcho2/testProcessEcho2");
    sink.start("testProcessEcho2/testProcessEcho2");
#endif

    QByteArray data("Hello, World");
    source.write(data);
    source.closeWriteChannel();
    QPROCESS_VERIFY(source, waitForFinished());
    QPROCESS_VERIFY(sink, waitForFinished());
    QByteArray all = sink.readAll();

    if (!merged)
        QCOMPARE(all, data);
    else
        QCOMPARE(all, QByteArray("HHeelllloo,,  WWoorrlldd"));
}

//-----------------------------------------------------------------------------
void tst_QProcess::fileWriterProcess()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Reading and writing to a process is not supported on Qt/Symbian", SkipAll);
#endif

    QString stdinStr;
    for (int i = 0; i < 5000; ++i)
        stdinStr += QString::fromLatin1("%1 -- testing testing 1 2 3\n").arg(i);

    QTime stopWatch;
    stopWatch.start();
    do {
        QFile::remove("fileWriterProcess.txt");
        QProcess process;
        process.start("fileWriterProcess/fileWriterProcess",
                      QIODevice::ReadWrite | QIODevice::Text);
        process.write(stdinStr.toLatin1());
        process.closeWriteChannel();
        while (process.bytesToWrite()) {
            QVERIFY(stopWatch.elapsed() < 3500);
            QVERIFY(process.waitForBytesWritten(2000));
        }
        QVERIFY(process.waitForFinished());
        QCOMPARE(QFile("fileWriterProcess.txt").size(), qint64(stdinStr.size()));
    } while (stopWatch.elapsed() < 3000);
}

//-----------------------------------------------------------------------------
void tst_QProcess::detachedWorkingDirectoryAndPid()
{
#if defined(Q_OS_SYMBIAN) && defined(Q_CC_NOKIAX86)
    // WINSCW builds in Symbian do not allow multiple processes to load Qt libraries,
    // so this test must be skipped.
    QSKIP("Multiple processes loading Qt are not allowed in Qt/Symbian emulator.", SkipAll);
#endif
    qint64 pid;

#ifdef Q_OS_WINCE
    QTest::qSleep(1000);
#endif

#if defined(Q_OS_SYMBIAN)
    // Symbian has no working directory support, so use logs dir as a shared directory
    QFile infoFile(QLatin1String("c:\\logs\\detachedinfo.txt"));
#else
    QFile infoFile(QDir::currentPath() + QLatin1String("/detachedinfo.txt"));
#endif
    infoFile.remove();

    QString workingDir = QDir::currentPath() + "/testDetached";

#ifndef Q_OS_SYMBIAN
    QVERIFY(QFile::exists(workingDir));
#endif

    QStringList args;
    args << infoFile.fileName();
    QVERIFY(QProcess::startDetached(QDir::currentPath() + QLatin1String("/testDetached/testDetached"), args, workingDir, &pid));

    QFileInfo fi(infoFile);
    fi.setCaching(false);
    while (fi.size() == 0) {
        QTest::qSleep(100);
    }

    QVERIFY(infoFile.open(QIODevice::ReadOnly | QIODevice::Text));
    QString actualWorkingDir = QString::fromUtf8(infoFile.readLine());
    actualWorkingDir.chop(1); // strip off newline
    QByteArray processIdString = infoFile.readLine();
    processIdString.chop(1);
    infoFile.close();
    infoFile.remove();

    bool ok = false;
    qint64 actualPid = processIdString.toLongLong(&ok);
    QVERIFY(ok);

#if defined(Q_OS_SYMBIAN)
    QEXPECT_FAIL("", "Working directory is not supported on Qt/symbian", Continue);
#endif
    QCOMPARE(actualWorkingDir, workingDir);
    QCOMPARE(actualPid, pid);
}

//-----------------------------------------------------------------------------
void tst_QProcess::switchReadChannels()
{
#ifdef Q_OS_WINCE
    QSKIP("Reading and writing to a process is not supported on Qt/CE", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Reading and writing to a process is not supported on Qt/Symbian", SkipAll);
#endif
    const char data[] = "ABCD";

    QProcess process;

#ifdef Q_OS_MAC
    process.start("testProcessEcho2/testProcessEcho2.app");
#else
    process.start("testProcessEcho2/testProcessEcho2");
#endif
    process.write(data);
    process.closeWriteChannel();
    QVERIFY(process.waitForFinished(5000));

    for (int i = 0; i < 4; ++i) {
        process.setReadChannel(QProcess::StandardOutput);
        QCOMPARE(process.read(1), QByteArray(&data[i], 1));
        process.setReadChannel(QProcess::StandardError);
        QCOMPARE(process.read(1), QByteArray(&data[i], 1));
    }

    process.ungetChar('D');
    process.setReadChannel(QProcess::StandardOutput);
    process.ungetChar('D');
    process.setReadChannel(QProcess::StandardError);
    QCOMPARE(process.read(1), QByteArray("D"));
    process.setReadChannel(QProcess::StandardOutput);
    QCOMPARE(process.read(1), QByteArray("D"));
}

//-----------------------------------------------------------------------------
void tst_QProcess::setWorkingDirectory()
{
#ifdef Q_OS_WINCE
    QSKIP("Windows CE does not support working directory logic", SkipAll);
#endif
#if defined(Q_OS_SYMBIAN)
    QSKIP("Symbian does not support working directory logic", SkipAll);
#endif
    process = new QProcess;
    process->setWorkingDirectory("test");
#ifdef Q_OS_MAC
    process->start("testSetWorkingDirectory/testSetWorkingDirectory.app");
#else
    process->start("testSetWorkingDirectory/testSetWorkingDirectory");
#endif
#ifndef Q_OS_WIN
    QSKIP("setWorkingDirectory will chdir before starting the process on unices", SkipAll);
#endif
    QVERIFY(process->waitForFinished());

    QByteArray workingDir = process->readAllStandardOutput();
    QCOMPARE(QDir("test").canonicalPath(), QDir(workingDir.constData()).canonicalPath());

    delete process;
    process = 0;
}

//-----------------------------------------------------------------------------
void tst_QProcess::startFinishStartFinish()
{
    QProcess process;

    for (int i = 0; i < 3; ++i) {
        QCOMPARE(process.state(), QProcess::NotRunning);

#ifdef Q_OS_MAC
        process.start("testProcessOutput/testProcessOutput.app");
#else
        process.start("testProcessOutput/testProcessOutput");
#endif
#if !defined(Q_OS_WINCE) && !defined(Q_OS_SYMBIAN)
        QVERIFY(process.waitForReadyRead(10000));
        QCOMPARE(QString::fromLatin1(process.readLine().trimmed()),
                 QString("0 -this is a number"));
#endif
        if (process.state() != QProcess::NotRunning)
            QVERIFY(process.waitForFinished(10000));
#if defined(Q_OS_SYMBIAN)
        // Symbian test outputs to a file, so check that
        FILE* file = fopen("c:\\logs\\qprocess_output_test.txt","r");
        char buf[30];
        fgets(buf, 30, file);
        QCOMPARE(QString::fromLatin1(buf),
                 QString("0 -this is a number\n"));
        fclose(file);
#endif
    }
}

//-----------------------------------------------------------------------------
void tst_QProcess::invalidProgramString_data()
{
    QTest::addColumn<QString>("programString");
    QTest::newRow("null string") << QString();
    QTest::newRow("empty string") << QString("");
    QTest::newRow("only blank string") << QString("  ");
}

void tst_QProcess::invalidProgramString()
{
    QFETCH(QString, programString);
    QProcess process;

    qRegisterMetaType<QProcess::ProcessError>("QProcess::ProcessError");
    QSignalSpy spy(&process, SIGNAL(error(QProcess::ProcessError)));

    process.start(programString);
    QCOMPARE(process.error(), QProcess::FailedToStart);
    QCOMPARE(spy.count(), 1);

    QVERIFY(!QProcess::startDetached(programString));
}

QTEST_MAIN(tst_QProcess)
#include "tst_qprocess.moc"
#endif

