Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(417)

Unified Diff: third_party/WebKit/LayoutTests/external/wpt/check_stability.py

Issue 2668783003: Import wpt@767dc2a4f049c761bd146d61de2ea860a895a624 (Closed)
Patch Set: Update test expectations and baselines. Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: third_party/WebKit/LayoutTests/external/wpt/check_stability.py
diff --git a/third_party/WebKit/LayoutTests/external/wpt/check_stability.py b/third_party/WebKit/LayoutTests/external/wpt/check_stability.py
index 248c447f3736d5fa94201a1f2cac38704e13464d..2d1b8729d5dab4b0cdc64ed8d2d28b840ec9e332 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/check_stability.py
+++ b/third_party/WebKit/LayoutTests/external/wpt/check_stability.py
@@ -36,6 +36,7 @@ logger = logging.getLogger(os.path.splitext(__file__)[0])
def do_delayed_imports():
+ """Import and set up modules only needed if execution gets to this point."""
global BaseHandler
global LogLevelFilter
global StreamHandler
@@ -52,6 +53,7 @@ def do_delayed_imports():
def setup_logging():
+ """Set up basic debug logger."""
handler = logging.StreamHandler(sys.stdout)
formatter = logging.Formatter(logging.BASIC_FORMAT, None)
handler.setFormatter(formatter)
@@ -62,38 +64,57 @@ setup_logging()
def setup_action_filter():
+ """Create global LogActionFilter class as part of deferred module load."""
global LogActionFilter
class LogActionFilter(BaseHandler):
- """Handler that filters out messages with action of log and a level
- lower than some specified level.
+
+ """Handler that filters out messages not of a given set of actions.
+
+ Subclasses BaseHandler.
:param inner: Handler to use for messages that pass this filter
- :param level: Minimum log level to process
+ :param actions: List of actions for which to fire the handler
"""
+
def __init__(self, inner, actions):
+ """Extend BaseHandler and set inner and actions props on self."""
BaseHandler.__init__(self, inner)
self.inner = inner
self.actions = actions
def __call__(self, item):
+ """Invoke handler if action is in list passed as constructor param."""
if item["action"] in self.actions:
return self.inner(item)
class TravisFold(object):
+
+ """Context for TravisCI folding mechanism. Subclasses object.
+
+ See: https://blog.travis-ci.com/2013-05-22-improving-build-visibility-log-folds/
+ """
+
def __init__(self, name):
+ """Register TravisCI folding section name."""
self.name = name
def __enter__(self):
+ """Emit fold start syntax."""
print("travis_fold:start:%s" % self.name, file=sys.stderr)
def __exit__(self, type, value, traceback):
+ """Emit fold end syntax."""
print("travis_fold:end:%s" % self.name, file=sys.stderr)
class GitHub(object):
+
+ """Interface for the GitHub API."""
+
def __init__(self, org, repo, token, product):
+ """Set properties required for communicating with GH API on self."""
self.token = token
self.headers = {"Accept": "application/vnd.github.v3+json"}
self.auth = (self.token, "x-oauth-basic")
@@ -103,6 +124,7 @@ class GitHub(object):
self.product = product
def _headers(self, headers):
+ """Extend existing HTTP headers and return new value."""
if headers is None:
headers = {}
rv = self.headers.copy()
@@ -110,6 +132,7 @@ class GitHub(object):
return rv
def post(self, url, data, headers=None):
+ """Serialize and POST data to given URL."""
logger.debug("POST %s" % url)
if data is not None:
data = json.dumps(data)
@@ -123,6 +146,7 @@ class GitHub(object):
return resp
def patch(self, url, data, headers=None):
+ """Serialize and PATCH data to given URL."""
logger.debug("PATCH %s" % url)
if data is not None:
data = json.dumps(data)
@@ -136,6 +160,7 @@ class GitHub(object):
return resp
def get(self, url, headers=None):
+ """Execute GET request for given URL."""
logger.debug("GET %s" % url)
resp = requests.get(
url,
@@ -146,6 +171,7 @@ class GitHub(object):
return resp
def post_comment(self, issue_number, body):
+ """Create or update comment in appropriate GitHub pull request comments."""
user = self.get(urljoin(self.base_url, "/user")).json()
issue_comments_url = urljoin(self.base_url, "issues/%s/comments" % issue_number)
comments = self.get(issue_comments_url).json()
@@ -162,13 +188,21 @@ class GitHub(object):
class GitHubCommentHandler(logging.Handler):
+
+ """GitHub pull request comment handler.
+
+ Subclasses logging.Handler to add ability to post comments to GitHub.
+ """
+
def __init__(self, github, pull_number):
+ """Extend logging.Handler and set required properties on self."""
logging.Handler.__init__(self)
self.github = github
self.pull_number = pull_number
self.log_data = []
def emit(self, record):
+ """Format record and add to log"""
try:
msg = self.format(record)
self.log_data.append(msg)
@@ -176,24 +210,36 @@ class GitHubCommentHandler(logging.Handler):
self.handleError(record)
def send(self):
+ """Post log to GitHub and flush log."""
self.github.post_comment(self.pull_number, "\n".join(self.log_data))
self.log_data = []
class Browser(object):
+
+ """Base browser class that sets a reference to a GitHub token."""
+
product = None
binary = None
def __init__(self, github_token):
+ """Set GitHub token property on self."""
self.github_token = github_token
class Firefox(Browser):
+
+ """Firefox-specific interface.
+
+ Includes installation, webdriver installation, and wptrunner setup methods.
+ """
+
product = "firefox"
binary = "%s/firefox/firefox"
platform_ini = "%s/firefox/platform.ini"
def install(self):
+ """Install Firefox."""
call("pip", "install", "-r", os.path.join(wptrunner_root, "requirements_firefox.txt"))
resp = get("https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/firefox-53.0a1.en-US.linux-x86_64.tar.bz2")
untar(resp.raw)
@@ -206,6 +252,7 @@ class Firefox(Browser):
call("pip", "install", "-r", os.path.join(wptrunner_root, "requirements_firefox.txt"))
def _latest_geckodriver_version(self):
+ """Get and return latest version number for geckodriver."""
# This is used rather than an API call to avoid rate limits
tags = call("git", "ls-remote", "--tags", "--refs",
"https://github.com/mozilla/geckodriver.git")
@@ -221,6 +268,7 @@ class Firefox(Browser):
return "v%s.%s.%s" % tuple(str(item) for item in latest_release)
def install_webdriver(self):
+ """Install latest Geckodriver."""
version = self._latest_geckodriver_version()
logger.debug("Latest geckodriver release %s" % version)
url = "https://github.com/mozilla/geckodriver/releases/download/%s/geckodriver-%s-linux64.tar.gz" % (version, version)
@@ -237,6 +285,7 @@ class Firefox(Browser):
platform_info.get("Build", "SourceStamp"))
def wptrunner_args(self, root):
+ """Return Firefox-specific wpt-runner arguments."""
return {
"product": "firefox",
"binary": self.binary % root,
@@ -247,16 +296,24 @@ class Firefox(Browser):
class Chrome(Browser):
+ """Chrome-specific interface.
+
+ Includes installation, webdriver installation, and wptrunner setup methods.
+ """
+
product = "chrome"
binary = "/usr/bin/google-chrome"
def install(self):
+ """Install Chrome."""
+
# Installing the Google Chrome browser requires administrative
# privileges, so that installation is handled by the invoking script.
call("pip", "install", "-r", os.path.join(wptrunner_root, "requirements_chrome.txt"))
def install_webdriver(self):
+ """Install latest Webdriver."""
latest = get("http://chromedriver.storage.googleapis.com/LATEST_RELEASE").text.strip()
url = "http://chromedriver.storage.googleapis.com/%s/chromedriver_linux64.zip" % latest
unzip(get(url).raw)
@@ -266,24 +323,20 @@ class Chrome(Browser):
def version(self, root):
"""Retrieve the release version of the installed browser."""
output = call(self.binary, "--version")
- return re.search(r"[0-9a-z\.]+$", output.strip()).group(0)
+ return re.search(r"[0-9\.]+( [a-z]+)?$", output.strip()).group(0)
def wptrunner_args(self, root):
+ """Return Chrome-specific wpt-runner arguments."""
return {
"product": "chrome",
"binary": self.binary,
- # Chrome's "sandbox" security feature must be disabled in order to
- # run the browser in OpenVZ environments such as the one provided
- # by TravisCI.
- #
- # Reference: https://github.com/travis-ci/travis-ci/issues/938
- "binary_arg": "--no-sandbox",
"webdriver_binary": "%s/chromedriver" % root,
"test_types": ["testharness", "reftest"]
}
def get(url):
+ """Issue GET request to a given URL and return the response."""
logger.debug("GET %s" % url)
resp = requests.get(url, stream=True)
resp.raise_for_status()
@@ -291,6 +344,10 @@ def get(url):
def call(*args):
+ """Log terminal command, invoke it as a subprocess.
+
+ Returns a bytestring of the subprocess output if no error.
+ """
logger.debug("%s" % " ".join(args))
try:
return subprocess.check_output(args)
@@ -302,6 +359,7 @@ def call(*args):
def get_git_cmd(repo_path):
+ """Create a function for invoking git commands as a subprocess."""
def git(cmd, *args):
full_cmd = ["git", cmd] + list(args)
try:
@@ -314,6 +372,7 @@ def get_git_cmd(repo_path):
def seekable(fileobj):
+ """Attempt to use file.seek on given file, with fallbacks."""
try:
fileobj.seek(fileobj.tell())
except Exception:
@@ -323,6 +382,7 @@ def seekable(fileobj):
def untar(fileobj):
+ """Extract tar archive."""
logger.debug("untar")
fileobj = seekable(fileobj)
with tarfile.open(fileobj=fileobj) as tar_data:
@@ -330,6 +390,7 @@ def untar(fileobj):
def unzip(fileobj):
+ """Extract zip archive."""
logger.debug("unzip")
fileobj = seekable(fileobj)
with zipfile.ZipFile(fileobj) as zip_data:
@@ -340,6 +401,10 @@ def unzip(fileobj):
def setup_github_logging(args):
+ """Set up and return GitHub comment handler.
+
+ :param args: the parsed arguments passed to the script
+ """
gh_handler = None
if args.comment_pr:
github = GitHub(args.user, "web-platform-tests", args.gh_token, args.product)
@@ -358,6 +423,7 @@ def setup_github_logging(args):
class pwd(object):
+ """Create context for temporarily changing present working directory."""
def __init__(self, dir):
self.dir = dir
self.old_dir = None
@@ -372,22 +438,26 @@ class pwd(object):
def fetch_wpt_master(user):
+ """Fetch the master branch via git."""
git = get_git_cmd(wpt_root)
git("fetch", "https://github.com/%s/web-platform-tests.git" % user, "master:master")
def get_sha1():
+ """ Get and return sha1 of current git branch HEAD commit."""
git = get_git_cmd(wpt_root)
return git("rev-parse", "HEAD").strip()
def build_manifest():
+ """Build manifest of all files in web-platform-tests"""
with pwd(wpt_root):
# TODO: Call the manifest code directly
call("python", "manifest")
def install_wptrunner():
+ """Clone and install wptrunner."""
call("git", "clone", "--depth=1", "https://github.com/w3c/wptrunner.git", wptrunner_root)
git = get_git_cmd(wptrunner_root)
git("submodule", "update", "--init", "--recursive")
@@ -395,11 +465,11 @@ def install_wptrunner():
def get_files_changed():
+ """Get and return files changed since current branch diverged from master."""
root = os.path.abspath(os.curdir)
git = get_git_cmd(wpt_root)
branch_point = git("merge-base", "HEAD", "master").strip()
logger.debug("Branch point from master: %s" % branch_point)
- logger.debug(git("log", "--oneline", "%s.." % branch_point))
files = git("diff", "--name-only", "-z", "%s.." % branch_point)
if not files:
return []
@@ -409,6 +479,7 @@ def get_files_changed():
def get_affected_testfiles(files_changed):
+ """Determine and return list of test files that reference changed files."""
affected_testfiles = set()
nontests_changed = set(files_changed)
manifest_file = os.path.join(wpt_root, "MANIFEST.json")
@@ -464,6 +535,7 @@ def get_affected_testfiles(files_changed):
def wptrunner_args(root, files_changed, iterations, browser):
+ """Derive and return arguments for wpt-runner."""
parser = wptcommandline.create_parser([browser.product])
args = vars(parser.parse_args([]))
args.update(browser.wptrunner_args(root))
@@ -481,9 +553,15 @@ def wptrunner_args(root, files_changed, iterations, browser):
def setup_log_handler():
+ """Set up LogHandler class as part of deferred module load."""
global LogHandler
class LogHandler(reader.LogHandler):
+
+ """Handle updating test and subtest status in log.
+
+ Subclasses reader.LogHandler.
+ """
def __init__(self):
self.results = defaultdict(lambda: defaultdict(lambda: defaultdict(int)))
@@ -495,10 +573,12 @@ def setup_log_handler():
def is_inconsistent(results_dict, iterations):
+ """Return whether or not a single test is inconsistent."""
return len(results_dict) > 1 or sum(results_dict.values()) != iterations
def err_string(results_dict, iterations):
+ """Create and return string with errors from test run."""
rv = []
total_results = sum(results_dict.values())
for key, value in sorted(results_dict.items()):
@@ -513,6 +593,7 @@ def err_string(results_dict, iterations):
def process_results(log, iterations):
+ """Process test log and return overall results and list of inconsistent tests."""
inconsistent = []
handler = LogHandler()
reader.handle_log(reader.read(log), handler)
@@ -540,6 +621,7 @@ def format_comment_title(product):
def markdown_adjust(s):
+ """Escape problematic markdown sequences."""
s = s.replace('\t', u'\\t')
s = s.replace('\n', u'\\n')
s = s.replace('\r', u'\\r')
@@ -548,6 +630,7 @@ def markdown_adjust(s):
def table(headings, data, log):
+ """Create and log data to specified logger in tabular format."""
cols = range(len(headings))
assert all(len(item) == len(cols) for item in data)
max_widths = reduce(lambda prev, cur: [(len(cur[i]) + 2)
@@ -564,6 +647,7 @@ def table(headings, data, log):
def write_inconsistent(inconsistent, iterations):
+ """Output inconsistent tests to logger.error."""
logger.error("## Unstable results ##\n")
strings = [("`%s`" % markdown_adjust(test), ("`%s`" % markdown_adjust(subtest)) if subtest else "", err_string(results, iterations))
for test, subtest, results in inconsistent]
@@ -571,6 +655,7 @@ def write_inconsistent(inconsistent, iterations):
def write_results(results, iterations, comment_pr):
+ """Output all test results to logger.info."""
logger.info("## All results ##\n")
for test, test_results in results.iteritems():
baseurl = "http://w3c-test.org/submissions"
@@ -599,6 +684,7 @@ def write_results(results, iterations, comment_pr):
def get_parser():
+ """Create and return script-specific argument parser."""
parser = argparse.ArgumentParser()
parser.add_argument("--root",
action="store",
@@ -630,6 +716,7 @@ def get_parser():
def main():
+ """Perform check_stability functionality and return exit code."""
global wpt_root
global wptrunner_root
« no previous file with comments | « third_party/WebKit/LayoutTests/external/wpt/README.md ('k') | third_party/WebKit/LayoutTests/external/wpt/ci_stability.sh » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698