///////////////////////////////////////////////////////////////////////////////
// Name:        tests/exec/exec.cpp
// Purpose:     test wxExecute()
// Author:      Francesco Montorsi
//              (based on console sample TestExecute() function)
// Created:     2009-01-10
// Copyright:   (c) 2009 Francesco Montorsi
//              (c) 2013 Rob Bresalier, Vadim Zeitlin
// Licence:     wxWindows licence
///////////////////////////////////////////////////////////////////////////////

// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------

#include "testprec.h"


#include "wx/utils.h"
#include "wx/process.h"
#include "wx/sstream.h"
#include "wx/evtloop.h"
#include "wx/file.h"
#include "wx/filename.h"
#include "wx/mstream.h"
#include "wx/scopeguard.h"
#include "wx/txtstrm.h"
#include "wx/timer.h"

// wxX11 didn't implement some required features. Disable these tests
// for now.
#if !defined (__WXX11__)

#ifdef __UNIX__
    #define COMMAND "echo hi"
    #define COMMAND_STDERR "cat nonexistentfile"
    #define ASYNC_COMMAND "sleep 86400"
    #define SHELL_COMMAND "echo hi from shell>/dev/null"
    #define COMMAND_NO_OUTPUT "echo -n"
#elif defined(__WINDOWS__)
    #define COMMAND "cmd.exe /c \"echo hi\""
    #define COMMAND_STDERR "cmd.exe /c \"type nonexistentfile\""
    #define ASYNC_COMMAND "mspaint"
    #define SHELL_COMMAND "echo hi > nul:"
    #define COMMAND_NO_OUTPUT COMMAND " > nul:"
#else
    #error "no command to exec"
#endif // OS

#define SLEEP_END_STRING "Done sleeping"

namespace
{
    enum AsyncExecLoopExitEnum
    {
        AsyncExec_DontExitLoop,
        AsyncExec_ExitLoop
    };
} // anonymous namespace

// ----------------------------------------------------------------------------
// namespace-like class with helpers for the test below
// ----------------------------------------------------------------------------

class ExecTestCase
{
public:
    ExecTestCase() { }

protected:

    // Helper: create an executable file sleeping for the given amount of
    // seconds with the specified base name.
    //
    // Returns the name of the file.
    static wxString CreateSleepFile(const wxString& basename, int seconds);

    // Return the full command, to be passed to wxExecute(), launching the
    // specified script file.
    static wxString MakeShellCommand(const wxString& filename);


    // Helper of TestAsyncRedirect(): tests that the output of the given
    // command on the given stream contains the expected string.
    enum CheckStream { Check_Stdout, Check_Stderr };

    void DoTestAsyncRedirect(const wxString& command,
                             CheckStream check,
                             const char* expectedContaining);

    // This class is used as a helper in order to run wxExecute(ASYNC)
    // inside of an event loop.
    class AsyncInEventLoop : public wxTimer
    {
    public:
        AsyncInEventLoop() { }

        long DoExecute(AsyncExecLoopExitEnum forceExitLoop_,
                     const wxString& command_,
                     int flags_ = wxEXEC_ASYNC,
                     wxProcess* callback_ = nullptr)
        {
            forceExitLoop = forceExitLoop_;

            // Prepend the command with the value of wxTEST_RUNNER if it's
            // defined to make this test work when using Wine too.
            if ( wxGetEnv("wxTEST_RUNNER", &command) )
                command += ' ';
            command += command_;

            flags = flags_;
            callback = callback_;

            wxEventLoop loop;

            // Trigger the timer to go off inside the event loop
            // so that we can run wxExecute there.
            StartOnce(10);

            // Run the event loop.
            loop.Run();

            return wxExecuteReturnCode;
        }

        void Notify() override
        {
            // Run wxExecute inside the event loop.
            wxExecuteReturnCode = wxExecute(command, flags, callback);

            if (forceExitLoop == AsyncExec_ExitLoop)
            {
                wxEventLoop::GetActive()->Exit();
            }
        }

    private:
        AsyncExecLoopExitEnum forceExitLoop;
        wxString command;
        int flags;
        wxProcess* callback;
        long wxExecuteReturnCode;
    };

    wxDECLARE_NO_COPY_CLASS(ExecTestCase);
};

// ----------------------------------------------------------------------------
// The tests themselves
// ----------------------------------------------------------------------------

TEST_CASE("wxShell", "[exec]")
{
    CHECK( wxShell(SHELL_COMMAND) );
}

