1010import json
1111import logging
1212import os
13+ import plistlib
1314import queue
1415import re
1516import shlex
3839from retryable_unittest import RetryableTestCase
3940
4041from tools import building , config , feature_matrix , shared , utils
42+ from tools .feature_matrix import UNSUPPORTED , Feature , min_browser_versions
4143from tools .settings import COMPILE_TIME_SETTINGS
4244from tools .shared import DEBUG , EMCC , EMXX , get_canonical_temp_dir , path_from_root
4345from tools .utils import (
4446 WINDOWS ,
4547 exit_with_error ,
48+ memoize ,
4649 read_binary ,
4750 read_file ,
4851 write_binary ,
108111browser_spawn_lock_filename = os .path .join (path_from_root ('out/browser_spawn_lock' ))
109112
110113
114+ @memoize
115+ def get_safari_version ():
116+ if not is_safari ():
117+ return UNSUPPORTED
118+ plist_path = os .path .join (EMTEST_BROWSER .strip (), 'Contents' , 'version.plist' )
119+ version_str = plistlib .load (open (plist_path , 'rb' )).get ('CFBundleShortVersionString' )
120+ # Split into parts (major.minor.patch)
121+ parts = (version_str .split ('.' ) + ['0' , '0' , '0' ])[:3 ]
122+ # Convert each part into integers, discarding any trailing string, e.g. '13a' -> 13.
123+ parts = [int (re .match (r"\d+" , s ).group ()) if re .match (r"\d+" , s ) else 0 for s in parts ]
124+ # Return version as XXYYZZ
125+ return parts [0 ] * 10000 + parts [1 ] * 100 + parts [2 ]
126+
127+
128+ @memoize
129+ def get_firefox_version ():
130+ if not is_firefox ():
131+ return UNSUPPORTED
132+ exe_path = shlex .split (EMTEST_BROWSER )[0 ]
133+ ini_path = os .path .join (os .path .dirname (exe_path ), "platform.ini" )
134+ # Extract the first numeric part before any dot (e.g. "Milestone=102.15.1" → 102)
135+ m = re .search (r"^Milestone=(.*)$" , open (ini_path ).read (), re .MULTILINE )
136+ milestone = m .group (1 ).strip ()
137+ version = int (re .match (r"(\d+)" , milestone ).group (1 ))
138+ # On Nightly and Beta, e.g. 145.0a1, pretend it to still mean version 144,
139+ # since it is a pre-release version
140+ if any (c in milestone for c in ('a' , 'b' )):
141+ version -= 1
142+ return version
143+
144+
145+ def browser_should_skip_feature (skip_env_var , feature ):
146+ # If an env. var. EMTEST_LACKS_x to skip the given test is set (to either
147+ # value 0 or 1), don't bother checking if current browser supports the feature
148+ # - just unconditionally run the test, or skip the test.
149+ if os .getenv (skip_env_var ) is not None :
150+ return int (os .getenv (skip_env_var )) != 0
151+
152+ # If there is no Feature object associated with this capability, then we
153+ # should run the test.
154+ if feature is None :
155+ return False
156+
157+ # If EMTEST_AUTOSKIP=0, also never skip.
158+ if os .getenv ('EMTEST_AUTOSKIP' ) == '0' :
159+ return False
160+
161+ # Otherwise EMTEST_AUTOSKIP=1 or EMTEST_AUTOSKIP is not set: check whether
162+ # the current browser supports the test or not.
163+ min_required = min_browser_versions [feature ]
164+ not_supported = get_firefox_version () < min_required ['firefox' ] or get_safari_version () < min_required ['safari' ]
165+
166+ # Current browser does not support the test, and EMTEST_AUTOSKIP is not set?
167+ # Then error out to have end user decide what to do in this situation.
168+ if not_supported and os .getenv ('EMTEST_AUTOSKIP' ) is None :
169+ return 'error'
170+
171+ # Report whether to skip the test based on browser support.
172+ return not_supported
173+
174+
111175# Default flags used to run browsers in CI testing:
112176class ChromeConfig :
113177 data_dir_flag = '--user-data-dir='
@@ -613,8 +677,16 @@ def require_simd(self):
613677 def require_wasm_legacy_eh (self ):
614678 if 'EMTEST_SKIP_WASM_LEGACY_EH' in os .environ :
615679 self .skipTest ('test requires node >= 17 or d8 (and EMTEST_SKIP_WASM_LEGACY_EH is set)' )
616-
617680 self .set_setting ('WASM_LEGACY_EXCEPTIONS' )
681+
682+ if self .is_browser_test ():
683+ skip = browser_should_skip_feature ('EMTEST_SKIP_WASM_LEGACY_EH' , Feature .WASM_LEGACY_EXCEPTIONS )
684+ if skip == 'error' :
685+ self .fail ('test requires Wasm Legacy EH' )
686+ elif skip :
687+ self .skipTest ('test requires Wasm Legacy EH' )
688+ return
689+
618690 if self .try_require_node_version (17 ):
619691 return
620692
@@ -630,11 +702,17 @@ def require_wasm_eh(self):
630702 if 'EMTEST_SKIP_WASM_EH' in os .environ :
631703 self .skipTest ('test requires node v24 or d8 (and EMTEST_SKIP_WASM_EH is set)' )
632704 self .set_setting ('WASM_LEGACY_EXCEPTIONS' , 0 )
633- if self .try_require_node_version (22 ):
634- self .node_args .append ('--experimental-wasm-exnref' )
635- return
636705
637706 if self .is_browser_test ():
707+ skip = browser_should_skip_feature ('EMTEST_SKIP_WASM_EH' , Feature .WASM_EXCEPTIONS )
708+ if skip == 'error' :
709+ self .fail ('test requires Wasm EH' )
710+ elif skip :
711+ self .skipTest ('test requires Wasm EH' )
712+ return
713+
714+ if self .try_require_node_version (22 ):
715+ self .node_args .append ('--experimental-wasm-exnref' )
638716 return
639717
640718 v8 = self .get_v8 ()
0 commit comments