##// END OF EJS Templates
Fix for test_pr script
Thomas Kluyver -
Show More
@@ -1,266 +1,266 b''
1 1 #!/usr/bin/env python
2 2 """
3 3 This is a script for testing pull requests for IPython. It merges the pull
4 4 request with current master, installs and tests on all available versions of
5 5 Python, and posts the results to Gist if any tests fail.
6 6
7 7 Usage:
8 8 python test_pr.py 1657
9 9 """
10 10 from __future__ import print_function
11 11
12 12 import errno
13 13 from glob import glob
14 14 import io
15 15 import json
16 16 import os
17 17 import pickle
18 18 import re
19 19 import requests
20 20 import shutil
21 21 import time
22 22 from subprocess import call, check_call, check_output, PIPE, STDOUT, CalledProcessError
23 23 import sys
24 24
25 25 import gh_api
26 26 from gh_api import Obj
27 27
28 28 basedir = os.path.join(os.path.expanduser("~"), ".ipy_pr_tests")
29 29 repodir = os.path.join(basedir, "ipython")
30 30 ipy_repository = 'git://github.com/ipython/ipython.git'
31 31 ipy_http_repository = 'http://github.com/ipython/ipython.git'
32 32 gh_project="ipython/ipython"
33 33
34 34 supported_pythons = ['python2.6', 'python2.7', 'python3.2']
35 35
36 36 missing_libs_re = re.compile(r"Tools and libraries NOT available at test time:\n"
37 37 r"\s*(.*?)\n")
38 38 def get_missing_libraries(log):
39 39 m = missing_libs_re.search(log)
40 40 if m:
41 41 return m.group(1)
42 42
43 43 class TestRun(object):
44 44 def __init__(self, pr_num):
45 45 self.unavailable_pythons = []
46 46 self.venvs = []
47 47 self.pr_num = pr_num
48 48
49 49 self.pr = gh_api.get_pull_request(gh_project, pr_num)
50 50
51 51 self.setup()
52 52
53 53 self.results = []
54 54
55 55 def available_python_versions(self):
56 56 """Get the executable names of available versions of Python on the system.
57 57 """
58 58 for py in supported_pythons:
59 59 try:
60 60 check_call([py, '-c', 'import nose'], stdout=PIPE)
61 61 yield py
62 62 except (OSError, CalledProcessError):
63 63 self.unavailable_pythons.append(py)
64 64
65 65 def setup(self):
66 66 """Prepare the repository and virtualenvs."""
67 67 try:
68 68 os.mkdir(basedir)
69 69 except OSError as e:
70 70 if e.errno != errno.EEXIST:
71 71 raise
72 72 os.chdir(basedir)
73 73
74 74 # Delete virtualenvs and recreate
75 75 for venv in glob('venv-*'):
76 76 shutil.rmtree(venv)
77 77 for py in self.available_python_versions():
78 78 check_call(['virtualenv', '-p', py, '--system-site-packages', 'venv-%s' % py])
79 79 self.venvs.append((py, 'venv-%s' % py))
80 80
81 81 # Check out and update the repository
82 82 if not os.path.exists('ipython'):
83 83 try :
84 84 check_call(['git', 'clone', ipy_repository])
85 85 except CalledProcessError :
86 86 check_call(['git', 'clone', ipy_http_repository])
87 87 os.chdir(repodir)
88 88 check_call(['git', 'checkout', 'master'])
89 89 try :
90 90 check_call(['git', 'pull', ipy_repository, 'master'])
91 91 except CalledProcessError :
92 92 check_call(['git', 'pull', ipy_http_repository, 'master'])
93 93 os.chdir(basedir)
94 94
95 95 def get_branch(self):
96 96 repo = self.pr['head']['repo']['clone_url']
97 97 branch = self.pr['head']['ref']
98 98 owner = self.pr['head']['repo']['owner']['login']
99 99 mergeable = self.pr['mergeable']
100 100
101 101 os.chdir(repodir)
102 102 if mergeable:
103 103 merged_branch = "%s-%s" % (owner, branch)
104 104 # Delete the branch first
105 105 call(['git', 'branch', '-D', merged_branch])
106 106 check_call(['git', 'checkout', '-b', merged_branch])
107 107 check_call(['git', 'pull', '--no-ff', '--no-commit', repo, branch])
108 108 check_call(['git', 'commit', '-m', "merge %s/%s" % (repo, branch)])
109 109 else:
110 110 # Fetch the branch without merging it.
111 111 check_call(['git', 'fetch', repo, branch])
112 112 check_call(['git', 'checkout', 'FETCH_HEAD'])
113 113 os.chdir(basedir)
114 114
115 115 def markdown_format(self):
116 116 def format_result(result):
117 117 s = "* %s: " % result.py
118 118 if result.passed:
119 119 s += "OK"
120 120 else:
121 121 s += "Failed, log at %s" % result.log_url
122 122 if result.missing_libraries:
123 123 s += " (libraries not available: " + result.missing_libraries + ")"
124 124 return s
125 125
126 126 if self.pr['mergeable']:
127 127 com = self.pr['head']['sha'][:7] + " merged into master"
128 128 else:
129 129 com = self.pr['head']['sha'][:7] + " (can't merge cleanly)"
130 130 lines = ["**Test results for commit %s**" % com,
131 131 "Platform: " + sys.platform,
132 132 ""] + \
133 133 [format_result(r) for r in self.results] + \
134 134 ["",
135 135 "Not available for testing: " + ", ".join(self.unavailable_pythons)]
136 136 return "\n".join(lines)
137 137
138 138 def post_results_comment(self):
139 139 body = self.markdown_format()
140 140 gh_api.post_issue_comment(gh_project, self.pr_num, body)
141 141
142 142 def print_results(self):
143 143 pr = self.pr
144 144
145 145 print("\n")
146 146 if pr['mergeable']:
147 147 print("**Test results for commit %s merged into master**" % pr['head']['sha'][:7])
148 148 else:
149 149 print("**Test results for commit %s (can't merge cleanly)**" % pr['head']['sha'][:7])
150 150 print("Platform:", sys.platform)
151 151 for result in self.results:
152 152 if result.passed:
153 153 print(result.py, ":", "OK")
154 154 else:
155 155 print(result.py, ":", "Failed")
156 156 print(" Test log:", result.get('log_url') or result.log_file)
157 157 if result.missing_libraries:
158 158 print(" Libraries not available:", result.missing_libraries)
159 159 print("Not available for testing:", ", ".join(self.unavailable_pythons))
160 160
161 161 def dump_results(self):
162 162 with open(os.path.join(basedir, 'lastresults.pkl'), 'wb') as f:
163 163 pickle.dump(self, f)
164 164
165 165 @staticmethod
166 166 def load_results():
167 167 with open(os.path.join(basedir, 'lastresults.pkl'), 'rb') as f:
168 168 return pickle.load(f)
169 169
170 170 def save_logs(self):
171 171 for result in self.results:
172 172 if not result.passed:
173 173 result_locn = os.path.abspath(os.path.join('venv-%s' % result.py,
174 174 self.pr['head']['sha'][:7]+".log"))
175 175 with io.open(result_locn, 'w', encoding='utf-8') as f:
176 176 f.write(result.log)
177 177
178 178 result.log_file = result_locn
179 179
180 180 def post_logs(self):
181 181 for result in self.results:
182 182 if not result.passed:
183 183 result.log_url = gh_api.post_gist(result.log,
184 184 description='IPython test log',
185 185 filename="results.log", auth=True)
186 186
187 187 def run(self):
188 188 for py, venv in self.venvs:
189 189 tic = time.time()
190 190 passed, log = run_tests(venv)
191 191 elapsed = int(time.time() - tic)
192 192 print("Ran tests with %s in %is" % (py, elapsed))
193 193 missing_libraries = get_missing_libraries(log)
194 194
195 195 self.results.append(Obj(py=py,
196 196 passed=passed,
197 197 log=log,
198 198 missing_libraries=missing_libraries
199 199 )
200 200 )
201 201
202 202
203 203 def run_tests(venv):
204 204 py = os.path.join(basedir, venv, 'bin', 'python')
205 205 print(py)
206 206 os.chdir(repodir)
207 207 # cleanup build-dir
208 208 if os.path.exists('build'):
209 209 shutil.rmtree('build')
210 210 check_call([py, 'setup.py', 'install'])
211 211 os.chdir(basedir)
212 212
213 213 # Environment variables:
214 214 orig_path = os.environ["PATH"]
215 215 os.environ["PATH"] = os.path.join(basedir, venv, 'bin') + ':' + os.environ["PATH"]
216 216 os.environ.pop("PYTHONPATH", None)
217 217
218 218 iptest = os.path.join(basedir, venv, 'bin', 'iptest')
219 219 if not os.path.exists(iptest):
220 220 iptest = os.path.join(basedir, venv, 'bin', 'iptest3')
221 221
222 222 print("\nRunning tests, this typically takes a few minutes...")
223 223 try:
224 224 return True, check_output([iptest], stderr=STDOUT).decode('utf-8')
225 225 except CalledProcessError as e:
226 226 return False, e.output.decode('utf-8')
227 227 finally:
228 228 # Restore $PATH
229 229 os.environ["PATH"] = orig_path
230 230
231 231
232 232 def test_pr(num, post_results=True):
233 233 # Get Github authorisation first, so that the user is prompted straight away
234 234 # if their login is needed.
235 235 if post_results:
236 236 gh_api.get_auth_token()
237 237
238 238 testrun = TestRun(num)
239 239
240 240 testrun.get_branch()
241 241
242 242 testrun.run()
243 243
244 244 testrun.dump_results()
245 245
246 246 testrun.save_logs()
247 247 testrun.print_results()
248 248
249 249 if post_results:
250 results_urls = testrun.post_logs
250 testrun.post_logs()
251 251 testrun.post_results_comment()
252 252 print("(Posted to Github)")
253 253 else:
254 254 post_script = os.path.join(os.path.dirname(sys.argv[0]), "post_pr_test.py")
255 255 print("To post the results to Github, run", post_script)
256 256
257 257
258 258 if __name__ == '__main__':
259 259 import argparse
260 260 parser = argparse.ArgumentParser(description="Test an IPython pull request")
261 261 parser.add_argument('-p', '--publish', action='store_true',
262 262 help="Publish the results to Github")
263 263 parser.add_argument('number', type=int, help="The pull request number")
264 264
265 265 args = parser.parse_args()
266 266 test_pr(args.number, post_results=args.publish)
General Comments 0
You need to be logged in to leave comments. Login now