diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6b74dbac..42fe6449 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -141,7 +141,7 @@ jobs: make stats - name: test - if: ${{ matrix.config.configType != 'examples' }} + if: ${{ matrix.config.configType != 'examples' && matrix.config.configType != 'tcc' }} run: | make test @@ -214,16 +214,7 @@ jobs: cp build\${{matrix.buildType}}\point.dll examples\ build\${{matrix.buildType}}\qjs.exe examples\test_fib.js build\${{matrix.buildType}}\qjs.exe examples\test_point.js - build\${{matrix.buildType}}\qjs.exe tests\test_bigint.js - build\${{matrix.buildType}}\qjs.exe tests\test_bjson.js - build\${{matrix.buildType}}\qjs.exe tests\test_closure.js - build\${{matrix.buildType}}\qjs.exe tests\test_language.js - build\${{matrix.buildType}}\qjs.exe tests\test_builtin.js - build\${{matrix.buildType}}\qjs.exe tests\test_loop.js - build\${{matrix.buildType}}\qjs.exe tests\test_std.js - build\${{matrix.buildType}}\qjs.exe tests\test_worker.js - build\${{matrix.buildType}}\qjs.exe tests\test_queue_microtask.js - build\${{matrix.buildType}}\qjs.exe tests\test_module_detect.js + build\${{matrix.buildType}}\run-test262.exe -c tests.conf build\${{matrix.buildType}}\function_source.exe windows-clang: @@ -247,16 +238,7 @@ jobs: cp build\${{matrix.buildType}}\point.dll examples\ build\${{matrix.buildType}}\qjs.exe examples\test_fib.js build\${{matrix.buildType}}\qjs.exe examples\test_point.js - build\${{matrix.buildType}}\qjs.exe tests\test_bigint.js - build\${{matrix.buildType}}\qjs.exe tests\test_bjson.js - build\${{matrix.buildType}}\qjs.exe tests\test_closure.js - build\${{matrix.buildType}}\qjs.exe tests\test_language.js - build\${{matrix.buildType}}\qjs.exe tests\test_builtin.js - build\${{matrix.buildType}}\qjs.exe tests\test_loop.js - build\${{matrix.buildType}}\qjs.exe tests\test_std.js - build\${{matrix.buildType}}\qjs.exe tests\test_worker.js - build\${{matrix.buildType}}\qjs.exe tests\test_queue_microtask.js - build\${{matrix.buildType}}\qjs.exe tests\test_module_detect.js + build\${{matrix.buildType}}\run-test262.exe -c tests.conf build\${{matrix.buildType}}\function_source.exe windows-ninja: @@ -284,16 +266,7 @@ jobs: cp build\point.dll examples\ build\qjs.exe examples\test_fib.js build\qjs.exe examples\test_point.js - build\qjs.exe tests\test_bigint.js - build\qjs.exe tests\test_bjson.js - build\qjs.exe tests\test_closure.js - build\qjs.exe tests\test_language.js - build\qjs.exe tests\test_builtin.js - build\qjs.exe tests\test_loop.js - build\qjs.exe tests\test_std.js - build\qjs.exe tests\test_worker.js - build\qjs.exe tests\test_queue_microtask.js - build\qjs.exe tests\test_module_detect.js + build\run-test262.exe -c tests.conf build\function_source.exe windows-mingw: diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ac9babd..572603de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -281,8 +281,7 @@ endif() # Test262 runner # -if(WIN32 -OR EMSCRIPTEN +if(EMSCRIPTEN OR CMAKE_C_COMPILER_ID STREQUAL "TinyCC" OR (CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_LESS 5)) # Empty. run-test262 uses pthreads, sorry Windows users. diff --git a/Makefile b/Makefile index 1703c07a..a5a5734f 100644 --- a/Makefile +++ b/Makefile @@ -80,15 +80,7 @@ stats: $(QJS) $(QJS) -qd test: $(QJS) - $(QJS) tests/test_bigint.js - $(QJS) tests/test_closure.js - $(QJS) tests/test_language.js - $(QJS) tests/test_builtin.js - $(QJS) tests/test_loop.js - $(QJS) tests/test_std.js - $(QJS) tests/test_worker.js - $(QJS) tests/test_queue_microtask.js - $(QJS) tests/test_module_detect.js + $(RUN262) -c tests.conf testconv: $(BUILD_DIR)/test_conv $(BUILD_DIR)/test_conv diff --git a/cutils.c b/cutils.c index 02829940..5515b460 100644 --- a/cutils.c +++ b/cutils.c @@ -1129,7 +1129,7 @@ void rqsort(void *base, size_t nmemb, size_t size, cmp_f cmp, void *opaque) /*---- Portable time functions ----*/ -#if defined(_MSC_VER) +#ifdef _WIN32 // From: https://stackoverflow.com/a/26085827 static int gettimeofday_msvc(struct timeval *tp, struct timezone *tzp) { @@ -1184,7 +1184,7 @@ uint64_t js__hrtime_ns(void) { int64_t js__gettimeofday_us(void) { struct timeval tv; -#if defined(_MSC_VER) +#ifdef _WIN32 gettimeofday_msvc(&tv, NULL); #else gettimeofday(&tv, NULL); diff --git a/run-test262.c b/run-test262.c index eb0785bf..e9ff83b0 100644 --- a/run-test262.c +++ b/run-test262.c @@ -29,12 +29,19 @@ #include #include #include -#include #include #include + +#ifdef _WIN32 +#include +#include +typedef HANDLE js_thread_t; +#else #include -#include #include +#include +typedef pthread_t js_thread_t; +#endif #include "cutils.h" #include "list.h" @@ -50,8 +57,8 @@ typedef struct namelist_t { } namelist_t; long nthreads; // invariant: 0 < nthreads < countof(threads) -pthread_t threads[32]; -pthread_t progress_thread; +js_thread_t threads[32]; +js_thread_t progress_thread; js_cond_t progress_cond; js_mutex_t progress_mutex; @@ -67,9 +74,9 @@ enum test_mode_t { TEST_STRICT, /* run tests as strict, skip nostrict tests */ TEST_ALL, /* run tests in both strict and nostrict, unless restricted by spec */ } test_mode = TEST_DEFAULT_NOSTRICT; +int local; int skip_async; int skip_module; -int new_style; int dump_memory; int stats_count; JSMemoryUsage stats_all, stats_avg, stats_min, stats_max; @@ -328,7 +335,7 @@ void namelist_load(namelist_t *lp, const char *filename) char *base_name; FILE *f; - f = fopen(filename, "rb"); + f = fopen(filename, "r"); if (!f) { perror_exit(1, filename); } @@ -369,7 +376,7 @@ void namelist_free(namelist_t *lp) lp->size = 0; } -static int add_test_file(const char *filename, const struct stat *ptr, int flag) +static int add_test_file(const char *filename) { namelist_t *lp = &test_list; if (has_suffix(filename, ".js") && !has_suffix(filename, "_FIXTURE.js")) @@ -377,12 +384,58 @@ static int add_test_file(const char *filename, const struct stat *ptr, int flag) return 0; } +static void find_test_files(const char *path); + +static void consider_test_file(const char *path, const char *name, int is_dir) +{ + char s[1024]; + + if (str_equal(name, ".") || str_equal(name, "..")) + return; + snprintf(s, sizeof(s), "%s/%s", path, name); + if (is_dir) + find_test_files(s); + else + add_test_file(s); +} + +static void find_test_files(const char *path) +{ +#ifdef _WIN32 + WIN32_FIND_DATAA d; + HANDLE h; + char s[1024]; + + snprintf(s, sizeof(s), "%s/*", path); + h = FindFirstFileA(s, &d); + if (h != INVALID_HANDLE_VALUE) { + do { + consider_test_file(path, + d.cFileName, + d.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); + } while (FindNextFileA(h, &d)); + FindClose(h); + } +#else + struct dirent *d, **ds = NULL; + int i, n; + + n = scandir(path, &ds, NULL, alphasort); + for (i = 0; i < n; i++) { + d = ds[i]; + consider_test_file(path, d->d_name, d->d_type == DT_DIR); + free(d); + } + free(ds); +#endif +} + /* 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); + find_test_files(path); qsort(lp->array + start, lp->count - start, sizeof(*lp->array), namelist_cmp_indirect); } @@ -435,25 +488,64 @@ static JSValue js_evalScript_262(JSContext *ctx, JSValue this_val, return ret; } -static void start_thread(pthread_t *thrd, void *(*start)(void *), void *arg) +static void start_thread(js_thread_t *thrd, void *(*start)(void *), void *arg) { + // musl libc gives threads 80 kb stacks, much smaller than + // JS_DEFAULT_STACK_SIZE (256 kb) + static const unsigned stacksize = 2 << 20; // 2 MB, glibc default +#ifdef _WIN32 + HANDLE h, cp; + + cp = GetCurrentProcess(); + h = (HANDLE)_beginthread((void (*)(void *))(void *)start, stacksize, arg); + if (!h) + fatal(1, "_beginthread error"); + // _endthread() automatically closes the handle but we want to wait on + // it so make a copy. Race-y for very short-lived threads. Can be solved + // by switching to _beginthreadex(CREATE_SUSPENDED) but means changing + // |start| from __cdecl to __stdcall. + if (!DuplicateHandle(cp, h, cp, thrd, 0, FALSE, DUPLICATE_SAME_ACCESS)) + fatal(1, "DuplicateHandle error"); +#else pthread_attr_t attr; if (pthread_attr_init(&attr)) fatal(1, "pthread_attr_init"); - // musl libc gives threads 80 kb stacks, much smaller than - // JS_DEFAULT_STACK_SIZE (256 kb) - if (pthread_attr_setstacksize(&attr, 2 << 20)) // 2 MB, glibc default + if (pthread_attr_setstacksize(&attr, stacksize)) fatal(1, "pthread_attr_setstacksize"); if (pthread_create(thrd, &attr, start, arg)) fatal(1, "pthread_create error"); pthread_attr_destroy(&attr); +#endif } -static void join_thread(pthread_t thrd) +static void join_thread(js_thread_t thrd) { +#ifdef _WIN32 + if (WaitForSingleObject(thrd, INFINITE)) + fatal(1, "WaitForSingleObject error"); + CloseHandle(thrd); +#else if (pthread_join(thrd, NULL)) fatal(1, "pthread_join error"); +#endif +} + +static long cpu_count(void) +{ +#ifdef _WIN32 + DWORD_PTR procmask, sysmask; + long count; + int i; + + count = 0; + if (GetProcessAffinityMask(GetCurrentProcess(), &procmask, &sysmask)) + for (i = 0; i < 8 * sizeof(procmask); i++) + count += 1 & (procmask >> i); + return count; +#else + return sysconf(_SC_NPROCESSORS_ONLN); +#endif } typedef struct { @@ -480,7 +572,7 @@ static _Thread_local ThreadLocalStorage *tls; typedef struct { struct list_head link; - pthread_t tid; + js_thread_t tid; char *script; JSValue broadcast_func; BOOL broadcast_pending; @@ -606,7 +698,7 @@ static void js_agent_free(JSContext *ctx) list_for_each_safe(el, el1, &tls->agent_list) { agent = list_entry(el, Test262Agent, link); - pthread_join(agent->tid, NULL); + join_thread(agent->tid); JS_FreeValue(ctx, agent->broadcast_sab); list_del(&agent->link); free(agent); @@ -695,15 +787,23 @@ static JSValue js_agent_sleep(JSContext *ctx, JSValue this_val, uint32_t duration; if (JS_ToUint32(ctx, &duration, argv[0])) return JS_EXCEPTION; +#ifdef _WIN32 + Sleep(duration); +#else usleep(duration * 1000); +#endif return JS_UNDEFINED; } static int64_t get_clock_ms(void) { +#ifdef _WIN32 + return GetTickCount64(); +#else struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (uint64_t)ts.tv_sec * 1000 + (ts.tv_nsec / 1000000); +#endif } static JSValue js_agent_monotonicNow(JSContext *ctx, JSValue this_val, @@ -985,7 +1085,7 @@ void load_config(const char *filename, const char *ignore) } section = SECTION_NONE; int lineno = 0; - f = fopen(filename, "rb"); + f = fopen(filename, "r"); if (!f) { perror_exit(1, filename); } @@ -1030,8 +1130,8 @@ void load_config(const char *filename, const char *ignore) printf("%s:%d: ignoring %s=%s\n", filename, lineno, p, q); continue; } - if (str_equal(p, "style")) { - new_style = str_equal(q, "new"); + if (str_equal(p, "local")) { + local = str_equal(q, "yes"); continue; } if (str_equal(p, "testdir")) { @@ -1417,6 +1517,11 @@ static int eval_buf(JSContext *ctx, const char *buf, size_t buf_len, JS_FreeCString(ctx, msg); free(s); } + + if (local) { + js_std_loop(ctx); + } + JS_FreeCString(ctx, error_name); JS_FreeValue(ctx, exception_val); JS_FreeValue(ctx, res_val); @@ -1584,6 +1689,19 @@ void update_stats(JSRuntime *rt, const char *filename) { js_mutex_unlock(&stats_mutex); } +JSContext *JS_NewCustomContext(JSRuntime *rt) +{ + JSContext *ctx; + + ctx = JS_NewContext(rt); + if (ctx && local) { + js_init_module_std(ctx, "std"); + js_init_module_os(ctx, "os"); + js_init_module_bjson(ctx, "bjson"); + } + return ctx; +} + 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, @@ -1597,7 +1715,8 @@ int run_test_buf(const char *filename, char *harness, namelist_t *ip, if (rt == NULL) { fatal(1, "JS_NewRuntime failure"); } - ctx = JS_NewContext(rt); + js_std_init_handlers(rt); + ctx = JS_NewCustomContext(rt); if (ctx == NULL) { JS_FreeRuntime(rt); fatal(1, "JS_NewContext failure"); @@ -1626,6 +1745,7 @@ int run_test_buf(const char *filename, char *harness, namelist_t *ip, } js_agent_free(ctx); JS_FreeContext(ctx); + js_std_free_handlers(rt); JS_FreeRuntime(rt); atomic_inc(&test_count); @@ -1654,136 +1774,92 @@ int run_test(const char *filename, int *msec) 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; + if (!harness) { + p = strstr(filename, "test/"); + if (p) { + snprintf(harnessbuf, sizeof(harnessbuf), "%.*s%s", + (int)(p - filename), filename, "harness"); } + harness = harnessbuf; + } + if (!local) { 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); + } + /* 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, "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); } - } - p = find_tag(desc, "negative:", &state); - if (p) { - /* XXX: should extract the phase */ - char *q = find_tag(p, "type:", &state); - if (q) { - while (isspace((unsigned char)*q)) - q++; - error_type = strdup_len(q, strcspn(q, " \n")); + else if (str_equal(option, "onlyStrict")) { + is_onlystrict = TRUE; + skip |= (test_mode == TEST_NOSTRICT); } - 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); + 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); } - 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); + p = find_tag(desc, "negative:", &state); + if (p) { + /* XXX: should extract the phase */ + char *q = find_tag(p, "type:", &state); + if (q) { + while (isspace((unsigned char)*q)) + q++; + error_type = strdup_len(q, strcspn(q, " \n")); } - free(ifile); + is_negative = TRUE; } - - /* 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; + 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); } + free(desc); } + if (is_async) + namelist_add(ip, NULL, "doneprintHandle.js"); use_strict = use_nostrict = 0; /* XXX: should remove 'test_mode' or simplify it just to force @@ -1825,6 +1901,9 @@ int run_test(const char *filename, int *msec) atomic_inc(&test_skipped); ret = -2; } else { + if (local) { + is_module = JS_DetectModule(buf, buf_len); + } if (is_module) { eval_flags = JS_EVAL_TYPE_MODULE; } else { @@ -1936,7 +2015,7 @@ void *show_progress(void *unused) { js_mutex_lock(&progress_mutex); while (js_cond_timedwait(&progress_cond, &progress_mutex, interval)) { /* output progress indicator: erase end of line and return to col 0 */ - fprintf(stderr, "%d/%d/%d\033[K\r", + fprintf(stderr, "%d/%d/%d \r", atomic_load(&test_failed), atomic_load(&test_count), atomic_load(&test_skipped)); @@ -1986,7 +2065,6 @@ void help(void) "-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" @@ -2022,16 +2100,20 @@ int main(int argc, char **argv) BOOL is_test262_harness = FALSE; BOOL is_module = FALSE; + js_std_set_worker_new_context_func(JS_NewCustomContext); + tls = &(ThreadLocalStorage){}; init_thread_local_storage(tls); js_mutex_init(&stats_mutex); -#if !defined(__MINGW32__) +#ifndef _WIN32 /* Date tests assume California local time */ setenv("TZ", "America/Los_Angeles", 1); - nthreads = sysconf(_SC_NPROCESSORS_ONLN) - 1; #endif + // minus one to not (over)commit the system completely + nthreads = cpu_count() - 1; + optind = 1; while (optind < argc) { char *arg = argv[optind]; @@ -2057,8 +2139,6 @@ int main(int argc, char **argv) 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")) { diff --git a/test262.conf b/test262.conf index 23860f14..0ed1f590 100644 --- a/test262.conf +++ b/test262.conf @@ -1,9 +1,6 @@ [config] # general settings for test262 ES6 version -# framework style: old, new -style=new - # handle tests tagged as [noStrict]: yes, no, skip nostrict=yes diff --git a/tests.conf b/tests.conf new file mode 100644 index 00000000..30ee794d --- /dev/null +++ b/tests.conf @@ -0,0 +1,8 @@ +[config] +local=yes +verbose=yes +testdir=tests + +[exclude] +tests/microbench.js +tests/test_worker_module.js diff --git a/tests/test_std.js b/tests/test_std.js index 7e9e2a42..b9ea6e2f 100644 --- a/tests/test_std.js +++ b/tests/test_std.js @@ -90,6 +90,7 @@ function test_popen() { var str, f, fname = "tmp_file.txt"; var content = "hello world"; + var cmd = isWin ? "type" : "cat"; f = std.open(fname, "w"); f.puts(content); @@ -98,8 +99,8 @@ function test_popen() /* test loadFile */ assert(std.loadFile(fname), content); - /* execute the 'cat' shell command */ - f = std.popen("cat " + fname, "r"); + /* execute shell command */ + f = std.popen(cmd + " " + fname, "r"); str = f.readAsString(); f.close();