445 lines
13 KiB
C++
445 lines
13 KiB
C++
// 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_signals_state_t state;
|
|
mx_status_t r = mx_handle_wait_one(
|
|
p, MX_SIGNAL_SIGNALED, MX_TIME_INFINITE, &state);
|
|
RETURN_IF_ERROR(r);
|
|
|
|
mx_info_process_t proc_info;
|
|
mx_ssize_t info_size = mx_object_get_info(
|
|
p, MX_INFO_PROCESS, sizeof(proc_info.rec), &proc_info, sizeof(proc_info));
|
|
RETURN_IF_ERROR(info_size);
|
|
|
|
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_duplicate(binary_vmo, MX_RIGHT_SAME_RIGHTS);
|
|
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_duplicate(binary_vmo, MX_RIGHT_SAME_RIGHTS);
|
|
RETURN_IF_ERROR(list_vmo);
|
|
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);
|
|
mx_status_t 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;
|
|
}
|