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