diff --git a/test/test_browser.py b/test/test_browser.py index a0ca3839bab5c..3f1bbe28b5ecb 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -28,7 +28,7 @@ from common import HttpServerThread, requires_dev_dependency from tools import shared from tools import ports -from tools.feature_matrix import UNSUPPORTED +from tools.feature_matrix import UNSUPPORTED, min_browser_versions, Feature from tools.shared import EMCC, WINDOWS, FILE_PACKAGER, PIPE, DEBUG from tools.utils import delete_dir, memoize @@ -158,6 +158,23 @@ def get_safari_version(): return parts[0] * 10000 + parts[1] * 100 + parts[2] +@memoize +def get_firefox_version(): + if not is_firefox(): + return UNSUPPORTED + exe_path = shlex.split(common.EMTEST_BROWSER)[0] + ini_path = os.path.join(os.path.dirname(exe_path), "platform.ini") + # Extract the first numeric part before any dot (e.g. "Milestone=102.15.1" → 102) + m = re.search(r"^Milestone=([^\n\r]+)", open(ini_path).read(), re.MULTILINE) + milestone = m.group(1).strip() + version = int(re.match(r"(\d+)", milestone).group(1)) + # On Nightly and BEta, e.g. 145.0a1, pretend it to still mean version 144, + # since it is a pre-release version + if any(c in milestone for c in ("a", "b")): + version -= 1 + return version + + no_swiftshader = skip_if_simple('not compatible with swiftshader', is_swiftshader) no_chrome = skip_if('no_chrome', lambda _: is_chrome(), 'chrome is not supported') @@ -213,12 +230,28 @@ def decorated(self, *args, **kwargs): return decorator +def test_browser_should_skip_feature(skip_env_var, feature): + if os.getenv(skip_env_var) is not None: + return int(os.getenv(skip_env_var)) != 0 + + min_required = min_browser_versions[feature] + not_supported = get_firefox_version() < min_required['firefox'] or get_safari_version() < min_required['safari'] + + if not_supported and int(os.getenv('EMTEST_AUTOSKIP', '0')): + return True + +# if not_supported and os.getenv('EMTEST_AUTOSKIP') is None: +# TODO: failTest(f'This test requires a browser that supports {feature} but your browser does not support this. Run with {skip_env_var}=1 or EMTEST_AUTOSKIP=1 to skip this test automatically.') + + return False + + def webgl2_disabled(): - return os.getenv('EMTEST_LACKS_WEBGL2') or os.getenv('EMTEST_LACKS_GRAPHICS_HARDWARE') + return os.getenv('EMTEST_LACKS_GRAPHICS_HARDWARE') or test_browser_should_skip_feature('EMTEST_LACKS_WEBGL2', Feature.WEBGL2) def webgpu_disabled(): - return os.getenv('EMTEST_LACKS_WEBGPU') or os.getenv('EMTEST_LACKS_GRAPHICS_HARDWARE') + return os.getenv('EMTEST_LACKS_GRAPHICS_HARDWARE') or test_browser_should_skip_feature('EMTEST_LACKS_WEBGPU', Feature.WEBGPU) requires_graphics_hardware = skipExecIf(os.getenv('EMTEST_LACKS_GRAPHICS_HARDWARE'), 'This test requires graphics hardware') @@ -226,13 +259,13 @@ def webgpu_disabled(): requires_webgpu = unittest.skipIf(webgpu_disabled(), "This test requires WebGPU to be available") requires_sound_hardware = skipExecIf(os.getenv('EMTEST_LACKS_SOUND_HARDWARE'), 'This test requires sound hardware') requires_microphone_access = skipExecIf(os.getenv('EMTEST_LACKS_MICROPHONE_ACCESS'), 'This test accesses microphone, which may need accepting a user prompt to enable it.') -requires_offscreen_canvas = unittest.skipIf(os.getenv('EMTEST_LACKS_OFFSCREEN_CANVAS'), 'This test requires a browser with OffscreenCanvas') -requires_es6_workers = unittest.skipIf(os.getenv('EMTEST_LACKS_ES6_WORKERS'), 'This test requires a browser with ES6 Module Workers support') -requires_growable_arraybuffers = unittest.skipIf(os.getenv('EMTEST_LACKS_GROWABLE_ARRAYBUFFERS'), 'This test requires a browser that supports growable ArrayBuffers') +requires_offscreen_canvas = unittest.skipIf(test_browser_should_skip_feature('EMTEST_LACKS_OFFSCREEN_CANVAS', Feature.OFFSCREENCANVAS_SUPPORT), 'This test requires a browser with OffscreenCanvas') +requires_es6_workers = unittest.skipIf(test_browser_should_skip_feature('EMTEST_LACKS_ES6_WORKERS', Feature.WORKER_ES6_MODULES), 'This test requires a browser with ES6 Module Workers support') +requires_growable_arraybuffers = unittest.skipIf(test_browser_should_skip_feature('EMTEST_LACKS_GROWABLE_ARRAYBUFFERS', Feature.GROWABLE_ARRAYBUFFERS), 'This test requires a browser that supports growable ArrayBuffers') # N.b. not all SharedArrayBuffer requiring tests are annotated with this decorator, since at this point there are so many of such tests. # As a middle ground, if a test has a name 'thread' or 'wasm_worker' in it, then it does not need decorating. To run all single-threaded tests in # the suite, one can run "EMTEST_LACKS_SHARED_ARRAY_BUFFER=1 test/runner browser skip:browser.test_*thread* skip:browser.test_*wasm_worker* skip:browser.test_*audio_worklet*" -requires_shared_array_buffer = unittest.skipIf(os.getenv('EMTEST_LACKS_SHARED_ARRAY_BUFFER'), 'This test requires a browser with SharedArrayBuffer support') +requires_shared_array_buffer = unittest.skipIf(test_browser_should_skip_feature('EMTEST_LACKS_SHARED_ARRAY_BUFFER', Feature.THREADS), 'This test requires a browser with SharedArrayBuffer support') class browser(BrowserCore): diff --git a/tools/feature_matrix.py b/tools/feature_matrix.py index 56a8f30b5c736..025664b75413f 100644 --- a/tools/feature_matrix.py +++ b/tools/feature_matrix.py @@ -43,6 +43,9 @@ class Feature(IntEnum): OFFSCREENCANVAS_SUPPORT = auto() WASM_LEGACY_EXCEPTIONS = auto() WASM_EXCEPTIONS = auto() + WEBGL2 = auto() + WEBGPU = auto() + GROWABLE_ARRAYBUFFERS = auto() disable_override_features = set() @@ -97,6 +100,18 @@ class Feature(IntEnum): 'safari': UNSUPPORTED, 'node': 230000, }, + Feature.WEBGL2: { + 'chrome': 56, + 'firefox': 51, + 'safari': 150000, + 'node': UNSUPPORTED, + }, + Feature.WEBGPU: { + 'chrome': 113, + 'firefox': 141, + 'safari': 260000, + 'node': UNSUPPORTED, + }, # https://caniuse.com/mdn-api_worker_worker_ecmascript_modules: The ability to # call new Worker(url, { type: 'module' }); Feature.WORKER_ES6_MODULES: { @@ -134,6 +149,14 @@ class Feature(IntEnum): # Node.js 26) 'node': 240000, }, + # Growable SharedArrayBuffers improves memory growth feature in multithreaded + # builds by avoiding need to poll resizes to ArrayBuffer views in Workers. + Feature.GROWABLE_ARRAYBUFFERS: { + 'chrome': 136, + 'firefox': 145, + 'safari': UNSUPPORTED, + 'node': 240000, + }, } # Static assertion to check that we actually need each of the above feature flags