TEST_CASE_METHOD(ExecTestCase, "wxExecute", "[exec]")
{
    // Launching interactive programs doesn't work without an interactive
    // session.
    if ( IsAutomaticTest() )
        return;

    AsyncInEventLoop asyncInEventLoop;

    // test asynch exec
    //
    // asyncInEventLoop.DoExecute will perform the
    // call to wxExecute(ASYNC) in an event loop, as required by
    // console test (and this same event loop will also
    // be used in GUI test too, even though not required, just to have
    // common code).
    long pid = asyncInEventLoop.DoExecute(AsyncExec_ExitLoop, // Force exit of event loop right
                                                // after the call to wxExecute()
                                          ASYNC_COMMAND, wxEXEC_ASYNC);
    REQUIRE( pid != 0 );

    // Give the system some time to launch the child.
    wxMilliSleep(200);

    // Try to terminate it gently first, but fall back to killing it
    // unconditionally if this fails.
    const int rc = wxKill(pid, wxSIGTERM);
    CHECK( rc == 0 );
    if ( rc != 0 )
        CHECK( wxKill(pid, wxSIGKILL) == 0 );

    int useNoeventsFlag;

    // Test the sync execution case with/without wxEXEC_NOEVENTS flag
    // because we use either an event loop or wxSelectDispatcher
    // depending on this flag, and we want to test both cases.
    for (useNoeventsFlag = 0; useNoeventsFlag <=1 ; ++useNoeventsFlag )
    {
        int execFlags = wxEXEC_SYNC;

        if (useNoeventsFlag)
        {
            INFO("Testing wxEXEC_SYNC | wxEXEC_NOEVENTS");
            execFlags |= wxEXEC_NOEVENTS;
        }

        // test sync exec (with a command not producing any output to avoid
        // interfering with the test):
        CHECK( wxExecute(COMMAND_NO_OUTPUT, execFlags) == 0 );

        // test running COMMAND again, but this time with redirection:
        // and the expected data is on stdout.
        wxArrayString stdout_arr;
        CHECK( wxExecute(COMMAND, stdout_arr, execFlags) == 0 );
        CHECK( stdout_arr[0] == "hi" );

        // test running COMMAND_STDERR with redirection and the expected data
        // is on stderr.
        wxArrayString stderr_arr;
        stdout_arr.Empty();
        CHECK( wxExecute(COMMAND_STDERR, stdout_arr, stderr_arr, execFlags) != 0 );

        // Check that there is something on stderr.
        // In Unix systems, the 'cat' command has the name of the file it could not
        // find in the error output.
        // In Windows, the 'type' command outputs the following when it can't find
        // a file:
        // "The system cannot find the file specified"
        // In both cases, we expect the word 'file' to be in the stderr.
        CHECK( stderr_arr[0].Contains("file") );
    }
}

TEST_CASE_METHOD(ExecTestCase, "wxProcess", "[exec]")
{
    if ( IsAutomaticTest() )
        return;

    AsyncInEventLoop asyncInEventLoop;

    // test wxExecute with wxProcess
    wxProcess *proc = new wxProcess;

    // asyncInEventLoop.DoExecute will perform the
    // call to wxExecute(ASYNC) in an event loop, as required by
    // console test (and this same event loop will also
    // be used in GUI test too, even though not required, just to have
    // common code).
    long pid = asyncInEventLoop.DoExecute(AsyncExec_ExitLoop, // Force exit of event loop right
                                                // after the call to wxExecute()
                                          ASYNC_COMMAND, wxEXEC_ASYNC, proc);
    CHECK( proc->GetPid() == pid );
    REQUIRE( pid != 0 );

    // As above, give the system time to launch the process.
    wxMilliSleep(200);

    // we're not going to process the wxEVT_END_PROCESS event,
    // so the proc instance will auto-delete itself after we kill
    // the asynch process:
    const int rc = wxKill(pid, wxSIGTERM);
    CHECK( rc == 0 );
    if ( rc != 0 )
        CHECK( wxKill(pid, wxSIGKILL) == 0 );


    // test wxExecute with wxProcess and REDIRECTION

    // Test the sync execution case with/without wxEXEC_NOEVENTS flag
    // because we use either an event loop or wxSelectDispatcher
    // depending on this flag, and we want to test both cases.

    // First the default case, dispatching the events while waiting.
    SECTION("With events")
    {
        wxProcess proc2;
        proc2.Redirect();
        CHECK( wxExecute(COMMAND, wxEXEC_SYNC, &proc2) == 0 );

        wxStringOutputStream procOutput;
        REQUIRE( proc2.GetInputStream() );
        CHECK( proc2.GetInputStream()->Read(procOutput).GetLastError() == wxSTREAM_EOF );

        wxString output = procOutput.GetString();
        CHECK( output.Trim() == "hi" );
    }

    // And now without event dispatching.
    SECTION("Without events")
    {
        wxProcess proc2;
        proc2.Redirect();
        CHECK( wxExecute(COMMAND, wxEXEC_SYNC | wxEXEC_NOEVENTS, &proc2) == 0 );

        wxStringOutputStream procOutput;
        REQUIRE( proc2.GetInputStream() );
        CHECK( proc2.GetInputStream()->Read(procOutput).GetLastError() == wxSTREAM_EOF );

        wxString output = procOutput.GetString();
        CHECK( output.Trim() == "hi" );
    }
}


