Skip to content

Commit 0a1e309

Browse files
committed
Bug 1919741 - Allow specifying extra websockets handler directories, r=Sasha
This allows vendors to write their own websockets handlers for non-shared tests. Note that the handlers still all share the same namespace, so vendor handlers must have a globally unique name, not just unique in the vendor directory. Differential Revision: https://phabricator.services.mozilla.com/D222758
1 parent e495a2a commit 0a1e309

File tree

8 files changed

+156
-32
lines changed

8 files changed

+156
-32
lines changed

testing/web-platform/manifestupdate.py

+15-7
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def run(src_root, obj_root, logger=None, **kwargs):
120120

121121
ensure_manifest_directories(logger, test_paths)
122122

123-
local_config = read_local_config(src_wpt_dir)
123+
local_config = read_local_config(os.path.join(src_wpt_dir, "wptrunner.ini"))
124124
for section in ["manifest:upstream", "manifest:mozilla"]:
125125
url_base = local_config.get(section, "url_base")
126126
manifest_rel_path = os.path.join(
@@ -172,9 +172,7 @@ def ensure_manifest_directories(logger, test_paths):
172172
raise IOError("Manifest directory is a file")
173173

174174

175-
def read_local_config(wpt_dir):
176-
src_config_path = os.path.join(wpt_dir, "wptrunner.ini")
177-
175+
def read_local_config(src_config_path):
178176
parser = configparser.ConfigParser()
179177
success = parser.read(src_config_path)
180178
assert src_config_path in success
@@ -193,15 +191,20 @@ def generate_config(logger, repo_root, wpt_dir, dest_path, force_rewrite=False):
193191
if e.errno != errno.EEXIST:
194192
raise
195193

194+
src_config_path = os.path.join(wpt_dir, "wptrunner.ini")
196195
dest_config_path = os.path.join(dest_path, "wptrunner.local.ini")
197196

198-
if not force_rewrite and os.path.exists(dest_config_path):
197+
if (
198+
not force_rewrite
199+
and os.path.exists(dest_config_path)
200+
and os.stat(dest_config_path).st_mtime >= os.stat(src_config_path).st_mtime
201+
):
199202
logger.debug("Config is up to date, not regenerating")
200203
return dest_config_path
201204

202-
logger.info("Creating config file %s" % dest_config_path)
205+
logger.info(f"Creating config file {dest_config_path}")
203206

204-
parser = read_local_config(wpt_dir)
207+
parser = read_local_config(src_config_path)
205208

206209
for section in ["manifest:upstream", "manifest:mozilla"]:
207210
meta_rel_path = parser.get(section, "metadata")
@@ -218,6 +221,11 @@ def generate_config(logger, repo_root, wpt_dir, dest_path, force_rewrite=False):
218221
"prefs",
219222
os.path.abspath(os.path.join(wpt_dir, parser.get("paths", "prefs"))),
220223
)
224+
ws_extra_paths = ";".join(
225+
os.path.abspath(os.path.join(wpt_dir, path))
226+
for path in parser.get("paths", "ws_extra").split(";")
227+
)
228+
parser.set("paths", "ws_extra", ws_extra_paths)
221229

222230
with open(dest_config_path, "wt") as config_file:
223231
parser.write(config_file)

testing/web-platform/tests/tools/serve/serve.py

