1971 lines
58 KiB
C
Executable File
1971 lines
58 KiB
C
Executable File
/*
|
|
* ECMA Test 262 Runner for QuickJS
|
|
*
|
|
* Copyright (c) 2017-2018 Fabrice Bellard
|
|
* Copyright (c) 2017-2018 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;
|
|
|
|
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;
|
|
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;
|
|
int len;
|
|
JSValue ret;
|
|
str = JS_ToCStringLen(ctx, &len, argv[0], FALSE);
|
|
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 void add_helpers(JSContext *ctx, int argc, char **argv);
|
|
|
|
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, 0, NULL);
|
|
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 JSValue js_agent_monotonicNow(JSContext *ctx, JSValue this_val,
|
|
int argc, JSValue *argv)
|
|
{
|
|
struct timespec ts;
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
return JS_NewInt64(ctx, (uint64_t)ts.tv_sec * 1000 +
|
|
(ts.tv_nsec / 1000000));
|
|
}
|
|
|
|
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 void add_helpers(JSContext *ctx, int argc, char **argv)
|
|
{
|
|
JSValue global_obj;
|
|
JSValue obj262;
|
|
|
|
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, global_obj, "$262", obj262);
|
|
|
|
JS_FreeValue(ctx, global_obj);
|
|
}
|
|
|
|
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;
|
|
|
|
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) {
|
|
JSValue global;
|
|
/* test if the test called $DONE() */
|
|
global = JS_GetGlobalObject(ctx);
|
|
res_val = JS_GetPropertyStr(ctx, global, "$async_done");
|
|
JS_FreeValue(ctx, global);
|
|
if (!JS_IsException(res_val) && JS_IsUndefined(res_val)) {
|
|
res_val = JS_ThrowTypeError(ctx, "$DONE() not called");
|
|
}
|
|
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, 0, NULL);
|
|
|
|
/* add backtrace if the isError property is present in a thrown
|
|
object */
|
|
JS_EnableIsErrorProperty(ctx, TRUE);
|
|
|
|
/* a few tests use it, probably a bug in the tests */
|
|
{
|
|
JSValue global_obj = JS_GetGlobalObject(ctx);
|
|
JS_DefinePropertyValueStr(ctx, global_obj, "global",
|
|
global_obj,
|
|
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
|
|
}
|
|
|
|
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 = FALSE;
|
|
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")) {
|
|
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, "CanBlockIsTrue")) {
|
|
can_block = TRUE;
|
|
}
|
|
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;
|
|
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_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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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 {
|
|
run_test(p, test_index);
|
|
show_progress(FALSE);
|
|
}
|
|
test_index++;
|
|
}
|
|
show_progress(TRUE);
|
|
}
|
|
|
|
void help(void)
|
|
{
|
|
printf("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"
|
|
"-s run tests in strict mode, skip @nostrict tests\n"
|
|
"-u update error file\n"
|
|
"-v verbose: output error messages\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;
|
|
|
|
#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 {
|
|
fatal(1, "unknown option: %s", arg);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (optind >= argc && !test_list.count)
|
|
help();
|
|
|
|
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;
|
|
}
|