source: mainline/uspace/lib/pcut/src/os/windows.c@ c631734

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since c631734 was 9b20126, checked in by Vojtech Horky <vojtechhorky@…>, 11 years ago

Update PCUT to newest version

  • Property mode set to 100644
File size: 9.9 KB
Line 
1/*
2 * Copyright (c) 2014 Vojtech Horky
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
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.
16 *
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.
27 */
28
29/** @file
30 *
31 * Windows-specific functions for test execution via the popen() system call.
32 */
33
34/*
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
37 */
38
39#include "../internal.h"
40
41#include <windows.h>
42#include <tchar.h>
43#include <stdio.h>
44#include <strsafe.h>
45
46
47/** Maximum size of stdout we are able to capture. */
48#define OUTPUT_BUFFER_SIZE 8192
49
50/** Maximum command-line length. */
51#define PCUT_COMMAND_LINE_BUFFER_SIZE 256
52
53/** Buffer for assertion and other error messages. */
54static char error_message_buffer[OUTPUT_BUFFER_SIZE];
55
56/** Buffer for stdout from the test. */
57static char extra_output_buffer[OUTPUT_BUFFER_SIZE];
58
59/** Prepare for a new test.
60 *
61 * @param test Test that is about to be run.
62 */
63static void before_test_start(pcut_item_t *test) {
64 pcut_report_test_start(test);
65
66 memset(error_message_buffer, 0, OUTPUT_BUFFER_SIZE);
67 memset(extra_output_buffer, 0, OUTPUT_BUFFER_SIZE);
68}
69
70/** Report that a certain function failed.
71 *
72 * @param test Current test.
73 * @param failed_function_name Name of the failed function.
74 */
75static void report_func_fail(pcut_item_t *test, const char *failed_function_name) {
76 /* TODO: get error description. */
77 sprintf_s(error_message_buffer, OUTPUT_BUFFER_SIZE - 1,
78 "%s failed: %s.", failed_function_name, "unknown reason");
79 pcut_report_test_done(test, TEST_OUTCOME_ERROR, error_message_buffer, NULL, NULL);
80}
81
82/** Read full buffer from given file descriptor.
83 *
84 * This function exists to overcome the possibility that read() may
85 * not fill the full length of the provided buffer even when EOF is
86 * not reached.
87 *
88 * @param fd Opened file descriptor.
89 * @param buffer Buffer to store data into.
90 * @param buffer_size Size of the @p buffer in bytes.
91 * @return Number of actually read bytes.
92 */
93static size_t read_all(HANDLE fd, char *buffer, size_t buffer_size) {
94 DWORD actually_read;
95 char *buffer_start = buffer;
96 BOOL okay = FALSE;
97
98 do {
99 okay = ReadFile(fd, buffer, buffer_size, &actually_read, NULL);
100 if (!okay) {
101 break;
102 }
103 if (actually_read > 0) {
104 buffer += actually_read;
105 buffer_size -= actually_read;
106 if (buffer_size == 0) {
107 break;
108 }
109 }
110 } while (actually_read > 0);
111 if (buffer_start != buffer) {
112 if (*(buffer - 1) == 10) {
113 *(buffer - 1) = 0;
114 buffer--;
115 }
116 }
117 return buffer - buffer_start;
118}
119
120struct test_output_data {
121 HANDLE pipe_stdout;
122 HANDLE pipe_stderr;
123 char *output_buffer;
124 size_t output_buffer_size;
125};
126
127static DWORD WINAPI read_test_output_on_background(LPVOID test_output_data_ptr) {
128 size_t stderr_size = 0;
129 struct test_output_data *test_output_data = (struct test_output_data *) test_output_data_ptr;
130
131 stderr_size = read_all(test_output_data->pipe_stderr,
132 test_output_data->output_buffer,
133 test_output_data->output_buffer_size - 1);
134 read_all(test_output_data->pipe_stdout,
135 test_output_data->output_buffer,
136 test_output_data->output_buffer_size - 1 - stderr_size);
137
138 return 0;
139}
140
141/** Run the test as a new process and report the result.
142 *
143 * @param self_path Path to itself, that is to current binary.
144 * @param test Test to be run.
145 */
146void pcut_run_test_forking(const char *self_path, pcut_item_t *test) {
147 /* TODO: clean-up if something goes wrong "in the middle" */
148 BOOL okay = FALSE;
149 DWORD rc;
150 int outcome;
151 int timed_out;
152 int time_out_millis;
153 SECURITY_ATTRIBUTES security_attributes;
154 HANDLE link_stdout[2] = { NULL, NULL };
155 HANDLE link_stderr[2] = { NULL, NULL };
156 HANDLE link_stdin[2] = { NULL, NULL };
157 PROCESS_INFORMATION process_info;
158 STARTUPINFO start_info;
159 char command[PCUT_COMMAND_LINE_BUFFER_SIZE];
160 struct test_output_data test_output_data;
161 HANDLE test_output_thread_reader;
162
163
164 before_test_start(test);
165
166 /* Pipe handles are inherited. */
167 security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
168 security_attributes.bInheritHandle = TRUE;
169 security_attributes.lpSecurityDescriptor = NULL;
170
171 /* Create pipe for stdout, make sure it is not inherited. */
172 okay = CreatePipe(&link_stdout[0], &link_stdout[1], &security_attributes, 0);
173 if (!okay) {
174 report_func_fail(test, "CreatePipe(/* stdout */)");
175 return;
176 }
177 okay = SetHandleInformation(link_stdout[0], HANDLE_FLAG_INHERIT, 0);
178 if (!okay) {
179 report_func_fail(test, "SetHandleInformation(/* stdout */)");
180 return;
181 }
182
183 /* Create pipe for stderr, make sure it is not inherited. */
184 okay = CreatePipe(&link_stderr[0], &link_stderr[1], &security_attributes, 0);
185 if (!okay) {
186 report_func_fail(test, "CreatePipe(/* stderr */)");
187 return;
188 }
189 okay = SetHandleInformation(link_stderr[0], HANDLE_FLAG_INHERIT, 0);
190 if (!okay) {
191 report_func_fail(test, "SetHandleInformation(/* stderr */)");
192 return;
193 }
194
195 /* Create pipe for stdin, make sure it is not inherited. */
196 okay = CreatePipe(&link_stdin[0], &link_stdin[1], &security_attributes, 0);
197 if (!okay) {
198 report_func_fail(test, "CreatePipe(/* stdin */)");
199 return;
200 }
201 okay = SetHandleInformation(link_stdin[1], HANDLE_FLAG_INHERIT, 0);
202 if (!okay) {
203 report_func_fail(test, "SetHandleInformation(/* stdin */)");
204 return;
205 }
206
207 /* Prepare information for the child process. */
208 ZeroMemory(&process_info, sizeof(PROCESS_INFORMATION));
209 ZeroMemory(&start_info, sizeof(STARTUPINFO));
210 start_info.cb = sizeof(STARTUPINFO);
211 start_info.hStdError = link_stderr[1];
212 start_info.hStdOutput = link_stdout[1];
213 start_info.hStdInput = link_stdin[0];
214 start_info.dwFlags |= STARTF_USESTDHANDLES;
215
216 /* Format the command line. */
217 sprintf_s(command, PCUT_COMMAND_LINE_BUFFER_SIZE - 1,
218 "\"%s\" -t%d", self_path, test->id);
219
220 /* Run the process. */
221 okay = CreateProcess(NULL, command, NULL, NULL, TRUE, 0, NULL, NULL,
222 &start_info, &process_info);
223
224 if (!okay) {
225 report_func_fail(test, "CreateProcess()");
226 return;
227 }
228
229 // FIXME: kill the process on error
230
231 /* Close handles to the first thread. */
232 CloseHandle(process_info.hThread);
233
234 /* Close the other ends of the pipes. */
235 okay = CloseHandle(link_stdout[1]);
236 if (!okay) {
237 report_func_fail(test, "CloseHandle(/* stdout */)");
238 return;
239 }
240 okay = CloseHandle(link_stderr[1]);
241 if (!okay) {
242 report_func_fail(test, "CloseHandle(/* stderr */)");
243 return;
244 }
245 okay = CloseHandle(link_stdin[0]);
246 if (!okay) {
247 report_func_fail(test, "CloseHandle(/* stdin */)");
248 return;
249 }
250
251 /*
252 * Read data from stdout and stderr.
253 * We need to do this in a separate thread to allow the
254 * time-out to work correctly.
255 * Probably, this can be done with asynchronous I/O but
256 * this works for now pretty well.
257 */
258 test_output_data.pipe_stderr = link_stderr[0];
259 test_output_data.pipe_stdout = link_stdout[0];
260 test_output_data.output_buffer = extra_output_buffer;
261 test_output_data.output_buffer_size = OUTPUT_BUFFER_SIZE;
262
263 test_output_thread_reader = CreateThread(NULL, 0,
264 read_test_output_on_background, &test_output_data,
265 0, NULL);
266
267 if (test_output_thread_reader == NULL) {
268 report_func_fail(test, "CreateThread(/* read test stdout */)");
269 return;
270 }
271
272 /* Wait for the process to terminate. */
273 timed_out = 0;
274 time_out_millis = pcut_get_test_timeout(test) * 1000;
275 rc = WaitForSingleObject(process_info.hProcess, time_out_millis);
276 PCUT_DEBUG("Waiting for test %s (%dms) returned %d.", test->name, time_out_millis, rc);
277 if (rc == WAIT_TIMEOUT) {
278 /* We timed-out: kill the process and wait for its termination again. */
279 timed_out = 1;
280 okay = TerminateProcess(process_info.hProcess, 5);
281 if (!okay) {
282 report_func_fail(test, "TerminateProcess(/* PROCESS_INFORMATION.hProcess */)");
283 return;
284 }
285 rc = WaitForSingleObject(process_info.hProcess, INFINITE);
286 }
287 if (rc != WAIT_OBJECT_0) {
288 report_func_fail(test, "WaitForSingleObject(/* PROCESS_INFORMATION.hProcess */)");
289 return;
290 }
291
292 /* Get the return code and convert it to outcome. */
293 okay = GetExitCodeProcess(process_info.hProcess, &rc);
294 if (!okay) {
295 report_func_fail(test, "GetExitCodeProcess()");
296 return;
297 }
298
299 if (rc == 0) {
300 outcome = TEST_OUTCOME_PASS;
301 } else if ((rc > 0) && (rc < 10) && !timed_out) {
302 outcome = TEST_OUTCOME_FAIL;
303 } else {
304 outcome = TEST_OUTCOME_ERROR;
305 }
306
307 /* Wait for the reader thread (shall be terminated by now). */
308 rc = WaitForSingleObject(test_output_thread_reader, INFINITE);
309 if (rc != WAIT_OBJECT_0) {
310 report_func_fail(test, "WaitForSingleObject(/* stdout reader thread */)");
311 return;
312 }
313
314 pcut_report_test_done_unparsed(test, outcome, extra_output_buffer, OUTPUT_BUFFER_SIZE);
315}
316
317void pcut_hook_before_test(pcut_item_t *test) {
318 PCUT_UNUSED(test);
319
320 /*
321 * Prevent displaying the dialog informing the user that the
322 * program unexpectedly failed.
323 */
324 SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
325}
Note: See TracBrowser for help on using the repository browser.