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