From a52e7b761a23edb8e065ea04deb13ee2fffb223c Mon Sep 17 00:00:00 2001 From: Ms2ger Date: Mon, 13 Jul 2026 14:01:53 +0200 Subject: [PATCH 01/10] [test] Remove argument from ensure_wasm_executable This is the only place that has an indirection for the path; the rest of the script assumes the executable will be at the fixed path. It would be pointless to call this function with another argument. --- test/build.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/build.py b/test/build.py index 6c10578385..d93284ab7d 100755 --- a/test/build.py +++ b/test/build.py @@ -50,11 +50,11 @@ def compile_wasm_interpreter(): sys.exit(1) print("Done!") -def ensure_wasm_executable(path_to_wasm): +def ensure_wasm_executable(): """ Ensure we have built the wasm spec interpreter. """ - result = call(path_to_wasm, '-v', '-e', '') + result = call(WASM_EXEC, '-v', '-e', '') if result != 0: print('Unable to run the wasm executable') sys.exit(1) @@ -260,7 +260,7 @@ def process_args(): if args.compile: compile_wasm_interpreter() - ensure_wasm_executable(WASM_EXEC) + ensure_wasm_executable() if js_dir is not None: ensure_empty_dir(js_dir) From 3e60650cec9fffc20de57286bdd749ac987de57b Mon Sep 17 00:00:00 2001 From: Ms2ger Date: Mon, 13 Jul 2026 14:01:55 +0200 Subject: [PATCH 02/10] [test] Show output if the wasm executable fails to build --- test/build.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/test/build.py b/test/build.py index d93284ab7d..837d0a8237 100755 --- a/test/build.py +++ b/test/build.py @@ -27,11 +27,6 @@ def run(*cmd): stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) -def call(*cmd): - return subprocess.call(cmd, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - universal_newlines=True) # Preconditions. def ensure_remove_dir(path): @@ -44,9 +39,10 @@ def ensure_empty_dir(path): def compile_wasm_interpreter(): print("Recompiling the wasm interpreter...") - result = call('make', '-C', INTERPRETER_DIR, 'clean', 'default') - if result != 0: - print("Couldn't recompile wasm spec interpreter") + result = run('make', '-C', INTERPRETER_DIR, 'clean', 'default') + if result.returncode != 0: + print("Couldn't recompile wasm spec interpreter. Output follows:") + print(result.stdout) sys.exit(1) print("Done!") @@ -54,9 +50,10 @@ def ensure_wasm_executable(): """ Ensure we have built the wasm spec interpreter. """ - result = call(WASM_EXEC, '-v', '-e', '') - if result != 0: - print('Unable to run the wasm executable') + result = run(WASM_EXEC, '-v', '-e', '') + if result.returncode != 0: + print("Unable to run the wasm executable. Output follows:") + print(result.stdout) sys.exit(1) # JS harness. From 4069d6f80caeb2a7c750915d0e1fdcbd2e888934 Mon Sep 17 00:00:00 2001 From: Ms2ger Date: Mon, 13 Jul 2026 14:01:55 +0200 Subject: [PATCH 03/10] [test] Remove unused argument to build_front_page --- test/build.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/build.py b/test/build.py index 837d0a8237..c0ab88c902 100755 --- a/test/build.py +++ b/test/build.py @@ -185,7 +185,7 @@ def build_html(html_dir, use_sync): # Front page harness. -def build_front_page(out_dir, js_dir, use_sync): +def build_front_page(out_dir, use_sync): print('Building front page containing all the HTML tests...') js_out_dir = os.path.join(out_dir, 'js') @@ -273,6 +273,6 @@ def process_args(): if front_dir is not None: ensure_empty_dir(front_dir) - build_front_page(front_dir, js_dir, args.use_sync) + build_front_page(front_dir, args.use_sync) print('Done!') From aabe5d3b78ca6e41a07548822e9d74954bffb5a0 Mon Sep 17 00:00:00 2001 From: Ms2ger Date: Mon, 13 Jul 2026 14:01:56 +0200 Subject: [PATCH 04/10] [test] Clear files in build_js This allows removing some duplicated code later. --- test/build.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/build.py b/test/build.py index c0ab88c902..3275871a64 100755 --- a/test/build.py +++ b/test/build.py @@ -103,6 +103,9 @@ def copy_harness_files(out_js_dir, include_harness): def build_js(out_js_dir): print('Building JS...') + ensure_empty_dir(out_js_dir) + for d in WAST_TEST_SUBDIRS: + ensure_empty_dir(os.path.join(out_js_dir, d)) convert_wast_to_js(out_js_dir) copy_harness_files(out_js_dir, False) print('Done building JS.') @@ -260,9 +263,6 @@ def process_args(): ensure_wasm_executable() if js_dir is not None: - ensure_empty_dir(js_dir) - for d in WAST_TEST_SUBDIRS: - ensure_empty_dir(os.path.join(js_dir, d)) build_js(js_dir) if html_dir is not None: From eec80166cbebebd13d13e9933de0b0303e1a9b82 Mon Sep 17 00:00:00 2001 From: Ms2ger Date: Mon, 13 Jul 2026 14:01:56 +0200 Subject: [PATCH 05/10] [test] Clear files in convert_wast_to_js This removes some duplicated code. Note that the copy_harness_files calls now need to happen after the convert_wast_to_js call. --- test/build.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/build.py b/test/build.py index 3275871a64..f44914358c 100755 --- a/test/build.py +++ b/test/build.py @@ -63,7 +63,14 @@ def convert_one_wast_file(inputs): return run(WASM_EXEC, wast_file, '-j', '-o', js_file) def convert_wast_to_js(out_js_dir): - """Compile all the wast files to JS and store the results in the JS dir.""" + """ + Compile all the wast files to JS and store the results in the JS dir, + which is cleared first. + """ + + ensure_empty_dir(out_js_dir) + for d in WAST_TEST_SUBDIRS: + ensure_empty_dir(os.path.join(out_js_dir, d)) inputs = [] @@ -103,9 +110,6 @@ def copy_harness_files(out_js_dir, include_harness): def build_js(out_js_dir): print('Building JS...') - ensure_empty_dir(out_js_dir) - for d in WAST_TEST_SUBDIRS: - ensure_empty_dir(os.path.join(out_js_dir, d)) convert_wast_to_js(out_js_dir) copy_harness_files(out_js_dir, False) print('Done building JS.') @@ -143,14 +147,10 @@ def wrap_single_test(js_file): f.write(content) def build_html_js(out_dir): - ensure_empty_dir(out_dir) - for d in WAST_TEST_SUBDIRS: - ensure_empty_dir(os.path.join(out_dir, d)) - copy_harness_files(out_dir, True) - tests = convert_wast_to_js(out_dir) for js_file in tests: wrap_single_test(js_file) + copy_harness_files(out_dir, True) return tests def build_html_from_js(tests, html_dir, use_sync): From 1e92f16701baf3a067a01fb3f2121f2fe1455489 Mon Sep 17 00:00:00 2001 From: Ms2ger Date: Mon, 13 Jul 2026 14:01:56 +0200 Subject: [PATCH 06/10] [test] Inline build_html_js This makes it possible to adjust the code to each caller. --- test/build.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/test/build.py b/test/build.py index f44914358c..af06f22deb 100755 --- a/test/build.py +++ b/test/build.py @@ -146,13 +146,6 @@ def wrap_single_test(js_file): with open(js_file, 'w') as f: f.write(content) -def build_html_js(out_dir): - tests = convert_wast_to_js(out_dir) - for js_file in tests: - wrap_single_test(js_file) - copy_harness_files(out_dir, True) - return tests - def build_html_from_js(tests, html_dir, use_sync): for js_file in tests: subdir = os.path.basename(os.path.dirname(js_file)) @@ -179,7 +172,10 @@ def build_html(html_dir, use_sync): js_html_dir = os.path.join(html_dir, 'js') - tests = build_html_js(js_html_dir) + tests = convert_wast_to_js(js_html_dir) + for js_file in tests: + wrap_single_test(js_file) + copy_harness_files(js_html_dir, True) print('Building WPT tests from JS tests...') build_html_from_js(tests, html_dir, use_sync) @@ -193,7 +189,10 @@ def build_front_page(out_dir, use_sync): js_out_dir = os.path.join(out_dir, 'js') - tests = build_html_js(js_out_dir) + tests = convert_wast_to_js(js_out_dir) + for js_file in tests: + wrap_single_test(js_file) + copy_harness_files(js_out_dir, True) front_page = os.path.join(out_dir, 'index.html') js_harness = "sync_index.js" if use_sync else "async_index.js" From 161fd03892774bc5d13aabe4f27db5e658301b35 Mon Sep 17 00:00:00 2001 From: Ms2ger Date: Mon, 13 Jul 2026 14:01:57 +0200 Subject: [PATCH 07/10] [test] Don't wrap JS files in an IIFE for the HTML tests This is pointless when each test runs in its own HTML file. --- test/build.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/build.py b/test/build.py index af06f22deb..13f4b1bec9 100755 --- a/test/build.py +++ b/test/build.py @@ -173,8 +173,6 @@ def build_html(html_dir, use_sync): js_html_dir = os.path.join(html_dir, 'js') tests = convert_wast_to_js(js_html_dir) - for js_file in tests: - wrap_single_test(js_file) copy_harness_files(js_html_dir, True) print('Building WPT tests from JS tests...') From 39ae1134b8358e930007e615b84791f0964258c9 Mon Sep 17 00:00:00 2001 From: Ms2ger Date: Mon, 13 Jul 2026 14:01:57 +0200 Subject: [PATCH 08/10] [test] Don't copy unnecessary harness files for HTML tests These tests assume the testharness files are in the resources folder in the root of the server. It's not useful to add another copy elsewhere. --- test/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/build.py b/test/build.py index 13f4b1bec9..6d7268bfb7 100755 --- a/test/build.py +++ b/test/build.py @@ -173,7 +173,7 @@ def build_html(html_dir, use_sync): js_html_dir = os.path.join(html_dir, 'js') tests = convert_wast_to_js(js_html_dir) - copy_harness_files(js_html_dir, True) + copy_harness_files(js_html_dir, False) print('Building WPT tests from JS tests...') build_html_from_js(tests, html_dir, use_sync) From e4077ef305f3cf16ca6bda21feb31276d55274a8 Mon Sep 17 00:00:00 2001 From: Ms2ger Date: Mon, 13 Jul 2026 14:01:58 +0200 Subject: [PATCH 09/10] [test] Add documentation to build.py --- test/build.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/test/build.py b/test/build.py index 6d7268bfb7..1714260c08 100755 --- a/test/build.py +++ b/test/build.py @@ -30,14 +30,17 @@ def run(*cmd): # Preconditions. def ensure_remove_dir(path): + """Remove `path` if it exists.""" if os.path.exists(path): shutil.rmtree(path) def ensure_empty_dir(path): + """Create an empty directory at `path`, removing any existing one.""" ensure_remove_dir(path) os.mkdir(path) def compile_wasm_interpreter(): + """Compile the wasm interpreter; exit the process if this fails.""" print("Recompiling the wasm interpreter...") result = run('make', '-C', INTERPRETER_DIR, 'clean', 'default') if result.returncode != 0: @@ -48,7 +51,7 @@ def compile_wasm_interpreter(): def ensure_wasm_executable(): """ - Ensure we have built the wasm spec interpreter. + Ensure we have built the wasm spec interpreter; exit the process if it doesn't exist. """ result = run(WASM_EXEC, '-v', '-e', '') if result.returncode != 0: @@ -58,14 +61,19 @@ def ensure_wasm_executable(): # JS harness. def convert_one_wast_file(inputs): + """Translate a `.wast` to JS using the wasm spec interpreter.""" wast_file, js_file = inputs print('Compiling {} to JS...'.format(wast_file)) return run(WASM_EXEC, wast_file, '-j', '-o', js_file) def convert_wast_to_js(out_js_dir): """ - Compile all the wast files to JS and store the results in the JS dir, + Compile the wast files in `WAST_TESTS_DIR` to JS and store the results in `out_js_dir`, which is cleared first. + + This excludes the tests with `.fail.` in the file name. + + The compilation happens in parallel, using 8 processes. """ ensure_empty_dir(out_js_dir) @@ -99,6 +107,12 @@ def convert_wast_to_js(out_js_dir): return [js_file for (wast_file, js_file) in inputs] def copy_harness_files(out_js_dir, include_harness): + """ + Copy harness files into `out_js_dir/harness`. + + This always includes the {sync,async}_index.js files, + and the testharness files if `include_harness` is true. + """ harness_dir = os.path.join(out_js_dir, 'harness') ensure_empty_dir(harness_dir) @@ -109,6 +123,7 @@ def copy_harness_files(out_js_dir, include_harness): shutil.copy(js_file, harness_dir) def build_js(out_js_dir): + """Entry point for building all the JS tests.""" print('Building JS...') convert_wast_to_js(out_js_dir) copy_harness_files(out_js_dir, False) @@ -136,6 +151,8 @@ def build_js(out_js_dir): """ def wrap_single_test(js_file): + """Replace the file at `js_file` with one that wraps the code in an IIFE that also calls + `reinitializeRegistry()` after the test.""" test_func_name = os.path.basename(js_file).replace('.', '_').replace('-', '_') content = "(function {}() {{\n".format(test_func_name) @@ -168,6 +185,12 @@ def build_html_from_js(tests, html_dir, use_sync): f.write(content) def build_html(html_dir, use_sync): + """ + Entry point for building the HTML versions of the tests. + + We assume the tests will be run using a global copy of `testharness.js`, served from + `/resources` as happens in web-platform-tests. + """ print("Building HTML tests...") js_html_dir = os.path.join(html_dir, 'js') @@ -183,6 +206,7 @@ def build_html(html_dir, use_sync): # Front page harness. def build_front_page(out_dir, use_sync): + """Entry point for building a single HTML file including all of the tests.""" print('Building front page containing all the HTML tests...') js_out_dir = os.path.join(out_dir, 'js') From 4b5008daa959c5f6a6b8d26c36f245821c228dd9 Mon Sep 17 00:00:00 2001 From: Ms2ger Date: Mon, 13 Jul 2026 14:01:58 +0200 Subject: [PATCH 10/10] [test] Add support for generating .any.js versions of the core tests --- test/build.py | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/test/build.py b/test/build.py index 1714260c08..43c44db1b5 100755 --- a/test/build.py +++ b/test/build.py @@ -204,6 +204,34 @@ def build_html(html_dir, use_sync): print("Done building HTML tests.") +def build_any_js(output_dir, use_sync): + """ + Entry point for building the `.any.js` versions of the tests. + """ + print("Building `.any.js` tests...") + + tests = convert_wast_to_js(output_dir) + + index_path = "sync_index.js" if use_sync else "async_index.js" + for js_file in tests: + harness_path = os.path.relpath(os.path.join(output_dir, "harness"), os.path.dirname(js_file)) + with open(js_file, "r") as f: + content = f.read() + + with open(js_file.replace(".js", ".any.js"), "w") as f: + f.write(f"""\ +// META: script={harness_path}/{index_path} +// META: global=window +""") + f.write(content) + + os.remove(js_file) + + copy_harness_files(output_dir, False) + + print("Done building `.any.js` tests.") + + # Front page harness. def build_front_page(out_dir, use_sync): """Entry point for building a single HTML file including all of the tests.""" @@ -245,6 +273,11 @@ def process_args(): help="Relative path to the output directory for the Web Platform tests.", type=str) + parser.add_argument('--any-js', + dest="any_js_dir", + help="Relative path to the output directory for the `.any.js` Web Platform tests.", + type=str) + parser.add_argument('--front', dest="front_dir", help="Relative path to the output directory for the front page.", @@ -271,9 +304,10 @@ def process_args(): js_dir = args.js_dir html_dir = args.html_dir + any_js_dir = args.any_js_dir front_dir = args.front_dir - if front_dir is None and js_dir is None and html_dir is None: + if all(d is None for d in [js_dir, html_dir, any_js_dir, front_dir]): print('At least one mode must be selected.\n') parser.print_help() sys.exit(1) @@ -292,6 +326,9 @@ def process_args(): ensure_empty_dir(os.path.join(html_dir, d)) build_html(html_dir, args.use_sync) + if any_js_dir is not None: + build_any_js(any_js_dir, args.use_sync) + if front_dir is not None: ensure_empty_dir(front_dir) build_front_page(front_dir, args.use_sync)