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