Skip to content

Commit 5d0adb5

Browse files
committed
Enable support for running the multithreaded test harness in Safari.
1 parent 9bc3ffa commit 5d0adb5

File tree

2 files changed

+65
-27
lines changed

2 files changed

+65
-27
lines changed

test/common.py

Lines changed: 64 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,15 @@ def configure(data_dir):
130130
shutil.copy(test_file('firefox_user.js'), os.path.join(data_dir, 'user.js'))
131131

132132

133+
class SafariConfig:
134+
default_flags = ('', )
135+
executable_name = 'Safari'
136+
137+
@staticmethod
138+
def configure(data_dir):
139+
""" Safari has no special configuration step."""
140+
141+
133142
# Special value for passing to assert_returncode which means we expect that program
134143
# to fail with non-zero return code, but we don't care about specifically which one.
135144
NON_ZERO = -1
@@ -202,6 +211,10 @@ def is_firefox():
202211
return EMTEST_BROWSER and 'firefox' in EMTEST_BROWSER.lower()
203212

204213

214+
def is_safari():
215+
return EMTEST_BROWSER and 'safari' in EMTEST_BROWSER.lower()
216+
217+
205218
def compiler_for(filename, force_c=False):
206219
if shared.suffix(filename) in ('.cc', '.cxx', '.cpp') and not force_c:
207220
return EMXX
@@ -2418,6 +2431,8 @@ def configure_test_browser():
24182431
config = ChromeConfig()
24192432
elif is_firefox():
24202433
config = FirefoxConfig()
2434+
elif is_safari():
2435+
config = SafariConfig()
24212436
if config:
24222437
EMTEST_BROWSER += ' ' + ' '.join(config.default_flags)
24232438
if EMTEST_HEADLESS == 1:
@@ -2548,11 +2563,21 @@ def browser_restart(cls):
25482563
def browser_open(cls, url):
25492564
assert has_browser()
25502565
browser_args = EMTEST_BROWSER
2566+
parallel_harness = worker_id is not None
25512567

2552-
if EMTEST_BROWSER_AUTO_CONFIG:
2568+
config = None
2569+
if is_chrome():
2570+
config = ChromeConfig()
2571+
elif is_firefox():
2572+
config = FirefoxConfig()
2573+
elif is_safari():
2574+
config = SafariConfig()
2575+
2576+
# Prepare the browser data directory, if it uses one.
2577+
if EMTEST_BROWSER_AUTO_CONFIG and config and hasattr(config, 'data_dir_flag'):
25532578
logger.info('Using default CI configuration.')
25542579
browser_data_dir = DEFAULT_BROWSER_DATA_DIR
2555-
if worker_id is not None:
2580+
if parallel_harness:
25562581
# Running in parallel mode, give each browser its own profile dir.
25572582
browser_data_dir += '-' + str(worker_id)
25582583

@@ -2567,52 +2592,65 @@ def browser_open(cls, url):
25672592
# Recreate the new data directory.
25682593
os.mkdir(browser_data_dir)
25692594

2570-
if is_chrome():
2571-
config = ChromeConfig()
2572-
elif is_firefox():
2573-
config = FirefoxConfig()
2574-
else:
2575-
exit_with_error(f'EMTEST_BROWSER_AUTO_CONFIG only currently works with firefox or chrome. EMTEST_BROWSER was "{EMTEST_BROWSER}"')
2595+
if not config:
2596+
exit_with_error(f'EMTEST_BROWSER_AUTO_CONFIG only currently works with firefox, chrome and safari. EMTEST_BROWSER was "{EMTEST_BROWSER}"')
25762597
if WINDOWS:
25772598
# Escape directory delimiter backslashes for shlex.split.
25782599
browser_data_dir = browser_data_dir.replace('\\', '\\\\')
25792600
config.configure(browser_data_dir)
25802601
browser_args += f' {config.data_dir_flag}"{browser_data_dir}"'
25812602

25822603
browser_args = shlex.split(browser_args)
2604+
if is_safari():
2605+
# For the macOS 'open' command, pass
2606+
# --new: to make a new Safari app be launched, rather than add a tab to an existing Safari process/window
2607+
# --fresh: do not restore old tabs (e.g. if user had old navigated windows open)
2608+
# --background: Open the new Safari window behind the current Terminal window, to make following the test run more pleasing (this is for convenience only)
2609+
# -a <exe_name>: The path to the executable to open, in this case Safari
2610+
browser_args = ['open', '--new', '--fresh', '--background', '-a'] + browser_args
2611+
25832612
logger.info('Launching browser: %s', str(browser_args))
25842613

