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
14 from six
import iteritems
16 here
= os
.path
.abspath(os
.path
.dirname(__file__
))
17 INTEROP_REQUIREMENTS_PATH
= os
.path
.join(here
, "interop_requirements.txt")
20 class WebPlatformTestsRunnerSetup(MozbuildObject
):
21 default_log_type
= "mach"
23 def __init__(self
, *args
, **kwargs
):
24 super(WebPlatformTestsRunnerSetup
, self
).__init
__(*args
, **kwargs
)
25 self
._here
= os
.path
.join(self
.topsrcdir
, "testing", "web-platform")
26 kwargs
["tests_root"] = os
.path
.join(self
._here
, "tests")
27 sys
.path
.insert(0, kwargs
["tests_root"])
28 build_path
= os
.path
.join(self
.topobjdir
, "build")
29 if build_path
not in sys
.path
:
30 sys
.path
.append(build_path
)
32 def kwargs_common(self
, kwargs
):
33 """Setup kwargs relevant for all browser products"""
35 tests_src_path
= os
.path
.join(self
._here
, "tests")
38 kwargs
["product"] in {"firefox", "firefox_android"}
39 and kwargs
["specialpowers_path"] is None
41 kwargs
["specialpowers_path"] = os
.path
.join(
42 self
.distdir
, "xpi-stage", "specialpowers@mozilla.org.xpi"
45 if kwargs
["product"] == "firefox_android":
46 # package_name may be different in the future
47 package_name
= kwargs
["package_name"]
51 ] = package_name
= "org.mozilla.geckoview.test_runner"
53 # Note that this import may fail in non-firefox-for-android trees
54 from mozrunner
.devices
.android_device
import (
57 verify_android_device
,
60 kwargs
["adb_binary"] = get_adb_path(self
)
62 InstallIntent
.NO
if kwargs
.pop("no_install") else InstallIntent
.YES
64 verify_android_device(
65 self
, install
=install
, verbose
=False, xre
=True, app
=package_name
68 if kwargs
["certutil_binary"] is None:
69 kwargs
["certutil_binary"] = os
.path
.join(
70 os
.environ
.get("MOZ_HOST_BIN"), "certutil"
73 if kwargs
["install_fonts"] is None:
74 kwargs
["install_fonts"] = True
76 if not kwargs
["device_serial"]:
77 kwargs
["device_serial"] = ["emulator-5554"]
79 if kwargs
["config"] is None:
80 kwargs
["config"] = os
.path
.join(
81 self
.topobjdir
, "_tests", "web-platform", "wptrunner.local.ini"
85 kwargs
["exclude"] is None
86 and kwargs
["include"] is None
87 and not sys
.platform
.startswith("linux")
89 kwargs
["exclude"] = ["css"]
91 if kwargs
["ssl_type"] in (None, "pregenerated"):
92 cert_root
= os
.path
.join(tests_src_path
, "tools", "certs")
93 if kwargs
["ca_cert_path"] is None:
94 kwargs
["ca_cert_path"] = os
.path
.join(cert_root
, "cacert.pem")
96 if kwargs
["host_key_path"] is None:
97 kwargs
["host_key_path"] = os
.path
.join(
98 cert_root
, "web-platform.test.key"
101 if kwargs
["host_cert_path"] is None:
102 kwargs
["host_cert_path"] = os
.path
.join(
103 cert_root
, "web-platform.test.pem"
106 if kwargs
["reftest_screenshot"] is None:
107 kwargs
["reftest_screenshot"] = "fail"
109 kwargs
["capture_stdio"] = True
113 def kwargs_firefox(self
, kwargs
):
114 """Setup kwargs specific to running Firefox and other gecko browsers"""
116 from wptrunner
import wptcommandline
118 kwargs
= self
.kwargs_common(kwargs
)
120 if kwargs
["binary"] is None:
121 kwargs
["binary"] = self
.get_binary_path()
123 if kwargs
["certutil_binary"] is None:
124 kwargs
["certutil_binary"] = self
.get_binary_path("certutil")
126 if kwargs
["webdriver_binary"] is None:
127 kwargs
["webdriver_binary"] = self
.get_binary_path(
128 "geckodriver", validate_exists
=False
131 if kwargs
["install_fonts"] is None:
132 kwargs
["install_fonts"] = True
135 kwargs
["install_fonts"]
136 and mozinfo
.info
["os"] == "win"
137 and mozinfo
.info
["os_version"] == "6.1"
139 # On Windows 7 --install-fonts fails, so fall back to a Firefox-specific codepath
140 self
.setup_fonts_firefox()
141 kwargs
["install_fonts"] = False
143 if kwargs
["preload_browser"] is None:
144 kwargs
["preload_browser"] = False
146 if kwargs
["prefs_root"] is None:
147 kwargs
["prefs_root"] = os
.path
.join(self
.topsrcdir
, "testing", "profiles")
149 if kwargs
["stackfix_dir"] is None:
150 kwargs
["stackfix_dir"] = self
.bindir
152 kwargs
= wptcommandline
.check_args(kwargs
)
156 def kwargs_wptrun(self
, kwargs
):
157 """Setup kwargs for wpt-run which is only used for non-gecko browser products"""
158 from tools
.wpt
import run
, virtualenv
160 kwargs
= self
.kwargs_common(kwargs
)
162 # Our existing kwargs corresponds to the wptrunner command line arguments.
163 # `wpt run` extends this with some additional arguments that are consumed by
164 # the frontend. Copy over the default values of these extra arguments so they
165 # are present when we call into that frontend.
166 run_parser
= run
.create_parser()
167 run_kwargs
= run_parser
.parse_args([kwargs
["product"], kwargs
["test_list"]])
169 for key
, value
in vars(run_kwargs
).items():
170 if key
not in kwargs
:
174 # We do this explicitly to avoid calling pip with options that aren't
175 # supported in the in-tree version
176 wptrunner_path
= os
.path
.join(self
._here
, "tests", "tools", "wptrunner")
177 browser_cls
= run
.product_setup
[kwargs
["product"]].browser_cls
178 requirements
= ["requirements.txt"]
179 if hasattr(browser_cls
, "requirements"):
180 requirements
.append(browser_cls
.requirements
)
182 for filename
in requirements
:
183 path
= os
.path
.join(wptrunner_path
, filename
)
184 if os
.path
.exists(path
):
185 self
.virtualenv_manager
.install_pip_requirements(
186 path
, require_hashes
=False
189 venv
= virtualenv
.Virtualenv(
190 self
.virtualenv_manager
.virtualenv_root
, skip_virtualenv_setup
=True
193 kwargs
= run
.setup_wptrunner(venv
, **kwargs
)
194 except run
.WptrunError
as e
:
195 print(e
, file=sys
.stderr
)
198 # This is kind of a hack; override the metadata paths so we don't use
199 # gecko metadata for non-gecko products
200 for key
, value
in list(iteritems(kwargs
["test_paths"])):
201 meta_suffix
= key
.strip("/")
202 meta_dir
= os
.path
.join(
203 self
._here
, "products", kwargs
["product"].name
, meta_suffix
205 value
["metadata_path"] = meta_dir
206 if not os
.path
.exists(meta_dir
):
207 os
.makedirs(meta_dir
)
210 def setup_fonts_firefox(self
):
211 # Ensure the Ahem font is available
212 if not sys
.platform
.startswith("darwin"):
213 font_path
= os
.path
.join(os
.path
.dirname(self
.get_binary_path()), "fonts")
215 font_path
= os
.path
.join(
216 os
.path
.dirname(self
.get_binary_path()),
222 ahem_src
= os
.path
.join(
223 self
.topsrcdir
, "testing", "web-platform", "tests", "fonts", "Ahem.ttf"
225 ahem_dest
= os
.path
.join(font_path
, "Ahem.ttf")
226 if not os
.path
.exists(ahem_dest
) and os
.path
.exists(ahem_src
):
227 with
open(ahem_src
, "rb") as src
, open(ahem_dest
, "wb") as dest
:
228 dest
.write(src
.read())
231 class WebPlatformTestsServeRunner(MozbuildObject
):
232 def run(self
, **kwargs
):
235 os
.path
.abspath(os
.path
.join(os
.path
.dirname(__file__
), "tests", "tools")),
239 import manifestupdate
240 from serve
import serve
241 from wptrunner
import wptcommandline
243 logger
= logging
.getLogger("web-platform-tests")
245 src_root
= self
.topsrcdir
246 obj_root
= self
.topobjdir
247 src_wpt_dir
= os
.path
.join(src_root
, "testing", "web-platform")
249 config_path
= manifestupdate
.generate_config(
253 os
.path
.join(obj_root
, "_tests", "web-platform"),
257 test_paths
= wptcommandline
.get_test_paths(
258 wptcommandline
.config
.read(config_path
)
261 def get_route_builder(*args
, **kwargs
):
262 route_builder
= serve
.get_route_builder(*args
, **kwargs
)
264 for url_base
, paths
in iteritems(test_paths
):
266 route_builder
.add_mount_point(url_base
, paths
["tests_path"])
270 return 0 if serve
.run(route_builder
=get_route_builder
, **kwargs
) else 1
273 class WebPlatformTestsUpdater(MozbuildObject
):
274 """Update web platform tests."""
276 def setup_logging(self
, **kwargs
):
279 return update
.setup_logging(kwargs
, {"mach": sys
.stdout
})
281 def update_manifest(self
, logger
, **kwargs
):
282 import manifestupdate
284 return manifestupdate
.run(
285 logger
=logger
, src_root
=self
.topsrcdir
, obj_root
=self
.topobjdir
, **kwargs
288 def run_update(self
, logger
, **kwargs
):
290 from update
import updatecommandline
292 self
.update_manifest(logger
, **kwargs
)
294 if kwargs
["config"] is None:
295 kwargs
["config"] = os
.path
.join(
296 self
.topobjdir
, "_tests", "web-platform", "wptrunner.local.ini"
298 if kwargs
["product"] is None:
299 kwargs
["product"] = "firefox"
301 kwargs
["store_state"] = False
303 kwargs
= updatecommandline
.check_args(kwargs
)
306 update
.run_update(logger
, **kwargs
)
310 traceback
.print_exc()
313 class WebPlatformTestsUnittestRunner(MozbuildObject
):
314 def run(self
, **kwargs
):
315 import unittestrunner
317 return unittestrunner
.run(self
.topsrcdir
, **kwargs
)
320 class WebPlatformTestsTestPathsRunner(MozbuildObject
):
321 """Update web platform tests."""
323 def run(self
, **kwargs
):
326 os
.path
.abspath(os
.path
.join(os
.path
.dirname(__file__
), "tests", "tools")),
330 import manifestupdate
331 from manifest
import testpaths
332 from wptrunner
import wptcommandline
334 logger
= logging
.getLogger("web-platform-tests")
336 src_root
= self
.topsrcdir
337 obj_root
= self
.topobjdir
338 src_wpt_dir
= os
.path
.join(src_root
, "testing", "web-platform")
340 config_path
= manifestupdate
.generate_config(
344 os
.path
.join(obj_root
, "_tests", "web-platform"),
348 test_paths
= wptcommandline
.get_test_paths(
349 wptcommandline
.config
.read(config_path
)
352 for url_base
, paths
in iteritems(test_paths
):
353 if "manifest_path" not in paths
:
354 paths
["manifest_path"] = os
.path
.join(
355 paths
["metadata_path"], "MANIFEST.json"
359 path
=paths
["manifest_path"],
361 tests_root
=paths
["tests_path"],
362 update
=kwargs
["update"],
363 rebuild
=kwargs
["rebuild"],
365 cache_root
=kwargs
["cache_root"],
366 test_ids
=kwargs
["test_ids"],
369 testpaths
.write_output(results
, kwargs
["json"])
373 class WebPlatformTestsFissionRegressionsRunner(MozbuildObject
):
374 def run(self
, **kwargs
):
375 import fissionregressions
378 src_root
= self
.topsrcdir
379 obj_root
= self
.topobjdir
380 logger
= mozlog
.structuredlog
.StructuredLogger("web-platform-tests")
383 return fissionregressions
.run(logger
, src_root
, obj_root
, **kwargs
)
388 traceback
.print_exc()
392 def create_parser_update():
393 from update
import updatecommandline
395 return updatecommandline
.create_parser()
398 def create_parser_manifest_update():
399 import manifestupdate
401 return manifestupdate
.create_parser()
404 def create_parser_metadata_summary():
407 return metasummary
.create_parser()
410 def create_parser_metadata_merge():
413 return metamerge
.get_parser()
416 def create_parser_serve():
418 0, os
.path
.abspath(os
.path
.join(os
.path
.dirname(__file__
), "tests", "tools"))
422 return serve
.serve
.get_parser()
425 def create_parser_unittest():
426 import unittestrunner
428 return unittestrunner
.get_parser()
431 def create_parser_fission_regressions():
432 import fissionregressions
434 return fissionregressions
.get_parser()
437 def create_parser_fetch_logs():
440 return interop
.get_parser_fetch_logs()
443 def create_parser_interop_score():
446 return interop
.get_parser_interop_score()
449 def create_parser_testpaths():
452 from mach
.util
import get_state_dir
454 parser
= argparse
.ArgumentParser()
458 action
="store_false",
460 help="Don't update manifest before continuing",
467 help="Force a full rebuild of the manifest.",
472 default
=os
.path
.join(get_state_dir(), "cache", "wpt"),
473 help="Path in which to store any caches (default <tests_root>/.wptcache/)",
476 "test_ids", action
="store", nargs
="+", help="Test ids for which to get paths"
479 "--json", action
="store_true", default
=False, help="Output as JSON"
485 "web-platform-tests",
487 conditions
=[conditions
.is_firefox_or_android
],
488 description
="Run web-platform-tests.",
489 parser
=create_parser_wpt
,
490 virtualenv_name
="wpt",
492 def run_web_platform_tests(command_context
, **params
):
493 if params
["product"] is None:
494 if conditions
.is_android(command_context
):
495 params
["product"] = "firefox_android"
497 params
["product"] = "firefox"
498 if "test_objects" in params
:
501 for item
in params
["test_objects"]:
502 include
.append(item
["name"])
503 test_types
.add(item
.get("subsuite"))
504 if None not in test_types
:
505 params
["test_types"] = list(test_types
)
506 params
["include"] = include
507 del params
["test_objects"]
508 # subsuite coming from `mach test` means something more like `test type`, so remove that argument
509 if "subsuite" in params
:
510 del params
["subsuite"]
511 if params
.get("debugger", None):
514 if not mozdebug
.get_debugger_info(params
.get("debugger")):
517 wpt_setup
= command_context
._spawn
(WebPlatformTestsRunnerSetup
)
518 wpt_setup
._mach
_context
= command_context
._mach
_context
519 wpt_setup
._virtualenv
_name
= command_context
._virtualenv
_name
520 wpt_runner
= WebPlatformTestsRunner(wpt_setup
)
522 logger
= wpt_runner
.setup_logging(**params
)
523 # wptrunner already handles setting any log parameter from
524 # mach test to the logger, so it's OK to remove that argument now
529 conditions
.is_android(command_context
)
530 and params
["product"] != "firefox_android"
532 logger
.warning("Must specify --product=firefox_android in Android environment.")
534 return wpt_runner
.run(logger
, **params
)
540 conditions
=[conditions
.is_firefox_or_android
],
541 description
="Run web-platform-tests.",
542 parser
=create_parser_wpt
,
543 virtualenv_name
="wpt",
545 def run_wpt(command_context
, **params
):
546 return run_web_platform_tests(command_context
, **params
)
550 "web-platform-tests-update",
552 description
="Update web-platform-test metadata.",
553 parser
=create_parser_update
,
554 virtualenv_name
="wpt",
556 def update_web_platform_tests(command_context
, **params
):
557 wpt_updater
= command_context
._spawn
(WebPlatformTestsUpdater
)
558 logger
= wpt_updater
.setup_logging(**params
)
559 return wpt_updater
.run_update(logger
, **params
)
565 description
="Update web-platform-test metadata.",
566 parser
=create_parser_update
,
567 virtualenv_name
="wpt",
569 def update_wpt(command_context
, **params
):
570 return update_web_platform_tests(command_context
, **params
)
574 "wpt-manifest-update",
576 description
="Update web-platform-test manifests.",
577 parser
=create_parser_manifest_update
,
578 virtualenv_name
="wpt",
580 def wpt_manifest_update(command_context
, **params
):
581 wpt_setup
= command_context
._spawn
(WebPlatformTestsRunnerSetup
)
582 wpt_runner
= WebPlatformTestsRunner(wpt_setup
)
583 logger
= wpt_runner
.setup_logging(**params
)
585 "The wpt manifest is now automatically updated, "
586 "so running this command is usually unnecessary"
588 return 0 if wpt_runner
.update_manifest(logger
, **params
) else 1
594 description
="Run the wpt server",
595 parser
=create_parser_serve
,
596 virtualenv_name
="wpt",
598 def wpt_serve(command_context
, **params
):
601 logger
= logging
.getLogger("web-platform-tests")
602 logger
.addHandler(logging
.StreamHandler(sys
.stdout
))
603 wpt_serve
= command_context
._spawn
(WebPlatformTestsServeRunner
)
604 return wpt_serve
.run(**params
)
608 "wpt-metadata-summary",
610 description
="Create a json summary of the wpt metadata",
611 parser
=create_parser_metadata_summary
,
612 virtualenv_name
="wpt",
614 def wpt_summary(command_context
, **params
):
617 wpt_setup
= command_context
._spawn
(WebPlatformTestsRunnerSetup
)
618 return metasummary
.run(wpt_setup
.topsrcdir
, wpt_setup
.topobjdir
, **params
)
622 "wpt-metadata-merge",
624 parser
=create_parser_metadata_merge
,
625 virtualenv_name
="wpt",
627 def wpt_meta_merge(command_context
, **params
):
630 if params
["dest"] is None:
631 params
["dest"] = params
["current"]
632 return metamerge
.run(**params
)
638 description
="Run the wpt tools and wptrunner unit tests",
639 parser
=create_parser_unittest
,
640 virtualenv_name
="wpt",
642 def wpt_unittest(command_context
, **params
):
643 runner
= command_context
._spawn
(WebPlatformTestsUnittestRunner
)
644 return 0 if runner
.run(**params
) else 1
650 description
="Get a mapping from test ids to files",
651 parser
=create_parser_testpaths
,
652 virtualenv_name
="wpt",
654 def wpt_test_paths(command_context
, **params
):
655 runner
= command_context
._spawn
(WebPlatformTestsTestPathsRunner
)
661 "wpt-fission-regressions",
663 description
="Dump a list of fission-specific regressions",
664 parser
=create_parser_fission_regressions
,
665 virtualenv_name
="wpt",
667 def wpt_fission_regressions(command_context
, **params
):
668 runner
= command_context
._spawn
(WebPlatformTestsFissionRegressionsRunner
)
676 description
="Fetch wptreport.json logs from taskcluster",
677 parser
=create_parser_fetch_logs
,
678 virtualenv_name
="wpt-interop",
680 def wpt_fetch_logs(command_context
, **params
):
683 interop
.fetch_logs(**params
)
690 description
="Score a run according to Interop 2023",
691 parser
=create_parser_interop_score
,
692 virtualenv_name
="wpt-interop",
694 def wpt_interop_score(command_context
, **params
):
697 interop
.score_runs(**params
)