quickjs/run-test262.c

2108 lines
61 KiB
C
Raw Permalink Normal View History

2020-09-06 16:53:08 +00:00
/*
* ECMA Test 262 Runner for QuickJS
*
2021-03-27 10:17:31 +00:00
* Copyright (c) 2017-2021 Fabrice Bellard
* Copyright (c) 2017-2021 Charlie Gordon
2020-09-06 16:53:08 +00:00
*
* 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;
2020-09-06 17:02:03 +00:00
if (!strcmp(str, "Test262:AsyncTestComplete")) {
2020-09-06 16:53:08 +00:00
async_done++;
2020-09-06 17:02:03 +00:00
} else if (strstart(str, "Test262:AsyncTestFailure", NULL)) {
async_done = 2; /* force an error */
}
2020-09-06 16:53:08 +00:00
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;
2020-09-06 17:04:20 +00:00
JSValue ret;
2020-09-06 16:53:08 +00:00
ctx1 = JS_NewContext(JS_GetRuntime(ctx));
if (!ctx1)
return JS_ThrowOutOfMemory(ctx);
2020-09-06 17:04:20 +00:00
ret = add_helpers1(ctx1);
/* ctx1 has a refcount so it stays alive */
JS_FreeContext(ctx1);
return ret;
2020-09-06 16:53:08 +00:00
}
2020-09-06 17:10:15 +00:00
static JSValue js_IsHTMLDDA(JSContext *ctx, JSValue this_val,
int argc, JSValue *argv)
{
return JS_NULL;
}
2020-09-06 16:53:08 +00:00
static JSValue add_helpers1(JSContext *ctx)
{
JSValue global_obj;
2020-09-06 17:10:15 +00:00
JSValue obj262, obj;
2020-09-06 16:53:08 +00:00
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));
2020-09-06 17:10:15 +00:00
obj = JS_NewCFunction(ctx, js_IsHTMLDDA, "IsHTMLDDA", 0);
JS_SetIsHTMLDDA(ctx, obj);
JS_SetPropertyStr(ctx, obj262, "IsHTMLDDA", obj);
2020-09-06 16:53:08 +00:00
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)