// 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
// ----------------------------------------------------------------------------
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)
};
// 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" );
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__
+}