Skip to content

Commit

Permalink
Add Chrome nightly (Chromium trunk) to wpt (#24702)
Browse files Browse the repository at this point in the history
* Add nightly channel to Chrome

* Implement install{,_webdriver} for Chrome nightly

* Support Chrome nightly in "wpt run"

* Add and fix some tests

Add a test for installing Chrome nightly.

In addition, create a safer version of shutil.rmtree based on existing
implementation in browser.py and reuse it in more places in place of
shutil.rmtree.
  • Loading branch information
Hexcles authored Jul 24, 2020
1 parent cf33f00 commit d745272
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 75 deletions.
162 changes: 98 additions & 64 deletions tools/wpt/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import re
import shutil
import stat
import errno
import subprocess
import tempfile
from abc import ABCMeta, abstractmethod
Expand All @@ -13,7 +12,7 @@
from six.moves.urllib.parse import urlsplit
import requests

from .utils import call, get, untar, unzip
from .utils import call, get, rmtree, untar, unzip

uname = platform.uname()

Expand All @@ -31,15 +30,6 @@ def _get_fileversion(binary, logger=None):
return None


def handle_remove_readonly(func, path, exc):
excvalue = exc[1]
if func in (os.rmdir, os.remove) and excvalue.errno == errno.EACCES:
os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) # 0777
func(path)
else:
raise


def get_ext(filename):
"""Get the extension from a filename with special handling for .tar.foo"""
name, ext = os.path.splitext(filename)
Expand All @@ -63,6 +53,18 @@ class Browser(object):
def __init__(self, logger):
self.logger = logger

def _get_dest(self, dest, channel):
if dest is None:
# os.getcwd() doesn't include the venv path
dest = os.path.join(os.getcwd(), "_venv")

dest = os.path.join(dest, "browsers", channel)

if not os.path.exists(dest):
os.makedirs(dest)

return dest