2585-
if WINDOWS and is_firefox():
2586-
cls.launch_browser_harness_windows_firefox(worker_id, config, browser_args, url)
2614+
if (WINDOWS and is_firefox()) or is_safari():
2615+
cls.launch_browser_harness_with_proc_snapshot_workaround(parallel_harness, config, browser_args, url)
25872616
else:
25882617
cls.browser_procs = [subprocess.Popen(browser_args + [url])]
25892618

25902619
@classmethod
2591-
def launch_browser_harness_windows_firefox(cls, worker_id, config, browser_args, url):
2592-
''' Dedicated function for launching browser harness on Firefox on Windows,
2593-
which requires extra care for window positioning and process tracking.'''
2620+
def launch_browser_harness_with_proc_snapshot_workaround(cls, parallel_harness, config, browser_args, url):
2621+
''' Dedicated function for launching browser harness in scenarios where
2622+
we need to identify the launched browser processes via a before-after
2623+
subprocess snapshotting delta workaround.'''
25942624

2625+
# In order for this to work, each browser needs to be launched one at a time
2626+
# so that we know which process belongs to which browser.
25952627
with FileLock(browser_spawn_lock_filename) as count:
2596-
# Firefox is a multiprocess browser. On Windows, killing the spawned
2597-
# process will not bring down the whole browser, but only one browser tab.
2598-
# So take a delta snapshot before->after spawning the browser to find
2599-
# which subprocesses we launched.
2600-
if worker_id is not None:
2628+
# Take a snapshot before spawning the browser to find which processes
2629+
# existed before launching the browser.
2630+
if parallel_harness or is_safari():
26012631
procs_before = list_processes_by_name(config.executable_name)
2632+
2633+
# Browser launch
26022634
cls.browser_procs = [subprocess.Popen(browser_args + [url])]
2603-
# Give Firefox time to spawn its subprocesses. Use an increasing timeout
2604-
# as a crude way to account for system load.
2605-
if worker_id is not None:
2635+
2636+
# Give the browser time to spawn its subprocesses. Use an increasing
2637+
# timeout as a crude way to account for system load.
2638+
if parallel_harness or is_safari():
26062639
time.sleep(2 + count * 0.3)
26072640
procs_after = list_processes_by_name(config.executable_name)
2641+
2642+
# Take a snapshot again to find which processes exist after launching
2643+
# the browser. Then the newly launched browser processes are determined
2644+
# by the delta before->after.
2645+
cls.browser_procs = list(set(procs_after).difference(set(procs_before)))
2646+
if len(cls.browser_procs) == 0:
2647+
logger.warning('Could not detect the launched browser subprocesses. The test harness may not be able to close browser windows if a test hangs, and at harness exit.')
2648+
2649+
# Firefox on Windows quirk:
26082650
# Make sure that each browser window is visible on the desktop. Otherwise
26092651
# browser might decide that the tab is backgrounded, and not load a test,
26102652
# or it might not tick rAF()s forward, causing tests to hang.
2611-
if worker_id is not None and not EMTEST_HEADLESS:
2612-
# On Firefox on Windows we needs to track subprocesses that got created
2613-
# by Firefox. Other setups can use 'browser_proc' directly to terminate
2614-
# the browser.
2615-
cls.browser_procs = list(set(procs_after).difference(set(procs_before)))
2653+
if WINDOWS and parallel_harness and not EMTEST_HEADLESS:
26162654
# Wrap window positions on a Full HD desktop area modulo primes.
26172655
for proc in cls.browser_procs:
26182656
move_browser_window(proc.pid, (300 + count * 47) % 1901, (10 + count * 37) % 997)

test/parallel_testsuite.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def python_multiprocessing_structures_are_buggy():
3535
def cap_max_workers_in_pool(max_workers, is_browser):
3636
if is_browser and 'EMTEST_CORES' not in os.environ and 'EMCC_CORES' not in os.environ:
3737
# TODO experiment with this number. In browser tests we'll be creating
38-
# a chrome instance per worker which is expensive.
38+
# a browser instance per worker which is expensive.
3939
max_workers = max_workers // 2
4040
# Python has an issue that it can only use max 61 cores on Windows: https://github.com/python/cpython/issues/89240
4141
if WINDOWS:

0 commit comments

Comments
 (0)