| # Copyright 2013 The Emscripten Authors. All rights reserved. | |
| # Emscripten is available under two separate licenses, the MIT license and the | |
| # University of Illinois/NCSA Open Source License. Both these licenses can be | |
| # found in the LICENSE file. | |
| import logging | |
| import os | |
| import shlex | |
| import subprocess | |
| import sys | |
| from subprocess import PIPE, CalledProcessError | |
| import common | |
| from tools import utils | |
| WORKING_ENGINES: dict[str, bool] = {} # Holds all configured engines and whether they work: maps path -> True/False | |
| DEFAULT_TIMEOUT = int(os.environ.get('EMTEST_TIMEOUT', str(5 * 60))) | |
| def make_command(filename, engine, args=None): | |
| args = args or [] | |
| # if no engine is needed, indicated by None, then there is a native executable | |
| # provided which we can just run | |
| if engine[0] is None: | |
| executable = utils.replace_suffix(os.path.abspath(filename), '.exe') | |
| return [executable] + args | |
| # Emscripten supports multiple javascript runtimes. The default is nodejs but | |
| # it can also use d8 (the v8 engine shell) or jsc (JavaScript Core aka | |
| # Safari). Both d8 and jsc require a '--' to delimit arguments to be passed | |
| # to the executed script from d8/jsc options. Node does not require a | |
| # delimiter--arguments after the filename are passed to the script. | |
| # | |
| # Check only the last part of the engine path to ensure we don't accidentally | |
| # label a path to nodejs containing a 'd8' as spidermonkey instead. | |
| jsengine = os.path.basename(engine[0]) | |
| # Use "'d8' in" because the name can vary, e.g. d8_g, d8, etc. | |
| is_d8 = 'd8' in jsengine or 'v8' in jsengine | |
| is_jsc = 'jsc' in jsengine or 'javascriptcore' in jsengine | |
| is_wasmer = 'wasmer' in jsengine | |
| is_wasmtime = 'wasmtime' in jsengine | |
| command_flags = [] | |
| if is_wasmer: | |
| command_flags += ['run'] | |
| if is_wasmer or is_wasmtime: | |
| # in a wasm runtime, run the wasm, not the js | |
| filename = utils.replace_suffix(filename, '.wasm') | |
| # Separates engine flags from script flags | |
| flag_separator = ['--'] if is_d8 or is_jsc else [] | |
| return engine + command_flags + [filename] + flag_separator + args | |
| def check_engine(engine): | |
| if type(engine) is list: | |
| engine_path = engine[0] | |
| else: | |
| engine_path = engine | |
| if engine_path not in WORKING_ENGINES: | |
| logging.debug(f'Checking JS engine {engine}') | |
| try: | |
| output = run_js(utils.path_from_root('test/hello_world.js'), engine, skip_check=True) | |
| if 'Hello, world!' in output: | |
| WORKING_ENGINES[engine_path] = True | |
| else: | |
| WORKING_ENGINES[engine_path] = False | |
| except Exception as e: | |
| logging.info(f'Checking JS engine {engine} failed. Check your config file. Details: {e}') | |
| WORKING_ENGINES[engine_path] = False | |
| return WORKING_ENGINES[engine_path] | |
| def require_engine(engine): | |
| engine_path = engine[0] | |
| # if None is the "engine", it means we compiled to a native executable; | |
| # there is no engine to check here | |
| if engine_path is None: | |
| return | |
| if engine_path not in WORKING_ENGINES: | |
| check_engine(engine) | |
| if not WORKING_ENGINES[engine_path]: | |
| logging.critical(f'The engine ({engine}) does not seem to work, check the paths in the config file') | |
| sys.exit(1) | |
| def run_js(filename, engine, args=None, | |
| stdin=None, stdout=PIPE, stderr=None, cwd=None, input=None, | |
| full_output=False, assert_returncode=0, skip_check=False, | |
| timeout=DEFAULT_TIMEOUT): | |
| """Execute javascript code generated by tests, with possible timeout.""" | |
| # We used to support True here but we no longer do. Assert here just in case. | |
| assert type(assert_returncode) is int | |
| if not os.path.exists(filename): | |
| raise Exception('output file not found: ' + filename) | |
| command = make_command(os.path.abspath(filename), engine, args) | |
| if common.EMTEST_VERBOSE: | |
| print(f"Running: '{shlex.join(command)}'") | |
| try: | |
| proc = subprocess.run( | |
| command, | |
| stdin=stdin, | |
| stdout=stdout, | |
| stderr=stderr, | |
| input=input, | |
| cwd=cwd, | |
| timeout=timeout, | |
| text=True) | |
| except Exception: | |
| # the failure may be because the engine is not present. show the proper | |
| # error in that case | |
| if not skip_check: | |
| require_engine(engine) | |
| # if we got here, then require_engine succeeded, so we can raise the original error | |
| raise | |
| out = ['' if o is None else o for o in (proc.stdout, proc.stderr)] | |
| ret = '\n'.join(out) if full_output else out[0] | |
| if assert_returncode == common.NON_ZERO: | |
| if proc.returncode == 0: | |
| raise CalledProcessError(proc.returncode, ' '.join(command), str(ret)) | |
| elif proc.returncode != assert_returncode: | |
| raise CalledProcessError(proc.returncode, ' '.join(command), str(ret)) | |
| return ret |