]> git.saurik.com Git - wxWidgets.git/blobdiff - tests/exec/exec.cpp
Make wxComboCtrlBase::Set*groundColour() methods public.
[wxWidgets.git] / tests / exec / exec.cpp
index b0f152ac3d253a3e0dd1242553f593162a9c4515..620fa9a3696390a8d0d5d7e47c533bd8d60ee2a8 100644 (file)
@@ -4,8 +4,9 @@
 // Author:      Francesco Montorsi
 //              (based on console sample TestExecute() function)
 // Created:     2009-01-10
-// RCS-ID:      $Id$
 // Copyright:   (c) 2009 Francesco Montorsi
+//              (c) 2013 Rob Bresalier, Vadim Zeitlin
+// Licence:     wxWindows licence
 ///////////////////////////////////////////////////////////////////////////////
 
 // ----------------------------------------------------------------------------
 #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"
 
 #ifdef __UNIX__
     #define COMMAND "echo hi"
+    #define COMMAND_STDERR "cat nonexistentfile"
     #define ASYNC_COMMAND "xclock"
-    #define SHELL_COMMAND "echo hi from shell"
-    #define REDIRECT_COMMAND "cat -n Makefile"
-#elif defined(__WXMSW__)
+    #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 "notepad"
-    #define SHELL_COMMAND "echo hi"
-    #define REDIRECT_COMMAND COMMAND
+    #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
+
 // ----------------------------------------------------------------------------
 // test class
 // ----------------------------------------------------------------------------
@@ -50,11 +71,84 @@ private:
         CPPUNIT_TEST( TestShell );
         CPPUNIT_TEST( TestExecute );
         CPPUNIT_TEST( TestProcess );
+        CPPUNIT_TEST( TestAsync );
+        CPPUNIT_TEST( TestAsyncRedirect );
+        CPPUNIT_TEST( TestOverlappedSyncExecute );
     CPPUNIT_TEST_SUITE_END();
 
     void TestShell();
     void TestExecute();
     void TestProcess();
+    void TestAsync();
+    void TestAsyncRedirect();
+    void TestOverlappedSyncExecute();
+
+    // 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_ = NULL)
+        {
+            forceExitLoop = forceExitLoop_;
+            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()
+        {
+            // 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;
+    };
 
     DECLARE_NO_COPY_CLASS(ExecTestCase)
 };
@@ -62,7 +156,7 @@ private:
 // register in the unnamed registry so that these tests are run by default
 CPPUNIT_TEST_SUITE_REGISTRATION( ExecTestCase );
 
-// also include in it's own registry so that these tests can be run alone
+// also include in its own registry so that these tests can be run alone
 CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( ExecTestCase, "ExecTestCase" );
 
 
