##// END OF EJS Templates
PR tester can now post results to Github as a comment.
Thomas Kluyver -
Show More
@@ -0,0 +1,42 b''
1 """Functions for Github authorisation."""
2 from __future__ import print_function
3
4 try:
5 input = raw_input
6 except NameError:
7 pass
8
9 import requests
10 import keyring
11 import getpass
12 import json
13
14 # Keyring stores passwords by a 'username', but we're not storing a username and
15 # password
16 fake_username = 'ipython_tools'
17
18 def get_auth_token():
19 token = keyring.get_password('github', fake_username)
20 if token is not None:
21 return token
22
23 print("Please enter your github username and password. These are not "
24 "stored, only used to get an oAuth token. You can revoke this at "
25 "any time on Github.")
26 user = input("Username: ")
27 pw = getpass.getpass("Password: ")
28
29 auth_request = {
30 "scopes": [
31 "public_repo"
32 ],
33 "note": "IPython tools"
34 }
35 response = requests.post('https://api.github.com/authorizations',
36 auth=(user, pw), data=json.dumps(auth_request))
37 response.raise_for_status()
38 token = json.loads(response.text)['token']
39 keyring.set_password('github', fake_username, token)
40 return token
41
42
@@ -1,153 +1,187 b''
1 1 """
2 2 This is a script for testing pull requests for IPython. It merges the pull
3 3 request with current master, installs and tests on all available versions of
4 4 Python, and posts the results to Gist if any tests fail.
5 5
6 6 Usage:
7 7 python test_pr.py 1657
8 8 """
9 9 from __future__ import print_function
10 10
11 11 import errno
12 12 from glob import glob
13 13 import json
14 14 import os
15 15 import re
16 import requests
16 17 import shutil
17 18 from subprocess import call, check_call, check_output, PIPE, STDOUT, CalledProcessError
18 19 try:
19 20 from urllib.request import urlopen
20 21 except ImportError:
21 22 from urllib2 import urlopen
22 23
24 import gh_auth
25
23 26 basedir = os.path.join(os.path.expanduser("~"), ".ipy_pr_tests")
24 27 repodir = os.path.join(basedir, "ipython")
25 28 ipy_repository = 'git://github.com/ipython/ipython.git'
29 gh_project="ipython/ipython"
26 30
27 31 supported_pythons = ['python2.6', 'python2.7', 'python3.1', 'python3.2']
28 32 unavailable_pythons = []
29 33
30 34 def available_python_versions():
31 35 """Get the executable names of available versions of Python on the system.
32 36 """
33 37 del unavailable_pythons[:]
34 38 for py in supported_pythons:
35 39 try:
36 40 check_call([py, '-c', 'import nose'], stdout=PIPE)
37 41 yield py
38 42 except (OSError, CalledProcessError):
39 43 unavailable_pythons.append(py)
40 44
41 45 venvs = []
42 46
43 47 def setup():
44 48 """Prepare the repository and virtualenvs."""
45 49 global venvs
46 50
47 51 try:
48 52 os.mkdir(basedir)
49 53 except OSError as e:
50 54 if e.errno != errno.EEXIST:
51 55 raise
52 56 os.chdir(basedir)
53 57
54 58 # Delete virtualenvs and recreate
55 59 for venv in glob('venv-*'):
56 60 shutil.rmtree(venv)
57 61 for py in available_python_versions():
58 62 check_call(['virtualenv', '-p', py, '--system-site-packages', 'venv-%s' % py])
59 63 venvs.append((py, 'venv-%s' % py))
60 64
61 65 # Check out and update the repository
62 66 if not os.path.exists('ipython'):
63 67 check_call(['git', 'clone', ipy_repository])
64 68 os.chdir(repodir)
65 69 check_call(['git', 'checkout', 'master'])
66 70 check_call(['git', 'pull', ipy_repository, 'master'])
67 71 os.chdir(basedir)
68 72
69 def get_pull_request(num, project="ipython/ipython"):
70 url = "https://api.github.com/repos/{project}/pulls/{num}".format(project=project, num=num)
73 def get_pull_request(num):
74 url = "https://api.github.com/repos/{project}/pulls/{num}".format(project=gh_project, num=num)
71 75 response = urlopen(url).read().decode('utf-8')
72 76 return json.loads(response)
73 77
74 78 missing_libs_re = re.compile(r"Tools and libraries NOT available at test time:\n"
75 79 r"\s*(.*?)\n")
76 80 def get_missing_libraries(log):
77 81 m = missing_libs_re.search(log)
78 82 if m:
79 83 return m.group(1)
80 84
81 85 def merge_branch(repo, branch, owner):
82 86 merged_branch = "%s-%s" % (owner, branch)
83 87 os.chdir(repodir)
84 88 # Delete the branch first
85 89 call(['git', 'branch', '-D', merged_branch])
86 90 check_call(['git', 'checkout', '-b', merged_branch])
87 91 check_call(['git', 'pull', repo, branch])
88 92 os.chdir(basedir)
89 93
90 94 def run_tests(venv):
91 95 py = os.path.join(basedir, venv, 'bin', 'python')
92 96 print(py)
93 97 os.chdir(repodir)
94 98 check_call([py, 'setup.py', 'install'])
95 99 os.chdir(basedir)
96 100
97 101 iptest = os.path.join(basedir, venv, 'bin', 'iptest')
98 102 if not os.path.exists(iptest):
99 103 iptest = os.path.join(basedir, venv, 'bin', 'iptest3')
100 104
101 105 print("\nRunning tests, this typically takes a few minutes...")
102 106 try:
103 107 return True, check_output([iptest], stderr=STDOUT).decode('utf-8')
104 108 except CalledProcessError as e:
105 return False, e.output
109 return False, e.output.decode('utf-8')
106 110
107 111 def post_gist(content, description='IPython test log', filename="results.log"):
108 112 """Post some text to a Gist, and return the URL."""
109 113 post_data = json.dumps({
110 114 "description": description,
111 115 "public": True,
112 116 "files": {
113 117 filename: {
114 118 "content": content
115 119 }
116 120 }
117 121 }).encode('utf-8')
118 122
119 123 response = urlopen("https://api.github.com/gists", post_data)
120 124 response_data = json.loads(response.read().decode('utf-8'))
121 125 return response_data['html_url']
122 126
127 def markdown_format(pr, results):
128 def format_result(py, passed, gist_url, missing_libraries):
129 s = "* %s: " % py
130 if passed:
131 s += "OK"
132 else:
133 s += "Failed, log at %s" % gist_url
134 if missing_libraries:
135 s += " (libraries not available: " + missing_libraries + ")"
136 return s
137
138 lines = ["**Test results for commit %s merged into master**" % pr['head']['sha'][:7],
139 "Platform: " + sys.platform,
140 ""] + \
141 [format_result(*r) for r in results] + \
142 ["",
143 "Not available for testing:" + ", ".join(unavailable_pythons)]
144 return "\n".join(lines)
145
146 def post_results_comment(pr, results, num):
147 body = markdown_format(pr, results)
148 url = 'https://api.github.com/repos/{project}/issues/{num}/comments'.format(project=gh_project, num=num)
149 payload = json.dumps({'body': body})
150 auth_token = gh_auth.get_auth_token()
151 headers = {'Authorization': 'token ' + auth_token}
152 r = requests.post(url, data=payload, headers=headers)
153
154
123 155 if __name__ == '__main__':
124 156 import sys
125 157 num = sys.argv[1]
126 158 setup()
127 159 pr = get_pull_request(num)
128 160 merge_branch(repo=pr['head']['repo']['clone_url'],
129 161 branch=pr['head']['ref'],
130 162 owner=pr['head']['repo']['owner']['login'])
131 163
132 164 results = []
133 165 for py, venv in venvs:
134 166 passed, log = run_tests(venv)
135 167 missing_libraries = get_missing_libraries(log)
136 168 if passed:
137 169 results.append((py, True, None, missing_libraries))
138 170 else:
139 171 gist_url = post_gist(log)
140 172 results.append((py, False, gist_url, missing_libraries))
141 173
142 174 print("\n")
143 175 print("**Test results for commit %s merged into master**" % pr['head']['sha'][:7])
144 176 print("Platform:", sys.platform)
145 177 for py, passed, gist_url, missing_libraries in results:
146 178 if passed:
147 179 print(py, ":", "OK")
148 180 else:
149 181 print(py, ":", "Failed")
150 182 print(" Test log:", gist_url)
151 183 if missing_libraries:
152 184 print(" Libraries not available:", missing_libraries)
153 185 print("Not available for testing:", ", ".join(unavailable_pythons))
186 post_results_comment(pr, results, num)
187 print("(Posted to Github)")
General Comments 0
You need to be logged in to leave comments. Login now