OLD | NEW |
| (Empty) |
1 #!/usr/bin/env python | |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
3 # Use of this source code is governed by a BSD-style license that can be | |
4 # found in the LICENSE file. | |
5 | |
6 """Integration tests for project.py.""" | |
7 | |
8 import logging | |
9 import os | |
10 import random | |
11 import shutil | |
12 import string | |
13 import StringIO | |
14 import sys | |
15 import tempfile | |
16 import time | |
17 import unittest | |
18 import urllib2 | |
19 | |
20 ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) | |
21 sys.path.insert(0, os.path.join(ROOT_DIR, '..')) | |
22 | |
23 import projects | |
24 from verification import base | |
25 from verification import presubmit_check | |
26 from verification import try_job_on_rietveld | |
27 | |
28 # From /tests | |
29 import mocks | |
30 | |
31 | |
32 def _try_comment(pc, issue=31337): | |
33 return ( | |
34 "add_comment(%d, u'%shttp://localhost/user@example.com/%d/1\\n')" % | |
35 (issue, pc.TRYING_PATCH.replace('\n', '\\n'), | |
36 issue)) | |
37 | |
38 | |
39 class TestCase(mocks.TestCase): | |
40 def setUp(self): | |
41 super(TestCase, self).setUp() | |
42 self.mock(projects, '_read_lines', self._read_lines) | |
43 self.mock( | |
44 projects.async_push, | |
45 'AsyncPush', lambda _1, _2: mocks.AsyncPushMock(self)) | |
46 class Dummy(object): | |
47 @staticmethod | |
48 def get_list(): | |
49 return [] | |
50 if not projects.chromium_committers: | |
51 projects.chromium_committers = Dummy() | |
52 self.mock( | |
53 projects.chromium_committers, 'get_list', self._get_committers_list) | |
54 if not projects.nacl_committers: | |
55 projects.nacl_committers = Dummy() | |
56 self.mock(projects.nacl_committers, 'get_list', self._get_committers_list) | |
57 self.mock(presubmit_check.subprocess2, 'check_output', self._check_output) | |
58 self.mock(urllib2, 'urlopen', self._urlopen) | |
59 self.mock(time, 'time', self._time) | |
60 self.check_output = [] | |
61 self.read_lines = [] | |
62 self.urlrequests = [] | |
63 self.time = [] | |
64 | |
65 def tearDown(self): | |
66 try: | |
67 if not self.has_failed(): | |
68 self.assertEqual([], self.check_output) | |
69 self.assertEqual([], self.read_lines) | |
70 self.assertEqual([], self.urlrequests) | |
71 self.assertEqual([], self.time) | |
72 finally: | |
73 super(TestCase, self).tearDown() | |
74 | |
75 # Mocks | |
76 def _urlopen(self, url): | |
77 if not self.urlrequests: | |
78 self.fail(url) | |
79 expected_url, data = self.urlrequests.pop(0) | |
80 self.assertEqual(expected_url, url) | |
81 return StringIO.StringIO(data) | |
82 | |
83 @staticmethod | |
84 def _get_committers_list(): | |
85 return ['user@example.com', 'user@example.org'] | |
86 | |
87 def _read_lines(self, root, error): | |
88 if not self.read_lines: | |
89 self.fail(root) | |
90 a = self.read_lines.pop(0) | |
91 self.assertEqual(a[0], root) | |
92 self.assertEqual(a[1], error) | |
93 return a[2] | |
94 | |
95 def _check_output(self, *args, **kwargs): | |
96 # For now, ignore the arguments. Change if necessary. | |
97 if not self.check_output: | |
98 self.fail((args, kwargs)) | |
99 return self.check_output.pop(0) | |
100 | |
101 def _time(self): | |
102 self.assertTrue(self.time) | |
103 return self.time.pop(0) | |
104 | |
105 | |
106 class ProjectTest(TestCase): | |
107 def setUp(self): | |
108 super(ProjectTest, self).setUp() | |
109 | |
110 def test_loaded(self): | |
111 members = ( | |
112 'blink', 'chromium', 'chromium_deps', 'gyp', 'nacl', 'skia', 'tools') | |
113 self.assertEqual(sorted(members), sorted(projects.supported_projects())) | |
114 | |
115 def test_all(self): | |
116 # Make sure it's possible to load each project. | |
117 self.time = [1] * 2 | |
118 root_dir = os.path.join(os.getcwd(), 'root_dir') | |
119 chromium_status_pwd = os.path.join(root_dir, '.chromium_status_pwd') | |
120 skia_status_pwd = os.path.join(root_dir, '.skia_status_pwd') | |
121 mapping = { | |
122 'blink': { | |
123 'lines': [ | |
124 chromium_status_pwd, 'chromium-status password', ['foo'], | |
125 ], | |
126 'pre_patch_verifiers': ['project_bases', 'reviewer_lgtm'], | |
127 'verifiers': ['try job rietveld', 'tree status'], | |
128 }, | |
129 'chromium': { | |
130 'lines': [ | |
131 chromium_status_pwd, 'chromium-status password', ['foo'], | |
132 ], | |
133 'pre_patch_verifiers': ['project_bases', 'reviewer_lgtm'], | |
134 'verifiers': ['try job rietveld', 'tree status'], | |
135 }, | |
136 'chromium_deps': { | |
137 'lines': [ | |
138 chromium_status_pwd, 'chromium-status password', ['foo'], | |
139 ], | |
140 'pre_patch_verifiers': ['project_bases', 'reviewer_lgtm'], | |
141 'verifiers': ['presubmit'], | |
142 }, | |
143 'gyp': { | |
144 'lines': [ | |
145 chromium_status_pwd, 'chromium-status password', ['foo'], | |
146 ], | |
147 'pre_patch_verifiers': ['project_bases', 'reviewer_lgtm'], | |
148 'verifiers': ['tree status'], | |
149 }, | |
150 'nacl': { | |
151 'lines': [ | |
152 chromium_status_pwd, 'chromium-status password', ['foo'], | |
153 ], | |
154 'pre_patch_verifiers': ['project_bases', 'reviewer_lgtm'], | |
155 'verifiers': ['presubmit', 'tree status'], | |
156 }, | |
157 'skia': { | |
158 'lines': [ | |
159 skia_status_pwd, 'skia-status password', ['foo'], | |
160 ], | |
161 'pre_patch_verifiers': ['project_bases', 'reviewer_lgtm'], | |
162 'verifiers': ['presubmit', 'tree status'], | |
163 }, | |
164 'tools': { | |
165 'lines': [ | |
166 chromium_status_pwd, 'chromium-status password', ['foo'], | |
167 ], | |
168 'pre_patch_verifiers': ['project_bases', 'reviewer_lgtm'], | |
169 'verifiers': ['presubmit'], | |
170 }, | |
171 } | |
172 for project in sorted(projects.supported_projects()): | |
173 logging.debug(project) | |
174 self.assertEqual([], self.read_lines) | |
175 expected = mapping.pop(project) | |
176 self.read_lines = [expected['lines']] | |
177 p = projects.load_project( | |
178 project, 'user', root_dir, self.context.rietveld, True) | |
179 self.assertEqual( | |
180 expected['pre_patch_verifiers'], | |
181 [x.name for x in p.pre_patch_verifiers], | |
182 (expected['pre_patch_verifiers'], | |
183 [x.name for x in p.pre_patch_verifiers], | |
184 project)) | |
185 self.assertEqual( | |
186 expected['verifiers'], [x.name for x in p.verifiers], | |
187 (expected['verifiers'], | |
188 [x.name for x in p.verifiers], | |
189 project)) | |
190 if project == 'tools': | |
191 # Add special checks for it. | |
192 project_bases_verifier = p.pre_patch_verifiers[0] | |
193 branch = '\\@[a-zA-Z0-9\\-_\\.]+$' | |
194 self.assertEqual( | |
195 [ | |
196 # svn | |
197 '^svn\\:\\/\\/svn\\.chromium\\.org\\/chrome/trunk/tools(|/.*)$', | |
198 '^svn\\:\\/\\/chrome\\-svn\\/chrome/trunk/tools(|/.*)$', | |
199 '^svn\\:\\/\\/chrome\\-svn\\.corp\\/chrome/trunk/tools(|/.*)$', | |
200 '^svn\\:\\/\\/chrome\\-svn\\.corp\\.google\\.com\\/chrome/trunk/' | |
201 'tools(|/.*)$', | |
202 '^http\\:\\/\\/src\\.chromium\\.org\\/svn/trunk/tools(|/.*)$', | |
203 '^https\\:\\/\\/src\\.chromium\\.org\\/svn/trunk/tools(|/.*)$', | |
204 '^http\\:\\/\\/src\\.chromium\\.org\\/chrome/trunk/tools(|/.*)$', | |
205 '^https\\:\\/\\/src\\.chromium\\.org\\/chrome/trunk/tools(|/.*)$', | |
206 | |
207 # git | |
208 '^https?\\:\\/\\/git\\.chromium\\.org\\/git\\/chromium\\/tools\\/' | |
209 '([a-z0-9\\-_]+)(?:\\.git)?' + branch, | |
210 '^https?\\:\\/\\/git\\.chromium\\.org\\/chromium\\/tools\\/' | |
211 '([a-z0-9\\-_]+)(?:\\.git)?' + branch, | |
212 '^https?\\:\\/\\/chromium\\.googlesource\\.com\\/chromium\\/tools' | |
213 '\\/([a-z0-9\\-_]+)(?:\\.git)?' + branch, | |
214 '^https?\\:\\/\\/chromium\\.googlesource\\.com\\/a\\/chromium\\/' | |
215 'tools\\/([a-z0-9\\-_]+)(?:\\.git)?' + branch, | |
216 ], | |
217 project_bases_verifier.project_bases) | |
218 self.assertEqual({}, mapping) | |
219 | |
220 | |
221 class ChromiumStateLoad(TestCase): | |
222 # Load a complete state and ensure the code is reacting properly. | |
223 def setUp(self): | |
224 super(ChromiumStateLoad, self).setUp() | |
225 self.buildbot = mocks.BuildbotMock(self) | |
226 self.mock( | |
227 try_job_on_rietveld.buildbot_json, 'Buildbot', lambda _: self.buildbot) | |
228 self.tempdir = tempfile.mkdtemp(prefix='project_test') | |
229 self.now = None | |
230 | |
231 def tearDown(self): | |
232 try: | |
233 shutil.rmtree(self.tempdir) | |
234 finally: | |
235 super(ChromiumStateLoad, self).tearDown() | |
236 | |
237 def _add_build(self, builder, buildnumber, revision, steps, completed): | |
238 """Adds a build with a randomly generated key.""" | |
239 key = ''.join(random.choice(string.ascii_letters) for _ in xrange(8)) | |
240 build = self.buildbot.add_build( | |
241 builder, buildnumber, revision, key, completed, None) | |
242 build.steps.extend(steps) | |
243 return key | |
244 | |
245 def _LoadPendingManagerState(self, issue): | |
246 self.urlrequests = [ | |
247 ( 'http://chromium-status.appspot.com/allstatus?format=json&endTime=%d' % | |
248 (self.now - 300), | |
249 # In theory we should return something but nothing works fine. | |
250 '[]'), | |
251 ] | |
252 self.read_lines = [ | |
253 [ | |
254 os.path.join(self.tempdir, '.chromium_status_pwd'), | |
255 'chromium-status password', | |
256 ['foo'], | |
257 ], | |
258 ] | |
259 self.context.rietveld.patchsets_properties[(issue, 1)] = {} | |
260 | |
261 self.time = [self.now] * 1 | |
262 pc = projects.load_project( | |
263 'chromium', 'invalid', self.tempdir, self.context.rietveld, False) | |
264 self.assertEqual(0, len(self.time)) | |
265 pc.context = self.context | |
266 pc.load(os.path.join(ROOT_DIR, 'chromium.%d.json' % issue)) | |
267 | |
268 # Verify the content a bit. | |
269 self.assertEqual(1, len(pc.queue.iterate())) | |
270 self.assertEqual(issue, pc.queue.get(issue).issue) | |
271 expected = [ | |
272 u'presubmit', | |
273 u'project_bases', | |
274 u'reviewer_lgtm', | |
275 u'tree status', | |
276 u'try job rietveld', | |
277 ] | |
278 self.assertEqual(expected, sorted(pc.queue.get(issue).verifications)) | |
279 | |
280 return pc | |
281 | |
282 def _verify_final_state(self, verifications, why_not, rietveld_calls): | |
283 for name, obj in verifications.iteritems(): | |
284 if name == 'try job rietveld': | |
285 self.assertEqual(base.PROCESSING, obj.get_state(), name) | |
286 self.assertEqual(why_not, obj.why_not()) | |
287 else: | |
288 self.assertEqual(base.SUCCEEDED, obj.get_state(), name) | |
289 self.assertEqual(None, obj.why_not()) | |
290 | |
291 if name == 'tree status': | |
292 self.time = [self.now] * 1 | |
293 self.assertEqual(False, obj.postpone(), name) | |
294 self.assertEqual(0, len(self.time)) | |
295 else: | |
296 self.assertEqual(False, obj.postpone(), name) | |
297 self.context.rietveld.check_calls(rietveld_calls) | |
298 | |
299 def testLoadState(self): | |
300 self.now = 1354207000. | |
301 issue = 31337 | |
302 pending_manager = self._LoadPendingManagerState(issue) | |
303 | |
304 # Then fix the crap out of it. | |
305 self.time = [self.now] * 3 | |
306 pending_manager.update_status() | |
307 self.assertEqual(0, len(self.time)) | |
308 self.assertEqual(1, len(pending_manager.queue.iterate())) | |
309 | |
310 why_not = (u'Waiting for the following jobs:\n' | |
311 ' win_rel: sync_integration_tests\n') | |
312 rietveld_calls = [ | |
313 "trigger_try_jobs(%d, 1, 'CQ', False, 'HEAD', {u'win_rel': " | |
314 "[u'sync_integration_tests']})" % issue | |
315 ] | |
316 self._verify_final_state(pending_manager.queue.get(issue).verifications, | |
317 why_not, rietveld_calls) | |
318 | |
319 def testLoadState11299256(self): | |
320 # Loads a saved state and try to revive it. | |
321 self.now = 1354551606. | |
322 issue = 11299256 | |
323 pending_manager = self._LoadPendingManagerState(issue) | |
324 self._add_build('ios_rel_device', 1, 2, [], 4) | |
325 | |
326 # Then fix the crap out of it. | |
327 self.time = [self.now] * 3 | |
328 pending_manager.update_status() | |
329 self.assertEqual(1, len(self.time)) | |
330 self.assertEqual(1, len(pending_manager.queue.iterate())) | |
331 | |
332 why_not = (u'Waiting for the following jobs:\n' | |
333 ' ios_rel_device: compile\n') | |
334 rietveld_calls = [] | |
335 # ios_rel_device seems lost. CQ should not reissue it now to avoid | |
336 # overloading the tryserver. | |
337 self._verify_final_state(pending_manager.queue.get(issue).verifications, | |
338 why_not, rietveld_calls) | |
339 | |
340 def testLoadState12208028(self): | |
341 # Loads a saved state and try to revive it. | |
342 self.now = 1360256000. | |
343 issue = 12208028 | |
344 pending_manager = self._LoadPendingManagerState(issue) | |
345 | |
346 # Then fix the crap out of it. | |
347 self.time = [self.now] * 3 | |
348 pending_manager.update_status() | |
349 self.assertEqual(0, len(self.time)) | |
350 self.assertEqual(1, len(pending_manager.queue.iterate())) | |
351 | |
352 why_not = (u'Waiting for the following jobs:\n' | |
353 ' android_dbg_triggered_tests: build\n') | |
354 rietveld_calls = [ | |
355 "trigger_try_jobs(%d, 1, 'CQ', False, 'HEAD', {u'android_dbg': " | |
356 "[u'build']})" % issue | |
357 ] | |
358 self._verify_final_state(pending_manager.queue.get(issue).verifications, | |
359 why_not, rietveld_calls) | |
360 | |
361 def testLoadState12253015(self): | |
362 # Loads a saved state and try to revive it. | |
363 self.now = 1360256000. | |
364 issue = 12253015 | |
365 pending_manager = self._LoadPendingManagerState(issue) | |
366 | |
367 # Then fix the crap out of it. | |
368 self.time = [self.now] * 3 | |
369 pending_manager.update_status() | |
370 self.assertEqual(0, len(self.time)) | |
371 self.assertEqual(1, len(pending_manager.queue.iterate())) | |
372 | |
373 why_not = ( | |
374 u'Waiting for the following jobs:\n' | |
375 ' win7_aura: browser_tests\n' | |
376 ' win_rel: chrome_frame_tests,chrome_frame_net_tests,browser_tests,' | |
377 'nacl_integration,sync_integration_tests,installer_util_unittests,' | |
378 'content_browsertests,chrome_frame_unittests,mini_installer_test\n') | |
379 rietveld_calls = [ | |
380 "trigger_try_jobs(%d, 1, 'CQ', False, 'HEAD', {u'win7_aura': " | |
381 "[u'browser_tests']})" % issue, | |
382 ] | |
383 self._verify_final_state(pending_manager.queue.get(issue).verifications, | |
384 why_not, rietveld_calls) | |
385 | |
386 def testLoadState12633013(self): | |
387 # Loads a saved state and try to revive it. | |
388 self.now = 1363610000. | |
389 issue = 12633013 | |
390 pending_manager = self._LoadPendingManagerState(issue) | |
391 | |
392 # Then fix the crap out of it. | |
393 self.time = [self.now] * 3 | |
394 pending_manager.update_status() | |
395 self.assertEqual(0, len(self.time)) | |
396 self.assertEqual(1, len(pending_manager.queue.iterate())) | |
397 | |
398 why_not = ( | |
399 u'Waiting for the following jobs:\n' | |
400 ' android_dbg_triggered_tests: slave_steps\n') | |
401 rietveld_calls = [ | |
402 "trigger_try_jobs(%d, 1, 'CQ', False, 'HEAD', {u'android_dbg': " | |
403 "[u'slave_steps']})" % issue, | |
404 ] | |
405 self._verify_final_state(pending_manager.queue.get(issue).verifications, | |
406 why_not, rietveld_calls) | |
407 | |
408 def testLoadStateSwarm(self): | |
409 # Loads a saved state and try to revive it. | |
410 self.now = 1360256000. | |
411 issue = 666 | |
412 pending_manager = self._LoadPendingManagerState(issue) | |
413 | |
414 # Then fix the crap out of it. | |
415 self.time = [self.now] * 5 | |
416 pending_manager.update_status() | |
417 self.assertEqual(0, len(self.time)) | |
418 self.assertEqual(1, len(pending_manager.queue.iterate())) | |
419 | |
420 why_not = (u'Waiting for the following jobs:\n' | |
421 ' linux_rel: browser_tests\n' | |
422 ' mac_rel: browser_tests\n' | |
423 ' win_rel: browser_tests\n') | |
424 # TODO(csharp): These triggered events should be the swarm versions, | |
425 # change them once swarm tests are enabled by default. | |
426 rietveld_calls = [ | |
427 "trigger_try_jobs(%d, 1, 'CQ', False, 'HEAD', {u'linux_rel': " | |
428 "[u'browser_tests']})" % issue, | |
429 "trigger_try_jobs(%d, 1, 'CQ', False, 'HEAD', {u'mac_rel': " | |
430 "[u'browser_tests']})" % issue, | |
431 "trigger_try_jobs(%d, 1, 'CQ', False, 'HEAD', {u'win_rel': " | |
432 "[u'browser_tests']})" % issue, | |
433 ] | |
434 self._verify_final_state(pending_manager.queue.get(issue).verifications, | |
435 why_not, rietveld_calls) | |
436 | |
437 def test_tbr(self): | |
438 self.time = map(lambda x: float(x*35), range(15)) | |
439 self.urlrequests = [ | |
440 ('https://chromium-status.appspot.com/allstatus?format=json&endTime=85', | |
441 # Cheap hack here. | |
442 '[]'), | |
443 ] | |
444 root_dir = os.path.join(os.getcwd(), 'root_dir') | |
445 self.read_lines = [ | |
446 [ | |
447 os.path.join(root_dir, '.chromium_status_pwd'), | |
448 'chromium-status password', | |
449 ['foo'], | |
450 ], | |
451 ] | |
452 pc = projects.load_project( | |
453 'chromium', 'commit-bot-test', root_dir, self.context.rietveld, True) | |
454 pc.context = self.context | |
455 issue = self.context.rietveld.issues[31337] | |
456 self.context.rietveld.patchsets_properties[(31337, 1)] = {} | |
457 | |
458 # A TBR= patch without reviewer nor messages, like a webkit roll. | |
459 issue['description'] += '\nTBR=' | |
460 issue['reviewers'] = [] | |
461 issue['messages'] = [] | |
462 issue['owner_email'] = u'user@example.com' | |
463 issue['base_url'] = u'svn://svn.chromium.org/chrome/trunk/src' | |
464 pc.look_for_new_pending_commit() | |
465 pc.process_new_pending_commit() | |
466 pc.update_status() | |
467 pc.scan_results() | |
468 self.assertEqual(1, len(pc.queue.iterate())) | |
469 key = self._add_build('chromium_presubmit', 123456, 124, | |
470 [mocks.BuildbotBuildStep('presubmit', False)], True) | |
471 self.context.rietveld.patchsets_properties[(31337, 1)] = { | |
472 'try_job_results': [{ | |
473 'builder': "chromium_presubmit", | |
474 'key': key, | |
475 'buildnumber': "123456", | |
476 }]} | |
477 build = self.buildbot.builders['chromium_presubmit'].builds[123456] | |
478 build.steps[0].simplified_result = True | |
479 pc.update_status() | |
480 pc.scan_results() | |
481 self.assertEqual(0, len(pc.queue.iterate())) | |
482 # check_calls | |
483 self.context.rietveld.check_calls([ | |
484 _try_comment(pc), | |
485 "trigger_try_jobs(31337, 1, 'CQ', False, 'HEAD', " | |
486 "{u'chromium_presubmit': ['presubmit']})", | |
487 'close_issue(31337)', | |
488 "update_description(31337, u'foo\\nTBR=')", | |
489 "add_comment(31337, 'Change committed as 125')", | |
490 ]) | |
491 self.context.checkout.check_calls([ | |
492 'prepare(None)', | |
493 'apply_patch(%r)' % self.context.rietveld.patchsets[0], | |
494 'prepare(None)', | |
495 'apply_patch(%r)' % self.context.rietveld.patchsets[1], | |
496 "commit(u'foo\\nTBR=\\n\\nReview URL: http://nowhere/31337', " | |
497 "u'user@example.com')", | |
498 ]) | |
499 self.context.status.check_names(['initial', 'why not', 'why not', | |
500 'why not', 'commit']) | |
501 | |
502 | |
503 | |
504 if __name__ == '__main__': | |
505 logging.basicConfig( | |
506 level=logging.DEBUG if '-v' in sys.argv else logging.WARNING, | |
507 format='%(levelname)5s %(module)15s(%(lineno)3d): %(message)s') | |
508 unittest.main() | |
OLD | NEW |