| OLD | NEW |
| (Empty) |
| 1 # coding=utf8 | |
| 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 """Defines base classes for pending change verification classes.""" | |
| 6 | |
| 7 import model | |
| 8 | |
| 9 | |
| 10 # Verifier state in priority level. | |
| 11 # SUCCEEDED : This verifier is fine to commit this patch. | |
| 12 # PROCESSING: No decision was made yet. The verifier runs asynchronously. | |
| 13 # FAILED : Verification failed, this patch must not be committed. | |
| 14 # IGNORED : This patch must be ignored and no comment added to the code | |
| 15 # review. | |
| 16 SUCCEEDED, PROCESSING, FAILED, IGNORED = range(4) | |
| 17 VALID_STATES = set((SUCCEEDED, PROCESSING, FAILED, IGNORED)) | |
| 18 | |
| 19 | |
| 20 class DiscardPending(Exception): | |
| 21 """Exception to be raised when a pending item should be discarded.""" | |
| 22 | |
| 23 def __init__(self, pending, status): | |
| 24 super(DiscardPending, self).__init__(status) | |
| 25 self.pending = pending | |
| 26 self.status = status | |
| 27 | |
| 28 | |
| 29 class Verified(model.PersistentMixIn): | |
| 30 """A set of verifications that are for a specific patch.""" | |
| 31 verifications = dict | |
| 32 | |
| 33 def pending_name(self): | |
| 34 raise NotImplementedError() | |
| 35 | |
| 36 @model.immutable | |
| 37 def get_state(self): | |
| 38 """Returns the combined state of all the verifiers for this item. | |
| 39 | |
| 40 Use priority with the states: IGNORED > FAILED > PROCESSING > SUCCEEDED. | |
| 41 """ | |
| 42 # If there's an error message, it failed. | |
| 43 if self.error_message(): | |
| 44 return FAILED | |
| 45 if not self.verifications: | |
| 46 return PROCESSING | |
| 47 states = set(v.get_state() for v in self.verifications.itervalues()) | |
| 48 assert states.issubset(VALID_STATES) | |
| 49 return max(states) | |
| 50 | |
| 51 @model.immutable | |
| 52 def postpone(self): | |
| 53 """This item shouldn't be committed right now. | |
| 54 | |
| 55 Call repeatedly until it returns False. This is a potentially slow call so | |
| 56 only call it when get_state() returns SUCEEDED. | |
| 57 """ | |
| 58 return any(v.postpone() for v in self.verifications.itervalues()) | |
| 59 | |
| 60 @model.immutable | |
| 61 def error_message(self): | |
| 62 """Returns all the error messages concatenated if any.""" | |
| 63 out = (i.error_message for i in self.verifications.itervalues()) | |
| 64 return '\n\n'.join(filter(None, out)) | |
| 65 | |
| 66 def apply_patch(self, context, prepare): | |
| 67 """Applies a patch from the codereview tool to the checkout.""" | |
| 68 raise NotImplementedError() | |
| 69 | |
| 70 @model.immutable | |
| 71 def why_not(self): | |
| 72 """Returns a string of all the reasons the current patch can't be | |
| 73 commited""" | |
| 74 why_nots = dict((k, v.why_not()) | |
| 75 for k, v in self.verifications.iteritems()) | |
| 76 return '\n'.join('%s: %s' % (k, v) for k, v in why_nots.iteritems() if v) | |
| 77 | |
| 78 | |
| 79 class IVerifierStatus(model.PersistentMixIn): | |
| 80 """Interface for objects in Verified.verifications dictionary.""" | |
| 81 error_message = (None, unicode) | |
| 82 | |
| 83 def get_state(self): | |
| 84 """See Verified.get_state().""" | |
| 85 raise NotImplementedError() | |
| 86 | |
| 87 @model.immutable | |
| 88 def postpone(self): # pylint: disable=R0201 | |
| 89 """See Verified.postpone().""" | |
| 90 return False | |
| 91 | |
| 92 def why_not(self): | |
| 93 """Returns a message why the commit cannot be committed yet. | |
| 94 | |
| 95 E.g. why get_state() == PROCESSING. | |
| 96 """ | |
| 97 raise NotImplementedError() | |
| 98 | |
| 99 | |
| 100 class SimpleStatus(IVerifierStatus): | |
| 101 """Base class to be used for simple true/false and why not verifiers.""" | |
| 102 state = int | |
| 103 | |
| 104 def __init__(self, state=PROCESSING, **kwargs): | |
| 105 super(SimpleStatus, self).__init__(state=state, **kwargs) | |
| 106 | |
| 107 @model.immutable | |
| 108 def get_state(self): | |
| 109 return self.state | |
| 110 | |
| 111 @model.immutable | |
| 112 def why_not(self): | |
| 113 if self.state == PROCESSING: | |
| 114 return 'Processing' | |
| 115 return | |
| 116 | |
| 117 | |
| 118 class Verifier(object): | |
| 119 """This class and its subclasses are *not* serialized.""" | |
| 120 name = None | |
| 121 | |
| 122 def __init__(self): | |
| 123 assert self.name is not None | |
| 124 | |
| 125 def verify(self, pending): | |
| 126 """Verifies a pending change. | |
| 127 | |
| 128 Called with os.getcwd() == checkout.project_path. | |
| 129 """ | |
| 130 raise NotImplementedError() | |
| 131 | |
| 132 def update_status(self, queue): | |
| 133 """Updates the status of all pending changes, for asynchronous checks. | |
| 134 | |
| 135 It is not necessarily called from inside checkout.project_path. | |
| 136 """ | |
| 137 raise NotImplementedError() | |
| 138 | |
| 139 @model.immutable | |
| 140 def loop(self, queue, gen_obj, pending_only): | |
| 141 """Loops in a pending queue and returns the verified item corresponding to | |
| 142 the Verifier. | |
| 143 """ | |
| 144 for pending in queue: | |
| 145 if self.name not in pending.verifications: | |
| 146 pending.verifications[self.name] = gen_obj() | |
| 147 if (not pending_only or | |
| 148 pending.verifications[self.name].get_state() == PROCESSING): | |
| 149 yield pending, pending.verifications[self.name] | |
| 150 | |
| 151 | |
| 152 class VerifierCheckout(Verifier): # pylint: disable=W0223 | |
| 153 """A verifier that needs a rietveld and checkout objects. | |
| 154 | |
| 155 When verify() is called, it is guaranteed that the patch is applied on the | |
| 156 checkout. | |
| 157 """ | |
| 158 def __init__(self, context_obj): | |
| 159 super(VerifierCheckout, self).__init__() | |
| 160 self.context = context_obj | |
| 161 | |
| 162 @model.immutable | |
| 163 def send_status(self, pending, data): | |
| 164 """Sends an update to the CQ dashboard. | |
| 165 | |
| 166 self.context.status is usually an instance of AsyncPush so the HTTP POST is | |
| 167 done in a background thread. | |
| 168 """ | |
| 169 self.context.status.send( | |
| 170 pending, {'verification': self.name, 'payload': data}) | |
| OLD | NEW |