+13-3
Original file line numberDiff line numberDiff line change
@@ -885,7 +885,8 @@ def start_http2_server(logger, host, port, paths, routes, bind_address, config,
885885

886886

887887
class WebSocketDaemon:
888-
def __init__(self, host, port, doc_root, handlers_root, bind_address, ssl_config):
888+
def __init__(self, host, port, doc_root, handlers_root, bind_address, ssl_config,
889+
extra_handler_paths=None):
889890
logger = logging.getLogger()
890891
self.host = host
891892
cmd_args = ["-p", port,
@@ -903,6 +904,9 @@ def __init__(self, host, port, doc_root, handlers_root, bind_address, ssl_config
903904
opts.cgi_directories = []
904905
opts.is_executable_method = None
905906
self.server = pywebsocket.WebSocketServer(opts)
907+
if extra_handler_paths:
908+
for path in extra_handler_paths:
909+
self.server.websocket_server_options.dispatcher._source_handler_files_in_dir(path, path, False, None)
906910
ports = [item[0].getsockname()[1] for item in self.server._sockets]
907911
if not ports:
908912
# TODO: Fix the logging configuration in WebSockets processes
@@ -946,7 +950,8 @@ def start_ws_server(logger, host, port, paths, routes, bind_address, config, **k
946950
repo_root,
947951
config.paths["ws_doc_root"],
948952
bind_address,
949-
ssl_config=None)
953+
ssl_config=None,
954+
extra_handler_paths=config.paths["ws_extra"])
950955
except Exception as error:
951956
logger.critical(f"start_ws_server: Caught exception from WebSocketDomain: {error}")
952957
startup_failed(logger)
@@ -959,7 +964,8 @@ def start_wss_server(logger, host, port, paths, routes, bind_address, config, **
959964
repo_root,
960965
config.paths["ws_doc_root"],
961966
bind_address,
962-
config.ssl_config)
967+
config.ssl_config,
968+
extra_handler_paths=config.paths["ws_extra"])
963969
except Exception as error:
964970
logger.critical(f"start_wss_server: Caught exception from WebSocketDomain: {error}")
965971
startup_failed(logger)
@@ -1032,6 +1038,7 @@ class ConfigBuilder(config.ConfigBuilder):
10321038
},
10331039
"doc_root": repo_root,
10341040
"ws_doc_root": os.path.join(repo_root, "websockets", "handlers"),
1041+
"ws_extra": None,
10351042
"server_host": None,
10361043
"ports": {
10371044
"http": [8000, "auto"],
@@ -1100,6 +1107,7 @@ def _get_ws_doc_root(self, data):
11001107
def _get_paths(self, data):
11011108
rv = super()._get_paths(data)
11021109
rv["ws_doc_root"] = data["ws_doc_root"]
1110+
rv["ws_extra"] = data["ws_extra"]
11031111
return rv
11041112

11051113

@@ -1155,6 +1163,8 @@ def get_parser():
11551163
help="Path to document root. Overrides config.")
11561164
parser.add_argument("--ws_doc_root", action="store", dest="ws_doc_root",
11571165
help="Path to WebSockets document root. Overrides config.")
1166+
parser.add_argument("--ws_extra", action="append", dest="ws_extra", default=[],
1167+
help="Path to extra directory containing ws handlers. Overrides config.")
11581168
parser.add_argument("--inject-script", default=None,
11591169
help="Path to script file to inject, useful for testing polyfills.")
11601170
parser.add_argument("--alias_file", action="store", dest="alias_file",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
wptrunner Configuration
2+
=======================
3+
4+
wptrunner can be configured using two mechanisms:
5+
6+
* Command line arguments
7+
8+
* A ``wptrunner.ini`` configuration file
9+
10+
Command Line Arguments
11+
----------------------
12+
13+
Command line arguments are the most common way of configuring
14+
wptrunner. The current list of command line arguments can be seen by
15+
starting wptrunner with the ``--help`` command line argument.
16+
17+
Command line arguments override options given in the configuration file.
18+
19+
20+
Configuration File
21+
------------------
22+
23+
A configuration file can be passed using the ``--config`` command line
24+
argument. If no argument is supplied then ``wptrunner.ini`` in the
25+
current working directory will be used, if it exists, otherwise
26+
``wptrunner.default.ini`` in the wptrunner directory. Only a single
27+
configuration file is used.
28+
29+
Typicaly frontends to wptrunner are expected to pass in their own
30+
configuration file.
31+
32+
The configuration file contains the following known paths and sections:
33+
34+
:paths:
35+
Data about default paths to use.
36+
37+
:prefs:
38+
Path to profile root directory. Equivalent to the
39+
``--profile-root`` command line argument.
40+
41+
:run_info:
42+
Path to the directory containing extra run info JSON
43+
files to add to the run info data. Equivalent to the ``--run-info``
44+
command line argument.
45+
46+
:ws_extra:
47+
Semicolon-separated list of extra paths to use for
48+
websockets handlers. Equivalent to the ``--ws-extra`` command line
49+
argument.
50+
51+
:web-platform-tests:
52+
Data about the web-platform-tests repository. This is only used by the
53+
repository sync code and can be considered deprecated.
54+
55+
:remote_url: URL of the wpt repository to sync from
56+
:branch: Branch name to sync from
57+
:sync_path: Directory to use when performing a sync
58+
59+
In addition the command line allows specifying *multiple* sections
60+
each corresponding to a test manifest. These are named
61+
``manifest:[name]``. The ``name`` is arbitary, but must be unique in
62+
the file. At least one such section is required so that wptrunner
63+
knows where to find some tests.
64+
65+
:manifest\:[name]:
66+
Data about tests in a given subtree.
67+
68+
:tests: Path to the root of the subtree containing tests.
69+
:meta: Path to the corresponding metadata directory.
70+
:url_base: URL prefix to for the tests in this manifest. This
71+
should be ``/`` for the default manifest but must be
72+
different for other manifests.
73+
74+
For example a vendor with both upstream web-platform-tests under an
75+
``upstream`` subtree, and vendor-specific web-platform-tests under a
76+
``local`` substree, might have a configuration like::
77+
78+
[manifest:upstream]
79+
tests = upstream/tests
80+
metadata = upstream/meta
81+
url_base = /
82+
83+
[manifest:vendor]
84+
tests = local/tests
85+
metadata = local/meta
86+
url_base = /_local/

testing/web-platform/tests/tools/wptrunner/wptrunner/config.py

+11-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import os
55
import sys
66
from collections import OrderedDict
7-
from typing import Dict, Mapping, Optional
7+
from typing import Dict, Mapping, Optional, List
88

99
here = os.path.dirname(__file__)
1010

@@ -14,12 +14,19 @@ def __init__(self, base_path: str, *args: str, **kwargs: str):
1414
self.base_path = base_path
1515
dict.__init__(self, *args, **kwargs)
1616

17+
def _normalize_path(self, path: str) -> str:
18+
os.path.expanduser(path)
19+
return os.path.abspath(os.path.join(self.base_path, path))
20+
1721
def get_path(self, key: str, default:Optional[str] = None) -> Optional[str]:
1822
if key not in self:
1923
return default
20-
path = self[key]
21-
os.path.expanduser(path)
22-
return os.path.abspath(os.path.join(self.base_path, path))
24+
return self._normalize_path(self[key])
25+
26+
def get_paths(self, key: str, default:Optional[List[str]] = None) -> Optional[List[str]]:
27+
if key not in self:
28+
return default
29+
return [self._normalize_path(item.strip()) for item in self[key].split(";")]
2330

2431

2532
def read(config_path: str) -> Mapping[str, ConfigDict]:

testing/web-platform/tests/tools/wptrunner/wptrunner/environment.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ class TestEnvironment:
9898
def __init__(self, test_paths, testharness_timeout_multipler,
9999
pause_after_test, debug_test, debug_info, options, ssl_config, env_extras,
100100
enable_webtransport=False, mojojs_path=None, inject_script=None,
101-
suppress_handler_traceback=None):
101+
suppress_handler_traceback=None, ws_extra=None):
102102