// This class exits the event loop associated with it when the child process
// terminates.
class TestAsyncProcess : public wxProcess
{
public:
    explicit TestAsyncProcess()
    {
    }

    // may be overridden to be notified about process termination
    virtual void OnTerminate(int WXUNUSED(pid), int WXUNUSED(status)) override
    {
        wxEventLoop::GetActive()->ScheduleExit();
    }

private:
    wxDECLARE_NO_COPY_CLASS(TestAsyncProcess);
};

TEST_CASE_METHOD(ExecTestCase, "wxExecute::Async", "[exec]")
{
    // Test asynchronous execution with no redirection, just to make sure we
    // get the OnTerminate() call.
    TestAsyncProcess proc;
    AsyncInEventLoop asyncInEventLoop;

    CHECK( asyncInEventLoop.DoExecute(
                       AsyncExec_DontExitLoop,  // proc is expected (inside of its OnTerminate())
                               // to trigger the exit of the event loop.
                       COMMAND_NO_OUTPUT, wxEXEC_ASYNC, &proc) != 0 );
}

void
ExecTestCase::DoTestAsyncRedirect(const wxString& command,
                                  CheckStream check,
                                  const char* expectedContaining)
{
    AsyncInEventLoop asyncInEventLoop;
    TestAsyncProcess proc;

    proc.Redirect();

    CHECK( asyncInEventLoop.DoExecute(
                       AsyncExec_DontExitLoop,  // proc is expected (inside of its OnTerminate())
                               // to trigger the exit of the event loop.
                       command, wxEXEC_ASYNC, &proc) != 0 );

    wxInputStream *streamToCheck = nullptr;
    switch ( check )
    {
        case Check_Stdout:
            streamToCheck = proc.GetInputStream();
            break;

        case Check_Stderr:
            streamToCheck = proc.GetErrorStream();
            break;
    }

    wxTextInputStream tis(*streamToCheck);

    // Check that the first line of output contains what we expect.
    CHECK( tis.ReadLine().Contains(expectedContaining) );
}

TEST_CASE_METHOD(ExecTestCase, "wxExecute::AsyncRedirect", "[exec]")
{
    // Test redirection with reading from the input stream after process termination.
    DoTestAsyncRedirect(COMMAND, Check_Stdout, "hi");

    // Test redirection with reading from the error stream after process termination.
    DoTestAsyncRedirect(COMMAND_STDERR, Check_Stderr, "file");
}

// static
wxString ExecTestCase::CreateSleepFile(const wxString& basename, int seconds)
{
#ifdef __UNIX__
    static const char* const scriptExt = ".sh";

    // The script text is a format string with a single "%d" appearing in it
    // which will be replaced by the number of seconds to sleep below.
    static const char* const scriptText =
        "sleep %d\n"
        "echo " SLEEP_END_STRING "\n";
#elif defined(__WINDOWS__)
    static const char* const scriptExt = ".bat";

    // Notice that we need to ping N+1 times for it to take N seconds as the
    // first ping is sent out immediately, without waiting a second.
    static const char* const scriptText =
        "@ ping 127.0.0.1 -n 1 > nul\n"
        "@ ping 127.0.0.1 -n %d > nul\n"
        "@ echo " SLEEP_END_STRING "\n";
#else
    #error "Need code to create sleep file for this platform"
#endif

    const wxString fnSleep = wxFileName(".", basename, scriptExt).GetFullPath();

    wxFile fileSleep;
    REQUIRE
    (
        fileSleep.Create(fnSleep, true, wxS_IRUSR | wxS_IWUSR | wxS_IXUSR)
    );

    fileSleep.Write(wxString::Format(scriptText, seconds));

    return fnSleep;
}

