Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.

Commit 78f7639

Browse files
committed
Bug 1571426 - Read geckodriver port on startup for all wdspec tests, r=webdriver-reviewers,jdescottes
Instead of using get_free_port(), pass in 0 as the port number and allow geckodriver to select a free port. Then read the port from the geckodriver output at startup. This should prevent rare race conditions where we happen to select a port that later turns out to be used. Differential Revision: https://phabricator.services.mozilla.com/D189927
1 parent 17743ef commit 78f7639

File tree

2 files changed

+61
-11
lines changed

2 files changed

+61
-11
lines changed

testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/base.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import os
66
import platform
77
import socket
8+
import time
89
import traceback
910
from abc import ABCMeta, abstractmethod
1011

@@ -302,8 +303,7 @@ def __init__(self, logger, binary=None, webdriver_binary=None,
302303
self.env = os.environ.copy() if env is None else env
303304
self.webdriver_args = webdriver_args if webdriver_args is not None else []
304305

305-
self.url = f"http://{self.host}:{self.port}{self.base_path}"
306-
306+
self.init_deadline = None
307307
self._output_handler = None
308308
self._cmd = None
309309
self._proc = None
@@ -314,10 +314,12 @@ def make_command(self):
314314
return [self.webdriver_binary] + self.webdriver_args
315315

316316
def start(self, group_metadata, **kwargs):
317+
self.init_deadline = time.time() + self.init_timeout
317318
try:
318319
self._run_server(group_metadata, **kwargs)
319320
except KeyboardInterrupt:
320321
self.stop()
322+
raise
321323

322324
def create_output_handler(self, cmd):
323325
"""Return an instance of the class used to handle application output.
@@ -351,7 +353,7 @@ def _run_server(self, group_metadata, **kwargs):
351353
self.logger,
352354
self.host,
353355
self.port,
354-
timeout=self.init_timeout,
356+
timeout=self.init_deadline - time.time(),
355357
server_process=self._proc,
356358
)
357359
except Exception:
@@ -384,6 +386,12 @@ def stop(self, force=False):
384386
def is_alive(self):
385387
return hasattr(self._proc, "proc") and self._proc.poll() is None
386388

389+
@property
390+
def url(self):
391+
if self.port is not None:
392+
return f"http://{self.host}:{self.port}{self.base_path}"
393+
raise ValueError("Can't get WebDriver URL before port is assigned")
394+
387395
@property
388396
def pid(self):
389397
if self._proc is not None:

testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/firefox.py

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import json
44
import os
55
import platform
6+
import re
67
import signal
78
import subprocess
89
import sys
@@ -604,6 +605,32 @@ def __call__(self, line):
604605
command=" ".join(self.command))
605606

606607

608+
class GeckodriverOutputHandler(FirefoxOutputHandler):
609+
PORT_RE = re.compile(b".*Listening on [^ :]*:(\d+)")
610+
611+
def __init__(self, logger, command, symbols_path=None, stackfix_dir=None, asan=False,
612+
leak_report_file=None, init_deadline=None):
613+
super().__init__(logger, command, symbols_path=symbols_path, stackfix_dir=stackfix_dir, asan=asan,
614+
leak_report_file=leak_report_file)
615+
self.port = None
616+
self.init_deadline = None
617+
618+
def after_process_start(self, pid):
619+
super().after_process_start(pid)
620+
while self.port is None:
621+
time.sleep(0.1)
622+
if self.init_deadline is not None and time.time() > self.init_deadline:
623+
raise TimeoutError("Failed to get geckodriver port within the timeout")
624+
625+
def __call__(self, line):
626+
if self.port is None:
627+
m = self.PORT_RE.match(line)
628+
if m is not None:
629+
self.port = int(m.groups()[0])
630+
self.logger.debug(f"Got geckodriver port {self.port}")
631+
super().__call__(line)
632+
633+
607634
class ProfileCreator:
608635
def __init__(self, logger, prefs_root, config, test_type, extra_prefs, e10s,
609636
disable_fission, debug_test, browser_channel, binary, certutil_binary,
@@ -922,12 +949,13 @@ def get_env(self, binary, debug_info, headless, chaos_mode_flags):
922949
return env
923950

924951
def create_output_handler(self, cmd):
925-
return FirefoxOutputHandler(self.logger,
926-
cmd,
927-
stackfix_dir=self.stackfix_dir,
928-
symbols_path=self.symbols_path,
929-
asan=self.asan,
930-
leak_report_file=self.leak_report_file)
952+
return GeckodriverOutputHandler(self.logger,
953+
cmd,
954+
stackfix_dir=self.stackfix_dir,
955+
symbols_path=self.symbols_path,
956+
asan=self.asan,
957+
leak_report_file=self.leak_report_file,
958+
init_deadline=self.init_deadline)
931959

932960
def start(self, group_metadata, **kwargs):
933961
self.leak_report_file = setup_leak_report(self.leak_check, self.profile, self.env)
@@ -971,7 +999,12 @@ def stop(self, force=False):
971999
time.sleep(1)
9721000
else:
9731001
self.logger.debug("WebDriver session didn't end")
974-
super().stop(force=force)
1002+
try:
1003+
super().stop(force=force)
1004+
finally:
1005+
if self._output_handler is not None:
1006+
self._output_handler.port = None
1007+
self._port = None
9751008

9761009
def cleanup(self):
9771010
super().cleanup()
@@ -985,10 +1018,19 @@ def settings(self, test):
9851018
"mozleak_allowed": self.leak_check and test.mozleak_allowed,
9861019
"mozleak_thresholds": self.leak_check and test.mozleak_threshold}
9871020

1021+
@property
1022+
def port(self):
1023+
# We read the port from geckodriver on startup
1024+
if self._port is None:
1025+
if self._output_handler is None or self._output_handler.port is None:
1026+
raise ValueError("Can't get geckodriver port before it's started")
1027+
self._port = self._output_handler.port
1028+
return self._port
1029+
9881030
def make_command(self):
9891031
return [self.webdriver_binary,
9901032
"--host", self.host,
991-
"--port", str(self.port)] + self.webdriver_args
1033+
"--port", "0"] + self.webdriver_args
9921034

9931035
def executor_browser(self):
9941036
cls, args = super().executor_browser()

0 commit comments

Comments
 (0)