##// END OF EJS Templates
time test run
MinRK -
Show More
@@ -1,239 +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 87 check_call(['git', 'pull', '--no-commit', repo, branch])
87 88 check_call(['git', 'commit', '-m', "merge %s/%s" % (repo, branch)])
88 89 else:
89 90 # Fetch the branch without merging it.
90 91 check_call(['git', 'fetch', repo, branch])
91 92 check_call(['git', 'checkout', 'FETCH_HEAD'])
92 93 os.chdir(basedir)
93 94
94 95 def run_tests(venv):
95 96 py = os.path.join(basedir, venv, 'bin', 'python')
96 97 print(py)
97 98 os.chdir(repodir)
98 99 # cleanup build-dir
99 100 if os.path.exists('build'):
100 101 shutil.rmtree('build')
101 102 check_call([py, 'setup.py', 'install'])
102 103 os.chdir(basedir)
103 104
104 105 iptest = os.path.join(basedir, venv, 'bin', 'iptest')
105 106 if not os.path.exists(iptest):
106 107 iptest = os.path.join(basedir, venv, 'bin', 'iptest3')
107 108
108 109 print("\nRunning tests, this typically takes a few minutes...")
109 110 try:
110 111 return True, check_output([iptest], stderr=STDOUT).decode('utf-8')
111 112 except CalledProcessError as e:
112 113 return False, e.output.decode('utf-8')
113 114
114 115 def markdown_format(pr, results_urls):
115 116 def format_result(py, passed, gist_url, missing_libraries):
116 117 s = "* %s: " % py
117 118 if passed:
118 119 s += "OK"
119 120 else:
120 121 s += "Failed, log at %s" % gist_url
121 122 if missing_libraries:
122 123 s += " (libraries not available: " + missing_libraries + ")"
123 124 return s
124 125
125 126 if pr['mergeable']:
126 127 com = pr['head']['sha'][:7] + " merged into master"
127 128 else:
128 129 com = pr['head']['sha'][:7] + " (can't merge cleanly)"
129 130 lines = ["**Test results for commit %s**" % com,
130 131 "Platform: " + sys.platform,
131 132 ""] + \
132 133 [format_result(*r) for r in results_urls] + \
133 134 ["",
134 135 "Not available for testing: " + ", ".join(unavailable_pythons)]
135 136 return "\n".join(lines)
136 137
137 138 def post_results_comment(pr, results, num):
138 139 body = markdown_format(pr, results)
139 140 gh_api.post_issue_comment(gh_project, num, body)
140 141
141 142 def print_results(pr, results_urls):
142 143 print("\n")
143 144 if pr['mergeable']:
144 145 print("**Test results for commit %s merged into master**" % pr['head']['sha'][:7])
145 146 else:
146 147 print("**Test results for commit %s (can't merge cleanly)**" % pr['head']['sha'][:7])
147 148 print("Platform:", sys.platform)
148 149 for py, passed, gist_url, missing_libraries in results_urls:
149 150 if passed:
150 151 print(py, ":", "OK")
151 152 else:
152 153 print(py, ":", "Failed")
153 154 print(" Test log:", gist_url)
154 155 if missing_libraries:
155 156 print(" Libraries not available:", missing_libraries)
156 157 print("Not available for testing:", ", ".join(unavailable_pythons))
157 158
158 159 def dump_results(num, results, pr):
159 160 with open(os.path.join(basedir, 'lastresults.pkl'), 'wb') as f:
160 161 pickle.dump((num, results, pr), f)
161 162
162 163 def load_results():
163 164 with open(os.path.join(basedir, 'lastresults.pkl'), 'rb') as f:
164 165 return pickle.load(f)
165 166
166 167 def save_logs(results, pr):
167 168 results_paths = []
168 169 for py, passed, log, missing_libraries in results:
169 170 if passed:
170 171 results_paths.append((py, passed, None, missing_libraries))
171 172 else:
172 173
173 174 result_locn = os.path.abspath(os.path.join('venv-%s' % py,
174 175 pr['head']['sha'][:7]+".log"))
175 176 with io.open(result_locn, 'w', encoding='utf-8') as f:
176 177 f.write(log)
177 178
178 179 results_paths.append((py, False, result_locn, missing_libraries))
179 180
180 181 return results_paths
181 182
182 183 def post_logs(results):
183 184 results_urls = []
184 185 for py, passed, log, missing_libraries in results:
185 186 if passed:
186 187 results_urls.append((py, passed, None, missing_libraries))
187 188 else:
188 189 result_locn = gh_api.post_gist(log, description='IPython test log',
189 190 filename="results.log", auth=True)
190 191 results_urls.append((py, False, result_locn, missing_libraries))
191 192
192 193 return results_urls
193 194
194 195 def test_pr(num, post_results=True):
195 196 # Get Github authorisation first, so that the user is prompted straight away
196 197 # if their login is needed.
197 198 if post_results:
198 199 gh_api.get_auth_token()
199 200
200 201 setup()
201 202 pr = gh_api.get_pull_request(gh_project, num)
202 203 get_branch(repo=pr['head']['repo']['clone_url'],
203 204 branch=pr['head']['ref'],
204 205 owner=pr['head']['repo']['owner']['login'],
205 206 mergeable=pr['mergeable'],
206 207 )
207 208
208 209 results = []
209 210 for py, venv in venvs:
211 tic = time.time()
210 212 passed, log = run_tests(venv)
213 elapsed = int(time.time() - tic)
214 print("Ran tests with %s in %is" % (py, elapsed))
211 215 missing_libraries = get_missing_libraries(log)
212 216 if passed:
213 217 results.append((py, True, None, missing_libraries))
214 218 else:
215 219 results.append((py, False, log, missing_libraries))
216 220
217 221 dump_results(num, results, pr)
218 222
219 223 results_paths = save_logs(results, pr)
220 224 print_results(pr, results_paths)
221 225
222 226 if post_results:
223 227 results_urls = post_logs(results)
224 228 post_results_comment(pr, results_urls, num)
225 229 print("(Posted to Github)")
226 230 else:
227 231 post_script = os.path.join(os.path.dirname(sys.argv[0]), "post_pr_test.py")
228 232 print("To post the results to Github, run", post_script)
229 233
230 234
231 235 if __name__ == '__main__':
232 236 import argparse
233 237 parser = argparse.ArgumentParser(description="Test an IPython pull request")
234 238 parser.add_argument('-p', '--publish', action='store_true',
235 239 help="Publish the results to Github")
236 240 parser.add_argument('number', type=int, help="The pull request number")
237 241
238 242 args = parser.parse_args()
239 243 test_pr(args.number, post_results=args.publish)
General Comments 0
You need to be logged in to leave comments. Login now