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