@abstractmethod
def download(self, dest=None, channel=None, rename=None):
"""Download a package or installer for the browser
Expand Down Expand Up @@ -156,18 +158,6 @@ def platform_string_geckodriver(self):

return "%s%s" % (self.platform, bits)

def _get_dest(self, dest, channel):
if dest is None:
# os.getcwd() doesn't include the venv path
dest = os.path.join(os.getcwd(), "_venv")

dest = os.path.join(dest, "browsers", channel)

if not os.path.exists(dest):
os.makedirs(dest)

return dest

def download(self, dest=None, channel="nightly", rename=None):
product = {
"nightly": "firefox-nightly-latest-ssl",
Expand Down Expand Up @@ -374,7 +364,7 @@ def install_prefs(self, binary, dest=None, channel=None):
# If we don't have a recent download, grab and extract the latest one
if not have_cache:
if os.path.exists(dest):
shutil.rmtree(dest)
rmtree(dest)
os.makedirs(dest)

url = self.get_profile_bundle_url(version, channel)
Expand All @@ -389,7 +379,7 @@ def install_prefs(self, binary, dest=None, channel=None):
path = os.path.join(profiles, name)
shutil.move(path, dest)
finally:
shutil.rmtree(extract_dir)
rmtree(extract_dir)
else:
self.logger.info("Using cached test prefs from %s" % dest)

Expand Down Expand Up @@ -534,22 +524,48 @@ class Chrome(Browser):

product = "chrome"
requirements = "requirements_chrome.txt"
platforms = {
"Linux": "Linux",
"Windows": "Win",
"Darwin": "Mac",
}

def __init__(self, logger):
super(Chrome, self).__init__(logger)
self._last_change = None

def download(self, dest=None, channel=None, rename=None):
raise NotImplementedError
if channel != "nightly":
raise NotImplementedError("We can only download Chrome Nightly (Chromium ToT) for you.")
if dest is None:
dest = self._get_dest(None, channel)

filename = self._chromium_package_name() + ".zip"
url = self._latest_chromium_snapshot_url() + filename
self.logger.info("Downloading Chrome from %s" % url)
resp = get(url)
installer_path = os.path.join(dest, filename)
with open(installer_path, "wb") as f:
f.write(resp.content)
return installer_path

def install(self, dest=None, channel=None):
raise NotImplementedError
if channel != "nightly":
raise NotImplementedError("We can only install Chrome Nightly (Chromium ToT) for you.")
dest = self._get_dest(dest, channel)

def platform_string(self):
platform = {
"Linux": "linux",
"Windows": "win",
"Darwin": "mac"
}.get(uname[0])
installer_path = self.download(dest, channel)
with open(installer_path, "rb") as f:
unzip(f, dest)
os.remove(installer_path)
return self.find_nightly_binary(dest, channel)

def _chromedriver_platform_string(self):
platform = self.platforms.get(uname[0])

if platform is None:
raise ValueError("Unable to construct a valid Chrome package name for current platform")
platform = platform.lower()

if platform == "linux":
bits = "64" if uname[4] == "x86_64" else "32"
Expand All @@ -560,12 +576,8 @@ def platform_string(self):

return "%s%s" % (platform, bits)

def chromium_platform_string(self):
platform = {
"Linux": "Linux",
"Windows": "Win",
"Darwin": "Mac"
}.get(uname[0])
def _chromium_platform_string(self):
platform = self.platforms.get(uname[0])

if platform is None:
raise ValueError("Unable to construct a valid Chromium package name for current platform")
Expand All @@ -575,7 +587,26 @@ def chromium_platform_string(self):

return platform

def _chromium_package_name(self):
return "chrome-%s" % self.platforms.get(uname[0]).lower()

def _latest_chromium_snapshot_url(self):
# Make sure we use the same revision in an invocation.
architecture = self._chromium_platform_string()
if self._last_change is None:
revision_url = "https://storage.googleapis.com/chromium-browser-snapshots/%s/LAST_CHANGE" % architecture
self._last_change = get(revision_url).text.strip()
return "https://storage.googleapis.com/chromium-browser-snapshots/%s/%s/" % (architecture, self._last_change)

def find_nightly_binary(self, dest, channel):
binary = "Chromium" if uname[0] == "Darwin" else "chrome"
# find_executable will add .exe on Windows automatically.
return find_executable(binary, os.path.join(dest, self._chromium_package_name()))

def find_binary(self, venv_path=None, channel=None):
if channel == "nightly":
return self.find_nightly_binary(self._get_dest(venv_path, channel))

if uname[0] == "Linux":
name = "google-chrome"
if channel == "stable":
Expand Down Expand Up @@ -613,25 +644,23 @@ def _official_chromedriver_url(self, chrome_version):
except requests.RequestException:
return None
return "https://chromedriver.storage.googleapis.com/%s/chromedriver_%s.zip" % (
latest, self.platform_string())
latest, self._chromedriver_platform_string())

def _chromium_chromedriver_url(self, chrome_version):
try:
# Try to find the Chromium build with the same revision.
omaha = get("https://omahaproxy.appspot.com/deps.json?version=" + chrome_version).json()
revision = omaha['chromium_base_position']
url = "https://storage.googleapis.com/chromium-browser-snapshots/%s/%s/chromedriver_%s.zip" % (
self.chromium_platform_string(), revision, self.platform_string())
# Check the status without downloading the content (this is a streaming request).
get(url)
except requests.RequestException:
# Fall back to the tip-of-tree Chromium build.
revision_url = "https://storage.googleapis.com/chromium-browser-snapshots/%s/LAST_CHANGE" % (
self.chromium_platform_string())
revision = get(revision_url).text.strip()
url = "https://storage.googleapis.com/chromium-browser-snapshots/%s/%s/chromedriver_%s.zip" % (
self.chromium_platform_string(), revision, self.platform_string())
return url
if chrome_version:
try:
# Try to find the Chromium build with the same revision.
omaha = get("https://omahaproxy.appspot.com/deps.json?version=" + chrome_version).json()
revision = omaha['chromium_base_position']
url = "https://storage.googleapis.com/chromium-browser-snapshots/%s/%s/chromedriver_%s.zip" % (
self._chromium_platform_string(), revision, self._chromedriver_platform_string())
# Check the status without downloading the content (this is a streaming request).
get(url)
return url
except requests.RequestException:
pass
# Fall back to the tip-of-tree Chromium build.
return "%schromedriver_%s.zip" % (self._latest_chromium_snapshot_url(), self._chromedriver_platform_string())

def _latest_chromedriver_url(self, chrome_version):
# Remove channel suffixes (e.g. " dev").
Expand All @@ -640,20 +669,25 @@ def _latest_chromedriver_url(self, chrome_version):
self._chromium_chromedriver_url(chrome_version))

def install_webdriver_by_version(self, version, dest=None):
assert version, "Cannot install ChromeDriver without Chrome version"
if dest is None:
dest = os.pwd
url = self._latest_chromedriver_url(version)
url = self._latest_chromedriver_url(version) if version \
else self._chromium_chromedriver_url(None)
self.logger.info("Downloading ChromeDriver from %s" % url)
unzip(get(url).raw, dest)
chromedriver_dir = os.path.join(
dest, 'chromedriver_%s' % self.platform_string())
if os.path.isfile(os.path.join(chromedriver_dir, "chromedriver")):
shutil.move(os.path.join(chromedriver_dir, "chromedriver"), dest)
shutil.rmtree(chromedriver_dir)
dest, 'chromedriver_%s' % self._chromedriver_platform_string())
unzipped_path = find_executable("chromedriver", chromedriver_dir)
assert unzipped_path is not None
shutil.move(unzipped_path, dest)
rmtree(chromedriver_dir)
return find_executable("chromedriver", dest)

def install_webdriver(self, dest=None, channel=None, browser_binary=None):
if channel == "nightly":
# The "nightly" channel is not an official channel, so we simply download ToT.
return self.install_webdriver_by_version(None, dest)

if browser_binary is None:
browser_binary = self.find_binary(channel)
return self.install_webdriver_by_version(
Expand Down Expand Up @@ -878,7 +912,7 @@ def install_webdriver(self, dest=None, channel=None, browser_binary=None):

operadriver_dir = os.path.join(dest, "operadriver_%s" % self.platform_string())
shutil.move(os.path.join(operadriver_dir, "operadriver"), dest)
shutil.rmtree(operadriver_dir)
rmtree(operadriver_dir)

path = find_executable("operadriver")
st = os.stat(path)
Expand Down Expand Up @@ -975,7 +1009,7 @@ def install_webdriver(self, dest=None, channel=None, browser_binary=None):
driver_notes_path = os.path.join(dest, "Driver_notes")
if os.path.isdir(driver_notes_path):
print("Delete %s folder" % driver_notes_path)
shutil.rmtree(driver_notes_path, ignore_errors=False, onerror=handle_remove_readonly)
rmtree(driver_notes_path)

self.logger.info("Downloading MSEdgeDriver from %s" % url)
unzip(get(url).raw, dest)
Expand Down
6 changes: 3 additions & 3 deletions tools/wpt/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

latest_channels = {
'firefox': 'nightly',
'chrome': 'dev',
'chrome': 'nightly',
'chrome_android': 'dev',
'edgechromium': 'dev',
'safari': 'preview',
Expand All @@ -14,11 +14,11 @@
'stable': 'stable',
'release': 'stable',
'beta': 'beta',
'dev': 'dev',
'canary': 'canary',
'nightly': latest_channels,
'dev': latest_channels,
'preview': latest_channels,
'experimental': latest_channels,
'canary': 'canary',
}

channel_args = argparse.ArgumentParser(add_help=False)
Expand Down
4 changes: 2 additions & 2 deletions tools/wpt/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,8 +343,8 @@ def setup_kwargs(self, kwargs):
kwargs["webdriver_binary"] = webdriver_binary
else:
raise WptrunError("Unable to locate or install chromedriver binary")
if browser_channel in ("dev", "canary"):
logger.info("Automatically turning on experimental features for Chrome Dev/Canary")
if browser_channel in ("dev", "canary", "nightly"):
logger.info("Automatically turning on experimental features for Chrome Dev/Canary or Chromium trunk")
kwargs["binary_args"].append("--enable-experimental-web-platform-features")
# HACK(Hexcles): work around https://github.com/web-platform-tests/wpt/issues/16448
kwargs["webdriver_args"].append("--disable-build-check")
Expand Down
Loading

0 comments on commit d745272

Please sign in to comment.