// static
wxString ExecTestCase::MakeShellCommand(const wxString& filename)
{
    wxString command;

#ifdef __UNIX__
    command = "/bin/sh " + filename;
#elif defined(__WINDOWS__)
    command = wxString::Format("cmd.exe /c \"%s\"", filename);
#else
    #error "Need to code to launch shell for this platform"
#endif

    return command;
}

TEST_CASE_METHOD(ExecTestCase, "wxExecute::Overlapped", "[exec]")
{
    // Windows Synchronous wxExecute implementation does not currently
    // support overlapped event loops.  It is still using wxYield, which is
    // not nestable.  Therefore, this test would fail in Windows.
    // If someday somebody changes that in Windows, they could use this
    // test to verify it.
    //
    // Because MSW is not yet ready for this test, it may make sense to
    // separate it out to its own test suite, so we could register it under
    // "fixme" for Windows, but a real test for Unix.  But that is more work,
    // so just #ifndefing it here for now.
    //
    // Too bad you can't just register one test case of a test suite as a
    // "fixme".
#ifndef __WINDOWS__
    // Simple helper delaying the call to wxExecute(): instead of running it
    // immediately, it runs it when we re-enter the event loop.
    class DelayedExecuteTimer : public wxTimer
    {
    public:
        DelayedExecuteTimer(const wxString& command, wxArrayString& outputArray)
            : m_command(command),
              m_outputArray(outputArray)
        {
            // The exact delay doesn't matter, anything short enough will do.
            StartOnce(10);
        }

        virtual void Notify() override
        {
            wxExecute(m_command, m_outputArray);
        }

    private:
        wxString m_command;
        wxArrayString& m_outputArray;
    };

    // Create two scripts with one of them taking longer than the other one to
    // execute.
    const wxString shortSleepFile = CreateSleepFile("shortsleep", 1);
    wxON_BLOCK_EXIT1( wxRemoveFile, shortSleepFile );
    const wxString longSleepFile = CreateSleepFile("longsleep", 2);
    wxON_BLOCK_EXIT1( wxRemoveFile, longSleepFile );

    const wxString shortSleepCommand = MakeShellCommand(shortSleepFile);
    const wxString longSleepCommand = MakeShellCommand(longSleepFile);

    // Collect the child process output
    wxArrayString shortSleepOutput,
                  longSleepOutput;

    // Test that launching a process taking a longer time to run while the
    // shorter process is running works, i.e. that our outer wxExecute()
    // doesn't return until both process terminate.
    DelayedExecuteTimer delayLongSleep(longSleepCommand, longSleepOutput);
    wxExecute(shortSleepCommand, shortSleepOutput);
    REQUIRE( !shortSleepOutput.empty() );
    CHECK( shortSleepOutput.Last() == SLEEP_END_STRING );

    REQUIRE( !longSleepOutput.empty() );
    CHECK( longSleepOutput.Last() == SLEEP_END_STRING );

    // And also that, vice versa, running a short-lived child process that both
    // starts and ends while a longer-lived parent process is still running
    // works too.
    DelayedExecuteTimer delayShortSleep(shortSleepCommand, shortSleepOutput);
    wxExecute(longSleepCommand, longSleepOutput);
    REQUIRE( !shortSleepOutput.empty() );
    CHECK( shortSleepOutput.Last() == SLEEP_END_STRING );

    REQUIRE( !longSleepOutput.empty() );
    CHECK( longSleepOutput.Last() == SLEEP_END_STRING );
#endif // !__WINDOWS__
}

#endif // !__WXX11__

#ifdef __UNIX__

// This test is disabled by default because it must be run in French locale,
// i.e. with explicit LC_ALL=fr_FR.UTF-8 and only works with GNU ls, which
// produces the expected output.
TEST_CASE("wxExecute::RedirectUTF8", "[.]")
{
    wxArrayString output;
    REQUIRE( wxExecute("/bin/ls --version", output) == 0 );

    for ( size_t n = 0; n < output.size(); ++n )
    {
        // It seems unlikely that this part of the output will change for GNU
        // ls, so check for its presence as a sign that the program output was
        // decoded correctly.
        if ( output[n].find(wxString::FromUTF8("vous êtes libre")) != wxString::npos )
            return;
    }

    INFO("output was:\n" << wxJoin(output, '\n'));
    FAIL("Expected output fragment not found.");
}

#endif // __UNIX__
