mirror of
https://github.com/bellard/quickjs.git
synced 2026-06-07 06:32:08 +00:00
added multi-threading support in run-test262 (initial patch by bnoordhuis) - fixed unsafe thread termination in show_progress() - use the number of physical cores by default - added -T option to force the number of threads - avoid hardcoding the maximum number of threads
Some checks are pending
ci / Linux (Ubuntu) (push) Waiting to run
ci / Linux LTO (push) Waiting to run
ci / Linux 32bit (push) Waiting to run
ci / linux-asan (push) Waiting to run
ci / linux-msan (push) Waiting to run
ci / linux-ubsan (push) Waiting to run
ci / macOS (push) Waiting to run
ci / macos-asan (push) Waiting to run
ci / macos-ubsan (push) Waiting to run
ci / freebsd (push) Waiting to run
ci / Cosmopolitan (push) Waiting to run
ci / MinGW Windows target (push) Waiting to run
ci / Windows MSYS2 (push) Waiting to run
ci / qemu-alpine (linux/386) (push) Waiting to run
ci / qemu-alpine (linux/arm/v6) (push) Waiting to run
ci / qemu-alpine (linux/arm/v7) (push) Waiting to run
ci / qemu-alpine (linux/arm64) (push) Waiting to run
ci / qemu-alpine (linux/ppc64le) (push) Waiting to run
ci / qemu-alpine (linux/riscv64) (push) Waiting to run
ci / qemu-alpine (linux/s390x) (push) Waiting to run
Some checks are pending
ci / Linux (Ubuntu) (push) Waiting to run
ci / Linux LTO (push) Waiting to run
ci / Linux 32bit (push) Waiting to run
ci / linux-asan (push) Waiting to run
ci / linux-msan (push) Waiting to run
ci / linux-ubsan (push) Waiting to run
ci / macOS (push) Waiting to run
ci / macos-asan (push) Waiting to run
ci / macos-ubsan (push) Waiting to run
ci / freebsd (push) Waiting to run
ci / Cosmopolitan (push) Waiting to run
ci / MinGW Windows target (push) Waiting to run
ci / Windows MSYS2 (push) Waiting to run
ci / qemu-alpine (linux/386) (push) Waiting to run
ci / qemu-alpine (linux/arm/v6) (push) Waiting to run
ci / qemu-alpine (linux/arm/v7) (push) Waiting to run
ci / qemu-alpine (linux/arm64) (push) Waiting to run
ci / qemu-alpine (linux/ppc64le) (push) Waiting to run
ci / qemu-alpine (linux/riscv64) (push) Waiting to run
ci / qemu-alpine (linux/s390x) (push) Waiting to run
This commit is contained in:
parent
458992ec8c
commit
2504823832
10
Makefile
10
Makefile
@ -53,6 +53,8 @@ PREFIX?=/usr/local
|
||||
#CONFIG_MSAN=y
|
||||
# use UB sanitizer
|
||||
#CONFIG_UBSAN=y
|
||||
# use thread sanitizer
|
||||
#CONFIG_TSAN=y
|
||||
|
||||
# TEST262 bootstrap config: commit id and shallow "since" parameter
|
||||
TEST262_COMMIT?=5c8206929d81b2d3d727ca6aac56c18358c8d790
|
||||
@ -192,6 +194,10 @@ ifdef CONFIG_UBSAN
|
||||
CFLAGS+=-fsanitize=undefined -fno-omit-frame-pointer
|
||||
LDFLAGS+=-fsanitize=undefined -fno-omit-frame-pointer
|
||||
endif
|
||||
ifdef CONFIG_TSAN
|
||||
CFLAGS+=-fsanitize=thread -fno-omit-frame-pointer
|
||||
LDFLAGS+=-fsanitize=thread -fno-omit-frame-pointer
|
||||
endif
|
||||
ifdef CONFIG_WIN32
|
||||
LDEXPORT=
|
||||
else
|
||||
@ -487,7 +493,7 @@ test2o: run-test262
|
||||
time ./run-test262 -t -m -c test262o.conf
|
||||
|
||||
test2o-update: run-test262
|
||||
./run-test262 -t -u -c test262o.conf
|
||||
./run-test262 -t -u -c test262o.conf -T 1
|
||||
endif
|
||||
|
||||
ifeq ($(wildcard test262/features.txt),)
|
||||
@ -502,7 +508,7 @@ test2: run-test262
|
||||
time ./run-test262 -t -m -c test262.conf -a
|
||||
|
||||
test2-update: run-test262
|
||||
./run-test262 -t -u -c test262.conf -a
|
||||
./run-test262 -t -u -c test262.conf -a -T 1
|
||||
|
||||
test2-check: run-test262
|
||||
time ./run-test262 -t -m -c test262.conf -E -a
|
||||
|
||||
502
run-test262.c
502
run-test262.c
@ -34,6 +34,11 @@
|
||||
#include <time.h>
|
||||
#include <dirent.h>
|
||||
#include <ftw.h>
|
||||
#include <stdatomic.h>
|
||||
#include <pthread.h>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include "cutils.h"
|
||||
#include "list.h"
|
||||
@ -45,13 +50,50 @@ typedef struct namelist_t {
|
||||
char **array;
|
||||
int count;
|
||||
int size;
|
||||
unsigned int sorted : 1;
|
||||
} namelist_t;
|
||||
|
||||
/* per execution thread context */
|
||||
typedef struct {
|
||||
pthread_mutex_t agent_mutex;
|
||||
pthread_cond_t agent_cond;
|
||||
/* list of Test262Agent.link */
|
||||
struct list_head agent_list;
|
||||
|
||||
pthread_mutex_t report_mutex;
|
||||
/* list of AgentReport.link */
|
||||
struct list_head report_list;
|
||||
|
||||
int async_done;
|
||||
} ThreadLocalStorage;
|
||||
|
||||
typedef struct {
|
||||
struct list_head link;
|
||||
ThreadLocalStorage *tls;
|
||||
pthread_t tid;
|
||||
char *script;
|
||||
JSValue broadcast_func;
|
||||
BOOL broadcast_pending;
|
||||
JSValue broadcast_sab; /* in the main context */
|
||||
uint8_t *broadcast_sab_buf;
|
||||
size_t broadcast_sab_size;
|
||||
int32_t broadcast_val;
|
||||
} Test262Agent;
|
||||
|
||||
typedef struct {
|
||||
struct list_head link;
|
||||
char *str;
|
||||
} AgentReport;
|
||||
|
||||
namelist_t test_list;
|
||||
namelist_t exclude_list;
|
||||
namelist_t exclude_dir_list;
|
||||
|
||||
int nthreads;
|
||||
pthread_t progress_thread;
|
||||
BOOL progress_exit_request;
|
||||
pthread_cond_t progress_cond;
|
||||
pthread_mutex_t progress_mutex;
|
||||
|
||||
FILE *outfile;
|
||||
enum test_mode_t {
|
||||
TEST_DEFAULT_NOSTRICT, /* run tests as nostrict unless test is flagged as strictonly */
|
||||
@ -70,6 +112,7 @@ int stats_count;
|
||||
JSMemoryUsage stats_all, stats_avg, stats_min, stats_max;
|
||||
char *stats_min_filename;
|
||||
char *stats_max_filename;
|
||||
pthread_mutex_t stats_mutex;
|
||||
int verbose;
|
||||
char *harness_dir;
|
||||
char *harness_exclude;
|
||||
@ -81,13 +124,110 @@ 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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
static int cpu_count(void)
|
||||
{
|
||||
DWORD_PTR procmask, sysmask;
|
||||
long count;
|
||||
int i;
|
||||
|
||||
count = 0;
|
||||
if (GetProcessAffinityMask(GetCurrentProcess(), &procmask, &sysmask))
|
||||
for (i = 0; i < 8 * sizeof(procmask); i++)
|
||||
count += 1 & (procmask >> i);
|
||||
return count;
|
||||
}
|
||||
#else
|
||||
/* return the number of available physical cores or -1 if not available */
|
||||
static int get_cpu_info_physical_cores(void)
|
||||
{
|
||||
FILE *f;
|
||||
int nb_cores, physical_id;
|
||||
char line[1024], *p;
|
||||
char *field, *value;
|
||||
int len;
|
||||
|
||||
f = fopen("/proc/cpuinfo", "rb");
|
||||
if (!f)
|
||||
return -1;
|
||||
nb_cores = 0;
|
||||
physical_id = -1;
|
||||
for(;;) {
|
||||
if (fgets(line, sizeof(line), f) == NULL)
|
||||
break;
|
||||
len = strlen(line);
|
||||
while (len > 0 && isspace(line[len - 1]))
|
||||
len--;
|
||||
line[len] = '\0';
|
||||
field = line;
|
||||
p = line;
|
||||
if (*p == '#')
|
||||
continue;
|
||||
while (*p != ':' && *p != '\0')
|
||||
p++;
|
||||
if (*p == '\0')
|
||||
continue;
|
||||
*p = '\0';
|
||||
p++;
|
||||
while (isspace(*p))
|
||||
p++;
|
||||
value = p;
|
||||
|
||||
len = strlen(field);
|
||||
while (len > 0 && isspace(field[len - 1]))
|
||||
len--;
|
||||
field[len] = '\0';
|
||||
|
||||
// printf("'%s' '%s'\n", field, value);
|
||||
if (!strcmp(field, "cpu cores")) {
|
||||
if (nb_cores == 0) {
|
||||
nb_cores = strtol(value, NULL, 0);
|
||||
}
|
||||
} else if (!strcmp(field, "physical id")) {
|
||||
physical_id = max_int(physical_id, strtol(value, NULL, 0));
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
// printf("nb_cores=%d physical_id=%d\n", nb_cores, physical_id);
|
||||
if (nb_cores <= 0 || physical_id < 0)
|
||||
return -1;
|
||||
return nb_cores * (physical_id + 1);
|
||||
}
|
||||
|
||||
static int cpu_count(void)
|
||||
{
|
||||
int n = get_cpu_info_physical_cores();
|
||||
if (n <= 0)
|
||||
n = 1;
|
||||
return n;
|
||||
}
|
||||
#endif /* !_WIN32 */
|
||||
|
||||
static void init_thread_local_storage(ThreadLocalStorage *tls)
|
||||
{
|
||||
memset(tls, 0, sizeof(*tls));
|
||||
pthread_mutex_init(&tls->agent_mutex, NULL);
|
||||
pthread_cond_init(&tls->agent_cond, NULL);
|
||||
init_list_head(&tls->agent_list);
|
||||
|
||||
pthread_mutex_init(&tls->report_mutex, NULL);
|
||||
init_list_head(&tls->report_list);
|
||||
}
|
||||
|
||||
void warning(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
@ -260,16 +400,13 @@ void namelist_sort(namelist_t *lp)
|
||||
}
|
||||
lp->count = count;
|
||||
}
|
||||
lp->sorted = 1;
|
||||
}
|
||||
|
||||
int namelist_find(namelist_t *lp, const char *name)
|
||||
/* the list must be sorted */
|
||||
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);
|
||||
@ -379,33 +516,37 @@ static void js_print_value_write(void *opaque, const char *buf, size_t len)
|
||||
static JSValue js_print(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv)
|
||||
{
|
||||
ThreadLocalStorage *tls = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
|
||||
int i;
|
||||
JSValueConst v;
|
||||
|
||||
if (outfile) {
|
||||
for (i = 0; i < argc; i++) {
|
||||
if (i != 0)
|
||||
fputc(' ', outfile);
|
||||
v = argv[i];
|
||||
if (JS_IsString(v)) {
|
||||
const char *str;
|
||||
size_t len;
|
||||
str = JS_ToCStringLen(ctx, &len, v);
|
||||
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 */
|
||||
}
|
||||
for (i = 0; i < argc; i++) {
|
||||
if (i != 0 && outfile)
|
||||
fputc(' ', outfile);
|
||||
v = argv[i];
|
||||
if (JS_IsString(v)) {
|
||||
const char *str;
|
||||
size_t len;
|
||||
str = JS_ToCStringLen(ctx, &len, v);
|
||||
if (!str)
|
||||
return JS_EXCEPTION;
|
||||
if (!strcmp(str, "Test262:AsyncTestComplete")) {
|
||||
tls->async_done++;
|
||||
} else if (strstart(str, "Test262:AsyncTestFailure", NULL)) {
|
||||
tls->async_done = 2; /* force an error */
|
||||
}
|
||||
if (outfile) {
|
||||
fwrite(str, 1, len, outfile);
|
||||
JS_FreeCString(ctx, str);
|
||||
} else {
|
||||
}
|
||||
JS_FreeCString(ctx, str);
|
||||
} else {
|
||||
if (outfile) {
|
||||
JS_PrintValue(ctx, js_print_value_write, outfile, v, NULL);
|
||||
}
|
||||
}
|
||||
fputc('\n', outfile);
|
||||
}
|
||||
if (outfile)
|
||||
fputc('\n', outfile);
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
@ -430,40 +571,13 @@ static JSValue js_evalScript(JSContext *ctx, JSValue this_val,
|
||||
return ret;
|
||||
}
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
typedef struct {
|
||||
struct list_head link;
|
||||
pthread_t tid;
|
||||
char *script;
|
||||
JSValue broadcast_func;
|
||||
BOOL broadcast_pending;
|
||||
JSValue broadcast_sab; /* in the main context */
|
||||
uint8_t *broadcast_sab_buf;
|
||||
size_t broadcast_sab_size;
|
||||
int32_t broadcast_val;
|
||||
} Test262Agent;
|
||||
|
||||
typedef struct {
|
||||
struct list_head link;
|
||||
char *str;
|
||||
} AgentReport;
|
||||
|
||||
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;
|
||||
ThreadLocalStorage *tls = agent->tls;
|
||||
JSRuntime *rt;
|
||||
JSContext *ctx;
|
||||
JSValue ret_val;
|
||||
@ -473,6 +587,7 @@ static void *agent_start(void *arg)
|
||||
if (rt == NULL) {
|
||||
fatal(1, "JS_NewRuntime failure");
|
||||
}
|
||||
JS_SetRuntimeOpaque(rt, tls);
|
||||
ctx = JS_NewContext(rt);
|
||||
if (ctx == NULL) {
|
||||
JS_FreeRuntime(rt);
|
||||
@ -502,15 +617,15 @@ static void *agent_start(void *arg)
|
||||
} else {
|
||||
JSValue args[2];
|
||||
|
||||
pthread_mutex_lock(&agent_mutex);
|
||||
pthread_mutex_lock(&tls->agent_mutex);
|
||||
while (!agent->broadcast_pending) {
|
||||
pthread_cond_wait(&agent_cond, &agent_mutex);
|
||||
pthread_cond_wait(&tls->agent_cond, &tls->agent_mutex);
|
||||
}
|
||||
|
||||
agent->broadcast_pending = FALSE;
|
||||
pthread_cond_signal(&agent_cond);
|
||||
pthread_cond_signal(&tls->agent_cond);
|
||||
|
||||
pthread_mutex_unlock(&agent_mutex);
|
||||
pthread_mutex_unlock(&tls->agent_mutex);
|
||||
|
||||
args[0] = JS_NewArrayBuffer(ctx, agent->broadcast_sab_buf,
|
||||
agent->broadcast_sab_size,
|
||||
@ -538,6 +653,7 @@ static void *agent_start(void *arg)
|
||||
static JSValue js_agent_start(JSContext *ctx, JSValue this_val,
|
||||
int argc, JSValue *argv)
|
||||
{
|
||||
ThreadLocalStorage *tls = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
|
||||
const char *script;
|
||||
Test262Agent *agent;
|
||||
pthread_attr_t attr;
|
||||
@ -550,11 +666,12 @@ static JSValue js_agent_start(JSContext *ctx, JSValue this_val,
|
||||
return JS_EXCEPTION;
|
||||
agent = malloc(sizeof(*agent));
|
||||
memset(agent, 0, sizeof(*agent));
|
||||
agent->tls = tls;
|
||||
agent->broadcast_func = JS_UNDEFINED;
|
||||
agent->broadcast_sab = JS_UNDEFINED;
|
||||
agent->script = strdup(script);
|
||||
JS_FreeCString(ctx, script);
|
||||
list_add_tail(&agent->link, &agent_list);
|
||||
list_add_tail(&agent->link, &tls->agent_list);
|
||||
pthread_attr_init(&attr);
|
||||
// musl libc gives threads 80 kb stacks, much smaller than
|
||||
// JS_DEFAULT_STACK_SIZE (256 kb)
|
||||
@ -566,10 +683,11 @@ static JSValue js_agent_start(JSContext *ctx, JSValue this_val,
|
||||
|
||||
static void js_agent_free(JSContext *ctx)
|
||||
{
|
||||
ThreadLocalStorage *tls = JS_GetRuntimeOpaque(JS_GetRuntime(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);
|
||||
@ -588,11 +706,11 @@ static JSValue js_agent_leaving(JSContext *ctx, JSValue this_val,
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
static BOOL is_broadcast_pending(void)
|
||||
static BOOL is_broadcast_pending(ThreadLocalStorage *tls)
|
||||
{
|
||||
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;
|
||||
@ -603,6 +721,7 @@ static BOOL is_broadcast_pending(void)
|
||||
static JSValue js_agent_broadcast(JSContext *ctx, JSValue this_val,
|
||||
int argc, JSValue *argv)
|
||||
{
|
||||
ThreadLocalStorage *tls = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
|
||||
JSValueConst sab = argv[0];
|
||||
struct list_head *el;
|
||||
Test262Agent *agent;
|
||||
@ -621,8 +740,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) {
|
||||
pthread_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 +751,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);
|
||||
pthread_cond_broadcast(&tls->agent_cond);
|
||||
|
||||
while (is_broadcast_pending()) {
|
||||
pthread_cond_wait(&agent_cond, &agent_mutex);
|
||||
while (is_broadcast_pending(tls)) {
|
||||
pthread_cond_wait(&tls->agent_cond, &tls->agent_mutex);
|
||||
}
|
||||
pthread_mutex_unlock(&agent_mutex);
|
||||
pthread_mutex_unlock(&tls->agent_mutex);
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
@ -680,17 +799,18 @@ static JSValue js_agent_monotonicNow(JSContext *ctx, JSValue this_val,
|
||||
static JSValue js_agent_getReport(JSContext *ctx, JSValue this_val,
|
||||
int argc, JSValue *argv)
|
||||
{
|
||||
ThreadLocalStorage *tls = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
|
||||
AgentReport *rep;
|
||||
JSValue ret;
|
||||
|
||||
pthread_mutex_lock(&report_mutex);
|
||||
if (list_empty(&report_list)) {
|
||||
pthread_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);
|
||||
pthread_mutex_unlock(&tls->report_mutex);
|
||||
if (rep) {
|
||||
ret = JS_NewString(ctx, rep->str);
|
||||
free(rep->str);
|
||||
@ -704,6 +824,7 @@ static JSValue js_agent_getReport(JSContext *ctx, JSValue this_val,
|
||||
static JSValue js_agent_report(JSContext *ctx, JSValue this_val,
|
||||
int argc, JSValue *argv)
|
||||
{
|
||||
ThreadLocalStorage *tls = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
|
||||
const char *str;
|
||||
AgentReport *rep;
|
||||
|
||||
@ -714,9 +835,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);
|
||||
pthread_mutex_lock(&tls->report_mutex);
|
||||
list_add_tail(&rep->link, &tls->report_list);
|
||||
pthread_mutex_unlock(&tls->report_mutex);
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
@ -939,7 +1060,7 @@ void update_exclude_dirs(void)
|
||||
char *name;
|
||||
int i, j, count;
|
||||
|
||||
/* split directpries from exclude_list */
|
||||
/* split directories from exclude_list */
|
||||
for (count = i = 0; i < ep->count; i++) {
|
||||
name = ep->array[i];
|
||||
if (has_suffix(name, "/")) {
|
||||
@ -1237,6 +1358,7 @@ static int eval_buf(JSContext *ctx, const char *buf, size_t buf_len,
|
||||
const char *error_type, FILE *outfile, int eval_flags,
|
||||
int is_async)
|
||||
{
|
||||
ThreadLocalStorage *tls = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
|
||||
JSValue res_val, exception_val;
|
||||
int ret, error_line, pos, pos_line;
|
||||
BOOL is_error, has_error_line, ret_promise;
|
||||
@ -1250,7 +1372,7 @@ static int eval_buf(JSContext *ctx, const char *buf, size_t buf_len,
|
||||
|
||||
/* a module evaluation returns a promise */
|
||||
ret_promise = ((eval_flags & JS_EVAL_TYPE_MODULE) != 0);
|
||||
async_done = 0; /* counter of "Test262:AsyncTestComplete" messages */
|
||||
tls->async_done = 0; /* counter of "Test262:AsyncTestComplete" messages */
|
||||
|
||||
res_val = JS_Eval(ctx, buf, buf_len, filename, eval_flags);
|
||||
|
||||
@ -1269,7 +1391,7 @@ static int eval_buf(JSContext *ctx, const char *buf, size_t buf_len,
|
||||
} else if (ret == 0) {
|
||||
if (is_async) {
|
||||
/* test if the test called $DONE() once */
|
||||
if (async_done != 1) {
|
||||
if (tls->async_done != 1) {
|
||||
res_val = JS_ThrowTypeError(ctx, "$DONE() not called");
|
||||
} else {
|
||||
res_val = JS_UNDEFINED;
|
||||
@ -1537,6 +1659,8 @@ static char *get_option(char **pp, int *state)
|
||||
void update_stats(JSRuntime *rt, const char *filename) {
|
||||
JSMemoryUsage stats;
|
||||
JS_ComputeMemoryUsage(rt, &stats);
|
||||
|
||||
pthread_mutex_lock(&stats_mutex);
|
||||
if (stats_count++ == 0) {
|
||||
stats_avg = stats_all = stats_min = stats_max = stats;
|
||||
stats_min_filename = strdup(filename);
|
||||
@ -1579,9 +1703,11 @@ void update_stats(JSRuntime *rt, const char *filename) {
|
||||
update(fast_array_elements);
|
||||
}
|
||||
#undef update
|
||||
pthread_mutex_unlock(&stats_mutex);
|
||||
}
|
||||
|
||||
int run_test_buf(const char *filename, const char *harness, namelist_t *ip,
|
||||
int run_test_buf(ThreadLocalStorage *tls,
|
||||
const char *filename, const char *harness, namelist_t *ip,
|
||||
char *buf, size_t buf_len, const char* error_type,
|
||||
int eval_flags, BOOL is_negative, BOOL is_async,
|
||||
BOOL can_block)
|
||||
@ -1594,6 +1720,7 @@ int run_test_buf(const char *filename, const char *harness, namelist_t *ip,
|
||||
if (rt == NULL) {
|
||||
fatal(1, "JS_NewRuntime failure");
|
||||
}
|
||||
JS_SetRuntimeOpaque(rt, tls);
|
||||
ctx = JS_NewContext(rt);
|
||||
if (ctx == NULL) {
|
||||
JS_FreeRuntime(rt);
|
||||
@ -1626,9 +1753,9 @@ int run_test_buf(const char *filename, const char *harness, namelist_t *ip,
|
||||
JS_FreeContext(ctx);
|
||||
JS_FreeRuntime(rt);
|
||||
|
||||
test_count++;
|
||||
atomic_inc(&test_count);
|
||||
if (ret) {
|
||||
test_failed++;
|
||||
atomic_inc(&test_failed);
|
||||
if (outfile) {
|
||||
/* do not output a failure number to minimize diff */
|
||||
fprintf(outfile, " FAILED\n");
|
||||
@ -1637,7 +1764,7 @@ int run_test_buf(const char *filename, const char *harness, namelist_t *ip,
|
||||
return ret;
|
||||
}
|
||||
|
||||
int run_test(const char *filename, int index)
|
||||
int run_test(ThreadLocalStorage *tls, const char *filename, int index)
|
||||
{
|
||||
char harnessbuf[1024];
|
||||
char *harness;
|
||||
@ -1843,7 +1970,7 @@ int run_test(const char *filename, int index)
|
||||
}
|
||||
|
||||
if (skip || use_strict + use_nostrict == 0) {
|
||||
test_skipped++;
|
||||
atomic_inc(&test_skipped);
|
||||
ret = -2;
|
||||
} else {
|
||||
clock_t clocks;
|
||||
@ -1856,12 +1983,12 @@ int run_test(const char *filename, int index)
|
||||
clocks = clock();
|
||||
ret = 0;
|
||||
if (use_nostrict) {
|
||||
ret = run_test_buf(filename, harness, ip, buf, buf_len,
|
||||
ret = run_test_buf(tls, filename, harness, ip, buf, buf_len,
|
||||
error_type, eval_flags, is_negative, is_async,
|
||||
can_block);
|
||||
}
|
||||
if (use_strict) {
|
||||
ret |= run_test_buf(filename, harness, ip, buf, buf_len,
|
||||
ret |= run_test_buf(tls, filename, harness, ip, buf, buf_len,
|
||||
error_type, eval_flags | JS_EVAL_FLAG_STRICT,
|
||||
is_negative, is_async, can_block);
|
||||
}
|
||||
@ -1879,7 +2006,8 @@ int run_test(const char *filename, int index)
|
||||
}
|
||||
|
||||
/* run a test when called by test262-harness+eshost */
|
||||
int run_test262_harness_test(const char *filename, BOOL is_module, BOOL can_block)
|
||||
int run_test262_harness_test(ThreadLocalStorage *tls,
|
||||
const char *filename, BOOL is_module, BOOL can_block)
|
||||
{
|
||||
JSRuntime *rt;
|
||||
JSContext *ctx;
|
||||
@ -1894,6 +2022,7 @@ int run_test262_harness_test(const char *filename, BOOL is_module, BOOL can_bloc
|
||||
if (rt == NULL) {
|
||||
fatal(1, "JS_NewRuntime failure");
|
||||
}
|
||||
JS_SetRuntimeOpaque(rt, tls);
|
||||
ctx = JS_NewContext(rt);
|
||||
if (ctx == NULL) {
|
||||
JS_FreeRuntime(rt);
|
||||
@ -1954,70 +2083,109 @@ int run_test262_harness_test(const char *filename, BOOL is_module, BOOL can_bloc
|
||||
return ret_code;
|
||||
}
|
||||
|
||||
clock_t last_clock;
|
||||
static int pthread_cond_timedwait2(pthread_cond_t *cond, pthread_mutex_t *mutex, int timeout)
|
||||
{
|
||||
struct timespec ts;
|
||||
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
ts.tv_sec += timeout / 1000;
|
||||
ts.tv_nsec += (timeout % 1000) * 1000000;
|
||||
if (ts.tv_nsec >= 1000000000) {
|
||||
ts.tv_nsec -= 1000000000;
|
||||
ts.tv_sec++;
|
||||
}
|
||||
return pthread_cond_timedwait(cond, mutex, &ts);
|
||||
}
|
||||
|
||||
void *show_progress(void *opaque)
|
||||
{
|
||||
int test_skipped1, test_failed1, test_count1;
|
||||
|
||||
pthread_mutex_lock(&progress_mutex);
|
||||
for(;;) {
|
||||
pthread_cond_timedwait2(&progress_cond, &progress_mutex, 50);
|
||||
|
||||
test_failed1 = atomic_load(&test_failed);
|
||||
test_count1 = atomic_load(&test_count);
|
||||
test_skipped1 = atomic_load(&test_skipped);
|
||||
|
||||
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)
|
||||
|
||||
if (test_skipped1 > last_test_skipped)
|
||||
c = '-';
|
||||
if (test_failed > last_test_failed)
|
||||
if (test_failed1 > last_test_failed)
|
||||
c = '!';
|
||||
last_test_skipped = test_skipped;
|
||||
last_test_failed = test_failed;
|
||||
last_test_skipped = test_skipped1;
|
||||
last_test_failed = test_failed1;
|
||||
|
||||
fputc(c, stderr);
|
||||
if (force || ++dots % 60 == 0) {
|
||||
if (progress_exit_request || ++dots % 60 == 0) {
|
||||
fprintf(stderr, " %d/%d/%d\n",
|
||||
test_failed, test_count, test_skipped);
|
||||
test_failed1, test_count1, test_skipped1);
|
||||
}
|
||||
} 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);
|
||||
test_failed1, test_count1, test_skipped1);
|
||||
}
|
||||
fflush(stderr);
|
||||
if (progress_exit_request)
|
||||
break;
|
||||
}
|
||||
pthread_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++) {
|
||||
typedef struct {
|
||||
pthread_t tid;
|
||||
int thread_index;
|
||||
} RunTestDirThread;
|
||||
|
||||
void *run_test_dir_list(void *opaque)
|
||||
{
|
||||
RunTestDirThread *th = opaque;
|
||||
ThreadLocalStorage tls_s, *tls = &tls_s;
|
||||
namelist_t *lp = &test_list;
|
||||
int i;
|
||||
|
||||
init_thread_local_storage(tls);
|
||||
|
||||
for (i = th->thread_index; i < lp->count; i += nthreads) {
|
||||
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++;
|
||||
int ti;
|
||||
if (INCLUDE != include_exclude_or_skip(i))
|
||||
continue;
|
||||
|
||||
if (slow_test_threshold != 0) {
|
||||
ti = get_clock_ms();
|
||||
} else {
|
||||
int ti;
|
||||
if (slow_test_threshold != 0) {
|
||||
ti = get_clock_ms();
|
||||
} else {
|
||||
ti = 0;
|
||||
}
|
||||
run_test(p, test_index);
|
||||
if (slow_test_threshold != 0) {
|
||||
ti = get_clock_ms() - ti;
|
||||
if (ti >= slow_test_threshold)
|
||||
fprintf(stderr, "\n%s (%d ms)\n", p, ti);
|
||||
}
|
||||
show_progress(FALSE);
|
||||
ti = 0;
|
||||
}
|
||||
run_test(tls, p, i);
|
||||
if (slow_test_threshold != 0) {
|
||||
ti = get_clock_ms() - ti;
|
||||
if (ti >= slow_test_threshold)
|
||||
fprintf(stderr, "\n%s (%d ms)\n", p, ti);
|
||||
}
|
||||
test_index++;
|
||||
}
|
||||
show_progress(TRUE);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void help(void)
|
||||
@ -2035,7 +2203,8 @@ void help(void)
|
||||
"-t show timings\n"
|
||||
"-u update error file\n"
|
||||
"-v verbose: output error messages\n"
|
||||
"-T duration display tests taking more than 'duration' ms\n"
|
||||
"-D duration display tests taking more than 'duration' ms\n"
|
||||
"-T threads number of parallel threads\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"
|
||||
@ -2056,7 +2225,8 @@ char *get_opt_arg(const char *option, char *arg)
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int optind, start_index, stop_index;
|
||||
ThreadLocalStorage tls_s, *tls = &tls_s;
|
||||
int optind;
|
||||
BOOL is_dir_list;
|
||||
BOOL only_check_errors = FALSE;
|
||||
const char *filename;
|
||||
@ -2066,6 +2236,9 @@ int main(int argc, char **argv)
|
||||
BOOL can_block = TRUE;
|
||||
BOOL count_skipped_features = FALSE;
|
||||
clock_t clocks;
|
||||
|
||||
init_thread_local_storage(tls);
|
||||
pthread_mutex_init(&stats_mutex, NULL);
|
||||
|
||||
#if !defined(_WIN32)
|
||||
compact = !isatty(STDERR_FILENO);
|
||||
@ -2079,7 +2252,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 -r -E -D -T", arg))
|
||||
optind++;
|
||||
if (strstr("-d -f", arg))
|
||||
ignore = "testdir"; // run only the tests from -d or -f
|
||||
@ -2126,8 +2299,10 @@ int main(int argc, char **argv)
|
||||
report_filename = get_opt_arg(arg, argv[optind++]);
|
||||
} else if (str_equal(arg, "-E")) {
|
||||
only_check_errors = TRUE;
|
||||
} else if (str_equal(arg, "-T")) {
|
||||
} else if (str_equal(arg, "-D")) {
|
||||
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")) {
|
||||
@ -2146,9 +2321,18 @@ int main(int argc, char **argv)
|
||||
help();
|
||||
|
||||
if (is_test262_harness) {
|
||||
return run_test262_harness_test(argv[optind], is_module, can_block);
|
||||
return run_test262_harness_test(tls, argv[optind], is_module, can_block);
|
||||
}
|
||||
|
||||
if (nthreads == 0) {
|
||||
nthreads = cpu_count();
|
||||
if (nthreads >= 8) {
|
||||
// minus one to not (over)commit the system completely
|
||||
nthreads--;
|
||||
}
|
||||
}
|
||||
nthreads = max_int(nthreads, 1);
|
||||
|
||||
error_out = stdout;
|
||||
if (error_filename) {
|
||||
error_file = load_file(error_filename, NULL);
|
||||
@ -2179,10 +2363,14 @@ int main(int argc, char **argv)
|
||||
}
|
||||
|
||||
if (is_dir_list) {
|
||||
RunTestDirThread *threads;
|
||||
int i;
|
||||
|
||||
if (optind < argc && !isdigit((unsigned char)argv[optind][0])) {
|
||||
filename = argv[optind++];
|
||||
namelist_load(&test_list, filename);
|
||||
}
|
||||
|
||||
start_index = 0;
|
||||
stop_index = -1;
|
||||
if (optind < argc) {
|
||||
@ -2191,7 +2379,8 @@ int main(int argc, char **argv)
|
||||
stop_index = atoi(argv[optind++]);
|
||||
}
|
||||
}
|
||||
if (!report_filename || str_equal(report_filename, "none")) {
|
||||
/* XXX: could reorder the report and the errors when nthreads > 1 */
|
||||
if (!report_filename || str_equal(report_filename, "none") || nthreads > 1) {
|
||||
outfile = NULL;
|
||||
} else if (str_equal(report_filename, "-")) {
|
||||
outfile = stdout;
|
||||
@ -2201,7 +2390,50 @@ int main(int argc, char **argv)
|
||||
perror_exit(1, report_filename);
|
||||
}
|
||||
}
|
||||
run_test_dir_list(&test_list, start_index, stop_index);
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_cond_init(&progress_cond, NULL);
|
||||
pthread_mutex_init(&progress_mutex, NULL);
|
||||
pthread_create(&progress_thread, NULL, show_progress, NULL);
|
||||
|
||||
threads = malloc(sizeof(threads[0]) * nthreads);
|
||||
for (i = 0; i < nthreads; i++) {
|
||||
RunTestDirThread *th = &threads[i];
|
||||
pthread_attr_t attr;
|
||||
|
||||
th->thread_index = i;
|
||||
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setstacksize(&attr, 2 << 20); // 2 MB, glibc default
|
||||
pthread_create(&th->tid, &attr, run_test_dir_list, th);
|
||||
pthread_attr_destroy(&attr);
|
||||
}
|
||||
for (i = 0; i < nthreads; i++)
|
||||
pthread_join(threads[i].tid, NULL);
|
||||
free(threads);
|
||||
|
||||
pthread_mutex_lock(&progress_mutex);
|
||||
progress_exit_request = TRUE;
|
||||
pthread_cond_signal(&progress_cond);
|
||||
pthread_mutex_unlock(&progress_mutex);
|
||||
pthread_join(progress_thread, NULL);
|
||||
|
||||
pthread_mutex_destroy(&progress_mutex);
|
||||
pthread_cond_destroy(&progress_cond);
|
||||
|
||||
if (outfile && outfile != stdout) {
|
||||
fclose(outfile);
|
||||
@ -2210,7 +2442,7 @@ int main(int argc, char **argv)
|
||||
} else {
|
||||
outfile = stdout;
|
||||
while (optind < argc) {
|
||||
run_test(argv[optind++], -1);
|
||||
run_test(tls, argv[optind++], -1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2269,7 +2501,7 @@ int main(int argc, char **argv)
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
if (show_timings)
|
||||
fprintf(stderr, "Total time: %.3fs\n", (double)clocks / CLOCKS_PER_SEC);
|
||||
fprintf(stderr, "Total user time: %.3fs (nthreads=%d)\n", (double)clocks / CLOCKS_PER_SEC, nthreads);
|
||||
}
|
||||
|
||||
if (error_out && error_out != stdout) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user