You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2107 lines
61 KiB
2107 lines
61 KiB
/* |
|
* ECMA Test 262 Runner for QuickJS |
|
* |
|
* Copyright (c) 2017-2021 Fabrice Bellard |
|
* Copyright (c) 2017-2021 Charlie Gordon |
|
* |
|
* Permission is hereby granted, free of charge, to any person obtaining a copy |
|
* of this software and associated documentation files (the "Software"), to deal |
|
* in the Software without restriction, including without limitation the rights |
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
* copies of the Software, and to permit persons to whom the Software is |
|
* furnished to do so, subject to the following conditions: |
|
* |
|
* The above copyright notice and this permission notice shall be included in |
|
* all copies or substantial portions of the Software. |
|
* |
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|
* THE SOFTWARE. |
|
*/ |
|
#include <stdlib.h> |
|
#include <stdio.h> |
|
#include <stdarg.h> |
|
#include <inttypes.h> |
|
#include <string.h> |
|
#include <assert.h> |
|
#include <ctype.h> |
|
#include <unistd.h> |
|
#include <errno.h> |
|
#include <time.h> |
|
#include <dirent.h> |
|
#include <ftw.h> |
|
|
|
#include "cutils.h" |
|
#include "list.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; |
|
|
|
namelist_t test_list; |
|
namelist_t exclude_list; |
|
namelist_t exclude_dir_list; |
|
|
|
FILE *outfile; |
|
enum test_mode_t { |
|
TEST_DEFAULT_NOSTRICT, /* run tests as nostrict unless test is flagged as strictonly */ |
|
TEST_DEFAULT_STRICT, /* run tests as strict unless test is flagged as nostrict */ |
|
TEST_NOSTRICT, /* run tests as nostrict, skip strictonly tests */ |
|
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 skip_async; |
|
int skip_module; |
|
int new_style; |
|
int dump_memory; |
|
int stats_count; |
|
JSMemoryUsage stats_all, stats_avg, stats_min, stats_max; |
|
char *stats_min_filename; |
|
char *stats_max_filename; |
|
int verbose; |
|
char *harness_dir; |
|
char *harness_exclude; |
|
char *harness_features; |
|
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; |
|
|
|
void warning(const char *, ...) __attribute__((__format__(__printf__, 1, 2))); |
|
void fatal(int, const char *, ...) __attribute__((__format__(__printf__, 2, 3))); |
|
|
|
void warning(const char *fmt, ...) |
|
{ |
|
va_list ap; |
|
|
|
fflush(stdout); |
|
fprintf(stderr, "%s: ", CMD_NAME); |
|
va_start(ap, fmt); |
|
vfprintf(stderr, fmt, ap); |
|
va_end(ap); |
|
fputc('\n', stderr); |
|
} |
|
|
|
void fatal(int errcode, const char *fmt, ...) |
|
{ |
|
va_list ap; |
|
|
|
fflush(stdout); |
|
fprintf(stderr, "%s: ", CMD_NAME); |
|
va_start(ap, fmt); |
|
vfprintf(stderr, fmt, ap); |
|
va_end(ap); |
|
fputc('\n', stderr); |
|
|
|
exit(errcode); |
|
} |
|
|
|
void perror_exit(int errcode, const char *s) |
|
{ |
|
fflush(stdout); |
|
fprintf(stderr, "%s: ", CMD_NAME); |
|
perror(s); |
|
exit(errcode); |
|
} |
|
|
|
char *strdup_len(const char *str, int len) |
|
{ |
|
char *p = malloc(len + 1); |
|
memcpy(p, str, len); |
|
p[len] = '\0'; |
|
return p; |
|
} |
|
|
|
static inline int str_equal(const char *a, const char *b) { |
|
return !strcmp(a, b); |
|
} |
|
|
|
char *str_append(char **pp, const char *sep, const char *str) { |
|
char *res, *p; |
|
size_t len = 0; |
|
p = *pp; |
|
if (p) { |
|
len = strlen(p) + strlen(sep); |
|
} |
|
res = malloc(len + strlen(str) + 1); |
|
if (p) { |
|
strcpy(res, p); |
|
strcat(res, sep); |
|
} |
|
strcpy(res + len, str); |
|
free(p); |
|
return *pp = res; |
|
} |
|
|
|
char *str_strip(char *p) |
|
{ |
|
size_t len = strlen(p); |
|
while (len > 0 && isspace((unsigned char)p[len - 1])) |
|
p[--len] = '\0'; |
|
while (isspace((unsigned char)*p)) |
|
p++; |
|
return p; |
|
} |
|
|
|
int has_prefix(const char *str, const char *prefix) |
|
{ |
|
return !strncmp(str, prefix, strlen(prefix)); |
|
} |
|
|
|
char *skip_prefix(const char *str, const char *prefix) |
|
{ |
|
int i; |
|
for (i = 0;; i++) { |
|
if (prefix[i] == '\0') { /* skip the prefix */ |
|
str += i; |
|
break; |
|
} |
|
if (str[i] != prefix[i]) |
|
break; |
|
} |
|
return (char *)str; |
|
} |
|
|
|
char *get_basename(const char *filename) |
|
{ |
|
char *p; |
|
|
|
p = strrchr(filename, '/'); |
|
if (!p) |
|
return NULL; |
|
return strdup_len(filename, p - filename); |
|
} |
|
|
|
char *compose_path(const char *path, const char *name) |
|
{ |
|
int path_len, name_len; |
|
char *d, *q; |
|
|
|
if (!path || path[0] == '\0' || *name == '/') { |
|
d = strdup(name); |
|
} else { |
|
path_len = strlen(path); |
|
name_len = strlen(name); |
|
d = malloc(path_len + 1 + name_len + 1); |
|
if (d) { |
|
q = d; |
|
memcpy(q, path, path_len); |
|
q += path_len; |
|
if (path[path_len - 1] != '/') |
|
*q++ = '/'; |
|
memcpy(q, name, name_len + 1); |
|
} |
|
} |
|
return d; |
|
} |
|
|
|
int namelist_cmp(const char *a, const char *b) |
|
{ |
|
/* compare strings in modified lexicographical order */ |
|
for (;;) { |
|
int ca = (unsigned char)*a++; |
|
int cb = (unsigned char)*b++; |
|
if (isdigit(ca) && isdigit(cb)) { |
|
int na = ca - '0'; |
|
int nb = cb - '0'; |
|
while (isdigit(ca = (unsigned char)*a++)) |
|
na = na * 10 + ca - '0'; |
|
while (isdigit(cb = (unsigned char)*b++)) |
|
nb = nb * 10 + cb - '0'; |
|
if (na < nb) |
|
return -1; |
|
if (na > nb) |
|
return +1; |
|
} |
|
if (ca < cb) |
|
return -1; |
|
if (ca > cb) |
|
return +1; |
|
if (ca == '\0') |
|
return 0; |
|
} |
|
} |
|
|
|
int namelist_cmp_indirect(const void *a, const void *b) |
|
{ |
|
return namelist_cmp(*(const char **)a, *(const char **)b); |
|
} |
|
|
|
void namelist_sort(namelist_t *lp) |
|
{ |
|
int i, count; |
|
if (lp->count > 1) { |
|
qsort(lp->array, lp->count, sizeof(*lp->array), namelist_cmp_indirect); |
|
/* remove duplicates */ |
|
for (count = i = 1; i < lp->count; i++) { |
|
if (namelist_cmp(lp->array[count - 1], lp->array[i]) == 0) { |
|
free(lp->array[i]); |
|
} else { |
|
lp->array[count++] = lp->array[i]; |
|
} |
|
} |
|
lp->count = count; |
|
} |
|
lp->sorted = 1; |
|
} |
|
|
|
int namelist_find(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); |
|
if (cmp < 0) |
|
a = m + 1; |
|
else if (cmp > 0) |
|
b = m; |
|
else |
|
return m; |
|
} |
|
return -1; |
|
} |
|
|
|
void namelist_add(namelist_t *lp, const char *base, const char *name) |
|
{ |
|
char *s; |
|
|
|
s = compose_path(base, name); |
|
if (!s) |
|
goto fail; |
|
if (lp->count == lp->size) { |
|
size_t newsize = lp->size + (lp->size >> 1) + 4; |
|
char **a = realloc(lp->array, sizeof(lp->array[0]) * newsize); |
|
if (!a) |
|
goto fail; |
|
lp->array = a; |
|
lp->size = newsize; |
|
} |
|
lp->array[lp->count] = s; |
|
lp->count++; |
|
return; |
|
fail: |
|
fatal(1, "allocation failure\n"); |
|
} |
|
|
|
void namelist_load(namelist_t *lp, const char *filename) |
|
{ |
|
char buf[1024]; |
|
char *base_name; |
|
FILE *f; |
|
|
|
f = fopen(filename, "rb"); |
|
if (!f) { |
|
perror_exit(1, filename); |
|
} |
|
base_name = get_basename(filename); |
|
|
|
while (fgets(buf, sizeof(buf), f) != NULL) { |
|
char *p = str_strip(buf); |
|
if (*p == '#' || *p == ';' || *p == '\0') |
|
continue; /* line comment */ |
|
|
|
namelist_add(lp, base_name, p); |
|
} |
|
free(base_name); |
|
fclose(f); |
|
} |
|
|
|
void namelist_add_from_error_file(namelist_t *lp, const char *file) |
|
{ |
|
const char *p, *p0; |
|
char *pp; |
|
|
|
for (p = file; (p = strstr(p, ".js:")) != NULL; p++) { |
|
for (p0 = p; p0 > file && p0[-1] != '\n'; p0--) |
|
continue; |
|
pp = strdup_len(p0, p + 3 - p0); |
|
namelist_add(lp, NULL, pp); |
|
free(pp); |
|
} |
|
} |
|
|
|
void namelist_free(namelist_t *lp) |
|
{ |
|
while (lp->count > 0) { |
|
free(lp->array[--lp->count]); |
|
} |
|
free(lp->array); |
|
lp->array = NULL; |
|
lp->size = 0; |
|
} |
|
|
|
static int add_test_file(const char *filename, const struct stat *ptr, int flag) |
|
{ |
|
namelist_t *lp = &test_list; |
|
if (has_suffix(filename, ".js") && !has_suffix(filename, "_FIXTURE.js")) |
|
namelist_add(lp, NULL, filename); |
|
return 0; |
|
} |
|
|
|
/* find js files from the directory tree and sort the list */ |
|
static void enumerate_tests(const char *path) |
|
{ |
|
namelist_t *lp = &test_list; |
|
int start = lp->count; |
|
ftw(path, add_test_file, 100); |
|
qsort(lp->array + start, lp->count - start, sizeof(*lp->array), |
|
namelist_cmp_indirect); |
|
} |
|
|
|
static JSValue js_print(JSContext *ctx, JSValueConst this_val, |
|
int argc, JSValueConst *argv) |
|
{ |
|
int i; |
|
const char *str; |
|
|
|
if (outfile) { |
|
for (i = 0; i < argc; i++) { |
|
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); |
|
} |
|
return JS_UNDEFINED; |
|
} |
|
|
|
static JSValue js_detachArrayBuffer(JSContext *ctx, JSValue this_val, |
|
int argc, JSValue *argv) |
|
{ |
|
JS_DetachArrayBuffer(ctx, argv[0]); |
|
return JS_UNDEFINED; |
|
} |
|
|
|
static JSValue js_evalScript(JSContext *ctx, JSValue this_val, |
|
int argc, JSValue *argv) |
|
{ |
|
const char *str; |
|
size_t len; |
|
JSValue ret; |
|
str = JS_ToCStringLen(ctx, &len, argv[0]); |
|
if (!str) |
|
return JS_EXCEPTION; |
|
ret = JS_Eval(ctx, str, len, "<evalScript>", JS_EVAL_TYPE_GLOBAL); |
|
JS_FreeCString(ctx, str); |
|
return ret; |
|
} |
|
|
|
#ifdef CONFIG_AGENT |
|
|
|
#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; |
|
JSRuntime *rt; |
|
JSContext *ctx; |
|
JSValue ret_val; |
|
int ret; |
|
|
|
rt = JS_NewRuntime(); |
|
if (rt == NULL) { |
|
fatal(1, "JS_NewRuntime failure"); |
|
} |
|
ctx = JS_NewContext(rt); |
|
if (ctx == NULL) { |
|
JS_FreeRuntime(rt); |
|
fatal(1, "JS_NewContext failure"); |
|
} |
|
JS_SetContextOpaque(ctx, agent); |
|
JS_SetRuntimeInfo(rt, "agent"); |
|
JS_SetCanBlock(rt, TRUE); |
|
|
|
add_helpers(ctx); |
|
ret_val = JS_Eval(ctx, agent->script, strlen(agent->script), |
|
"<evalScript>", JS_EVAL_TYPE_GLOBAL); |
|
free(agent->script); |
|
agent->script = NULL; |
|
if (JS_IsException(ret_val)) |
|
js_std_dump_error(ctx); |
|
JS_FreeValue(ctx, ret_val); |
|
|
|
for(;;) { |
|
JSContext *ctx1; |
|
ret = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1); |
|
if (ret < 0) { |
|
js_std_dump_error(ctx); |
|
break; |
|
} else if (ret == 0) { |
|
if (JS_IsUndefined(agent->broadcast_func)) { |
|
break; |
|
} else { |
|
JSValue args[2]; |
|
|
|
pthread_mutex_lock(&agent_mutex); |
|
while (!agent->broadcast_pending) { |
|
pthread_cond_wait(&agent_cond, &agent_mutex); |
|
} |
|
|
|
agent->broadcast_pending = FALSE; |
|
pthread_cond_signal(&agent_cond); |
|
|
|
pthread_mutex_unlock(&agent_mutex); |
|
|
|
args[0] = JS_NewArrayBuffer(ctx, agent->broadcast_sab_buf, |
|
agent->broadcast_sab_size, |
|
NULL, NULL, TRUE); |
|
args[1] = JS_NewInt32(ctx, agent->broadcast_val); |
|
ret_val = JS_Call(ctx, agent->broadcast_func, JS_UNDEFINED, |
|
2, (JSValueConst *)args); |
|
JS_FreeValue(ctx, args[0]); |
|
JS_FreeValue(ctx, args[1]); |
|
if (JS_IsException(ret_val)) |
|
js_std_dump_error(ctx); |
|
JS_FreeValue(ctx, ret_val); |
|
JS_FreeValue(ctx, agent->broadcast_func); |
|
agent->broadcast_func = JS_UNDEFINED; |
|
} |
|
} |
|
} |
|
JS_FreeValue(ctx, agent->broadcast_func); |
|
|
|
JS_FreeContext(ctx); |
|
JS_FreeRuntime(rt); |
|
return NULL; |
|
} |
|
|
|
static JSValue js_agent_start(JSContext *ctx, JSValue this_val, |
|
int argc, JSValue *argv) |
|
{ |
|
const char *script; |
|
Test262Agent *agent; |
|
|
|
if (JS_GetContextOpaque(ctx) != NULL) |
|
return JS_ThrowTypeError(ctx, "cannot be called inside an agent"); |
|
|
|
script = JS_ToCString(ctx, argv[0]); |
|
if (!script) |
|
return JS_EXCEPTION; |
|
agent = malloc(sizeof(*agent)); |
|
memset(agent, 0, sizeof(*agent)); |
|
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); |
|
pthread_create(&agent->tid, NULL, agent_start, agent); |
|
return JS_UNDEFINED; |
|
} |
|
|
|
static void js_agent_free(JSContext *ctx) |
|
{ |
|
struct list_head *el, *el1; |
|
Test262Agent *agent; |
|
|
|
list_for_each_safe(el, el1, &agent_list) { |
|
agent = list_entry(el, Test262Agent, link); |
|
pthread_join(agent->tid, NULL); |
|
JS_FreeValue(ctx, agent->broadcast_sab); |
|
list_del(&agent->link); |
|
free(agent); |
|
} |
|
} |
|
|
|
static JSValue js_agent_leaving(JSContext *ctx, JSValue this_val, |
|
int argc, JSValue *argv) |
|
{ |
|
Test262Agent *agent = JS_GetContextOpaque(ctx); |
|
if (!agent) |
|
return JS_ThrowTypeError(ctx, "must be called inside an agent"); |
|
/* nothing to do */ |
|
return JS_UNDEFINED; |
|
} |
|
|
|
static BOOL is_broadcast_pending(void) |
|
{ |
|
struct list_head *el; |
|
Test262Agent *agent; |
|
list_for_each(el, &agent_list) { |
|
agent = list_entry(el, Test262Agent, link); |
|
if (agent->broadcast_pending) |
|
return TRUE; |
|
} |
|
return FALSE; |
|
} |
|
|
|
static JSValue js_agent_broadcast(JSContext *ctx, JSValue this_val, |
|
int argc, JSValue *argv) |
|
{ |
|
JSValueConst sab = argv[0]; |
|
struct list_head *el; |
|
Test262Agent *agent; |
|
uint8_t *buf; |
|
size_t buf_size; |
|
int32_t val; |
|
|
|
if (JS_GetContextOpaque(ctx) != NULL) |
|
return JS_ThrowTypeError(ctx, "cannot be called inside an agent"); |
|
|
|
buf = JS_GetArrayBuffer(ctx, &buf_size, sab); |
|
if (!buf) |
|
return JS_EXCEPTION; |
|
if (JS_ToInt32(ctx, &val, argv[1])) |
|
return JS_EXCEPTION; |
|
|
|
/* broadcast the values and wait until all agents have started |
|
calling their callbacks */ |
|
pthread_mutex_lock(&agent_mutex); |
|
list_for_each(el, &agent_list) { |
|
agent = list_entry(el, Test262Agent, link); |
|
agent->broadcast_pending = TRUE; |
|
/* the shared array buffer is used by the thread, so increment |
|
its refcount */ |
|
agent->broadcast_sab = JS_DupValue(ctx, sab); |
|
agent->broadcast_sab_buf = buf; |
|
agent->broadcast_sab_size = buf_size; |
|
agent->broadcast_val = val; |
|
} |
|
pthread_cond_broadcast(&agent_cond); |
|
|
|
while (is_broadcast_pending()) { |
|
pthread_cond_wait(&agent_cond, &agent_mutex); |
|
} |
|
pthread_mutex_unlock(&agent_mutex); |
|
return JS_UNDEFINED; |
|
} |
|
|
|
static JSValue js_agent_receiveBroadcast(JSContext *ctx, JSValue this_val, |
|
int argc, JSValue *argv) |
|
{ |
|
Test262Agent *agent = JS_GetContextOpaque(ctx); |
|
if (!agent) |
|
return JS_ThrowTypeError(ctx, "must be called inside an agent"); |
|
if (!JS_IsFunction(ctx, argv[0])) |
|
return JS_ThrowTypeError(ctx, "expecting function"); |
|
JS_FreeValue(ctx, agent->broadcast_func); |
|
agent->broadcast_func = JS_DupValue(ctx, argv[0]); |
|
return JS_UNDEFINED; |
|
} |
|
|
|
static JSValue js_agent_sleep(JSContext *ctx, JSValue this_val, |
|
int argc, JSValue *argv) |
|
{ |
|
uint32_t duration; |
|
if (JS_ToUint32(ctx, &duration, argv[0])) |
|
return JS_EXCEPTION; |
|
usleep(duration * 1000); |
|
return JS_UNDEFINED; |
|
} |
|
|
|
static int64_t get_clock_ms(void) |
|
{ |
|
struct timespec ts; |
|
clock_gettime(CLOCK_MONOTONIC, &ts); |
|
return (uint64_t)ts.tv_sec * 1000 + (ts.tv_nsec / 1000000); |
|
} |
|
|
|
static JSValue js_agent_monotonicNow(JSContext *ctx, JSValue this_val, |
|
int argc, JSValue *argv) |
|
{ |
|
return JS_NewInt64(ctx, get_clock_ms()); |
|
} |
|
|
|
static JSValue js_agent_getReport(JSContext *ctx, JSValue this_val, |
|
int argc, JSValue *argv) |
|
{ |
|
AgentReport *rep; |
|
JSValue ret; |
|
|
|
pthread_mutex_lock(&report_mutex); |
|
if (list_empty(&report_list)) { |
|
rep = NULL; |
|
} else { |
|
rep = list_entry(report_list.next, AgentReport, link); |
|
list_del(&rep->link); |
|
} |
|
pthread_mutex_unlock(&report_mutex); |
|
if (rep) { |
|
ret = JS_NewString(ctx, rep->str); |
|
free(rep->str); |
|
free(rep); |
|
} else { |
|
ret = JS_NULL; |
|
} |
|
return ret; |
|
} |
|
|
|
static JSValue js_agent_report(JSContext *ctx, JSValue this_val, |
|
int argc, JSValue *argv) |
|
{ |
|
const char *str; |
|
AgentReport *rep; |
|
|
|
str = JS_ToCString(ctx, argv[0]); |
|
if (!str) |
|
return JS_EXCEPTION; |
|
rep = malloc(sizeof(*rep)); |
|
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); |
|
return JS_UNDEFINED; |
|
} |
|
|
|
static const JSCFunctionListEntry js_agent_funcs[] = { |
|
/* only in main */ |
|
JS_CFUNC_DEF("start", 1, js_agent_start ), |
|
JS_CFUNC_DEF("getReport", 0, js_agent_getReport ), |
|
JS_CFUNC_DEF("broadcast", 2, js_agent_broadcast ), |
|
/* only in agent */ |
|
JS_CFUNC_DEF("report", 1, js_agent_report ), |
|
JS_CFUNC_DEF("leaving", 0, js_agent_leaving ), |
|
JS_CFUNC_DEF("receiveBroadcast", 1, js_agent_receiveBroadcast ), |
|
/* in both */ |
|
JS_CFUNC_DEF("sleep", 1, js_agent_sleep ), |
|
JS_CFUNC_DEF("monotonicNow", 0, js_agent_monotonicNow ), |
|
}; |
|
|
|
static JSValue js_new_agent(JSContext *ctx) |
|
{ |
|
JSValue agent; |
|
agent = JS_NewObject(ctx); |
|
JS_SetPropertyFunctionList(ctx, agent, js_agent_funcs, |
|
countof(js_agent_funcs)); |
|
return agent; |
|
} |
|
#endif |
|
|
|
static JSValue js_createRealm(JSContext *ctx, JSValue this_val, |
|
int argc, JSValue *argv) |
|
{ |
|
JSContext *ctx1; |
|
JSValue ret; |
|
|
|
ctx1 = JS_NewContext(JS_GetRuntime(ctx)); |
|
if (!ctx1) |
|
return JS_ThrowOutOfMemory(ctx); |
|
ret = add_helpers1(ctx1); |
|
/* ctx1 has a refcount so it stays alive */ |
|
JS_FreeContext(ctx1); |
|
return ret; |
|
} |
|
|
|
static JSValue js_IsHTMLDDA(JSContext *ctx, JSValue this_val, |
|
int argc, JSValue *argv) |
|
{ |
|
return JS_NULL; |
|
} |
|
|
|
static JSValue add_helpers1(JSContext *ctx) |
|
{ |
|
JSValue global_obj; |
|
JSValue obj262, obj; |
|
|
|
global_obj = JS_GetGlobalObject(ctx); |
|
|
|
JS_SetPropertyStr(ctx, global_obj, "print", |
|
JS_NewCFunction(ctx, js_print, "print", 1)); |
|
|
|
/* $262 special object used by the tests */ |
|
obj262 = JS_NewObject(ctx); |
|
JS_SetPropertyStr(ctx, obj262, "detachArrayBuffer", |
|
JS_NewCFunction(ctx, js_detachArrayBuffer, |
|
"detachArrayBuffer", 1)); |
|
JS_SetPropertyStr(ctx, obj262, "evalScript", |
|
JS_NewCFunction(ctx, js_evalScript, |
|
"evalScript", 1)); |
|
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)); |
|
JS_SetPropertyStr(ctx, obj262, "createRealm", |
|
JS_NewCFunction(ctx, js_createRealm, |
|
"createRealm", 0)); |
|
obj = JS_NewCFunction(ctx, js_IsHTMLDDA, "IsHTMLDDA", 0); |
|
JS_SetIsHTMLDDA(ctx, obj); |
|
JS_SetPropertyStr(ctx, obj262, "IsHTMLDDA", obj); |
|
|
|
JS_SetPropertyStr(ctx, global_obj, "$262", JS_DupValue(ctx, obj262)); |
|
|
|
JS_FreeValue(ctx, global_obj); |
|
return obj262; |
|
} |
|
|
|
static void add_helpers(JSContext *ctx) |
|
{ |
|
JS_FreeValue(ctx, add_helpers1(ctx)); |
|
} |
|
|
|
static char *load_file(const char *filename, size_t *lenp) |
|
{ |
|
char *buf; |
|
size_t buf_len; |
|
buf = (char *)js_load_file(NULL, &buf_len, filename); |
|
if (!buf) |
|
perror_exit(1, filename); |
|
if (lenp) |
|
*lenp = buf_len; |
|
return buf; |
|
} |
|
|
|
static JSModuleDef *js_module_loader_test(JSContext *ctx, |
|
const char *module_name, void *opaque) |
|
{ |
|
size_t buf_len; |
|
uint8_t *buf; |
|
JSModuleDef *m; |
|
JSValue func_val; |
|
|
|
buf = js_load_file(ctx, &buf_len, module_name); |
|
if (!buf) { |
|
JS_ThrowReferenceError(ctx, "could not load module filename '%s'", |
|
module_name); |
|
return NULL; |
|
} |
|
|
|
/* compile the module */ |
|
func_val = JS_Eval(ctx, (char *)buf, buf_len, module_name, |
|
JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); |
|
js_free(ctx, buf); |
|
if (JS_IsException(func_val)) |
|
return NULL; |
|
/* the module is already referenced, so we must free it */ |
|
m = JS_VALUE_GET_PTR(func_val); |
|
JS_FreeValue(ctx, func_val); |
|
return m; |
|
} |
|
|
|
int is_line_sep(char c) |
|
{ |
|
return (c == '\0' || c == '\n' || c == '\r'); |
|
} |
|
|
|
char *find_line(const char *str, const char *line) |
|
{ |
|
if (str) { |
|
const char *p; |
|
int len = strlen(line); |
|
for (p = str; (p = strstr(p, line)) != NULL; p += len + 1) { |
|
if ((p == str || is_line_sep(p[-1])) && is_line_sep(p[len])) |
|
return (char *)p; |
|
} |
|
} |
|
return NULL; |
|
} |
|
|
|
int is_word_sep(char c) |
|
{ |
|
return (c == '\0' || isspace((unsigned char)c) || c == ','); |
|
} |
|
|
|
char *find_word(const char *str, const char *word) |
|
{ |
|
const char *p; |
|
int len = strlen(word); |
|
if (str && len) { |
|
for (p = str; (p = strstr(p, word)) != NULL; p += len) { |
|
if ((p == str || is_word_sep(p[-1])) && is_word_sep(p[len])) |
|
return (char *)p; |
|
} |
|
} |
|
return NULL; |
|
} |
|
|
|
/* handle exclude directories */ |
|
void update_exclude_dirs(void) |
|
{ |
|
namelist_t *lp = &test_list; |
|
namelist_t *ep = &exclude_list; |
|
namelist_t *dp = &exclude_dir_list; |
|
char *name; |
|
int i, j, count; |
|
|
|
/* split directpries from exclude_list */ |
|
for (count = i = 0; i < ep->count; i++) { |
|
name = ep->array[i]; |
|
if (has_suffix(name, "/")) { |
|
namelist_add(dp, NULL, name); |
|
free(name); |
|
} else { |
|
ep->array[count++] = name; |
|
} |
|
} |
|
ep->count = count; |
|
|
|
namelist_sort(dp); |
|
|
|
/* filter out excluded directories */ |
|
for (count = i = 0; i < lp->count; i++) { |
|
name = lp->array[i]; |
|
for (j = 0; j < dp->count; j++) { |
|
if (has_prefix(name, dp->array[j])) { |
|
test_excluded++; |
|
free(name); |
|
name = NULL; |
|
break; |
|
} |
|
} |
|
if (name) { |
|
lp->array[count++] = name; |
|
} |
|
} |
|
lp->count = count; |
|
} |
|
|
|
void load_config(const char *filename) |
|
{ |
|
char buf[1024]; |
|
FILE *f; |
|
char *base_name; |
|
enum { |
|
SECTION_NONE = 0, |
|
SECTION_CONFIG, |
|
SECTION_EXCLUDE, |
|
SECTION_FEATURES, |
|
SECTION_TESTS, |
|
} section = SECTION_NONE; |
|
int lineno = 0; |
|
|
|
f = fopen(filename, "rb"); |
|
if (!f) { |
|
perror_exit(1, filename); |
|
} |
|
base_name = get_basename(filename); |
|
|
|
while (fgets(buf, sizeof(buf), f) != NULL) { |
|
char *p, *q; |
|
lineno++; |
|
p = str_strip(buf); |
|
if (*p == '#' || *p == ';' || *p == '\0') |
|
continue; /* line comment */ |
|
|
|
if (*p == "[]"[0]) { |
|
/* new section */ |
|
p++; |
|
p[strcspn(p, "]")] = '\0'; |
|
if (str_equal(p, "config")) |
|
section = SECTION_CONFIG; |
|
else if (str_equal(p, "exclude")) |
|
section = SECTION_EXCLUDE; |
|
else if (str_equal(p, "features")) |
|
section = SECTION_FEATURES; |
|
else if (str_equal(p, "tests")) |
|
section = SECTION_TESTS; |
|
else |
|
section = SECTION_NONE; |
|
continue; |
|
} |
|
q = strchr(p, '='); |
|
if (q) { |
|
/* setting: name=value */ |
|
*q++ = '\0'; |
|
q = str_strip(q); |
|
} |
|
switch (section) { |
|
case SECTION_CONFIG: |
|
if (!q) { |
|
printf("%s:%d: syntax error\n", filename, lineno); |
|
continue; |
|
} |
|
if (str_equal(p, "style")) { |
|
new_style = str_equal(q, "new"); |
|
continue; |
|
} |
|
if (str_equal(p, "testdir")) { |
|
char *testdir = compose_path(base_name, q); |
|
enumerate_tests(testdir); |
|
free(testdir); |
|
continue; |
|
} |
|
if (str_equal(p, "harnessdir")) { |
|
harness_dir = compose_path(base_name, q); |
|
continue; |
|
} |
|
if (str_equal(p, "harnessexclude")) { |
|
str_append(&harness_exclude, " ", q); |
|
continue; |
|
} |
|
if (str_equal(p, "features")) { |
|
str_append(&harness_features, " ", q); |
|
continue; |
|
} |
|
if (str_equal(p, "skip-features")) { |
|
str_append(&harness_skip_features, " ", q); |
|
continue; |
|
} |
|
if (str_equal(p, "mode")) { |
|
if (str_equal(q, "default") || str_equal(q, "default-nostrict")) |
|
test_mode = TEST_DEFAULT_NOSTRICT; |
|
else if (str_equal(q, "default-strict")) |
|
test_mode = TEST_DEFAULT_STRICT; |
|
else if (str_equal(q, "nostrict")) |
|
test_mode = TEST_NOSTRICT; |
|
else if (str_equal(q, "strict")) |
|
test_mode = TEST_STRICT; |
|
else if (str_equal(q, "all") || str_equal(q, "both")) |
|
test_mode = TEST_ALL; |
|
else |
|
fatal(2, "unknown test mode: %s", q); |
|
continue; |
|
} |
|
if (str_equal(p, "strict")) { |
|
if (str_equal(q, "skip") || str_equal(q, "no")) |
|
test_mode = TEST_NOSTRICT; |
|
continue; |
|
} |
|
if (str_equal(p, "nostrict")) { |
|
if (str_equal(q, "skip") || str_equal(q, "no")) |
|
test_mode = TEST_STRICT; |
|
continue; |
|
} |
|
if (str_equal(p, "async")) { |
|
skip_async = !str_equal(q, "yes"); |
|
continue; |
|
} |
|
if (str_equal(p, "module")) { |
|
skip_module = !str_equal(q, "yes"); |
|
continue; |
|
} |
|
if (str_equal(p, "verbose")) { |
|
verbose = str_equal(q, "yes"); |
|
continue; |
|
} |
|
if (str_equal(p, "errorfile")) { |
|
error_filename = compose_path(base_name, q); |
|
continue; |
|
} |
|
if (str_equal(p, "excludefile")) { |
|
char *path = compose_path(base_name, q); |
|
namelist_load(&exclude_list, path); |
|
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; |
|
case SECTION_FEATURES: |
|
if (!q || str_equal(q, "yes")) |
|
str_append(&harness_features, " ", p); |
|
else |
|
str_append(&harness_skip_features, " ", p); |
|
break; |
|
case SECTION_TESTS: |
|
namelist_add(&test_list, base_name, p); |
|
break; |
|
default: |
|
/* ignore settings in other sections */ |
|
break; |
|
} |
|
} |
|
fclose(f); |
|
free(base_name); |
|
} |
|
|
|
char *find_error(const char *filename, int *pline, int is_strict) |
|
{ |
|
if (error_file) { |
|
size_t len = strlen(filename); |
|
const char *p, *q, *r; |
|
int line; |
|
|
|
for (p = error_file; (p = strstr(p, filename)) != NULL; p += len) { |
|
if ((p == error_file || p[-1] == '\n' || p[-1] == '(') && p[len] == ':') { |
|
q = p + len; |
|
line = 1; |
|
if (*q == ':') { |
|
line = strtol(q + 1, (char**)&q, 10); |
|
if (*q == ':') |
|
q++; |
|
} |
|
while (*q == ' ') { |
|
q++; |
|
} |
|
/* check strict mode indicator */ |
|
if (!strstart(q, "strict mode: ", &q) != !is_strict) |
|
continue; |
|
r = q = skip_prefix(q, "unexpected error: "); |
|
r += strcspn(r, "\n"); |
|
while (r[0] == '\n' && r[1] && strncmp(r + 1, filename, 8)) { |
|
r++; |
|
r += strcspn(r, "\n"); |
|
} |
|
if (pline) |
|
*pline = line; |
|
return strdup_len(q, r - q); |
|
} |
|
} |
|
} |
|
return NULL; |
|
} |
|
|
|
int skip_comments(const char *str, int line, int *pline) |
|
{ |
|
const char *p; |
|
int c; |
|
|
|
p = str; |
|
while ((c = (unsigned char)*p++) != '\0') { |
|
if (isspace(c)) { |
|
if (c == '\n') |
|
line++; |
|
continue; |
|
} |
|
if (c == '/' && *p == '/') { |
|
while (*++p && *p != '\n') |
|
continue; |
|
continue; |
|
} |
|
if (c == '/' && *p == '*') { |
|
for (p += 1; *p; p++) { |
|
if (*p == '\n') { |
|
line++; |
|
continue; |
|
} |
|
if (*p == '*' && p[1] == '/') { |
|
p += 2; |
|
break; |
|
} |
|
} |
|
continue; |
|
} |
|
break; |
|
} |
|
if (pline) |
|
*pline = line; |
|
|
|
return p - str; |
|
} |
|
|
|
int longest_match(const char *str, const char *find, int pos, int *ppos, int line, int *pline) |
|
{ |
|
int len, maxlen; |
|
|
|
maxlen = 0; |
|
|
|
if (*find) { |
|
const char *p; |
|
for (p = str + pos; *p; p++) { |
|
if (*p == *find) { |
|
for (len = 1; p[len] && p[len] == find[len]; len++) |
|
continue; |
|
if (len > maxlen) { |
|
maxlen = len; |
|
if (ppos) |
|
*ppos = p - str; |
|
if (pline) |
|
*pline = line; |
|
if (!find[len]) |
|
break; |
|
} |
|
} |
|
if (*p == '\n') |
|
line++; |
|
} |
|
} |
|
return maxlen; |
|
} |
|
|
|
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) |
|
{ |
|
JSValue res_val, exception_val; |
|
int ret, error_line, pos, pos_line; |
|
BOOL is_error, has_error_line; |
|
const char *error_name; |
|
|
|
pos = skip_comments(buf, 1, &pos_line); |
|
error_line = pos_line; |
|
has_error_line = FALSE; |
|
exception_val = JS_UNDEFINED; |
|
error_name = NULL; |
|
|
|
async_done = 0; /* counter of "Test262:AsyncTestComplete" messages */ |
|
|
|
res_val = JS_Eval(ctx, buf, buf_len, filename, eval_flags); |
|
|
|
if (is_async && !JS_IsException(res_val)) { |
|
JS_FreeValue(ctx, res_val); |
|
for(;;) { |
|
JSContext *ctx1; |
|
ret = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1); |
|
if (ret < 0) { |
|
res_val = JS_EXCEPTION; |
|
break; |
|
} else if (ret == 0) { |
|
/* test if the test called $DONE() once */ |
|
if (async_done != 1) { |
|
res_val = JS_ThrowTypeError(ctx, "$DONE() not called"); |
|
} else { |
|
res_val = JS_UNDEFINED; |
|
} |
|
break; |
|
} |
|
} |
|
} |
|
|
|
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(ctx, JS_NULL, 1, &exception_val); |
|
} |
|
if (is_error) { |
|
JSValue name, stack; |
|
const char *stack_str; |
|
|
|
name = JS_GetPropertyStr(ctx, exception_val, "name"); |
|
error_name = JS_ToCString(ctx, name); |
|
stack = JS_GetPropertyStr(ctx, exception_val, "stack"); |
|
if (!JS_IsUndefined(stack)) { |
|
stack_str = JS_ToCString(ctx, stack); |
|
if (stack_str) { |
|
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] == ':') { |
|
error_line = atoi(p + len + 1); |
|
has_error_line = TRUE; |
|
} |
|
JS_FreeCString(ctx, stack_str); |
|
} |
|
} |
|
JS_FreeValue(ctx, stack); |
|
JS_FreeValue(ctx, name); |
|
} |
|
if (is_negative) { |
|
ret = 0; |
|
if (error_type) { |
|
char *error_class; |
|
const char *msg; |
|
|
|
msg = JS_ToCString(ctx, exception_val); |
|
error_class = strdup_len(msg, strcspn(msg, ":")); |
|
if (!str_equal(error_class, error_type)) |
|
ret = -1; |
|
free(error_class); |
|
JS_FreeCString(ctx, msg); |
|
} |
|
} else { |
|
ret = -1; |
|
} |
|
} else { |
|
if (is_negative) |
|
ret = -1; |
|
else |
|
ret = 0; |
|
} |
|
|
|
if (verbose && is_test) { |
|
JSValue msg_val = JS_UNDEFINED; |
|
const char *msg = NULL; |
|
int s_line; |
|
char *s = find_error(filename, &s_line, eval_flags & JS_EVAL_FLAG_STRICT); |
|
const char *strict_mode = (eval_flags & JS_EVAL_FLAG_STRICT) ? "strict mode: " : ""; |
|
|
|
if (!JS_IsUndefined(exception_val)) { |
|
msg_val = JS_ToString(ctx, exception_val); |
|
msg = JS_ToCString(ctx, msg_val); |
|
} |
|
if (is_negative) { // expect error |
|
if (ret == 0) { |
|
if (msg && s && |
|
(str_equal(s, "expected error") || |
|
strstart(s, "unexpected error type:", NULL) || |
|
str_equal(s, msg))) { // did not have error yet |
|
if (!has_error_line) { |
|
longest_match(buf, msg, pos, &pos, pos_line, &error_line); |
|
} |
|
printf("%s:%d: %sOK, now has error %s\n", |
|
filename, error_line, strict_mode, msg); |
|
fixed_errors++; |
|
} |
|
} else { |
|
if (!s) { // not yet reported |
|
if (msg) { |
|
fprintf(error_out, "%s:%d: %sunexpected error type: %s\n", |
|
filename, error_line, strict_mode, msg); |
|
} else { |
|
fprintf(error_out, "%s:%d: %sexpected error\n", |
|
filename, error_line, strict_mode); |
|
} |
|
new_errors++; |
|
} |
|
} |
|
} else { // should not have error |
|
if (msg) { |
|
if (!s || !str_equal(s, msg)) { |
|
if (!has_error_line) { |
|
char *p = skip_prefix(msg, "Test262 Error: "); |
|
if (strstr(p, "Test case returned non-true value!")) { |
|
longest_match(buf, "runTestCase", pos, &pos, pos_line, &error_line); |
|
} else { |
|
longest_match(buf, p, pos, &pos, pos_line, &error_line); |
|
} |
|
} |
|
fprintf(error_out, "%s:%d: %s%s%s\n", filename, error_line, strict_mode, |
|
error_file ? "unexpected error: " : "", msg); |
|
|
|
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++; |
|
} else { |
|
new_errors++; |
|
} |
|
} |
|
} else { |
|
if (s) { |
|
printf("%s:%d: %sOK, fixed error: %s\n", filename, s_line, strict_mode, s); |
|
fixed_errors++; |
|
} |
|
} |
|
} |
|
JS_FreeValue(ctx, msg_val); |
|
JS_FreeCString(ctx, msg); |
|
free(s); |
|
} |
|
JS_FreeCString(ctx, error_name); |
|
JS_FreeValue(ctx, exception_val); |
|
JS_FreeValue(ctx, res_val); |
|
return ret; |
|
} |
|
|
|
static int eval_file(JSContext *ctx, const char *base, const char *p, |
|
int eval_flags) |
|
{ |
|
char *buf; |
|
size_t buf_len; |
|
char *filename = compose_path(base, p); |
|
|
|
buf = load_file(filename, &buf_len); |
|
if (!buf) { |
|
warning("cannot load %s", filename); |
|
goto fail; |
|
} |
|
if (eval_buf(ctx, buf, buf_len, filename, FALSE, FALSE, NULL, stderr, |
|
eval_flags, FALSE)) { |
|
warning("error evaluating %s", filename); |
|
goto fail; |
|
} |
|
free(buf); |
|
free(filename); |
|
return 0; |
|
|
|
fail: |
|
free(buf); |
|
free(filename); |
|
return 1; |
|
} |
|
|
|
char *extract_desc(const char *buf, char style) |
|
{ |
|
const char *p, *desc_start; |
|
char *desc; |
|
int len; |
|
|
|
p = buf; |
|
while (*p != '\0') { |
|
if (p[0] == '/' && p[1] == '*' && p[2] == style && p[3] != '/') { |
|
p += 3; |
|
desc_start = p; |
|
while (*p != '\0' && (p[0] != '*' || p[1] != '/')) |
|
p++; |
|
if (*p == '\0') { |
|
warning("Expecting end of desc comment"); |
|
return NULL; |
|
} |
|
len = p - desc_start; |
|
desc = malloc(len + 1); |
|
memcpy(desc, desc_start, len); |
|
desc[len] = '\0'; |
|
return desc; |
|
} else { |
|
p++; |
|
} |
|
} |
|
return NULL; |
|
} |
|
|
|
static char *find_tag(char *desc, const char *tag, int *state) |
|
{ |
|
char *p; |
|
p = strstr(desc, tag); |
|
if (p) { |
|
p += strlen(tag); |
|
*state = 0; |
|
} |
|
return p; |
|
} |
|
|
|
static char *get_option(char **pp, int *state) |
|
{ |
|
char *p, *p0, *option = NULL; |
|
if (*pp) { |
|
for (p = *pp;; p++) { |
|
switch (*p) { |
|
case '[': |
|
*state += 1; |
|
continue; |
|
case ']': |
|
*state -= 1; |
|
if (*state > 0) |
|
continue; |
|
p = NULL; |
|
break; |
|
case ' ': |
|
case '\t': |
|
case '\r': |
|
case ',': |
|
case '-': |
|
continue; |
|
case '\n': |
|
if (*state > 0 || p[1] == ' ') |
|
continue; |
|
p = NULL; |
|
break; |
|
case '\0': |
|
p = NULL; |
|
break; |
|
default: |
|
p0 = p; |
|
p += strcspn(p0, " \t\r\n,]"); |
|
option = strdup_len(p0, p - p0); |
|
break; |
|
} |
|
break; |
|
} |
|
*pp = p; |
|
} |
|
return option; |
|
} |
|
|
|
void update_stats(JSRuntime *rt, const char *filename) { |
|
JSMemoryUsage stats; |
|
JS_ComputeMemoryUsage(rt, &stats); |
|
if (stats_count++ == 0) { |
|
stats_avg = stats_all = stats_min = stats_max = stats; |
|
stats_min_filename = strdup(filename); |
|
stats_max_filename = strdup(filename); |
|
} else { |
|
if (stats_max.malloc_size < stats.malloc_size) { |
|
stats_max = stats; |
|
free(stats_max_filename); |
|
stats_max_filename = strdup(filename); |
|
} |
|
if (stats_min.malloc_size > stats.malloc_size) { |
|
stats_min = stats; |
|
free(stats_min_filename); |
|
stats_min_filename = strdup(filename); |
|
} |
|
|
|
#define update(f) stats_avg.f = (stats_all.f += stats.f) / stats_count |
|
update(malloc_count); |
|
update(malloc_size); |
|
update(memory_used_count); |
|
update(memory_used_size); |
|
update(atom_count); |
|
update(atom_size); |
|
update(str_count); |
|
update(str_size); |
|
update(obj_count); |
|
update(obj_size); |
|
update(prop_count); |
|
update(prop_size); |
|
update(shape_count); |
|
update(shape_size); |
|
update(js_func_count); |
|
update(js_func_size); |
|
update(js_func_code_size); |
|
update(js_func_pc2line_count); |
|
update(js_func_pc2line_size); |
|
update(c_func_count); |
|
update(array_count); |
|
update(fast_array_count); |
|
update(fast_array_elements); |
|
} |
|
#undef update |
|
} |
|
|
|
int run_test_buf(const char *filename, 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) |
|
{ |
|
JSRuntime *rt; |
|
JSContext *ctx; |
|
int i, ret; |
|
|
|
rt = JS_NewRuntime(); |
|
if (rt == NULL) { |
|
fatal(1, "JS_NewRuntime failure"); |
|
} |
|
ctx = JS_NewContext(rt); |
|
if (ctx == NULL) { |
|
JS_FreeRuntime(rt); |
|
fatal(1, "JS_NewContext failure"); |
|
} |
|
JS_SetRuntimeInfo(rt, filename); |
|
|
|
JS_SetCanBlock(rt, can_block); |
|
|
|
/* loader for ES6 modules */ |
|
JS_SetModuleLoaderFunc(rt, NULL, js_module_loader_test, NULL); |
|
|
|
add_helpers(ctx); |
|
|
|
for (i = 0; i < ip->count; i++) { |
|
if (eval_file(ctx, harness, ip->array[i], |
|
JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_STRIP)) { |
|
fatal(1, "error including %s for %s", ip->array[i], filename); |
|
} |
|
} |
|
|
|
ret = eval_buf(ctx, buf, buf_len, filename, TRUE, is_negative, |
|
error_type, outfile, eval_flags, is_async); |
|
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"); |
|
} |
|
} |
|
return ret; |
|
} |
|
|
|
int run_test(const char *filename, int index) |
|
{ |
|
char harnessbuf[1024]; |
|
char *harness; |
|
char *buf; |
|
size_t buf_len; |
|
char *desc, *p; |
|
char *error_type; |
|
int ret, eval_flags, use_strict, use_nostrict; |
|
BOOL is_negative, is_nostrict, is_onlystrict, is_async, is_module, skip; |
|
BOOL can_block; |
|
namelist_t include_list = { 0 }, *ip = &include_list; |
|
|
|
is_nostrict = is_onlystrict = is_negative = is_async = is_module = skip = FALSE; |
|
can_block = TRUE; |
|
error_type = NULL; |
|
buf = load_file(filename, &buf_len); |
|
|
|
harness = harness_dir; |
|
|
|
if (new_style) { |
|
if (!harness) { |
|
p = strstr(filename, "test/"); |
|
if (p) { |
|
snprintf(harnessbuf, sizeof(harnessbuf), "%.*s%s", |
|
(int)(p - filename), filename, "harness"); |
|
} |
|
harness = harnessbuf; |
|
} |
|
namelist_add(ip, NULL, "sta.js"); |
|
namelist_add(ip, NULL, "assert.js"); |
|
/* extract the YAML frontmatter */ |
|
desc = extract_desc(buf, '-'); |
|
if (desc) { |
|
char *ifile, *option; |
|
int state; |
|
p = find_tag(desc, "includes:", &state); |
|
if (p) { |
|
while ((ifile = get_option(&p, &state)) != NULL) { |
|
// skip unsupported harness files |
|
if (find_word(harness_exclude, ifile)) { |
|
skip |= 1; |
|
} else { |
|
namelist_add(ip, NULL, ifile); |
|
} |
|
free(ifile); |
|
} |
|
} |
|
p = find_tag(desc, "flags:", &state); |
|
if (p) { |
|
while ((option = get_option(&p, &state)) != NULL) { |
|
if (str_equal(option, "noStrict") || |
|
str_equal(option, "raw")) { |
|
is_nostrict = TRUE; |
|
skip |= (test_mode == TEST_STRICT); |
|
} |
|
else if (str_equal(option, "onlyStrict")) { |
|
is_onlystrict = TRUE; |
|
skip |= (test_mode == TEST_NOSTRICT); |
|
} |
|
else if (str_equal(option, "async")) { |
|
is_async = TRUE; |
|
skip |= skip_async; |
|
} |
|
else if (str_equal(option, "module")) { |
|
is_module = TRUE; |
|
skip |= skip_module; |
|
} |
|
else if (str_equal(option, "CanBlockIsFalse")) { |
|
can_block = FALSE; |
|
} |
|
free(option); |
|
} |
|
} |
|
p = find_tag(desc, "negative:", &state); |
|
if (p) { |
|
/* XXX: should extract the phase */ |
|
char *q = find_tag(p, "type:", &state); |
|
if (q) { |
|
while (isspace(*q)) |
|
q++; |
|
error_type = strdup_len(q, strcspn(q, " \n")); |
|
} |
|
is_negative = TRUE; |
|
} |
|
p = find_tag(desc, "features:", &state); |
|
if (p) { |
|
while ((option = get_option(&p, &state)) != NULL) { |
|
if (find_word(harness_features, option)) { |
|
/* feature is enabled */ |
|
} else if (find_word(harness_skip_features, option)) { |
|
/* skip disabled feature */ |
|
skip |= 1; |
|
} else { |
|
/* feature is not listed: skip and warn */ |
|
printf("%s:%d: unknown feature: %s\n", filename, 1, option); |
|
skip |= 1; |
|
} |
|
free(option); |
|
} |
|
} |
|
free(desc); |
|
} |
|
if (is_async) |
|
namelist_add(ip, NULL, "doneprintHandle.js"); |
|
} else { |
|
char *ifile; |
|
|
|
if (!harness) { |
|
p = strstr(filename, "test/"); |
|
if (p) { |
|
snprintf(harnessbuf, sizeof(harnessbuf), "%.*s%s", |
|
(int)(p - filename), filename, "test/harness"); |
|
} |
|
harness = harnessbuf; |
|
} |
|
|
|
namelist_add(ip, NULL, "sta.js"); |
|
|
|
/* include extra harness files */ |
|
for (p = buf; (p = strstr(p, "$INCLUDE(\"")) != NULL; p++) { |
|
p += 10; |
|
ifile = strdup_len(p, strcspn(p, "\"")); |
|
// skip unsupported harness files |
|
if (find_word(harness_exclude, ifile)) { |
|
skip |= 1; |
|
} else { |
|
namelist_add(ip, NULL, ifile); |
|
} |
|
free(ifile); |
|
} |
|
|
|
/* locate the old style configuration comment */ |
|
desc = extract_desc(buf, '*'); |
|
if (desc) { |
|
if (strstr(desc, "@noStrict")) { |
|
is_nostrict = TRUE; |
|
skip |= (test_mode == TEST_STRICT); |
|
} |
|
if (strstr(desc, "@onlyStrict")) { |
|
is_onlystrict = TRUE; |
|
skip |= (test_mode == TEST_NOSTRICT); |
|
} |
|
if (strstr(desc, "@negative")) { |
|
/* XXX: should extract the regex to check error type */ |
|
is_negative = TRUE; |
|
} |
|
free(desc); |
|
} |
|
} |
|
|
|
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 */ |
|
switch (test_mode) { |
|
case TEST_DEFAULT_NOSTRICT: |
|
if (is_onlystrict) |
|
use_strict = 1; |
|
else |
|
use_nostrict = 1; |
|
break; |
|
case TEST_DEFAULT_STRICT: |
|
if (is_nostrict) |
|
use_nostrict = 1; |
|
else |
|
use_strict = 1; |
|
break; |
|
case TEST_NOSTRICT: |
|
if (!is_onlystrict) |
|
use_nostrict = 1; |
|
break; |
|
case TEST_STRICT: |
|
if (!is_nostrict) |
|
use_strict = 1; |
|
break; |
|
case TEST_ALL: |
|
if (is_module) { |
|
use_nostrict = 1; |
|
} else { |
|
if (!is_nostrict) |
|
use_strict = 1; |
|
if (!is_onlystrict) |
|
use_nostrict = 1; |
|
} |
|
break; |
|
} |
|
|
|
if (skip || use_strict + use_nostrict == 0) { |
|
test_skipped++; |
|
ret = -2; |
|
} else { |
|
clock_t clocks; |
|
|
|
if (is_module) { |
|
eval_flags = JS_EVAL_TYPE_MODULE; |
|
} else { |
|
eval_flags = JS_EVAL_TYPE_GLOBAL; |
|
} |
|
clocks = clock(); |
|
ret = 0; |
|
if (use_nostrict) { |
|
ret = run_test_buf(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, |
|
error_type, eval_flags | JS_EVAL_FLAG_STRICT, |
|
is_negative, is_async, can_block); |
|
} |
|
clocks = clock() - clocks; |
|
if (outfile && index >= 0 && clocks >= CLOCKS_PER_SEC / 10) { |
|
/* output timings for tests that take more than 100 ms */ |
|
fprintf(outfile, " time: %d ms\n", (int)(clocks * 1000LL / CLOCKS_PER_SEC)); |
|
} |
|
} |
|
namelist_free(&include_list); |
|
free(error_type); |
|
free(buf); |
|
|
|
return ret; |
|
} |
|
|
|
/* run a test when called by test262-harness+eshost */ |
|
int run_test262_harness_test(const char *filename, BOOL is_module) |
|
{ |
|
JSRuntime *rt; |
|
JSContext *ctx; |
|
char *buf; |
|
size_t buf_len; |
|
int eval_flags, ret_code, ret; |
|
JSValue res_val; |
|
BOOL can_block; |
|
|
|
outfile = stdout; /* for js_print */ |
|
|
|
rt = JS_NewRuntime(); |
|
if (rt == NULL) { |
|
fatal(1, "JS_NewRuntime failure"); |
|
} |
|
ctx = JS_NewContext(rt); |
|
if (ctx == NULL) { |
|
JS_FreeRuntime(rt); |
|
fatal(1, "JS_NewContext failure"); |
|
} |
|
JS_SetRuntimeInfo(rt, filename); |
|
|
|
can_block = TRUE; |
|
JS_SetCanBlock(rt, can_block); |
|
|
|
/* loader for ES6 modules */ |
|
JS_SetModuleLoaderFunc(rt, NULL, js_module_loader_test, NULL); |
|
|
|
add_helpers(ctx); |
|
|
|
buf = load_file(filename, &buf_len); |
|
|
|
if (is_module) { |
|
eval_flags = JS_EVAL_TYPE_MODULE; |
|
} else { |
|
eval_flags = JS_EVAL_TYPE_GLOBAL; |
|
} |
|
res_val = JS_Eval(ctx, buf, buf_len, filename, eval_flags); |
|
ret_code = 0; |
|
if (JS_IsException(res_val)) { |
|
js_std_dump_error(ctx); |
|
ret_code = 1; |
|
} else { |
|
JS_FreeValue(ctx, res_val); |
|
for(;;) { |
|
JSContext *ctx1; |
|
ret = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1); |
|
if (ret < 0) { |
|
js_std_dump_error(ctx1); |
|
ret_code = 1; |
|
} else if (ret == 0) { |
|
break; |
|
} |
|
} |
|
} |
|
free(buf); |
|
#ifdef CONFIG_AGENT |
|
js_agent_free(ctx); |
|
#endif |
|
JS_FreeContext(ctx); |
|
JS_FreeRuntime(rt); |
|
return ret_code; |
|
} |
|
|
|
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; |
|
/* 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); |
|
fflush(stderr); |
|
} |
|
} |
|
|
|
static int slow_test_threshold; |
|
|
|
void run_test_dir_list(namelist_t *lp, int start_index, int stop_index) |
|
{ |
|
int i; |
|
|
|
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 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); |
|
} |
|
test_index++; |
|
} |
|
show_progress(TRUE); |
|
} |
|
|
|
void help(void) |
|
{ |
|
printf("run-test262 version " CONFIG_VERSION "\n" |
|
"usage: run-test262 [options] {-f file ... | [dir_list] [index range]}\n" |
|
"-h help\n" |
|
"-a run tests in strict and nostrict modes\n" |
|
"-m print memory usage summary\n" |
|
"-n use new style harness\n" |
|
"-N run test prepared by test262-harness+eshost\n" |
|
"-s run tests in strict mode, skip @nostrict tests\n" |
|
"-E only run tests from the error file\n" |
|
"-u update error file\n" |
|
"-v verbose: output error messages\n" |
|
"-T duration display tests taking more than 'duration' ms\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"); |
|
exit(1); |
|
} |
|
|
|
char *get_opt_arg(const char *option, char *arg) |
|
{ |
|
if (!arg) { |
|
fatal(2, "missing argument for option %s", option); |
|
} |
|
return arg; |
|
} |
|
|
|
int main(int argc, char **argv) |
|
{ |
|
int optind, start_index, stop_index; |
|
BOOL is_dir_list; |
|
BOOL only_check_errors = FALSE; |
|
const char *filename; |
|
BOOL is_test262_harness = FALSE; |
|
BOOL is_module = FALSE; |
|
|
|
#if !defined(_WIN32) |
|
/* Date tests assume California local time */ |
|
setenv("TZ", "America/Los_Angeles", 1); |
|
#endif |
|
|
|
/* cannot use getopt because we want to pass the command line to |
|
the script */ |
|
optind = 1; |
|
is_dir_list = TRUE; |
|
while (optind < argc) { |
|
char *arg = argv[optind]; |
|
if (*arg != '-') |
|
break; |
|
optind++; |
|
if (str_equal(arg, "-h")) { |
|
help(); |
|
} else if (str_equal(arg, "-m")) { |
|
dump_memory++; |
|
} else if (str_equal(arg, "-n")) { |
|
new_style++; |
|
} else if (str_equal(arg, "-s")) { |
|
test_mode = TEST_STRICT; |
|
} else if (str_equal(arg, "-a")) { |
|
test_mode = TEST_ALL; |
|
} else if (str_equal(arg, "-u")) { |
|
update_errors++; |
|
} else if (str_equal(arg, "-v")) { |
|
verbose++; |
|
} else if (str_equal(arg, "-c")) { |
|
load_config(get_opt_arg(arg, argv[optind++])); |
|
} else if (str_equal(arg, "-d")) { |
|
enumerate_tests(get_opt_arg(arg, argv[optind++])); |
|
} else if (str_equal(arg, "-e")) { |
|
error_filename = get_opt_arg(arg, argv[optind++]); |
|
} else if (str_equal(arg, "-x")) { |
|
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 = 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, "-N")) { |
|
is_test262_harness = TRUE; |
|
} else if (str_equal(arg, "--module")) { |
|
is_module = TRUE; |
|
} else { |
|
fatal(1, "unknown option: %s", arg); |
|
break; |
|
} |
|
} |
|
|
|
if (optind >= argc && !test_list.count) |
|
help(); |
|
|
|
if (is_test262_harness) { |
|
return run_test262_harness_test(argv[optind], is_module); |
|
} |
|
|
|
error_out = stdout; |
|
if (error_filename) { |
|
error_file = load_file(error_filename, NULL); |
|
if (only_check_errors && error_file) { |
|
namelist_free(&test_list); |
|
namelist_add_from_error_file(&test_list, error_file); |
|
} |
|
if (update_errors) { |
|
free(error_file); |
|
error_file = NULL; |
|
error_out = fopen(error_filename, "w"); |
|
if (!error_out) { |
|
perror_exit(1, error_filename); |
|
} |
|
} |
|
} |
|
|
|
update_exclude_dirs(); |
|
|
|
if (is_dir_list) { |
|
if (optind < argc && !isdigit(argv[optind][0])) { |
|
filename = argv[optind++]; |
|
namelist_load(&test_list, filename); |
|
} |
|
start_index = 0; |
|
stop_index = -1; |
|
if (optind < argc) { |
|
start_index = atoi(argv[optind++]); |
|
if (optind < argc) { |
|
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); |
|
} |
|
} |
|
run_test_dir_list(&test_list, start_index, stop_index); |
|
|
|
if (outfile && outfile != stdout) { |
|
fclose(outfile); |
|
outfile = NULL; |
|
} |
|
} else { |
|
outfile = stdout; |
|
while (optind < argc) { |
|
run_test(argv[optind++], -1); |
|
} |
|
} |
|
|
|
if (dump_memory) { |
|
if (dump_memory > 1 && stats_count > 1) { |
|
printf("\nMininum memory statistics for %s:\n\n", stats_min_filename); |
|
JS_DumpMemoryUsage(stdout, &stats_min, NULL); |
|
printf("\nMaximum memory statistics for %s:\n\n", stats_max_filename); |
|
JS_DumpMemoryUsage(stdout, &stats_max, NULL); |
|
} |
|
printf("\nAverage memory statistics for %d tests:\n\n", stats_count); |
|
JS_DumpMemoryUsage(stdout, &stats_avg, NULL); |
|
printf("\n"); |
|
} |
|
|
|
if (is_dir_list) { |
|
fprintf(stderr, "Result: %d/%d error%s", |
|
test_failed, test_count, test_count != 1 ? "s" : ""); |
|
if (test_excluded) |
|
fprintf(stderr, ", %d excluded", test_excluded); |
|
if (test_skipped) |
|
fprintf(stderr, ", %d skipped", test_skipped); |
|
if (error_file) { |
|
if (new_errors) |
|
fprintf(stderr, ", %d new", new_errors); |
|
if (changed_errors) |
|
fprintf(stderr, ", %d changed", changed_errors); |
|
if (fixed_errors) |
|
fprintf(stderr, ", %d fixed", fixed_errors); |
|
} |
|
fprintf(stderr, "\n"); |
|
} |
|
|
|
if (error_out && error_out != stdout) { |
|
fclose(error_out); |
|
error_out = NULL; |
|
} |
|
|
|
namelist_free(&test_list); |
|
namelist_free(&exclude_list); |
|
namelist_free(&exclude_dir_list); |
|
free(harness_dir); |
|
free(harness_features); |
|
free(harness_exclude); |
|
free(error_file); |
|
|
|
return 0; |
|
}
|
|
|