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