diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 0000000..a2d7a71
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,7 @@
+[run]
+include:
+ errorlogparser.py
+ githubapiprovider.py
+ payloadhandler.py
+ payloadreceiver.py
+ travisapiprovider.py
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7aa59b9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+*.pyc
+
+.DS_Store
+.coverage
+htmlcov
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index d1ffe46..db4df32 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,4 +2,4 @@ language: python
python:
- "2.7"
script: python test.py
-sudo: false
+sudo: false
\ No newline at end of file
diff --git a/config.example b/config.example
new file mode 100644
index 0000000..70d1386
--- /dev/null
+++ b/config.example
@@ -0,0 +1,3 @@
+[github]
+user=UserNameHere
+token=SomeTokenHere
\ No newline at end of file
diff --git a/errorlogparser.py b/errorlogparser.py
new file mode 100644
index 0000000..ffaa655
--- /dev/null
+++ b/errorlogparser.py
@@ -0,0 +1,59 @@
+import re
+
+class ErrorLogParser():
+ def parse_log(self, log, error_re):
+ raise NotImplementedError
+
+class ServoErrorLogParser(ErrorLogParser):
+ path_key = 'path'
+ position_key = 'position'
+ body_key = 'body'
+
+ def parse_log(self, log):
+ error_re = "\\x1b\[94m(.+?)\\x1b\[0m:\\x1b\[93m(.+?)\\x1b\[0m:\s\\x1b\[91m(.+?)(?:\\x1b\[0m|$)"
+ cont_comment_re = "\t\\x1b\[\d{2}m(.+?)\\x1b\[0m"
+ # error_re = "File:\s(.+?)\sLine:\s(.+?)\sComment:\s(.+)"
+ # cont_comment_re = "(\t.+)"
+ matches = []
+ log_list = log.splitlines()
+
+ trimmed_log_list = self._trim_log(log_list, error_re)
+
+ for log_line in trimmed_log_list:
+ err_match = re.match(error_re, log_line)
+ if err_match:
+ matches.append(list(err_match.groups()))
+ else:
+ cont_comment_match = re.match(cont_comment_re, log_line)
+ if cont_comment_match:
+ matches[-1][-1] += "\n{}".format(list(cont_comment_match.groups())[0])
+
+ return self._process_errors(matches)
+
+
+ def _trim_log(self, log_list, error_re):
+ """
+ Cut off irrelevant details so cont_comment_re doesn't match something
+ that isn't an error comment
+ """
+ trimmed_log_list = log_list
+ err_match = None
+ i = 0
+
+ while not err_match and i < len(log_list):
+ err_match = re.match(error_re, log_list[i])
+ i += 1
+
+ if err_match:
+ trimmed_log_list = log_list[i - 1:]
+
+ return trimmed_log_list
+
+
+ def _process_errors(self, matches):
+ return (self._convert_match_to_dict(match) for match in matches)
+
+
+ def _convert_match_to_dict(self, match):
+ return {self.path_key: match[0], self.position_key: int(match[1]), self.body_key: match[2]}
+
\ No newline at end of file
diff --git a/githubapiprovider.py b/githubapiprovider.py
new file mode 100644
index 0000000..91880df
--- /dev/null
+++ b/githubapiprovider.py
@@ -0,0 +1,212 @@
+from StringIO import StringIO
+import base64
+import gzip
+try:
+ import simplejson as json
+except:
+ import json
+import urllib2
+
+
+class APIProvider:
+ def __init__(self, user):
+ self.user = user
+
+
+ def is_new_contributor(self, username):
+ raise NotImplementedError
+
+
+ def post_comment(self, body, issue):
+ raise NotImplementedError
+
+
+ def post_review_comment(self, pr_num, commit_id, path, pos, body):
+ raise NotImplementedError
+
+
+ def add_label(self, label, issue):
+ raise NotImplementedError
+
+
+ def remove_label(self, label, issue):
+ raise NotImplementedError
+
+
+ def get_labels(self, issue):
+ raise NotImplementedError
+
+
+ def get_diff(self, url):
+ raise NotImplementedError
+
+
+ def set_assignee(self, assignee, issue):
+ raise NotImplementedError
+
+
+class GithubApiProvider(APIProvider):
+ contributors_url = "https://api.github.com/repos/%s/%s/contributors?per_page=400"
+ post_comment_url = "https://api.github.com/repos/%s/%s/issues/%s/comments"
+ review_comment_url = "https://api.github.com/repos/%s/%s/pulls/%s/comments"
+ collaborators_url = "https://api.github.com/repos/%s/%s/collaborators"
+ issue_url = "https://api.github.com/repos/%s/%s/issues/%s"
+ get_label_url = "https://api.github.com/repos/%s/%s/issues/%s/labels"
+ add_label_url = "https://api.github.com/repos/%s/%s/issues/%s/labels"
+ remove_label_url = "https://api.github.com/repos/%s/%s/issues/%s/labels/%s"
+
+ def __init__(self, user, token, owner, repo):
+ APIProvider.__init__(self, user)
+ self.token = token
+
+ self.owner = owner
+ self.repo = repo
+
+ def api_req(self, method, url, data=None, media_type=None):
+ data = None if not data else json.dumps(data)
+ headers = {} if not data else {'Content-Type': 'application/json'}
+ req = urllib2.Request(url, data, headers)
+ req.get_method = lambda: method
+ if self.token:
+ base64string = base64.standard_b64encode('%s:%s' % (self.user, self.token)).replace('\n', '')
+ req.add_header("Authorization", "Basic %s" % base64string)
+
+ if media_type:
+ req.add_header("Accept", media_type)
+ f = urllib2.urlopen(req)
+ header = f.info()
+ if header.get('Content-Encoding') == 'gzip':
+ buf = StringIO(f.read())
+ f = gzip.GzipFile(fileobj=buf)
+
+ return { "header": header, "body": f.read() }
+
+
+ # This function is adapted from https://github.com/kennethreitz/requests/blob/209a871b638f85e2c61966f82e547377ed4260d9/requests/utils.py#L562
+ # Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ def parse_header_links(self, value):
+ if not value:
+ return None
+
+ links = {}
+ replace_chars = " '\""
+ for val in value.split(","):
+ try:
+ url, params = val.split(";", 1)
+ except ValueError:
+ url, params = val, ''
+
+ url = url.strip("<> '\"")
+
+ for param in params.split(";"):
+ try:
+ key, value = param.split("=")
+ except ValueError:
+ break
+ key = key.strip(replace_chars)
+ if key == 'rel':
+ links[value.strip(replace_chars)] = url
+
+ return links
+
+
+ def is_new_contributor(self, username):
+ url = self.contributors_url % (self.owner, self.repo)
+ # iterate through the pages to try and find the contributor
+ while True:
+ stats_raw = self.api_req("GET", url)
+ stats = json.loads(stats_raw['body'])
+ links = self.parse_header_links(stats_raw['header'].get('Link'))
+
+ for contributor in stats:
+ if contributor['login'] == username:
+ return False
+
+ if not links or 'next' not in links:
+ return True
+
+ url = links['next']
+
+
+ def post_comment(self, body, issue):
+ try:
+ result = self.api_req("POST", self.post_comment_url % (self.owner, self.repo, issue),
+ {"body": body})
+ except urllib2.HTTPError, e:
+ if e.code == 201:
+ pass
+ else:
+ raise e
+
+
+ def post_review_comment(self, pr_num, commit_id, path, pos, body):
+ try:
+ result = self.api_req("POST", self.review_comment_url % (self.owner, self.repo, pr_num),
+ {"body": body, "commit_id":commit_id, "path":path, "position":pos})
+ except urllib2.HTTPError, e:
+ if e.code == 201:
+ pass
+ else:
+ raise e
+
+
+ def get_review_comments(self, pr_num):
+ try:
+ result = self.api_req("GET", self.review_comment_url % (self.owner, self.repo, pr_num))
+
+ return json.loads(result['body'])
+ except urllib2.HTTPError, e:
+ if e.code == 201:
+ pass
+ else:
+ raise e
+
+
+ def add_label(self, label, issue):
+ try:
+ result = self.api_req("POST", self.add_label_url % (self.owner, self.repo, issue),
+ [label])
+ except urllib2.HTTPError, e:
+ if e.code == 201:
+ pass
+ else:
+ raise e
+
+
+ def remove_label(self, label, issue):
+ try:
+ result = self.api_req("DELETE", self.remove_label_url % (self.owner, self.repo, issue, label), {})
+ except urllib2.HTTPError, e:
+ #if e.code == 201:
+ # pass
+ #else:
+ # raise e
+ pass
+
+
+ def get_labels(self, issue):
+ try:
+ result = self.api_req("GET", self.get_label_url % (self.owner, self.repo, issue))
+ except urllib2.HTTPError, e:
+ if e.code == 201:
+ pass
+ else:
+ raise e
+ return map(lambda x: x["name"], json.loads(result['body']))
+
+
+ def get_diff(self, diff_url):
+ return self.api_req("GET", diff_url)['body']
+
+
+ def set_assignee(self, assignee, issue):
+ try:
+ result = self.api_req("PATCH", self.issue_url % (self.owner, self.repo, issue),
+ {"assignee": assignee})
+
+ return result['body']
+ except urllib2.HTTPError, e:
+ if e.code == 201:
+ pass
+ else:
+ raise e
\ No newline at end of file
diff --git a/newpr.py b/newpr.py
deleted file mode 100755
index 929cb6e..0000000
--- a/newpr.py
+++ /dev/null
@@ -1,323 +0,0 @@
-#!/usr/bin/env python
-
-import base64
-import urllib, urllib2
-import cgi
-import cgitb
-try:
- import simplejson as json
-except:
- import json
-import random
-import re
-import sys
-import ConfigParser
-from StringIO import StringIO
-import gzip
-
-class APIProvider:
- def __init__(self, payload, user):
- (owner, repo, issue) = extract_globals_from_payload(payload)
- self.owner = owner
- self.repo = repo
- self.issue = issue
- self.user = user
-
- def is_new_contributor(self, username):
- raise NotImplementedError
-
- def post_comment(self, body):
- raise NotImplementedError
-
- def add_label(self, label):
- raise NotImplementedError
-
- def remove_label(self, label):
- raise NotImplementedError
-
- def get_labels(self):
- raise NotImplementedError
-
- def get_diff(self):
- return NotImplementedError
-
- def set_assignee(self, assignee):
- raise NotImplementedError
-
-
-class GithubAPIProvider(APIProvider):
- contributors_url = "https://api.github.com/repos/%s/%s/contributors?per_page=400"
- post_comment_url = "https://api.github.com/repos/%s/%s/issues/%s/comments"
- collaborators_url = "https://api.github.com/repos/%s/%s/collaborators"
- issue_url = "https://api.github.com/repos/%s/%s/issues/%s"
- get_label_url = "https://api.github.com/repos/%s/%s/issues/%s/labels"
- add_label_url = "https://api.github.com/repos/%s/%s/issues/%s/labels"
- remove_label_url = "https://api.github.com/repos/%s/%s/issues/%s/labels/%s"
-
- def __init__(self, payload, user, token):
- APIProvider.__init__(self, payload, user)
- self.token = token
- if "pull_request" in payload:
- self.diff_url = payload["pull_request"]["diff_url"]
-
- def api_req(self, method, url, data=None, media_type=None):
- data = None if not data else json.dumps(data)
- headers = {} if not data else {'Content-Type': 'application/json'}
- req = urllib2.Request(url, data, headers)
- req.get_method = lambda: method
- if token:
- base64string = base64.standard_b64encode('%s:%s' % (self.user, self.token)).replace('\n', '')
- req.add_header("Authorization", "Basic %s" % base64string)
-
- if media_type:
- req.add_header("Accept", media_type)
- f = urllib2.urlopen(req)
- header = f.info()
- if header.get('Content-Encoding') == 'gzip':
- buf = StringIO(f.read())
- f = gzip.GzipFile(fileobj=buf)
- return { "header": header, "body": f.read() }
-
- # This function is adapted from https://github.com/kennethreitz/requests/blob/209a871b638f85e2c61966f82e547377ed4260d9/requests/utils.py#L562
- # Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
- def parse_header_links(self, value):
- if not value:
- return None
-
- links = {}
- replace_chars = " '\""
- for val in value.split(","):
- try:
- url, params = val.split(";", 1)
- except ValueError:
- url, params = val, ''
-
- url = url.strip("<> '\"")
-
- for param in params.split(";"):
- try:
- key, value = param.split("=")
- except ValueError:
- break
- key = key.strip(replace_chars)
- if key == 'rel':
- links[value.strip(replace_chars)] = url
-
- return links
-
- def is_new_contributor(self, username):
- url = self.contributors_url % (self.owner, self.repo)
- # iterate through the pages to try and find the contributor
- while True:
- stats_raw = self.api_req("GET", url)
- stats = json.loads(stats_raw['body'])
- links = self.parse_header_links(stats_raw['header'].get('Link'))
-
- for contributor in stats:
- if contributor['login'] == username:
- return False
-
- if not links or 'next' not in links:
- return True
- url = links['next']
-
- def post_comment(self, body):
- try:
- result = self.api_req("POST", self.post_comment_url % (self.owner, self.repo, self.issue),
- {"body": body})
- except urllib2.HTTPError, e:
- if e.code == 201:
- pass
- else:
- raise e
-
- def add_label(self, label):
- try:
- result = self.api_req("POST", self.add_label_url % (self.owner, self.repo, self.issue),
- [label])
- except urllib2.HTTPError, e:
- if e.code == 201:
- pass
- else:
- raise e
-
- def remove_label(self, label):
- try:
- result = self.api_req("DELETE", self.remove_label_url % (self.owner, self.repo, self.issue, label), {})
- except urllib2.HTTPError, e:
- #if e.code == 201:
- # pass
- #else:
- # raise e
- pass
-
- def get_labels(self):
- try:
- result = self.api_req("GET", self.get_label_url % (self.owner, self.repo, self.issue))
- except urllib2.HTTPError, e:
- if e.code == 201:
- pass
- else:
- raise e
- return map(lambda x: x["name"], json.loads(result['body']))
-
- def get_diff(self):
- return self.api_req("GET", self.diff_url)['body']
-
- def set_assignee(self, assignee):
- try:
- result = self.api_req("PATCH", self.issue_url % (self.owner, self.repo, self.issue),
- {"assignee": assignee})['body']
- except urllib2.HTTPError, e:
- if e.code == 201:
- pass
- else:
- raise e
-
-
-# If the user specified a reviewer, return the username, otherwise returns None.
-def find_reviewer(commit_msg):
- match = reviewer_re.search(commit_msg)
- if not match:
- return None
- return match.group(1)
-
-
-welcome_msg = "Thanks for the pull request, and welcome! The Servo team is excited to review your changes, and you should hear from @%s (or someone else) soon."
-warning_summary = '
**Warning**
\n\n%s'
-unsafe_warning_msg = 'These commits modify **unsafe code**. Please review it carefully!'
-reftest_required_msg = 'These commits modify layout code, but no reftests are modified. Please consider adding a reftest!'
-
-reviewer_re = re.compile("\\b[rR]\?[:\- ]*@([a-zA-Z0-9\-]+)")
-
-def extract_globals_from_payload(payload):
- if payload["action"] == "created":
- owner = payload['repository']['owner']['login']
- repo = payload['repository']['name']
- issue = str(payload['issue']['number'])
- else:
- owner = payload['pull_request']['base']['repo']['owner']['login']
- repo = payload['pull_request']['base']['repo']['name']
- issue = str(payload["number"])
- return (owner, repo, issue)
-
-
-def manage_pr_state(api, payload):
- labels = api.get_labels();
-
- if payload["action"] in ["synchronize", "opened"]:
- for label in ["S-awaiting-merge", "S-tests-failed", "S-needs-code-changes"]:
- if label in labels:
- api.remove_label(label)
- if not "S-awaiting-review" in labels:
- api.add_label("S-awaiting-review")
-
- # If mergeable is null, the data wasn't available yet. It would be nice to try to fetch that
- # information again.
- if payload["action"] == "synchronize" and payload['pull_request']['mergeable']:
- if "S-needs-rebase" in labels:
- api.remove_label("S-needs-rebase")
-
-
-def new_comment(api, payload):
- # We only care about comments in open PRs
- if payload['issue']['state'] != 'open' or 'pull_request' not in payload['issue']:
- return
-
- commenter = payload['comment']['user']['login']
- # Ignore our own comments.
- if commenter == api.user:
- return
-
- msg = payload["comment"]["body"]
- reviewer = find_reviewer(msg)
- if reviewer:
- api.set_assignee(reviewer)
-
- if commenter == 'bors-servo':
- labels = api.get_labels();
-
- if 'has been approved by' in msg or 'Testing commit' in msg:
- for label in ["S-awaiting-review", "S-needs-rebase", "S-tests-failed",
- "S-needs-code-changes", "S-needs-squash", "S-awaiting-answer"]:
- if label in labels:
- api.remove_label(label)
- if not "S-awaiting-merge" in labels:
- api.add_label("S-awaiting-merge")
-
- elif 'Test failed' in msg:
- api.remove_label("S-awaiting-merge")
- api.add_label("S-tests-failed")
-
- elif 'Please resolve the merge conflicts' in msg:
- api.remove_label("S-awaiting-merge")
- api.add_label("S-needs-rebase")
-
-
-def new_pr(api, payload):
- manage_pr_state(api, payload)
-
- author = payload["pull_request"]['user']['login']
- if api.is_new_contributor(author):
- #collaborators = json.load(urllib2.urlopen(collaborators_url))
- collaborators = ['jdm', 'larsbergstrom', 'metajack', 'mbrubeck', 'Ms2ger', 'Manishearth', 'glennw', 'pcwalton', 'SimonSapin'] if api.repo == 'servo' and api.owner == 'servo' else ['test_user_selection_ignore_this']
- random.seed()
- to_notify = random.choice(collaborators)
- api.post_comment(welcome_msg % to_notify)
-
- warn_unsafe = False
- layout_changed = False
- saw_reftest = False
- diff = api.get_diff()
- for line in diff.split('\n'):
- if line.startswith('+') and not line.startswith('+++') and line.find('unsafe') > -1:
- warn_unsafe = True
- if line.startswith('diff --git') and line.find('components/layout/') > -1:
- layout_changed = True
- if line.startswith('diff --git') and line.find('tests/ref') > -1:
- saw_reftest = True
- if line.startswith('diff --git') and line.find('tests/wpt') > -1:
- saw_reftest = True
-
- warnings = []
- if warn_unsafe:
- warnings += [unsafe_warning_msg]
-
- if layout_changed:
- if not saw_reftest:
- warnings += [reftest_required_msg]
-
- if warnings:
- api.post_comment(warning_summary % '\n'.join(map(lambda x: '* ' + x, warnings)))
-
-
-def update_pr(api, payload):
- manage_pr_state(api, payload)
-
-
-def handle_payload(api, payload):
- if payload["action"] == "opened":
- new_pr(api, payload)
- elif payload["action"] == "synchronize":
- update_pr(api, payload)
- elif payload["action"] == "created":
- new_comment(api, payload)
- else:
- pass
-
-if __name__ == "__main__":
- print "Content-Type: text/html;charset=utf-8"
- print
-
- cgitb.enable()
-
- config = ConfigParser.RawConfigParser()
- config.read('./config')
- user = config.get('github', 'user')
- token = config.get('github', 'token')
-
- post = cgi.FieldStorage()
- payload_raw = post.getfirst("payload",'')
- payload = json.loads(payload_raw)
-
- handle_payload(GithubAPIProvider(payload, user, token), payload)
diff --git a/payloadhandler.py b/payloadhandler.py
new file mode 100644
index 0000000..ad25c45
--- /dev/null
+++ b/payloadhandler.py
@@ -0,0 +1,199 @@
+import random
+import re
+from copy import copy
+from errorlogparser import ServoErrorLogParser
+
+
+class PayloadHandler():
+ def handle_payload(self, payload):
+ raise NotImplementedError
+
+
+class TravisPayloadHandler(PayloadHandler):
+ msg_template = "Please fix the following error:\n\n**File:** {}\n**Line Number:** {}\n**Error:** {}"
+
+ def __init__(self, github, travis, error_parser):
+ self.travis = travis
+ self.github = github
+ self.error_parser = error_parser
+
+ def handle_payload(self, payload):
+ commit_id = self._get_commit_id(payload)
+ build_data = self.travis.get_build(self._get_build_id(payload))
+ log = self.travis.get_log(build_data)
+ err_data = self.error_parser.parse_log(log)
+ pr_num = self.travis.get_pull_request_number(build_data)
+
+ existing_comments = [self._build_existing_comment_dict(comment) for comment in self.github.get_review_comments(pr_num)]
+ new_comments = [self._build_review_comment_dict(err_datum) for err_datum in err_data]
+
+ comments_to_post = self._delete_existing_comments(new_comments, existing_comments)
+
+ for comment in comments_to_post:
+ self.github.post_review_comment(pr_num, commit_id, comment[self.error_parser.body_key], comment[self.error_parser.path_key], comment[self.error_parser.position_key])
+
+ def _build_review_comment_dict(self, err_datum):
+ new_datum = err_datum.copy()
+ new_datum[self.error_parser.body_key] = self.msg_template.format(err_datum[self.error_parser.path_key], err_datum[self.error_parser.position_key], err_datum[self.error_parser.body_key])
+
+ return new_datum
+
+ def _build_existing_comment_dict(self, comment):
+ new_comment = {}
+
+ new_comment[self.error_parser.body_key] = comment[self.error_parser.body_key]
+ new_comment[self.error_parser.path_key] = comment[self.error_parser.path_key]
+ new_comment[self.error_parser.position_key] = comment[self.error_parser.position_key]
+
+ return new_comment
+
+ def _delete_existing_comments(self, new, existing):
+ new_copy = copy(new)
+ to_delete = []
+
+ for subject in reversed(new):
+ for comment in existing:
+ if subject[self.error_parser.body_key] == comment[self.error_parser.body_key] and \
+ subject[self.error_parser.path_key] == comment[self.error_parser.path_key] and \
+ subject[self.error_parser.position_key] == comment[self.error_parser.position_key]:
+
+ new_copy.remove(subject)
+ break
+
+ return new_copy
+
+
+ def _get_build_id(self, payload):
+ return int(payload["target_url"].split("/")[-1])
+
+ def _get_commit_id(self, payload):
+ return payload["commit"]["sha"]
+
+
+class GithubPayloadHandler(PayloadHandler):
+ welcome_msg = "Thanks for the pull request, and welcome! The Servo team is excited to review your changes, and you should hear from @%s (or someone else) soon."
+ warning_summary = '
**Warning**
\n\n%s'
+ unsafe_warning_msg = 'These commits modify **unsafe code**. Please review it carefully!'
+ reftest_required_msg = 'These commits modify layout code, but no reftests are modified. Please consider adding a reftest!'
+
+ def __init__(self, github):
+ self.github = github
+
+ def handle_payload(self, payload):
+ if payload["action"] == "created":
+ issue = str(payload['issue']['number'])
+ else:
+ issue = str(payload["number"])
+
+ if payload["action"] == "opened":
+ self.new_pr(issue, payload)
+ elif payload["action"] == "synchronize":
+ self.update_pr(issue, payload)
+ elif payload["action"] == "created":
+ self.new_comment(issue, payload)
+ else:
+ pass
+
+ def manage_pr_state(self, issue, payload):
+ labels = self.github.get_labels(issue);
+
+ if payload["action"] in ["synchronize", "opened"]:
+ for label in ["S-awaiting-merge", "S-tests-failed", "S-needs-code-changes"]:
+ if label in labels:
+ self.github.remove_label(label, issue)
+ if not "S-awaiting-review" in labels:
+ self.github.add_label("S-awaiting-review", issue)
+
+
+ # If mergeable is null, the data wasn't available yet. It would be nice to try to fetch that
+ # information again.
+ if payload["action"] == "synchronize" and payload['pull_request']['mergeable']:
+ if "S-needs-rebase" in labels:
+ self.github.remove_label("S-needs-rebase", issue)
+
+
+ def new_comment(self, issue, payload):
+ # We only care about comments in open PRs
+ if payload['issue']['state'] != 'open' or 'pull_request' not in payload['issue']:
+ return
+
+ commenter = payload['comment']['user']['login']
+ # Ignore our own comments.
+ if commenter == self.github.user:
+ return
+
+ msg = payload["comment"]["body"]
+ reviewer = self._find_reviewer(msg)
+ if reviewer:
+ self.github.set_assignee(reviewer, issue)
+
+ if commenter == 'bors-servo':
+ labels = self.github.get_labels(issue)
+
+ if 'has been approved by' in msg or 'Testing commit' in msg:
+ for label in ["S-awaiting-review", "S-needs-rebase", "S-tests-failed",
+ "S-needs-code-changes", "S-needs-squash", "S-awaiting-answer"]:
+ if label in labels:
+ self.github.remove_label(label, issue)
+ if not "S-awaiting-merge" in labels:
+ self.github.add_label("S-awaiting-merge")
+
+ elif 'Test failed' in msg:
+ self.github.remove_label("S-awaiting-merge", issue),
+ self.github.add_label("S-tests-failed", issue)
+
+ elif 'Please resolve the merge conflicts' in msg:
+ self.github.remove_label("S-awaiting-merge", issue)
+ self.github.add_label("S-needs-rebase", issue)
+
+
+ def new_pr(self, issue, payload):
+ self.manage_pr_state(issue, payload)
+
+ author = payload["pull_request"]['user']['login']
+ if self.github.is_new_contributor(author):
+ #collaborators = json.load(urllib2.urlopen(collaborators_url))
+ collaborators = ['jdm', 'larsbergstrom', 'metajack', 'mbrubeck', 'Ms2ger', 'Manishearth', 'glennw', 'pcwalton', 'SimonSapin'] if self.github.repo == 'servo' and self.github.owner == 'servo' else ['test_user_selection_ignore_this']
+ random.seed()
+ to_notify = random.choice(collaborators)
+ self.github.post_comment(self.welcome_msg % to_notify, issue)
+
+ warn_unsafe = False
+ layout_changed = False
+ saw_reftest = False
+ diff = self.github.get_diff(payload["pull_request"]["diff_url"])
+ for line in diff.split('\n'):
+ if line.startswith('+') and not line.startswith('+++') and line.find('unsafe') > -1:
+ warn_unsafe = True
+ if line.startswith('diff --git') and line.find('components/layout/') > -1:
+ layout_changed = True
+ if line.startswith('diff --git') and line.find('tests/ref') > -1:
+ saw_reftest = True
+ if line.startswith('diff --git') and line.find('tests/wpt') > -1:
+ saw_reftest = True
+
+ warnings = []
+ if warn_unsafe:
+ warnings += [self.unsafe_warning_msg]
+
+ if layout_changed:
+ if not saw_reftest:
+ warnings += [self.reftest_required_msg]
+
+ if warnings:
+ self.github.post_comment(self.warning_summary % '\n'.join(map(lambda x: '* ' + x, warnings)), issue)
+
+
+ def update_pr(self, issue, payload):
+ self.manage_pr_state(issue, payload)
+
+
+ # If the user specified a reviewer, return the username, otherwise returns None.
+ def _find_reviewer(self, commit_msg):
+ reviewer_re = re.compile("\\b[rR]\?[:\- ]*@([a-zA-Z0-9\-]+)")
+ match = reviewer_re.search(commit_msg)
+ if not match:
+ return None
+
+ return match.group(1)
+
diff --git a/payloadreceiver.py b/payloadreceiver.py
new file mode 100755
index 0000000..2ca979a
--- /dev/null
+++ b/payloadreceiver.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+
+from payloadhandler import TravisPayloadHandler, GithubPayloadHandler
+from errorlogparser import ServoErrorLogParser
+from githubapiprovider import GithubApiProvider
+from travisciapiprovider import TravisCiApiProvider
+import cgi, cgitb
+import ConfigParser
+
+def extract_globals_from_payload(payload):
+ if "action" in payload:
+ owner, repo = _extract_globals_from_github_payload(payload)
+ elif "state" in payload:
+ owner, repo = _extract_globals_from_travis_payload(payload)
+
+ return owner, repo
+
+def _extract_globals_from_github_payload(payload):
+ if payload["action"] == "created":
+ owner = payload['repository']['owner']['login']
+ repo = payload['repository']['name']
+ else:
+ owner = payload['pull_request']['base']['repo']['owner']['login']
+ repo = payload['pull_request']['base']['repo']['name']
+
+ return owner, repo
+
+def _extract_globals_from_travis_payload(payload):
+ return payload['name'].split('/')
+
+if __name__ == "__main__":
+ print "Content-Type: text/html;charset=utf-8"
+ print
+
+ cgitb.enable()
+
+ config = ConfigParser.RawConfigParser()
+ config.read('./config')
+ user = config.get('github', 'user')
+ token = config.get('github', 'token')
+
+ post = cgi.FieldStorage()
+ payload_raw = post.getfirst("payload",'')
+ payload = json.loads(payload_raw)
+
+ owner, repo = extract_globals_from_payload(payload)
+ github = GithubApiProvider(user, token, owner, repo)
+
+ if "action" in payload:
+ payload_handler = GithubPayloadHandler(github)
+ elif "state" in payload:
+ travis = TravisCiApiProvider()
+ error_parser = ServoErrorLogParser()
+ payload_handler = TravisPayloadHandler(github, travis, error_parser)
+ else:
+ pass
+
+ if payload_handler:
+ payload_handler.handle_payload(payload)
\ No newline at end of file
diff --git a/resources/expected_errors.json b/resources/expected_errors.json
new file mode 100644
index 0000000..c792a94
--- /dev/null
+++ b/resources/expected_errors.json
@@ -0,0 +1,32 @@
+[
+ {
+ "comment": "use statement is not in alphabetical order\n\texpected: dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull\n\tfound: dom::bindings::conversions::get_dom_class",
+ "line": "7",
+ "file": "./components/script/dom/eventtarget.rs"
+ },
+ {
+ "comment": "use statement is not in alphabetical order\n\texpected: dom::bindings::codegen::Bindings::EventListenerBinding::EventListener\n\tfound: dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull",
+ "line": "8",
+ "file": "./components/script/dom/eventtarget.rs"
+ },
+ {
+ "comment": "use statement is not in alphabetical order\n\texpected: dom::bindings::codegen::Bindings::EventTargetBinding::EventTargetMethods\n\tfound: dom::bindings::codegen::Bindings::EventListenerBinding::EventListener",
+ "line": "9",
+ "file": "./components/script/dom/eventtarget.rs"
+ },
+ {
+ "comment": "use statement is not in alphabetical order\n\texpected: dom::bindings::conversions::get_dom_class\n\tfound: dom::bindings::codegen::Bindings::EventTargetBinding::EventTargetMethods",
+ "line": "10",
+ "file": "./components/script/dom/eventtarget.rs"
+ },
+ {
+ "comment": "use statement is not in alphabetical order\n\texpected: dom::browsercontext\n\tfound: dom::eventtarget::EventTargetTypeId",
+ "line": "17",
+ "file": "./components/script/dom/bindings/utils.rs"
+ },
+ {
+ "comment": "use statement is not in alphabetical order\n\texpected: dom::eventtarget::EventTargetTypeId\n\tfound: dom::browsercontext",
+ "line": "18",
+ "file": "./components/script/dom/bindings/utils.rs"
+ }
+]
\ No newline at end of file
diff --git a/resources/multi-line-comment.log b/resources/multi-line-comment.log
new file mode 100644
index 0000000..eaac944
--- /dev/null
+++ b/resources/multi-line-comment.log
@@ -0,0 +1,767 @@
+Using worker: worker-linux-docker-176118fe.prod.travis-ci.org:travis-linux-8
+
+travis_fold:start:system_info
+[0K[33;1mBuild system information[0m
+Build language: python
+[34m[1mBuild image provisioning date and time[0m
+Thu Feb 5 15:09:33 UTC 2015
+[34m[1mOperating System Details[0m
+Distributor ID: Ubuntu
+Description: Ubuntu 12.04.5 LTS
+Release: 12.04
+Codename: precise
+[34m[1mLinux Version[0m
+3.13.0-29-generic
+[34m[1mCookbooks Version[0m
+a68419e https://github.com/travis-ci/travis-cookbooks/tree/a68419e
+[34m[1mGCC version[0m
+gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
+Copyright (C) 2011 Free Software Foundation, Inc.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+[34m[1mLLVM version[0m
+clang version 3.4 (tags/RELEASE_34/final)
+Target: x86_64-unknown-linux-gnu
+Thread model: posix
+[34m[1mPre-installed Ruby versions[0m
+ruby-1.9.3-p551
+[34m[1mPre-installed Node.js versions[0m
+v0.10.36
+[34m[1mPre-installed Go versions[0m
+1.4.1
+[34m[1mRedis version[0m
+redis-server 2.8.19
+[34m[1mriak version[0m
+2.0.2
+[34m[1mMongoDB version[0m
+MongoDB 2.4.12
+[34m[1mCouchDB version[0m
+couchdb 1.6.1
+[34m[1mNeo4j version[0m
+1.9.4
+[34m[1mRabbitMQ Version[0m
+3.4.3
+[34m[1mElasticSearch version[0m
+1.4.0
+[34m[1mInstalled Sphinx versions[0m
+2.0.10
+2.1.9
+2.2.6
+[34m[1mDefault Sphinx version[0m
+2.2.6
+[34m[1mInstalled Firefox version[0m
+firefox 31.0esr
+[34m[1mPhantomJS version[0m
+1.9.8
+[34m[1mant -version[0m
+Apache Ant(TM) version 1.8.2 compiled on December 3 2011
+[34m[1mmvn -version[0m
+Apache Maven 3.2.5 (12a6b3acb947671f09b81f49094c53f426d8cea1; 2014-12-14T17:29:23+00:00)
+Maven home: /usr/local/maven
+Java version: 1.7.0_76, vendor: Oracle Corporation
+Java home: /usr/lib/jvm/java-7-oracle/jre
+Default locale: en_US, platform encoding: ANSI_X3.4-1968
+OS name: "linux", version: "3.13.0-29-generic", arch: "amd64", family: "unix"
+travis_fold:end:system_info
+[0K
+travis_fold:start:git.checkout
+[0Ktravis_time:start:16daecd0
+[0K$ git clone --depth=50 https://github.com/servo/servo.git servo/servo
+Cloning into 'servo/servo'...
+remote: Counting objects: 101276, done.[K
+remote: Compressing objects: 0% (1/37782) [K
+remote: Compressing objects: 1% (378/37782) [K
+remote: Compressing objects: 2% (756/37782) [K
+remote: Compressing objects: 3% (1134/37782) [K
+remote: Compressing objects: 4% (1512/37782) [K
+remote: Compressing objects: 5% (1890/37782) [K
+remote: Compressing objects: 6% (2267/37782) [K
+remote: Compressing objects: 7% (2645/37782) [K
+remote: Compressing objects: 8% (3023/37782) [K
+remote: Compressing objects: 9% (3401/37782) [K
+remote: Compressing objects: 10% (3779/37782) [K
+remote: Compressing objects: 11% (4157/37782) [K
+remote: Compressing objects: 12% (4534/37782) [K
+remote: Compressing objects: 13% (4912/37782) [K
+remote: Compressing objects: 14% (5290/37782) [K
+remote: Compressing objects: 15% (5668/37782) [K
+remote: Compressing objects: 16% (6046/37782) [K
+remote: Compressing objects: 17% (6423/37782) [K
+remote: Compressing objects: 18% (6801/37782) [K
+remote: Compressing objects: 19% (7179/37782) [K
+remote: Compressing objects: 20% (7557/37782) [K
+remote: Compressing objects: 21% (7935/37782) [K
+remote: Compressing objects: 22% (8313/37782) [K
+remote: Compressing objects: 23% (8690/37782) [K
+remote: Compressing objects: 24% (9068/37782) [K
+remote: Compressing objects: 25% (9446/37782) [K
+remote: Compressing objects: 26% (9824/37782) [K
+remote: Compressing objects: 27% (10202/37782) [K
+remote: Compressing objects: 28% (10579/37782) [K
+remote: Compressing objects: 29% (10957/37782) [K
+remote: Compressing objects: 30% (11335/37782) [K
+remote: Compressing objects: 31% (11713/37782) [K
+remote: Compressing objects: 32% (12091/37782) [K
+remote: Compressing objects: 33% (12469/37782) [K
+remote: Compressing objects: 34% (12846/37782) [K
+remote: Compressing objects: 35% (13224/37782) [K
+remote: Compressing objects: 36% (13602/37782) [K
+remote: Compressing objects: 37% (13980/37782) [K
+remote: Compressing objects: 38% (14358/37782) [K
+remote: Compressing objects: 39% (14735/37782) [K
+remote: Compressing objects: 40% (15113/37782) [K
+remote: Compressing objects: 41% (15491/37782) [K
+remote: Compressing objects: 42% (15869/37782) [K
+remote: Compressing objects: 43% (16247/37782) [K
+remote: Compressing objects: 44% (16625/37782) [K
+remote: Compressing objects: 45% (17002/37782) [K
+remote: Compressing objects: 46% (17380/37782) [K
+remote: Compressing objects: 47% (17758/37782) [K
+remote: Compressing objects: 48% (18136/37782) [K
+remote: Compressing objects: 49% (18514/37782) [K
+remote: Compressing objects: 50% (18891/37782) [K
+remote: Compressing objects: 51% (19269/37782) [K
+remote: Compressing objects: 52% (19647/37782) [K
+remote: Compressing objects: 53% (20025/37782) [K
+remote: Compressing objects: 54% (20403/37782) [K
+remote: Compressing objects: 55% (20781/37782) [K
+remote: Compressing objects: 56% (21158/37782) [K
+remote: Compressing objects: 57% (21536/37782) [K
+remote: Compressing objects: 58% (21914/37782) [K
+remote: Compressing objects: 59% (22292/37782) [K
+remote: Compressing objects: 60% (22670/37782) [K
+remote: Compressing objects: 61% (23048/37782) [K
+remote: Compressing objects: 62% (23425/37782) [K
+remote: Compressing objects: 63% (23803/37782) [K
+remote: Compressing objects: 64% (24181/37782) [K
+remote: Compressing objects: 64% (24498/37782) [K
+remote: Compressing objects: 65% (24559/37782) [K
+remote: Compressing objects: 66% (24937/37782) [K
+remote: Compressing objects: 67% (25314/37782) [K
+remote: Compressing objects: 68% (25692/37782) [K
+remote: Compressing objects: 69% (26070/37782) [K
+remote: Compressing objects: 70% (26448/37782) [K
+remote: Compressing objects: 71% (26826/37782) [K
+remote: Compressing objects: 72% (27204/37782) [K
+remote: Compressing objects: 73% (27581/37782) [K
+remote: Compressing objects: 74% (27959/37782) [K
+remote: Compressing objects: 75% (28337/37782) [K
+remote: Compressing objects: 76% (28715/37782) [K
+remote: Compressing objects: 77% (29093/37782) [K
+remote: Compressing objects: 78% (29470/37782) [K
+remote: Compressing objects: 79% (29848/37782) [K
+remote: Compressing objects: 80% (30226/37782) [K
+remote: Compressing objects: 81% (30604/37782) [K
+remote: Compressing objects: 82% (30982/37782) [K
+remote: Compressing objects: 83% (31360/37782) [K
+remote: Compressing objects: 84% (31737/37782) [K
+remote: Compressing objects: 85% (32115/37782) [K
+remote: Compressing objects: 86% (32493/37782) [K
+remote: Compressing objects: 87% (32871/37782) [K
+remote: Compressing objects: 88% (33249/37782) [K
+remote: Compressing objects: 89% (33626/37782) [K
+remote: Compressing objects: 90% (34004/37782) [K
+remote: Compressing objects: 91% (34382/37782) [K
+remote: Compressing objects: 92% (34760/37782) [K
+remote: Compressing objects: 93% (35138/37782) [K
+remote: Compressing objects: 94% (35516/37782) [K
+remote: Compressing objects: 95% (35893/37782) [K
+remote: Compressing objects: 96% (36271/37782) [K
+remote: Compressing objects: 97% (36649/37782) [K
+remote: Compressing objects: 98% (37027/37782) [K
+remote: Compressing objects: 99% (37405/37782) [K
+remote: Compressing objects: 100% (37782/37782) [K
+remote: Compressing objects: 100% (37782/37782), done.[K
+Receiving objects: 0% (1/101276)
+Receiving objects: 1% (1013/101276)
+Receiving objects: 2% (2026/101276)
+Receiving objects: 3% (3039/101276)
+Receiving objects: 4% (4052/101276)
+Receiving objects: 5% (5064/101276)
+Receiving objects: 6% (6077/101276)
+Receiving objects: 7% (7090/101276)
+Receiving objects: 8% (8103/101276)
+Receiving objects: 9% (9115/101276)
+Receiving objects: 10% (10128/101276)
+Receiving objects: 11% (11141/101276)
+Receiving objects: 12% (12154/101276)
+Receiving objects: 13% (13166/101276)
+Receiving objects: 14% (14179/101276)
+Receiving objects: 15% (15192/101276)
+Receiving objects: 16% (16205/101276), 16.13 MiB | 32.19 MiB/s
+Receiving objects: 17% (17217/101276), 16.13 MiB | 32.19 MiB/s
+Receiving objects: 18% (18230/101276), 16.13 MiB | 32.19 MiB/s
+Receiving objects: 19% (19243/101276), 16.13 MiB | 32.19 MiB/s
+Receiving objects: 20% (20256/101276), 16.13 MiB | 32.19 MiB/s
+Receiving objects: 21% (21268/101276), 16.13 MiB | 32.19 MiB/s
+Receiving objects: 22% (22281/101276), 16.13 MiB | 32.19 MiB/s
+Receiving objects: 23% (23294/101276), 16.13 MiB | 32.19 MiB/s
+Receiving objects: 24% (24307/101276), 16.13 MiB | 32.19 MiB/s
+Receiving objects: 25% (25319/101276), 16.13 MiB | 32.19 MiB/s
+Receiving objects: 26% (26332/101276), 16.13 MiB | 32.19 MiB/s
+Receiving objects: 27% (27345/101276), 16.13 MiB | 32.19 MiB/s
+Receiving objects: 28% (28358/101276), 16.13 MiB | 32.19 MiB/s
+Receiving objects: 29% (29371/101276), 16.13 MiB | 32.19 MiB/s
+Receiving objects: 30% (30383/101276), 16.13 MiB | 32.19 MiB/s
+Receiving objects: 31% (31396/101276), 16.13 MiB | 32.19 MiB/s
+Receiving objects: 32% (32409/101276), 16.13 MiB | 32.19 MiB/s
+Receiving objects: 33% (33422/101276), 16.13 MiB | 32.19 MiB/s
+Receiving objects: 34% (34434/101276), 16.13 MiB | 32.19 MiB/s
+Receiving objects: 35% (35447/101276), 16.13 MiB | 32.19 MiB/s
+Receiving objects: 36% (36460/101276), 16.13 MiB | 32.19 MiB/s
+Receiving objects: 37% (37473/101276), 16.13 MiB | 32.19 MiB/s
+Receiving objects: 38% (38485/101276), 16.13 MiB | 32.19 MiB/s
+Receiving objects: 39% (39498/101276), 16.13 MiB | 32.19 MiB/s
+Receiving objects: 40% (40511/101276), 16.13 MiB | 32.19 MiB/s
+Receiving objects: 40% (41312/101276), 16.13 MiB | 32.19 MiB/s
+Receiving objects: 41% (41524/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 42% (42536/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 43% (43549/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 44% (44562/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 45% (45575/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 46% (46587/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 47% (47600/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 48% (48613/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 49% (49626/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 50% (50638/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 51% (51651/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 52% (52664/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 53% (53677/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 54% (54690/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 55% (55702/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 56% (56715/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 57% (57728/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 58% (58741/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 59% (59753/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 60% (60766/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 61% (61779/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 62% (62792/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 63% (63804/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 64% (64817/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 65% (65830/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 66% (66843/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 67% (67855/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 68% (68868/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 69% (69881/101276), 34.46 MiB | 34.39 MiB/s
+Receiving objects: 70% (70894/101276), 46.09 MiB | 30.67 MiB/s
+Receiving objects: 71% (71906/101276), 46.09 MiB | 30.67 MiB/s
+Receiving objects: 72% (72919/101276), 46.09 MiB | 30.67 MiB/s
+Receiving objects: 73% (73932/101276), 46.09 MiB | 30.67 MiB/s
+Receiving objects: 74% (74945/101276), 46.09 MiB | 30.67 MiB/s
+Receiving objects: 75% (75957/101276), 46.09 MiB | 30.67 MiB/s
+Receiving objects: 76% (76970/101276), 46.09 MiB | 30.67 MiB/s
+Receiving objects: 77% (77983/101276), 46.09 MiB | 30.67 MiB/s
+Receiving objects: 78% (78996/101276), 46.09 MiB | 30.67 MiB/s
+Receiving objects: 79% (80009/101276), 46.09 MiB | 30.67 MiB/s
+Receiving objects: 80% (81021/101276), 46.09 MiB | 30.67 MiB/s
+Receiving objects: 81% (82034/101276), 46.09 MiB | 30.67 MiB/s
+Receiving objects: 82% (83047/101276), 46.09 MiB | 30.67 MiB/s
+Receiving objects: 83% (84060/101276), 46.09 MiB | 30.67 MiB/s
+Receiving objects: 84% (85072/101276), 46.09 MiB | 30.67 MiB/s
+Receiving objects: 85% (86085/101276), 46.09 MiB | 30.67 MiB/s
+Receiving objects: 86% (87098/101276), 46.09 MiB | 30.67 MiB/s
+Receiving objects: 87% (88111/101276), 46.09 MiB | 30.67 MiB/s
+Receiving objects: 87% (88482/101276), 46.09 MiB | 30.67 MiB/s
+Receiving objects: 88% (89123/101276), 60.51 MiB | 30.19 MiB/s
+Receiving objects: 89% (90136/101276), 60.51 MiB | 30.19 MiB/s
+Receiving objects: 90% (91149/101276), 60.51 MiB | 30.19 MiB/s
+Receiving objects: 91% (92162/101276), 60.51 MiB | 30.19 MiB/s
+Receiving objects: 92% (93174/101276), 60.51 MiB | 30.19 MiB/s
+Receiving objects: 93% (94187/101276), 60.51 MiB | 30.19 MiB/s
+Receiving objects: 94% (95200/101276), 60.51 MiB | 30.19 MiB/s
+Receiving objects: 95% (96213/101276), 60.51 MiB | 30.19 MiB/s
+Receiving objects: 96% (97225/101276), 60.51 MiB | 30.19 MiB/s
+Receiving objects: 97% (98238/101276), 60.51 MiB | 30.19 MiB/s
+Receiving objects: 98% (99251/101276), 60.51 MiB | 30.19 MiB/s
+Receiving objects: 99% (100264/101276), 60.51 MiB | 30.19 MiB/s
+remote: Total 101276 (delta 64964), reused 97481 (delta 62116), pack-reused 0[K
+Receiving objects: 100% (101276/101276), 60.51 MiB | 30.19 MiB/s
+Receiving objects: 100% (101276/101276), 71.83 MiB | 30.19 MiB/s, done.
+Resolving deltas: 0% (0/64964)
+Resolving deltas: 1% (719/64964)
+Resolving deltas: 2% (1326/64964)
+Resolving deltas: 3% (1970/64964)
+Resolving deltas: 4% (2608/64964)
+Resolving deltas: 5% (3250/64964)
+Resolving deltas: 6% (3921/64964)
+Resolving deltas: 7% (4548/64964)
+Resolving deltas: 8% (5234/64964)
+Resolving deltas: 9% (5847/64964)
+Resolving deltas: 10% (6558/64964)
+Resolving deltas: 11% (7160/64964)
+Resolving deltas: 12% (7800/64964)
+Resolving deltas: 13% (8446/64964)
+Resolving deltas: 14% (9106/64964)
+Resolving deltas: 15% (9747/64964)
+Resolving deltas: 16% (10412/64964)
+Resolving deltas: 17% (11044/64964)
+Resolving deltas: 18% (11731/64964)
+Resolving deltas: 19% (12381/64964)
+Resolving deltas: 20% (13002/64964)
+Resolving deltas: 21% (13644/64964)
+Resolving deltas: 22% (14312/64964)
+Resolving deltas: 23% (14952/64964)
+Resolving deltas: 24% (15607/64964)
+Resolving deltas: 25% (16304/64964)
+Resolving deltas: 26% (16894/64964)
+Resolving deltas: 27% (17541/64964)
+Resolving deltas: 28% (18202/64964)
+Resolving deltas: 29% (18841/64964)
+Resolving deltas: 30% (19496/64964)
+Resolving deltas: 31% (20144/64964)
+Resolving deltas: 32% (20799/64964)
+Resolving deltas: 33% (21703/64964)
+Resolving deltas: 34% (22121/64964)
+Resolving deltas: 35% (22907/64964)
+Resolving deltas: 36% (23425/64964)
+Resolving deltas: 37% (24146/64964)
+Resolving deltas: 38% (24798/64964)
+Resolving deltas: 39% (25511/64964)
+Resolving deltas: 40% (25992/64964)
+Resolving deltas: 41% (26691/64964)
+Resolving deltas: 42% (27305/64964)
+Resolving deltas: 43% (28051/64964)
+Resolving deltas: 44% (28654/64964)
+Resolving deltas: 45% (29257/64964)
+Resolving deltas: 46% (29895/64964)
+Resolving deltas: 47% (30534/64964)
+Resolving deltas: 48% (31201/64964)
+Resolving deltas: 49% (31852/64964)
+Resolving deltas: 50% (32496/64964)
+Resolving deltas: 51% (33137/64964)
+Resolving deltas: 52% (33782/64964)
+Resolving deltas: 53% (34442/64964)
+Resolving deltas: 54% (35081/64964)
+Resolving deltas: 55% (35731/64964)
+Resolving deltas: 56% (36380/64964)
+Resolving deltas: 57% (37030/64964)
+Resolving deltas: 58% (37686/64964)
+Resolving deltas: 59% (38377/64964)
+Resolving deltas: 60% (38980/64964)
+Resolving deltas: 61% (39712/64964)
+Resolving deltas: 62% (40287/64964)
+Resolving deltas: 63% (40929/64964)
+Resolving deltas: 63% (41089/64964)
+Resolving deltas: 64% (41590/64964)
+Resolving deltas: 65% (42231/64964)
+Resolving deltas: 66% (42880/64964)
+Resolving deltas: 67% (43528/64964)
+Resolving deltas: 68% (44176/64964)
+Resolving deltas: 69% (44828/64964)
+Resolving deltas: 70% (45531/64964)
+Resolving deltas: 71% (46132/64964)
+Resolving deltas: 72% (46805/64964)
+Resolving deltas: 73% (47479/64964)
+Resolving deltas: 74% (48074/64964)
+Resolving deltas: 75% (48740/64964)
+Resolving deltas: 76% (49450/64964)
+Resolving deltas: 77% (50030/64964)
+Resolving deltas: 78% (50672/64964)
+Resolving deltas: 79% (51327/64964)
+Resolving deltas: 80% (51972/64964)
+Resolving deltas: 81% (52621/64964)
+Resolving deltas: 82% (53276/64964)
+Resolving deltas: 83% (53921/64964)
+Resolving deltas: 84% (54589/64964)
+Resolving deltas: 85% (55243/64964)
+Resolving deltas: 86% (55871/64964)
+Resolving deltas: 87% (56520/64964)
+Resolving deltas: 88% (57360/64964)
+Resolving deltas: 89% (57822/64964)
+Resolving deltas: 90% (58468/64964)
+Resolving deltas: 91% (59120/64964)
+Resolving deltas: 92% (59768/64964)
+Resolving deltas: 93% (60422/64964)
+Resolving deltas: 94% (61069/64964)
+Resolving deltas: 95% (61721/64964)
+Resolving deltas: 96% (62679/64964)
+Resolving deltas: 97% (63035/64964)
+Resolving deltas: 98% (63671/64964)
+Resolving deltas: 99% (64317/64964)
+Resolving deltas: 100% (64964/64964)
+Resolving deltas: 100% (64964/64964), done.
+Checking connectivity... done.
+Checking out files: 15% (17972/118796)
+Checking out files: 16% (19008/118796)
+Checking out files: 17% (20196/118796)
+Checking out files: 18% (21384/118796)
+Checking out files: 19% (22572/118796)
+Checking out files: 20% (23760/118796)
+Checking out files: 21% (24948/118796)
+Checking out files: 22% (26136/118796)
+Checking out files: 23% (27324/118796)
+Checking out files: 24% (28512/118796)
+Checking out files: 25% (29699/118796)
+Checking out files: 26% (30887/118796)
+Checking out files: 27% (32075/118796)
+Checking out files: 28% (33263/118796)
+Checking out files: 29% (34451/118796)
+Checking out files: 29% (34953/118796)
+Checking out files: 30% (35639/118796)
+Checking out files: 31% (36827/118796)
+Checking out files: 32% (38015/118796)
+Checking out files: 33% (39203/118796)
+Checking out files: 34% (40391/118796)
+Checking out files: 35% (41579/118796)
+Checking out files: 36% (42767/118796)
+Checking out files: 37% (43955/118796)
+Checking out files: 38% (45143/118796)
+Checking out files: 39% (46331/118796)
+Checking out files: 40% (47519/118796)
+Checking out files: 41% (48707/118796)
+Checking out files: 42% (49895/118796)
+Checking out files: 42% (50145/118796)
+Checking out files: 43% (51083/118796)
+Checking out files: 44% (52271/118796)
+Checking out files: 45% (53459/118796)
+Checking out files: 46% (54647/118796)
+Checking out files: 47% (55835/118796)
+Checking out files: 48% (57023/118796)
+Checking out files: 49% (58211/118796)
+Checking out files: 50% (59398/118796)
+Checking out files: 51% (60586/118796)
+Checking out files: 52% (61774/118796)
+Checking out files: 53% (62962/118796)
+Checking out files: 54% (64150/118796)
+Checking out files: 55% (65338/118796)
+Checking out files: 56% (66526/118796)
+Checking out files: 56% (66983/118796)
+Checking out files: 57% (67714/118796)
+Checking out files: 58% (68902/118796)
+Checking out files: 59% (70090/118796)
+Checking out files: 60% (71278/118796)
+Checking out files: 61% (72466/118796)
+Checking out files: 62% (73654/118796)
+Checking out files: 63% (74842/118796)
+Checking out files: 64% (76030/118796)
+Checking out files: 65% (77218/118796)
+Checking out files: 66% (78406/118796)
+Checking out files: 67% (79594/118796)
+Checking out files: 68% (80782/118796)
+Checking out files: 68% (81956/118796)
+Checking out files: 69% (81970/118796)
+Checking out files: 70% (83158/118796)
+Checking out files: 71% (84346/118796)
+Checking out files: 72% (85534/118796)
+Checking out files: 73% (86722/118796)
+Checking out files: 74% (87910/118796)
+Checking out files: 75% (89097/118796)
+Checking out files: 76% (90285/118796)
+Checking out files: 77% (91473/118796)
+Checking out files: 78% (92661/118796)
+Checking out files: 79% (93849/118796)
+Checking out files: 80% (95037/118796)
+Checking out files: 81% (96225/118796)
+Checking out files: 82% (97413/118796)
+Checking out files: 82% (97614/118796)
+Checking out files: 83% (98601/118796)
+Checking out files: 84% (99789/118796)
+Checking out files: 85% (100977/118796)
+Checking out files: 86% (102165/118796)
+Checking out files: 87% (103353/118796)
+Checking out files: 88% (104541/118796)
+Checking out files: 89% (105729/118796)
+Checking out files: 90% (106917/118796)
+Checking out files: 91% (108105/118796)
+Checking out files: 92% (109293/118796)
+Checking out files: 93% (110481/118796)
+Checking out files: 94% (111669/118796)
+Checking out files: 95% (112857/118796)
+Checking out files: 95% (113057/118796)
+Checking out files: 96% (114045/118796)
+Checking out files: 97% (115233/118796)
+Checking out files: 98% (116421/118796)
+Checking out files: 99% (117609/118796)
+Checking out files: 100% (118796/118796)
+Checking out files: 100% (118796/118796), done.
+travis_time:end:16daecd0:start=1442012004129913797,finish=1442012018866609279,duration=14736695482
+[0K$ cd servo/servo
+travis_time:start:04bda51d
+[0K$ git fetch origin +refs/pull/7606/merge:
+remote: Counting objects: 61, done.[K
+remote: Compressing objects: 4% (1/22) [K
+remote: Compressing objects: 9% (2/22) [K
+remote: Compressing objects: 13% (3/22) [K
+remote: Compressing objects: 18% (4/22) [K
+remote: Compressing objects: 22% (5/22) [K
+remote: Compressing objects: 27% (6/22) [K
+remote: Compressing objects: 31% (7/22) [K
+remote: Compressing objects: 36% (8/22) [K
+remote: Compressing objects: 40% (9/22) [K
+remote: Compressing objects: 45% (10/22) [K
+remote: Compressing objects: 50% (11/22) [K
+remote: Compressing objects: 54% (12/22) [K
+remote: Compressing objects: 59% (13/22) [K
+remote: Compressing objects: 63% (14/22) [K
+remote: Compressing objects: 68% (15/22) [K
+remote: Compressing objects: 72% (16/22) [K
+remote: Compressing objects: 77% (17/22) [K
+remote: Compressing objects: 81% (18/22) [K
+remote: Compressing objects: 86% (19/22) [K
+remote: Compressing objects: 90% (20/22) [K
+remote: Compressing objects: 95% (21/22) [K
+remote: Compressing objects: 100% (22/22) [K
+remote: Compressing objects: 100% (22/22), done.[K
+remote: Total 61 (delta 55), reused 44 (delta 39), pack-reused 0[K
+Unpacking objects: 1% (1/61)
+Unpacking objects: 3% (2/61)
+Unpacking objects: 4% (3/61)
+Unpacking objects: 6% (4/61)
+Unpacking objects: 8% (5/61)
+Unpacking objects: 9% (6/61)
+Unpacking objects: 11% (7/61)
+Unpacking objects: 13% (8/61)
+Unpacking objects: 14% (9/61)
+Unpacking objects: 16% (10/61)
+Unpacking objects: 18% (11/61)
+Unpacking objects: 19% (12/61)
+Unpacking objects: 21% (13/61)
+Unpacking objects: 22% (14/61)
+Unpacking objects: 24% (15/61)
+Unpacking objects: 26% (16/61)
+Unpacking objects: 27% (17/61)
+Unpacking objects: 29% (18/61)
+Unpacking objects: 31% (19/61)
+Unpacking objects: 32% (20/61)
+Unpacking objects: 34% (21/61)
+Unpacking objects: 36% (22/61)
+Unpacking objects: 37% (23/61)
+Unpacking objects: 39% (24/61)
+Unpacking objects: 40% (25/61)
+Unpacking objects: 42% (26/61)
+Unpacking objects: 44% (27/61)
+Unpacking objects: 45% (28/61)
+Unpacking objects: 47% (29/61)
+Unpacking objects: 49% (30/61)
+Unpacking objects: 50% (31/61)
+Unpacking objects: 52% (32/61)
+Unpacking objects: 54% (33/61)
+Unpacking objects: 55% (34/61)
+Unpacking objects: 57% (35/61)
+Unpacking objects: 59% (36/61)
+Unpacking objects: 60% (37/61)
+Unpacking objects: 62% (38/61)
+Unpacking objects: 63% (39/61)
+Unpacking objects: 65% (40/61)
+Unpacking objects: 67% (41/61)
+Unpacking objects: 68% (42/61)
+Unpacking objects: 70% (43/61)
+Unpacking objects: 72% (44/61)
+Unpacking objects: 73% (45/61)
+Unpacking objects: 75% (46/61)
+Unpacking objects: 77% (47/61)
+Unpacking objects: 78% (48/61)
+Unpacking objects: 80% (49/61)
+Unpacking objects: 81% (50/61)
+Unpacking objects: 83% (51/61)
+Unpacking objects: 85% (52/61)
+Unpacking objects: 86% (53/61)
+Unpacking objects: 88% (54/61)
+Unpacking objects: 90% (55/61)
+Unpacking objects: 91% (56/61)
+Unpacking objects: 93% (57/61)
+Unpacking objects: 95% (58/61)
+Unpacking objects: 96% (59/61)
+Unpacking objects: 98% (60/61)
+Unpacking objects: 100% (61/61)
+Unpacking objects: 100% (61/61), done.
+From https://github.com/servo/servo
+ * branch refs/pull/7606/merge -> FETCH_HEAD
+travis_time:end:04bda51d:start=1442012018870347891,finish=1442012019489804985,duration=619457094
+[0K$ git checkout -qf FETCH_HEAD
+travis_fold:end:git.checkout
+[0Ktravis_fold:start:git.submodule
+[0Ktravis_time:start:04378686
+[0K$ git submodule init
+Submodule 'support/android-rs-glue' (https://github.com/tomaka/android-rs-glue) registered for path 'support/android-rs-glue'
+travis_time:end:04378686:start=1442012020599535551,finish=1442012020975447711,duration=375912160
+[0Ktravis_time:start:326023b0
+[0K$ git submodule update
+Cloning into 'support/android-rs-glue'...
+remote: Counting objects: 564, done.[K
+Receiving objects: 0% (1/564)
+Receiving objects: 1% (6/564)
+Receiving objects: 2% (12/564)
+Receiving objects: 3% (17/564)
+Receiving objects: 4% (23/564)
+Receiving objects: 5% (29/564)
+Receiving objects: 6% (34/564)
+Receiving objects: 7% (40/564)
+Receiving objects: 8% (46/564)
+Receiving objects: 9% (51/564)
+Receiving objects: 10% (57/564)
+Receiving objects: 11% (63/564)
+Receiving objects: 12% (68/564)
+Receiving objects: 13% (74/564)
+Receiving objects: 14% (79/564)
+Receiving objects: 15% (85/564)
+Receiving objects: 16% (91/564)
+Receiving objects: 17% (96/564)
+Receiving objects: 18% (102/564)
+Receiving objects: 19% (108/564)
+Receiving objects: 20% (113/564)
+Receiving objects: 21% (119/564)
+Receiving objects: 22% (125/564)
+Receiving objects: 23% (130/564)
+Receiving objects: 24% (136/564)
+Receiving objects: 25% (141/564)
+Receiving objects: 26% (147/564)
+Receiving objects: 27% (153/564)
+Receiving objects: 28% (158/564)
+Receiving objects: 29% (164/564)
+Receiving objects: 30% (170/564)
+Receiving objects: 31% (175/564)
+Receiving objects: 32% (181/564)
+Receiving objects: 33% (187/564)
+Receiving objects: 34% (192/564)
+Receiving objects: 35% (198/564)
+Receiving objects: 36% (204/564)
+Receiving objects: 37% (209/564)
+Receiving objects: 38% (215/564)
+Receiving objects: 39% (220/564)
+Receiving objects: 40% (226/564)
+Receiving objects: 41% (232/564)
+Receiving objects: 42% (237/564)
+Receiving objects: 43% (243/564)
+Receiving objects: 44% (249/564)
+Receiving objects: 45% (254/564)
+Receiving objects: 46% (260/564)
+Receiving objects: 47% (266/564)
+Receiving objects: 48% (271/564)
+Receiving objects: 49% (277/564)
+Receiving objects: 50% (282/564)
+Receiving objects: 51% (288/564)
+Receiving objects: 52% (294/564)
+Receiving objects: 53% (299/564)
+Receiving objects: 54% (305/564)
+Receiving objects: 55% (311/564)
+Receiving objects: 56% (316/564)
+Receiving objects: 57% (322/564)
+Receiving objects: 58% (328/564)
+Receiving objects: 59% (333/564)
+Receiving objects: 60% (339/564)
+Receiving objects: 61% (345/564)
+Receiving objects: 62% (350/564)
+Receiving objects: 63% (356/564)
+Receiving objects: 64% (361/564)
+Receiving objects: 65% (367/564)
+Receiving objects: 66% (373/564)
+Receiving objects: 67% (378/564)
+Receiving objects: 68% (384/564)
+Receiving objects: 69% (390/564)
+Receiving objects: 70% (395/564)
+Receiving objects: 71% (401/564)
+Receiving objects: 72% (407/564)
+Receiving objects: 73% (412/564)
+Receiving objects: 74% (418/564)
+Receiving objects: 75% (423/564)
+Receiving objects: 76% (429/564)
+Receiving objects: 77% (435/564)
+Receiving objects: 78% (440/564)
+Receiving objects: 79% (446/564)
+Receiving objects: 80% (452/564)
+Receiving objects: 81% (457/564)
+remote: Total 564 (delta 0), reused 0 (delta 0), pack-reused 564[K
+Receiving objects: 82% (463/564)
+Receiving objects: 83% (469/564)
+Receiving objects: 84% (474/564)
+Receiving objects: 85% (480/564)
+Receiving objects: 86% (486/564)
+Receiving objects: 87% (491/564)
+Receiving objects: 88% (497/564)
+Receiving objects: 89% (502/564)
+Receiving objects: 90% (508/564)
+Receiving objects: 91% (514/564)
+Receiving objects: 92% (519/564)
+Receiving objects: 93% (525/564)
+Receiving objects: 94% (531/564)
+Receiving objects: 95% (536/564)
+Receiving objects: 96% (542/564)
+Receiving objects: 97% (548/564)
+Receiving objects: 98% (553/564)
+Receiving objects: 99% (559/564)
+Receiving objects: 100% (564/564)
+Receiving objects: 100% (564/564), 133.16 KiB | 0 bytes/s, done.
+Resolving deltas: 0% (0/276)
+Resolving deltas: 4% (13/276)
+Resolving deltas: 5% (15/276)
+Resolving deltas: 6% (17/276)
+Resolving deltas: 7% (21/276)
+Resolving deltas: 8% (24/276)
+Resolving deltas: 9% (25/276)
+Resolving deltas: 14% (39/276)
+Resolving deltas: 17% (48/276)
+Resolving deltas: 19% (54/276)
+Resolving deltas: 31% (87/276)
+Resolving deltas: 32% (90/276)
+Resolving deltas: 35% (99/276)
+Resolving deltas: 44% (124/276)
+Resolving deltas: 51% (142/276)
+Resolving deltas: 57% (160/276)
+Resolving deltas: 67% (187/276)
+Resolving deltas: 69% (192/276)
+Resolving deltas: 70% (194/276)
+Resolving deltas: 81% (225/276)
+Resolving deltas: 83% (231/276)
+Resolving deltas: 84% (232/276)
+Resolving deltas: 87% (242/276)
+Resolving deltas: 88% (245/276)
+Resolving deltas: 92% (255/276)
+Resolving deltas: 93% (257/276)
+Resolving deltas: 95% (264/276)
+Resolving deltas: 96% (267/276)
+Resolving deltas: 100% (276/276)
+Resolving deltas: 100% (276/276), done.
+Checking connectivity... done.
+Submodule path 'support/android-rs-glue': checked out '4ed3cb30b289aa0aa84a00e0d5682bc853108e5a'
+travis_time:end:326023b0:start=1442012020979079782,finish=1442012021621488690,duration=642408908
+[0Ktravis_fold:end:git.submodule
+[0K
+[33;1mThis job is running on container-based infrastructure, which does not allow use of 'sudo', setuid and setguid executables.[0m
+[33;1mIf you require sudo, add 'sudo: required' to your .travis.yml[0m
+[33;1mSee http://docs.travis-ci.com/user/workers/container-based-infrastructure/ for details.[0m
+travis_time:start:1d674e1b
+[0K$ source ~/virtualenv/python2.7/bin/activate
+travis_time:end:1d674e1b:start=1442012024198107459,finish=1442012024205105107,duration=6997648
+[0K$ python --version
+Python 2.7.9
+$ pip --version
+pip 6.0.7 from /home/travis/virtualenv/python2.7.9/lib/python2.7/site-packages (python 2.7)
+Could not locate requirements.txt. Override the install: key in your .travis.yml to install dependencies.
+travis_time:start:011277c0
+[0K$ ./mach test-tidy
+Running virtualenv with interpreter /home/travis/virtualenv/python2.7.9/bin/python2
+Using real prefix '/opt/python/2.7.9'
+New python executable in /home/travis/build/servo/servo/python/_virtualenv/bin/python2
+Also creating executable in /home/travis/build/servo/servo/python/_virtualenv/bin/python
+Installing setuptools, pip...done.
+[33mYou are using pip version 6.0.7, however version 7.1.2 is available.
+You should consider upgrading via the 'pip install --upgrade pip' command.[0m
+[33mYou are using pip version 6.0.7, however version 7.1.2 is available.
+You should consider upgrading via the 'pip install --upgrade pip' command.[0m
+[33mYou are using pip version 6.0.7, however version 7.1.2 is available.
+You should consider upgrading via the 'pip install --upgrade pip' command.[0m
+[94m./components/script/dom/eventtarget.rs[0m:[93m7[0m: [91muse statement is not in alphabetical order
+ [93mexpected: dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull[0m
+ [91mfound: dom::bindings::conversions::get_dom_class[0m[0m
+[94m./components/script/dom/eventtarget.rs[0m:[93m8[0m: [91muse statement is not in alphabetical order
+ [93mexpected: dom::bindings::codegen::Bindings::EventListenerBinding::EventListener[0m
+ [91mfound: dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull[0m[0m
+[94m./components/script/dom/eventtarget.rs[0m:[93m9[0m: [91muse statement is not in alphabetical order
+ [93mexpected: dom::bindings::codegen::Bindings::EventTargetBinding::EventTargetMethods[0m
+ [91mfound: dom::bindings::codegen::Bindings::EventListenerBinding::EventListener[0m[0m
+[94m./components/script/dom/eventtarget.rs[0m:[93m10[0m: [91muse statement is not in alphabetical order
+ [93mexpected: dom::bindings::conversions::get_dom_class[0m
+ [91mfound: dom::bindings::codegen::Bindings::EventTargetBinding::EventTargetMethods[0m[0m
+[94m./components/script/dom/bindings/utils.rs[0m:[93m17[0m: [91muse statement is not in alphabetical order
+ [93mexpected: dom::browsercontext[0m
+ [91mfound: dom::eventtarget::EventTargetTypeId[0m[0m
+[94m./components/script/dom/bindings/utils.rs[0m:[93m18[0m: [91muse statement is not in alphabetical order
+ [93mexpected: dom::eventtarget::EventTargetTypeId[0m
+ [91mfound: dom::browsercontext[0m[0m
+travis_time:end:011277c0:start=1442012024569595079,finish=1442012039323338483,duration=14753743404
+[0K
+[31;1mThe command "./mach test-tidy" exited with 1.[0m
+
+Done. Your build exited with 1.
diff --git a/resources/needs_reftest.diff b/resources/needs_reftest.diff
new file mode 100644
index 0000000..344a170
--- /dev/null
+++ b/resources/needs_reftest.diff
@@ -0,0 +1,64 @@
+diff --git a/components/layout/layout_task.rs b/components/layout/layout_task.rs
+index 28eacfd..f0aba98 100644
+--- a/components/layout/layout_task.rs
++++ b/components/layout/layout_task.rs
+@@ -834,19 +834,6 @@ impl LayoutTask {
+ traversal);
+ }
+
+- /// Verifies that every node was either marked as a leaf or as a nonleaf in the flow tree.
+- /// This is only on in debug builds.
+- #[inline(never)]
+- #[cfg(debug)]
+- fn verify_flow_tree(&self, layout_root: &mut FlowRef) {
+- let mut traversal = traversal::FlowTreeVerification;
+- layout_root.traverse_preorder(&mut traversal);
+- }
+-
+- #[cfg(not(debug))]
+- fn verify_flow_tree(&self, _: &mut FlowRef) {
+- }
+-
+ fn process_node_geometry_request<'a>(&'a self,
+ requested_node: TrustedNodeAddress,
+ layout_root: &mut FlowRef,
+@@ -1338,11 +1325,6 @@ impl LayoutTask {
+ }
+ });
+
+- // Verification of the flow tree, which ensures that all nodes were either marked as
+- // leaves or as non-leaves. This becomes a no-op in release builds. (It is
+- // inconsequential to memory safety but is a useful debugging tool.)
+- self.verify_flow_tree(&mut root_flow);
+-
+ if opts::get().trace_layout {
+ layout_debug::begin_trace(root_flow.clone());
+ }
+diff --git a/components/layout/traversal.rs b/components/layout/traversal.rs
+index 43199d7..7742ee3 100644
+--- a/components/layout/traversal.rs
++++ b/components/layout/traversal.rs
+@@ -292,23 +292,6 @@ impl<'a> PostorderDomTraversal for ConstructFlows<'a> {
+ }
+ }
+
+-/// The flow tree verification traversal. This is only on in debug builds.
+-#[cfg(debug)]
+-struct FlowTreeVerification;
+-
+-#[cfg(debug)]
+-impl PreorderFlow for FlowTreeVerification {
+- #[inline]
+- fn process(&mut self, flow: &mut Flow) {
+- let base = flow::base(flow);
+- if !base.flags.is_leaf() && !base.flags.is_nonleaf() {
+- println!("flow tree verification failed: flow wasn't a leaf or a nonleaf!");
+- flow.dump();
+- panic!("flow tree verification failed")
+- }
+- }
+-}
+-
+ /// The bubble-inline-sizes traversal, the first part of layout computation. This computes
+ /// preferred and intrinsic inline-sizes and bubbles them up the tree.
+ pub struct BubbleISizes<'a> {
\ No newline at end of file
diff --git a/resources/review_comments.json b/resources/review_comments.json
new file mode 100644
index 0000000..b608ec4
--- /dev/null
+++ b/resources/review_comments.json
@@ -0,0 +1,93 @@
+[
+ {
+ "url": "https://api.github.com/repos/octocat/Hello-World/pulls/comments/1",
+ "id": 1,
+ "diff_hunk": "@@ -16,33 +16,40 @@ public class Connection : IConnection...",
+ "path": "./components/script/dom/bindings/utils.rs",
+ "position": 17,
+ "original_position": 4,
+ "commit_id": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
+ "original_commit_id": "9c48853fa3dc5c1c3d6f1f1cd1f2743e72652840",
+ "user": {
+ "login": "octocat",
+ "id": 1,
+ "avatar_url": "https://github.com/images/error/octocat_happy.gif",
+ "gravatar_id": "",
+ "url": "https://api.github.com/users/octocat",
+ "html_url": "https://github.com/octocat",
+ "followers_url": "https://api.github.com/users/octocat/followers",
+ "following_url": "https://api.github.com/users/octocat/following{/other_user}",
+ "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
+ "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
+ "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
+ "organizations_url": "https://api.github.com/users/octocat/orgs",
+ "repos_url": "https://api.github.com/users/octocat/repos",
+ "events_url": "https://api.github.com/users/octocat/events{/privacy}",
+ "received_events_url": "https://api.github.com/users/octocat/received_events",
+ "type": "User",
+ "site_admin": false
+ },
+
+ "body": "Please fix the following error:\n\n**File:** ./components/script/dom/bindings/utils.rs\n**Line Number:** 17\n**Error:** use statement is not in alphabetical order\nexpected: dom::browsercontext\nfound: dom::eventtarget::EventTargetTypeId",
+ "created_at": "2011-04-14T16:00:49Z",
+ "updated_at": "2011-04-14T16:00:49Z",
+ "html_url": "https://github.com/octocat/Hello-World/pull/1#discussion-diff-1",
+ "pull_request_url": "https://api.github.com/repos/octocat/Hello-World/pulls/1",
+ "_links": {
+ "self": {
+ "href": "https://api.github.com/repos/octocat/Hello-World/pulls/comments/1"
+ },
+ "html": {
+ "href": "https://github.com/octocat/Hello-World/pull/1#discussion-diff-1"
+ },
+ "pull_request": {
+ "href": "https://api.github.com/repos/octocat/Hello-World/pulls/1"
+ }
+ }
+ },
+ {
+ "url": "https://api.github.com/repos/octocat/Hello-World/pulls/comments/1",
+ "id": 1,
+ "diff_hunk": "@@ -16,33 +16,40 @@ public class Connection : IConnection...",
+ "path": "./components/script/dom/bindings/utils.rs",
+ "position": 18,
+ "original_position": 4,
+ "commit_id": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
+ "original_commit_id": "9c48853fa3dc5c1c3d6f1f1cd1f2743e72652840",
+ "user": {
+ "login": "octocat",
+ "id": 1,
+ "avatar_url": "https://github.com/images/error/octocat_happy.gif",
+ "gravatar_id": "",
+ "url": "https://api.github.com/users/octocat",
+ "html_url": "https://github.com/octocat",
+ "followers_url": "https://api.github.com/users/octocat/followers",
+ "following_url": "https://api.github.com/users/octocat/following{/other_user}",
+ "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
+ "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
+ "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
+ "organizations_url": "https://api.github.com/users/octocat/orgs",
+ "repos_url": "https://api.github.com/users/octocat/repos",
+ "events_url": "https://api.github.com/users/octocat/events{/privacy}",
+ "received_events_url": "https://api.github.com/users/octocat/received_events",
+ "type": "User",
+ "site_admin": false
+ },
+ "body": "Please fix the following error:\n\n**File:** ./components/script/dom/bindings/utils.rs\n**Line Number:** 18\n**Error:** use statement is not in alphabetical order\nexpected: dom::eventtarget::EventTargetTypeId\nfound: dom::browsercontext",
+ "created_at": "2011-04-14T16:00:49Z",
+ "updated_at": "2011-04-14T16:00:49Z",
+ "html_url": "https://github.com/octocat/Hello-World/pull/1#discussion-diff-1",
+ "pull_request_url": "https://api.github.com/repos/octocat/Hello-World/pulls/1",
+ "_links": {
+ "self": {
+ "href": "https://api.github.com/repos/octocat/Hello-World/pulls/comments/1"
+ },
+ "html": {
+ "href": "https://github.com/octocat/Hello-World/pull/1#discussion-diff-1"
+ },
+ "pull_request": {
+ "href": "https://api.github.com/repos/octocat/Hello-World/pulls/1"
+ }
+ }
+ }
+]
\ No newline at end of file
diff --git a/resources/single-line-comment.log b/resources/single-line-comment.log
new file mode 100644
index 0000000..22fff91
--- /dev/null
+++ b/resources/single-line-comment.log
@@ -0,0 +1 @@
+Using worker: worker-linux-docker-201055c9.prod.travis-ci.org:travis-linux-6
travis_fold:start:system_info
[0K[33;1mBuild system information[0m
Build language: python
[34m[1mBuild image provisioning date and time[0m
Thu Feb 5 15:09:33 UTC 2015
[34m[1mOperating System Details[0m
Distributor ID: Ubuntu
Description: Ubuntu 12.04.5 LTS
Release: 12.04
Codename: precise
[34m[1mLinux Version[0m
3.13.0-29-generic
[34m[1mCookbooks Version[0m
a68419e https://github.com/travis-ci/travis-cookbooks/tree/a68419e
[34m[1mGCC version[0m
gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
Copyright (C) 2011 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
[34m[1mLLVM version[0m
clang version 3.4 (tags/RELEASE_34/final)
Target: x86_64-unknown-linux-gnu
Thread model: posix
[34m[1mPre-installed Ruby versions[0m
ruby-1.9.3-p551
[34m[1mPre-installed Node.js versions[0m
v0.10.36
[34m[1mPre-installed Go versions[0m
1.4.1
[34m[1mRedis version[0m
redis-server 2.8.19
[34m[1mriak version[0m
2.0.2
[34m[1mMongoDB version[0m
MongoDB 2.4.12
[34m[1mCouchDB version[0m
couchdb 1.6.1
[34m[1mNeo4j version[0m
1.9.4
[34m[1mRabbitMQ Version[0m
3.4.3
[34m[1mElasticSearch version[0m
1.4.0
[34m[1mInstalled Sphinx versions[0m
2.0.10
2.1.9
2.2.6
[34m[1mDefault Sphinx version[0m
2.2.6
[34m[1mInstalled Firefox version[0m
firefox 31.0esr
[34m[1mPhantomJS version[0m
1.9.8
[34m[1mant -version[0m
Apache Ant(TM) version 1.8.2 compiled on December 3 2011
[34m[1mmvn -version[0m
Apache Maven 3.2.5 (12a6b3acb947671f09b81f49094c53f426d8cea1; 2014-12-14T17:29:23+00:00)
Maven home: /usr/local/maven
Java version: 1.7.0_76, vendor: Oracle Corporation
Java home: /usr/lib/jvm/java-7-oracle/jre
Default locale: en_US, platform encoding: ANSI_X3.4-1968
OS name: "linux", version: "3.13.0-29-generic", arch: "amd64", family: "unix"
travis_fold:end:system_info
[0K
travis_fold:start:git.checkout
[0Ktravis_time:start:1f55a9d0
[0K$ git clone --depth=50 https://github.com/servo/servo.git servo/servo
Cloning into 'servo/servo'...
remote: Counting objects: 100268, done.[K
remote: Compressing objects: 0% (1/37443) [K
remote: Compressing objects: 1% (375/37443) [K
remote: Compressing objects: 2% (749/37443) [K
remote: Compressing objects: 3% (1124/37443) [K
remote: Compressing objects: 4% (1498/37443) [K
remote: Compressing objects: 5% (1873/37443) [K
remote: Compressing objects: 6% (2247/37443) [K
remote: Compressing objects: 7% (2622/37443) [K
remote: Compressing objects: 8% (2996/37443) [K
remote: Compressing objects: 9% (3370/37443) [K
remote: Compressing objects: 10% (3745/37443) [K
remote: Compressing objects: 11% (4119/37443) [K
remote: Compressing objects: 12% (4494/37443) [K
remote: Compressing objects: 13% (4868/37443) [K
remote: Compressing objects: 14% (5243/37443) [K
remote: Compressing objects: 15% (5617/37443) [K
remote: Compressing objects: 16% (5991/37443) [K
remote: Compressing objects: 17% (6366/37443) [K
remote: Compressing objects: 18% (6740/37443) [K
remote: Compressing objects: 19% (7115/37443) [K
remote: Compressing objects: 20% (7489/37443) [K
remote: Compressing objects: 21% (7864/37443) [K
remote: Compressing objects: 22% (8238/37443) [K
remote: Compressing objects: 23% (8612/37443) [K
remote: Compressing objects: 24% (8987/37443) [K
remote: Compressing objects: 25% (9361/37443) [K
remote: Compressing objects: 26% (9736/37443) [K
remote: Compressing objects: 27% (10110/37443) [K
remote: Compressing objects: 27% (10133/37443) [K
remote: Compressing objects: 28% (10485/37443) [K
remote: Compressing objects: 29% (10859/37443) [K
remote: Compressing objects: 30% (11233/37443) [K
remote: Compressing objects: 31% (11608/37443) [K
remote: Compressing objects: 32% (11982/37443) [K
remote: Compressing objects: 33% (12357/37443) [K
remote: Compressing objects: 34% (12731/37443) [K
remote: Compressing objects: 35% (13106/37443) [K
remote: Compressing objects: 36% (13480/37443) [K
remote: Compressing objects: 37% (13854/37443) [K
remote: Compressing objects: 38% (14229/37443) [K
remote: Compressing objects: 39% (14603/37443) [K
remote: Compressing objects: 40% (14978/37443) [K
remote: Compressing objects: 41% (15352/37443) [K
remote: Compressing objects: 42% (15727/37443) [K
remote: Compressing objects: 43% (16101/37443) [K
remote: Compressing objects: 44% (16475/37443) [K
remote: Compressing objects: 45% (16850/37443) [K
remote: Compressing objects: 46% (17224/37443) [K
remote: Compressing objects: 47% (17599/37443) [K
remote: Compressing objects: 48% (17973/37443) [K
remote: Compressing objects: 49% (18348/37443) [K
remote: Compressing objects: 50% (18722/37443) [K
remote: Compressing objects: 51% (19096/37443) [K
remote: Compressing objects: 52% (19471/37443) [K
remote: Compressing objects: 53% (19845/37443) [K
remote: Compressing objects: 54% (20220/37443) [K
remote: Compressing objects: 55% (20594/37443) [K
remote: Compressing objects: 56% (20969/37443) [K
remote: Compressing objects: 57% (21343/37443) [K
remote: Compressing objects: 58% (21717/37443) [K
remote: Compressing objects: 59% (22092/37443) [K
remote: Compressing objects: 60% (22466/37443) [K
remote: Compressing objects: 61% (22841/37443) [K
remote: Compressing objects: 62% (23215/37443) [K
remote: Compressing objects: 63% (23590/37443) [K
remote: Compressing objects: 64% (23964/37443) [K
remote: Compressing objects: 65% (24338/37443) [K
remote: Compressing objects: 66% (24713/37443) [K
remote: Compressing objects: 67% (25087/37443) [K
remote: Compressing objects: 68% (25462/37443) [K
remote: Compressing objects: 69% (25836/37443) [K
remote: Compressing objects: 70% (26211/37443) [K
remote: Compressing objects: 71% (26585/37443) [K
remote: Compressing objects: 72% (26959/37443) [K
remote: Compressing objects: 73% (27334/37443) [K
remote: Compressing objects: 74% (27708/37443) [K
remote: Compressing objects: 75% (28083/37443) [K
remote: Compressing objects: 76% (28457/37443) [K
remote: Compressing objects: 77% (28832/37443) [K
remote: Compressing objects: 78% (29206/37443) [K
remote: Compressing objects: 79% (29580/37443) [K
remote: Compressing objects: 80% (29955/37443) [K
remote: Compressing objects: 81% (30329/37443) [K
remote: Compressing objects: 82% (30704/37443) [K
remote: Compressing objects: 83% (31078/37443) [K
remote: Compressing objects: 84% (31453/37443) [K
remote: Compressing objects: 85% (31827/37443) [K
remote: Compressing objects: 86% (32201/37443) [K
remote: Compressing objects: 87% (32576/37443) [K
remote: Compressing objects: 88% (32950/37443) [K
remote: Compressing objects: 89% (33325/37443) [K
remote: Compressing objects: 90% (33699/37443) [K
remote: Compressing objects: 91% (34074/37443) [K
remote: Compressing objects: 92% (34448/37443) [K
remote: Compressing objects: 93% (34822/37443) [K
remote: Compressing objects: 94% (35197/37443) [K
remote: Compressing objects: 95% (35571/37443) [K
remote: Compressing objects: 96% (35946/37443) [K
remote: Compressing objects: 97% (36320/37443) [K
remote: Compressing objects: 98% (36695/37443) [K
remote: Compressing objects: 99% (37069/37443) [K
remote: Compressing objects: 100% (37443/37443) [K
remote: Compressing objects: 100% (37443/37443), done.[K
Receiving objects: 0% (1/100268)
Receiving objects: 1% (1003/100268)
Receiving objects: 2% (2006/100268)
Receiving objects: 3% (3009/100268)
Receiving objects: 4% (4011/100268)
Receiving objects: 5% (5014/100268), 9.87 MiB | 19.69 MiB/s
Receiving objects: 6% (6017/100268), 9.87 MiB | 19.69 MiB/s
Receiving objects: 7% (7019/100268), 9.87 MiB | 19.69 MiB/s
Receiving objects: 8% (8022/100268), 9.87 MiB | 19.69 MiB/s
Receiving objects: 9% (9025/100268), 9.87 MiB | 19.69 MiB/s
Receiving objects: 10% (10027/100268), 9.87 MiB | 19.69 MiB/s
Receiving objects: 11% (11030/100268), 9.87 MiB | 19.69 MiB/s
Receiving objects: 12% (12033/100268), 9.87 MiB | 19.69 MiB/s
Receiving objects: 13% (13035/100268), 9.87 MiB | 19.69 MiB/s
Receiving objects: 14% (14038/100268), 9.87 MiB | 19.69 MiB/s
Receiving objects: 15% (15041/100268), 9.87 MiB | 19.69 MiB/s
Receiving objects: 16% (16043/100268), 9.87 MiB | 19.69 MiB/s
Receiving objects: 17% (17046/100268), 9.87 MiB | 19.69 MiB/s
Receiving objects: 17% (17527/100268), 19.21 MiB | 19.17 MiB/s
Receiving objects: 18% (18049/100268), 19.21 MiB | 19.17 MiB/s
Receiving objects: 19% (19051/100268), 19.21 MiB | 19.17 MiB/s
Receiving objects: 20% (20054/100268), 19.21 MiB | 19.17 MiB/s
Receiving objects: 21% (21057/100268), 19.21 MiB | 19.17 MiB/s
Receiving objects: 22% (22059/100268), 19.21 MiB | 19.17 MiB/s
Receiving objects: 23% (23062/100268), 19.21 MiB | 19.17 MiB/s
Receiving objects: 24% (24065/100268), 19.21 MiB | 19.17 MiB/s
Receiving objects: 25% (25067/100268), 19.21 MiB | 19.17 MiB/s
Receiving objects: 26% (26070/100268), 30.19 MiB | 20.08 MiB/s
Receiving objects: 27% (27073/100268), 30.19 MiB | 20.08 MiB/s
Receiving objects: 28% (28076/100268), 30.19 MiB | 20.08 MiB/s
Receiving objects: 29% (29078/100268), 30.19 MiB | 20.08 MiB/s
Receiving objects: 30% (30081/100268), 30.19 MiB | 20.08 MiB/s
Receiving objects: 31% (31084/100268), 30.19 MiB | 20.08 MiB/s
Receiving objects: 32% (32086/100268), 30.19 MiB | 20.08 MiB/s
Receiving objects: 33% (33089/100268), 30.19 MiB | 20.08 MiB/s
Receiving objects: 34% (34092/100268), 30.19 MiB | 20.08 MiB/s
Receiving objects: 35% (35094/100268), 30.19 MiB | 20.08 MiB/s
Receiving objects: 36% (36097/100268), 30.19 MiB | 20.08 MiB/s
Receiving objects: 37% (37100/100268), 30.19 MiB | 20.08 MiB/s
Receiving objects: 38% (38102/100268), 30.19 MiB | 20.08 MiB/s
Receiving objects: 39% (39105/100268), 30.19 MiB | 20.08 MiB/s
Receiving objects: 40% (40108/100268), 30.19 MiB | 20.08 MiB/s
Receiving objects: 41% (41110/100268), 30.19 MiB | 20.08 MiB/s
Receiving objects: 42% (42113/100268), 30.19 MiB | 20.08 MiB/s
Receiving objects: 43% (43116/100268), 30.19 MiB | 20.08 MiB/s
Receiving objects: 44% (44118/100268), 30.19 MiB | 20.08 MiB/s
Receiving objects: 45% (45121/100268), 30.19 MiB | 20.08 MiB/s
Receiving objects: 46% (46124/100268), 30.19 MiB | 20.08 MiB/s
Receiving objects: 47% (47126/100268), 30.19 MiB | 20.08 MiB/s
Receiving objects: 47% (47779/100268), 30.19 MiB | 20.08 MiB/s
Receiving objects: 48% (48129/100268), 38.65 MiB | 19.29 MiB/s
Receiving objects: 49% (49132/100268), 38.65 MiB | 19.29 MiB/s
Receiving objects: 50% (50134/100268), 38.65 MiB | 19.29 MiB/s
Receiving objects: 51% (51137/100268), 38.65 MiB | 19.29 MiB/s
Receiving objects: 52% (52140/100268), 38.65 MiB | 19.29 MiB/s
Receiving objects: 53% (53143/100268), 38.65 MiB | 19.29 MiB/s
Receiving objects: 54% (54145/100268), 38.65 MiB | 19.29 MiB/s
Receiving objects: 55% (55148/100268), 38.65 MiB | 19.29 MiB/s
Receiving objects: 56% (56151/100268), 38.65 MiB | 19.29 MiB/s
Receiving objects: 57% (57153/100268), 38.65 MiB | 19.29 MiB/s
Receiving objects: 58% (58156/100268), 38.65 MiB | 19.29 MiB/s
Receiving objects: 59% (59159/100268), 38.65 MiB | 19.29 MiB/s
Receiving objects: 60% (60161/100268), 38.65 MiB | 19.29 MiB/s
Receiving objects: 61% (61164/100268), 38.65 MiB | 19.29 MiB/s
Receiving objects: 62% (62167/100268), 38.65 MiB | 19.29 MiB/s
Receiving objects: 63% (63169/100268), 38.65 MiB | 19.29 MiB/s
Receiving objects: 64% (64172/100268), 38.65 MiB | 19.29 MiB/s
Receiving objects: 65% (65175/100268), 38.65 MiB | 19.29 MiB/s
Receiving objects: 66% (66177/100268), 38.65 MiB | 19.29 MiB/s
Receiving objects: 67% (67180/100268), 38.65 MiB | 19.29 MiB/s
Receiving objects: 68% (68183/100268), 38.65 MiB | 19.29 MiB/s
Receiving objects: 69% (69185/100268), 38.65 MiB | 19.29 MiB/s
Receiving objects: 70% (70188/100268), 45.49 MiB | 18.16 MiB/s
Receiving objects: 71% (71191/100268), 45.49 MiB | 18.16 MiB/s
Receiving objects: 72% (72193/100268), 45.49 MiB | 18.16 MiB/s
Receiving objects: 73% (73196/100268), 45.49 MiB | 18.16 MiB/s
Receiving objects: 74% (74199/100268), 45.49 MiB | 18.16 MiB/s
Receiving objects: 75% (75201/100268), 45.49 MiB | 18.16 MiB/s
Receiving objects: 76% (76204/100268), 45.49 MiB | 18.16 MiB/s
Receiving objects: 77% (77207/100268), 45.49 MiB | 18.16 MiB/s
Receiving objects: 78% (78210/100268), 45.49 MiB | 18.16 MiB/s
Receiving objects: 78% (78473/100268), 51.43 MiB | 17.11 MiB/s
Receiving objects: 79% (79212/100268), 51.43 MiB | 17.11 MiB/s
Receiving objects: 80% (80215/100268), 51.43 MiB | 17.11 MiB/s
Receiving objects: 81% (81218/100268), 51.43 MiB | 17.11 MiB/s
Receiving objects: 82% (82220/100268), 51.43 MiB | 17.11 MiB/s
Receiving objects: 83% (83223/100268), 51.43 MiB | 17.11 MiB/s
Receiving objects: 84% (84226/100268), 51.43 MiB | 17.11 MiB/s
Receiving objects: 85% (85228/100268), 51.43 MiB | 17.11 MiB/s
Receiving objects: 86% (86231/100268), 62.97 MiB | 17.96 MiB/s
Receiving objects: 87% (87234/100268), 62.97 MiB | 17.96 MiB/s
Receiving objects: 88% (88236/100268), 62.97 MiB | 17.96 MiB/s
Receiving objects: 89% (89239/100268), 62.97 MiB | 17.96 MiB/s
Receiving objects: 90% (90242/100268), 62.97 MiB | 17.96 MiB/s
Receiving objects: 91% (91244/100268), 62.97 MiB | 17.96 MiB/s
Receiving objects: 92% (92247/100268), 62.97 MiB | 17.96 MiB/s
Receiving objects: 93% (93250/100268), 62.97 MiB | 17.96 MiB/s
Receiving objects: 94% (94252/100268), 62.97 MiB | 17.96 MiB/s
Receiving objects: 95% (95255/100268), 62.97 MiB | 17.96 MiB/s
Receiving objects: 96% (96258/100268), 62.97 MiB | 17.96 MiB/s
Receiving objects: 97% (97260/100268), 62.97 MiB | 17.96 MiB/s
Receiving objects: 98% (98263/100268), 62.97 MiB | 17.96 MiB/s
Receiving objects: 99% (99266/100268), 62.97 MiB | 17.96 MiB/s
remote: Total 100268 (delta 64207), reused 96530 (delta 61453), pack-reused 0[K
Receiving objects: 100% (100268/100268), 62.97 MiB | 17.96 MiB/s
Receiving objects: 100% (100268/100268), 71.58 MiB | 17.96 MiB/s, done.
Resolving deltas: 0% (0/64207)
Resolving deltas: 1% (672/64207)
Resolving deltas: 2% (1287/64207)
Resolving deltas: 3% (1928/64207)
Resolving deltas: 4% (2595/64207)
Resolving deltas: 5% (3217/64207)
Resolving deltas: 6% (3861/64207)
Resolving deltas: 7% (4495/64207)
Resolving deltas: 8% (5137/64207)
Resolving deltas: 9% (5781/64207)
Resolving deltas: 10% (6421/64207)
Resolving deltas: 11% (7085/64207)
Resolving deltas: 12% (7709/64207)
Resolving deltas: 13% (8360/64207)
Resolving deltas: 14% (9000/64207)
Resolving deltas: 15% (9707/64207)
Resolving deltas: 16% (10276/64207)
Resolving deltas: 17% (10916/64207)
Resolving deltas: 18% (11558/64207)
Resolving deltas: 19% (12200/64207)
Resolving deltas: 20% (12854/64207)
Resolving deltas: 21% (13496/64207)
Resolving deltas: 22% (14146/64207)
Resolving deltas: 23% (14816/64207)
Resolving deltas: 24% (15411/64207)
Resolving deltas: 25% (16114/64207)
Resolving deltas: 26% (16814/64207)
Resolving deltas: 27% (17346/64207)
Resolving deltas: 28% (18004/64207)
Resolving deltas: 29% (18679/64207)
Resolving deltas: 30% (19476/64207)
Resolving deltas: 31% (19918/64207)
Resolving deltas: 32% (20564/64207)
Resolving deltas: 33% (21216/64207)
Resolving deltas: 34% (21964/64207)
Resolving deltas: 35% (22496/64207)
Resolving deltas: 35% (22885/64207)
Resolving deltas: 36% (23138/64207)
Resolving deltas: 37% (23771/64207)
Resolving deltas: 38% (24404/64207)
Resolving deltas: 39% (25041/64207)
Resolving deltas: 40% (25715/64207)
Resolving deltas: 41% (26341/64207)
Resolving deltas: 42% (26968/64207)
Resolving deltas: 43% (27612/64207)
Resolving deltas: 44% (28278/64207)
Resolving deltas: 45% (28895/64207)
Resolving deltas: 46% (29536/64207)
Resolving deltas: 47% (30179/64207)
Resolving deltas: 48% (30820/64207)
Resolving deltas: 49% (31463/64207)
Resolving deltas: 50% (32120/64207)
Resolving deltas: 51% (32746/64207)
Resolving deltas: 52% (33436/64207)
Resolving deltas: 53% (34064/64207)
Resolving deltas: 54% (34672/64207)
Resolving deltas: 55% (35317/64207)
Resolving deltas: 56% (35961/64207)
Resolving deltas: 57% (36607/64207)
Resolving deltas: 58% (37241/64207)
Resolving deltas: 59% (37883/64207)
Resolving deltas: 60% (38530/64207)
Resolving deltas: 61% (39172/64207)
Resolving deltas: 62% (39904/64207)
Resolving deltas: 63% (40516/64207)
Resolving deltas: 64% (41094/64207)
Resolving deltas: 65% (41739/64207)
Resolving deltas: 66% (42379/64207)
Resolving deltas: 67% (43024/64207)
Resolving deltas: 68% (43722/64207)
Resolving deltas: 69% (44303/64207)
Resolving deltas: 70% (44947/64207)
Resolving deltas: 71% (45588/64207)
Resolving deltas: 72% (46239/64207)
Resolving deltas: 73% (46872/64207)
Resolving deltas: 74% (47516/64207)
Resolving deltas: 75% (48156/64207)
Resolving deltas: 76% (48800/64207)
Resolving deltas: 77% (49440/64207)
Resolving deltas: 78% (50082/64207)
Resolving deltas: 79% (50724/64207)
Resolving deltas: 79% (50845/64207)
Resolving deltas: 80% (51368/64207)
Resolving deltas: 81% (52011/64207)
Resolving deltas: 82% (52683/64207)
Resolving deltas: 83% (53295/64207)
Resolving deltas: 84% (54001/64207)
Resolving deltas: 85% (54592/64207)
Resolving deltas: 86% (55219/64207)
Resolving deltas: 87% (56307/64207)
Resolving deltas: 88% (56507/64207)
Resolving deltas: 89% (57180/64207)
Resolving deltas: 90% (57787/64207)
Resolving deltas: 91% (58429/64207)
Resolving deltas: 92% (59076/64207)
Resolving deltas: 93% (59715/64207)
Resolving deltas: 94% (60357/64207)
Resolving deltas: 95% (61032/64207)
Resolving deltas: 96% (61641/64207)
Resolving deltas: 97% (62460/64207)
Resolving deltas: 98% (62924/64207)
Resolving deltas: 99% (63577/64207)
Resolving deltas: 100% (64207/64207)
Resolving deltas: 100% (64207/64207), done.
Checking connectivity... done.
Checking out files: 11% (13688/118796)
Checking out files: 12% (14256/118796)
Checking out files: 13% (15444/118796)
Checking out files: 14% (16632/118796)
Checking out files: 15% (17820/118796)
Checking out files: 16% (19008/118796)
Checking out files: 17% (20196/118796)
Checking out files: 18% (21384/118796)
Checking out files: 19% (22572/118796)
Checking out files: 20% (23760/118796)
Checking out files: 21% (24948/118796)
Checking out files: 22% (26136/118796)
Checking out files: 23% (27324/118796)
Checking out files: 23% (28377/118796)
Checking out files: 24% (28512/118796)
Checking out files: 25% (29699/118796)
Checking out files: 26% (30887/118796)
Checking out files: 27% (32075/118796)
Checking out files: 28% (33263/118796)
Checking out files: 29% (34451/118796)
Checking out files: 30% (35639/118796)
Checking out files: 31% (36827/118796)
Checking out files: 32% (38015/118796)
Checking out files: 33% (39203/118796)
Checking out files: 34% (40391/118796)
Checking out files: 34% (40927/118796)
Checking out files: 35% (41579/118796)
Checking out files: 36% (42767/118796)
Checking out files: 37% (43955/118796)
Checking out files: 38% (45143/118796)
Checking out files: 39% (46331/118796)
Checking out files: 40% (47519/118796)
Checking out files: 41% (48707/118796)
Checking out files: 42% (49895/118796)
Checking out files: 43% (51083/118796)
Checking out files: 44% (52271/118796)
Checking out files: 45% (53459/118796)
Checking out files: 46% (54647/118796)
Checking out files: 47% (55835/118796)
Checking out files: 47% (56675/118796)
Checking out files: 48% (57023/118796)
Checking out files: 49% (58211/118796)
Checking out files: 50% (59398/118796)
Checking out files: 51% (60586/118796)
Checking out files: 52% (61774/118796)
Checking out files: 53% (62962/118796)
Checking out files: 54% (64150/118796)
Checking out files: 55% (65338/118796)
Checking out files: 56% (66526/118796)
Checking out files: 57% (67714/118796)
Checking out files: 58% (68902/118796)
Checking out files: 59% (70090/118796)
Checking out files: 59% (70952/118796)
Checking out files: 60% (71278/118796)
Checking out files: 61% (72466/118796)
Checking out files: 62% (73654/118796)
Checking out files: 63% (74842/118796)
Checking out files: 64% (76030/118796)
Checking out files: 65% (77218/118796)
Checking out files: 66% (78406/118796)
Checking out files: 67% (79594/118796)
Checking out files: 68% (80782/118796)
Checking out files: 69% (81970/118796)
Checking out files: 70% (83158/118796)
Checking out files: 70% (83859/118796)
Checking out files: 71% (84346/118796)
Checking out files: 72% (85534/118796)
Checking out files: 73% (86722/118796)
Checking out files: 74% (87910/118796)
Checking out files: 75% (89097/118796)
Checking out files: 76% (90285/118796)
Checking out files: 77% (91473/118796)
Checking out files: 78% (92661/118796)
Checking out files: 79% (93849/118796)
Checking out files: 80% (95037/118796)
Checking out files: 81% (96225/118796)
Checking out files: 82% (97413/118796)
Checking out files: 82% (97447/118796)
Checking out files: 83% (98601/118796)
Checking out files: 84% (99789/118796)
Checking out files: 85% (100977/118796)
Checking out files: 86% (102165/118796)
Checking out files: 87% (103353/118796)
Checking out files: 88% (104541/118796)
Checking out files: 89% (105729/118796)
Checking out files: 90% (106917/118796)
Checking out files: 91% (108105/118796)
Checking out files: 92% (109293/118796)
Checking out files: 93% (110481/118796)
Checking out files: 94% (111669/118796)
Checking out files: 95% (112857/118796)
Checking out files: 95% (113416/118796)
Checking out files: 96% (114045/118796)
Checking out files: 97% (115233/118796)
Checking out files: 98% (116421/118796)
Checking out files: 99% (117609/118796)
Checking out files: 100% (118796/118796)
Checking out files: 100% (118796/118796), done.
travis_time:end:1f55a9d0:start=1442247746413860778,finish=1442247765356038094,duration=18942177316
[0K$ cd servo/servo
travis_time:start:004b80a4
[0K$ git fetch origin +refs/pull/7546/merge:
remote: Counting objects: 11, done.[K
remote: Compressing objects: 33% (1/3) [K
remote: Compressing objects: 66% (2/3) [K
remote: Compressing objects: 100% (3/3) [K
remote: Compressing objects: 100% (3/3), done.[K
remote: Total 11 (delta 8), reused 11 (delta 8), pack-reused 0[K
Unpacking objects: 9% (1/11)
Unpacking objects: 18% (2/11)
Unpacking objects: 27% (3/11)
Unpacking objects: 36% (4/11)
Unpacking objects: 45% (5/11)
Unpacking objects: 54% (6/11)
Unpacking objects: 63% (7/11)
Unpacking objects: 72% (8/11)
Unpacking objects: 81% (9/11)
Unpacking objects: 90% (10/11)
Unpacking objects: 100% (11/11)
Unpacking objects: 100% (11/11), done.
From https://github.com/servo/servo
* branch refs/pull/7546/merge -> FETCH_HEAD
travis_time:end:004b80a4:start=1442247765360657996,finish=1442247766168170067,duration=807512071
[0K$ git checkout -qf FETCH_HEAD
travis_fold:end:git.checkout
[0Ktravis_fold:start:git.submodule
[0Ktravis_time:start:3160d4ee
[0K$ git submodule init
Submodule 'support/android-rs-glue' (https://github.com/tomaka/android-rs-glue) registered for path 'support/android-rs-glue'
travis_time:end:3160d4ee:start=1442247767205028590,finish=1442247767688167701,duration=483139111
[0Ktravis_time:start:0aeec95e
[0K$ git submodule update
Cloning into 'support/android-rs-glue'...
remote: Counting objects: 564, done.[K
Receiving objects: 0% (1/564)
Receiving objects: 1% (6/564)
Receiving objects: 2% (12/564)
Receiving objects: 3% (17/564)
Receiving objects: 4% (23/564)
Receiving objects: 5% (29/564)
Receiving objects: 6% (34/564)
Receiving objects: 7% (40/564)
Receiving objects: 8% (46/564)
Receiving objects: 9% (51/564)
Receiving objects: 10% (57/564)
Receiving objects: 11% (63/564)
Receiving objects: 12% (68/564)
Receiving objects: 13% (74/564)
Receiving objects: 14% (79/564)
Receiving objects: 15% (85/564)
Receiving objects: 16% (91/564)
Receiving objects: 17% (96/564)
Receiving objects: 18% (102/564)
Receiving objects: 19% (108/564)
Receiving objects: 20% (113/564)
Receiving objects: 21% (119/564)
Receiving objects: 22% (125/564)
Receiving objects: 23% (130/564)
Receiving objects: 24% (136/564)
Receiving objects: 25% (141/564)
Receiving objects: 26% (147/564)
Receiving objects: 27% (153/564)
Receiving objects: 28% (158/564)
Receiving objects: 29% (164/564)
Receiving objects: 30% (170/564)
remote: Total 564 (delta 0), reused 0 (delta 0), pack-reused 564[K
Receiving objects: 31% (175/564)
Receiving objects: 32% (181/564)
Receiving objects: 33% (187/564)
Receiving objects: 34% (192/564)
Receiving objects: 35% (198/564)
Receiving objects: 36% (204/564)
Receiving objects: 37% (209/564)
Receiving objects: 38% (215/564)
Receiving objects: 39% (220/564)
Receiving objects: 40% (226/564)
Receiving objects: 41% (232/564)
Receiving objects: 42% (237/564)
Receiving objects: 43% (243/564)
Receiving objects: 44% (249/564)
Receiving objects: 45% (254/564)
Receiving objects: 46% (260/564)
Receiving objects: 47% (266/564)
Receiving objects: 48% (271/564)
Receiving objects: 49% (277/564)
Receiving objects: 50% (282/564)
Receiving objects: 51% (288/564)
Receiving objects: 52% (294/564)
Receiving objects: 53% (299/564)
Receiving objects: 54% (305/564)
Receiving objects: 55% (311/564)
Receiving objects: 56% (316/564)
Receiving objects: 57% (322/564)
Receiving objects: 58% (328/564)
Receiving objects: 59% (333/564)
Receiving objects: 60% (339/564)
Receiving objects: 61% (345/564)
Receiving objects: 62% (350/564)
Receiving objects: 63% (356/564)
Receiving objects: 64% (361/564)
Receiving objects: 65% (367/564)
Receiving objects: 66% (373/564)
Receiving objects: 67% (378/564)
Receiving objects: 68% (384/564)
Receiving objects: 69% (390/564)
Receiving objects: 70% (395/564)
Receiving objects: 71% (401/564)
Receiving objects: 72% (407/564)
Receiving objects: 73% (412/564)
Receiving objects: 74% (418/564)
Receiving objects: 75% (423/564)
Receiving objects: 76% (429/564)
Receiving objects: 77% (435/564)
Receiving objects: 78% (440/564)
Receiving objects: 79% (446/564)
Receiving objects: 80% (452/564)
Receiving objects: 81% (457/564)
Receiving objects: 82% (463/564)
Receiving objects: 83% (469/564)
Receiving objects: 84% (474/564)
Receiving objects: 85% (480/564)
Receiving objects: 86% (486/564)
Receiving objects: 87% (491/564)
Receiving objects: 88% (497/564)
Receiving objects: 89% (502/564)
Receiving objects: 90% (508/564)
Receiving objects: 91% (514/564)
Receiving objects: 92% (519/564)
Receiving objects: 93% (525/564)
Receiving objects: 94% (531/564)
Receiving objects: 95% (536/564)
Receiving objects: 96% (542/564)
Receiving objects: 97% (548/564)
Receiving objects: 98% (553/564)
Receiving objects: 99% (559/564)
Receiving objects: 100% (564/564)
Receiving objects: 100% (564/564), 133.16 KiB | 0 bytes/s, done.
Resolving deltas: 0% (0/276)
Resolving deltas: 4% (13/276)
Resolving deltas: 6% (17/276)
Resolving deltas: 16% (45/276)
Resolving deltas: 17% (47/276)
Resolving deltas: 18% (52/276)
Resolving deltas: 19% (53/276)
Resolving deltas: 22% (63/276)
Resolving deltas: 25% (71/276)
Resolving deltas: 27% (76/276)
Resolving deltas: 39% (108/276)
Resolving deltas: 43% (119/276)
Resolving deltas: 53% (147/276)
Resolving deltas: 58% (161/276)
Resolving deltas: 68% (188/276)
Resolving deltas: 69% (191/276)
Resolving deltas: 71% (196/276)
Resolving deltas: 82% (228/276)
Resolving deltas: 85% (235/276)
Resolving deltas: 88% (243/276)
Resolving deltas: 92% (255/276)
Resolving deltas: 95% (263/276)
Resolving deltas: 96% (265/276)
Resolving deltas: 98% (272/276)
Resolving deltas: 100% (276/276)
Resolving deltas: 100% (276/276), done.
Checking connectivity... done.
Submodule path 'support/android-rs-glue': checked out '4ed3cb30b289aa0aa84a00e0d5682bc853108e5a'
travis_time:end:0aeec95e:start=1442247767691765610,finish=1442247768647542158,duration=955776548
[0Ktravis_fold:end:git.submodule
[0K
[33;1mThis job is running on container-based infrastructure, which does not allow use of 'sudo', setuid and setguid executables.[0m
[33;1mIf you require sudo, add 'sudo: required' to your .travis.yml[0m
[33;1mSee http://docs.travis-ci.com/user/workers/container-based-infrastructure/ for details.[0m
travis_time:start:12ea261e
[0K$ source ~/virtualenv/python2.7/bin/activate
travis_time:end:12ea261e:start=1442247771676023426,finish=1442247771679776983,duration=3753557
[0K$ python --version
Python 2.7.9
$ pip --version
pip 6.0.7 from /home/travis/virtualenv/python2.7.9/lib/python2.7/site-packages (python 2.7)
Could not locate requirements.txt. Override the install: key in your .travis.yml to install dependencies.
travis_time:start:0d35b7a4
[0K$ ./mach test-tidy
Running virtualenv with interpreter /home/travis/virtualenv/python2.7.9/bin/python2
Using real prefix '/opt/python/2.7.9'
New python executable in /home/travis/build/servo/servo/python/_virtualenv/bin/python2
Also creating executable in /home/travis/build/servo/servo/python/_virtualenv/bin/python
Installing setuptools, pip...done.
[33mYou are using pip version 6.0.7, however version 7.1.2 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.[0m
[33mYou are using pip version 6.0.7, however version 7.1.2 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.[0m
[33mYou are using pip version 6.0.7, however version 7.1.2 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.[0m
[94m./components/plugins/lints/sorter.rs[0m:[93m49[0m: [91mmissing space before {[0m
travis_time:end:0d35b7a4:start=1442247773149902192,finish=1442247792879237673,duration=19729335481
[0K
[31;1mThe command "./mach test-tidy" exited with 1.[0m
Done. Your build exited with 1.
\ No newline at end of file
diff --git a/resources/test-data-lowfive.json b/resources/test-data-lowfive.json
new file mode 100644
index 0000000..f43ebd5
--- /dev/null
+++ b/resources/test-data-lowfive.json
@@ -0,0 +1,7 @@
+{
+ "name": "JoshTheGoldfish/lowfive",
+ "target_url": "https://travis-ci.org/servo/servo/builds/74856035",
+ "commit": {
+ "sha": "9b6313fd5ab92de5a3fd9f13f8421a929b2a8ef6",
+ }
+}
\ No newline at end of file
diff --git a/test_comment.json b/resources/test_comment.json
similarity index 100%
rename from test_comment.json
rename to resources/test_comment.json
diff --git a/test_ignored_action.json b/resources/test_ignored_action.json
similarity index 100%
rename from test_ignored_action.json
rename to resources/test_ignored_action.json
diff --git a/test_merge_approved.json b/resources/test_merge_approved.json
similarity index 100%
rename from test_merge_approved.json
rename to resources/test_merge_approved.json
diff --git a/test_merge_conflict.json b/resources/test_merge_conflict.json
similarity index 100%
rename from test_merge_conflict.json
rename to resources/test_merge_conflict.json
diff --git a/test_new_pr.json b/resources/test_new_pr.json
similarity index 100%
rename from test_new_pr.json
rename to resources/test_new_pr.json
diff --git a/test_post_retry.json b/resources/test_post_retry.json
similarity index 100%
rename from test_post_retry.json
rename to resources/test_post_retry.json
diff --git a/test_synchronize.json b/resources/test_synchronize.json
similarity index 100%
rename from test_synchronize.json
rename to resources/test_synchronize.json
diff --git a/test_tests_failed.json b/resources/test_tests_failed.json
similarity index 100%
rename from test_tests_failed.json
rename to resources/test_tests_failed.json
diff --git a/resources/test_travis_payload.json b/resources/test_travis_payload.json
new file mode 100644
index 0000000..80b322e
--- /dev/null
+++ b/resources/test_travis_payload.json
@@ -0,0 +1,206 @@
+{
+ "id": 281385646,
+ "sha": "9b6313fd5ab92de5a3fd9f13f8421a929b2a8ef6",
+ "name": "servo/servo",
+ "target_url": "https://travis-ci.org/servo/servo/builds/74856035",
+ "context": "continuous-integration/travis-ci/pr",
+ "description": "The Travis CI build failed",
+ "state": "failure",
+ "commit": {
+ "sha": "9b6313fd5ab92de5a3fd9f13f8421a929b2a8ef6",
+ "commit": {
+ "author": {
+ "name": "Fabrice Desré",
+ "email": "fabrice@desre.org",
+ "date": "2015-08-10T02:52:05Z"
+ },
+ "committer": {
+ "name": "Fabrice Desré",
+ "email": "fabrice@desre.org",
+ "date": "2015-08-10T02:52:05Z"
+ },
+ "message": "merging master",
+ "tree": {
+ "sha": "f8acfc1d29d1d9c9024e9e4785d7877e3d09d631",
+ "url": "https://api.github.com/repos/servo/servo/git/trees/f8acfc1d29d1d9c9024e9e4785d7877e3d09d631"
+ },
+ "url": "https://api.github.com/repos/servo/servo/git/commits/9b6313fd5ab92de5a3fd9f13f8421a929b2a8ef6",
+ "comment_count": 0
+ },
+ "url": "https://api.github.com/repos/servo/servo/commits/9b6313fd5ab92de5a3fd9f13f8421a929b2a8ef6",
+ "html_url": "https://github.com/servo/servo/commit/9b6313fd5ab92de5a3fd9f13f8421a929b2a8ef6",
+ "comments_url": "https://api.github.com/repos/servo/servo/commits/9b6313fd5ab92de5a3fd9f13f8421a929b2a8ef6/comments",
+ "author": {
+ "login": "fabricedesre",
+ "id": 984767,
+ "avatar_url": "https://avatars.githubusercontent.com/u/984767?v=3",
+ "gravatar_id": "",
+ "url": "https://api.github.com/users/fabricedesre",
+ "html_url": "https://github.com/fabricedesre",
+ "followers_url": "https://api.github.com/users/fabricedesre/followers",
+ "following_url": "https://api.github.com/users/fabricedesre/following{/other_user}",
+ "gists_url": "https://api.github.com/users/fabricedesre/gists{/gist_id}",
+ "starred_url": "https://api.github.com/users/fabricedesre/starred{/owner}{/repo}",
+ "subscriptions_url": "https://api.github.com/users/fabricedesre/subscriptions",
+ "organizations_url": "https://api.github.com/users/fabricedesre/orgs",
+ "repos_url": "https://api.github.com/users/fabricedesre/repos",
+ "events_url": "https://api.github.com/users/fabricedesre/events{/privacy}",
+ "received_events_url": "https://api.github.com/users/fabricedesre/received_events",
+ "type": "User",
+ "site_admin": false
+ },
+ "committer": {
+ "login": "fabricedesre",
+ "id": 984767,
+ "avatar_url": "https://avatars.githubusercontent.com/u/984767?v=3",
+ "gravatar_id": "",
+ "url": "https://api.github.com/users/fabricedesre",
+ "html_url": "https://github.com/fabricedesre",
+ "followers_url": "https://api.github.com/users/fabricedesre/followers",
+ "following_url": "https://api.github.com/users/fabricedesre/following{/other_user}",
+ "gists_url": "https://api.github.com/users/fabricedesre/gists{/gist_id}",
+ "starred_url": "https://api.github.com/users/fabricedesre/starred{/owner}{/repo}",
+ "subscriptions_url": "https://api.github.com/users/fabricedesre/subscriptions",
+ "organizations_url": "https://api.github.com/users/fabricedesre/orgs",
+ "repos_url": "https://api.github.com/users/fabricedesre/repos",
+ "events_url": "https://api.github.com/users/fabricedesre/events{/privacy}",
+ "received_events_url": "https://api.github.com/users/fabricedesre/received_events",
+ "type": "User",
+ "site_admin": false
+ },
+ "parents": [
+ {
+ "sha": "d90f28648c9e56d73fdd65026b01a820ab154890",
+ "url": "https://api.github.com/repos/servo/servo/commits/d90f28648c9e56d73fdd65026b01a820ab154890",
+ "html_url": "https://github.com/servo/servo/commit/d90f28648c9e56d73fdd65026b01a820ab154890"
+ },
+ {
+ "sha": "f77973c903a3e08067feed3ba39cff3c6bf8ac11",
+ "url": "https://api.github.com/repos/servo/servo/commits/f77973c903a3e08067feed3ba39cff3c6bf8ac11",
+ "html_url": "https://github.com/servo/servo/commit/f77973c903a3e08067feed3ba39cff3c6bf8ac11"
+ }
+ ]
+ },
+ "branches": [
+
+ ],
+ "created_at": "2015-08-10T02:53:52Z",
+ "updated_at": "2015-08-10T02:53:52Z",
+ "repository": {
+ "id": 3390243,
+ "name": "servo",
+ "full_name": "servo/servo",
+ "owner": {
+ "login": "servo",
+ "id": 2566135,
+ "avatar_url": "https://avatars.githubusercontent.com/u/2566135?v=3",
+ "gravatar_id": "",
+ "url": "https://api.github.com/users/servo",
+ "html_url": "https://github.com/servo",
+ "followers_url": "https://api.github.com/users/servo/followers",
+ "following_url": "https://api.github.com/users/servo/following{/other_user}",
+ "gists_url": "https://api.github.com/users/servo/gists{/gist_id}",
+ "starred_url": "https://api.github.com/users/servo/starred{/owner}{/repo}",
+ "subscriptions_url": "https://api.github.com/users/servo/subscriptions",
+ "organizations_url": "https://api.github.com/users/servo/orgs",
+ "repos_url": "https://api.github.com/users/servo/repos",
+ "events_url": "https://api.github.com/users/servo/events{/privacy}",
+ "received_events_url": "https://api.github.com/users/servo/received_events",
+ "type": "Organization",
+ "site_admin": false
+ },
+ "private": false,
+ "html_url": "https://github.com/servo/servo",
+ "description": "The Servo Browser Engine",
+ "fork": false,
+ "url": "https://api.github.com/repos/servo/servo",
+ "forks_url": "https://api.github.com/repos/servo/servo/forks",
+ "keys_url": "https://api.github.com/repos/servo/servo/keys{/key_id}",
+ "collaborators_url": "https://api.github.com/repos/servo/servo/collaborators{/collaborator}",
+ "teams_url": "https://api.github.com/repos/servo/servo/teams",
+ "hooks_url": "https://api.github.com/repos/servo/servo/hooks",
+ "issue_events_url": "https://api.github.com/repos/servo/servo/issues/events{/number}",
+ "events_url": "https://api.github.com/repos/servo/servo/events",
+ "assignees_url": "https://api.github.com/repos/servo/servo/assignees{/user}",
+ "branches_url": "https://api.github.com/repos/servo/servo/branches{/branch}",
+ "tags_url": "https://api.github.com/repos/servo/servo/tags",
+ "blobs_url": "https://api.github.com/repos/servo/servo/git/blobs{/sha}",
+ "git_tags_url": "https://api.github.com/repos/servo/servo/git/tags{/sha}",
+ "git_refs_url": "https://api.github.com/repos/servo/servo/git/refs{/sha}",
+ "trees_url": "https://api.github.com/repos/servo/servo/git/trees{/sha}",
+ "statuses_url": "https://api.github.com/repos/servo/servo/statuses/{sha}",
+ "languages_url": "https://api.github.com/repos/servo/servo/languages",
+ "stargazers_url": "https://api.github.com/repos/servo/servo/stargazers",
+ "contributors_url": "https://api.github.com/repos/servo/servo/contributors",
+ "subscribers_url": "https://api.github.com/repos/servo/servo/subscribers",
+ "subscription_url": "https://api.github.com/repos/servo/servo/subscription",
+ "commits_url": "https://api.github.com/repos/servo/servo/commits{/sha}",
+ "git_commits_url": "https://api.github.com/repos/servo/servo/git/commits{/sha}",
+ "comments_url": "https://api.github.com/repos/servo/servo/comments{/number}",
+ "issue_comment_url": "https://api.github.com/repos/servo/servo/issues/comments{/number}",
+ "contents_url": "https://api.github.com/repos/servo/servo/contents/{+path}",
+ "compare_url": "https://api.github.com/repos/servo/servo/compare/{base}...{head}",
+ "merges_url": "https://api.github.com/repos/servo/servo/merges",
+ "archive_url": "https://api.github.com/repos/servo/servo/{archive_format}{/ref}",
+ "downloads_url": "https://api.github.com/repos/servo/servo/downloads",
+ "issues_url": "https://api.github.com/repos/servo/servo/issues{/number}",
+ "pulls_url": "https://api.github.com/repos/servo/servo/pulls{/number}",
+ "milestones_url": "https://api.github.com/repos/servo/servo/milestones{/number}",
+ "notifications_url": "https://api.github.com/repos/servo/servo/notifications{?since,all,participating}",
+ "labels_url": "https://api.github.com/repos/servo/servo/labels{/name}",
+ "releases_url": "https://api.github.com/repos/servo/servo/releases{/id}",
+ "created_at": "2012-02-08T19:07:25Z",
+ "updated_at": "2015-08-09T23:35:17Z",
+ "pushed_at": "2015-08-10T02:53:32Z",
+ "git_url": "git://github.com/servo/servo.git",
+ "ssh_url": "git@github.com:servo/servo.git",
+ "clone_url": "https://github.com/servo/servo.git",
+ "svn_url": "https://github.com/servo/servo",
+ "homepage": "",
+ "size": 204624,
+ "stargazers_count": 4584,
+ "watchers_count": 4584,
+ "language": "Rust",
+ "has_issues": true,
+ "has_downloads": true,
+ "has_wiki": true,
+ "has_pages": false,
+ "forks_count": 722,
+ "mirror_url": null,
+ "open_issues_count": 1049,
+ "forks": 722,
+ "open_issues": 1049,
+ "watchers": 4584,
+ "default_branch": "master"
+ },
+ "organization": {
+ "login": "servo",
+ "id": 2566135,
+ "url": "https://api.github.com/orgs/servo",
+ "repos_url": "https://api.github.com/orgs/servo/repos",
+ "events_url": "https://api.github.com/orgs/servo/events",
+ "members_url": "https://api.github.com/orgs/servo/members{/member}",
+ "public_members_url": "https://api.github.com/orgs/servo/public_members{/member}",
+ "avatar_url": "https://avatars.githubusercontent.com/u/2566135?v=3",
+ "description": null
+ },
+ "sender": {
+ "login": "mrobinson",
+ "id": 13536,
+ "avatar_url": "https://avatars.githubusercontent.com/u/13536?v=3",
+ "gravatar_id": "",
+ "url": "https://api.github.com/users/mrobinson",
+ "html_url": "https://github.com/mrobinson",
+ "followers_url": "https://api.github.com/users/mrobinson/followers",
+ "following_url": "https://api.github.com/users/mrobinson/following{/other_user}",
+ "gists_url": "https://api.github.com/users/mrobinson/gists{/gist_id}",
+ "starred_url": "https://api.github.com/users/mrobinson/starred{/owner}{/repo}",
+ "subscriptions_url": "https://api.github.com/users/mrobinson/subscriptions",
+ "organizations_url": "https://api.github.com/users/mrobinson/orgs",
+ "repos_url": "https://api.github.com/users/mrobinson/repos",
+ "events_url": "https://api.github.com/users/mrobinson/events{/privacy}",
+ "received_events_url": "https://api.github.com/users/mrobinson/received_events",
+ "type": "User",
+ "site_admin": false
+ }
+}
\ No newline at end of file
diff --git a/resources/unsafe.diff b/resources/unsafe.diff
new file mode 100644
index 0000000..a047d74
--- /dev/null
+++ b/resources/unsafe.diff
@@ -0,0 +1,223 @@
+diff --git a/components/script/dom/domexception.rs b/components/script/dom/domexception.rs
+index 1975128..e63cbed 100644
+--- a/components/script/dom/domexception.rs
++++ b/components/script/dom/domexception.rs
+@@ -104,4 +104,9 @@ impl DOMExceptionMethods for DOMException {
+
+ message.to_owned()
+ }
++
++ // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-error.prototype.tostring
++ fn Stringifier(&self) -> String {
++ format!("{}: {}", self.Name(), self.Message())
++ }
+ }
+diff --git a/components/script/dom/htmlbuttonelement.rs b/components/script/dom/htmlbuttonelement.rs
+index 075df00..7023a8f 100644
+--- a/components/script/dom/htmlbuttonelement.rs
++++ b/components/script/dom/htmlbuttonelement.rs
+@@ -15,7 +15,7 @@ use dom::event::Event;
+ use dom::eventtarget::{EventTarget, EventTargetTypeId};
+ use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
+ use dom::htmlformelement::{FormControl, FormSubmitter};
+-use dom::htmlformelement::{SubmittedFrom};
++use dom::htmlformelement::{SubmittedFrom, HTMLFormElement};
+ use dom::node::{Node, NodeTypeId, document_from_node, window_from_node};
+ use dom::validitystate::ValidityState;
+ use dom::virtualmethods::VirtualMethods;
+@@ -82,6 +82,11 @@ impl HTMLButtonElementMethods for HTMLButtonElement {
+ // https://www.whatwg.org/html/#dom-fe-disabled
+ make_bool_setter!(SetDisabled, "disabled");
+
++ // https://html.spec.whatwg.org/multipage#dom-fae-form
++ fn GetForm(&self) -> Option> {
++ self.form_owner()
++ }
++
+ // https://html.spec.whatwg.org/multipage/#dom-button-type
+ fn Type(&self) -> DOMString {
+ let elem = ElementCast::from_ref(self);
+diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs
+index 0de98f1..33098d1 100644
+--- a/components/script/dom/htmlinputelement.rs
++++ b/components/script/dom/htmlinputelement.rs
+@@ -229,6 +229,11 @@ impl HTMLInputElementMethods for HTMLInputElement {
+ // https://www.whatwg.org/html/#dom-fe-disabled
+ make_bool_setter!(SetDisabled, "disabled");
+
++ // https://html.spec.whatwg.org/multipage/#dom-fae-form
++ fn GetForm(&self) -> Option> {
++ self.form_owner()
++ }
++
+ // https://html.spec.whatwg.org/multipage/#dom-input-defaultchecked
+ make_bool_getter!(DefaultChecked, "checked");
+
+diff --git a/components/script/dom/webidls/DOMException.webidl b/components/script/dom/webidls/DOMException.webidl
+index 7c09054..0dfb714 100644
+--- a/components/script/dom/webidls/DOMException.webidl
++++ b/components/script/dom/webidls/DOMException.webidl
+@@ -44,4 +44,6 @@ interface DOMException {
+
+ // A custom message set by the thrower.
+ readonly attribute DOMString message;
++
++ stringifier;
+ };
+diff --git a/components/script/dom/webidls/HTMLButtonElement.webidl b/components/script/dom/webidls/HTMLButtonElement.webidl
+index 7613bd5..73eec85 100644
+--- a/components/script/dom/webidls/HTMLButtonElement.webidl
++++ b/components/script/dom/webidls/HTMLButtonElement.webidl
+@@ -7,7 +7,7 @@
+ interface HTMLButtonElement : HTMLElement {
+ // attribute boolean autofocus;
+ attribute boolean disabled;
+- //readonly attribute HTMLFormElement? form;
++ readonly attribute HTMLFormElement? form;
+ attribute DOMString formAction;
+ attribute DOMString formEnctype;
+ attribute DOMString formMethod;
+diff --git a/components/script/dom/webidls/HTMLInputElement.webidl b/components/script/dom/webidls/HTMLInputElement.webidl
+index afd605b..60c45c4 100644
+--- a/components/script/dom/webidls/HTMLInputElement.webidl
++++ b/components/script/dom/webidls/HTMLInputElement.webidl
+@@ -13,7 +13,7 @@ interface HTMLInputElement : HTMLElement {
+ attribute boolean checked;
+ // attribute DOMString dirName;
+ attribute boolean disabled;
+- //readonly attribute HTMLFormElement? form;
++ readonly attribute HTMLFormElement? form;
+ //readonly attribute FileList? files;
+ attribute DOMString formAction;
+ attribute DOMString formEnctype;
+diff --git a/components/script/script_task.rs b/components/script/script_task.rs
+index d8ac155..96ff1a7 100644
+--- a/components/script/script_task.rs
++++ b/components/script/script_task.rs
+@@ -55,6 +55,7 @@ use js::jsapi::{DisableIncrementalGC, JS_AddExtraGCRootsTracer, JS_SetWrapObject
+ use js::jsapi::{GCDescription, GCProgress, JSGCInvocationKind, SetGCSliceCallback};
+ use js::jsapi::{JSAutoRequest, JSGCStatus, JS_GetRuntime, JS_SetGCCallback, SetDOMCallbacks};
+ use js::jsapi::{JSContext, JSRuntime, JSTracer};
++use js::jsapi::{JSObject, SetPreserveWrapperCallback};
+ use js::jsval::UndefinedValue;
+ use js::rust::Runtime;
+ use layout_interface::{ReflowQueryType};
+@@ -654,8 +655,10 @@ impl ScriptTask {
+ }
+
+ unsafe {
++ unsafe extern "C" fn empty_wrapper_callback(_: *mut JSContext, _: *mut JSObject) -> u8 { 1 }
+ SetDOMProxyInformation(ptr::null(), 0, Some(shadow_check_callback));
+ SetDOMCallbacks(runtime.rt(), &DOM_CALLBACKS);
++ SetPreserveWrapperCallback(runtime.rt(), Some(empty_wrapper_callback));
+ // Pre barriers aren't working correctly at the moment
+ DisableIncrementalGC(runtime.rt());
+ }
+diff --git a/tests/wpt/metadata/html/dom/interfaces.html.ini b/tests/wpt/metadata/html/dom/interfaces.html.ini
+index 29648c3..f4b6a11 100644
+--- a/tests/wpt/metadata/html/dom/interfaces.html.ini
++++ b/tests/wpt/metadata/html/dom/interfaces.html.ini
+@@ -5079,9 +5079,6 @@
+ [HTMLInputElement interface: attribute dirName]
+ expected: FAIL
+
+- [HTMLInputElement interface: attribute form]
+- expected: FAIL
+-
+ [HTMLInputElement interface: attribute files]
+ expected: FAIL
+
+@@ -5208,9 +5205,6 @@
+ [HTMLInputElement interface: document.createElement("input") must inherit property "dirName" with the proper type (6)]
+ expected: FAIL
+
+- [HTMLInputElement interface: document.createElement("input") must inherit property "form" with the proper type (8)]
+- expected: FAIL
+-
+ [HTMLInputElement interface: document.createElement("input") must inherit property "files" with the proper type (9)]
+ expected: FAIL
+
+@@ -5346,9 +5340,6 @@
+ [HTMLButtonElement interface: attribute autofocus]
+ expected: FAIL
+
+- [HTMLButtonElement interface: attribute form]
+- expected: FAIL
+-
+ [HTMLButtonElement interface: attribute formNoValidate]
+ expected: FAIL
+
+@@ -5376,9 +5367,6 @@
+ [HTMLButtonElement interface: document.createElement("button") must inherit property "autofocus" with the proper type (0)]
+ expected: FAIL
+
+- [HTMLButtonElement interface: document.createElement("button") must inherit property "form" with the proper type (2)]
+- expected: FAIL
+-
+ [HTMLButtonElement interface: document.createElement("button") must inherit property "formNoValidate" with the proper type (6)]
+ expected: FAIL
+
+diff --git a/tests/ref/metadata/html/semantics/forms/form-control-infrastructure/form.html.ini b/tests/wpt/metadata/html/semantics/forms/form-control-infrastructure/form.html.ini
+index 4ba6553..3a4b932 100644
+--- a/tests/wpt/metadata/html/semantics/forms/form-control-infrastructure/form.html.ini
++++ b/tests/wpt/metadata/html/semantics/forms/form-control-infrastructure/form.html.ini
+@@ -1,14 +1,8 @@
+ [form.html]
+ type: testharness
+- [button.form]
+- expected: FAIL
+-
+ [fieldset.form]
+ expected: FAIL
+
+- [input.form]
+- expected: FAIL
+-
+ [keygen.form]
+ expected: FAIL
+
+diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json
+index 3cd5550..1449da6 100644
+--- a/tests/wpt/mozilla/meta/MANIFEST.json
++++ b/tests/wpt/mozilla/meta/MANIFEST.json
+@@ -749,6 +749,12 @@
+ "url": "/_mozilla/mozilla/parentnodes.html"
+ }
+ ],
++ "mozilla/preserve_wrapper_callback.html": [
++ {
++ "path": "mozilla/preserve_wrapper_callback.html",
++ "url": "/_mozilla/mozilla/preserve_wrapper_callback.html"
++ }
++ ],
+ "mozilla/proxy_setter.html": [
+ {
+ "path": "mozilla/proxy_setter.html",
+diff --git a/tests/wpt/mozilla/tests/mozilla/preserve_wrapper_callback.html b/tests/wpt/mozilla/tests/mozilla/preserve_wrapper_callback.html
+new file mode 100644
+index 0000000..13369b9
+--- /dev/null
++++ b/tests/wpt/mozilla/tests/mozilla/preserve_wrapper_callback.html
+@@ -0,0 +1,22 @@
++
++
++
++
++
++
++
++
++
++
\ No newline at end of file
diff --git a/test.py b/test.py
index 8917538..52c5a18 100644
--- a/test.py
+++ b/test.py
@@ -1,124 +1,527 @@
-from newpr import APIProvider, handle_payload
+from errorlogparser import ErrorLogParser, ServoErrorLogParser
+from githubapiprovider import APIProvider, GithubApiProvider
+from mock import call, Mock, patch
+from payloadhandler import PayloadHandler, GithubPayloadHandler, TravisPayloadHandler
+from travisciapiprovider import TravisCiApiProvider
import json
import os
+import payloadreceiver
import sys
import traceback
+import unittest
+import urlparse
-class TestAPIProvider(APIProvider):
- def __init__(self, payload, user, new_contributor, labels, assignee, diff=""):
- APIProvider.__init__(self, payload, user)
- self.new_contributor = new_contributor
- self.comments_posted = []
- self.labels = labels
- self.assignee = assignee
- self.diff = diff
-
- def is_new_contributor(self, username):
- return self.new_contributor
-
- def post_comment(self, body):
- self.comments_posted += [body]
-
- def add_label(self, label):
- self.labels += [label]
-
- def remove_label(self, label):
- self.labels.remove(label)
-
- def get_labels(self):
- return self.labels
-
- def get_diff(self):
- return self.diff
-
- def set_assignee(self, assignee):
- self.assignee = assignee
-
-def get_payload(filename):
- with open(filename) as f:
- return json.load(f)
-
-tests = []
-def add_test(filename, initial, expected):
- global tests
- initial_values = {'new_contributor': initial.get('new_contributor', False),
- 'labels': initial.get('labels', []),
- 'diff': initial.get('diff', ''),
- 'assignee': initial.get('assignee', None)}
- expected_values = {'labels': expected.get('labels', []),
- 'assignee': expected.get('assignee', None),
- 'comments': expected.get('comments', 0)}
- tests += [{'filename': filename,
- 'initial': initial_values,
- 'expected': expected_values}]
-
-def run_tests(tests):
- failed = 0
- for test in tests:
- try:
- payload = get_payload(test['filename'])
- initial = test['initial']
- api = TestAPIProvider(payload, 'highfive', initial['new_contributor'], initial['labels'],
- initial['assignee'], initial['diff'])
- handle_payload(api, payload)
- expected = test['expected']
- assert len(api.comments_posted) == expected['comments']
- assert api.labels == expected['labels']
- assert api.assignee == expected['assignee']
- except AssertionError:
- _, _, tb = sys.exc_info()
- traceback.print_tb(tb) # Fixed format
- tb_info = traceback.extract_tb(tb)
- filename, line, func, text = tb_info[-1]
- print('{}: An error occurred on line {} in statement {}'.format(test['filename'], line, text))
- failed += 1
-
- possible_tests = [f for f in os.listdir('.') if f.endswith('.json')]
- test_files = set([test['filename'] for test in tests])
- if len(possible_tests) != len(test_files):
- print 'Found unused JSON test data: %s' % ', '.join(filter(lambda x: x not in test_files, possible_tests))
- sys.exit(1)
- print 'Ran %d tests, %d failed' % (len(tests), failed)
-
- if failed:
- sys.exit(1)
-
-add_test('test_new_pr.json', {'new_contributor': True},
- {'labels': ['S-awaiting-review'], 'comments': 1})
-
-add_test('test_new_pr.json', {'diff': "+ unsafe fn foo()"},
- {'labels': ['S-awaiting-review'], 'comments': 1})
-
-add_test('test_new_pr.json', {'diff': "diff --git components/layout/"},
- {'labels': ['S-awaiting-review'], 'comments': 1})
-
-add_test('test_new_pr.json', {'diff': "diff --git components/layout/\ndiff --git tests/wpt"},
- {'labels': ['S-awaiting-review'], 'comments': 0})
-
-add_test('test_new_pr.json', {'new_contributor': True},
- {'labels': ['S-awaiting-review'], 'comments': 1})
-
-add_test('test_ignored_action.json', {}, {})
-
-add_test('test_synchronize.json', {'labels': ['S-needs-code-changes', 'S-tests-failed', 'S-awaiting-merge']},
- {'labels': ['S-awaiting-review']})
-
-add_test('test_comment.json', {}, {'assignee': 'jdm'})
-
-add_test('test_merge_approved.json', {'labels': ['S-needs-code-changes', 'S-needs-rebase',
- 'S-tests-failed', 'S-needs-squash',
- 'S-awaiting-review']}, {'labels': ['S-awaiting-merge']})
-
-add_test('test_merge_conflict.json', {'labels': ['S-awaiting-merge']},
- {'labels': ['S-needs-rebase']})
-
-add_test('test_tests_failed.json', {'labels': ['S-awaiting-merge']},
- {'labels': ['S-tests-failed']})
-
-add_test('test_post_retry.json', {'labels': ['S-tests-failed']},
- {'labels': ['S-awaiting-merge']})
-
-add_test('test_post_retry.json', {'labels': ['S-awaiting-merge']},
- {'labels': ['S-awaiting-merge']})
-
-run_tests(tests)
+error_sample = [
+ {
+ "body": "use statement is not in alphabetical order\nexpected: dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull\nfound: dom::bindings::conversions::get_dom_class",
+ "position": 7,
+ "path": "./components/script/dom/eventtarget.rs"
+ },
+ {
+ "body": "use statement is not in alphabetical order\nexpected: dom::bindings::codegen::Bindings::EventListenerBinding::EventListener\nfound: dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull",
+ "position": 8,
+ "path": "./components/script/dom/eventtarget.rs"
+ },
+ {
+ "body": "use statement is not in alphabetical order\nexpected: dom::bindings::codegen::Bindings::EventTargetBinding::EventTargetMethods\nfound: dom::bindings::codegen::Bindings::EventListenerBinding::EventListener",
+ "position": 9,
+ "path": "./components/script/dom/eventtarget.rs"
+ },
+ {
+ "body": "use statement is not in alphabetical order\nexpected: dom::bindings::conversions::get_dom_class\nfound: dom::bindings::codegen::Bindings::EventTargetBinding::EventTargetMethods",
+ "position": 10,
+ "path": "./components/script/dom/eventtarget.rs"
+ },
+ {
+ "body": "use statement is not in alphabetical order\nexpected: dom::browsercontext\nfound: dom::eventtarget::EventTargetTypeId",
+ "position": 17,
+ "path": "./components/script/dom/bindings/utils.rs"
+ },
+ {
+ "body": "use statement is not in alphabetical order\nexpected: dom::eventtarget::EventTargetTypeId\nfound: dom::browsercontext",
+ "position": 18,
+ "path": "./components/script/dom/bindings/utils.rs"
+ }
+]
+
+
+class TestAPIProvider(unittest.TestCase):
+ def setUp(self):
+ self.api_provider = APIProvider('jdm')
+
+
+ def test_is_new_contributor(self):
+ with self.assertRaises(NotImplementedError):
+ self.api_provider.is_new_contributor("jdm")
+
+
+ def test_post_comment(self):
+ with self.assertRaises(NotImplementedError):
+ self.api_provider.post_comment("Nice job!", 3947)
+
+
+ def test_post_review_comment(self):
+ with self.assertRaises(NotImplementedError):
+ self.api_provider.post_review_comment(1234, "a453b3923e893f0383cd2893f...", "foo/bar/spam/eggs", 3, "Remove extra space")
+
+
+ def test_add_label(self):
+ with self.assertRaises(NotImplementedError):
+ self.api_provider.add_label("S-awaiting-review", 1234)
+
+
+ def test_remove_label(self):
+ with self.assertRaises(NotImplementedError):
+ self.api_provider.remove_label("S-awaiting-review", 1234)
+
+
+ def test_get_labels(self):
+ with self.assertRaises(NotImplementedError):
+ self.api_provider.get_labels(1234)
+
+
+ def test_get_diff(self):
+ with self.assertRaises(NotImplementedError):
+ self.api_provider.get_diff("https://github.com/servo/servo/pull/1234.diff")
+
+
+ def test_set_assignee(self):
+ with self.assertRaises(NotImplementedError):
+ self.api_provider.set_assignee("jdm", 1234)
+
+
+# TODO - add tests for exception handling
+class TestGithubApiProvider(unittest.TestCase):
+ class FakeHeader():
+ def get(self, something):
+ pass
+
+ def setUp(self):
+ self.owner = "servo"
+ self.repo = "servo"
+ self.gh_provider = GithubApiProvider("jdm", "a453b3923e893f0383cd2893f...", self.owner, self.repo)
+ self.gh_provider.api_req = Mock()
+
+ @patch('githubapiprovider.gzip')
+ @patch('githubapiprovider.urllib2')
+ @patch('githubapiprovider.base64')
+ def test_api_req(self, base64_mock, urllib_mock, gzip_mock):
+ class FakeRequest():
+ def __init__(self):
+ self.headers = {}
+ self.get_method = lambda: "Get"
+
+
+ def add_header(self, key, value):
+ self.headers[key] = value
+
+
+ class FakeHeader():
+ def get(self, type):
+ return 'gzip'
+
+
+ class FakeResponse():
+ def __init__(self, fakeHeader):
+ self.header = fakeHeader
+
+
+ def info(self):
+ return self.header
+
+
+ def read(self):
+ return "content"
+
+ faux_request = FakeRequest()
+ base64_mock.standard_b64encode = Mock(return_value="User:Token")
+ urllib_mock.Request = Mock(return_value=faux_request)
+ urllib_mock.urlopen = Mock(return_value=FakeResponse(FakeHeader()))
+ gzip_mock.GzipFile = Mock(return_value=FakeResponse(FakeHeader()))
+ gh_provider = GithubApiProvider("jdm", "a453b3923e893f0383cd2893f...", self.owner, self.repo)
+ gh_provider.api_req("GET", "https://api.github.com/repos/servo/servo/contributors", data={"test":"data"}, media_type="Test Media")
+ urllib_mock.Request.assert_called_with("https://api.github.com/repos/servo/servo/contributors", json.dumps({"test":"data"}), {'Content-Type':'application/json'})
+ urllib_mock.urlopen.assert_called_with(faux_request)
+
+
+ def test_parse_header_links(self):
+ header_links = '; rel="next", ; rel="last"'
+ expected = {'last': 'https://api.github.com/repos/servo/servo/contributors?page=11', 'next': 'https://api.github.com/repos/servo/servo/contributors?page=2'}
+ gh_provider = GithubApiProvider("jdm", "a453b3923e893f0383cd2893f...", self.owner, self.repo)
+ self.assertEquals(expected, gh_provider.parse_header_links(header_links))
+ self.assertEquals(None, gh_provider.parse_header_links(None))
+
+
+ def test_is_new_contributor(self):
+ next_page_url = "https://api.github.com/repos/servo/servo/contributors?page=2"
+ calls = [call("GET", GithubApiProvider.contributors_url % (self.owner, self.repo)), call("GET", next_page_url)]
+
+ gh_provider = GithubApiProvider("jdm", "a453b3923e893f0383cd2893f...", self.owner, self.repo)
+ gh_provider.api_req = Mock(side_effect = [{ "header": self.FakeHeader(), "body": json.dumps([{"login":"Ms2ger"}])}, { "header": self.FakeHeader(), "body": json.dumps([{"login":"jdm"}, {"login":"Ms2ger"}])}] )
+ gh_provider.parse_header_links = Mock(side_effect = [{"next": next_page_url}, ""])
+
+ self.assertFalse(gh_provider.is_new_contributor("jdm"))
+
+ gh_provider.api_req = Mock(side_effect = [{ "header": self.FakeHeader(), "body": json.dumps([{"login":"Ms2ger"}])}, { "header": self.FakeHeader(), "body": json.dumps([{"login":"jdm"}, {"login":"Ms2ger"}])}] )
+ gh_provider.parse_header_links = Mock(side_effect = [{"next": next_page_url}, ""])
+
+ self.assertTrue(gh_provider.is_new_contributor("JoshTheGoldfish"))
+
+
+ gh_provider.api_req.assert_has_calls(calls)
+
+
+ def test_post_comment(self):
+ body = "Great job!"
+ issue_num = 1
+ self.gh_provider.post_comment(body, issue_num)
+
+ self.gh_provider.api_req.assert_called_with("POST", GithubApiProvider.post_comment_url % (self.owner, self.repo, issue_num), {"body":body})
+
+
+ def test_post_review_comment(self):
+ pr_num = 1
+ commit_id = "a453b3923e893f0383cd2893f..."
+ path = "./file/path",
+ pos = 1
+ body = "Revmoe extra newline"
+ self.gh_provider.post_review_comment(pr_num, commit_id, path, pos, body)
+
+ self.gh_provider.api_req.assert_called_with("POST", GithubApiProvider.review_comment_url % (self.owner, self.repo, pr_num), {"body": body, "commit_id":commit_id, "path":path, "position":pos})
+
+
+ def test_add_label(self):
+ label = "S-awaiting-review"
+ issue_num = 1
+ self.gh_provider.add_label(label, issue_num)
+
+ self.gh_provider.api_req.assert_called_with("POST", GithubApiProvider.add_label_url % (self.owner, self.repo, issue_num), [label])
+
+
+ def test_remove_label(self):
+ label = "S-awaiting-review"
+ issue_num = 1
+ self.gh_provider.remove_label(label, issue_num)
+
+ self.gh_provider.api_req.assert_called_with("DELETE", GithubApiProvider.remove_label_url % (self.owner, self.repo, issue_num, label), {})
+
+
+ def test_get_labels(self):
+ gh_provider = GithubApiProvider("jdm", "a453b3923e893f0383cd2893f...", self.owner, self.repo)
+ issue_num = 1
+ label1 = 'S-awaiting-review'
+ label2 = 'C-assigned'
+ gh_provider.api_req = Mock(return_value = {"header":"", "body":json.dumps([{'name':label1}, {'name':label2}])})
+
+ self.assertEquals([label1, label2], gh_provider.get_labels(issue_num))
+ gh_provider.api_req.assert_called_with("GET", GithubApiProvider.get_label_url % (self.owner, self.repo, issue_num))
+
+
+ def test_get_diff(self):
+ gh_provider = GithubApiProvider("jdm", "a453b3923e893f0383cd2893f...", self.owner, self.repo)
+ diff = "fake diff"
+ diff_url = "https://github.com/servo/servo/pull/1.diff"
+ gh_provider.api_req = Mock(return_value = {"header":"", "body":diff})
+
+ self.assertEquals(diff, gh_provider.get_diff("https://github.com/servo/servo/pull/1.diff"))
+ gh_provider.api_req.assert_called_with("GET", diff_url)
+
+
+ def test_set_assignee(self):
+ gh_provider = GithubApiProvider("jdm", "a453b3923e893f0383cd2893f...", self.owner, self.repo)
+ issue_num = 1
+ assignee = "jdm"
+ gh_provider.api_req = Mock(return_value = {"header":"", "body":assignee})
+
+ self.assertEquals(assignee, gh_provider.set_assignee(assignee, issue_num))
+ gh_provider.api_req.assert_called_with("PATCH", GithubApiProvider.issue_url % (self.owner, self.repo, issue_num), {"assignee":assignee})
+
+@patch('travisciapiprovider.urllib.urlopen')
+class TestTravisCiApiProvider(unittest.TestCase):
+ class FakeFile():
+ def __init__(self, contents):
+ self.contents = contents
+
+
+ def read(self):
+ return self.contents
+
+
+ def setUp(self):
+ self.travis = TravisCiApiProvider()
+
+
+ def test_get_build(self, urlopen_mock):
+ urlopen_mock.return_value = self.FakeFile(json.dumps({"Something":"here"}))
+ self.travis.get_build(1)
+ urlopen_mock.assert_called_with(TravisCiApiProvider.build_url.format(build_id=1))
+
+
+ def test_get_log(self, urlopen_mock):
+ job_id = 1
+ urlopen_mock.return_value = self.FakeFile(open('resources/single-line-comment.log').read())
+ self.travis.get_log({"matrix":[{'id':job_id}]})
+ urlopen_mock.assert_called_with(TravisCiApiProvider.log_url.format(job_id=job_id))
+
+
+ def test_get_pull_request_number(self, urlopen_mock):
+ pr_num = 1234
+ self.assertEquals(1234, self.travis.get_pull_request_number({"compare_url":"https://github.com/servo/servo/{}".format(pr_num)}))
+
+
+class TestErrorLogParser(unittest.TestCase):
+ def setUp(self):
+ self.log_parser = ErrorLogParser()
+
+
+ def test_parse_log(self):
+ with self.assertRaises(NotImplementedError):
+ self.log_parser.parse_log("log", "regex")
+
+
+class TestServoErrorLogParser(unittest.TestCase):
+ def setUp(self):
+ self.error_parser = ServoErrorLogParser()
+ self.multi_log = open('resources/multi-line-comment.log').read()
+ self.expected_multi_errors = error_sample
+ self.single_log = open('resources/single-line-comment.log').read()
+ self.expected_single_errors = [
+ {
+ 'body': 'missing space before {',
+ 'position': 49,
+ 'path': './components/plugins/lints/sorter.rs'
+ }
+ ]
+
+
+ def test_parse_errors(self):
+ self.assertEqual(self.expected_multi_errors, list(self.error_parser.parse_log(self.multi_log)))
+ self.assertEqual(self.expected_single_errors, list(self.error_parser.parse_log(self.single_log)))
+
+
+class TestPayloadHandler(unittest.TestCase):
+ def setUp(self):
+ self.handler = PayloadHandler()
+
+
+ def test_handle_payload(self):
+ with self.assertRaises(NotImplementedError):
+ self.handler.handle_payload("payload")
+
+
+class TestTravisPayloadHandler(unittest.TestCase):
+ def setUp(self):
+ class TravisDouble():
+ def get_build(self, build_id):
+ return 1
+
+
+ def get_log(self, build_data):
+ return open('resources/multi-line-comment.log').read()
+
+
+ def get_pull_request_number(self, build_data):
+ return 1
+
+
+ class ErrorParserDouble():
+ path_key = ServoErrorLogParser.path_key
+ body_key = ServoErrorLogParser.body_key
+ position_key = ServoErrorLogParser.position_key
+
+ def parse_log(self, log):
+ return error_sample
+
+
+ travis_dbl = TravisDouble()
+ error_parser_dbl = ErrorParserDouble()
+ self.github = GithubApiProvider("jdm", "a453b3923e893f0383cd2893f...", "servo", "servo")
+ self.github.post_review_comment = Mock()
+ self.github.get_review_comments = Mock(return_value=json.loads(open('resources/review_comments.json').read()))
+
+ self.payload_handler = TravisPayloadHandler(self.github, travis_dbl, error_parser_dbl)
+
+
+ def test_handle_payload(self):
+ payload = json.loads(open('resources/test_travis_payload.json').read())
+ self.payload_handler.handle_payload(payload)
+ err_msg = TravisPayloadHandler.msg_template
+
+ self.github.post_review_comment.assert_called_with(1, "9b6313fd5ab92de5a3fd9f13f8421a929b2a8ef6", err_msg.format(error_sample[3]['path'], error_sample[3]['position'], error_sample[3]['body']), error_sample[3]['path'], error_sample[3]['position'])
+
+class TestGithubPayloadHandler(unittest.TestCase):
+ def setUp(self):
+ self.github_user = "jdm"
+ self.github = GithubApiProvider(self.github_user, "a453b3923e893f0383cd2893f...", "servo", "servo")
+ self.github.remove_label = Mock()
+ self.github.add_label = Mock()
+
+
+ def test_handle_payload(self):
+ pl_handler = GithubPayloadHandler(None)
+ pl_handler.new_pr = Mock()
+ pl_handler.update_pr = Mock()
+ pl_handler.new_comment = Mock()
+
+ payload = {"action":"created", "issue":{"number":1}}
+ pl_handler.handle_payload(payload)
+ pl_handler.new_comment.assert_called_with("1", payload)
+
+ payload = {"action":"opened", "number":"1"}
+ pl_handler.handle_payload(payload)
+ pl_handler.new_pr.assert_called_with("1", payload)
+
+ payload = {"action":"synchronize", "number":"1"}
+ pl_handler.handle_payload(payload)
+ pl_handler.new_pr.update_pr("1", payload)
+
+ # should not break
+ payload = {"action":"other", "number":"1"}
+ pl_handler.handle_payload(payload)
+
+
+ def test_manage_pr_state(self):
+ self.github.get_labels = Mock(return_value=["S-awaiting-merge", "S-tests-failed", "S-needs-code-changes", "S-needs-rebase"])
+ pl_handler = GithubPayloadHandler(self.github)
+ full_cov_payload = {"action":"synchronize", "pull_request":{"mergeable":True}}
+ issue_num = 1
+
+ pl_handler.manage_pr_state(issue_num, full_cov_payload)
+ self.github.get_labels.assert_called_with(issue_num)
+ calls = [
+ call("S-awaiting-merge", issue_num),
+ call("S-tests-failed", issue_num),
+ call("S-needs-code-changes", issue_num),
+ call("S-needs-rebase", issue_num)
+ ]
+ self.github.remove_label.assert_has_calls(calls)
+ self.github.add_label.assert_called_with("S-awaiting-review", issue_num)
+
+ # Not sure it's worth the effort to cover all branches of manage_pr_state
+
+
+ def test_new_comment_no_action(self):
+ pl_handler = GithubPayloadHandler(self.github)
+ pl_handler._find_reviewer = Mock()
+ payload = {"issue":{"state":"closed"}}
+ issue_num = 1
+
+ pl_handler.new_comment(issue_num, payload)
+ pl_handler._find_reviewer.assert_not_called()
+
+ payload = {"issue":{"state":"open","pull_request":1},"comment":{"user":{"login":self.github_user}}}
+ pl_handler.new_comment(issue_num, payload)
+ pl_handler._find_reviewer.assert_not_called()
+
+
+ def test_new_comment_approved(self):
+ self.github.set_assignee = Mock()
+ self.github.get_labels = Mock(return_value=["S-awaiting-review"])
+ pl_handler = GithubPayloadHandler(self.github)
+ payload = {"issue":{"state":"open","pull_request":1},"comment":{"user":{"login":"bors-servo"},"body":"\br?:@jdm Testing commit"}}
+ issue_num = 1
+
+ pl_handler.new_comment(issue_num, payload)
+ self.github.set_assignee.assert_called_with("jdm", issue_num)
+ self.github.get_labels.assert_called_with(issue_num)
+ self.github.remove_label.assert_called_with("S-awaiting-review", issue_num)
+ self.github.add_label.assert_called_with("S-awaiting-merge")
+
+
+ def test_new_comment_failed_test(self):
+ self.github.set_assignee = Mock()
+ self.github.get_labels = Mock()
+ pl_handler = GithubPayloadHandler(self.github)
+ payload = {"issue":{"state":"open","pull_request":1},"comment":{"user":{"login":"bors-servo"},"body":"\br?:@jdm Test failed"}}
+ issue_num = 1
+
+ pl_handler.new_comment(issue_num, payload)
+ self.github.remove_label.assert_called_with("S-awaiting-merge", issue_num)
+ self.github.add_label.assert_called_with("S-tests-failed", issue_num)
+
+
+ def test_new_comment_merge_conflicts(self):
+ self.github.set_assignee = Mock()
+ self.github.get_labels = Mock()
+ pl_handler = GithubPayloadHandler(self.github)
+ payload = {"issue":{"state":"open","pull_request":1},"comment":{"user":{"login":"bors-servo"},"body":"\br?:@jdm Please resolve the merge conflicts"}}
+ issue_num = 1
+
+ pl_handler.new_comment(issue_num, payload)
+ self.github.remove_label.assert_called_with("S-awaiting-merge", issue_num)
+ self.github.add_label.assert_called_with("S-needs-rebase", issue_num)
+
+
+ @patch('payloadhandler.random')
+ def test_new_pr_no_msg(self, random_mock):
+ self.github.is_new_contributor = Mock(return_value=True)
+ self.github.post_comment = Mock()
+ self.github.get_diff = Mock(return_value="")
+ pl_handler = GithubPayloadHandler(self.github)
+ pl_handler.manage_pr_state = Mock()
+ pl_handler.post_comment = Mock()
+ diff_url = "https://github.com/servo/servo/pull/1234.diff"
+ payload = {"pull_request":{"user":{"login":"jdm"},"diff_url":diff_url}}
+ issue_num = 1
+ rand_val = 'jdm'
+ random_mock.choice.return_value = rand_val
+
+ pl_handler.new_pr(issue_num, payload)
+ pl_handler.manage_pr_state.assert_called_with(issue_num, payload)
+ self.github.post_comment.assert_called_with(GithubPayloadHandler.welcome_msg % rand_val, issue_num)
+ self.github.get_diff.assert_called_with(diff_url)
+
+
+ def test_new_pr_unsafe_msg(self):
+ self.github.is_new_contributor = Mock(return_value=False)
+ self.github.post_comment = Mock()
+ self.github.get_diff = Mock(return_value=open('resources/unsafe.diff').read())
+ pl_handler = GithubPayloadHandler(self.github)
+ pl_handler.manage_pr_state = Mock()
+ pl_handler.post_comment = Mock()
+ payload = {"pull_request":{"user":{"login":"jdm"},"diff_url":"https://github.com/servo/servo/pull/1234.diff"}}
+ issue_num = 1
+
+ pl_handler.new_pr(issue_num, payload)
+ self.github.post_comment.assert_called_with(GithubPayloadHandler.warning_summary % '* ' + GithubPayloadHandler.unsafe_warning_msg, issue_num)
+
+
+ def test_new_pr_needs_reftest(self):
+ self.github.is_new_contributor = Mock(return_value=False)
+ self.github.post_comment = Mock()
+ self.github.get_diff = Mock(return_value=open('resources/needs_reftest.diff').read())
+ pl_handler = GithubPayloadHandler(self.github)
+ pl_handler.manage_pr_state = Mock()
+ pl_handler.post_comment = Mock()
+ payload = {"pull_request":{"user":{"login":"jdm"},"diff_url":"https://github.com/servo/servo/pull/1234.diff"}}
+ issue_num = 1
+
+ pl_handler.new_pr(issue_num, payload)
+ self.github.post_comment.assert_called_with(GithubPayloadHandler.warning_summary % '* ' + GithubPayloadHandler.reftest_required_msg, issue_num)
+
+
+ def test_update_pr(self):
+ pl_handler = GithubPayloadHandler(self.github)
+ pl_handler.manage_pr_state = Mock()
+ issue_num = 1
+ payload = {"payload"}
+
+ pl_handler.update_pr(issue_num, payload)
+ pl_handler.manage_pr_state.assert_called_with(issue_num, payload)
+
+
+class TestPayloadReceiver(unittest.TestCase):
+ def setUp(self):
+ self.github_created_payload = json.loads(open('resources/test_post_retry.json').read())
+ self.github_other_payload = json.loads(open('resources/test_new_pr.json').read())
+ self.travis_payload = json.loads(open('resources/test_travis_payload.json').read())
+
+
+ def test_extract_globals_from_payload(self):
+ self.assertEqual(("servo", "servo"), payloadreceiver.extract_globals_from_payload(self.github_created_payload))
+ self.assertEqual(("servo", "servo"), payloadreceiver.extract_globals_from_payload(self.github_other_payload))
+ self.assertEqual(("servo", "servo"), payloadreceiver.extract_globals_from_payload(self.travis_payload))
+
+
+if __name__ == "__main__":
+ unittest.main()
\ No newline at end of file
diff --git a/travisciapiprovider.py b/travisciapiprovider.py
new file mode 100644
index 0000000..ef4b7ff
--- /dev/null
+++ b/travisciapiprovider.py
@@ -0,0 +1,30 @@
+import urllib
+import json
+
+# If more functionality is needed from this class, it might be a
+# better decision to use travispy
+class TravisCiApiProvider():
+ host_url = 'https://api.travis-ci.org'
+ build_url = host_url + '/builds/{build_id}'
+ log_url = host_url + '/jobs/{job_id}/log'
+
+ def get_build(self, build_id):
+ return json.loads(urllib.urlopen(self.build_url.format(build_id=build_id)).read())
+
+
+ def _get_job_id(self, build_data, job_index=0):
+ try:
+ job_id = build_data['matrix'][job_index]['id']
+ except IndexError:
+ print "job_index out of bounds"
+ job_id = -1
+
+ return job_id
+
+
+ def get_log(self, build_data):
+ return urllib.urlopen(self.log_url.format(job_id=self._get_job_id(build_data))).read()
+
+
+ def get_pull_request_number(self, build_data):
+ return int(build_data['compare_url'].split('/')[-1])
\ No newline at end of file