Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(910)

Side by Side Diff: verification/try_job_on_rietveld.py

Issue 10907197: Differentiate between tests that needs to be run or need to wait for completion. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/commit-queue
Patch Set: Created 8 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « tests/try_job_on_rietveld_test.py ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # coding=utf8 1 # coding=utf8
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 """Sends patches to the Try server and reads back results. 5 """Sends patches to the Try server and reads back results.
6 6
7 - RietveldTryJobs contains RietveldTryJob, one per try job on a builder. 7 - RietveldTryJobs contains RietveldTryJob, one per try job on a builder.
8 - TryRunnerRietveld uses Rietveld to signal and poll job results. 8 - TryRunnerRietveld uses Rietveld to signal and poll job results.
9 """ 9 """
10 10
(...skipping 12 matching lines...) Expand all
23 A job that occured more than 4 days ago or more than 200 commits behind 23 A job that occured more than 4 days ago or more than 200 commits behind
24 is 'expired'. 24 is 'expired'.
25 """ 25 """
26 if timestamp < (time.time() - 4*24*60*60): 26 if timestamp < (time.time() - 4*24*60*60):
27 return True 27 return True
28 if checkout.revisions(revision, None) >= 200: 28 if checkout.revisions(revision, None) >= 200:
29 return True 29 return True
30 return False 30 return False
31 31
32 32
33 class RietveldTryJobPending(model.PersistentMixIn):
34 """Represents a pending try job for a pending commit that we care about.
35 """
36 persistent = [
37 'builder', 'revision', 'requested_steps',
38 'clobber', 'tries',
39 ]
40 def __init__(self, builder, revision, requested_steps, clobber, tries):
41 super(RietveldTryJobPending, self).__init__()
42 self.builder = builder
43 self.revision = revision
44 self.requested_steps = requested_steps
45 self.clobber = clobber
46 # Number of retries for this configuration. Initial try is 1.
47 self.tries = tries
48
49
33 class RietveldTryJob(model.PersistentMixIn): 50 class RietveldTryJob(model.PersistentMixIn):
34 """Represents a try job for a pending commit that we care about. 51 """Represents a try job for a pending commit that we care about.
35 52
36 This data can be regenerated by parsing all the try job names but it is a bit 53 This data can be regenerated by parsing all the try job names but it is a bit
37 hard on the try server. 54 hard on the try server.
38
39 TODO(maruel): Should use __getstate__(), __setstate__() and __reduce__().
40 """ 55 """
41 persistent = [ 56 persistent = [
42 'builder', 'build', 'revision', 'requested_steps', 'started', 57 'builder', 'build', 'revision', 'requested_steps', 'started',
43 'steps_passed', 'steps_failed', 'clobber', 'completed', 'tries', 58 'steps_passed', 'steps_failed', 'clobber', 'completed', 'tries',
44 ] 59 ]
45 60
46 def __init__( 61 def __init__(
47 self, builder, build, revision, requested_steps, started, passed, failed, 62 self, builder, build, revision, requested_steps, started, passed, failed,
48 clobber, completed, tries): 63 clobber, completed, tries):
49 super(RietveldTryJob, self).__init__() 64 super(RietveldTryJob, self).__init__()
50 self.builder = builder 65 self.builder = builder
51 self.build = build 66 self.build = build
52 self.revision = revision 67 self.revision = revision
53 self.requested_steps = requested_steps 68 self.requested_steps = requested_steps
69 # The timestamp when the build started.
54 self.started = started 70 self.started = started
55 self.steps_passed = passed 71 self.steps_passed = passed
56 self.steps_failed = failed 72 self.steps_failed = failed
57 self.clobber = clobber 73 self.clobber = clobber
58 self.completed = completed 74 self.completed = completed
59 # Number of retries for this configuration. Initial try is 1. 75 # Number of retries for this configuration. Initial try is 1.
60 self.tries = tries 76 self.tries = tries
61 77
62 @property 78 @property
63 def result(self): 79 def result(self):
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
95 # An dict of RietveldTryJob objects per key. 111 # An dict of RietveldTryJob objects per key.
96 self.try_jobs = {} 112 self.try_jobs = {}
97 # The try job keys we ignore because they can't be used to give a good 113 # The try job keys we ignore because they can't be used to give a good
98 # signal: either they are too old (old revision) or they were not triggerd 114 # signal: either they are too old (old revision) or they were not triggerd
99 # by Rietveld, so we don't know if the diff is 100% good. 115 # by Rietveld, so we don't know if the diff is 100% good.
100 self.irrelevant = [] 116 self.irrelevant = []
101 # When NOTRY=true is specified. 117 # When NOTRY=true is specified.
102 self.skipped = False 118 self.skipped = False
103 self.builders_and_tests = {} 119 self.builders_and_tests = {}
104 # Jobs that have been sent but are not found yet. Likely a builder is fully 120 # Jobs that have been sent but are not found yet. Likely a builder is fully
105 # utilized or the try server hasn't polled Rietveld yet. 121 # utilized or the try server hasn't polled Rietveld yet. list of
122 # RietveldTryJobPending() instances.
106 self.pendings = [] 123 self.pendings = []
107 124
108 def get_state(self): 125 def get_state(self):
109 if self.skipped or (not self.remaining() and not self.pendings): 126 if self.skipped or not self.tests_waiting_for_result():
110 return base.SUCCEEDED 127 return base.SUCCEEDED
111 if (self.pendings or 128 if (self.pendings or
112 not all(t.completed for t in self.try_jobs.itervalues())): 129 not all(t.completed for t in self.try_jobs.itervalues())):
113 return base.PROCESSING 130 return base.PROCESSING
114 logging.debug('Not pending, all %d jobs completed' % len(self.try_jobs)) 131 logging.debug('Not pending, all %d jobs completed' % len(self.try_jobs))
115 return base.FAILED 132 return base.FAILED
116 133
117 def remaining(self): 134 def tests_need_to_be_run(self):
118 """Returns what remains to be tested. 135 """Returns which tests need to be run.
119 136
120 This excludes tests from any pending build so they are not retried 137 These are the tests that are not pending on any try job, either running or
121 unnecessarily. 138 in the pending list.
122 """ 139 """
123 # What needs to be run. 140 # What needs to be run.
124 all_tests = dict( 141 all_tests = dict(
125 (builder, set(tests)) 142 (builder, set(tests))
126 for builder, tests in self.builders_and_tests.iteritems()) 143 for builder, tests in self.builders_and_tests.iteritems())
127 144
128 def clean(job): 145 # Removes what was run and almost to be run, e.g. the build is running.
129 if job.builder in all_tests: 146 for try_job in self.try_jobs.itervalues():
130 all_tests[job.builder] -= set(job.steps_passed) 147 if try_job.builder in all_tests:
148 all_tests[try_job.builder] -= set(try_job.steps_passed)
131 # If it was requested but still not run, do not add it either. Only do 149 # If it was requested but still not run, do not add it either. Only do
132 # that if the job hasn't completed yet to catch issues like: a test was 150 # that if the job hasn't completed yet to catch issues like: a test was
133 # requested on a builder but the test was not run because it is not in 151 # requested on a builder but the test was not run because it is not in
134 # the BuildFactory for the Builder. 152 # the BuildFactory for the Builder.
135 if not job.completed: 153 if not try_job.completed:
136 to_be_run = set(job.requested_steps) - set(job.steps_failed) 154 to_be_run = set(try_job.requested_steps) - set(try_job.steps_failed)
137 all_tests[job.builder] -= to_be_run 155 all_tests[try_job.builder] -= to_be_run
156
157 # Removes what is queued to be run but hasn't started yet.
158 for try_job in self.pendings:
159 if try_job.builder in all_tests:
160 all_tests[try_job.builder] -= set(try_job.requested_steps)
161
162 return dict(
163 (builder, list(tests)) for builder, tests in all_tests.iteritems()
164 if tests)
165
166 def tests_waiting_for_result(self):
167 """Returns the tests that we are waiting for results on pending or running
168 builds.
169 """
170 all_tests = dict(
171 (builder, set(tests))
172 for builder, tests in self.builders_and_tests.iteritems())
138 173
139 # Removes what was run. 174 # Removes what was run.
140 for try_job in self.try_jobs.itervalues(): 175 for try_job in self.try_jobs.itervalues():
141 clean(try_job) 176 if try_job.builder in all_tests:
142 for try_job in self.pendings: 177 all_tests[try_job.builder] -= set(try_job.steps_passed)
143 clean(try_job)
144 178
145 return dict( 179 return dict(
146 (builder, list(tests)) for builder, tests in all_tests.iteritems() 180 (builder, list(tests)) for builder, tests in all_tests.iteritems()
147 if tests) 181 if tests)
148 182
149 def update_jobs_from_rietveld(self, data, status, checkout): 183 def update_jobs_from_rietveld(self, data, status, checkout):
150 """Retrieves the jobs statuses from rietveld and updates its state. 184 """Retrieves the jobs statuses from rietveld and updates its state.
151 185
152 Arguments: 186 Arguments:
153 - data: Patchset properties as returned from Rietveld. 187 - data: Patchset properties as returned from Rietveld.
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
208 if is_job_expired(build.revision, build.start_time, checkout): 242 if is_job_expired(build.revision, build.start_time, checkout):
209 logging.debug('Ignoring %s/%d; expired', builder, buildnumber) 243 logging.debug('Ignoring %s/%d; expired', builder, buildnumber)
210 self.irrelevant.append(key) 244 self.irrelevant.append(key)
211 continue 245 continue
212 else: 246 else:
213 requested_steps = job.requested_steps 247 requested_steps = job.requested_steps
214 tries = job.tries 248 tries = job.tries
215 249
216 passed = [s.name for s in build.steps if s.simplified_result] 250 passed = [s.name for s in build.steps if s.simplified_result]
217 failed = [s.name for s in build.steps if s.simplified_result is False] 251 failed = [s.name for s in build.steps if s.simplified_result is False]
252 # The steps in neither passed or failed were skipped.
218 logging.info( 253 logging.info(
219 'Found Job success: %s/%d: %s', 254 'Found Job success: %s/%d: %s',
220 builder, buildnumber, ','.join(passed)) 255 builder, buildnumber, ','.join(passed))
221 new_job = RietveldTryJob( 256 new_job = RietveldTryJob(
222 builder, 257 builder,
223 buildnumber, 258 buildnumber,
224 build.revision, 259 build.revision,
225 requested_steps, 260 requested_steps,
226 build.start_time, 261 build.start_time,
227 passed, 262 passed,
(...skipping 18 matching lines...) Expand all
246 Analysis goes as following: 281 Analysis goes as following:
247 - compile step itself is not flaky. compile.py already takes care of most 282 - compile step itself is not flaky. compile.py already takes care of most
248 flakiness and clobber build is done by default. If compile step fails, try 283 flakiness and clobber build is done by default. If compile step fails, try
249 again with clobber=True 284 again with clobber=True
250 - test steps are flaky and can be retried as necessary. 285 - test steps are flaky and can be retried as necessary.
251 286
252 1. For each existing try jobs from rietveld. 287 1. For each existing try jobs from rietveld.
253 1. Fetch result from try server. 288 1. Fetch result from try server.
254 2. If try job was generated from rietveld; 289 2. If try job was generated from rietveld;
255 1. If not is_job_expired(); 290 1. If not is_job_expired();
256 1. Strips from jobs to run for each succeeded steps 291 1. Skip any scheduled test that succeeded on this builder.
257 2. If no step remaining, mark job as succeeded. 292 2. For each builder with tests scheduled;
293 1. If no step waiting to be triggered, skip this builder completely.
258 2. For each non succeeded job; 294 2. For each non succeeded job;
259 1. Send try jobs to rietveld. 295 1. Send try jobs to rietveld.
260 296
261 Note: It needs rietveld, hence it uses VerifierCheckout, but it doesn't need a 297 Note: It needs rietveld, hence it uses VerifierCheckout, but it doesn't need a
262 checkout. 298 checkout.
263 """ 299 """
264 name = 'try job rietveld' 300 name = 'try job rietveld'
265 301
266 # Only updates a job status once every 60 seconds. 302 # Only updates a job status once every 60 seconds.
267 update_latency = 60 303 update_latency = 60
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
323 # Send any necessary job. Noop if not needed. 359 # Send any necessary job. Noop if not needed.
324 self._send_jobs(pending, jobs) 360 self._send_jobs(pending, jobs)
325 361
326 def _send_jobs(self, pending, jobs): 362 def _send_jobs(self, pending, jobs):
327 """Prepares the RietveldTryJobs instance |jobs| to send try jobs to the try 363 """Prepares the RietveldTryJobs instance |jobs| to send try jobs to the try
328 server. 364 server.
329 """ 365 """
330 if jobs.error_message: 366 if jobs.error_message:
331 # Too late. 367 # Too late.
332 return 368 return
333 remaining = jobs.remaining() 369 remaining = jobs.tests_need_to_be_run()
334 if not remaining: 370 if not remaining:
335 return 371 return
336 # Send them in order to simplify testing. 372 # Send them in order to simplify testing.
337 for builder in sorted(remaining): 373 for builder in sorted(remaining):
338 tests = remaining[builder] 374 tests = remaining[builder]
339 # Find if there was a previous try. 375 # Find if there was a previous try.
340 previous_jobs = [ 376 previous_jobs = [
341 job for job in jobs.try_jobs.itervalues() 377 job for job in jobs.try_jobs.itervalues()
342 if job.builder == builder 378 if job.builder == builder
343 ] 379 ]
(...skipping 16 matching lines...) Expand all
360 396
361 # Do one request per builder so each can have a different 'clobber' 397 # Do one request per builder so each can have a different 'clobber'
362 # setting. 398 # setting.
363 logging.debug( 399 logging.debug(
364 'Sending job %s for %s: %s', pending.issue, builder, ','.join(tests)) 400 'Sending job %s for %s: %s', pending.issue, builder, ','.join(tests))
365 # TODO(maruel): Use keys. 401 # TODO(maruel): Use keys.
366 self.context.rietveld.trigger_try_jobs( 402 self.context.rietveld.trigger_try_jobs(
367 pending.issue, pending.patchset, 'CQ', clobber, None, 403 pending.issue, pending.patchset, 'CQ', clobber, None,
368 {builder: tests}) 404 {builder: tests})
369 jobs.pendings.append( 405 jobs.pendings.append(
370 RietveldTryJob( 406 RietveldTryJobPending(builder, None, tests, clobber, tries + 1))
371 builder, None, None, tests, time.time(), [], [],
372 clobber, False, tries + 1))
373 # Update the status on the AppEngine status to signal a new try job was 407 # Update the status on the AppEngine status to signal a new try job was
374 # sent. 408 # sent.
375 info = { 409 info = {
376 'builder': builder, 410 'builder': builder,
377 'clobber': clobber, 411 'clobber': clobber,
378 'job_name': 'CQ', 412 'job_name': 'CQ',
379 'revision': None, #revision, 413 'revision': None, #revision,
380 } 414 }
381 self.send_status(pending, info) 415 self.send_status(pending, info)
382 416
(...skipping 15 matching lines...) Expand all
398 'build': job.build, 432 'build': job.build,
399 'builder': job.builder, 433 'builder': job.builder,
400 'duration': build.duration, 434 'duration': build.duration,
401 'job_name': 'CQ', 435 'job_name': 'CQ',
402 'result': job.result, 436 'result': job.result,
403 'revision': job.revision, 437 'revision': job.revision,
404 'url': self._build_status_url(job), 438 'url': self._build_status_url(job),
405 } 439 }
406 self.send_status(pending, info) 440 self.send_status(pending, info)
407 441
408 remaining = jobs.remaining() 442 remaining = jobs.tests_need_to_be_run()
409 retry = [ 443 retry = [
410 step for step in job.steps_failed 444 step for step in job.steps_failed
411 if step in remaining.get(job.builder, []) 445 if step in remaining.get(job.builder, [])
412 ] 446 ]
413 if job.tries > 2: 447 if job.tries > 2:
414 jobs.error_message = 'Retried try job too often for step(s) %s' % ( 448 jobs.error_message = 'Retried try job too often for step(s) %s' % (
415 ', '.join(retry)) 449 ', '.join(retry))
416 logging.info(jobs.error_message) 450 logging.info(jobs.error_message)
417 elif 'update' in job.steps_failed: 451 elif 'update' in job.steps_failed:
418 jobs.error_message = ( 452 jobs.error_message = (
(...skipping 16 matching lines...) Expand all
435 return match and match.group(1).lower() == 'true' 469 return match and match.group(1).lower() == 'true'
436 470
437 def _update_jobs_from_rietveld(self, pending, jobs): 471 def _update_jobs_from_rietveld(self, pending, jobs):
438 """Grabs data from Rietveld and pass it to 472 """Grabs data from Rietveld and pass it to
439 RietveldTryJobs.update_jobs_from_rietveld(). 473 RietveldTryJobs.update_jobs_from_rietveld().
440 """ 474 """
441 data = self.context.rietveld.get_patchset_properties( 475 data = self.context.rietveld.get_patchset_properties(
442 pending.issue, pending.patchset) 476 pending.issue, pending.patchset)
443 return jobs.update_jobs_from_rietveld( 477 return jobs.update_jobs_from_rietveld(
444 data, self.status, self.context.checkout) 478 data, self.status, self.context.checkout)
OLDNEW
« no previous file with comments | « tests/try_job_on_rietveld_test.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698