From e1564526ea0f53b5f6f1b426444d066b8f74f3d1 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Mon, 30 Sep 2024 18:35:35 +0200 Subject: [PATCH] Run test262 tests in parallel (#564) This commit introduces a couple of changes in order to make run-test262 go brr and execute tests in parallel: - Remove CONFIG_AGENT build option. The disabled version of the build was already broken and no one noticed, Remove the define altogether. - Remove the -C switch. Hard to support in multi-threaded mode. I may bring it back some day because it _is_ useful. - Remove the -r switch. Also hard to support and I never look at test262_report.txt anyway so on the chopping block it goes. - Judicious use of thread-local storage so I don't have to thread through state everywhere and embiggen the diff even more. This is what Real Programmers(TM) do: stay up coding way past midnight just so the test suite finishes in one minute instead of four. Fixes: https://github.com/quickjs-ng/quickjs/issues/547 --- CMakeLists.txt | 11 +- run-test262.c | 378 +++++++++++++++++++++++++------------------------ test262.conf | 3 - 3 files changed, 201 insertions(+), 191 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f6d377..8bd0c3a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -269,8 +269,15 @@ endif() # Test262 runner # -# run-test262 uses pthreads. -if(NOT WIN32 AND NOT EMSCRIPTEN) +if(WIN32 +OR EMSCRIPTEN +OR CMAKE_C_COMPILER_ID STREQUAL "TinyCC" +OR CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_LESS 5) + # Empty. run-test262 uses pthreads, sorry Windows users. + # tcc and gcc 4.8 don't understand _Thread_local, whereas I + # don't understand why people still use 4.8 in this day and age + # but hey, here we are. +else() add_executable(run-test262 run-test262.c ) diff --git a/run-test262.c b/run-test262.c index 0d9bbf6..eb0785b 100644 --- a/run-test262.c +++ b/run-test262.c @@ -34,23 +34,27 @@ #include #include #include +#include #include "cutils.h" #include "list.h" +#include "quickjs-c-atomics.h" #include "quickjs-libc.h" -/* enable test262 thread support to test SharedArrayBuffer and Atomics */ -#define CONFIG_AGENT - #define CMD_NAME "run-test262" typedef struct namelist_t { char **array; int count; int size; - unsigned int sorted : 1; } namelist_t; +long nthreads; // invariant: 0 < nthreads < countof(threads) +pthread_t threads[32]; +pthread_t progress_thread; +js_cond_t progress_cond; +js_mutex_t progress_mutex; + namelist_t test_list; namelist_t exclude_list; namelist_t exclude_dir_list; @@ -63,7 +67,6 @@ enum test_mode_t { TEST_STRICT, /* run tests as strict, skip nostrict tests */ TEST_ALL, /* run tests in both strict and nostrict, unless restricted by spec */ } test_mode = TEST_DEFAULT_NOSTRICT; -int compact; int skip_async; int skip_module; int new_style; @@ -72,6 +75,7 @@ int stats_count; JSMemoryUsage stats_all, stats_avg, stats_min, stats_max; char *stats_min_filename; char *stats_max_filename; +js_mutex_t stats_mutex; int verbose; char *harness_dir; char *harness_exclude; @@ -80,15 +84,22 @@ char *harness_skip_features; char *error_filename; char *error_file; FILE *error_out; -char *report_filename; int update_errors; -int test_count, test_failed, test_index, test_skipped, test_excluded; -int new_errors, changed_errors, fixed_errors; -int async_done; +int slow_test_threshold; +int start_index, stop_index; +int test_excluded; +_Atomic int test_count, test_failed, test_skipped; +_Atomic int new_errors, changed_errors, fixed_errors; +_Thread_local int async_done; void warning(const char *, ...) __attribute__((__format__(__printf__, 1, 2))); void fatal(int, const char *, ...) __attribute__((__format__(__printf__, 2, 3))); +void atomic_inc(volatile _Atomic int *p) +{ + atomic_fetch_add(p, 1); +} + void warning(const char *fmt, ...) { va_list ap; @@ -270,16 +281,12 @@ void namelist_sort(namelist_t *lp) } lp->count = count; } - lp->sorted = 1; } -int namelist_find(namelist_t *lp, const char *name) +int namelist_find(const namelist_t *lp, const char *name) { int a, b, m, cmp; - if (!lp->sorted) { - namelist_sort(lp); - } for (a = 0, b = lp->count; a < b;) { m = a + (b - a) / 2; cmp = namelist_cmp(lp->array[m], name); @@ -386,23 +393,24 @@ static JSValue js_print_262(JSContext *ctx, JSValue this_val, int i; const char *str; - if (outfile) { - for (i = 0; i < argc; i++) { + for (i = 0; i < argc; i++) { + str = JS_ToCString(ctx, argv[i]); + if (!str) + return JS_EXCEPTION; + if (!strcmp(str, "Test262:AsyncTestComplete")) { + async_done++; + } else if (strstart(str, "Test262:AsyncTestFailure", NULL)) { + async_done = 2; /* force an error */ + } + if (outfile) { if (i != 0) fputc(' ', outfile); - str = JS_ToCString(ctx, argv[i]); - if (!str) - return JS_EXCEPTION; - if (!strcmp(str, "Test262:AsyncTestComplete")) { - async_done++; - } else if (strstart(str, "Test262:AsyncTestFailure", NULL)) { - async_done = 2; /* force an error */ - } fputs(str, outfile); - JS_FreeCString(ctx, str); } - fputc('\n', outfile); + JS_FreeCString(ctx, str); } + if (outfile) + fputc('\n', outfile); return JS_UNDEFINED; } @@ -427,9 +435,48 @@ static JSValue js_evalScript_262(JSContext *ctx, JSValue this_val, return ret; } -#ifdef CONFIG_AGENT +static void start_thread(pthread_t *thrd, void *(*start)(void *), void *arg) +{ + pthread_attr_t attr; -#include + if (pthread_attr_init(&attr)) + fatal(1, "pthread_attr_init"); + // musl libc gives threads 80 kb stacks, much smaller than + // JS_DEFAULT_STACK_SIZE (256 kb) + if (pthread_attr_setstacksize(&attr, 2 << 20)) // 2 MB, glibc default + fatal(1, "pthread_attr_setstacksize"); + if (pthread_create(thrd, &attr, start, arg)) + fatal(1, "pthread_create error"); + pthread_attr_destroy(&attr); +} + +static void join_thread(pthread_t thrd) +{ + if (pthread_join(thrd, NULL)) + fatal(1, "pthread_join error"); +} + +typedef struct { + js_mutex_t agent_mutex; + js_cond_t agent_cond; + /* list of Test262Agent.link */ + struct list_head agent_list; + js_mutex_t report_mutex; + /* list of AgentReport.link */ + struct list_head report_list; +} ThreadLocalStorage; + +static void init_thread_local_storage(ThreadLocalStorage *p) +{ + js_mutex_init(&p->agent_mutex); + js_cond_init(&p->agent_cond); + init_list_head(&p->agent_list); + js_mutex_init(&p->report_mutex); + init_list_head(&p->report_list); +} + +// points to parent thread's TLS in agent threads +static _Thread_local ThreadLocalStorage *tls; typedef struct { struct list_head link; @@ -441,6 +488,7 @@ typedef struct { uint8_t *broadcast_sab_buf; size_t broadcast_sab_size; int32_t broadcast_val; + ThreadLocalStorage *tls; } Test262Agent; typedef struct { @@ -451,15 +499,6 @@ typedef struct { static JSValue add_helpers1(JSContext *ctx); static void add_helpers(JSContext *ctx); -static pthread_mutex_t agent_mutex = PTHREAD_MUTEX_INITIALIZER; -static pthread_cond_t agent_cond = PTHREAD_COND_INITIALIZER; -/* list of Test262Agent.link */ -static struct list_head agent_list = LIST_HEAD_INIT(agent_list); - -static pthread_mutex_t report_mutex = PTHREAD_MUTEX_INITIALIZER; -/* list of AgentReport.link */ -static struct list_head report_list = LIST_HEAD_INIT(report_list); - static void *agent_start(void *arg) { Test262Agent *agent = arg; @@ -468,6 +507,7 @@ static void *agent_start(void *arg) JSValue ret_val; int ret; + tls = agent->tls; // shares thread-local storage with parent thread rt = JS_NewRuntime(); if (rt == NULL) { fatal(1, "JS_NewRuntime failure"); @@ -502,15 +542,15 @@ static void *agent_start(void *arg) } else { JSValue args[2]; - pthread_mutex_lock(&agent_mutex); + js_mutex_lock(&tls->agent_mutex); while (!agent->broadcast_pending) { - pthread_cond_wait(&agent_cond, &agent_mutex); + js_cond_wait(&tls->agent_cond, &tls->agent_mutex); } agent->broadcast_pending = FALSE; - pthread_cond_signal(&agent_cond); + js_cond_signal(&tls->agent_cond); - pthread_mutex_unlock(&agent_mutex); + js_mutex_unlock(&tls->agent_mutex); args[0] = JS_NewArrayBuffer(ctx, agent->broadcast_sab_buf, agent->broadcast_sab_size, @@ -540,7 +580,6 @@ static JSValue js_agent_start(JSContext *ctx, JSValue this_val, { const char *script; Test262Agent *agent; - pthread_attr_t attr; if (JS_GetContextOpaque(ctx) != NULL) return JS_ThrowTypeError(ctx, "cannot be called inside an agent"); @@ -553,14 +592,10 @@ static JSValue js_agent_start(JSContext *ctx, JSValue this_val, agent->broadcast_func = JS_UNDEFINED; agent->broadcast_sab = JS_UNDEFINED; agent->script = strdup(script); + agent->tls = tls; JS_FreeCString(ctx, script); - list_add_tail(&agent->link, &agent_list); - pthread_attr_init(&attr); - // musl libc gives threads 80 kb stacks, much smaller than - // JS_DEFAULT_STACK_SIZE (256 kb) - pthread_attr_setstacksize(&attr, 2 << 20); // 2 MB, glibc default - pthread_create(&agent->tid, &attr, agent_start, agent); - pthread_attr_destroy(&attr); + list_add_tail(&agent->link, &tls->agent_list); + start_thread(&agent->tid, agent_start, agent); return JS_UNDEFINED; } @@ -569,7 +604,7 @@ static void js_agent_free(JSContext *ctx) struct list_head *el, *el1; Test262Agent *agent; - list_for_each_safe(el, el1, &agent_list) { + list_for_each_safe(el, el1, &tls->agent_list) { agent = list_entry(el, Test262Agent, link); pthread_join(agent->tid, NULL); JS_FreeValue(ctx, agent->broadcast_sab); @@ -592,7 +627,7 @@ static BOOL is_broadcast_pending(void) { struct list_head *el; Test262Agent *agent; - list_for_each(el, &agent_list) { + list_for_each(el, &tls->agent_list) { agent = list_entry(el, Test262Agent, link); if (agent->broadcast_pending) return TRUE; @@ -621,8 +656,8 @@ static JSValue js_agent_broadcast(JSContext *ctx, JSValue this_val, /* broadcast the values and wait until all agents have started calling their callbacks */ - pthread_mutex_lock(&agent_mutex); - list_for_each(el, &agent_list) { + js_mutex_lock(&tls->agent_mutex); + list_for_each(el, &tls->agent_list) { agent = list_entry(el, Test262Agent, link); agent->broadcast_pending = TRUE; /* the shared array buffer is used by the thread, so increment @@ -632,12 +667,12 @@ static JSValue js_agent_broadcast(JSContext *ctx, JSValue this_val, agent->broadcast_sab_size = buf_size; agent->broadcast_val = val; } - pthread_cond_broadcast(&agent_cond); + js_cond_broadcast(&tls->agent_cond); while (is_broadcast_pending()) { - pthread_cond_wait(&agent_cond, &agent_mutex); + js_cond_wait(&tls->agent_cond, &tls->agent_mutex); } - pthread_mutex_unlock(&agent_mutex); + js_mutex_unlock(&tls->agent_mutex); return JS_UNDEFINED; } @@ -683,14 +718,14 @@ static JSValue js_agent_getReport(JSContext *ctx, JSValue this_val, AgentReport *rep; JSValue ret; - pthread_mutex_lock(&report_mutex); - if (list_empty(&report_list)) { + js_mutex_lock(&tls->report_mutex); + if (list_empty(&tls->report_list)) { rep = NULL; } else { - rep = list_entry(report_list.next, AgentReport, link); + rep = list_entry(tls->report_list.next, AgentReport, link); list_del(&rep->link); } - pthread_mutex_unlock(&report_mutex); + js_mutex_unlock(&tls->report_mutex); if (rep) { ret = JS_NewString(ctx, rep->str); free(rep->str); @@ -714,9 +749,9 @@ static JSValue js_agent_report(JSContext *ctx, JSValue this_val, rep->str = strdup(str); JS_FreeCString(ctx, str); - pthread_mutex_lock(&report_mutex); - list_add_tail(&rep->link, &report_list); - pthread_mutex_unlock(&report_mutex); + js_mutex_lock(&tls->report_mutex); + list_add_tail(&rep->link, &tls->report_list); + js_mutex_unlock(&tls->report_mutex); return JS_UNDEFINED; } @@ -742,7 +777,6 @@ static JSValue js_new_agent(JSContext *ctx) countof(js_agent_funcs)); return agent; } -#endif static JSValue js_createRealm(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) @@ -786,9 +820,7 @@ static JSValue add_helpers1(JSContext *ctx) JS_SetPropertyStr(ctx, obj262, "codePointRange", JS_NewCFunction(ctx, js_string_codePointRange, "codePointRange", 2)); -#ifdef CONFIG_AGENT JS_SetPropertyStr(ctx, obj262, "agent", js_new_agent(ctx)); -#endif JS_SetPropertyStr(ctx, obj262, "global", JS_DupValue(ctx, global_obj)); @@ -1072,10 +1104,6 @@ void load_config(const char *filename, const char *ignore) free(path); continue; } - if (str_equal(p, "reportfile")) { - report_filename = compose_path(base_name, q); - continue; - } case SECTION_EXCLUDE: namelist_add(&exclude_list, base_name, p); break; @@ -1203,8 +1231,8 @@ int longest_match(const char *str, const char *find, int pos, int *ppos, int lin static int eval_buf(JSContext *ctx, const char *buf, size_t buf_len, const char *filename, int is_test, int is_negative, - const char *error_type, FILE *outfile, int eval_flags, - int is_async, int *msec) + const char *error_type, int eval_flags, int is_async, + int *msec) { JSValue res_val, exception_val; int ret, error_line, pos, pos_line; @@ -1268,13 +1296,7 @@ static int eval_buf(JSContext *ctx, const char *buf, size_t buf_len, if (JS_IsException(res_val)) { exception_val = JS_GetException(ctx); is_error = JS_IsError(ctx, exception_val); - /* XXX: should get the filename and line number */ - if (outfile) { - if (!is_error) - fprintf(outfile, "%sThrow: ", (eval_flags & JS_EVAL_FLAG_STRICT) ? - "strict mode: " : ""); - js_print_262(ctx, JS_NULL, 1, &exception_val); - } + js_print_262(ctx, JS_NULL, 1, &exception_val); if (is_error) { JSValue name, stack; const char *stack_str; @@ -1288,9 +1310,6 @@ static int eval_buf(JSContext *ctx, const char *buf, size_t buf_len, const char *p; int len; - if (outfile) - fprintf(outfile, "%s", stack_str); - len = strlen(filename); p = strstr(stack_str, filename); if (p != NULL && p[len] == ':') { @@ -1352,7 +1371,7 @@ static int eval_buf(JSContext *ctx, const char *buf, size_t buf_len, } printf("%s:%d: %sOK, now has error %s\n", filename, error_line, strict_mode, msg); - fixed_errors++; + atomic_inc(&fixed_errors); } } else { if (!s) { // not yet reported @@ -1363,7 +1382,7 @@ static int eval_buf(JSContext *ctx, const char *buf, size_t buf_len, fprintf(error_out, "%s:%d: %sexpected error\n", filename, error_line, strict_mode); } - new_errors++; + atomic_inc(&new_errors); } } } else { // should not have error @@ -1382,9 +1401,9 @@ static int eval_buf(JSContext *ctx, const char *buf, size_t buf_len, if (s && (!str_equal(s, msg) || error_line != s_line)) { printf("%s:%d: %sprevious error: %s\n", filename, s_line, strict_mode, s); - changed_errors++; + atomic_inc(&changed_errors); } else { - new_errors++; + atomic_inc(&new_errors); } } } else { @@ -1417,7 +1436,7 @@ static int eval_file(JSContext *ctx, const char *base, const char *p, warning("cannot load %s", filename); goto fail; } - if (eval_buf(ctx, buf, buf_len, filename, FALSE, FALSE, NULL, stderr, + if (eval_buf(ctx, buf, buf_len, filename, FALSE, FALSE, NULL, eval_flags, FALSE, &msec)) { warning("error evaluating %s", filename); goto fail; @@ -1517,6 +1536,7 @@ static char *get_option(char **pp, int *state) void update_stats(JSRuntime *rt, const char *filename) { JSMemoryUsage stats; JS_ComputeMemoryUsage(rt, &stats); + js_mutex_lock(&stats_mutex); if (stats_count++ == 0) { stats_avg = stats_all = stats_min = stats_max = stats; free(stats_min_filename); @@ -1561,6 +1581,7 @@ void update_stats(JSRuntime *rt, const char *filename) { update(fast_array_elements); } #undef update + js_mutex_unlock(&stats_mutex); } int run_test_buf(const char *filename, char *harness, namelist_t *ip, @@ -1597,30 +1618,23 @@ int run_test_buf(const char *filename, char *harness, namelist_t *ip, } ret = eval_buf(ctx, buf, buf_len, filename, TRUE, is_negative, - error_type, outfile, eval_flags, is_async, msec); + error_type, eval_flags, is_async, msec); ret = (ret != 0); if (dump_memory) { update_stats(rt, filename); } -#ifdef CONFIG_AGENT js_agent_free(ctx); -#endif JS_FreeContext(ctx); JS_FreeRuntime(rt); - test_count++; - if (ret) { - test_failed++; - if (outfile) { - /* do not output a failure number to minimize diff */ - fprintf(outfile, " FAILED\n"); - } - } + atomic_inc(&test_count); + if (ret) + atomic_inc(&test_failed); return ret; } -int run_test(const char *filename, int index, int *msec) +int run_test(const char *filename, int *msec) { char harnessbuf[1024]; char *harness; @@ -1771,17 +1785,6 @@ int run_test(const char *filename, int index, int *msec) } } - if (outfile && index >= 0) { - fprintf(outfile, "%d: %s%s%s%s%s%s%s\n", index, filename, - is_nostrict ? " @noStrict" : "", - is_onlystrict ? " @onlyStrict" : "", - is_async ? " async" : "", - is_module ? " module" : "", - is_negative ? " @negative" : "", - skip ? " SKIPPED" : ""); - fflush(outfile); - } - use_strict = use_nostrict = 0; /* XXX: should remove 'test_mode' or simplify it just to force strict or non strict mode for single file tests */ @@ -1819,7 +1822,7 @@ int run_test(const char *filename, int index, int *msec) } if (skip || use_strict + use_nostrict == 0) { - test_skipped++; + atomic_inc(&test_skipped); ret = -2; } else { if (is_module) { @@ -1838,8 +1841,6 @@ int run_test(const char *filename, int index, int *msec) error_type, eval_flags | JS_EVAL_FLAG_STRICT, is_negative, is_async, can_block, msec); } - if (outfile && index >= 0 && *msec >= 100) - fprintf(outfile, " time: %d ms\n", *msec); } namelist_free(&include_list); free(error_type); @@ -1921,9 +1922,7 @@ int run_test262_harness_test(const char *filename, BOOL is_module) JS_FreeValue(ctx, promise); } free(buf); -#ifdef CONFIG_AGENT js_agent_free(ctx); -#endif JS_FreeContext(ctx); JS_FreeRuntime(rt); return ret_code; @@ -1931,58 +1930,53 @@ int run_test262_harness_test(const char *filename, BOOL is_module) clock_t last_clock; -void show_progress(int force) { - clock_t t = clock(); - if (force || !last_clock || (t - last_clock) > CLOCKS_PER_SEC / 20) { - last_clock = t; - if (compact) { - static int last_test_skipped; - static int last_test_failed; - static int dots; - char c = '.'; - if (test_skipped > last_test_skipped) c = '-'; - if (test_failed > last_test_failed) c = '!'; - last_test_skipped = test_skipped; - last_test_failed = test_failed; - fputc(c, stderr); - if (force || ++dots % 60 == 0) { - fprintf(stderr, " %d/%d/%d\n", - test_failed, test_count, test_skipped); - } - } else { - /* output progress indicator: erase end of line and return to col 0 */ - fprintf(stderr, "%d/%d/%d\033[K\r", - test_failed, test_count, test_skipped); - } +void *show_progress(void *unused) { + int interval = 1000*1000*1000 / 4; // 250 ms + + js_mutex_lock(&progress_mutex); + while (js_cond_timedwait(&progress_cond, &progress_mutex, interval)) { + /* output progress indicator: erase end of line and return to col 0 */ + fprintf(stderr, "%d/%d/%d\033[K\r", + atomic_load(&test_failed), + atomic_load(&test_count), + atomic_load(&test_skipped)); fflush(stderr); } + js_mutex_unlock(&progress_mutex); + return NULL; } -static int slow_test_threshold; +enum { INCLUDE, EXCLUDE, SKIP }; -void run_test_dir_list(namelist_t *lp, int start_index, int stop_index) +int include_exclude_or_skip(int i) // naming is hard... { - int i; + if (namelist_find(&exclude_list, test_list.array[i]) >= 0) + return EXCLUDE; + if (i < start_index) + return SKIP; + if (stop_index >= 0 && i > stop_index) + return SKIP; + return INCLUDE; +} - namelist_sort(lp); - for (i = 0; i < lp->count; i++) { - const char *p = lp->array[i]; - if (namelist_find(&exclude_list, p) >= 0) { - test_excluded++; - } else if (test_index < start_index) { - test_skipped++; - } else if (stop_index >= 0 && test_index > stop_index) { - test_skipped++; - } else { - int msec = 0; - run_test(p, test_index, &msec); - if (verbose > 1 || (slow_test_threshold && msec >= slow_test_threshold)) - fprintf(stderr, "%s (%d ms)\n", p, msec); - show_progress(FALSE); - } - test_index++; +void *run_test_dir_list(void *arg) +{ + const char *p; + int i, msec; + + tls = &(ThreadLocalStorage){}; + init_thread_local_storage(tls); + + for (i = (uintptr_t)arg; i < test_list.count; i += nthreads) { + if (INCLUDE != include_exclude_or_skip(i)) + continue; + p = test_list.array[i]; + msec = 0; + run_test(p, &msec); + if (verbose > 1 || (slow_test_threshold && msec >= slow_test_threshold)) + fprintf(stderr, "%s (%d ms)\n", p, msec); } - show_progress(TRUE); + return NULL; } void help(void) @@ -1997,15 +1991,14 @@ void help(void) "-s run tests in strict mode, skip @nostrict tests\n" "-E only run tests from the error file\n" "-u update error file\n" - "-C compact output mode; enabled when stderr is not a tty\n" "-v verbose: output error messages\n" "-vv like -v but also print test name and running time\n" "-T duration display tests taking more than 'duration' ms\n" + "-t threads number of parallel threads; default: numcpus - 1\n" "-c file read configuration from 'file'\n" "-d dir run all test files in directory tree 'dir'\n" "-e file load the known errors from 'file'\n" "-f file execute single test from 'file'\n" - "-r file set the report file name (default=none)\n" "-x file exclude tests listed in 'file'\n", JS_GetVersion()); exit(1); @@ -2021,7 +2014,7 @@ char *get_opt_arg(const char *option, char *arg) int main(int argc, char **argv) { - int optind, start_index, stop_index; + int i, optind; BOOL is_dir_list; BOOL only_check_errors = FALSE; const char *filename; @@ -2029,10 +2022,14 @@ int main(int argc, char **argv) BOOL is_test262_harness = FALSE; BOOL is_module = FALSE; -#if !defined(_WIN32) - compact = !isatty(STDERR_FILENO); + tls = &(ThreadLocalStorage){}; + init_thread_local_storage(tls); + js_mutex_init(&stats_mutex); + +#if !defined(__MINGW32__) /* Date tests assume California local time */ setenv("TZ", "America/Los_Angeles", 1); + nthreads = sysconf(_SC_NPROCESSORS_ONLN) - 1; #endif optind = 1; @@ -2041,7 +2038,7 @@ int main(int argc, char **argv) if (*arg != '-') break; optind++; - if (strstr("-c -d -e -x -f -r -E -T", arg)) + if (strstr("-c -d -e -x -f -E -T -t", arg)) optind++; if (strstr("-d -f", arg)) ignore = "testdir"; // run only the tests from -d or -f @@ -2070,8 +2067,6 @@ int main(int argc, char **argv) update_errors++; } else if (arg == strstr(arg, "-v")) { verbose += str_count(arg, "v"); - } else if (str_equal(arg, "-C")) { - compact++; } else if (str_equal(arg, "-c")) { load_config(get_opt_arg(arg, argv[optind++]), ignore); } else if (str_equal(arg, "-d")) { @@ -2082,12 +2077,12 @@ int main(int argc, char **argv) namelist_load(&exclude_list, get_opt_arg(arg, argv[optind++])); } else if (str_equal(arg, "-f")) { is_dir_list = FALSE; - } else if (str_equal(arg, "-r")) { - report_filename = strdup(get_opt_arg(arg, argv[optind++])); } else if (str_equal(arg, "-E")) { only_check_errors = TRUE; } else if (str_equal(arg, "-T")) { slow_test_threshold = atoi(get_opt_arg(arg, argv[optind++])); + } else if (str_equal(arg, "-t")) { + nthreads = atoi(get_opt_arg(arg, argv[optind++])); } else if (str_equal(arg, "-N")) { is_test262_harness = TRUE; } else if (str_equal(arg, "--module")) { @@ -2105,6 +2100,9 @@ int main(int argc, char **argv) return run_test262_harness_test(argv[optind], is_module); } + nthreads = max_int(nthreads, 1); + nthreads = min_int(nthreads, countof(threads)); + error_out = stdout; if (error_filename) { error_file = load_file(error_filename, NULL); @@ -2137,27 +2135,36 @@ int main(int argc, char **argv) stop_index = atoi(argv[optind++]); } } - if (!report_filename || str_equal(report_filename, "none")) { - outfile = NULL; - } else if (str_equal(report_filename, "-")) { - outfile = stdout; - } else { - outfile = fopen(report_filename, "wb"); - if (!outfile) { - perror_exit(1, report_filename); + // exclude_dir_list has already been sorted by update_exclude_dirs() + namelist_sort(&test_list); + namelist_sort(&exclude_list); + for (i = 0; i < test_list.count; i++) { + switch (include_exclude_or_skip(i)) { + case EXCLUDE: + test_excluded++; + break; + case SKIP: + test_skipped++; + break; } } - run_test_dir_list(&test_list, start_index, stop_index); - - if (outfile && outfile != stdout) { - fclose(outfile); - outfile = NULL; - } + js_cond_init(&progress_cond); + js_mutex_init(&progress_mutex); + start_thread(&progress_thread, show_progress, NULL); + for (i = 0; i < nthreads; i++) + start_thread(&threads[i], run_test_dir_list, (void *)(uintptr_t)i); + for (i = 0; i < nthreads; i++) + join_thread(threads[i]); + js_mutex_lock(&progress_mutex); + js_cond_signal(&progress_cond); + js_mutex_unlock(&progress_mutex); + join_thread(progress_thread); + js_mutex_destroy(&progress_mutex); + js_cond_destroy(&progress_cond); } else { - outfile = stdout; while (optind < argc) { int msec = 0; - run_test(argv[optind++], -1, &msec); + run_test(argv[optind++], &msec); } } @@ -2205,7 +2212,6 @@ int main(int argc, char **argv) free(harness_skip_features); free(error_file); free(error_filename); - free(report_filename); free(stats_min_filename); free(stats_max_filename); diff --git a/test262.conf b/test262.conf index 394d0a6..23860f1 100644 --- a/test262.conf +++ b/test262.conf @@ -37,9 +37,6 @@ errorfile=test262_errors.txt # exclude tests enumerated in this file (see also [exclude] section) #excludefile=test262_exclude.txt -# report test results to this file -reportfile=test262_report.txt - # enumerate tests from this directory testdir=test262/test