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