OLD | NEW |
| (Empty) |
1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 #include <fcntl.h> | |
6 #include <launchpad/launchpad.h> | |
7 #include <launchpad/vmo.h> | |
8 #include <magenta/status.h> | |
9 #include <magenta/syscalls.h> | |
10 #include <magenta/syscalls/object.h> | |
11 #include <mxio/util.h> | |
12 #include <pthread.h> | |
13 #include <stdbool.h> | |
14 #include <stdio.h> | |
15 #include <stdlib.h> | |
16 #include <string.h> | |
17 #include <unistd.h> | |
18 | |
19 // This program runs Dart VM unit tests. The Dart VM unit tests are contained | |
20 // in a separate binary whose location is defined in kRunVmTestsPath below. | |
21 // That program accepts a command line argument --list to list all the available | |
22 // tests, or the name of a single test to run. This program grabs the list of | |
23 // tests, and then runs them. | |
24 | |
25 // TODO(zra): Make this a command line argument | |
26 const char* kRunVmTestsPath = "/system/bin/dart_vm_tests"; | |
27 | |
28 // clang-format off | |
29 // Tests that are invalid, wedge, or cause panics. | |
30 const char* kSkip[] = { | |
31 // These expect a file to exist that we aren't putting in the image. | |
32 "Read", | |
33 "FileLength", | |
34 "FilePosition", | |
35 // No realpath, files not in image. | |
36 "Dart2JSCompilerStats", | |
37 "Dart2JSCompileAll", | |
38 // The profiler is turned off. | |
39 "Profiler_AllocationSampleTest", | |
40 "Profiler_ArrayAllocation", | |
41 "Profiler_BasicSourcePosition", | |
42 "Profiler_BasicSourcePositionOptimized", | |
43 "Profiler_BinaryOperatorSourcePosition", | |
44 "Profiler_BinaryOperatorSourcePositionOptimized", | |
45 "Profiler_ChainedSamples", | |
46 "Profiler_ClosureAllocation", | |
47 "Profiler_CodeTicks", | |
48 "Profiler_ContextAllocation", | |
49 "Profiler_FunctionInline", | |
50 "Profiler_FunctionTicks", | |
51 "Profiler_InliningIntervalBoundry", | |
52 "Profiler_IntrinsicAllocation", | |
53 "Profiler_SampleBufferIterateTest", | |
54 "Profiler_SampleBufferWrapTest", | |
55 "Profiler_SourcePosition", | |
56 "Profiler_SourcePositionOptimized", | |
57 "Profiler_StringAllocation", | |
58 "Profiler_StringInterpolation", | |
59 "Profiler_ToggleRecordAllocation", | |
60 "Profiler_TrivialRecordAllocation", | |
61 "Profiler_TypedArrayAllocation", | |
62 "Profiler_GetSourceReport", | |
63 "Service_Profile", | |
64 }; | |
65 | |
66 // Expected to fail/crash. | |
67 const char* kExpectFail[] = { | |
68 "Fail0", | |
69 "Fail1", | |
70 "Fail2", | |
71 "AllocGeneric_Overflow", | |
72 "CodeImmutability", | |
73 "IsolateReload_PendingUnqualifiedCall_StaticToInstance", | |
74 "IsolateReload_PendingConstructorCall_AbstractToConcrete", | |
75 "IsolateReload_PendingConstructorCall_ConcreteToAbstract", | |
76 "IsolateReload_PendingUnqualifiedCall_InstanceToStatic", | |
77 "IsolateReload_PendingStaticCall_DefinedToNSM", | |
78 "IsolateReload_PendingStaticCall_NSMToDefined", | |
79 "ArrayNew_Overflow_Crash", | |
80 "SNPrint_BadArgs", | |
81 }; | |
82 | |
83 // Bugs to fix, or things that are not yet implemented. | |
84 const char* kBugs[] = { | |
85 // Assumes initial thread's stack is the same size as spawned thread stacks. | |
86 "StackOverflowStackTraceInfo", | |
87 }; | |
88 // clang-format on | |
89 | |
90 static bool contains(const char** list, intptr_t len, const char* str) { | |
91 for (intptr_t i = 0; i < len; i++) { | |
92 if (strcmp(list[i], str) == 0) { | |
93 return true; | |
94 } | |
95 } | |
96 return false; | |
97 } | |
98 | |
99 | |
100 static bool isSkip(const char* test) { | |
101 return contains(kSkip, sizeof(kSkip) / sizeof(kSkip[0]), test); | |
102 } | |
103 | |
104 | |
105 static bool isExpectFail(const char* test) { | |
106 return contains(kExpectFail, sizeof(kExpectFail) / sizeof(kExpectFail[0]), | |
107 test); | |
108 } | |
109 | |
110 | |
111 static bool isBug(const char* test) { | |
112 return contains(kBugs, sizeof(kBugs) / sizeof(kBugs[0]), test); | |
113 } | |
114 | |
115 #define RETURN_IF_ERROR(status) \ | |
116 if (status < 0) { \ | |
117 fprintf(stderr, "%s:%d: Magenta call failed: %s\n", __FILE__, __LINE__, \ | |
118 mx_status_get_string(static_cast<mx_status_t>(status))); \ | |
119 fflush(0); \ | |
120 return status; \ | |
121 } | |
122 | |
123 // This is mostly taken from //magenta/system/uapp/mxsh with the addtion of | |
124 // launchpad_add_pipe calls to setup pipes for stdout and stderr. | |
125 static mx_status_t lp_setup(launchpad_t** lp_out, | |
126 mx_handle_t binary_vmo, | |
127 int argc, | |
128 const char* const* argv, | |
129 int* stdout_out, | |
130 int* stderr_out) { | |
131 if ((lp_out == NULL) || (stdout_out == NULL) || (stderr_out == NULL)) { | |
132 return ERR_INVALID_ARGS; | |
133 } | |
134 launchpad_t* lp; | |
135 mx_status_t status; | |
136 mx_handle_t job = MX_HANDLE_INVALID; | |
137 status = mx_handle_duplicate(mx_job_default(), MX_RIGHT_SAME_RIGHTS, &job); | |
138 RETURN_IF_ERROR(status); | |
139 status = launchpad_create(job, argv[0], &lp); | |
140 RETURN_IF_ERROR(status); | |
141 status = launchpad_arguments(lp, argc, argv); | |
142 RETURN_IF_ERROR(status); | |
143 status = launchpad_clone_mxio_root(lp); | |
144 RETURN_IF_ERROR(status); | |
145 status = launchpad_add_pipe(lp, stdout_out, 1); | |
146 RETURN_IF_ERROR(status); | |
147 status = launchpad_add_pipe(lp, stderr_out, 2); | |
148 RETURN_IF_ERROR(status); | |
149 status = launchpad_add_vdso_vmo(lp); | |
150 RETURN_IF_ERROR(status); | |
151 status = launchpad_elf_load(lp, binary_vmo); | |
152 RETURN_IF_ERROR(status); | |
153 status = launchpad_load_vdso(lp, MX_HANDLE_INVALID); | |
154 RETURN_IF_ERROR(status); | |
155 *lp_out = lp; | |
156 return status; | |
157 } | |
158 | |
159 | |
160 // Start the test running and return file descriptors for the stdout and stderr | |
161 // pipes. | |
162 static mx_handle_t start_test(mx_handle_t binary_vmo, | |
163 const char* test_name, | |
164 int* stdout_out, | |
165 int* stderr_out) { | |
166 const intptr_t kArgc = 2; | |
167 const char* argv[kArgc]; | |
168 | |
169 argv[0] = kRunVmTestsPath; | |
170 argv[1] = test_name; | |
171 | |
172 launchpad_t* lp = NULL; | |
173 int stdout_pipe = -1; | |
174 int stderr_pipe = -1; | |
175 mx_status_t status = | |
176 lp_setup(&lp, binary_vmo, kArgc, argv, &stdout_pipe, &stderr_pipe); | |
177 if (status != NO_ERROR) { | |
178 if (lp != NULL) { | |
179 launchpad_destroy(lp); | |
180 } | |
181 if (stdout_pipe != -1) { | |
182 close(stdout_pipe); | |
183 } | |
184 if (stderr_pipe != -1) { | |
185 close(stderr_pipe); | |
186 } | |
187 } | |
188 RETURN_IF_ERROR(status); | |
189 | |
190 mx_handle_t p = launchpad_start(lp); | |
191 launchpad_destroy(lp); | |
192 if (p < 0) { | |
193 close(stdout_pipe); | |
194 close(stderr_pipe); | |
195 } | |
196 RETURN_IF_ERROR(p); | |
197 | |
198 if (stdout_out != NULL) { | |
199 *stdout_out = stdout_pipe; | |
200 } else { | |
201 close(stdout_pipe); | |
202 } | |
203 if (stderr_out != NULL) { | |
204 *stderr_out = stderr_pipe; | |
205 } else { | |
206 close(stderr_pipe); | |
207 } | |
208 return p; | |
209 } | |
210 | |
211 | |
212 // Drain fd into a buffer pointed to by 'buffer'. Assumes that the data is a | |
213 // C string, and null-terminates it. Returns the number of bytes read. | |
214 static intptr_t drain_fd(int fd, char** buffer) { | |
215 const intptr_t kDrainInitSize = 64; | |
216 char* buf = reinterpret_cast<char*>(malloc(kDrainInitSize)); | |
217 intptr_t free_space = kDrainInitSize; | |
218 intptr_t total_read = 0; | |
219 intptr_t read_size = 0; | |
220 while ((read_size = read(fd, buf + total_read, free_space)) != 0) { | |
221 if (read_size == -1) { | |
222 break; | |
223 } | |
224 total_read += read_size; | |
225 free_space -= read_size; | |
226 if (free_space <= 1) { | |
227 // new_size = size * 1.5. | |
228 intptr_t new_size = (total_read << 1) - (total_read >> 1); | |
229 buf = reinterpret_cast<char*>(realloc(buf, new_size)); | |
230 free_space = new_size - total_read; | |
231 } | |
232 } | |
233 buf[total_read] = '\0'; | |
234 close(fd); | |
235 *buffer = buf; | |
236 return total_read; | |
237 } | |
238 | |
239 | |
240 // Runs test 'test_name' and gives stdout and stderr for the test in | |
241 // 'test_stdout' and 'test_stderr'. Returns the exit code from the test. | |
242 static int run_test(mx_handle_t binary_vmo, | |
243 const char* test_name, | |
244 char** test_stdout, | |
245 char** test_stderr) { | |
246 int stdout_pipe = -1; | |
247 int stderr_pipe = -1; | |
248 mx_handle_t p = start_test(binary_vmo, test_name, &stdout_pipe, &stderr_pipe); | |
249 RETURN_IF_ERROR(p); | |
250 | |
251 drain_fd(stdout_pipe, test_stdout); | |
252 drain_fd(stderr_pipe, test_stderr); | |
253 | |
254 mx_status_t r = | |
255 mx_object_wait_one(p, MX_PROCESS_SIGNALED, MX_TIME_INFINITE, NULL); | |
256 RETURN_IF_ERROR(r); | |
257 | |
258 mx_info_process_t proc_info; | |
259 mx_status_t status = mx_object_get_info(p, MX_INFO_PROCESS, &proc_info, | |
260 sizeof(proc_info), nullptr, nullptr); | |
261 RETURN_IF_ERROR(status); | |
262 | |
263 r = mx_handle_close(p); | |
264 RETURN_IF_ERROR(r); | |
265 return proc_info.return_code; | |
266 } | |
267 | |
268 | |
269 static void handle_result(intptr_t result, | |
270 char* test_stdout, | |
271 char* test_stderr, | |
272 const char* test) { | |
273 if (result != 0) { | |
274 if (!isExpectFail(test) && !isBug(test)) { | |
275 printf("**** Test %s FAILED\n\nstdout:\n%s\nstderr:\n%s\n", test, | |
276 test_stdout, test_stderr); | |
277 } else { | |
278 printf("Test %s FAILED and is expected to fail\n", test); | |
279 } | |
280 } else { | |
281 if (isExpectFail(test)) { | |
282 printf( | |
283 "**** Test %s is expected to fail, but PASSED\n\n" | |
284 "stdout:\n%s\nstderr:\n%s\n", | |
285 test, test_stdout, test_stderr); | |
286 } else if (isBug(test)) { | |
287 printf("**** Test %s is marked as a bug, but PASSED\n", test); | |
288 } else { | |
289 printf("Test %s PASSED\n", test); | |
290 } | |
291 } | |
292 } | |
293 | |
294 | |
295 typedef struct { | |
296 pthread_mutex_t* test_list_lock; | |
297 char** test_list; | |
298 intptr_t test_list_length; | |
299 intptr_t* test_list_index; | |
300 mx_handle_t binary_vmo; | |
301 } runner_args_t; | |
302 | |
303 | |
304 static void* test_runner_thread(void* arg) { | |
305 runner_args_t* args = reinterpret_cast<runner_args_t*>(arg); | |
306 | |
307 pthread_mutex_lock(args->test_list_lock); | |
308 mx_handle_t binary_vmo = args->binary_vmo; | |
309 while (*args->test_list_index < args->test_list_length) { | |
310 const intptr_t index = *args->test_list_index; | |
311 *args->test_list_index = index + 1; | |
312 pthread_mutex_unlock(args->test_list_lock); | |
313 | |
314 const char* test = args->test_list[index]; | |
315 char* test_stdout = NULL; | |
316 char* test_stderr = NULL; | |
317 mx_handle_t vmo_dup = MX_HANDLE_INVALID; | |
318 mx_handle_duplicate(binary_vmo, MX_RIGHT_SAME_RIGHTS, &vmo_dup); | |
319 int test_status = run_test(vmo_dup, test, &test_stdout, &test_stderr); | |
320 handle_result(test_status, test_stdout, test_stderr, test); | |
321 free(test_stdout); | |
322 free(test_stderr); | |
323 pthread_mutex_lock(args->test_list_lock); | |
324 } | |
325 pthread_mutex_unlock(args->test_list_lock); | |
326 | |
327 return NULL; | |
328 } | |
329 | |
330 | |
331 static void run_all_tests(runner_args_t* args) { | |
332 const intptr_t num_cpus = sysconf(_SC_NPROCESSORS_CONF); | |
333 pthread_t* threads = | |
334 reinterpret_cast<pthread_t*>(malloc(num_cpus * sizeof(pthread_t))); | |
335 for (int i = 0; i < num_cpus; i++) { | |
336 pthread_create(&threads[i], NULL, test_runner_thread, args); | |
337 } | |
338 for (int i = 0; i < num_cpus; i++) { | |
339 pthread_join(threads[i], NULL); | |
340 } | |
341 free(threads); | |
342 } | |
343 | |
344 | |
345 static bool should_run(const char* test) { | |
346 return !(test[0] == '#') && !isSkip(test); | |
347 } | |
348 | |
349 | |
350 static char** parse_test_list(char* list_output, intptr_t* length) { | |
351 const intptr_t list_output_length = strlen(list_output); | |
352 intptr_t test_count = 0; | |
353 for (int i = 0; i < list_output_length; i++) { | |
354 if (list_output[i] == '\n') { | |
355 test_count++; | |
356 } | |
357 } | |
358 char** test_list; | |
359 test_list = reinterpret_cast<char**>(malloc(test_count * sizeof(*test_list))); | |
360 char* test = list_output; | |
361 char* strtok_context; | |
362 intptr_t idx = 0; | |
363 while ((test = strtok_r(test, "\n", &strtok_context)) != NULL) { | |
364 if (should_run(test)) { | |
365 test_list[idx] = strdup(test); | |
366 idx++; | |
367 } | |
368 test = NULL; | |
369 } | |
370 | |
371 *length = idx; | |
372 return test_list; | |
373 } | |
374 | |
375 | |
376 int main(int argc, char** argv) { | |
377 // TODO(zra): Read test binary path from the command line. | |
378 | |
379 // Load in the binary. | |
380 mx_handle_t binary_vmo = launchpad_vmo_from_file(kRunVmTestsPath); | |
381 RETURN_IF_ERROR(binary_vmo); | |
382 | |
383 // Run with --list to grab the list of tests. | |
384 char* list_stdout = NULL; | |
385 char* list_stderr = NULL; | |
386 mx_handle_t list_vmo = MX_HANDLE_INVALID; | |
387 mx_status_t status = | |
388 mx_handle_duplicate(binary_vmo, MX_RIGHT_SAME_RIGHTS, &list_vmo); | |
389 RETURN_IF_ERROR(status); | |
390 int list_result = run_test(list_vmo, "--list", &list_stdout, &list_stderr); | |
391 if (list_result != 0) { | |
392 fprintf(stderr, "Failed to list tests: %s\n%s\n", list_stdout, list_stderr); | |
393 fflush(0); | |
394 free(list_stdout); | |
395 free(list_stderr); | |
396 return list_result; | |
397 } | |
398 | |
399 // Parse the test list into an array of C strings. | |
400 intptr_t lines_count; | |
401 char** test_list = parse_test_list(list_stdout, &lines_count); | |
402 free(list_stdout); | |
403 free(list_stderr); | |
404 | |
405 fprintf(stdout, "Found %ld tests\n", lines_count); | |
406 fflush(0); | |
407 | |
408 // Run the tests across a number of threads equal to the number of cores. | |
409 pthread_mutex_t args_mutex; | |
410 pthread_mutex_init(&args_mutex, NULL); | |
411 intptr_t test_list_index = 0; | |
412 runner_args_t args; | |
413 args.test_list_lock = &args_mutex; | |
414 args.test_list = test_list; | |
415 args.test_list_length = lines_count; | |
416 args.test_list_index = &test_list_index; | |
417 args.binary_vmo = binary_vmo; | |
418 run_all_tests(&args); | |
419 | |
420 // Cleanup. | |
421 for (int i = 0; i < lines_count; i++) { | |
422 free(test_list[i]); | |
423 } | |
424 free(test_list); | |
425 pthread_mutex_destroy(&args_mutex); | |
426 status = mx_handle_close(binary_vmo); | |
427 RETURN_IF_ERROR(status); | |
428 | |
429 // Complain if we didn't try to run all of the tests. | |
430 if (test_list_index != lines_count) { | |
431 fprintf(stderr, "Failed to attempt all the tests!\n"); | |
432 fflush(0); | |
433 return -1; | |
434 } | |
435 return 0; | |
436 } | |
OLD | NEW |