103103
self.test_paths = test_paths
104104
self.server = None
@@ -124,6 +124,7 @@ def __init__(self, test_paths, testharness_timeout_multipler,
124124
self.mojojs_path = mojojs_path
125125
self.inject_script = inject_script
126126
self.suppress_handler_traceback = suppress_handler_traceback
127+
self.ws_extra = ws_extra
127128

128129
def __enter__(self):
129130
server_log_handler = self._stack.enter_context(self.server_logging_ctx)
@@ -177,7 +178,7 @@ def ignore_interrupts(self):
177178
def build_config(self):
178179
override_path = os.path.join(serve_path(self.test_paths), "config.json")
179180

180-
config = serve.ConfigBuilder(self.server_logger)
181+
config = serve.ConfigBuilder(self.server_logger, ws_extra=self.ws_extra)
181182

182183
ports = {
183184
"http": [8000, 8001],

testing/web-platform/tests/tools/wptrunner/wptrunner/wptcommandline.py

+25-15
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,9 @@ def create_parser(product_choices=None):
290290
config_group.add_argument("--no-suppress-handler-traceback", action="store_false",
291291
dest="supress_handler_traceback",
292292
help="Write the stacktrace for exceptions in server handlers")
293+
config_group.add_argument("--ws-extra", action="append", default=None,
294+
dest="ws_extra",
295+
help="Extra paths containing websockets handlers")
293296

294297
build_type = parser.add_mutually_exclusive_group()
295298
build_type.add_argument("--debug-build", dest="debug", action="store_true",
@@ -469,24 +472,31 @@ def set_from_config(kwargs):
469472

470473
kwargs["product"] = products.Product(kwargs["config"], kwargs["product"])
471474

472-
keys = {"paths": [("prefs", "prefs_root", True),
473-
("run_info", "run_info", True)],
474-
"web-platform-tests": [("remote_url", "remote_url", False),
475-
("branch", "branch", False),
476-
("sync_path", "sync_path", True)],
477-
"SSL": [("openssl_binary", "openssl_binary", True),
478-
("certutil_binary", "certutil_binary", True),
479-
("ca_cert_path", "ca_cert_path", True),
480-
("host_cert_path", "host_cert_path", True),
481-
("host_key_path", "host_key_path", True)]}
475+
keys = {"paths": [("prefs", "prefs_root", "path"),
476+
("run_info", "run_info", "path"),
477+
("ws_extra", "ws_extra", "paths")],
478+
"web-platform-tests": [("remote_url", "remote_url", "str"),
479+
("branch", "branch", "str"),
480+
("sync_path", "sync_path", "path")],
481+
"SSL": [("openssl_binary", "openssl_binary", "path"),
482+
("certutil_binary", "certutil_binary", "path"),
483+
("ca_cert_path", "ca_cert_path", "path"),
484+
("host_cert_path", "host_cert_path", "path"),
485+
("host_key_path", "host_key_path", "path")]}
486+
487+
getters = {
488+
"str": "get",
489+
"path": "get_path",
490+
"paths": "get_paths"
491+
}
482492

483493
for section, values in keys.items():
484-
for config_value, kw_value, is_path in values:
494+
for config_value, kw_value, prop_type in values:
495+
if prop_type not in getters:
496+
raise ValueError(f"Unknown config property type {prop_type}")
497+
getter_name = getters[prop_type]
485498
if kw_value in kwargs and kwargs[kw_value] is None:
486-
if not is_path:
487-
new_value = kwargs["config"].get(section, config.ConfigDict({})).get(config_value)
488-
else:
489-
new_value = kwargs["config"].get(section, config.ConfigDict({})).get_path(config_value)
499+
new_value = getattr(kwargs["config"].get(section, config.ConfigDict({})), getter_name)(config_value)
490500
kwargs[kw_value] = new_value
491501

492502
test_paths = get_test_paths(kwargs["config"],

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,8 @@ def run_tests(config, product, test_paths, **kwargs):
456456
kwargs["enable_webtransport_h3"],
457457
mojojs_path,
458458
inject_script,
459-
kwargs["suppress_handler_traceback"]) as test_environment:
459+
kwargs["suppress_handler_traceback"],
460+
kwargs["ws_extra"]) as test_environment:
460461
recording.set(["startup", "ensure_environment"])
461462
try:
462463
test_environment.ensure_started()

testing/web-platform/wptrunner.ini

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ sync_path = sync
66
[paths]
77
prefs = ../profiles
88
run_info = .
9+
ws_extra = ./mozilla/tests/websockets/handlers
910

1011
[manifest:upstream]
1112
tests = tests

0 commit comments

Comments
 (0)