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"""
114 from wptrunner
import wptcommandline
116 kwargs
= self
.kwargs_common(kwargs
)
118 if kwargs
["binary"] is None:
119 kwargs
["binary"] = self
.get_binary_path()
121 if kwargs
["certutil_binary"] is None:
122 kwargs
["certutil_binary"] = self
.get_binary_path("certutil")
124 if kwargs
["webdriver_binary"] is None:
125 try_paths
= [self
.get_binary_path("geckodriver", validate_exists
=False)]
126 ext
= ".exe" if sys
.platform
in ["win32", "msys", "cygwin"] else ""
127 for build_type
in ["release", "debug"]:
130 self
.topsrcdir
, "target", build_type
, f
"geckodriver{ext}"
134 for path
in try_paths
:
135 if os
.path
.exists(path
):
136 found_paths
.append(path
)
139 # Pick the most recently modified version
140 found_paths
.sort(key
=os
.path
.getmtime
)
141 kwargs
["webdriver_binary"] = found_paths
[-1]
143 if kwargs
["install_fonts"] is None:
144 kwargs
["install_fonts"] = True
146 if kwargs
["preload_browser"] is None:
147 kwargs
["preload_browser"] = False
149 if kwargs
["prefs_root"] is None:
150 kwargs
["prefs_root"] = os
.path
.join(self
.topsrcdir
, "testing", "profiles")
152 if kwargs
["stackfix_dir"] is None:
153 kwargs
["stackfix_dir"] = self
.bindir
155 kwargs
= wptcommandline
.check_args(kwargs
)
159 def kwargs_wptrun(self
, kwargs
):
160 """Setup kwargs for wpt-run which is only used for non-gecko browser products"""
161 from tools
.wpt
import run
, virtualenv
163 kwargs
= self
.kwargs_common(kwargs
)
165 # Our existing kwargs corresponds to the wptrunner command line arguments.
166 # `wpt run` extends this with some additional arguments that are consumed by
167 # the frontend. Copy over the default values of these extra arguments so they
168 # are present when we call into that frontend.
169 run_parser
= run
.create_parser()
170 run_kwargs
= run_parser
.parse_args([kwargs
["product"], kwargs
["test_list"]])
172 for key
, value
in vars(run_kwargs
).items():
173 if key
not in kwargs
:
177 # We do this explicitly to avoid calling pip with options that aren't
178 # supported in the in-tree version
179 wptrunner_path
= os
.path
.join(self
._here
, "tests", "tools", "wptrunner")
180 browser_cls
= run
.product_setup
[kwargs
["product"]].browser_cls
181 requirements
= ["requirements.txt"]
183 hasattr(browser_cls
, "requirements")
184 and browser_cls
.requirements
is not None
186 requirements
.append(browser_cls
.requirements
)
188 for filename
in requirements
:
189 path
= os
.path
.join(wptrunner_path
, filename
)
190 if os
.path
.exists(path
):
191 self
.virtualenv_manager
.install_pip_requirements(
192 path
, require_hashes
=False
195 venv
= virtualenv
.Virtualenv(
196 self
.virtualenv_manager
.virtualenv_root
, skip_virtualenv_setup
=True
199 browser_cls
, kwargs
= run
.setup_wptrunner(venv
, **kwargs
)
200 except run
.WptrunError
as e
:
201 print(e
, file=sys
.stderr
)
204 # This is kind of a hack; override the metadata paths so we don't use
205 # gecko metadata for non-gecko products
206 for url_base
, test_root
in kwargs
["test_paths"].items():
207 meta_suffix
= url_base
.strip("/")
208 meta_dir
= os
.path
.join(
209 self
._here
, "products", kwargs
["product"].name
, meta_suffix
211 test_root
.metadata_path
= meta_dir
212 if not os
.path
.exists(meta_dir
):
213 os
.makedirs(meta_dir
)
216 def setup_fonts_firefox(self
):
217 # Ensure the Ahem font is available
218 if not sys
.platform
.startswith("darwin"):
219 font_path
= os
.path
.join(os
.path
.dirname(self
.get_binary_path()), "fonts")
221 font_path
= os
.path
.join(
222 os
.path
.dirname(self
.get_binary_path()),
228 ahem_src
= os
.path
.join(
229 self
.topsrcdir
, "testing", "web-platform", "tests", "fonts", "Ahem.ttf"
231 ahem_dest
= os
.path
.join(font_path
, "Ahem.ttf")
232 if not os
.path
.exists(ahem_dest
) and os
.path
.exists(ahem_src
):
233 with
open(ahem_src
, "rb") as src
, open(ahem_dest
, "wb") as dest
:
234 dest
.write(src
.read())
237 class WebPlatformTestsServeRunner(MozbuildObject
):
238 def run(self
, **kwargs
):
239 sys
.path
.insert(0, os
.path
.join(here
, "tests"))
240 sys
.path
.insert(0, os
.path
.join(here
, "tests", "tools"))
243 import manifestupdate
244 from serve
import serve
245 from wptrunner
import wptcommandline
247 logger
= logging
.getLogger("web-platform-tests")
249 src_root
= self
.topsrcdir
250 obj_root
= self
.topobjdir
251 src_wpt_dir
= os
.path
.join(src_root
, "testing", "web-platform")
253 config_path
= manifestupdate
.generate_config(
257 os
.path
.join(obj_root
, "_tests", "web-platform"),
261 test_paths
= wptcommandline
.get_test_paths(
262 wptcommandline
.config
.read(config_path
)
265 def get_route_builder(*args
, **kwargs
):
266 route_builder
= serve
.get_route_builder(*args
, **kwargs
)
268 for url_base
, paths
in test_paths
.items():
270 route_builder
.add_mount_point(url_base
, paths
.tests_path
)
274 return 0 if serve
.run(route_builder
=get_route_builder
, **kwargs
) else 1
277 class WebPlatformTestsUpdater(MozbuildObject
):
278 """Update web platform tests."""
280 def setup_logging(self
, **kwargs
):
283 return update
.setup_logging(kwargs
, {"mach": sys
.stdout
})
285 def update_manifest(self
, logger
, **kwargs
):
286 import manifestupdate
288 return manifestupdate
.run(
289 logger
=logger
, src_root
=self
.topsrcdir
, obj_root
=self
.topobjdir
, **kwargs
292 def run_update(self
, logger
, **kwargs
):
294 from update
import updatecommandline
296 self
.update_manifest(logger
, **kwargs
)
298 if kwargs
["config"] is None:
299 kwargs
["config"] = os
.path
.join(
300 self
.topobjdir
, "_tests", "web-platform", "wptrunner.local.ini"
302 if kwargs
["product"] is None:
303 kwargs
["product"] = "firefox"
305 kwargs
["store_state"] = False
307 kwargs
= updatecommandline
.check_args(kwargs
)
310 update
.run_update(logger
, **kwargs
)
314 traceback
.print_exc()
317 class WebPlatformTestsUnittestRunner(MozbuildObject
):
318 def run(self
, **kwargs
):
319 import unittestrunner
321 return unittestrunner
.run(self
.topsrcdir
, **kwargs
)
324 class WebPlatformTestsTestPathsRunner(MozbuildObject
):
325 """Update web platform tests."""
327 def run(self
, **kwargs
):
330 os
.path
.abspath(os
.path
.join(os
.path
.dirname(__file__
), "tests", "tools")),
334 import manifestupdate
335 from manifest
import testpaths
336 from wptrunner
import wptcommandline
338 logger
= logging
.getLogger("web-platform-tests")
340 src_root
= self
.topsrcdir
341 obj_root
= self
.topobjdir
342 src_wpt_dir
= os
.path
.join(src_root
, "testing", "web-platform")
344 config_path
= manifestupdate
.generate_config(
348 os
.path
.join(obj_root
, "_tests", "web-platform"),
352 test_paths
= wptcommandline
.get_test_paths(
353 wptcommandline
.config
.read(config_path
)
356 for url_base
, paths
in test_paths
.items():
357 if "manifest_path" not in paths
:
358 paths
["manifest_path"] = os
.path
.join(
359 paths
["metadata_path"], "MANIFEST.json"
363 path
=paths
["manifest_path"],
365 tests_root
=paths
["tests_path"],
366 update
=kwargs
["update"],
367 rebuild
=kwargs
["rebuild"],
369 cache_root
=kwargs
["cache_root"],
370 test_ids
=kwargs
["test_ids"],
373 testpaths
.write_output(results
, kwargs
["json"])
377 class WebPlatformTestsFissionRegressionsRunner(MozbuildObject
):
378 def run(self
, **kwargs
):
379 import fissionregressions
382 src_root
= self
.topsrcdir
383 obj_root
= self
.topobjdir
384 logger
= mozlog
.structuredlog
.StructuredLogger("web-platform-tests")
387 return fissionregressions
.run(logger
, src_root
, obj_root
, **kwargs
)
392 traceback
.print_exc()
396 def create_parser_update():
397 from update
import updatecommandline
399 return updatecommandline
.create_parser()
402 def create_parser_manifest_update():
403 import manifestupdate
405 return manifestupdate
.create_parser()
408 def create_parser_metadata_summary():
411 return metasummary
.create_parser()
414 def create_parser_metadata_merge():
417 return metamerge
.get_parser()
420 def create_parser_serve():
422 0, os
.path
.abspath(os
.path
.join(os
.path
.dirname(__file__
), "tests", "tools"))
426 return serve
.serve
.get_parser()
429 def create_parser_unittest():
430 import unittestrunner
432 return unittestrunner
.get_parser()
435 def create_parser_fission_regressions():
436 import fissionregressions
438 return fissionregressions
.get_parser()
441 def create_parser_fetch_logs():
444 return interop
.get_parser_fetch_logs()
447 def create_parser_interop_score():
450 return interop
.get_parser_interop_score()
453 def create_parser_testpaths():
456 from mach
.util
import get_state_dir
458 parser
= argparse
.ArgumentParser()
462 action
="store_false",
464 help="Don't update manifest before continuing",
471 help="Force a full rebuild of the manifest.",
476 default
=os
.path
.join(get_state_dir(), "cache", "wpt"),
477 help="Path in which to store any caches (default <tests_root>/.wptcache/)",
480 "test_ids", action
="store", nargs
="+", help="Test ids for which to get paths"
483 "--json", action
="store_true", default
=False, help="Output as JSON"
489 "web-platform-tests",
491 conditions
=[conditions
.is_firefox_or_android
],
492 description
="Run web-platform-tests.",
493 parser
=create_parser_wpt
,
494 virtualenv_name
="wpt",
496 def run_web_platform_tests(command_context
, **params
):
497 if params
["product"] is None:
498 if conditions
.is_android(command_context
):
499 params
["product"] = "firefox_android"
501 params
["product"] = "firefox"
502 if "test_objects" in params
:
505 for item
in params
["test_objects"]:
506 include
.append(item
["name"])
507 test_types
.add(item
.get("subsuite"))
508 if None not in test_types
:
509 params
["test_types"] = list(test_types
)
510 params
["include"] = include
511 del params
["test_objects"]
512 # subsuite coming from `mach test` means something more like `test type`, so remove that argument
513 if "subsuite" in params
:
514 del params
["subsuite"]
515 if params
.get("debugger", None):
518 if not mozdebug
.get_debugger_info(params
.get("debugger")):
521 wpt_setup
= command_context
._spawn
(WebPlatformTestsRunnerSetup
)
522 wpt_setup
._mach
_context
= command_context
._mach
_context
523 wpt_setup
._virtualenv
_name
= command_context
._virtualenv
_name
524 wpt_runner
= WebPlatformTestsRunner(wpt_setup
)
526 logger
= wpt_runner
.setup_logging(**params
)
527 # wptrunner already handles setting any log parameter from
528 # mach test to the logger, so it's OK to remove that argument now
533 conditions
.is_android(command_context
)
534 and params
["product"] != "firefox_android"
536 logger
.warning("Must specify --product=firefox_android in Android environment.")
538 return wpt_runner
.run(logger
, **params
)
544 conditions
=[conditions
.is_firefox_or_android
],
545 description
="Run web-platform-tests.",
546 parser
=create_parser_wpt
,
547 virtualenv_name
="wpt",
549 def run_wpt(command_context
, **params
):
550 return run_web_platform_tests(command_context
, **params
)
554 "web-platform-tests-update",
556 description
="Update web-platform-test metadata.",
557 parser
=create_parser_update
,
558 virtualenv_name
="wpt",
560 def update_web_platform_tests(command_context
, **params
):
561 wpt_updater
= command_context
._spawn
(WebPlatformTestsUpdater
)
562 logger
= wpt_updater
.setup_logging(**params
)
563 return wpt_updater
.run_update(logger
, **params
)
569 description
="Update web-platform-test metadata.",
570 parser
=create_parser_update
,
571 virtualenv_name
="wpt",
573 def update_wpt(command_context
, **params
):
574 return update_web_platform_tests(command_context
, **params
)
578 "wpt-manifest-update",
580 description
="Update web-platform-test manifests.",
581 parser
=create_parser_manifest_update
,
582 virtualenv_name
="wpt",
584 def wpt_manifest_update(command_context
, **params
):
585 wpt_setup
= command_context
._spawn
(WebPlatformTestsRunnerSetup
)
586 wpt_runner
= WebPlatformTestsRunner(wpt_setup
)
587 logger
= wpt_runner
.setup_logging(**params
)
589 "The wpt manifest is now automatically updated, "
590 "so running this command is usually unnecessary"
592 return 0 if wpt_runner
.update_manifest(logger
, **params
) else 1
598 description
="Run the wpt server",
599 parser
=create_parser_serve
,
600 virtualenv_name
="wpt",
602 def wpt_serve(command_context
, **params
):
605 logger
= logging
.getLogger("web-platform-tests")
606 logger
.addHandler(logging
.StreamHandler(sys
.stdout
))
607 wpt_serve
= command_context
._spawn
(WebPlatformTestsServeRunner
)
608 return wpt_serve
.run(**params
)
612 "wpt-metadata-summary",
614 description
="Create a json summary of the wpt metadata",
615 parser
=create_parser_metadata_summary
,
616 virtualenv_name
="wpt",
618 def wpt_summary(command_context
, **params
):
621 wpt_setup
= command_context
._spawn
(WebPlatformTestsRunnerSetup
)
622 return metasummary
.run(wpt_setup
.topsrcdir
, wpt_setup
.topobjdir
, **params
)
626 "wpt-metadata-merge",
628 parser
=create_parser_metadata_merge
,
629 virtualenv_name
="wpt",
631 def wpt_meta_merge(command_context
, **params
):
634 if params
["dest"] is None:
635 params
["dest"] = params
["current"]
636 return metamerge
.run(**params
)
642 description
="Run the wpt tools and wptrunner unit tests",
643 parser
=create_parser_unittest
,
644 virtualenv_name
="wpt",
646 def wpt_unittest(command_context
, **params
):
647 runner
= command_context
._spawn
(WebPlatformTestsUnittestRunner
)
648 return 0 if runner
.run(**params
) else 1
654 description
="Get a mapping from test ids to files",
655 parser
=create_parser_testpaths
,
656 virtualenv_name
="wpt",
658 def wpt_test_paths(command_context
, **params
):
659 runner
= command_context
._spawn
(WebPlatformTestsTestPathsRunner
)
665 "wpt-fission-regressions",
667 description
="Dump a list of fission-specific regressions",
668 parser
=create_parser_fission_regressions
,
669 virtualenv_name
="wpt",
671 def wpt_fission_regressions(command_context
, **params
):
672 runner
= command_context
._spawn
(WebPlatformTestsFissionRegressionsRunner
)
680 description
="Fetch wptreport.json logs from taskcluster",
681 parser
=create_parser_fetch_logs
,
682 virtualenv_name
="wpt-interop",
684 def wpt_fetch_logs(command_context
, **params
):
687 interop
.fetch_logs(**params
)
694 description
="Score a run according to Interop 2023",
695 parser
=create_parser_interop_score
,
696 virtualenv_name
="wpt-interop",
698 def wpt_interop_score(command_context
, **params
):
701 interop
.score_runs(**params
)