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