Permalink
Switch branches/tags
v2.2.0-alpha.00000000 v2.1.0-beta.20181015 v2.1.0-beta.20181008 v2.1.0-beta.20181001 v2.1.0-beta.20180924 v2.1.0-beta.20180917 v2.1.0-beta.20180910 v2.1.0-beta.20180904 v2.1.0-beta.20180827 v2.1.0-alpha.20180730 v2.1.0-alpha.20180702 v2.1.0-alpha.20180604 v2.1.0-alpha.20180507 v2.1.0-alpha.20180416 v2.1.0-alpha.00000000 v2.0.6 v2.0.6-rc.1 v2.0.5 v2.0.4 v2.0.3 v2.0.2 v2.0.1 v2.0.0 v2.0-rc.1 v2.0-beta.20180326 v2.0-beta.20180319 v2.0-beta.20180312 v2.0-beta.20180305 v2.0-alpha.20180212 v2.0-alpha.20180129 v2.0-alpha.20180122 v2.0-alpha.20180116 v2.0-alpha.20171218 v2.0-alpha.20171218-plus-left-join-fix v1.2-alpha.20171211 v1.2-alpha.20171204 v1.2-alpha.20171113 v1.2-alpha.20171026 v1.2-alpha.20170901 v1.1.9 v1.1.9-rc.1 v1.1.8 v1.1.7 v1.1.6 v1.1.5 v1.1.4 v1.1.3 v1.1.2 v1.1.1 v1.1.0 v1.1.0-rc.1 v1.1-beta.20170928 v1.1-beta.20170921 v1.1-beta.20170907 v1.1-alpha.20170817 v1.1-alpha.20170810 v1.1-alpha.20170803 v1.1-alpha.20170720 v1.1-alpha.20170713 v1.1-alpha.20170629 v1.1-alpha.20170622 v1.1-alpha.20170608 v1.1-alpha.20170601 v1.0.7 v1.0.6 v1.0.5 v1.0.4 v1.0.3 v1.0.2 v1.0.1 v1.0 v1.0-rc.3 v1.0-rc.2 v1.0-rc.1 v0.1-alpha beta-20170420 beta-20170413 beta-20170406 beta-20170330 beta-20170323 beta-20170309 beta-20170223 beta-20170216 beta-20170209 beta-20170126 beta-20170112 beta-20170105 beta-20161215 beta-20161208 beta-20161201 beta-20161110 beta-20161103 beta-20161027 beta-20161013 beta-20161006 beta-20160929 beta-20160915 beta-20160908 beta-20160829 beta-20160728
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 176 lines (146 sloc) 6.56 KB
#!/usr/bin/env python3
"""Post failures from the current teamcity job as github issues.
Requires the following environment variables:
- TC_API_PASSWORD
- TC_BUILD_BRANCH
- TC_BUILD_ID
- GITHUB_API_TOKEN
"""
import json
import os
import re
import subprocess
import urllib.error
import urllib.request
import xml.etree.ElementTree as ET
from pkg_resources import parse_version
from urllib.parse import urljoin, urlencode
BASEURL = "https://teamcity.cockroachdb.com/httpAuth/app/rest/"
auth_handler = urllib.request.HTTPBasicAuthHandler()
auth_handler.add_password(realm='TeamCity',
uri='https://teamcity.cockroachdb.com',
user='robot',
passwd=os.environ['TC_API_PASSWORD'])
opener = urllib.request.build_opener(auth_handler)
def tc_url(path, **params):
return urljoin(BASEURL, path) + '?' + urlencode(params)
def collect_build_results(build_id):
"""Yield a sequence of (name, log, category) pairs for all failed tests.
Looks at the given build ID and all its dependencies.
"""
dep_data = ET.parse(opener.open(tc_url('builds/{0}'.format(build_id),
fields='snapshot-dependencies(build(id,status))')))
for b in dep_data.findall("./snapshot-dependencies/build"):
if b.attrib['status'] != 'SUCCESS':
yield from collect_build_results(b.attrib['id'])
test_data = ET.parse(opener.open(tc_url('testOccurrences',
locator='count:100,status:FAILURE,build:(id:{0})'.format(build_id),
fields='testOccurrence(details,name,duration,build(buildType(name)))')))
for o in test_data.findall('./testOccurrence'):
# Category is test, race, acceptance, ...
category = o.find('build/buildType').attrib['name']
# Extract TestLint/Foo from "TestLint: TestLint/Foo".
matches = re.split(r"[: ]+", o.attrib['name'], 1)
test_name = matches[-1]
test_log = '--- FAIL: {0}/{1} ({2:.3f}s)\n{3}\n'.format(
o.find("build/buildType").attrib["name"],
o.attrib["name"],
int(o.attrib["duration"])/1000.,
o.findtext("details"))
yield (test_name, test_log, category)
def get_probable_milestone():
try:
tag = subprocess.check_output(['git', 'describe', '--abbrev=0', '--tags'],
universal_newlines=True)
except subprocess.CalledProcessError as e:
print('warning: unable to load latest tag: {0}'.format(e))
print('issue will be posted without milestone')
return None
match = re.match(r'v(\d+\.\d+)', tag)
if not match:
print('unable to parse version {0}; issue will be posted without milestone'.format(tag))
return None
version = match.group(1)
try:
res = urllib.request.urlopen(
'https://api.github.com/repos/cockroachdb/cockroach/milestones?state=open')
milestones = json.loads(res.read().decode(res.info().get_param('charset') or 'utf-8'))
except (ValueError, urllib.error.HTTPError) as e:
print('warning: unable to load milestones: {0}'.format(e))
print('issue will be posted without milestone')
return None
for m in milestones:
if m['title'] == version:
return m['number']
print('no milestone matching {0}; issue will be posted without milestone'.format(version))
return None
def create_issue(build_id, topname, category, failed_tests):
"""Format a list of failed tests as an issue.
Returns a dict which should be encoded as json for posting to the
github API.
"""
return {
'title': 'teamcity: failed test: {0}'.format(topname),
'body': '''\
The following tests appear to have failed on {0} ({5}): {4}
You may want to check [for open issues](https://github.com/cockroachdb/cockroach/issues?q=is%3Aissue+is%3Aopen+{3}).
[#{1}](https://teamcity.cockroachdb.com/viewLog.html?buildId={1}):
```
{2}
```
Please assign, take a look and update the issue accordingly.
'''.format(
os.environ['TC_BUILD_BRANCH'],
build_id,
''.join(set(t[0]+"\n"+("..." if len(t[1]) > 4000 else "") + t[1][-4000:]+"\n\n\n" for t in failed_tests)),
topname,
', '.join(set(t[0] for t in failed_tests)),
category,
),
'labels': ['C-test-failure', 'O-robot'],
'milestone': get_probable_milestone(),
}
def post_issue(issue):
req = urllib.request.Request(
'https://api.github.com/repos/cockroachdb/cockroach/issues',
data=json.dumps(issue).encode('utf-8'),
headers={'Authorization': 'token {0}'.format(os.environ['GITHUB_API_TOKEN'])})
try:
opener.open(req).read()
except urllib.error.HTTPError as e:
# Github's error response bodies include useful information, so print them.
print('Posting to github failed with error code {0}'.format(e.code))
print('Error response body: {0}'.format(e.read()))
print('Request body: {0}'.format(issue))
raise
if __name__ == '__main__':
branch = os.environ['TC_BUILD_BRANCH']
if branch == 'master' or branch.startswith('release-'):
build_id = os.environ['TC_BUILD_ID']
failed_tests = list(collect_build_results(build_id))
issues = {}
for failed_test in failed_tests:
category = failed_test[2]
# From TestImportPGDump/Subtest, extract TestImportPGDump.
matches = re.split(r"^([^/]+)/?", failed_test[0], 1)
if len(matches) < 3:
matches.append("")
topname, subname = matches[1], matches[2]
if category == "roachtest" and topname == "acceptance":
if subname == "":
# Don't post roachtest failures for the umbrella acceptance test.
# In roachtest, parent tests contain no logic.
continue
# Special casing for roachtest so that it doesn't lump any
# acceptance failure into an issue that just says "acceptance".
topname = subname
subname = ""
if topname in issues:
issues[topname].append(failed_test)
else:
issues[topname] = [failed_test]
for topname, failures in issues.items():
# TODO(tschottdorf): this should really shell out to our Go issue
# poster in pkg/cmd/internal/issues to avoid duplicating logic.
issue = create_issue(build_id, topname, failures[0][2], failures)
post_issue(issue)