Commit 1bb35d1e authored by Stephen McGruer's avatar Stephen McGruer Committed by Commit Bot

Roll WPT internal tools to 9f6a7e3cfdae54795a0ca58e194b828537f9659e

Bug: None
Change-Id: I8dcbf7f1e583df2f20560a6f6dc2337847c42805
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2432629Reviewed-by: default avatarRobert Ma <robertma@chromium.org>
Commit-Queue: Stephen McGruer <smcgruer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#811214}
parent 39d35e95
...@@ -22,7 +22,7 @@ Local Modifications: None ...@@ -22,7 +22,7 @@ Local Modifications: None
Name: web-platform-tests - Test Suites for Web Platform specifications Name: web-platform-tests - Test Suites for Web Platform specifications
Short Name: wpt Short Name: wpt
URL: https://github.com/web-platform-tests/wpt/ URL: https://github.com/web-platform-tests/wpt/
Version: 358439343c2878d6a3784964a11e43644d49c452 Version: 9f6a7e3cfdae54795a0ca58e194b828537f9659e
License: LICENSES FOR W3C TEST SUITES (https://www.w3.org/Consortium/Legal/2008/03-bsd-license.html) License: LICENSES FOR W3C TEST SUITES (https://www.w3.org/Consortium/Legal/2008/03-bsd-license.html)
License File: wpt/wpt/LICENSE.md License File: wpt/wpt/LICENSE.md
Security Critical: no Security Critical: no
......
...@@ -9,7 +9,7 @@ cd $DIR ...@@ -9,7 +9,7 @@ cd $DIR
TARGET_DIR=$DIR/wpt TARGET_DIR=$DIR/wpt
REMOTE_REPO="https://github.com/web-platform-tests/wpt.git" REMOTE_REPO="https://github.com/web-platform-tests/wpt.git"
WPT_HEAD=358439343c2878d6a3784964a11e43644d49c452 WPT_HEAD=9f6a7e3cfdae54795a0ca58e194b828537f9659e
function clone { function clone {
# Remove existing repo if already exists. # Remove existing repo if already exists.
......
...@@ -6,6 +6,7 @@ import ast ...@@ -6,6 +6,7 @@ import ast
import io import io
import json import json
import logging import logging
import multiprocessing
import os import os
import re import re
import subprocess import subprocess
...@@ -34,6 +35,7 @@ if MYPY: ...@@ -34,6 +35,7 @@ if MYPY:
from typing import Callable from typing import Callable
from typing import Dict from typing import Dict
from typing import IO from typing import IO
from typing import Iterator
from typing import Iterable from typing import Iterable
from typing import List from typing import List
from typing import Optional from typing import Optional
...@@ -42,6 +44,7 @@ if MYPY: ...@@ -42,6 +44,7 @@ if MYPY:
from typing import Text from typing import Text
from typing import Tuple from typing import Tuple
from typing import Type from typing import Type
from typing import TypeVar
# The Ignorelist is a two level dictionary. The top level is indexed by # The Ignorelist is a two level dictionary. The top level is indexed by
# error names (e.g. 'TRAILING WHITESPACE'). Each of those then has a map of # error names (e.g. 'TRAILING WHITESPACE'). Each of those then has a map of
...@@ -50,12 +53,26 @@ if MYPY: ...@@ -50,12 +53,26 @@ if MYPY:
# ignores the error. # ignores the error.
Ignorelist = Dict[Text, Dict[Text, Set[Optional[int]]]] Ignorelist = Dict[Text, Dict[Text, Set[Optional[int]]]]
# Define an arbitrary typevar
T = TypeVar("T")
try: try:
from xml.etree import cElementTree as ElementTree from xml.etree import cElementTree as ElementTree
except ImportError: except ImportError:
from xml.etree import ElementTree as ElementTree # type: ignore from xml.etree import ElementTree as ElementTree # type: ignore
if sys.version_info >= (3, 7):
from contextlib import nullcontext
else:
from contextlib import contextmanager
@contextmanager
def nullcontext(enter_result=None):
# type: (Optional[T]) -> Iterator[Optional[T]]
yield enter_result
logger = None # type: Optional[logging.Logger] logger = None # type: Optional[logging.Logger]
...@@ -367,7 +384,7 @@ def check_unique_testharness_basenames(repo_root, paths): ...@@ -367,7 +384,7 @@ def check_unique_testharness_basenames(repo_root, paths):
file_dict = defaultdict(list) file_dict = defaultdict(list)
for path in paths: for path in paths:
source_file = SourceFile(repo_root, path, "/") source_file = SourceFile(repo_root, path, "/")
if source_file.type != "testharness": if "testharness" not in source_file.possible_types:
continue continue
file_name, file_extension = os.path.splitext(path) file_name, file_extension = os.path.splitext(path)
file_dict[file_name].append(file_extension) file_dict[file_name].append(file_extension)
...@@ -793,8 +810,8 @@ def check_all_paths(repo_root, paths): ...@@ -793,8 +810,8 @@ def check_all_paths(repo_root, paths):
return errors return errors
def check_file_contents(repo_root, path, f): def check_file_contents(repo_root, path, f=None):
# type: (Text, Text, IO[bytes]) -> List[rules.Error] # type: (Text, Text, Optional[IO[bytes]]) -> List[rules.Error]
""" """
Runs lints that check the file contents. Runs lints that check the file contents.
...@@ -803,12 +820,18 @@ def check_file_contents(repo_root, path, f): ...@@ -803,12 +820,18 @@ def check_file_contents(repo_root, path, f):
:param f: a file-like object with the file contents :param f: a file-like object with the file contents
:returns: a list of errors found in ``f`` :returns: a list of errors found in ``f``
""" """
with io.open(os.path.join(repo_root, path), 'rb') if f is None else nullcontext(f) as real_f:
assert real_f is not None # Py2: prod mypy -2 into accepting this isn't None
errors = []
for file_fn in file_lints:
errors.extend(file_fn(repo_root, path, real_f))
real_f.seek(0)
return errors
errors = []
for file_fn in file_lints: def check_file_contents_apply(args):
errors.extend(file_fn(repo_root, path, f)) # type: (Tuple[Text, Text]) -> List[rules.Error]
f.seek(0) return check_file_contents(*args)
return errors
def output_errors_text(log, errors): def output_errors_text(log, errors):
...@@ -964,6 +987,10 @@ def main(**kwargs_str): ...@@ -964,6 +987,10 @@ def main(**kwargs_str):
return lint(repo_root, paths, output_format, ignore_glob, github_checks_outputter) return lint(repo_root, paths, output_format, ignore_glob, github_checks_outputter)
# best experimental guess at a decent cut-off for using the parallel path
MIN_FILES_FOR_PARALLEL = 80
def lint(repo_root, paths, output_format, ignore_glob=None, github_checks_outputter=None): def lint(repo_root, paths, output_format, ignore_glob=None, github_checks_outputter=None):
# type: (Text, List[Text], Text, Optional[List[Text]], Optional[GitHubChecksOutputter]) -> int # type: (Text, List[Text], Text, Optional[List[Text]], Optional[GitHubChecksOutputter]) -> int
error_count = defaultdict(int) # type: Dict[Text, int] error_count = defaultdict(int) # type: Dict[Text, int]
...@@ -1005,26 +1032,47 @@ def lint(repo_root, paths, output_format, ignore_glob=None, github_checks_output ...@@ -1005,26 +1032,47 @@ def lint(repo_root, paths, output_format, ignore_glob=None, github_checks_output
return (errors[-1][0], path) return (errors[-1][0], path)
for path in paths[:]: to_check_content = []
skip = set()
for path in paths:
abs_path = os.path.join(repo_root, path) abs_path = os.path.join(repo_root, path)
if not os.path.exists(abs_path): if not os.path.exists(abs_path):
paths.remove(path) skip.add(path)
continue continue
if any(fnmatch.fnmatch(path, file_match) for file_match in skipped_files): if any(fnmatch.fnmatch(path, file_match) for file_match in skipped_files):
paths.remove(path) skip.add(path)
continue continue
errors = check_path(repo_root, path) errors = check_path(repo_root, path)
last = process_errors(errors) or last last = process_errors(errors) or last
if not os.path.isdir(abs_path): if not os.path.isdir(abs_path):
with io.open(abs_path, 'rb') as test_file: to_check_content.append((repo_root, path))
errors = check_file_contents(repo_root, path, test_file)
last = process_errors(errors) or last paths = [p for p in paths if p not in skip]
if len(to_check_content) >= MIN_FILES_FOR_PARALLEL:
pool = multiprocessing.Pool()
# submit this job first, as it's the longest running
all_paths_result = pool.apply_async(check_all_paths, (repo_root, paths))
# each item tends to be quick, so pass things in large chunks to avoid too much IPC overhead
errors_it = pool.imap_unordered(check_file_contents_apply, to_check_content, chunksize=40)
pool.close()
for errors in errors_it:
last = process_errors(errors) or last
errors = all_paths_result.get()
pool.join()
last = process_errors(errors) or last
else:
for item in to_check_content:
errors = check_file_contents(*item)
last = process_errors(errors) or last
errors = check_all_paths(repo_root, paths) errors = check_all_paths(repo_root, paths)
last = process_errors(errors) or last last = process_errors(errors) or last
if output_format in ("normal", "markdown"): if output_format in ("normal", "markdown"):
output_error_count(error_count) output_error_count(error_count)
......
...@@ -865,9 +865,68 @@ class SourceFile(object): ...@@ -865,9 +865,68 @@ class SourceFile(object):
@property @property
def type(self): def type(self):
# type: () -> Text # type: () -> Text
possible_types = self.possible_types
if len(possible_types) == 1:
return possible_types.pop()
rv, _ = self.manifest_items() rv, _ = self.manifest_items()
return rv return rv
@property
def possible_types(self):
# type: () -> Set[Text]
"""Determines the set of possible types without reading the file"""
if self.items_cache:
return {self.items_cache[0]}
if self.name_is_non_test:
return {SupportFile.item_type}
if self.name_is_manual:
return {ManualTest.item_type}
if self.name_is_conformance:
return {ConformanceCheckerTest.item_type}
if self.name_is_conformance_support:
return {SupportFile.item_type}
if self.name_is_webdriver:
return {WebDriverSpecTest.item_type}
if self.name_is_visual:
return {VisualTest.item_type}
if self.name_is_crashtest:
return {CrashTest.item_type}
if self.name_is_print_reftest:
return {PrintRefTest.item_type}
if self.name_is_multi_global:
return {TestharnessTest.item_type}
if self.name_is_worker:
return {TestharnessTest.item_type}
if self.name_is_window:
return {TestharnessTest.item_type}
if self.markup_type is None:
return {SupportFile.item_type}
if not self.name_is_reference:
return {ManualTest.item_type,
TestharnessTest.item_type,
RefTest.item_type,
VisualTest.item_type,
SupportFile.item_type}
return {TestharnessTest.item_type,
RefTest.item_type,
SupportFile.item_type}
def manifest_items(self): def manifest_items(self):
# type: () -> Tuple[Text, List[ManifestItem]] # type: () -> Tuple[Text, List[ManifestItem]]
"""List of manifest items corresponding to the file. There is typically one """List of manifest items corresponding to the file. There is typically one
...@@ -1070,6 +1129,7 @@ class SourceFile(object): ...@@ -1070,6 +1129,7 @@ class SourceFile(object):
self.rel_path self.rel_path
)] )]
assert rv[0] in self.possible_types
assert len(rv[1]) == len(set(rv[1])) assert len(rv[1]) == len(set(rv[1]))
self.items_cache = rv self.items_cache = rv
......
...@@ -418,6 +418,19 @@ class ServerProc(object): ...@@ -418,6 +418,19 @@ class ServerProc(object):
def create_daemon(self, init_func, host, port, paths, routes, bind_address, def create_daemon(self, init_func, host, port, paths, routes, bind_address,
config, **kwargs): config, **kwargs):
if sys.platform == "darwin":
# on Darwin, NOFILE starts with a very low limit (256), so bump it up a little
# by way of comparison, Debian starts with a limit of 1024, Windows 512
import resource # local, as it only exists on Unix-like systems
maxfilesperproc = int(subprocess.check_output(
["sysctl", "-n", "kern.maxfilesperproc"]
).strip())
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
# 2048 is somewhat arbitrary, but gives us some headroom for wptrunner --parallel
# note that it's expected that 2048 will be the min here
new_soft = min(2048, maxfilesperproc, hard)
if soft < new_soft:
resource.setrlimit(resource.RLIMIT_NOFILE, (new_soft, hard))
try: try:
self.daemon = init_func(host, port, paths, routes, bind_address, config, **kwargs) self.daemon = init_func(host, port, paths, routes, bind_address, config, **kwargs)
except socket.error: except socket.error:
......
...@@ -203,6 +203,32 @@ class ActionSequence(object): ...@@ -203,6 +203,32 @@ class ActionSequence(object):
self.key_up(c) self.key_up(c)
return self return self
def scroll(self, x, y, delta_x, delta_y, duration=None, origin=None):
"""Queue a scroll action.
:param x: Destination x-axis coordinate of pointer in CSS pixels.
:param y: Destination y-axis coordinate of pointer in CSS pixels.
:param delta_x: scroll delta on x-axis in CSS pixels.
:param delta_y: scroll delta on y-axis in CSS pixels.
:param duration: Number of milliseconds over which to distribute the
scroll. If None, remote end defaults to 0.
:param origin: Origin of coordinates, either "viewport" or an Element.
If None, remote end defaults to "viewport".
"""
action = {
"type": "scroll",
"x": x,
"y": y,
"deltaX": delta_x,
"deltaY": delta_y
}
if duration is not None:
action["duration"] = duration
if origin is not None:
action["origin"] = origin
self._actions.append(action)
return self
class Actions(object): class Actions(object):
def __init__(self, session): def __init__(self, session):
...@@ -218,10 +244,6 @@ class Actions(object): ...@@ -218,10 +244,6 @@ class Actions(object):
""" """
body = {"actions": [] if actions is None else actions} body = {"actions": [] if actions is None else actions}
actions = self.session.send_session_command("POST", "actions", body) actions = self.session.send_session_command("POST", "actions", body)
"""WebDriver window should be set to the top level window when wptrunner
processes the next event.
"""
self.session.switch_frame(None)
return actions return actions
@command @command
...@@ -321,9 +343,7 @@ class Find(object): ...@@ -321,9 +343,7 @@ class Find(object):
self.session = session self.session = session
@command @command
def css(self, element_selector, all=True, frame="window"): def css(self, element_selector, all=True):
if (frame != "window"):
self.session.switch_frame(frame)
elements = self._find_element("css selector", element_selector, all) elements = self._find_element("css selector", element_selector, all)
return elements return elements
......
...@@ -656,9 +656,33 @@ class Chrome(Browser): ...@@ -656,9 +656,33 @@ class Chrome(Browser):
self.logger.warning("Unable to find the browser binary.") self.logger.warning("Unable to find the browser binary.")
return None return None
def find_webdriver(self, channel=None): def find_webdriver(self, channel=None, browser_binary=None):
return find_executable("chromedriver") return find_executable("chromedriver")
def webdriver_supports_browser(self, webdriver_binary, browser_binary):
chromedriver_version = self.webdriver_version(webdriver_binary)
if not chromedriver_version:
self.logger.warning(
"Unable to get version for ChromeDriver %s, rejecting it" %
webdriver_binary)
return False
browser_version = self.version(browser_binary)
if not browser_version:
# If we can't get the browser version, we just have to assume the
# ChromeDriver is good.
return True
# Check that the ChromeDriver version matches the Chrome version.
chromedriver_major = chromedriver_version.split('.')[0]
browser_major = browser_version.split('.')[0]
if chromedriver_major != browser_major:
self.logger.warning(
"ChromeDriver %s does not match Chrome/Chromium %s" %
(chromedriver_version, browser_version))
return False
return True
def _official_chromedriver_url(self, chrome_version): def _official_chromedriver_url(self, chrome_version):
# http://chromedriver.chromium.org/downloads/version-selection # http://chromedriver.chromium.org/downloads/version-selection
parts = chrome_version.split(".") parts = chrome_version.split(".")
...@@ -700,6 +724,17 @@ class Chrome(Browser): ...@@ -700,6 +724,17 @@ class Chrome(Browser):
def install_webdriver_by_version(self, version, dest=None): def install_webdriver_by_version(self, version, dest=None):
if dest is None: if dest is None:
dest = os.pwd dest = os.pwd
# There may be an existing chromedriver binary from a previous install.
# To provide a clean install experience, remove the old binary - this
# avoids tricky issues like unzipping over a read-only file.
existing_binary_path = find_executable("chromedriver", dest)
if existing_binary_path:
self.logger.info("Removing existing ChromeDriver binary: %s" %
existing_binary_path)
os.chmod(existing_binary_path, stat.S_IWUSR)
os.remove(existing_binary_path)
url = self._latest_chromedriver_url(version) if version \ url = self._latest_chromedriver_url(version) if version \
else self._chromium_chromedriver_url(None) else self._chromium_chromedriver_url(None)
self.logger.info("Downloading ChromeDriver from %s" % url) self.logger.info("Downloading ChromeDriver from %s" % url)
...@@ -749,6 +784,21 @@ class Chrome(Browser): ...@@ -749,6 +784,21 @@ class Chrome(Browser):
return None return None
return m.group(1) return m.group(1)
def webdriver_version(self, webdriver_binary):
if uname[0] == "Windows":
return _get_fileversion(webdriver_binary, self.logger)
try:
version_string = call(webdriver_binary, "--version").strip()
except subprocess.CalledProcessError:
self.logger.warning("Failed to call %s" % webdriver_binary)
return None
m = re.match(r"ChromeDriver ([0-9][0-9.]*)", version_string)
if not m:
self.logger.warning("Failed to extract version from: %s" % version_string)
return None
return m.group(1)
class ChromeAndroidBase(Browser): class ChromeAndroidBase(Browser):
"""A base class for ChromeAndroid and AndroidWebView. """A base class for ChromeAndroid and AndroidWebView.
...@@ -1016,6 +1066,29 @@ class EdgeChromium(Browser): ...@@ -1016,6 +1066,29 @@ class EdgeChromium(Browser):
def find_webdriver(self, channel=None): def find_webdriver(self, channel=None):
return find_executable("msedgedriver") return find_executable("msedgedriver")
def webdriver_supports_browser(self, webdriver_binary, browser_binary):
edgedriver_version = self.webdriver_version(webdriver_binary)
if not edgedriver_version:
self.logger.warning(
"Unable to get version for EdgeDriver %s, rejecting it" %
webdriver_binary)
return False
browser_version = self.version(browser_binary)
if not browser_version:
# If we can't get the browser version, we just have to assume the
# EdgeDriver is good.
return True
# Check that the EdgeDriver version matches the Edge version.
edgedriver_major = edgedriver_version.split('.')[0]
browser_major = browser_version.split('.')[0]
if edgedriver_major != browser_major:
self.logger.warning("EdgeDriver %s does not match Edge %s" %
(edgedriver_version, browser_version))
return False
return True
def install_webdriver(self, dest=None, channel=None, browser_binary=None): def install_webdriver(self, dest=None, channel=None, browser_binary=None):
if self.platform != "win" and self.platform != "macos": if self.platform != "win" and self.platform != "macos":
raise ValueError("Only Windows and Mac platforms are currently supported") raise ValueError("Only Windows and Mac platforms are currently supported")
...@@ -1074,6 +1147,21 @@ class EdgeChromium(Browser): ...@@ -1074,6 +1147,21 @@ class EdgeChromium(Browser):
self.logger.warning("Failed to find Edge binary.") self.logger.warning("Failed to find Edge binary.")
return None return None
def webdriver_version(self, webdriver_binary):
if self.platform == "win":
return _get_fileversion(webdriver_binary, self.logger)
try:
version_string = call(webdriver_binary, "--version").strip()
except subprocess.CalledProcessError:
self.logger.warning("Failed to call %s" % webdriver_binary)
return None
m = re.match(r"MSEdgeDriver ([0-9][0-9.]*)", version_string)
if not m:
self.logger.warning("Failed to extract version from: %s" % version_string)
return None
return m.group(1)
class Edge(Browser): class Edge(Browser):
"""Edge-specific interface.""" """Edge-specific interface."""
......
...@@ -334,16 +334,19 @@ class Chrome(BrowserSetup): ...@@ -334,16 +334,19 @@ class Chrome(BrowserSetup):
logger.info("MojoJS enabled") logger.info("MojoJS enabled")
except Exception as e: except Exception as e:
logger.error("Cannot enable MojoJS: %s" % e) logger.error("Cannot enable MojoJS: %s" % e)
if kwargs["webdriver_binary"] is None: if kwargs["webdriver_binary"] is None:
webdriver_binary = None webdriver_binary = None
if not kwargs["install_webdriver"]: if not kwargs["install_webdriver"]:
webdriver_binary = self.browser.find_webdriver() webdriver_binary = self.browser.find_webdriver()
if webdriver_binary and not self.browser.webdriver_supports_browser(
webdriver_binary, kwargs["binary"]):
webdriver_binary = None
if webdriver_binary is None: if webdriver_binary is None:
install = self.prompt_install("chromedriver") install = self.prompt_install("chromedriver")
if install: if install:
logger.info("Downloading chromedriver")
webdriver_binary = self.browser.install_webdriver( webdriver_binary = self.browser.install_webdriver(
dest=self.venv.bin_path, dest=self.venv.bin_path,
channel=browser_channel, channel=browser_channel,
...@@ -355,7 +358,7 @@ class Chrome(BrowserSetup): ...@@ -355,7 +358,7 @@ class Chrome(BrowserSetup):
if webdriver_binary: if webdriver_binary:
kwargs["webdriver_binary"] = webdriver_binary kwargs["webdriver_binary"] = webdriver_binary
else: else:
raise WptrunError("Unable to locate or install chromedriver binary") raise WptrunError("Unable to locate or install matching ChromeDriver binary")
if browser_channel in self.experimental_channels: if browser_channel in self.experimental_channels:
logger.info("Automatically turning on experimental features for Chrome Dev/Canary or Chromium trunk") logger.info("Automatically turning on experimental features for Chrome Dev/Canary or Chromium trunk")
kwargs["binary_args"].append("--enable-experimental-web-platform-features") kwargs["binary_args"].append("--enable-experimental-web-platform-features")
...@@ -513,13 +516,16 @@ class EdgeChromium(BrowserSetup): ...@@ -513,13 +516,16 @@ class EdgeChromium(BrowserSetup):
kwargs["binary"] = binary kwargs["binary"] = binary
else: else:
raise WptrunError("Unable to locate Edge binary") raise WptrunError("Unable to locate Edge binary")
if kwargs["webdriver_binary"] is None: if kwargs["webdriver_binary"] is None:
webdriver_binary = None webdriver_binary = None
if not kwargs["install_webdriver"]: if not kwargs["install_webdriver"]:
webdriver_binary = self.browser.find_webdriver() webdriver_binary = self.browser.find_webdriver()
if (webdriver_binary and not self.browser.webdriver_supports_browser(
webdriver_binary, kwargs["binary"])):
webdriver_binary = None
# Install browser if none are found or if it's found in venv path if webdriver_binary is None:
if webdriver_binary is None or webdriver_binary in self.venv.bin_path:
install = self.prompt_install("msedgedriver") install = self.prompt_install("msedgedriver")
if install: if install:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment