| // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| #include <fcntl.h> |
| #include <launchpad/launchpad.h> |
| #include <launchpad/vmo.h> |
| #include <magenta/status.h> |
| #include <magenta/syscalls.h> |
| #include <magenta/syscalls/object.h> |
| #include <mxio/util.h> |
| #include <pthread.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| // This program runs Dart VM unit tests. The Dart VM unit tests are contained |
| // in a separate binary whose location is defined in kRunVmTestsPath below. |
| // That program accepts a command line argument --list to list all the available |
| // tests, or the name of a single test to run. This program grabs the list of |
| // tests, and then runs them. |
| |
| // TODO(zra): Make this a command line argument |
| const char* kRunVmTestsPath = "/boot/bin/dart_vm_tests"; |
| |
| // Tests that are invalid, wedge, or cause panics. |
| const char* kSkip[] = { |
| // These expect a file to exist that we aren't putting in the image. |
| "Read", |
| "FileLength", |
| "FilePosition", |
| // Crash in abort() and then hang. (MG-252) |
| "ArrayLengthMaxElements", |
| "Int8ListLengthMaxElements", |
| "ArrayNew_Overflow_Crash", |
| "SNPrint_BadArgs", |
| "IsolateReload_PendingUnqualifiedCall_InstanceToStatic", |
| "IsolateReload_PendingUnqualifiedCall_StaticToInstance", |
| "IsolateReload_PendingConstructorCall_AbstractToConcrete", |
| "IsolateReload_PendingConstructorCall_ConcreteToAbstract", |
| "IsolateReload_PendingStaticCall_DefinedToNSM", |
| "IsolateReload_PendingStaticCall_NSMToDefined", |
| // The profiler is turned off. |
| "Profiler_AllocationSampleTest", |
| "Profiler_ArrayAllocation", |
| "Profiler_BasicSourcePosition", |
| "Profiler_BasicSourcePositionOptimized", |
| "Profiler_BinaryOperatorSourcePosition", |
| "Profiler_BinaryOperatorSourcePositionOptimized", |
| "Profiler_ChainedSamples", |
| "Profiler_ClosureAllocation", |
| "Profiler_CodeTicks", |
| "Profiler_ContextAllocation", |
| "Profiler_FunctionInline", |
| "Profiler_FunctionTicks", |
| "Profiler_InliningIntervalBoundry", |
| "Profiler_IntrinsicAllocation", |
| "Profiler_SampleBufferIterateTest", |
| "Profiler_SampleBufferWrapTest", |
| "Profiler_SourcePosition", |
| "Profiler_SourcePositionOptimized", |
| "Profiler_StringAllocation", |
| "Profiler_StringInterpolation", |
| "Profiler_ToggleRecordAllocation", |
| "Profiler_TrivialRecordAllocation", |
| "Profiler_TypedArrayAllocation", |
| "Profiler_GetSourceReport", |
| "Service_Profile", |
| // No realpath, files not in image. |
| "Dart2JSCompilerStats", |
| "Dart2JSCompileAll", |
| // Uses too much memory. |
| "PrintJSON", |
| // Need OS::GetCurrentThreadCPUMicros. |
| // Skipping because they crash and hang. (MG-252) |
| "Timeline_Dart_TimelineGetTrace", |
| "Timeline_Dart_TimelineGetTraceOnlyDartEvents", |
| "Timeline_Dart_TimelineGetTraceWithDartEvents", |
| "Timeline_Dart_TimelineGetTraceGlobalOverride", |
| "Timeline_Dart_GlobalTimelineGetTrace", |
| "Timeline_Dart_GlobalTimelineGetTrace_Threaded", |
| "TimelineEventDuration", |
| "TimelineEventDurationPrintJSON", |
| "TimelineEventArguments", |
| "TimelineEventArgumentsPrintJSON", |
| "TimelineEventCallbackRecorderBasic", |
| "TimelineAnalysis_ThreadBlockCount", |
| "TimelineRingRecorderJSONOrder", |
| "TimelinePauses_BeginEnd", |
| }; |
| |
| // Expected to fail/crash. |
| const char* kExpectFail[] = { |
| "Fail0", |
| "Fail1", |
| "Fail2", |
| "AllocGeneric_Overflow", |
| "CodeImmutability", |
| // Assumes initial thread's stack is the same size as spawned thread stacks. |
| "StackOverflowStacktraceInfo", |
| }; |
| |
| // Bugs to fix, or things that are not yet implemented. |
| const char* kBugs[] = { |
| // Needs read of RSS. |
| "InitialRSS", |
| }; |
| |
| |
| static bool contains(const char** list, intptr_t len, const char* str) { |
| for (intptr_t i = 0; i < len; i++) { |
| if (strcmp(list[i], str) == 0) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| |
| static bool isSkip(const char* test) { |
| return contains( |
| kSkip, sizeof(kSkip) / sizeof(kSkip[0]), test); |
| } |
| |
| |
| static bool isExpectFail(const char* test) { |
| return contains( |
| kExpectFail, sizeof(kExpectFail) / sizeof(kExpectFail[0]), test); |
| } |
| |
| |
| static bool isBug(const char* test) { |
| return contains(kBugs, sizeof(kBugs) / sizeof(kBugs[0]), test); |
| } |
| |
| #define RETURN_IF_ERROR(status) \ |
| if (status < 0) { \ |
| fprintf(stderr, "%s:%d: Magenta call failed: %s\n", __FILE__, __LINE__, \ |
| mx_status_get_string(static_cast<mx_status_t>(status))); \ |
| fflush(0); \ |
| return status; \ |
| } |
| |
| // This is mostly taken from //magenta/system/uapp/mxsh with the addtion of |
| // launchpad_add_pipe calls to setup pipes for stdout and stderr. |
| static mx_status_t lp_setup(launchpad_t** lp_out, mx_handle_t binary_vmo, |
| int argc, const char* const* argv, |
| int *stdout_out, int *stderr_out) { |
| if ((lp_out == NULL) || (stdout_out == NULL) || (stderr_out == NULL)) { |
| return ERR_INVALID_ARGS; |
| } |
| launchpad_t* lp; |
| mx_status_t status; |
| status = launchpad_create(argv[0], &lp); |
| RETURN_IF_ERROR(status); |
| status = launchpad_arguments(lp, argc, argv); |
| RETURN_IF_ERROR(status); |
| status = launchpad_clone_mxio_root(lp); |
| RETURN_IF_ERROR(status); |
| status = launchpad_add_pipe(lp, stdout_out, 1); |
| RETURN_IF_ERROR(status); |
| status = launchpad_add_pipe(lp, stderr_out, 2); |
| RETURN_IF_ERROR(status); |
| status = launchpad_add_vdso_vmo(lp); |
| RETURN_IF_ERROR(status); |
| status = launchpad_elf_load(lp, binary_vmo); |
| RETURN_IF_ERROR(status); |
| status = launchpad_load_vdso(lp, MX_HANDLE_INVALID); |
| RETURN_IF_ERROR(status); |
| *lp_out = lp; |
| return status; |
| } |
| |
| |
| // Start the test running and return file descriptors for the stdout and stderr |
| // pipes. |
| static mx_handle_t start_test(mx_handle_t binary_vmo, const char* test_name, |
| int* stdout_out, int* stderr_out) { |
| const intptr_t kArgc = 2; |
| const char* argv[kArgc]; |
| |
| argv[0] = kRunVmTestsPath; |
| argv[1] = test_name; |
| |
| launchpad_t* lp = NULL; |
| int stdout_pipe = -1; |
| int stderr_pipe = -1; |
| mx_status_t status = lp_setup( |
| &lp, binary_vmo, kArgc, argv, &stdout_pipe, &stderr_pipe); |
| if (status != NO_ERROR) { |
| if (lp != NULL) { |
| launchpad_destroy(lp); |
| } |
| if (stdout_pipe != -1) { |
| close(stdout_pipe); |
| } |
| if (stderr_pipe != -1) { |
| close(stderr_pipe); |
| } |
| } |
| RETURN_IF_ERROR(status); |
| |
| mx_handle_t p = launchpad_start(lp); |
| launchpad_destroy(lp); |
| if (p < 0) { |
| close(stdout_pipe); |
| close(stderr_pipe); |
| } |
| RETURN_IF_ERROR(p); |
| |
| if (stdout_out != NULL) { |
| *stdout_out = stdout_pipe; |
| } else { |
| close(stdout_pipe); |
| } |
| if (stderr_out != NULL) { |
| *stderr_out = stderr_pipe; |
| } else { |
| close(stderr_pipe); |
| } |
| return p; |
| } |
| |
| |
| // Drain fd into a buffer pointed to by 'buffer'. Assumes that the data is a |
| // C string, and null-terminates it. Returns the number of bytes read. |
| static intptr_t drain_fd(int fd, char** buffer) { |
| const intptr_t kDrainInitSize = 64; |
| char* buf = reinterpret_cast<char*>(malloc(kDrainInitSize)); |
| intptr_t free_space = kDrainInitSize; |
| intptr_t total_read = 0; |
| intptr_t read_size = 0; |
| while ((read_size = read(fd, buf + total_read, free_space)) != 0) { |
| if (read_size == -1) { |
| break; |
| } |
| total_read += read_size; |
| free_space -= read_size; |
| if (free_space <= 1) { |
| // new_size = size * 1.5. |
| intptr_t new_size = (total_read << 1) - (total_read >> 1); |
| buf = reinterpret_cast<char*>(realloc(buf, new_size)); |
| free_space = new_size - total_read; |
| } |
| } |
| buf[total_read] = '\0'; |
| close(fd); |
| *buffer = buf; |
| return total_read; |
| } |
| |
| |
| // Runs test 'test_name' and gives stdout and stderr for the test in |
| // 'test_stdout' and 'test_stderr'. Returns the exit code from the test. |
| static int run_test(mx_handle_t binary_vmo, const char* test_name, |
| char** test_stdout, char** test_stderr) { |
| int stdout_pipe = -1; |
| int stderr_pipe = -1; |
| mx_handle_t p = start_test(binary_vmo, test_name, &stdout_pipe, &stderr_pipe); |
| RETURN_IF_ERROR(p); |
| |
| drain_fd(stdout_pipe, test_stdout); |
| drain_fd(stderr_pipe, test_stderr); |
| |
| mx_status_t r = mx_handle_wait_one( |
| p, MX_SIGNAL_SIGNALED, MX_TIME_INFINITE, NULL); |
| RETURN_IF_ERROR(r); |
| |
| mx_info_process_t proc_info; |
| mx_size_t info_size; |
| mx_status_t status = |
| mx_object_get_info(p, MX_INFO_PROCESS, sizeof(proc_info.rec), |
| &proc_info, sizeof(proc_info), &info_size); |
| RETURN_IF_ERROR(status); |
| |
| r = mx_handle_close(p); |
| RETURN_IF_ERROR(r); |
| return proc_info.rec.return_code; |
| } |
| |
| |
| static void handle_result( |
| intptr_t result, char* test_stdout, char* test_stderr, const char* test) { |
| if (result != 0) { |
| if (!isExpectFail(test) && !isBug(test)) { |
| printf("**** Test %s FAILED\n\nstdout:\n%s\nstderr:\n%s\n", |
| test, test_stdout, test_stderr); |
| } else { |
| printf("Test %s FAILED and is expected to fail\n", test); |
| } |
| } else { |
| if (isExpectFail(test)) { |
| printf("**** Test %s is expected to fail, but PASSED\n\n" |
| "stdout:\n%s\nstderr:\n%s\n", |
| test, test_stdout, test_stderr); |
| } else if (isBug(test)) { |
| printf("**** Test %s is marked as a bug, but PASSED\n", test); |
| } else { |
| printf("Test %s PASSED\n", test); |
| } |
| } |
| } |
| |
| |
| typedef struct { |
| pthread_mutex_t* test_list_lock; |
| char** test_list; |
| intptr_t test_list_length; |
| intptr_t* test_list_index; |
| mx_handle_t binary_vmo; |
| } runner_args_t; |
| |
| |
| static void* test_runner_thread(void* arg) { |
| runner_args_t* args = reinterpret_cast<runner_args_t*>(arg); |
| |
| pthread_mutex_lock(args->test_list_lock); |
| mx_handle_t binary_vmo = args->binary_vmo; |
| while (*args->test_list_index < args->test_list_length) { |
| const intptr_t index = *args->test_list_index; |
| *args->test_list_index = index + 1; |
| pthread_mutex_unlock(args->test_list_lock); |
| |
| const char* test = args->test_list[index]; |
| char* test_stdout = NULL; |
| char* test_stderr = NULL; |
| mx_handle_t vmo_dup = MX_HANDLE_INVALID; |
| mx_handle_duplicate(binary_vmo, MX_RIGHT_SAME_RIGHTS, &vmo_dup); |
| int test_status = run_test(vmo_dup, test, &test_stdout, &test_stderr); |
| handle_result(test_status, test_stdout, test_stderr, test); |
| free(test_stdout); |
| free(test_stderr); |
| pthread_mutex_lock(args->test_list_lock); |
| } |
| pthread_mutex_unlock(args->test_list_lock); |
| |
| return NULL; |
| } |
| |
| |
| static void run_all_tests(runner_args_t* args) { |
| const intptr_t num_cpus = sysconf(_SC_NPROCESSORS_CONF); |
| pthread_t* threads = |
| reinterpret_cast<pthread_t*>(malloc(num_cpus * sizeof(pthread_t))); |
| for (int i = 0; i < num_cpus; i++) { |
| pthread_create(&threads[i], NULL, test_runner_thread, args); |
| } |
| for (int i = 0; i < num_cpus; i++) { |
| pthread_join(threads[i], NULL); |
| } |
| free(threads); |
| } |
| |
| |
| static bool should_run(const char* test) { |
| return !(test[0] == '#') && !isSkip(test); |
| } |
| |
| |
| static char** parse_test_list(char* list_output, intptr_t* length) { |
| const intptr_t list_output_length = strlen(list_output); |
| intptr_t test_count = 0; |
| for (int i = 0; i < list_output_length; i++) { |
| if (list_output[i] == '\n') { |
| test_count++; |
| } |
| } |
| char** test_list; |
| test_list = reinterpret_cast<char**>(malloc(test_count * sizeof(*test_list))); |
| char* test = list_output; |
| char* strtok_context; |
| intptr_t idx = 0; |
| while ((test = strtok_r(test, "\n", &strtok_context)) != NULL) { |
| if (should_run(test)) { |
| test_list[idx] = strdup(test); |
| idx++; |
| } |
| test = NULL; |
| } |
| |
| *length = idx; |
| return test_list; |
| } |
| |
| |
| int main(int argc, char** argv) { |
| // TODO(zra): Read test binary path from the command line. |
| |
| // Load in the binary. |
| mx_handle_t binary_vmo = launchpad_vmo_from_file(kRunVmTestsPath); |
| RETURN_IF_ERROR(binary_vmo); |
| |
| // Run with --list to grab the list of tests. |
| char* list_stdout = NULL; |
| char* list_stderr = NULL; |
| mx_handle_t list_vmo = MX_HANDLE_INVALID; |
| mx_status_t status = |
| mx_handle_duplicate(binary_vmo, MX_RIGHT_SAME_RIGHTS, &list_vmo); |
| RETURN_IF_ERROR(status); |
| int list_result = run_test(list_vmo, "--list", &list_stdout, &list_stderr); |
| if (list_result != 0) { |
| fprintf(stderr, "Failed to list tests: %s\n%s\n", list_stdout, list_stderr); |
| fflush(0); |
| free(list_stdout); |
| free(list_stderr); |
| return list_result; |
| } |
| |
| // Parse the test list into an array of C strings. |
| intptr_t lines_count; |
| char** test_list = parse_test_list(list_stdout, &lines_count); |
| free(list_stdout); |
| free(list_stderr); |
| |
| fprintf(stdout, "Found %ld tests\n", lines_count); |
| fflush(0); |
| |
| // Run the tests across a number of threads equal to the number of cores. |
| pthread_mutex_t args_mutex; |
| pthread_mutex_init(&args_mutex, NULL); |
| intptr_t test_list_index = 0; |
| runner_args_t args; |
| args.test_list_lock = &args_mutex; |
| args.test_list = test_list; |
| args.test_list_length = lines_count; |
| args.test_list_index = &test_list_index; |
| args.binary_vmo = binary_vmo; |
| run_all_tests(&args); |
| |
| // Cleanup. |
| for (int i = 0; i < lines_count; i++) { |
| free(test_list[i]); |
| } |
| free(test_list); |
| pthread_mutex_destroy(&args_mutex); |
| status = mx_handle_close(binary_vmo); |
| RETURN_IF_ERROR(status); |
| |
| // Complain if we didn't try to run all of the tests. |
| if (test_list_index != lines_count) { |
| fprintf(stderr, "Failed to attempt all the tests!\n"); |
| fflush(0); |
| return -1; |
| } |
| return 0; |
| } |