| 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 |