Merge lp:~zyga/checkbox/result-history into lp:checkbox
- result-history
- Merge into trunk
Proposed by
Zygmunt Krynicki
Status: | Merged |
---|---|
Approved by: | Sylvain Pineau |
Approved revision: | 3824 |
Merged at revision: | 3816 |
Proposed branch: | lp:~zyga/checkbox/result-history |
Merge into: | lp:checkbox |
Diff against target: |
585 lines (+157/-30) 12 files modified
checkbox-ng/checkbox_ng/service.py (+1/-1) plainbox/plainbox/data/report/checkbox.html (+25/-0) plainbox/plainbox/impl/exporter/test_text.py (+1/-1) plainbox/plainbox/impl/exporter/text.py (+17/-3) plainbox/plainbox/impl/session/jobs.py (+59/-13) plainbox/plainbox/impl/session/resume.py (+4/-7) plainbox/plainbox/impl/session/suspend.py (+4/-5) plainbox/plainbox/impl/session/test_jobs.py (+11/-0) plainbox/plainbox/test-data/html-exporter/with_both_certification_status.html (+14/-0) plainbox/plainbox/test-data/html-exporter/with_certification_blocker.html (+7/-0) plainbox/plainbox/test-data/html-exporter/with_certification_non_blocker.html (+7/-0) plainbox/plainbox/test-data/html-exporter/without_certification_status.html (+7/-0) |
To merge this branch: | bzr merge lp:~zyga/checkbox/result-history |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Sylvain Pineau (community) | Approve | ||
Review via email: mp+260457@code.launchpad.net |
Commit message
Description of the change
56b7b43 checkbox-
17c2aea plainbox:
f23ccc2 plainbox:
284f0b8 plainbox:
98647a1 plainbox:
d2282fb plainbox:
19e21e2 plainbox:
8b389d0 plainbox:
3f0f87d plainbox:
To post a comment you must log in.
lp:~zyga/checkbox/result-history
updated
- 3823. By Zygmunt Krynicki
-
plainbox:
exporter: text: display result history Signed-off-by: Zygmunt Krynicki <email address hidden>
- 3824. By Zygmunt Krynicki
-
plainbox:
exporter: html: display result history Signed-off-by: Zygmunt Krynicki <email address hidden>
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'checkbox-ng/checkbox_ng/service.py' | |||
2 | --- checkbox-ng/checkbox_ng/service.py 2015-05-28 10:04:37 +0000 | |||
3 | +++ checkbox-ng/checkbox_ng/service.py 2015-05-28 11:25:44 +0000 | |||
4 | @@ -528,7 +528,7 @@ | |||
5 | 528 | return self.native.comments or "" | 528 | return self.native.comments or "" |
6 | 529 | 529 | ||
7 | 530 | @comments.setter | 530 | @comments.setter |
9 | 531 | def comments(self, value): | 531 | def comments(self, new_value): |
10 | 532 | """ | 532 | """ |
11 | 533 | set comments to a new value | 533 | set comments to a new value |
12 | 534 | """ | 534 | """ |
13 | 535 | 535 | ||
14 | === modified file 'plainbox/plainbox/data/report/checkbox.html' | |||
15 | --- plainbox/plainbox/data/report/checkbox.html 2015-05-19 17:56:35 +0000 | |||
16 | +++ plainbox/plainbox/data/report/checkbox.html 2015-05-28 11:25:44 +0000 | |||
17 | @@ -127,6 +127,9 @@ | |||
18 | 127 | font-size: 10px; | 127 | font-size: 10px; |
19 | 128 | line-height: 14px; | 128 | line-height: 14px; |
20 | 129 | } | 129 | } |
21 | 130 | tr.historic-run td:first-child { | ||
22 | 131 | padding-left: 2em; | ||
23 | 132 | } | ||
24 | 130 | .data { | 133 | .data { |
25 | 131 | display: none; | 134 | display: none; |
26 | 132 | } | 135 | } |
27 | @@ -217,6 +220,7 @@ | |||
28 | 217 | <th>Test ID</th> | 220 | <th>Test ID</th> |
29 | 218 | <th>Result</th> | 221 | <th>Result</th> |
30 | 219 | <th>Certification status</th> | 222 | <th>Certification status</th> |
31 | 223 | <th>Run</th> | ||
32 | 220 | <th>Comment</th> | 224 | <th>Comment</th> |
33 | 221 | </tr> | 225 | </tr> |
34 | 222 | </thead> | 226 | </thead> |
35 | @@ -226,6 +230,7 @@ | |||
36 | 226 | <td>{{ job_state.job.tr_summary() }}</td> | 230 | <td>{{ job_state.job.tr_summary() }}</td> |
37 | 227 | <td style='font-weight: bold; color: {{ job_state.result.outcome_meta().color_hex }}'>{{ job_state.result.outcome_meta().tr_label }}</td> | 231 | <td style='font-weight: bold; color: {{ job_state.result.outcome_meta().color_hex }}'>{{ job_state.result.outcome_meta().tr_label }}</td> |
38 | 228 | <td>{{ job_state.effective_certification_status }}</td> | 232 | <td>{{ job_state.effective_certification_status }}</td> |
39 | 233 | <td>{{ job_state.result_history|length }}</td> | ||
40 | 229 | {%- if job_state.result.comments != None %} | 234 | {%- if job_state.result.comments != None %} |
41 | 230 | <td>{{ job_state.result.comments }}</td> | 235 | <td>{{ job_state.result.comments }}</td> |
42 | 231 | {%- else %} | 236 | {%- else %} |
43 | @@ -239,6 +244,26 @@ | |||
44 | 239 | {%- endif %} | 244 | {%- endif %} |
45 | 240 | {%- endif %} | 245 | {%- endif %} |
46 | 241 | </tr> | 246 | </tr> |
47 | 247 | {%- for result in job_state.result_history[:-1] %} | ||
48 | 248 | <tr class='historic-run'> | ||
49 | 249 | <td>{{ job_state.job.tr_summary() }}</td> | ||
50 | 250 | <td style='font-weight: bold; color: {{ result.outcome_meta().color_hex }}'>{{ result.outcome_meta().tr_label }}</td> | ||
51 | 251 | <td></td> | ||
52 | 252 | <td>{{ loop.index }}</td> | ||
53 | 253 | {%- if result.comments != None %} | ||
54 | 254 | <td>{{ result.comments }}</td> | ||
55 | 255 | {%- else %} | ||
56 | 256 | {%- if result.io_log_as_flat_text != "" %} | ||
57 | 257 | <td><div style="vertical-align: middle; width: 420px; overflow: auto;"> | ||
58 | 258 | <pre>{{ result.io_log_as_flat_text }}</pre> | ||
59 | 259 | </div> | ||
60 | 260 | </td> | ||
61 | 261 | {%- else %} | ||
62 | 262 | <td> </td> | ||
63 | 263 | {%- endif %} | ||
64 | 264 | {%- endif %} | ||
65 | 265 | </tr> | ||
66 | 266 | {%- endfor %} | ||
67 | 242 | {%- endfor %} | 267 | {%- endfor %} |
68 | 243 | </tbody> | 268 | </tbody> |
69 | 244 | </table> | 269 | </table> |
70 | 245 | 270 | ||
71 | === modified file 'plainbox/plainbox/impl/exporter/test_text.py' | |||
72 | --- plainbox/plainbox/impl/exporter/test_text.py 2014-11-11 06:06:55 +0000 | |||
73 | +++ plainbox/plainbox/impl/exporter/test_text.py 2015-05-28 11:25:44 +0000 | |||
74 | @@ -44,7 +44,7 @@ | |||
75 | 44 | data = mock.Mock( | 44 | data = mock.Mock( |
76 | 45 | run_list=[job], | 45 | run_list=[job], |
77 | 46 | job_state_map={ | 46 | job_state_map={ |
79 | 47 | job.id: mock.Mock(result=result, job=job) | 47 | job.id: mock.Mock(result=result, job=job, result_history=()) |
80 | 48 | } | 48 | } |
81 | 49 | ) | 49 | ) |
82 | 50 | stream = BytesIO() | 50 | stream = BytesIO() |
83 | 51 | 51 | ||
84 | === modified file 'plainbox/plainbox/impl/exporter/text.py' | |||
85 | --- plainbox/plainbox/impl/exporter/text.py 2015-03-31 08:38:35 +0000 | |||
86 | +++ plainbox/plainbox/impl/exporter/text.py 2015-05-28 11:25:44 +0000 | |||
87 | @@ -25,15 +25,15 @@ | |||
88 | 25 | 25 | ||
89 | 26 | THIS MODULE DOES NOT HAVE STABLE PUBLIC API | 26 | THIS MODULE DOES NOT HAVE STABLE PUBLIC API |
90 | 27 | """ | 27 | """ |
91 | 28 | from plainbox.i18n import gettext as _ | ||
92 | 28 | from plainbox.impl.commands.inv_run import Colorizer | 29 | from plainbox.impl.commands.inv_run import Colorizer |
93 | 29 | from plainbox.impl.exporter import SessionStateExporterBase | 30 | from plainbox.impl.exporter import SessionStateExporterBase |
94 | 30 | from plainbox.impl.result import outcome_meta | 31 | from plainbox.impl.result import outcome_meta |
95 | 31 | 32 | ||
96 | 32 | 33 | ||
97 | 33 | class TextSessionStateExporter(SessionStateExporterBase): | 34 | class TextSessionStateExporter(SessionStateExporterBase): |
101 | 34 | """ | 35 | |
102 | 35 | Human-readable session state exporter. | 36 | """Human-readable session state exporter.""" |
100 | 36 | """ | ||
103 | 37 | 37 | ||
104 | 38 | def __init__(self, option_list=None, color=None): | 38 | def __init__(self, option_list=None, color=None): |
105 | 39 | super().__init__(option_list) | 39 | super().__init__(option_list) |
106 | @@ -55,9 +55,23 @@ | |||
107 | 55 | outcome_meta(state.result.outcome).color_ansi | 55 | outcome_meta(state.result.outcome).color_ansi |
108 | 56 | ), state.job.tr_summary(), | 56 | ), state.job.tr_summary(), |
109 | 57 | ).encode("UTF-8")) | 57 | ).encode("UTF-8")) |
110 | 58 | if len(state.result_history) > 1: | ||
111 | 59 | stream.write(_(" history: {0}\n").format( | ||
112 | 60 | ', '.join( | ||
113 | 61 | self.C.custom( | ||
114 | 62 | result.outcome_meta().tr_outcome, | ||
115 | 63 | result.outcome_meta().color_ansi) | ||
116 | 64 | for result in state.result_history) | ||
117 | 65 | ).encode("UTF-8")) | ||
118 | 58 | else: | 66 | else: |
119 | 59 | stream.write( | 67 | stream.write( |
120 | 60 | "{:^15}: {}\n".format( | 68 | "{:^15}: {}\n".format( |
121 | 61 | state.result.tr_outcome(), | 69 | state.result.tr_outcome(), |
122 | 62 | state.job.tr_summary(), | 70 | state.job.tr_summary(), |
123 | 63 | ).encode("UTF-8")) | 71 | ).encode("UTF-8")) |
124 | 72 | if state.result_history: | ||
125 | 73 | print(_("History:"), ', '.join( | ||
126 | 74 | self.C.custom( | ||
127 | 75 | result.outcome_meta().unicode_sigil, | ||
128 | 76 | result.outcome_meta().color_ansi) | ||
129 | 77 | for result in state.result_history)) | ||
130 | 64 | 78 | ||
131 | === modified file 'plainbox/plainbox/impl/session/jobs.py' | |||
132 | --- plainbox/plainbox/impl/session/jobs.py 2015-04-09 13:20:17 +0000 | |||
133 | +++ plainbox/plainbox/impl/session/jobs.py 2015-05-28 11:25:44 +0000 | |||
134 | @@ -16,6 +16,8 @@ | |||
135 | 16 | # You should have received a copy of the GNU General Public License | 16 | # You should have received a copy of the GNU General Public License |
136 | 17 | # along with Checkbox. If not, see <http://www.gnu.org/licenses/>. | 17 | # along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
137 | 18 | """ | 18 | """ |
138 | 19 | Job State. | ||
139 | 20 | |||
140 | 19 | :mod:`plainbox.impl.session.jobs` -- jobs state handling | 21 | :mod:`plainbox.impl.session.jobs` -- jobs state handling |
141 | 20 | ======================================================== | 22 | ======================================================== |
142 | 21 | 23 | ||
143 | @@ -39,8 +41,9 @@ | |||
144 | 39 | 41 | ||
145 | 40 | 42 | ||
146 | 41 | class InhibitionCause(IntEnum): | 43 | class InhibitionCause(IntEnum): |
147 | 44 | |||
148 | 42 | """ | 45 | """ |
150 | 43 | There are four possible not-ready causes: | 46 | There are four possible not-ready causes. |
151 | 44 | 47 | ||
152 | 45 | UNDESIRED: | 48 | UNDESIRED: |
153 | 46 | This job was not selected to run in this session | 49 | This job was not selected to run in this session |
154 | @@ -58,6 +61,7 @@ | |||
155 | 58 | FAILED_RESOURCE: | 61 | FAILED_RESOURCE: |
156 | 59 | This job has a resource requirement that evaluated to a false value | 62 | This job has a resource requirement that evaluated to a false value |
157 | 60 | """ | 63 | """ |
158 | 64 | |||
159 | 61 | UNDESIRED = 0 | 65 | UNDESIRED = 0 |
160 | 62 | PENDING_DEP = 1 | 66 | PENDING_DEP = 1 |
161 | 63 | FAILED_DEP = 2 | 67 | FAILED_DEP = 2 |
162 | @@ -68,6 +72,8 @@ | |||
163 | 68 | def cause_convert_assign_filter( | 72 | def cause_convert_assign_filter( |
164 | 69 | instance: pod.POD, field: pod.Field, old: "Any", new: "Any") -> "Any": | 73 | instance: pod.POD, field: pod.Field, old: "Any", new: "Any") -> "Any": |
165 | 70 | """ | 74 | """ |
166 | 75 | Assign filter for for JobReadinessInhibitor.cause. | ||
167 | 76 | |||
168 | 71 | Custom assign filter for the JobReadinessInhibitor.cause field that | 77 | Custom assign filter for the JobReadinessInhibitor.cause field that |
169 | 72 | produces a very specific error message. | 78 | produces a very specific error message. |
170 | 73 | """ | 79 | """ |
171 | @@ -78,6 +84,7 @@ | |||
172 | 78 | 84 | ||
173 | 79 | 85 | ||
174 | 80 | class JobReadinessInhibitor(pod.POD): | 86 | class JobReadinessInhibitor(pod.POD): |
175 | 87 | |||
176 | 81 | """ | 88 | """ |
177 | 82 | Class representing the cause of a job not being ready to execute. | 89 | Class representing the cause of a job not being ready to execute. |
178 | 83 | 90 | ||
179 | @@ -122,6 +129,7 @@ | |||
180 | 122 | Provides additional context for the problem caused by a failing | 129 | Provides additional context for the problem caused by a failing |
181 | 123 | resource expression. | 130 | resource expression. |
182 | 124 | """ | 131 | """ |
183 | 132 | |||
184 | 125 | # XXX: PENDING_RESOURCE is not strict, there are multiple states that are | 133 | # XXX: PENDING_RESOURCE is not strict, there are multiple states that are |
185 | 126 | # clumped here which is something I don't like. A resource may be still | 134 | # clumped here which is something I don't like. A resource may be still |
186 | 127 | # "pending" as in PENDING_DEP (it has not ran yet) or it could have ran but | 135 | # "pending" as in PENDING_DEP (it has not ran yet) or it could have ran but |
187 | @@ -176,11 +184,13 @@ | |||
188 | 176 | ).format(self.cause.name)) | 184 | ).format(self.cause.name)) |
189 | 177 | 185 | ||
190 | 178 | def __repr__(self): | 186 | def __repr__(self): |
191 | 187 | """Get a custom debugging representation of an inhibitor.""" | ||
192 | 179 | return "<{} cause:{} related_job:{!r} related_expression:{!r}>".format( | 188 | return "<{} cause:{} related_job:{!r} related_expression:{!r}>".format( |
193 | 180 | self.__class__.__name__, self.cause.name, self.related_job, | 189 | self.__class__.__name__, self.cause.name, self.related_job, |
194 | 181 | self.related_expression) | 190 | self.related_expression) |
195 | 182 | 191 | ||
196 | 183 | def __str__(self): | 192 | def __str__(self): |
197 | 193 | """Get a human-readable text representation of an inhibitor.""" | ||
198 | 184 | if self.cause == InhibitionCause.UNDESIRED: | 194 | if self.cause == InhibitionCause.UNDESIRED: |
199 | 185 | # TRANSLATORS: as in undesired job | 195 | # TRANSLATORS: as in undesired job |
200 | 186 | return _("undesired") | 196 | return _("undesired") |
201 | @@ -211,7 +221,10 @@ | |||
202 | 211 | 221 | ||
203 | 212 | 222 | ||
204 | 213 | class OverridableJobField(pod.Field): | 223 | class OverridableJobField(pod.Field): |
205 | 224 | |||
206 | 214 | """ | 225 | """ |
207 | 226 | A custom Field for modeling job values that can be overridden. | ||
208 | 227 | |||
209 | 215 | A readable-writable field that has a special initial value ``JOB_VALUE`` | 228 | A readable-writable field that has a special initial value ``JOB_VALUE`` |
210 | 216 | which is interpreted as "load this value from the corresponding job | 229 | which is interpreted as "load this value from the corresponding job |
211 | 217 | definition". | 230 | definition". |
212 | @@ -222,11 +235,13 @@ | |||
213 | 222 | 235 | ||
214 | 223 | def __init__(self, job_field, doc=None, type=None, notify=False, | 236 | def __init__(self, job_field, doc=None, type=None, notify=False, |
215 | 224 | assign_filter_list=None): | 237 | assign_filter_list=None): |
216 | 238 | """Initialize a new overridable job field.""" | ||
217 | 225 | super().__init__( | 239 | super().__init__( |
218 | 226 | doc, type, JOB_VALUE, None, notify, assign_filter_list) | 240 | doc, type, JOB_VALUE, None, notify, assign_filter_list) |
219 | 227 | self.job_field = job_field | 241 | self.job_field = job_field |
220 | 228 | 242 | ||
221 | 229 | def __get__(self, instance, owner): | 243 | def __get__(self, instance, owner): |
222 | 244 | """Get an overriden (if any) value of an overridable job field.""" | ||
223 | 230 | value = super().__get__(instance, owner) | 245 | value = super().__get__(instance, owner) |
224 | 231 | if value is JOB_VALUE: | 246 | if value is JOB_VALUE: |
225 | 232 | return getattr(instance.job, self.job_field) | 247 | return getattr(instance.job, self.job_field) |
226 | @@ -235,22 +250,28 @@ | |||
227 | 235 | 250 | ||
228 | 236 | 251 | ||
229 | 237 | def job_assign_filter(instance, field, old_value, new_value): | 252 | def job_assign_filter(instance, field, old_value, new_value): |
235 | 238 | # FIXME: This setter should not exist. job attribute should be | 253 | """ |
236 | 239 | # read-only. This is a temporary kludge to get session restoring | 254 | A custom setter for the JobState.job. |
237 | 240 | # over DBus working. Once a solution that doesn't involve setting | 255 | |
238 | 241 | # a JobState's job attribute is implemented, please remove this | 256 | .. warning:: |
239 | 242 | # awful method. | 257 | This setter should not exist. job attribute should be read-only. This |
240 | 258 | is a temporary kludge to get session restoring over DBus working. Once | ||
241 | 259 | a solution that doesn't involve setting a JobState's job attribute is | ||
242 | 260 | implemented, please remove this awful method. | ||
243 | 261 | """ | ||
244 | 243 | return new_value | 262 | return new_value |
245 | 244 | 263 | ||
246 | 245 | 264 | ||
247 | 246 | def job_via_assign_filter(instance, field, old_value, new_value): | 265 | def job_via_assign_filter(instance, field, old_value, new_value): |
248 | 266 | """A custom setter for JobState.via_job.""" | ||
249 | 247 | if (old_value is not pod.UNSET and not isinstance(new_value, JobDefinition) | 267 | if (old_value is not pod.UNSET and not isinstance(new_value, JobDefinition) |
251 | 248 | and not new_value is None): | 268 | and new_value is not None): |
252 | 249 | raise TypeError("via_job must be the actual job, not the checksum") | 269 | raise TypeError("via_job must be the actual job, not the checksum") |
253 | 250 | return new_value | 270 | return new_value |
254 | 251 | 271 | ||
255 | 252 | 272 | ||
256 | 253 | class JobState(pod.POD): | 273 | class JobState(pod.POD): |
257 | 274 | |||
258 | 254 | """ | 275 | """ |
259 | 255 | Class representing the state of a job in a session. | 276 | Class representing the state of a job in a session. |
260 | 256 | 277 | ||
261 | @@ -284,6 +305,11 @@ | |||
262 | 284 | initial_fn=lambda: MemoryJobResult({}), | 305 | initial_fn=lambda: MemoryJobResult({}), |
263 | 285 | notify=True) | 306 | notify=True) |
264 | 286 | 307 | ||
265 | 308 | result_history = pod.Field( | ||
266 | 309 | doc="a tuple of result_history of the associated job", | ||
267 | 310 | type=tuple, initial=(), notify=True, | ||
268 | 311 | assign_filter_list=[pod.typed, pod.typed.sequence(IJobResult)]) | ||
269 | 312 | |||
270 | 287 | via_job = pod.Field( | 313 | via_job = pod.Field( |
271 | 288 | doc="the parent job definition", | 314 | doc="the parent job definition", |
272 | 289 | type=JobDefinition, | 315 | type=JobDefinition, |
273 | @@ -299,16 +325,36 @@ | |||
274 | 299 | doc="the effective certification status of this job", | 325 | doc="the effective certification status of this job", |
275 | 300 | type=str) | 326 | type=str) |
276 | 301 | 327 | ||
277 | 328 | # NOTE: the `result` property just exposes the last result from the | ||
278 | 329 | # `result_history` tuple above. The API is used everywhere so it should not | ||
279 | 330 | # be broken in any way but the way forward is the sequence stored in | ||
280 | 331 | # `result_history`. | ||
281 | 332 | # | ||
282 | 333 | # The one particularly annoying part of this implementation is that each | ||
283 | 334 | # job state always has at least one result. Even if there was no testing | ||
284 | 335 | # done yet. This OUTCOME_NONE result needs to be filtered out at various | ||
285 | 336 | # times. I think it would be better if we could not have it in the | ||
286 | 337 | # sequence-based API anymore. Otherwise each test will have two | ||
287 | 338 | # result_history (more if you count things like resuming a session). | ||
288 | 339 | |||
289 | 340 | @result.change_notifier | ||
290 | 341 | def _result_changed(self, old, new): | ||
291 | 342 | # Don't track the initial assignment over UNSET | ||
292 | 343 | if old is pod.UNSET: | ||
293 | 344 | return | ||
294 | 345 | assert new != old | ||
295 | 346 | assert isinstance(new, IJobResult) | ||
296 | 347 | if new.is_hollow: | ||
297 | 348 | return | ||
298 | 349 | logger.debug("Appending result %r to history: %r", new, self.result_history) | ||
299 | 350 | self.result_history += (new,) | ||
300 | 351 | |||
301 | 302 | def can_start(self): | 352 | def can_start(self): |
305 | 303 | """ | 353 | """Quickly check if the associated job can run right now.""" |
303 | 304 | Quickly check if the associated job can run right now. | ||
304 | 305 | """ | ||
306 | 306 | return len(self.readiness_inhibitor_list) == 0 | 354 | return len(self.readiness_inhibitor_list) == 0 |
307 | 307 | 355 | ||
308 | 308 | def get_readiness_description(self): | 356 | def get_readiness_description(self): |
312 | 309 | """ | 357 | """Get a human readable description of the current readiness state.""" |
310 | 310 | Get a human readable description of the current readiness state | ||
311 | 311 | """ | ||
313 | 312 | if self.readiness_inhibitor_list: | 358 | if self.readiness_inhibitor_list: |
314 | 313 | return _("job cannot be started: {}").format( | 359 | return _("job cannot be started: {}").format( |
315 | 314 | ", ".join((str(inhibitor) | 360 | ", ".join((str(inhibitor) |
316 | 315 | 361 | ||
317 | === modified file 'plainbox/plainbox/impl/session/resume.py' | |||
318 | --- plainbox/plainbox/impl/session/resume.py 2015-04-13 18:20:44 +0000 | |||
319 | +++ plainbox/plainbox/impl/session/resume.py 2015-05-28 11:25:44 +0000 | |||
320 | @@ -707,13 +707,10 @@ | |||
321 | 707 | result = self._build_JobResult( | 707 | result = self._build_JobResult( |
322 | 708 | result_repr, self.flags, self.location) | 708 | result_repr, self.flags, self.location) |
323 | 709 | result_list.append(result) | 709 | result_list.append(result) |
331 | 710 | # Show the _LAST_ result to the session. Currently we only store one | 710 | # Replay each result, one by one |
332 | 711 | # result but showing the most recent (last) result should be good | 711 | for result in result_list: |
333 | 712 | # in general. | 712 | logger.debug(_("calling update_job_result(%r, %r)"), job, result) |
334 | 713 | if len(result_list) > 0: | 713 | session.update_job_result(job, result) |
328 | 714 | logger.debug( | ||
329 | 715 | _("calling update_job_result(%r, %r)"), job, result_list[-1]) | ||
330 | 716 | session.update_job_result(job, result_list[-1]) | ||
335 | 717 | 714 | ||
336 | 718 | @classmethod | 715 | @classmethod |
337 | 719 | def _restore_SessionState_desired_job_list(cls, session, session_repr): | 716 | def _restore_SessionState_desired_job_list(cls, session, session_repr): |
338 | 720 | 717 | ||
339 | === modified file 'plainbox/plainbox/impl/session/suspend.py' | |||
340 | --- plainbox/plainbox/impl/session/suspend.py 2015-04-13 13:58:00 +0000 | |||
341 | +++ plainbox/plainbox/impl/session/suspend.py 2015-05-28 11:25:44 +0000 | |||
342 | @@ -238,7 +238,7 @@ | |||
343 | 238 | } | 238 | } |
344 | 239 | 239 | ||
345 | 240 | def _repr_JobResult(self, obj, session_dir): | 240 | def _repr_JobResult(self, obj, session_dir): |
347 | 241 | """ Compute the representation of one of IJobResult subclasses. """ | 241 | """Compute the representation of one of IJobResult subclasses.""" |
348 | 242 | if isinstance(obj, DiskJobResult): | 242 | if isinstance(obj, DiskJobResult): |
349 | 243 | return self._repr_DiskJobResult(obj, session_dir) | 243 | return self._repr_DiskJobResult(obj, session_dir) |
350 | 244 | elif isinstance(obj, MemoryJobResult): | 244 | elif isinstance(obj, MemoryJobResult): |
351 | @@ -510,11 +510,10 @@ | |||
352 | 510 | if not state.result.is_hollow or state.job.id in id_run_list | 510 | if not state.result.is_hollow or state.job.id in id_run_list |
353 | 511 | }, | 511 | }, |
354 | 512 | "results": { | 512 | "results": { |
358 | 513 | # Currently we store only one result but we may store | 513 | state.job.id: [self._repr_JobResult(result, session_dir) |
359 | 514 | # more than that in a later version. | 514 | for result in state.result_history] |
357 | 515 | state.job.id: [self._repr_JobResult(state.result, session_dir)] | ||
360 | 516 | for state in obj.job_state_map.values() | 515 | for state in obj.job_state_map.values() |
362 | 517 | if not state.result.is_hollow | 516 | if len(state.result_history) > 0 |
363 | 518 | }, | 517 | }, |
364 | 519 | "desired_job_list": [ | 518 | "desired_job_list": [ |
365 | 520 | job.id for job in obj.desired_job_list | 519 | job.id for job in obj.desired_job_list |
366 | 521 | 520 | ||
367 | === modified file 'plainbox/plainbox/impl/session/test_jobs.py' | |||
368 | --- plainbox/plainbox/impl/session/test_jobs.py 2015-04-09 13:20:17 +0000 | |||
369 | +++ plainbox/plainbox/impl/session/test_jobs.py 2015-05-28 11:25:44 +0000 | |||
370 | @@ -140,6 +140,7 @@ | |||
371 | 140 | def test_smoke(self): | 140 | def test_smoke(self): |
372 | 141 | self.assertIsNotNone(self.job_state.result) | 141 | self.assertIsNotNone(self.job_state.result) |
373 | 142 | self.assertIs(self.job_state.result.outcome, IJobResult.OUTCOME_NONE) | 142 | self.assertIs(self.job_state.result.outcome, IJobResult.OUTCOME_NONE) |
374 | 143 | self.assertEqual(self.job_state.result_history, ()) | ||
375 | 143 | self.assertEqual(self.job_state.readiness_inhibitor_list, [ | 144 | self.assertEqual(self.job_state.readiness_inhibitor_list, [ |
376 | 144 | UndesiredJobReadinessInhibitor]) | 145 | UndesiredJobReadinessInhibitor]) |
377 | 145 | self.assertEqual(self.job_state.effective_category_id, | 146 | self.assertEqual(self.job_state.effective_category_id, |
378 | @@ -164,6 +165,16 @@ | |||
379 | 164 | self.job_state.result = result | 165 | self.job_state.result = result |
380 | 165 | self.assertIs(self.job_state.result, result) | 166 | self.assertIs(self.job_state.result, result) |
381 | 166 | 167 | ||
382 | 168 | def test_result_history_keeps_track_of_result_changes(self): | ||
383 | 169 | # XXX: this example will fail if subsequent results are identical | ||
384 | 170 | self.assertEqual(self.job_state.result_history, ()) | ||
385 | 171 | result1 = make_job_result(outcome='fail') | ||
386 | 172 | self.job_state.result = result1 | ||
387 | 173 | self.assertEqual(self.job_state.result_history, (result1,)) | ||
388 | 174 | result2 = make_job_result(outcome='pass') | ||
389 | 175 | self.job_state.result = result2 | ||
390 | 176 | self.assertEqual(self.job_state.result_history, (result1, result2)) | ||
391 | 177 | |||
392 | 167 | def test_setting_result_fires_signal(self): | 178 | def test_setting_result_fires_signal(self): |
393 | 168 | """ | 179 | """ |
394 | 169 | verify that assigning state.result fires the on_result_changed signal | 180 | verify that assigning state.result fires the on_result_changed signal |
395 | 170 | 181 | ||
396 | === modified file 'plainbox/plainbox/test-data/html-exporter/with_both_certification_status.html' | |||
397 | --- plainbox/plainbox/test-data/html-exporter/with_both_certification_status.html 2015-05-19 17:56:35 +0000 | |||
398 | +++ plainbox/plainbox/test-data/html-exporter/with_both_certification_status.html 2015-05-28 11:25:44 +0000 | |||
399 | @@ -123,6 +123,9 @@ | |||
400 | 123 | font-size: 10px; | 123 | font-size: 10px; |
401 | 124 | line-height: 14px; | 124 | line-height: 14px; |
402 | 125 | } | 125 | } |
403 | 126 | tr.historic-run td:first-child { | ||
404 | 127 | padding-left: 2em; | ||
405 | 128 | } | ||
406 | 126 | .data { | 129 | .data { |
407 | 127 | display: none; | 130 | display: none; |
408 | 128 | } | 131 | } |
409 | @@ -203,6 +206,7 @@ | |||
410 | 203 | <th>Test ID</th> | 206 | <th>Test ID</th> |
411 | 204 | <th>Result</th> | 207 | <th>Result</th> |
412 | 205 | <th>Certification status</th> | 208 | <th>Certification status</th> |
413 | 209 | <th>Run</th> | ||
414 | 206 | <th>Comment</th> | 210 | <th>Comment</th> |
415 | 207 | </tr> | 211 | </tr> |
416 | 208 | </thead> | 212 | </thead> |
417 | @@ -211,6 +215,7 @@ | |||
418 | 211 | <td>job 1</td> | 215 | <td>job 1</td> |
419 | 212 | <td style='font-weight: bold; color: #DC3912'>failed</td> | 216 | <td style='font-weight: bold; color: #DC3912'>failed</td> |
420 | 213 | <td>blocker</td> | 217 | <td>blocker</td> |
421 | 218 | <td>1</td> | ||
422 | 214 | <td><div style="vertical-align: middle; width: 420px; overflow: auto;"> | 219 | <td><div style="vertical-align: middle; width: 420px; overflow: auto;"> |
423 | 215 | <pre>FATAL ERROR | 220 | <pre>FATAL ERROR |
424 | 216 | </pre> | 221 | </pre> |
425 | @@ -221,16 +226,25 @@ | |||
426 | 221 | <td>job 2</td> | 226 | <td>job 2</td> |
427 | 222 | <td style='font-weight: bold; color: #DC3912'>failed</td> | 227 | <td style='font-weight: bold; color: #DC3912'>failed</td> |
428 | 223 | <td>non-blocker</td> | 228 | <td>non-blocker</td> |
429 | 229 | <td>2</td> | ||
430 | 224 | <td><div style="vertical-align: middle; width: 420px; overflow: auto;"> | 230 | <td><div style="vertical-align: middle; width: 420px; overflow: auto;"> |
431 | 225 | <pre>FATAL ERROR | 231 | <pre>FATAL ERROR |
432 | 226 | </pre> | 232 | </pre> |
433 | 227 | </div> | 233 | </div> |
434 | 228 | </td> | 234 | </td> |
435 | 229 | </tr> | 235 | </tr> |
436 | 236 | <tr class='historic-run'> | ||
437 | 237 | <td>job 2</td> | ||
438 | 238 | <td style='font-weight: bold; color: #6AA84F'>passed</td> | ||
439 | 239 | <td></td> | ||
440 | 240 | <td>1</td> | ||
441 | 241 | <td>blah blah</td> | ||
442 | 242 | </tr> | ||
443 | 230 | <tr> | 243 | <tr> |
444 | 231 | <td>job 3</td> | 244 | <td>job 3</td> |
445 | 232 | <td style='font-weight: bold; color: #FF9900'>skipped</td> | 245 | <td style='font-weight: bold; color: #FF9900'>skipped</td> |
446 | 233 | <td>unspecified</td> | 246 | <td>unspecified</td> |
447 | 247 | <td>1</td> | ||
448 | 234 | <td>No such device</td> | 248 | <td>No such device</td> |
449 | 235 | </tr> | 249 | </tr> |
450 | 236 | </tbody> | 250 | </tbody> |
451 | 237 | 251 | ||
452 | === modified file 'plainbox/plainbox/test-data/html-exporter/with_certification_blocker.html' | |||
453 | --- plainbox/plainbox/test-data/html-exporter/with_certification_blocker.html 2015-05-19 17:56:35 +0000 | |||
454 | +++ plainbox/plainbox/test-data/html-exporter/with_certification_blocker.html 2015-05-28 11:25:44 +0000 | |||
455 | @@ -123,6 +123,9 @@ | |||
456 | 123 | font-size: 10px; | 123 | font-size: 10px; |
457 | 124 | line-height: 14px; | 124 | line-height: 14px; |
458 | 125 | } | 125 | } |
459 | 126 | tr.historic-run td:first-child { | ||
460 | 127 | padding-left: 2em; | ||
461 | 128 | } | ||
462 | 126 | .data { | 129 | .data { |
463 | 127 | display: none; | 130 | display: none; |
464 | 128 | } | 131 | } |
465 | @@ -186,6 +189,7 @@ | |||
466 | 186 | <th>Test ID</th> | 189 | <th>Test ID</th> |
467 | 187 | <th>Result</th> | 190 | <th>Result</th> |
468 | 188 | <th>Certification status</th> | 191 | <th>Certification status</th> |
469 | 192 | <th>Run</th> | ||
470 | 189 | <th>Comment</th> | 193 | <th>Comment</th> |
471 | 190 | </tr> | 194 | </tr> |
472 | 191 | </thead> | 195 | </thead> |
473 | @@ -194,6 +198,7 @@ | |||
474 | 194 | <td>job 1</td> | 198 | <td>job 1</td> |
475 | 195 | <td style='font-weight: bold; color: #DC3912'>failed</td> | 199 | <td style='font-weight: bold; color: #DC3912'>failed</td> |
476 | 196 | <td>blocker</td> | 200 | <td>blocker</td> |
477 | 201 | <td>1</td> | ||
478 | 197 | <td><div style="vertical-align: middle; width: 420px; overflow: auto;"> | 202 | <td><div style="vertical-align: middle; width: 420px; overflow: auto;"> |
479 | 198 | <pre>FATAL ERROR | 203 | <pre>FATAL ERROR |
480 | 199 | </pre> | 204 | </pre> |
481 | @@ -204,12 +209,14 @@ | |||
482 | 204 | <td>job 2</td> | 209 | <td>job 2</td> |
483 | 205 | <td style='font-weight: bold; color: #6AA84F'>passed</td> | 210 | <td style='font-weight: bold; color: #6AA84F'>passed</td> |
484 | 206 | <td>unspecified</td> | 211 | <td>unspecified</td> |
485 | 212 | <td>1</td> | ||
486 | 207 | <td>blah blah</td> | 213 | <td>blah blah</td> |
487 | 208 | </tr> | 214 | </tr> |
488 | 209 | <tr> | 215 | <tr> |
489 | 210 | <td>job 3</td> | 216 | <td>job 3</td> |
490 | 211 | <td style='font-weight: bold; color: #FF9900'>skipped</td> | 217 | <td style='font-weight: bold; color: #FF9900'>skipped</td> |
491 | 212 | <td>unspecified</td> | 218 | <td>unspecified</td> |
492 | 219 | <td>1</td> | ||
493 | 213 | <td>No such device</td> | 220 | <td>No such device</td> |
494 | 214 | </tr> | 221 | </tr> |
495 | 215 | </tbody> | 222 | </tbody> |
496 | 216 | 223 | ||
497 | === modified file 'plainbox/plainbox/test-data/html-exporter/with_certification_non_blocker.html' | |||
498 | --- plainbox/plainbox/test-data/html-exporter/with_certification_non_blocker.html 2015-05-19 17:56:35 +0000 | |||
499 | +++ plainbox/plainbox/test-data/html-exporter/with_certification_non_blocker.html 2015-05-28 11:25:44 +0000 | |||
500 | @@ -123,6 +123,9 @@ | |||
501 | 123 | font-size: 10px; | 123 | font-size: 10px; |
502 | 124 | line-height: 14px; | 124 | line-height: 14px; |
503 | 125 | } | 125 | } |
504 | 126 | tr.historic-run td:first-child { | ||
505 | 127 | padding-left: 2em; | ||
506 | 128 | } | ||
507 | 126 | .data { | 129 | .data { |
508 | 127 | display: none; | 130 | display: none; |
509 | 128 | } | 131 | } |
510 | @@ -184,6 +187,7 @@ | |||
511 | 184 | <th>Test ID</th> | 187 | <th>Test ID</th> |
512 | 185 | <th>Result</th> | 188 | <th>Result</th> |
513 | 186 | <th>Certification status</th> | 189 | <th>Certification status</th> |
514 | 190 | <th>Run</th> | ||
515 | 187 | <th>Comment</th> | 191 | <th>Comment</th> |
516 | 188 | </tr> | 192 | </tr> |
517 | 189 | </thead> | 193 | </thead> |
518 | @@ -192,6 +196,7 @@ | |||
519 | 192 | <td>job 1</td> | 196 | <td>job 1</td> |
520 | 193 | <td style='font-weight: bold; color: #DC3912'>failed</td> | 197 | <td style='font-weight: bold; color: #DC3912'>failed</td> |
521 | 194 | <td>non-blocker</td> | 198 | <td>non-blocker</td> |
522 | 199 | <td>1</td> | ||
523 | 195 | <td><div style="vertical-align: middle; width: 420px; overflow: auto;"> | 200 | <td><div style="vertical-align: middle; width: 420px; overflow: auto;"> |
524 | 196 | <pre>FATAL ERROR | 201 | <pre>FATAL ERROR |
525 | 197 | </pre> | 202 | </pre> |
526 | @@ -202,12 +207,14 @@ | |||
527 | 202 | <td>job 2</td> | 207 | <td>job 2</td> |
528 | 203 | <td style='font-weight: bold; color: #6AA84F'>passed</td> | 208 | <td style='font-weight: bold; color: #6AA84F'>passed</td> |
529 | 204 | <td>unspecified</td> | 209 | <td>unspecified</td> |
530 | 210 | <td>1</td> | ||
531 | 205 | <td>blah blah</td> | 211 | <td>blah blah</td> |
532 | 206 | </tr> | 212 | </tr> |
533 | 207 | <tr> | 213 | <tr> |
534 | 208 | <td>job 3</td> | 214 | <td>job 3</td> |
535 | 209 | <td style='font-weight: bold; color: #FF9900'>skipped</td> | 215 | <td style='font-weight: bold; color: #FF9900'>skipped</td> |
536 | 210 | <td>unspecified</td> | 216 | <td>unspecified</td> |
537 | 217 | <td>1</td> | ||
538 | 211 | <td>No such device</td> | 218 | <td>No such device</td> |
539 | 212 | </tr> | 219 | </tr> |
540 | 213 | </tbody> | 220 | </tbody> |
541 | 214 | 221 | ||
542 | === modified file 'plainbox/plainbox/test-data/html-exporter/without_certification_status.html' | |||
543 | --- plainbox/plainbox/test-data/html-exporter/without_certification_status.html 2015-05-19 17:56:35 +0000 | |||
544 | +++ plainbox/plainbox/test-data/html-exporter/without_certification_status.html 2015-05-28 11:25:44 +0000 | |||
545 | @@ -123,6 +123,9 @@ | |||
546 | 123 | font-size: 10px; | 123 | font-size: 10px; |
547 | 124 | line-height: 14px; | 124 | line-height: 14px; |
548 | 125 | } | 125 | } |
549 | 126 | tr.historic-run td:first-child { | ||
550 | 127 | padding-left: 2em; | ||
551 | 128 | } | ||
552 | 126 | .data { | 129 | .data { |
553 | 127 | display: none; | 130 | display: none; |
554 | 128 | } | 131 | } |
555 | @@ -167,6 +170,7 @@ | |||
556 | 167 | <th>Test ID</th> | 170 | <th>Test ID</th> |
557 | 168 | <th>Result</th> | 171 | <th>Result</th> |
558 | 169 | <th>Certification status</th> | 172 | <th>Certification status</th> |
559 | 173 | <th>Run</th> | ||
560 | 170 | <th>Comment</th> | 174 | <th>Comment</th> |
561 | 171 | </tr> | 175 | </tr> |
562 | 172 | </thead> | 176 | </thead> |
563 | @@ -175,6 +179,7 @@ | |||
564 | 175 | <td>job 1</td> | 179 | <td>job 1</td> |
565 | 176 | <td style='font-weight: bold; color: #DC3912'>failed</td> | 180 | <td style='font-weight: bold; color: #DC3912'>failed</td> |
566 | 177 | <td>unspecified</td> | 181 | <td>unspecified</td> |
567 | 182 | <td>1</td> | ||
568 | 178 | <td><div style="vertical-align: middle; width: 420px; overflow: auto;"> | 183 | <td><div style="vertical-align: middle; width: 420px; overflow: auto;"> |
569 | 179 | <pre>FATAL ERROR | 184 | <pre>FATAL ERROR |
570 | 180 | </pre> | 185 | </pre> |
571 | @@ -185,12 +190,14 @@ | |||
572 | 185 | <td>job 2</td> | 190 | <td>job 2</td> |
573 | 186 | <td style='font-weight: bold; color: #6AA84F'>passed</td> | 191 | <td style='font-weight: bold; color: #6AA84F'>passed</td> |
574 | 187 | <td>unspecified</td> | 192 | <td>unspecified</td> |
575 | 193 | <td>1</td> | ||
576 | 188 | <td>blah blah</td> | 194 | <td>blah blah</td> |
577 | 189 | </tr> | 195 | </tr> |
578 | 190 | <tr> | 196 | <tr> |
579 | 191 | <td>job 3</td> | 197 | <td>job 3</td> |
580 | 192 | <td style='font-weight: bold; color: #FF9900'>skipped</td> | 198 | <td style='font-weight: bold; color: #FF9900'>skipped</td> |
581 | 193 | <td>unspecified</td> | 199 | <td>unspecified</td> |
582 | 200 | <td>1</td> | ||
583 | 194 | <td>No such device</td> | 201 | <td>No such device</td> |
584 | 195 | </tr> | 202 | </tr> |
585 | 196 | </tbody> | 203 | </tbody> |
tested with both gui and cli using the smoke testplan. all the results are available in the report.
+1