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