]>
Commit | Line | Data |
---|---|---|
ca5016d4 FM |
1 | /////////////////////////////////////////////////////////////////////////////// |
2 | // Name: tests/exec/exec.cpp | |
3 | // Purpose: test wxExecute() | |
4 | // Author: Francesco Montorsi | |
5 | // (based on console sample TestExecute() function) | |
6 | // Created: 2009-01-10 | |
7 | // RCS-ID: $Id$ | |
8 | // Copyright: (c) 2009 Francesco Montorsi | |
821d856a VZ |
9 | // (c) 2013 Rob Bresalier, Vadim Zeitlin |
10 | // Licence: wxWindows licence | |
ca5016d4 FM |
11 | /////////////////////////////////////////////////////////////////////////////// |
12 | ||
13 | // ---------------------------------------------------------------------------- | |
14 | // headers | |
15 | // ---------------------------------------------------------------------------- | |
16 | ||
17 | #include "testprec.h" | |
18 | ||
19 | #ifdef __BORLANDC__ | |
20 | #pragma hdrstop | |
21 | #endif | |
22 | ||
23 | #include "wx/utils.h" | |
24 | #include "wx/process.h" | |
25 | #include "wx/sstream.h" | |
d6655d44 VZ |
26 | #include "wx/evtloop.h" |
27 | #include "wx/file.h" | |
28 | #include "wx/filename.h" | |
29 | #include "wx/mstream.h" | |
30 | #include "wx/scopeguard.h" | |
31 | #include "wx/txtstrm.h" | |
32 | #include "wx/timer.h" | |
ca5016d4 FM |
33 | |
34 | #ifdef __UNIX__ | |
35 | #define COMMAND "echo hi" | |
d6655d44 | 36 | #define COMMAND_STDERR "cat nonexistentfile" |
ca5016d4 | 37 | #define ASYNC_COMMAND "xclock" |
561ff470 VZ |
38 | #define SHELL_COMMAND "echo hi from shell>/dev/null" |
39 | #define COMMAND_NO_OUTPUT "echo -n" | |
bb5a9514 | 40 | #elif defined(__WINDOWS__) |
ca5016d4 | 41 | #define COMMAND "cmd.exe /c \"echo hi\"" |
d6655d44 | 42 | #define COMMAND_STDERR "cmd.exe /c \"type nonexistentfile\"" |
ca5016d4 | 43 | #define ASYNC_COMMAND "notepad" |
561ff470 VZ |
44 | #define SHELL_COMMAND "echo hi > nul:" |
45 | #define COMMAND_NO_OUTPUT COMMAND " > nul:" | |
ca5016d4 FM |
46 | #else |
47 | #error "no command to exec" | |
48 | #endif // OS | |
49 | ||
d6655d44 VZ |
50 | #define SLEEP_END_STRING "Done sleeping" |
51 | ||
5737bab7 VZ |
52 | namespace |
53 | { | |
54 | enum AsyncExecLoopExitEnum | |
55 | { | |
56 | AsyncExec_DontExitLoop, | |
57 | AsyncExec_ExitLoop | |
58 | }; | |
59 | } // anonymous namespace | |
60 | ||
ca5016d4 FM |
61 | // ---------------------------------------------------------------------------- |
62 | // test class | |
63 | // ---------------------------------------------------------------------------- | |
64 | ||
65 | class ExecTestCase : public CppUnit::TestCase | |
66 | { | |
67 | public: | |
68 | ExecTestCase() { } | |
69 | ||
70 | private: | |
71 | CPPUNIT_TEST_SUITE( ExecTestCase ); | |
72 | CPPUNIT_TEST( TestShell ); | |
73 | CPPUNIT_TEST( TestExecute ); | |
74 | CPPUNIT_TEST( TestProcess ); | |
d6655d44 VZ |
75 | CPPUNIT_TEST( TestAsync ); |
76 | CPPUNIT_TEST( TestAsyncRedirect ); | |
77 | CPPUNIT_TEST( TestOverlappedSyncExecute ); | |
ca5016d4 FM |
78 | CPPUNIT_TEST_SUITE_END(); |
79 | ||
80 | void TestShell(); | |
81 | void TestExecute(); | |
82 | void TestProcess(); | |
d6655d44 VZ |
83 | void TestAsync(); |
84 | void TestAsyncRedirect(); | |
85 | void TestOverlappedSyncExecute(); | |
86 | ||
87 | // Helper: create an executable file sleeping for the given amount of | |
88 | // seconds with the specified base name. | |
89 | // | |
90 | // Returns the name of the file. | |
91 | static wxString CreateSleepFile(const wxString& basename, int seconds); | |
92 | ||
93 | // Return the full command, to be passed to wxExecute(), launching the | |
94 | // specified script file. | |
95 | static wxString MakeShellCommand(const wxString& filename); | |
96 | ||
97 | ||
98 | // Helper of TestAsyncRedirect(): tests that the output of the given | |
99 | // command on the given stream contains the expected string. | |
100 | enum CheckStream { Check_Stdout, Check_Stderr }; | |
101 | ||
102 | void DoTestAsyncRedirect(const wxString& command, | |
103 | CheckStream check, | |
104 | const char* expectedContaining); | |
105 | ||
d6655d44 VZ |
106 | // This class is used as a helper in order to run wxExecute(ASYNC) |
107 | // inside of an event loop. | |
108 | class AsyncInEventLoop : public wxTimer | |
109 | { | |
110 | public: | |
111 | AsyncInEventLoop() { } | |
112 | ||
113 | long DoExecute(AsyncExecLoopExitEnum forceExitLoop_, | |
114 | const wxString& command_, | |
115 | int flags_ = wxEXEC_ASYNC, | |
116 | wxProcess* callback_ = NULL) | |
117 | { | |
118 | forceExitLoop = forceExitLoop_; | |
119 | command = command_; | |
120 | flags = flags_; | |
121 | callback = callback_; | |
122 | ||
123 | wxEventLoop loop; | |
124 | ||
125 | // Trigger the timer to go off inside the event loop | |
126 | // so that we can run wxExecute there. | |
127 | StartOnce(10); | |
128 | ||
129 | // Run the event loop. | |
130 | loop.Run(); | |
131 | ||
132 | return wxExecuteReturnCode; | |
133 | } | |
134 | ||
135 | void Notify() | |
136 | { | |
137 | // Run wxExecute inside the event loop. | |
138 | wxExecuteReturnCode = wxExecute(command, flags, callback); | |
139 | ||
140 | if (forceExitLoop == AsyncExec_ExitLoop) | |
141 | { | |
142 | wxEventLoop::GetActive()->Exit(); | |
143 | } | |
144 | } | |
145 | ||
146 | private: | |
147 | AsyncExecLoopExitEnum forceExitLoop; | |
148 | wxString command; | |
149 | int flags; | |
150 | wxProcess* callback; | |
151 | long wxExecuteReturnCode; | |
152 | }; | |
ca5016d4 FM |
153 | |
154 | DECLARE_NO_COPY_CLASS(ExecTestCase) | |
155 | }; | |
156 | ||
157 | // register in the unnamed registry so that these tests are run by default | |
158 | CPPUNIT_TEST_SUITE_REGISTRATION( ExecTestCase ); | |
159 | ||
e3778b4d | 160 | // also include in its own registry so that these tests can be run alone |
ca5016d4 FM |
161 | CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( ExecTestCase, "ExecTestCase" ); |
162 | ||
163 | ||
164 | void ExecTestCase::TestShell() | |
165 | { | |
166 | CPPUNIT_ASSERT( wxShell(SHELL_COMMAND) ); | |
167 | } | |
168 | ||
169 | void ExecTestCase::TestExecute() | |
170 | { | |
d6655d44 | 171 | AsyncInEventLoop asyncInEventLoop; |
ca5016d4 FM |
172 | |
173 | // test asynch exec | |
d6655d44 VZ |
174 | // |
175 | // asyncInEventLoop.DoExecute will perform the | |
176 | // call to wxExecute(ASYNC) in an event loop, as required by | |
177 | // console test (and this same event loop will also | |
178 | // be used in GUI test too, even though not required, just to have | |
179 | // common code). | |
180 | long pid = asyncInEventLoop.DoExecute(AsyncExec_ExitLoop, // Force exit of event loop right | |
181 | // after the call to wxExecute() | |
182 | ASYNC_COMMAND, wxEXEC_ASYNC); | |
ca5016d4 | 183 | CPPUNIT_ASSERT( pid != 0 ); |
3162be2b FM |
184 | |
185 | // NOTE: under Windows the first wxKill() invocation with wxSIGTERM | |
186 | // may fail if the system is fast and the ASYNC_COMMAND app | |
187 | // doesn't manage to create its HWND before our wxKill() is | |
561ff470 | 188 | // executed; in that case we "fall back" to the second invocation |
3162be2b FM |
189 | // with wxSIGKILL (which should always succeed) |
190 | CPPUNIT_ASSERT( wxKill(pid, wxSIGTERM) == 0 || | |
191 | wxKill(pid, wxSIGKILL) == 0 ); | |
ca5016d4 | 192 | |
d6655d44 VZ |
193 | int useNoeventsFlag; |
194 | ||
195 | // Test the sync execution case with/without wxEXEC_NOEVENTS flag | |
196 | // because we use either an event loop or wxSelectDispatcher | |
197 | // depending on this flag, and we want to test both cases. | |
198 | for (useNoeventsFlag = 0; useNoeventsFlag <=1 ; ++useNoeventsFlag ) | |
199 | { | |
200 | int execFlags = wxEXEC_SYNC; | |
201 | ||
202 | if (useNoeventsFlag) | |
203 | { | |
204 | execFlags |= wxEXEC_NOEVENTS; | |
205 | } | |
206 | ||
207 | // test sync exec (with a command not producing any output to avoid | |
208 | // interfering with the test): | |
209 | CPPUNIT_ASSERT( wxExecute(COMMAND_NO_OUTPUT, execFlags) == 0 ); | |
210 | ||
211 | // test running COMMAND again, but this time with redirection: | |
212 | // and the expected data is on stdout. | |
213 | wxArrayString stdout_arr; | |
214 | CPPUNIT_ASSERT_EQUAL( 0, wxExecute(COMMAND, stdout_arr, execFlags) ); | |
215 | CPPUNIT_ASSERT_EQUAL( "hi", stdout_arr[0] ); | |
216 | ||
217 | // test running COMMAND_STDERR with redirection and the expected data | |
218 | // is on stderr. | |
219 | wxArrayString stderr_arr; | |
220 | stdout_arr.Empty(); | |
221 | CPPUNIT_ASSERT( wxExecute(COMMAND_STDERR, stdout_arr, stderr_arr, execFlags) != 0 ); | |
222 | ||
223 | // Check that there is something on stderr. | |
224 | // In Unix systems, the 'cat' command has the name of the file it could not | |
225 | // find in the error output. | |
226 | // In Windows, the 'type' command outputs the following when it can't find | |
227 | // a file: | |
228 | // "The system cannot find the file specified" | |
229 | // In both cases, we expect the word 'file' to be in the stderr. | |
230 | CPPUNIT_ASSERT( stderr_arr[0].Contains("file") ); | |
231 | } | |
ca5016d4 FM |
232 | } |
233 | ||
234 | void ExecTestCase::TestProcess() | |
235 | { | |
d6655d44 VZ |
236 | AsyncInEventLoop asyncInEventLoop; |
237 | ||
ca5016d4 FM |
238 | // test wxExecute with wxProcess |
239 | wxProcess *proc = new wxProcess; | |
d6655d44 VZ |
240 | |
241 | // asyncInEventLoop.DoExecute will perform the | |
242 | // call to wxExecute(ASYNC) in an event loop, as required by | |
243 | // console test (and this same event loop will also | |
244 | // be used in GUI test too, even though not required, just to have | |
245 | // common code). | |
246 | long pid = asyncInEventLoop.DoExecute(AsyncExec_ExitLoop, // Force exit of event loop right | |
247 | // after the call to wxExecute() | |
248 | ASYNC_COMMAND, wxEXEC_ASYNC, proc); | |
ca5016d4 | 249 | CPPUNIT_ASSERT( proc->GetPid() == pid && pid != 0 ); |
561ff470 | 250 | |
ca5016d4 FM |
251 | // we're not going to process the wxEVT_END_PROCESS event, |
252 | // so the proc instance will auto-delete itself after we kill | |
253 | // the asynch process: | |
3162be2b FM |
254 | CPPUNIT_ASSERT( wxKill(pid, wxSIGTERM) == 0 || |
255 | wxKill(pid, wxSIGKILL) == 0 ); | |
ca5016d4 | 256 | |
561ff470 | 257 | |
ca5016d4 | 258 | // test wxExecute with wxProcess and REDIRECTION |
561ff470 | 259 | |
d6655d44 VZ |
260 | // Test the sync execution case with/without wxEXEC_NOEVENTS flag |
261 | // because we use either an event loop or wxSelectDispatcher | |
262 | // depending on this flag, and we want to test both cases. | |
263 | ||
264 | // First the default case, dispatching the events while waiting. | |
265 | { | |
266 | wxProcess proc2; | |
267 | proc2.Redirect(); | |
268 | CPPUNIT_ASSERT_EQUAL( 0, wxExecute(COMMAND, wxEXEC_SYNC, &proc2) ); | |
269 | ||
270 | wxStringOutputStream procOutput; | |
271 | CPPUNIT_ASSERT( proc2.GetInputStream() ); | |
272 | CPPUNIT_ASSERT_EQUAL( wxSTREAM_EOF, | |
273 | proc2.GetInputStream()->Read(procOutput).GetLastError() ); | |
274 | ||
275 | wxString output = procOutput.GetString(); | |
276 | CPPUNIT_ASSERT_EQUAL( "hi", output.Trim() ); | |
277 | } | |
278 | ||
279 | // And now without event dispatching. | |
280 | { | |
281 | wxProcess proc2; | |
282 | proc2.Redirect(); | |
283 | CPPUNIT_ASSERT_EQUAL( 0, | |
284 | wxExecute(COMMAND, wxEXEC_SYNC | wxEXEC_NOEVENTS, &proc2) ); | |
285 | ||
286 | wxStringOutputStream procOutput; | |
287 | CPPUNIT_ASSERT( proc2.GetInputStream() ); | |
288 | CPPUNIT_ASSERT_EQUAL( wxSTREAM_EOF, | |
289 | proc2.GetInputStream()->Read(procOutput).GetLastError() ); | |
290 | ||
291 | wxString output = procOutput.GetString(); | |
292 | CPPUNIT_ASSERT_EQUAL( "hi", output.Trim() ); | |
293 | } | |
294 | } | |
295 | ||
296 | ||
297 | // This class exits the event loop associated with it when the child process | |
298 | // terminates. | |
299 | class TestAsyncProcess : public wxProcess | |
300 | { | |
301 | public: | |
302 | wxEXPLICIT TestAsyncProcess() | |
303 | { | |
304 | } | |
305 | ||
306 | // may be overridden to be notified about process termination | |
307 | virtual void OnTerminate(int WXUNUSED(pid), int WXUNUSED(status)) | |
308 | { | |
309 | wxEventLoop::GetActive()->ScheduleExit(); | |
310 | } | |
311 | ||
312 | private: | |
313 | wxDECLARE_NO_COPY_CLASS(TestAsyncProcess); | |
314 | }; | |
561ff470 | 315 | |
d6655d44 VZ |
316 | void ExecTestCase::TestAsync() |
317 | { | |
318 | // Test asynchronous execution with no redirection, just to make sure we | |
319 | // get the OnTerminate() call. | |
320 | TestAsyncProcess proc; | |
321 | AsyncInEventLoop asyncInEventLoop; | |
322 | ||
323 | CPPUNIT_ASSERT( asyncInEventLoop.DoExecute( | |
324 | AsyncExec_DontExitLoop, // proc is expected (inside of its OnTerminate()) | |
325 | // to trigger the exit of the event loop. | |
326 | COMMAND_NO_OUTPUT, wxEXEC_ASYNC, &proc) != 0 ); | |
ca5016d4 FM |
327 | } |
328 | ||
d6655d44 VZ |
329 | void |
330 | ExecTestCase::DoTestAsyncRedirect(const wxString& command, | |
331 | CheckStream check, | |
332 | const char* expectedContaining) | |
333 | { | |
334 | AsyncInEventLoop asyncInEventLoop; | |
335 | TestAsyncProcess proc; | |
336 | ||
337 | proc.Redirect(); | |
338 | ||
339 | CPPUNIT_ASSERT( asyncInEventLoop.DoExecute( | |
340 | AsyncExec_DontExitLoop, // proc is expected (inside of its OnTerminate()) | |
341 | // to trigger the exit of the event loop. | |
342 | command, wxEXEC_ASYNC, &proc) != 0 ); | |
343 | ||
344 | wxInputStream *streamToCheck = NULL; | |
345 | switch ( check ) | |
346 | { | |
347 | case Check_Stdout: | |
348 | streamToCheck = proc.GetInputStream(); | |
349 | break; | |
350 | ||
351 | case Check_Stderr: | |
352 | streamToCheck = proc.GetErrorStream(); | |
353 | break; | |
354 | } | |
355 | ||
356 | wxTextInputStream tis(*streamToCheck); | |
357 | ||
358 | // Check that the first line of output contains what we expect. | |
359 | CPPUNIT_ASSERT( tis.ReadLine().Contains(expectedContaining) ); | |
360 | } | |
361 | ||
362 | void ExecTestCase::TestAsyncRedirect() | |
363 | { | |
364 | // Test redirection with reading from the input stream after process termination. | |
365 | DoTestAsyncRedirect(COMMAND, Check_Stdout, "hi"); | |
366 | ||
367 | // Test redirection with reading from the error stream after process termination. | |
368 | DoTestAsyncRedirect(COMMAND_STDERR, Check_Stderr, "file"); | |
369 | } | |
370 | ||
371 | // static | |
372 | wxString ExecTestCase::CreateSleepFile(const wxString& basename, int seconds) | |
373 | { | |
374 | #ifdef __UNIX__ | |
375 | static const char* const scriptExt = ".sh"; | |
376 | ||
377 | // The script text is a format string with a single "%d" appearing in it | |
378 | // which will be replaced by the number of seconds to sleep below. | |
379 | static const char* const scriptText = | |
380 | "sleep %d\n" | |
381 | "echo " SLEEP_END_STRING "\n"; | |
382 | #elif defined(__WINDOWS__) | |
383 | static const char* const scriptExt = ".bat"; | |
384 | ||
385 | // Notice that we need to ping N+1 times for it to take N seconds as the | |
386 | // first ping is sent out immediately, without waiting a second. | |
387 | static const char* const scriptText = | |
388 | "@ ping 127.0.0.1 -n 1 > nul\n" | |
389 | "@ ping 127.0.0.1 -n %d > nul\n" | |
390 | "@ echo " SLEEP_END_STRING "\n"; | |
391 | #else | |
392 | #error "Need code to create sleep file for this platform" | |
393 | #endif | |
394 | ||
395 | const wxString fnSleep = wxFileName(".", basename, scriptExt).GetFullPath(); | |
396 | ||
397 | wxFile fileSleep; | |
398 | CPPUNIT_ASSERT | |
399 | ( | |
400 | fileSleep.Create(fnSleep, true, wxS_IRUSR | wxS_IWUSR | wxS_IXUSR) | |
401 | ); | |
402 | ||
403 | fileSleep.Write(wxString::Format(scriptText, seconds)); | |
404 | ||
405 | return fnSleep; | |
406 | } | |
407 | ||
408 | // static | |
409 | wxString ExecTestCase::MakeShellCommand(const wxString& filename) | |
410 | { | |
411 | wxString command; | |
412 | ||
413 | #ifdef __UNIX__ | |
414 | command = "/bin/sh " + filename; | |
415 | #elif defined(__WINDOWS__) | |
416 | command = wxString::Format("cmd.exe /c \"%s\"", filename); | |
417 | #else | |
418 | #error "Need to code to launch shell for this platform" | |
419 | #endif | |
420 | ||
421 | return command; | |
422 | } | |
423 | ||
424 | void ExecTestCase::TestOverlappedSyncExecute() | |
425 | { | |
426 | // Windows Synchronous wxExecute implementation does not currently | |
427 | // support overlapped event loops. It is still using wxYield, which is | |
428 | // not nestable. Therefore, this test would fail in Windows. | |
429 | // If someday somebody changes that in Windows, they could use this | |
430 | // test to verify it. | |
431 | // | |
432 | // Because MSW is not yet ready for this test, it may make sense to | |
433 | // separate it out to its own test suite, so we could register it under | |
434 | // "fixme" for Windows, but a real test for Unix. But that is more work, | |
435 | // so just #ifndefing it here for now. | |
436 | // | |
437 | // Too bad you can't just register one test case of a test suite as a | |
438 | // "fixme". | |
431b8364 | 439 | #ifndef __WINDOWS__ |
d6655d44 VZ |
440 | // Simple helper delaying the call to wxExecute(): instead of running it |
441 | // immediately, it runs it when we re-enter the event loop. | |
442 | class DelayedExecuteTimer : public wxTimer | |
443 | { | |
444 | public: | |
445 | DelayedExecuteTimer(const wxString& command, wxArrayString& outputArray) | |
446 | : m_command(command), | |
447 | m_outputArray(outputArray) | |
448 | { | |
449 | // The exact delay doesn't matter, anything short enough will do. | |
450 | StartOnce(10); | |
451 | } | |
452 | ||
453 | virtual void Notify() | |
454 | { | |
455 | wxExecute(m_command, m_outputArray); | |
456 | } | |
457 | ||
458 | private: | |
459 | wxString m_command; | |
460 | wxArrayString& m_outputArray; | |
461 | }; | |
462 | ||
463 | // Create two scripts with one of them taking longer than the other one to | |
464 | // execute. | |
465 | const wxString shortSleepFile = CreateSleepFile("shortsleep", 1); | |
466 | wxON_BLOCK_EXIT1( wxRemoveFile, shortSleepFile ); | |
467 | const wxString longSleepFile = CreateSleepFile("longsleep", 2); | |
468 | wxON_BLOCK_EXIT1( wxRemoveFile, longSleepFile ); | |
469 | ||
470 | const wxString shortSleepCommand = MakeShellCommand(shortSleepFile); | |
471 | const wxString longSleepCommand = MakeShellCommand(longSleepFile); | |
472 | ||
473 | // Collect the child process output | |
474 | wxArrayString shortSleepOutput, | |
475 | longSleepOutput; | |
476 | ||
477 | // Test that launching a process taking a longer time to run while the | |
478 | // shorter process is running works, i.e. that our outer wxExecute() | |
479 | // doesn't return until both process terminate. | |
480 | DelayedExecuteTimer delayLongSleep(longSleepCommand, longSleepOutput); | |
481 | wxExecute(shortSleepCommand, shortSleepOutput); | |
482 | CPPUNIT_ASSERT( !shortSleepOutput.empty() ); | |
483 | CPPUNIT_ASSERT_EQUAL( SLEEP_END_STRING, shortSleepOutput.Last() ); | |
484 | ||
485 | CPPUNIT_ASSERT( !longSleepOutput.empty() ); | |
486 | CPPUNIT_ASSERT_EQUAL( SLEEP_END_STRING, longSleepOutput.Last() ); | |
487 | ||
488 | // And also that, vice versa, running a short-lived child process that both | |
489 | // starts and ends while a longer-lived parent process is still running | |
490 | // works too. | |
491 | DelayedExecuteTimer delayShortSleep(shortSleepCommand, shortSleepOutput); | |
492 | wxExecute(longSleepCommand, longSleepOutput); | |
493 | CPPUNIT_ASSERT( !shortSleepOutput.empty() ); | |
494 | CPPUNIT_ASSERT_EQUAL( SLEEP_END_STRING, shortSleepOutput.Last() ); | |
495 | ||
496 | CPPUNIT_ASSERT( !longSleepOutput.empty() ); | |
497 | CPPUNIT_ASSERT_EQUAL( SLEEP_END_STRING, longSleepOutput.Last() ); | |
431b8364 | 498 | #endif // !__WINDOWS__ |
d6655d44 | 499 | } |