test_pr.py
153 lines
| 4.9 KiB
| text/x-python
|
PythonLexer
/ tools / test_pr.py
Thomas Kluyver
|
r6686 | """ | ||
This is a script for testing pull requests for IPython. It merges the pull | ||||
request with current master, installs and tests on all available versions of | ||||
Python, and posts the results to Gist if any tests fail. | ||||
Usage: | ||||
python test_pr.py 1657 | ||||
""" | ||||
from __future__ import print_function | ||||
import errno | ||||
from glob import glob | ||||
import json | ||||
import os | ||||
Thomas Kluyver
|
r6692 | import re | ||
Thomas Kluyver
|
r6686 | import shutil | ||
from subprocess import call, check_call, check_output, PIPE, STDOUT, CalledProcessError | ||||
try: | ||||
from urllib.request import urlopen | ||||
except ImportError: | ||||
from urllib2 import urlopen | ||||
basedir = os.path.join(os.path.expanduser("~"), ".ipy_pr_tests") | ||||
repodir = os.path.join(basedir, "ipython") | ||||
ipy_repository = 'git://github.com/ipython/ipython.git' | ||||
supported_pythons = ['python2.6', 'python2.7', 'python3.1', 'python3.2'] | ||||
unavailable_pythons = [] | ||||
def available_python_versions(): | ||||
"""Get the executable names of available versions of Python on the system. | ||||
""" | ||||
del unavailable_pythons[:] | ||||
for py in supported_pythons: | ||||
try: | ||||
check_call([py, '-c', 'import nose'], stdout=PIPE) | ||||
yield py | ||||
except (OSError, CalledProcessError): | ||||
unavailable_pythons.append(py) | ||||
venvs = [] | ||||
def setup(): | ||||
"""Prepare the repository and virtualenvs.""" | ||||
global venvs | ||||
try: | ||||
os.mkdir(basedir) | ||||
except OSError as e: | ||||
if e.errno != errno.EEXIST: | ||||
raise | ||||
os.chdir(basedir) | ||||
# Delete virtualenvs and recreate | ||||
for venv in glob('venv-*'): | ||||
shutil.rmtree(venv) | ||||
for py in available_python_versions(): | ||||
check_call(['virtualenv', '-p', py, '--system-site-packages', 'venv-%s' % py]) | ||||
venvs.append((py, 'venv-%s' % py)) | ||||
# Check out and update the repository | ||||
if not os.path.exists('ipython'): | ||||
check_call(['git', 'clone', ipy_repository]) | ||||
os.chdir(repodir) | ||||
check_call(['git', 'checkout', 'master']) | ||||
check_call(['git', 'pull', ipy_repository, 'master']) | ||||
os.chdir(basedir) | ||||
def get_pull_request(num, project="ipython/ipython"): | ||||
url = "https://api.github.com/repos/{project}/pulls/{num}".format(project=project, num=num) | ||||
response = urlopen(url).read().decode('utf-8') | ||||
return json.loads(response) | ||||
Thomas Kluyver
|
r6692 | missing_libs_re = re.compile(r"Tools and libraries NOT available at test time:\n" | ||
r"\s*(.*?)\n") | ||||
def get_missing_libraries(log): | ||||
m = missing_libs_re.search(log) | ||||
if m: | ||||
return m.group(1) | ||||
Thomas Kluyver
|
r6686 | def merge_branch(repo, branch, owner): | ||
merged_branch = "%s-%s" % (owner, branch) | ||||
os.chdir(repodir) | ||||
# Delete the branch first | ||||
call(['git', 'branch', '-D', merged_branch]) | ||||
check_call(['git', 'checkout', '-b', merged_branch]) | ||||
check_call(['git', 'pull', repo, branch]) | ||||
os.chdir(basedir) | ||||
def run_tests(venv): | ||||
py = os.path.join(basedir, venv, 'bin', 'python') | ||||
print(py) | ||||
os.chdir(repodir) | ||||
check_call([py, 'setup.py', 'install']) | ||||
os.chdir(basedir) | ||||
iptest = os.path.join(basedir, venv, 'bin', 'iptest') | ||||
if not os.path.exists(iptest): | ||||
iptest = os.path.join(basedir, venv, 'bin', 'iptest3') | ||||
print("\nRunning tests, this typically takes a few minutes...") | ||||
try: | ||||
return True, check_output([iptest], stderr=STDOUT).decode('utf-8') | ||||
except CalledProcessError as e: | ||||
return False, e.output | ||||
def post_gist(content, description='IPython test log', filename="results.log"): | ||||
"""Post some text to a Gist, and return the URL.""" | ||||
post_data = json.dumps({ | ||||
"description": description, | ||||
"public": True, | ||||
"files": { | ||||
filename: { | ||||
"content": content | ||||
} | ||||
} | ||||
}).encode('utf-8') | ||||
response = urlopen("https://api.github.com/gists", post_data) | ||||
response_data = json.loads(response.read().decode('utf-8')) | ||||
return response_data['html_url'] | ||||
if __name__ == '__main__': | ||||
import sys | ||||
num = sys.argv[1] | ||||
setup() | ||||
pr = get_pull_request(num) | ||||
merge_branch(repo=pr['head']['repo']['clone_url'], | ||||
branch=pr['head']['ref'], | ||||
owner=pr['head']['repo']['owner']['login']) | ||||
results = [] | ||||
for py, venv in venvs: | ||||
passed, log = run_tests(venv) | ||||
Thomas Kluyver
|
r6692 | missing_libraries = get_missing_libraries(log) | ||
Thomas Kluyver
|
r6686 | if passed: | ||
Thomas Kluyver
|
r6692 | results.append((py, True, None, missing_libraries)) | ||
Thomas Kluyver
|
r6686 | else: | ||
gist_url = post_gist(log) | ||||
Thomas Kluyver
|
r6692 | results.append((py, False, gist_url, missing_libraries)) | ||
Thomas Kluyver
|
r6686 | |||
print("\n") | ||||
print("**Test results for commit %s merged into master**" % pr['head']['sha'][:7]) | ||||
Thomas Kluyver
|
r6692 | print("Platform:", sys.platform) | ||
for py, passed, gist_url, missing_libraries in results: | ||||
Thomas Kluyver
|
r6686 | if passed: | ||
print(py, ":", "OK") | ||||
else: | ||||
print(py, ":", "Failed") | ||||
print(" Test log:", gist_url) | ||||
Thomas Kluyver
|
r6692 | if missing_libraries: | ||
print(" Libraries not available:", missing_libraries) | ||||
Thomas Kluyver
|
r6686 | print("Not available for testing:", ", ".join(unavailable_pythons)) | ||