@@ -73,52 +167,332 @@ void ExecTestCase::TestShell()
 
 void ExecTestCase::TestExecute()
 {
-    // test sync exec:
-    CPPUNIT_ASSERT( wxExecute(COMMAND, wxEXEC_SYNC) == 0 );
+    AsyncInEventLoop asyncInEventLoop;
 
     // test asynch exec
-    long pid = wxExecute(ASYNC_COMMAND, wxEXEC_ASYNC);
+    //
+    // 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);
     CPPUNIT_ASSERT( pid != 0 );
 
     // NOTE: under Windows the first wxKill() invocation with wxSIGTERM
     //       may fail if the system is fast and the ASYNC_COMMAND app
     //       doesn't manage to create its HWND before our wxKill() is
-    //       executed; in that case we "fall back" to the second invocation 
+    //       executed; in that case we "fall back" to the second invocation
     //       with wxSIGKILL (which should always succeed)
     CPPUNIT_ASSERT( wxKill(pid, wxSIGTERM) == 0 ||
                     wxKill(pid, wxSIGKILL) == 0 );
 
-    // test running COMMAND again, but this time with redirection:
-    wxArrayString stdout_arr;
-    CPPUNIT_ASSERT( wxExecute(COMMAND, stdout_arr, wxEXEC_SYNC) == 0 );
-    CPPUNIT_ASSERT( stdout_arr[0] == "hi" );
+    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)
+        {
+            execFlags |= wxEXEC_NOEVENTS;
+        }
+
+        // test sync exec (with a command not producing any output to avoid
+        // interfering with the test):
+        CPPUNIT_ASSERT( 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;
+        CPPUNIT_ASSERT_EQUAL( 0, wxExecute(COMMAND, stdout_arr, execFlags) );
+        CPPUNIT_ASSERT_EQUAL( "hi", stdout_arr[0] );
+
+        // test running COMMAND_STDERR with redirection and the expected data
+        // is on stderr.
+        wxArrayString stderr_arr;
+        stdout_arr.Empty();
+        CPPUNIT_ASSERT( 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.
+        CPPUNIT_ASSERT( stderr_arr[0].Contains("file") );
+    }
 }
 
 void ExecTestCase::TestProcess()
 {
+    AsyncInEventLoop asyncInEventLoop;
+
     // test wxExecute with wxProcess
     wxProcess *proc = new wxProcess;
-    long pid = wxExecute(ASYNC_COMMAND, wxEXEC_ASYNC, proc);
+
+    // 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);
     CPPUNIT_ASSERT( proc->GetPid() == pid && pid != 0 );
-    
+
     // 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:
     CPPUNIT_ASSERT( wxKill(pid, wxSIGTERM) == 0 ||
                     wxKill(pid, wxSIGKILL) == 0 );
 
-    
+
     // test wxExecute with wxProcess and REDIRECTION
-    wxProcess *proc2 = new wxProcess;
-    proc2->Redirect();
-    CPPUNIT_ASSERT( wxExecute(COMMAND, wxEXEC_SYNC, proc2) == 0 );
-    
-    wxStringOutputStream stdout_stream;
-    CPPUNIT_ASSERT( proc2->GetInputStream() );
-    CPPUNIT_ASSERT_EQUAL( wxSTREAM_EOF,
-        proc2->GetInputStream()->Read(stdout_stream).GetLastError() );
-    
-    wxString str(stdout_stream.GetString());
-    CPPUNIT_ASSERT_EQUAL( "hi", str.Trim() );
+
+    // 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.
+    {
+        wxProcess proc2;
+        proc2.Redirect();
+        CPPUNIT_ASSERT_EQUAL( 0, wxExecute(COMMAND, wxEXEC_SYNC, &proc2) );
+
+        wxStringOutputStream procOutput;
+        CPPUNIT_ASSERT( proc2.GetInputStream() );
+        CPPUNIT_ASSERT_EQUAL( wxSTREAM_EOF,
+            proc2.GetInputStream()->Read(procOutput).GetLastError() );
+
+        wxString output = procOutput.GetString();
+        CPPUNIT_ASSERT_EQUAL( "hi", output.Trim() );
+    }
+
+    // And now without event dispatching.
+    {
+        wxProcess proc2;
+        proc2.Redirect();
+        CPPUNIT_ASSERT_EQUAL( 0,
+            wxExecute(COMMAND, wxEXEC_SYNC | wxEXEC_NOEVENTS, &proc2) );
+
+        wxStringOutputStream procOutput;
+        CPPUNIT_ASSERT( proc2.GetInputStream() );
+        CPPUNIT_ASSERT_EQUAL( wxSTREAM_EOF,
+            proc2.GetInputStream()->Read(procOutput).GetLastError() );
+
+        wxString output = procOutput.GetString();
+        CPPUNIT_ASSERT_EQUAL( "hi", output.Trim() );
+    }
+}
+
+
+// This class exits the event loop associated with it when the child process
+// terminates.
+class TestAsyncProcess : public wxProcess
+{
+public:
+    wxEXPLICIT TestAsyncProcess()
+    {
+    }
+
+    // may be overridden to be notified about process termination
+    virtual void OnTerminate(int WXUNUSED(pid), int WXUNUSED(status))
+    {
+        wxEventLoop::GetActive()->ScheduleExit();
+    }
+
+private:
+    wxDECLARE_NO_COPY_CLASS(TestAsyncProcess);
+};
+
+void ExecTestCase::TestAsync()
+{
+    // Test asynchronous execution with no redirection, just to make sure we
+    // get the OnTerminate() call.
+    TestAsyncProcess proc;
+    AsyncInEventLoop asyncInEventLoop;
+
+    CPPUNIT_ASSERT( 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();
+
+    CPPUNIT_ASSERT( 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 = NULL;
+    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.
+    CPPUNIT_ASSERT( tis.ReadLine().Contains(expectedContaining) );
 }
 
+void ExecTestCase::TestAsyncRedirect()
+{
+    // 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;
+    CPPUNIT_ASSERT
+    (
+        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;
+}
+
+void ExecTestCase::TestOverlappedSyncExecute()
+{
+    // 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()
+        {
+            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);
+    CPPUNIT_ASSERT( !shortSleepOutput.empty() );
+    CPPUNIT_ASSERT_EQUAL( SLEEP_END_STRING, shortSleepOutput.Last() );
+
+    CPPUNIT_ASSERT( !longSleepOutput.empty() );
+    CPPUNIT_ASSERT_EQUAL( SLEEP_END_STRING, longSleepOutput.Last() );
+
+    // 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);
+    CPPUNIT_ASSERT( !shortSleepOutput.empty() );
+    CPPUNIT_ASSERT_EQUAL( SLEEP_END_STRING, shortSleepOutput.Last() );
+
+    CPPUNIT_ASSERT( !longSleepOutput.empty() );
+    CPPUNIT_ASSERT_EQUAL( SLEEP_END_STRING, longSleepOutput.Last() );
+#endif // !__WINDOWS__
+}