|  | # coding=utf8 | 
|  | # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
|  | # Use of this source code is governed by a BSD-style license that can be | 
|  | # found in the LICENSE file. | 
|  | """Defines base classes for pending change verification classes.""" | 
|  |  | 
|  | import model | 
|  |  | 
|  |  | 
|  | # Verifier state in priority level. | 
|  | # SUCCEEDED : This verifier is fine to commit this patch. | 
|  | # PROCESSING: No decision was made yet. The verifier runs asynchronously. | 
|  | # FAILED    : Verification failed, this patch must not be committed. | 
|  | # IGNORED   : This patch must be ignored and no comment added to the code | 
|  | #             review. | 
|  | SUCCEEDED, PROCESSING, FAILED, IGNORED = range(4) | 
|  | VALID_STATES = set((SUCCEEDED, PROCESSING, FAILED, IGNORED)) | 
|  |  | 
|  |  | 
|  | class DiscardPending(Exception): | 
|  | """Exception to be raised when a pending item should be discarded.""" | 
|  |  | 
|  | def __init__(self, pending, status): | 
|  | super(DiscardPending, self).__init__(status) | 
|  | self.pending = pending | 
|  | self.status = status | 
|  |  | 
|  |  | 
|  | class Verified(model.PersistentMixIn): | 
|  | """A set of verifications that are for a specific patch.""" | 
|  | verifications = dict | 
|  |  | 
|  | def pending_name(self): | 
|  | raise NotImplementedError() | 
|  |  | 
|  | @model.immutable | 
|  | def get_state(self): | 
|  | """Returns the combined state of all the verifiers for this item. | 
|  |  | 
|  | Use priority with the states: IGNORED > FAILED > PROCESSING > SUCCEEDED. | 
|  | """ | 
|  | # If there's an error message, it failed. | 
|  | if self.error_message(): | 
|  | return FAILED | 
|  | if not self.verifications: | 
|  | return PROCESSING | 
|  | states = set(v.get_state() for v in self.verifications.itervalues()) | 
|  | assert states.issubset(VALID_STATES) | 
|  | return max(states) | 
|  |  | 
|  | @model.immutable | 
|  | def postpone(self): | 
|  | """This item shouldn't be committed right now. | 
|  |  | 
|  | Call repeatedly until it returns False. This is a potentially slow call so | 
|  | only call it when get_state() returns SUCEEDED. | 
|  | """ | 
|  | return any(v.postpone() for v in self.verifications.itervalues()) | 
|  |  | 
|  | @model.immutable | 
|  | def error_message(self): | 
|  | """Returns all the error messages concatenated if any.""" | 
|  | out = (i.error_message for i in self.verifications.itervalues()) | 
|  | return '\n\n'.join(filter(None, out)) | 
|  |  | 
|  | def apply_patch(self, context, prepare): | 
|  | """Applies a patch from the codereview tool to the checkout.""" | 
|  | raise NotImplementedError() | 
|  |  | 
|  | @model.immutable | 
|  | def why_not(self): | 
|  | """Returns a string of all the reasons the current patch can't be | 
|  | commited""" | 
|  | why_nots = dict((k, v.why_not()) | 
|  | for k, v in self.verifications.iteritems()) | 
|  | return '\n'.join('%s: %s' % (k, v) for k, v in why_nots.iteritems() if v) | 
|  |  | 
|  |  | 
|  | class IVerifierStatus(model.PersistentMixIn): | 
|  | """Interface for objects in Verified.verifications dictionary.""" | 
|  | error_message = (None, unicode) | 
|  |  | 
|  | def get_state(self): | 
|  | """See Verified.get_state().""" | 
|  | raise NotImplementedError() | 
|  |  | 
|  | @model.immutable | 
|  | def postpone(self):  # pylint: disable=R0201 | 
|  | """See Verified.postpone().""" | 
|  | return False | 
|  |  | 
|  | def why_not(self): | 
|  | """Returns a message why the commit cannot be committed yet. | 
|  |  | 
|  | E.g. why get_state() == PROCESSING. | 
|  | """ | 
|  | raise NotImplementedError() | 
|  |  | 
|  |  | 
|  | class SimpleStatus(IVerifierStatus): | 
|  | """Base class to be used for simple true/false and why not verifiers.""" | 
|  | state = int | 
|  |  | 
|  | def __init__(self, state=PROCESSING, **kwargs): | 
|  | super(SimpleStatus, self).__init__(state=state, **kwargs) | 
|  |  | 
|  | @model.immutable | 
|  | def get_state(self): | 
|  | return self.state | 
|  |  | 
|  | @model.immutable | 
|  | def why_not(self): | 
|  | if self.state == PROCESSING: | 
|  | return 'Processing' | 
|  | return | 
|  |  | 
|  |  | 
|  | class Verifier(object): | 
|  | """This class and its subclasses are *not* serialized.""" | 
|  | name = None | 
|  |  | 
|  | def __init__(self): | 
|  | assert self.name is not None | 
|  |  | 
|  | def verify(self, pending): | 
|  | """Verifies a pending change. | 
|  |  | 
|  | Called with os.getcwd() == checkout.project_path. | 
|  | """ | 
|  | raise NotImplementedError() | 
|  |  | 
|  | def update_status(self, queue): | 
|  | """Updates the status of all pending changes, for asynchronous checks. | 
|  |  | 
|  | It is not necessarily called from inside checkout.project_path. | 
|  | """ | 
|  | raise NotImplementedError() | 
|  |  | 
|  | @model.immutable | 
|  | def loop(self, queue, gen_obj, pending_only): | 
|  | """Loops in a pending queue and returns the verified item corresponding to | 
|  | the Verifier. | 
|  | """ | 
|  | for pending in queue: | 
|  | if self.name not in pending.verifications: | 
|  | pending.verifications[self.name] = gen_obj() | 
|  | if (not pending_only or | 
|  | pending.verifications[self.name].get_state() == PROCESSING): | 
|  | yield pending, pending.verifications[self.name] | 
|  |  | 
|  |  | 
|  | class VerifierCheckout(Verifier):  # pylint: disable=W0223 | 
|  | """A verifier that needs a rietveld and checkout objects. | 
|  |  | 
|  | When verify() is called, it is guaranteed that the patch is applied on the | 
|  | checkout. | 
|  | """ | 
|  | def __init__(self, context_obj): | 
|  | super(VerifierCheckout, self).__init__() | 
|  | self.context = context_obj | 
|  |  | 
|  | @model.immutable | 
|  | def send_status(self, pending, data): | 
|  | """Sends an update to the CQ dashboard. | 
|  |  | 
|  | self.context.status is usually an instance of AsyncPush so the HTTP POST is | 
|  | done in a background thread. | 
|  | """ | 
|  | self.context.status.send( | 
|  | pending, {'verification': self.name, 'payload': data}) |