1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 # Integrates the web-platform-tests test runner with mach.
10 from mach
.decorators
import Command
11 from mach_commands_base
import WebPlatformTestsRunner
, create_parser_wpt
12 from mozbuild
.base
import MachCommandConditions
as conditions
13 from mozbuild
.base
import MozbuildObject
15 here
= os
.path
.abspath(os
.path
.dirname(__file__
))
16 INTEROP_REQUIREMENTS_PATH
= os
.path
.join(here
, "interop_requirements.txt")
19 class WebPlatformTestsRunnerSetup(MozbuildObject
):
20 default_log_type
= "mach"
22 def __init__(self
, *args
, **kwargs
):
23 super(WebPlatformTestsRunnerSetup
, self
).__init
__(*args
, **kwargs
)
24 self
._here
= os
.path
.join(self
.topsrcdir
, "testing", "web-platform")
25 kwargs
["tests_root"] = os
.path
.join(self
._here
, "tests")
26 sys
.path
.insert(0, kwargs
["tests_root"])
27 build_path
= os
.path
.join(self
.topobjdir
, "build")
28 if build_path
not in sys
.path
:
29 sys
.path
.append(build_path
)
31 def kwargs_common(self
, kwargs
):
32 """Setup kwargs relevant for all browser products"""
34 tests_src_path
= os
.path
.join(self
._here
, "tests")
37 kwargs
["product"] in {"firefox", "firefox_android"}
38 and kwargs
["specialpowers_path"] is None
40 kwargs
["specialpowers_path"] = os
.path
.join(
41 self
.distdir
, "xpi-stage", "specialpowers@mozilla.org.xpi"
44 if kwargs
["product"] == "firefox_android":
45 # package_name may be different in the future
46 package_name
= kwargs
["package_name"]
50 ] = package_name
= "org.mozilla.geckoview.test_runner"
52 # Note that this import may fail in non-firefox-for-android trees
53 from mozrunner
.devices
.android_device
import (
56 verify_android_device
,
59 kwargs
["adb_binary"] = get_adb_path(self
)
61 InstallIntent
.NO
if kwargs
.pop("no_install") else InstallIntent
.YES
63 verify_android_device(
64 self
, install
=install
, verbose
=False, xre
=True, app
=package_name
67 if kwargs
["certutil_binary"] is None:
68 kwargs
["certutil_binary"] = os
.path
.join(
69 os
.environ
.get("MOZ_HOST_BIN"), "certutil"
72 if kwargs
["install_fonts"] is None:
73 kwargs
["install_fonts"] = True
75 if not kwargs
["device_serial"]:
76 kwargs
["device_serial"] = ["emulator-5554"]
78 if kwargs
["config"] is None:
79 kwargs
["config"] = os
.path
.join(
80 self
.topobjdir
, "_tests", "web-platform", "wptrunner.local.ini"
84 kwargs
["exclude"] is None
85 and kwargs
["include"] is None
86 and not sys
.platform
.startswith("linux")
88 kwargs
["exclude"] = ["css"]
90 if kwargs
["ssl_type"] in (None, "pregenerated"):
91 cert_root
= os
.path
.join(tests_src_path
, "tools", "certs")
92 if kwargs
["ca_cert_path"] is None:
93 kwargs
["ca_cert_path"] = os
.path
.join(cert_root
, "cacert.pem")
95 if kwargs
["host_key_path"] is None:
96 kwargs
["host_key_path"] = os
.path
.join(
97 cert_root
, "web-platform.test.key"
100 if kwargs
["host_cert_path"] is None:
101 kwargs
["host_cert_path"] = os
.path
.join(
102 cert_root
, "web-platform.test.pem"
105 if kwargs
["reftest_screenshot"] is None:
106 kwargs
["reftest_screenshot"] = "fail"
108 kwargs
["capture_stdio"] = True
112 def kwargs_firefox(self
, kwargs
):
113 """Setup kwargs specific to running Firefox and other gecko browsers"""
115 from wptrunner
import wptcommandline
117 kwargs
= self
.kwargs_common(kwargs
)
119 if kwargs
["binary"] is None:
120 kwargs
["binary"] = self
.get_binary_path()
122 if kwargs
["certutil_binary"] is None:
123 kwargs
["certutil_binary"] = self
.get_binary_path("certutil")
125 if kwargs
["webdriver_binary"] is None:
126 try_paths
= [self
.get_binary_path("geckodriver", validate_exists
=False)]
127 ext
= ".exe" if sys
.platform
in ["win32", "msys", "cygwin"] else ""
128 for build_type
in ["release", "debug"]:
131 self
.topsrcdir
, "target", build_type
, f
"geckodriver{ext}"
135 for path
in try_paths
:
136 if os
.path
.exists(path
):
137 found_paths
.append(path
)
140 # Pick the most recently modified version
141 found_paths
.sort(key
=os
.path
.getmtime
)
142 kwargs
["webdriver_binary"] = found_paths
[-1]
144 if kwargs
["install_fonts"] is None:
145 kwargs
["install_fonts"] = True
148 kwargs
["install_fonts"]
149 and mozinfo
.info
["os"] == "win"
150 and mozinfo
.info
["os_version"] == "6.1"
152 # On Windows 7 --install-fonts fails, so fall back to a Firefox-specific codepath
153 self
.setup_fonts_firefox()
154 kwargs
["install_fonts"] = False
156 if kwargs
["preload_browser"] is None:
157 kwargs
["preload_browser"] = False
159 if kwargs
["prefs_root"] is None:
160 kwargs
["prefs_root"] = os
.path
.join(self
.topsrcdir
, "testing", "profiles")
162 if kwargs
["stackfix_dir"] is None:
163 kwargs
["stackfix_dir"] = self
.bindir
165 kwargs
= wptcommandline
.check_args(kwargs
)
169 def kwargs_wptrun(self
, kwargs
):
170 """Setup kwargs for wpt-run which is only used for non-gecko browser products"""
171 from tools
.wpt
import run
, virtualenv
173 kwargs
= self
.kwargs_common(kwargs
)
175 # Our existing kwargs corresponds to the wptrunner command line arguments.
176 # `wpt run` extends this with some additional arguments that are consumed by
177 # the frontend. Copy over the default values of these extra arguments so they
178 # are present when we call into that frontend.
179 run_parser
= run
.create_parser()
180 run_kwargs
= run_parser
.parse_args([kwargs
["product"], kwargs
["test_list"]])
182 for key
, value
in vars(run_kwargs
).items():
183 if key
not in kwargs
:
187 # We do this explicitly to avoid calling pip with options that aren't
188 # supported in the in-tree version
189 wptrunner_path
= os
.path
.join(self
._here
, "tests", "tools", "wptrunner")
190 browser_cls
= run
.product_setup
[kwargs
["product"]].browser_cls
191 requirements
= ["requirements.txt"]
192 if hasattr(browser_cls
, "requirements"):
193 requirements
.append(browser_cls
.requirements
)
195 for filename
in requirements
:
196 path
= os
.path
.join(wptrunner_path
, filename
)
197 if os
.path
.exists(path
):
198 self
.virtualenv_manager
.install_pip_requirements(
199 path
, require_hashes
=False
202 venv
= virtualenv
.Virtualenv(
203 self
.virtualenv_manager
.virtualenv_root
, skip_virtualenv_setup
=True
206 browser_cls
, kwargs
= run
.setup_wptrunner(venv
, **kwargs
)
207 except run
.WptrunError
as e
:
208 print(e
, file=sys
.stderr
)
211 # This is kind of a hack; override the metadata paths so we don't use
212 # gecko metadata for non-gecko products
213 for url_base
, test_root
in kwargs
["test_paths"].items():
214 meta_suffix
= url_base
.strip("/")
215 meta_dir
= os
.path
.join(
216 self
._here
, "products", kwargs
["product"].name
, meta_suffix
218 test_root
.metadata_path
= meta_dir
219 if not os
.path
.exists(meta_dir
):
220 os
.makedirs(meta_dir
)
223 def setup_fonts_firefox(self
):
224 # Ensure the Ahem font is available
225 if not sys
.platform
.startswith("darwin"):
226 font_path
= os
.path
.join(os
.path
.dirname(self
.get_binary_path()), "fonts")
228 font_path
= os
.path
.join(
229 os
.path
.dirname(self
.get_binary_path()),
235 ahem_src
= os
.path
.join(
236 self
.topsrcdir
, "testing", "web-platform", "tests", "fonts", "Ahem.ttf"
238 ahem_dest
= os
.path
.join(font_path
, "Ahem.ttf")
239 if not os
.path
.exists(ahem_dest
) and os
.path
.exists(ahem_src
):
240 with
open(ahem_src
, "rb") as src
, open(ahem_dest
, "wb") as dest
:
241 dest
.write(src
.read())
244 class WebPlatformTestsServeRunner(MozbuildObject
):
245 def run(self
, **kwargs
):
246 sys
.path
.insert(0, os
.path
.join(here
, "tests"))
247 sys
.path
.insert(0, os
.path
.join(here
, "tests", "tools"))
250 import manifestupdate
251 from serve
import serve
252 from wptrunner
import wptcommandline
254 logger
= logging
.getLogger("web-platform-tests")
256 src_root
= self
.topsrcdir
257 obj_root
= self
.topobjdir
258 src_wpt_dir
= os
.path
.join(src_root
, "testing", "web-platform")
260 config_path
= manifestupdate
.generate_config(
264 os
.path
.join(obj_root
, "_tests", "web-platform"),
268 test_paths
= wptcommandline
.get_test_paths(
269 wptcommandline
.config
.read(config_path
)
272 def get_route_builder(*args
, **kwargs
):
273 route_builder
= serve
.get_route_builder(*args
, **kwargs
)
275 for url_base
, paths
in test_paths
.items():
277 route_builder
.add_mount_point(url_base
, paths
.tests_path
)
281 return 0 if serve
.run(route_builder
=get_route_builder
, **kwargs
) else 1
284 class WebPlatformTestsUpdater(MozbuildObject
):
285 """Update web platform tests."""
287 def setup_logging(self
, **kwargs
):
290 return update
.setup_logging(kwargs
, {"mach": sys
.stdout
})
292 def update_manifest(self
, logger
, **kwargs
):
293 import manifestupdate
295 return manifestupdate
.run(
296 logger
=logger
, src_root
=self
.topsrcdir
, obj_root
=self
.topobjdir
, **kwargs
299 def run_update(self
, logger
, **kwargs
):
301 from update
import updatecommandline
303 self
.update_manifest(logger
, **kwargs
)
305 if kwargs
["config"] is None:
306 kwargs
["config"] = os
.path
.join(
307 self
.topobjdir
, "_tests", "web-platform", "wptrunner.local.ini"
309 if kwargs
["product"] is None:
310 kwargs
["product"] = "firefox"
312 kwargs
["store_state"] = False
314 kwargs
= updatecommandline
.check_args(kwargs
)
317 update
.run_update(logger
, **kwargs
)
321 traceback
.print_exc()
324 class WebPlatformTestsUnittestRunner(MozbuildObject
):
325 def run(self
, **kwargs
):
326 import unittestrunner
328 return unittestrunner
.run(self
.topsrcdir
, **kwargs
)
331 class WebPlatformTestsTestPathsRunner(MozbuildObject
):
332 """Update web platform tests."""
334 def run(self
, **kwargs
):
337 os
.path
.abspath(os
.path
.join(os
.path
.dirname(__file__
), "tests", "tools")),
341 import manifestupdate
342 from manifest
import testpaths
343 from wptrunner
import wptcommandline
345 logger
= logging
.getLogger("web-platform-tests")
347 src_root
= self
.topsrcdir
348 obj_root
= self
.topobjdir
349 src_wpt_dir
= os
.path
.join(src_root
, "testing", "web-platform")
351 config_path
= manifestupdate
.generate_config(
355 os
.path
.join(obj_root
, "_tests", "web-platform"),
359 test_paths
= wptcommandline
.get_test_paths(
360 wptcommandline
.config
.read(config_path
)
363 for url_base
, paths
in test_paths
.items():
364 if "manifest_path" not in paths
:
365 paths
["manifest_path"] = os
.path
.join(
366 paths
["metadata_path"], "MANIFEST.json"
370 path
=paths
["manifest_path"],
372 tests_root
=paths
["tests_path"],
373 update
=kwargs
["update"],
374 rebuild
=kwargs
["rebuild"],
376 cache_root
=kwargs
["cache_root"],
377 test_ids
=kwargs
["test_ids"],
380 testpaths
.write_output(results
, kwargs
["json"])
384 class WebPlatformTestsFissionRegressionsRunner(MozbuildObject
):
385 def run(self
, **kwargs
):
386 import fissionregressions
389 src_root
= self
.topsrcdir
390 obj_root
= self
.topobjdir
391 logger
= mozlog
.structuredlog
.StructuredLogger("web-platform-tests")
394 return fissionregressions
.run(logger
, src_root
, obj_root
, **kwargs
)
399 traceback
.print_exc()
403 def create_parser_update():
404 from update
import updatecommandline
406 return updatecommandline
.create_parser()
409 def create_parser_manifest_update():
410 import manifestupdate
412 return manifestupdate
.create_parser()
415 def create_parser_metadata_summary():
418 return metasummary
.create_parser()
421 def create_parser_metadata_merge():
424 return metamerge
.get_parser()
427 def create_parser_serve():
429 0, os
.path
.abspath(os
.path
.join(os
.path
.dirname(__file__
), "tests", "tools"))
433 return serve
.serve
.get_parser()
436 def create_parser_unittest():
437 import unittestrunner
439 return unittestrunner
.get_parser()
442 def create_parser_fission_regressions():
443 import fissionregressions
445 return fissionregressions
.get_parser()
448 def create_parser_fetch_logs():
451 return interop
.get_parser_fetch_logs()
454 def create_parser_interop_score():
457 return interop
.get_parser_interop_score()
460 def create_parser_testpaths():
463 from mach
.util
import get_state_dir
465 parser
= argparse
.ArgumentParser()
469 action
="store_false",
471 help="Don't update manifest before continuing",
478 help="Force a full rebuild of the manifest.",
483 default
=os
.path
.join(get_state_dir(), "cache", "wpt"),
484 help="Path in which to store any caches (default <tests_root>/.wptcache/)",
487 "test_ids", action
="store", nargs
="+", help="Test ids for which to get paths"
490 "--json", action
="store_true", default
=False, help="Output as JSON"
496 "web-platform-tests",
498 conditions
=[conditions
.is_firefox_or_android
],
499 description
="Run web-platform-tests.",
500 parser
=create_parser_wpt
,
501 virtualenv_name
="wpt",
503 def run_web_platform_tests(command_context
, **params
):
504 if params
["product"] is None:
505 if conditions
.is_android(command_context
):
506 params
["product"] = "firefox_android"
508 params
["product"] = "firefox"
509 if "test_objects" in params
:
512 for item
in params
["test_objects"]:
513 include
.append(item
["name"])
514 test_types
.add(item
.get("subsuite"))
515 if None not in test_types
:
516 params
["test_types"] = list(test_types
)
517 params
["include"] = include
518 del params
["test_objects"]
519 # subsuite coming from `mach test` means something more like `test type`, so remove that argument
520 if "subsuite" in params
:
521 del params
["subsuite"]
522 if params
.get("debugger", None):
525 if not mozdebug
.get_debugger_info(params
.get("debugger")):
528 wpt_setup
= command_context
._spawn
(WebPlatformTestsRunnerSetup
)
529 wpt_setup
._mach
_context
= command_context
._mach
_context
530 wpt_setup
._virtualenv
_name
= command_context
._virtualenv
_name
531 wpt_runner
= WebPlatformTestsRunner(wpt_setup
)
533 logger
= wpt_runner
.setup_logging(**params
)
534 # wptrunner already handles setting any log parameter from
535 # mach test to the logger, so it's OK to remove that argument now
540 conditions
.is_android(command_context
)
541 and params
["product"] != "firefox_android"
543 logger
.warning("Must specify --product=firefox_android in Android environment.")
545 return wpt_runner
.run(logger
, **params
)
551 conditions
=[conditions
.is_firefox_or_android
],
552 description
="Run web-platform-tests.",
553 parser
=create_parser_wpt
,
554 virtualenv_name
="wpt",
556 def run_wpt(command_context
, **params
):
557 return run_web_platform_tests(command_context
, **params
)
561 "web-platform-tests-update",
563 description
="Update web-platform-test metadata.",
564 parser
=create_parser_update
,
565 virtualenv_name
="wpt",
567 def update_web_platform_tests(command_context
, **params
):
568 wpt_updater
= command_context
._spawn
(WebPlatformTestsUpdater
)
569 logger
= wpt_updater
.setup_logging(**params
)
570 return wpt_updater
.run_update(logger
, **params
)
576 description
="Update web-platform-test metadata.",
577 parser
=create_parser_update
,
578 virtualenv_name
="wpt",
580 def update_wpt(command_context
, **params
):
581 return update_web_platform_tests(command_context
, **params
)
585 "wpt-manifest-update",
587 description
="Update web-platform-test manifests.",
588 parser
=create_parser_manifest_update
,
589 virtualenv_name
="wpt",
591 def wpt_manifest_update(command_context
, **params
):
592 wpt_setup
= command_context
._spawn
(WebPlatformTestsRunnerSetup
)
593 wpt_runner
= WebPlatformTestsRunner(wpt_setup
)
594 logger
= wpt_runner
.setup_logging(**params
)
596 "The wpt manifest is now automatically updated, "
597 "so running this command is usually unnecessary"
599 return 0 if wpt_runner
.update_manifest(logger
, **params
) else 1
605 description
="Run the wpt server",
606 parser
=create_parser_serve
,
607 virtualenv_name
="wpt",
609 def wpt_serve(command_context
, **params
):
612 logger
= logging
.getLogger("web-platform-tests")
613 logger
.addHandler(logging
.StreamHandler(sys
.stdout
))
614 wpt_serve
= command_context
._spawn
(WebPlatformTestsServeRunner
)
615 return wpt_serve
.run(**params
)
619 "wpt-metadata-summary",
621 description
="Create a json summary of the wpt metadata",
622 parser
=create_parser_metadata_summary
,
623 virtualenv_name
="wpt",
625 def wpt_summary(command_context
, **params
):
628 wpt_setup
= command_context
._spawn
(WebPlatformTestsRunnerSetup
)
629 return metasummary
.run(wpt_setup
.topsrcdir
, wpt_setup
.topobjdir
, **params
)
633 "wpt-metadata-merge",
635 parser
=create_parser_metadata_merge
,
636 virtualenv_name
="wpt",
638 def wpt_meta_merge(command_context
, **params
):
641 if params
["dest"] is None:
642 params
["dest"] = params
["current"]
643 return metamerge
.run(**params
)
649 description
="Run the wpt tools and wptrunner unit tests",
650 parser
=create_parser_unittest
,
651 virtualenv_name
="wpt",
653 def wpt_unittest(command_context
, **params
):
654 runner
= command_context
._spawn
(WebPlatformTestsUnittestRunner
)
655 return 0 if runner
.run(**params
) else 1
661 description
="Get a mapping from test ids to files",
662 parser
=create_parser_testpaths
,
663 virtualenv_name
="wpt",
665 def wpt_test_paths(command_context
, **params
):
666 runner
= command_context
._spawn
(WebPlatformTestsTestPathsRunner
)
672 "wpt-fission-regressions",
674 description
="Dump a list of fission-specific regressions",
675 parser
=create_parser_fission_regressions
,
676 virtualenv_name
="wpt",
678 def wpt_fission_regressions(command_context
, **params
):
679 runner
= command_context
._spawn
(WebPlatformTestsFissionRegressionsRunner
)
687 description
="Fetch wptreport.json logs from taskcluster",
688 parser
=create_parser_fetch_logs
,
689 virtualenv_name
="wpt-interop",
691 def wpt_fetch_logs(command_context
, **params
):
694 interop
.fetch_logs(**params
)
701 description
="Score a run according to Interop 2023",
702 parser
=create_parser_interop_score
,
703 virtualenv_name
="wpt-interop",
705 def wpt_interop_score(command_context
, **params
):
708 interop
.score_runs(**params
)