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 |