2 * Copyright (c) 2014 Vojtech Horky
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 * Windows-specific functions for test execution via the popen() system call.
35 * Code inspired by Creating a Child Process with Redirected Input and Output:
36 * http://msdn.microsoft.com/en-us/library/ms682499%28VS.85%29.aspx
39 #include "../internal.h"
41 #pragma warning(push, 0)
50 /** Maximum size of stdout we are able to capture. */
51 #define OUTPUT_BUFFER_SIZE 8192
53 /** Maximum command-line length. */
54 #define PCUT_COMMAND_LINE_BUFFER_SIZE 256
56 /** Buffer for assertion and other error messages. */
57 static char error_message_buffer
[OUTPUT_BUFFER_SIZE
];
59 /** Buffer for stdout from the test. */
60 static char extra_output_buffer
[OUTPUT_BUFFER_SIZE
];
62 /** Prepare for a new test.
64 * @param test Test that is about to be run.
66 static void before_test_start(pcut_item_t
*test
) {
67 pcut_report_test_start(test
);
69 memset(error_message_buffer
, 0, OUTPUT_BUFFER_SIZE
);
70 memset(extra_output_buffer
, 0, OUTPUT_BUFFER_SIZE
);
73 /** Report that a certain function failed.
75 * @param test Current test.
76 * @param failed_function_name Name of the failed function.
78 static void report_func_fail(pcut_item_t
*test
, const char *failed_function_name
) {
79 /* TODO: get error description. */
80 pcut_snprintf(error_message_buffer
, OUTPUT_BUFFER_SIZE
- 1,
81 "%s failed: %s.", failed_function_name
, "unknown reason");
82 pcut_report_test_done(test
, PCUT_OUTCOME_INTERNAL_ERROR
, error_message_buffer
, NULL
, NULL
);
85 /** Read full buffer from given file descriptor.
87 * This function exists to overcome the possibility that read() may
88 * not fill the full length of the provided buffer even when EOF is
91 * @param fd Opened file descriptor.
92 * @param buffer Buffer to store data into.
93 * @param buffer_size Size of the @p buffer in bytes.
94 * @return Number of actually read bytes.
96 static size_t read_all(HANDLE fd
, char *buffer
, size_t buffer_size
) {
98 char *buffer_start
= buffer
;
102 okay
= ReadFile(fd
, buffer
, buffer_size
, &actually_read
, NULL
);
106 if (actually_read
> 0) {
107 buffer
+= actually_read
;
108 buffer_size
-= actually_read
;
109 if (buffer_size
== 0) {
113 } while (actually_read
> 0);
114 if (buffer_start
!= buffer
) {
115 if (*(buffer
- 1) == 10) {
120 return buffer
- buffer_start
;
123 struct test_output_data
{
127 size_t output_buffer_size
;
130 static DWORD WINAPI
read_test_output_on_background(LPVOID test_output_data_ptr
) {
131 size_t stderr_size
= 0;
132 struct test_output_data
*test_output_data
= (struct test_output_data
*) test_output_data_ptr
;
134 stderr_size
= read_all(test_output_data
->pipe_stderr
,
135 test_output_data
->output_buffer
,
136 test_output_data
->output_buffer_size
- 1);
137 read_all(test_output_data
->pipe_stdout
,
138 test_output_data
->output_buffer
,
139 test_output_data
->output_buffer_size
- 1 - stderr_size
);
144 /** Run the test as a new process and report the result.
146 * @param self_path Path to itself, that is to current binary.
147 * @param test Test to be run.
149 int pcut_run_test_forking(const char *self_path
, pcut_item_t
*test
) {
150 /* TODO: clean-up if something goes wrong "in the middle" */
156 SECURITY_ATTRIBUTES security_attributes
;
157 HANDLE link_stdout
[2] = { NULL
, NULL
};
158 HANDLE link_stderr
[2] = { NULL
, NULL
};
159 HANDLE link_stdin
[2] = { NULL
, NULL
};
160 PROCESS_INFORMATION process_info
;
161 STARTUPINFO start_info
;
162 char command
[PCUT_COMMAND_LINE_BUFFER_SIZE
];
163 struct test_output_data test_output_data
;
164 HANDLE test_output_thread_reader
;
167 before_test_start(test
);
169 /* Pipe handles are inherited. */
170 security_attributes
.nLength
= sizeof(SECURITY_ATTRIBUTES
);
171 security_attributes
.bInheritHandle
= TRUE
;
172 security_attributes
.lpSecurityDescriptor
= NULL
;
174 /* Create pipe for stdout, make sure it is not inherited. */
175 okay
= CreatePipe(&link_stdout
[0], &link_stdout
[1], &security_attributes
, 0);
177 report_func_fail(test
, "CreatePipe(/* stdout */)");
178 return PCUT_OUTCOME_INTERNAL_ERROR
;
180 okay
= SetHandleInformation(link_stdout
[0], HANDLE_FLAG_INHERIT
, 0);
182 report_func_fail(test
, "SetHandleInformation(/* stdout */)");
183 return PCUT_OUTCOME_INTERNAL_ERROR
;
186 /* Create pipe for stderr, make sure it is not inherited. */
187 okay
= CreatePipe(&link_stderr
[0], &link_stderr
[1], &security_attributes
, 0);
189 report_func_fail(test
, "CreatePipe(/* stderr */)");
190 return PCUT_OUTCOME_INTERNAL_ERROR
;
192 okay
= SetHandleInformation(link_stderr
[0], HANDLE_FLAG_INHERIT
, 0);
194 report_func_fail(test
, "SetHandleInformation(/* stderr */)");
195 return PCUT_OUTCOME_INTERNAL_ERROR
;
198 /* Create pipe for stdin, make sure it is not inherited. */
199 okay
= CreatePipe(&link_stdin
[0], &link_stdin
[1], &security_attributes
, 0);
201 report_func_fail(test
, "CreatePipe(/* stdin */)");
202 return PCUT_OUTCOME_INTERNAL_ERROR
;
204 okay
= SetHandleInformation(link_stdin
[1], HANDLE_FLAG_INHERIT
, 0);
206 report_func_fail(test
, "SetHandleInformation(/* stdin */)");
207 return PCUT_OUTCOME_INTERNAL_ERROR
;
210 /* Prepare information for the child process. */
211 ZeroMemory(&process_info
, sizeof(PROCESS_INFORMATION
));
212 ZeroMemory(&start_info
, sizeof(STARTUPINFO
));
213 start_info
.cb
= sizeof(STARTUPINFO
);
214 start_info
.hStdError
= link_stderr
[1];
215 start_info
.hStdOutput
= link_stdout
[1];
216 start_info
.hStdInput
= link_stdin
[0];
217 start_info
.dwFlags
|= STARTF_USESTDHANDLES
;
219 /* Format the command line. */
220 pcut_snprintf(command
, PCUT_COMMAND_LINE_BUFFER_SIZE
- 1,
221 "\"%s\" -t%d", self_path
, test
->id
);
223 /* Run the process. */
224 okay
= CreateProcess(NULL
, command
, NULL
, NULL
, TRUE
, 0, NULL
, NULL
,
225 &start_info
, &process_info
);
228 report_func_fail(test
, "CreateProcess()");
229 return PCUT_OUTCOME_INTERNAL_ERROR
;
232 // FIXME: kill the process on error
234 /* Close handles to the first thread. */
235 CloseHandle(process_info
.hThread
);
237 /* Close the other ends of the pipes. */
238 okay
= CloseHandle(link_stdout
[1]);
240 report_func_fail(test
, "CloseHandle(/* stdout */)");
241 return PCUT_OUTCOME_INTERNAL_ERROR
;
243 okay
= CloseHandle(link_stderr
[1]);
245 report_func_fail(test
, "CloseHandle(/* stderr */)");
246 return PCUT_OUTCOME_INTERNAL_ERROR
;
248 okay
= CloseHandle(link_stdin
[0]);
250 report_func_fail(test
, "CloseHandle(/* stdin */)");
251 return PCUT_OUTCOME_INTERNAL_ERROR
;
255 * Read data from stdout and stderr.
256 * We need to do this in a separate thread to allow the
257 * time-out to work correctly.
258 * Probably, this can be done with asynchronous I/O but
259 * this works for now pretty well.
261 test_output_data
.pipe_stderr
= link_stderr
[0];
262 test_output_data
.pipe_stdout
= link_stdout
[0];
263 test_output_data
.output_buffer
= extra_output_buffer
;
264 test_output_data
.output_buffer_size
= OUTPUT_BUFFER_SIZE
;
266 test_output_thread_reader
= CreateThread(NULL
, 0,
267 read_test_output_on_background
, &test_output_data
,
270 if (test_output_thread_reader
== NULL
) {
271 report_func_fail(test
, "CreateThread(/* read test stdout */)");
272 return PCUT_OUTCOME_INTERNAL_ERROR
;
275 /* Wait for the process to terminate. */
277 time_out_millis
= pcut_get_test_timeout(test
) * 1000;
278 rc
= WaitForSingleObject(process_info
.hProcess
, time_out_millis
);
279 PCUT_DEBUG("Waiting for test %s (%dms) returned %d.", test
->name
, time_out_millis
, rc
);
280 if (rc
== WAIT_TIMEOUT
) {
281 /* We timed-out: kill the process and wait for its termination again. */
283 okay
= TerminateProcess(process_info
.hProcess
, 5);
285 report_func_fail(test
, "TerminateProcess(/* PROCESS_INFORMATION.hProcess */)");
286 return PCUT_OUTCOME_INTERNAL_ERROR
;
288 rc
= WaitForSingleObject(process_info
.hProcess
, INFINITE
);
290 if (rc
!= WAIT_OBJECT_0
) {
291 report_func_fail(test
, "WaitForSingleObject(/* PROCESS_INFORMATION.hProcess */)");
292 return PCUT_OUTCOME_INTERNAL_ERROR
;
295 /* Get the return code and convert it to outcome. */
296 okay
= GetExitCodeProcess(process_info
.hProcess
, &rc
);
298 report_func_fail(test
, "GetExitCodeProcess()");
299 return PCUT_OUTCOME_INTERNAL_ERROR
;
303 outcome
= PCUT_OUTCOME_PASS
;
304 } else if ((rc
> 0) && (rc
< 10) && !timed_out
) {
305 outcome
= PCUT_OUTCOME_FAIL
;
307 outcome
= PCUT_OUTCOME_INTERNAL_ERROR
;
310 /* Wait for the reader thread (shall be terminated by now). */
311 rc
= WaitForSingleObject(test_output_thread_reader
, INFINITE
);
312 if (rc
!= WAIT_OBJECT_0
) {
313 report_func_fail(test
, "WaitForSingleObject(/* stdout reader thread */)");
314 return PCUT_OUTCOME_INTERNAL_ERROR
;
317 pcut_report_test_done_unparsed(test
, outcome
, extra_output_buffer
, OUTPUT_BUFFER_SIZE
);
322 void pcut_hook_before_test(pcut_item_t
*test
) {
326 * Prevent displaying the dialog informing the user that the
327 * program unexpectedly failed.
329 SetErrorMode(SEM_FAILCRITICALERRORS
| SEM_NOGPFAULTERRORBOX
);