Merge lp:~zyga/checkbox/result-history into lp:checkbox

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
Reviewer Review Type Date Requested Status
Sylvain Pineau (community) Approve
Review via email: mp+260457@code.launchpad.net

Description of the change

56b7b43 checkbox-ng:service: fix copy-paste error
17c2aea plainbox:session:jobs: fix PEP257 issues
f23ccc2 plainbox:session:jobs: keep track of result history
284f0b8 plainbox:session:suspend: fix PEP257 issue
98647a1 plainbox:session:suspend: store full result history
d2282fb plainbox:session:result: replay the full result history
19e21e2 plainbox:exporter:text: fix PEP257 issue
8b389d0 plainbox:exporter:text: display result history
3f0f87d plainbox:exporter:html: display result history

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>

Revision history for this message
Sylvain Pineau (sylvain-pineau) wrote :

tested with both gui and cli using the smoke testplan. all the results are available in the report.

+1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'checkbox-ng/checkbox_ng/service.py'
--- checkbox-ng/checkbox_ng/service.py 2015-05-28 10:04:37 +0000
+++ checkbox-ng/checkbox_ng/service.py 2015-05-28 11:25:44 +0000
@@ -528,7 +528,7 @@
528 return self.native.comments or ""528 return self.native.comments or ""
529529
530 @comments.setter530 @comments.setter
531 def comments(self, value):531 def comments(self, new_value):
532 """532 """
533 set comments to a new value533 set comments to a new value
534 """534 """
535535
=== modified file 'plainbox/plainbox/data/report/checkbox.html'
--- plainbox/plainbox/data/report/checkbox.html 2015-05-19 17:56:35 +0000
+++ plainbox/plainbox/data/report/checkbox.html 2015-05-28 11:25:44 +0000
@@ -127,6 +127,9 @@
127 font-size: 10px;127 font-size: 10px;
128 line-height: 14px;128 line-height: 14px;
129 }129 }
130 tr.historic-run td:first-child {
131 padding-left: 2em;
132 }
130 .data {133 .data {
131 display: none;134 display: none;
132 }135 }
@@ -217,6 +220,7 @@
217 <th>Test ID</th>220 <th>Test ID</th>
218 <th>Result</th>221 <th>Result</th>
219 <th>Certification status</th>222 <th>Certification status</th>
223 <th>Run</th>
220 <th>Comment</th>224 <th>Comment</th>
221 </tr>225 </tr>
222 </thead>226 </thead>
@@ -226,6 +230,7 @@
226 <td>{{ job_state.job.tr_summary() }}</td>230 <td>{{ job_state.job.tr_summary() }}</td>
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>
228 <td>{{ job_state.effective_certification_status }}</td>232 <td>{{ job_state.effective_certification_status }}</td>
233 <td>{{ job_state.result_history|length }}</td>
229 {%- if job_state.result.comments != None %}234 {%- if job_state.result.comments != None %}
230 <td>{{ job_state.result.comments }}</td>235 <td>{{ job_state.result.comments }}</td>
231 {%- else %}236 {%- else %}
@@ -239,6 +244,26 @@
239 {%- endif %}244 {%- endif %}
240 {%- endif %}245 {%- endif %}
241 </tr>246 </tr>
247 {%- for result in job_state.result_history[:-1] %}
248 <tr class='historic-run'>
249 <td>{{ job_state.job.tr_summary() }}</td>
250 <td style='font-weight: bold; color: {{ result.outcome_meta().color_hex }}'>{{ result.outcome_meta().tr_label }}</td>
251 <td></td>
252 <td>{{ loop.index }}</td>
253 {%- if result.comments != None %}
254 <td>{{ result.comments }}</td>
255 {%- else %}
256 {%- if result.io_log_as_flat_text != "" %}
257 <td><div style="vertical-align: middle; width: 420px; overflow: auto;">
258 <pre>{{ result.io_log_as_flat_text }}</pre>
259 </div>
260 </td>
261 {%- else %}
262 <td>&nbsp;</td>
263 {%- endif %}
264 {%- endif %}
265 </tr>
266 {%- endfor %}
242 {%- endfor %}267 {%- endfor %}
243 </tbody>268 </tbody>
244 </table>269 </table>
245270
=== modified file 'plainbox/plainbox/impl/exporter/test_text.py'
--- plainbox/plainbox/impl/exporter/test_text.py 2014-11-11 06:06:55 +0000
+++ plainbox/plainbox/impl/exporter/test_text.py 2015-05-28 11:25:44 +0000
@@ -44,7 +44,7 @@
44 data = mock.Mock(44 data = mock.Mock(
45 run_list=[job],45 run_list=[job],
46 job_state_map={46 job_state_map={
47 job.id: mock.Mock(result=result, job=job)47 job.id: mock.Mock(result=result, job=job, result_history=())
48 }48 }
49 )49 )
50 stream = BytesIO()50 stream = BytesIO()
5151
=== modified file 'plainbox/plainbox/impl/exporter/text.py'
--- plainbox/plainbox/impl/exporter/text.py 2015-03-31 08:38:35 +0000
+++ plainbox/plainbox/impl/exporter/text.py 2015-05-28 11:25:44 +0000
@@ -25,15 +25,15 @@
2525
26 THIS MODULE DOES NOT HAVE STABLE PUBLIC API26 THIS MODULE DOES NOT HAVE STABLE PUBLIC API
27"""27"""
28from plainbox.i18n import gettext as _
28from plainbox.impl.commands.inv_run import Colorizer29from plainbox.impl.commands.inv_run import Colorizer
29from plainbox.impl.exporter import SessionStateExporterBase30from plainbox.impl.exporter import SessionStateExporterBase
30from plainbox.impl.result import outcome_meta31from plainbox.impl.result import outcome_meta
3132
3233
33class TextSessionStateExporter(SessionStateExporterBase):34class TextSessionStateExporter(SessionStateExporterBase):
34 """35
35 Human-readable session state exporter.36 """Human-readable session state exporter."""
36 """
3737
38 def __init__(self, option_list=None, color=None):38 def __init__(self, option_list=None, color=None):
39 super().__init__(option_list)39 super().__init__(option_list)
@@ -55,9 +55,23 @@
55 outcome_meta(state.result.outcome).color_ansi55 outcome_meta(state.result.outcome).color_ansi
56 ), state.job.tr_summary(),56 ), state.job.tr_summary(),
57 ).encode("UTF-8"))57 ).encode("UTF-8"))
58 if len(state.result_history) > 1:
59 stream.write(_(" history: {0}\n").format(
60 ', '.join(
61 self.C.custom(
62 result.outcome_meta().tr_outcome,
63 result.outcome_meta().color_ansi)
64 for result in state.result_history)
65 ).encode("UTF-8"))
58 else:66 else:
59 stream.write(67 stream.write(
60 "{:^15}: {}\n".format(68 "{:^15}: {}\n".format(
61 state.result.tr_outcome(),69 state.result.tr_outcome(),
62 state.job.tr_summary(),70 state.job.tr_summary(),
63 ).encode("UTF-8"))71 ).encode("UTF-8"))
72 if state.result_history:
73 print(_("History:"), ', '.join(
74 self.C.custom(
75 result.outcome_meta().unicode_sigil,
76 result.outcome_meta().color_ansi)
77 for result in state.result_history))
6478
=== modified file 'plainbox/plainbox/impl/session/jobs.py'
--- plainbox/plainbox/impl/session/jobs.py 2015-04-09 13:20:17 +0000
+++ plainbox/plainbox/impl/session/jobs.py 2015-05-28 11:25:44 +0000
@@ -16,6 +16,8 @@
16# You should have received a copy of the GNU General Public License16# You should have received a copy of the GNU General Public License
17# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.17# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
18"""18"""
19Job State.
20
19:mod:`plainbox.impl.session.jobs` -- jobs state handling21:mod:`plainbox.impl.session.jobs` -- jobs state handling
20========================================================22========================================================
2123
@@ -39,8 +41,9 @@
3941
4042
41class InhibitionCause(IntEnum):43class InhibitionCause(IntEnum):
44
42 """45 """
43 There are four possible not-ready causes:46 There are four possible not-ready causes.
4447
45 UNDESIRED:48 UNDESIRED:
46 This job was not selected to run in this session49 This job was not selected to run in this session
@@ -58,6 +61,7 @@
58 FAILED_RESOURCE:61 FAILED_RESOURCE:
59 This job has a resource requirement that evaluated to a false value62 This job has a resource requirement that evaluated to a false value
60 """63 """
64
61 UNDESIRED = 065 UNDESIRED = 0
62 PENDING_DEP = 166 PENDING_DEP = 1
63 FAILED_DEP = 267 FAILED_DEP = 2
@@ -68,6 +72,8 @@
68def cause_convert_assign_filter(72def cause_convert_assign_filter(
69 instance: pod.POD, field: pod.Field, old: "Any", new: "Any") -> "Any":73 instance: pod.POD, field: pod.Field, old: "Any", new: "Any") -> "Any":
70 """74 """
75 Assign filter for for JobReadinessInhibitor.cause.
76
71 Custom assign filter for the JobReadinessInhibitor.cause field that77 Custom assign filter for the JobReadinessInhibitor.cause field that
72 produces a very specific error message.78 produces a very specific error message.
73 """79 """
@@ -78,6 +84,7 @@
7884
7985
80class JobReadinessInhibitor(pod.POD):86class JobReadinessInhibitor(pod.POD):
87
81 """88 """
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.
8390
@@ -122,6 +129,7 @@
122 Provides additional context for the problem caused by a failing129 Provides additional context for the problem caused by a failing
123 resource expression.130 resource expression.
124 """131 """
132
125 # XXX: PENDING_RESOURCE is not strict, there are multiple states that are133 # XXX: PENDING_RESOURCE is not strict, there are multiple states that are
126 # clumped here which is something I don't like. A resource may be still134 # clumped here which is something I don't like. A resource may be still
127 # "pending" as in PENDING_DEP (it has not ran yet) or it could have ran but135 # "pending" as in PENDING_DEP (it has not ran yet) or it could have ran but
@@ -176,11 +184,13 @@
176 ).format(self.cause.name))184 ).format(self.cause.name))
177185
178 def __repr__(self):186 def __repr__(self):
187 """Get a custom debugging representation of an inhibitor."""
179 return "<{} cause:{} related_job:{!r} related_expression:{!r}>".format(188 return "<{} cause:{} related_job:{!r} related_expression:{!r}>".format(
180 self.__class__.__name__, self.cause.name, self.related_job,189 self.__class__.__name__, self.cause.name, self.related_job,
181 self.related_expression)190 self.related_expression)
182191
183 def __str__(self):192 def __str__(self):
193 """Get a human-readable text representation of an inhibitor."""
184 if self.cause == InhibitionCause.UNDESIRED:194 if self.cause == InhibitionCause.UNDESIRED:
185 # TRANSLATORS: as in undesired job195 # TRANSLATORS: as in undesired job
186 return _("undesired")196 return _("undesired")
@@ -211,7 +221,10 @@
211221
212222
213class OverridableJobField(pod.Field):223class OverridableJobField(pod.Field):
224
214 """225 """
226 A custom Field for modeling job values that can be overridden.
227
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``
216 which is interpreted as "load this value from the corresponding job229 which is interpreted as "load this value from the corresponding job
217 definition".230 definition".
@@ -222,11 +235,13 @@
222235
223 def __init__(self, job_field, doc=None, type=None, notify=False,236 def __init__(self, job_field, doc=None, type=None, notify=False,
224 assign_filter_list=None):237 assign_filter_list=None):
238 """Initialize a new overridable job field."""
225 super().__init__(239 super().__init__(
226 doc, type, JOB_VALUE, None, notify, assign_filter_list)240 doc, type, JOB_VALUE, None, notify, assign_filter_list)
227 self.job_field = job_field241 self.job_field = job_field
228242
229 def __get__(self, instance, owner):243 def __get__(self, instance, owner):
244 """Get an overriden (if any) value of an overridable job field."""
230 value = super().__get__(instance, owner)245 value = super().__get__(instance, owner)
231 if value is JOB_VALUE:246 if value is JOB_VALUE:
232 return getattr(instance.job, self.job_field)247 return getattr(instance.job, self.job_field)
@@ -235,22 +250,28 @@
235250
236251
237def job_assign_filter(instance, field, old_value, new_value):252def job_assign_filter(instance, field, old_value, new_value):
238 # FIXME: This setter should not exist. job attribute should be253 """
239 # read-only. This is a temporary kludge to get session restoring254 A custom setter for the JobState.job.
240 # over DBus working. Once a solution that doesn't involve setting255
241 # a JobState's job attribute is implemented, please remove this256 .. warning::
242 # awful method.257 This setter should not exist. job attribute should be read-only. This
258 is a temporary kludge to get session restoring over DBus working. Once
259 a solution that doesn't involve setting a JobState's job attribute is
260 implemented, please remove this awful method.
261 """
243 return new_value262 return new_value
244263
245264
246def job_via_assign_filter(instance, field, old_value, new_value):265def job_via_assign_filter(instance, field, old_value, new_value):
266 """A custom setter for JobState.via_job."""
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)
248 and not new_value is None):268 and new_value is not None):
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")
250 return new_value270 return new_value
251271
252272
253class JobState(pod.POD):273class JobState(pod.POD):
274
254 """275 """
255 Class representing the state of a job in a session.276 Class representing the state of a job in a session.
256277
@@ -284,6 +305,11 @@
284 initial_fn=lambda: MemoryJobResult({}),305 initial_fn=lambda: MemoryJobResult({}),
285 notify=True)306 notify=True)
286307
308 result_history = pod.Field(
309 doc="a tuple of result_history of the associated job",
310 type=tuple, initial=(), notify=True,
311 assign_filter_list=[pod.typed, pod.typed.sequence(IJobResult)])
312
287 via_job = pod.Field(313 via_job = pod.Field(
288 doc="the parent job definition",314 doc="the parent job definition",
289 type=JobDefinition,315 type=JobDefinition,
@@ -299,16 +325,36 @@
299 doc="the effective certification status of this job",325 doc="the effective certification status of this job",
300 type=str)326 type=str)
301327
328 # NOTE: the `result` property just exposes the last result from the
329 # `result_history` tuple above. The API is used everywhere so it should not
330 # be broken in any way but the way forward is the sequence stored in
331 # `result_history`.
332 #
333 # The one particularly annoying part of this implementation is that each
334 # job state always has at least one result. Even if there was no testing
335 # done yet. This OUTCOME_NONE result needs to be filtered out at various
336 # times. I think it would be better if we could not have it in the
337 # sequence-based API anymore. Otherwise each test will have two
338 # result_history (more if you count things like resuming a session).
339
340 @result.change_notifier
341 def _result_changed(self, old, new):
342 # Don't track the initial assignment over UNSET
343 if old is pod.UNSET:
344 return
345 assert new != old
346 assert isinstance(new, IJobResult)
347 if new.is_hollow:
348 return
349 logger.debug("Appending result %r to history: %r", new, self.result_history)
350 self.result_history += (new,)
351
302 def can_start(self):352 def can_start(self):
303 """353 """Quickly check if the associated job can run right now."""
304 Quickly check if the associated job can run right now.
305 """
306 return len(self.readiness_inhibitor_list) == 0354 return len(self.readiness_inhibitor_list) == 0
307355
308 def get_readiness_description(self):356 def get_readiness_description(self):
309 """357 """Get a human readable description of the current readiness state."""
310 Get a human readable description of the current readiness state
311 """
312 if self.readiness_inhibitor_list:358 if self.readiness_inhibitor_list:
313 return _("job cannot be started: {}").format(359 return _("job cannot be started: {}").format(
314 ", ".join((str(inhibitor)360 ", ".join((str(inhibitor)
315361
=== modified file 'plainbox/plainbox/impl/session/resume.py'
--- plainbox/plainbox/impl/session/resume.py 2015-04-13 18:20:44 +0000
+++ plainbox/plainbox/impl/session/resume.py 2015-05-28 11:25:44 +0000
@@ -707,13 +707,10 @@
707 result = self._build_JobResult(707 result = self._build_JobResult(
708 result_repr, self.flags, self.location)708 result_repr, self.flags, self.location)
709 result_list.append(result)709 result_list.append(result)
710 # Show the _LAST_ result to the session. Currently we only store one710 # Replay each result, one by one
711 # result but showing the most recent (last) result should be good711 for result in result_list:
712 # in general.712 logger.debug(_("calling update_job_result(%r, %r)"), job, result)
713 if len(result_list) > 0:713 session.update_job_result(job, result)
714 logger.debug(
715 _("calling update_job_result(%r, %r)"), job, result_list[-1])
716 session.update_job_result(job, result_list[-1])
717714
718 @classmethod715 @classmethod
719 def _restore_SessionState_desired_job_list(cls, session, session_repr):716 def _restore_SessionState_desired_job_list(cls, session, session_repr):
720717
=== modified file 'plainbox/plainbox/impl/session/suspend.py'
--- plainbox/plainbox/impl/session/suspend.py 2015-04-13 13:58:00 +0000
+++ plainbox/plainbox/impl/session/suspend.py 2015-05-28 11:25:44 +0000
@@ -238,7 +238,7 @@
238 }238 }
239239
240 def _repr_JobResult(self, obj, session_dir):240 def _repr_JobResult(self, obj, session_dir):
241 """ Compute the representation of one of IJobResult subclasses. """241 """Compute the representation of one of IJobResult subclasses."""
242 if isinstance(obj, DiskJobResult):242 if isinstance(obj, DiskJobResult):
243 return self._repr_DiskJobResult(obj, session_dir)243 return self._repr_DiskJobResult(obj, session_dir)
244 elif isinstance(obj, MemoryJobResult):244 elif isinstance(obj, MemoryJobResult):
@@ -510,11 +510,10 @@
510 if not state.result.is_hollow or state.job.id in id_run_list510 if not state.result.is_hollow or state.job.id in id_run_list
511 },511 },
512 "results": {512 "results": {
513 # Currently we store only one result but we may store513 state.job.id: [self._repr_JobResult(result, session_dir)
514 # more than that in a later version.514 for result in state.result_history]
515 state.job.id: [self._repr_JobResult(state.result, session_dir)]
516 for state in obj.job_state_map.values()515 for state in obj.job_state_map.values()
517 if not state.result.is_hollow516 if len(state.result_history) > 0
518 },517 },
519 "desired_job_list": [518 "desired_job_list": [
520 job.id for job in obj.desired_job_list519 job.id for job in obj.desired_job_list
521520
=== modified file 'plainbox/plainbox/impl/session/test_jobs.py'
--- plainbox/plainbox/impl/session/test_jobs.py 2015-04-09 13:20:17 +0000
+++ plainbox/plainbox/impl/session/test_jobs.py 2015-05-28 11:25:44 +0000
@@ -140,6 +140,7 @@
140 def test_smoke(self):140 def test_smoke(self):
141 self.assertIsNotNone(self.job_state.result)141 self.assertIsNotNone(self.job_state.result)
142 self.assertIs(self.job_state.result.outcome, IJobResult.OUTCOME_NONE)142 self.assertIs(self.job_state.result.outcome, IJobResult.OUTCOME_NONE)
143 self.assertEqual(self.job_state.result_history, ())
143 self.assertEqual(self.job_state.readiness_inhibitor_list, [144 self.assertEqual(self.job_state.readiness_inhibitor_list, [
144 UndesiredJobReadinessInhibitor])145 UndesiredJobReadinessInhibitor])
145 self.assertEqual(self.job_state.effective_category_id,146 self.assertEqual(self.job_state.effective_category_id,
@@ -164,6 +165,16 @@
164 self.job_state.result = result165 self.job_state.result = result
165 self.assertIs(self.job_state.result, result)166 self.assertIs(self.job_state.result, result)
166167
168 def test_result_history_keeps_track_of_result_changes(self):
169 # XXX: this example will fail if subsequent results are identical
170 self.assertEqual(self.job_state.result_history, ())
171 result1 = make_job_result(outcome='fail')
172 self.job_state.result = result1
173 self.assertEqual(self.job_state.result_history, (result1,))
174 result2 = make_job_result(outcome='pass')
175 self.job_state.result = result2
176 self.assertEqual(self.job_state.result_history, (result1, result2))
177
167 def test_setting_result_fires_signal(self):178 def test_setting_result_fires_signal(self):
168 """179 """
169 verify that assigning state.result fires the on_result_changed signal180 verify that assigning state.result fires the on_result_changed signal
170181
=== modified file 'plainbox/plainbox/test-data/html-exporter/with_both_certification_status.html'
--- plainbox/plainbox/test-data/html-exporter/with_both_certification_status.html 2015-05-19 17:56:35 +0000
+++ plainbox/plainbox/test-data/html-exporter/with_both_certification_status.html 2015-05-28 11:25:44 +0000
@@ -123,6 +123,9 @@
123 font-size: 10px;123 font-size: 10px;
124 line-height: 14px;124 line-height: 14px;
125 }125 }
126 tr.historic-run td:first-child {
127 padding-left: 2em;
128 }
126 .data {129 .data {
127 display: none;130 display: none;
128 }131 }
@@ -203,6 +206,7 @@
203 <th>Test ID</th>206 <th>Test ID</th>
204 <th>Result</th>207 <th>Result</th>
205 <th>Certification status</th>208 <th>Certification status</th>
209 <th>Run</th>
206 <th>Comment</th>210 <th>Comment</th>
207 </tr>211 </tr>
208 </thead>212 </thead>
@@ -211,6 +215,7 @@
211 <td>job 1</td>215 <td>job 1</td>
212 <td style='font-weight: bold; color: #DC3912'>failed</td>216 <td style='font-weight: bold; color: #DC3912'>failed</td>
213 <td>blocker</td>217 <td>blocker</td>
218 <td>1</td>
214 <td><div style="vertical-align: middle; width: 420px; overflow: auto;">219 <td><div style="vertical-align: middle; width: 420px; overflow: auto;">
215 <pre>FATAL ERROR220 <pre>FATAL ERROR
216</pre>221</pre>
@@ -221,16 +226,25 @@
221 <td>job 2</td>226 <td>job 2</td>
222 <td style='font-weight: bold; color: #DC3912'>failed</td>227 <td style='font-weight: bold; color: #DC3912'>failed</td>
223 <td>non-blocker</td>228 <td>non-blocker</td>
229 <td>2</td>
224 <td><div style="vertical-align: middle; width: 420px; overflow: auto;">230 <td><div style="vertical-align: middle; width: 420px; overflow: auto;">
225 <pre>FATAL ERROR231 <pre>FATAL ERROR
226</pre>232</pre>
227 </div>233 </div>
228 </td>234 </td>
229 </tr>235 </tr>
236 <tr class='historic-run'>
237 <td>job 2</td>
238 <td style='font-weight: bold; color: #6AA84F'>passed</td>
239 <td></td>
240 <td>1</td>
241 <td>blah blah</td>
242 </tr>
230 <tr>243 <tr>
231 <td>job 3</td>244 <td>job 3</td>
232 <td style='font-weight: bold; color: #FF9900'>skipped</td>245 <td style='font-weight: bold; color: #FF9900'>skipped</td>
233 <td>unspecified</td>246 <td>unspecified</td>
247 <td>1</td>
234 <td>No such device</td>248 <td>No such device</td>
235 </tr>249 </tr>
236 </tbody>250 </tbody>
237251
=== modified file 'plainbox/plainbox/test-data/html-exporter/with_certification_blocker.html'
--- plainbox/plainbox/test-data/html-exporter/with_certification_blocker.html 2015-05-19 17:56:35 +0000
+++ plainbox/plainbox/test-data/html-exporter/with_certification_blocker.html 2015-05-28 11:25:44 +0000
@@ -123,6 +123,9 @@
123 font-size: 10px;123 font-size: 10px;
124 line-height: 14px;124 line-height: 14px;
125 }125 }
126 tr.historic-run td:first-child {
127 padding-left: 2em;
128 }
126 .data {129 .data {
127 display: none;130 display: none;
128 }131 }
@@ -186,6 +189,7 @@
186 <th>Test ID</th>189 <th>Test ID</th>
187 <th>Result</th>190 <th>Result</th>
188 <th>Certification status</th>191 <th>Certification status</th>
192 <th>Run</th>
189 <th>Comment</th>193 <th>Comment</th>
190 </tr>194 </tr>
191 </thead>195 </thead>
@@ -194,6 +198,7 @@
194 <td>job 1</td>198 <td>job 1</td>
195 <td style='font-weight: bold; color: #DC3912'>failed</td>199 <td style='font-weight: bold; color: #DC3912'>failed</td>
196 <td>blocker</td>200 <td>blocker</td>
201 <td>1</td>
197 <td><div style="vertical-align: middle; width: 420px; overflow: auto;">202 <td><div style="vertical-align: middle; width: 420px; overflow: auto;">
198 <pre>FATAL ERROR203 <pre>FATAL ERROR
199</pre>204</pre>
@@ -204,12 +209,14 @@
204 <td>job 2</td>209 <td>job 2</td>
205 <td style='font-weight: bold; color: #6AA84F'>passed</td>210 <td style='font-weight: bold; color: #6AA84F'>passed</td>
206 <td>unspecified</td>211 <td>unspecified</td>
212 <td>1</td>
207 <td>blah blah</td>213 <td>blah blah</td>
208 </tr>214 </tr>
209 <tr>215 <tr>
210 <td>job 3</td>216 <td>job 3</td>
211 <td style='font-weight: bold; color: #FF9900'>skipped</td>217 <td style='font-weight: bold; color: #FF9900'>skipped</td>
212 <td>unspecified</td>218 <td>unspecified</td>
219 <td>1</td>
213 <td>No such device</td>220 <td>No such device</td>
214 </tr>221 </tr>
215 </tbody>222 </tbody>
216223
=== modified file 'plainbox/plainbox/test-data/html-exporter/with_certification_non_blocker.html'
--- plainbox/plainbox/test-data/html-exporter/with_certification_non_blocker.html 2015-05-19 17:56:35 +0000
+++ plainbox/plainbox/test-data/html-exporter/with_certification_non_blocker.html 2015-05-28 11:25:44 +0000
@@ -123,6 +123,9 @@
123 font-size: 10px;123 font-size: 10px;
124 line-height: 14px;124 line-height: 14px;
125 }125 }
126 tr.historic-run td:first-child {
127 padding-left: 2em;
128 }
126 .data {129 .data {
127 display: none;130 display: none;
128 }131 }
@@ -184,6 +187,7 @@
184 <th>Test ID</th>187 <th>Test ID</th>
185 <th>Result</th>188 <th>Result</th>
186 <th>Certification status</th>189 <th>Certification status</th>
190 <th>Run</th>
187 <th>Comment</th>191 <th>Comment</th>
188 </tr>192 </tr>
189 </thead>193 </thead>
@@ -192,6 +196,7 @@
192 <td>job 1</td>196 <td>job 1</td>
193 <td style='font-weight: bold; color: #DC3912'>failed</td>197 <td style='font-weight: bold; color: #DC3912'>failed</td>
194 <td>non-blocker</td>198 <td>non-blocker</td>
199 <td>1</td>
195 <td><div style="vertical-align: middle; width: 420px; overflow: auto;">200 <td><div style="vertical-align: middle; width: 420px; overflow: auto;">
196 <pre>FATAL ERROR201 <pre>FATAL ERROR
197</pre>202</pre>
@@ -202,12 +207,14 @@
202 <td>job 2</td>207 <td>job 2</td>
203 <td style='font-weight: bold; color: #6AA84F'>passed</td>208 <td style='font-weight: bold; color: #6AA84F'>passed</td>
204 <td>unspecified</td>209 <td>unspecified</td>
210 <td>1</td>
205 <td>blah blah</td>211 <td>blah blah</td>
206 </tr>212 </tr>
207 <tr>213 <tr>
208 <td>job 3</td>214 <td>job 3</td>
209 <td style='font-weight: bold; color: #FF9900'>skipped</td>215 <td style='font-weight: bold; color: #FF9900'>skipped</td>
210 <td>unspecified</td>216 <td>unspecified</td>
217 <td>1</td>
211 <td>No such device</td>218 <td>No such device</td>
212 </tr>219 </tr>
213 </tbody>220 </tbody>
214221
=== modified file 'plainbox/plainbox/test-data/html-exporter/without_certification_status.html'
--- plainbox/plainbox/test-data/html-exporter/without_certification_status.html 2015-05-19 17:56:35 +0000
+++ plainbox/plainbox/test-data/html-exporter/without_certification_status.html 2015-05-28 11:25:44 +0000
@@ -123,6 +123,9 @@
123 font-size: 10px;123 font-size: 10px;
124 line-height: 14px;124 line-height: 14px;
125 }125 }
126 tr.historic-run td:first-child {
127 padding-left: 2em;
128 }
126 .data {129 .data {
127 display: none;130 display: none;
128 }131 }
@@ -167,6 +170,7 @@
167 <th>Test ID</th>170 <th>Test ID</th>
168 <th>Result</th>171 <th>Result</th>
169 <th>Certification status</th>172 <th>Certification status</th>
173 <th>Run</th>
170 <th>Comment</th>174 <th>Comment</th>
171 </tr>175 </tr>
172 </thead>176 </thead>
@@ -175,6 +179,7 @@
175 <td>job 1</td>179 <td>job 1</td>
176 <td style='font-weight: bold; color: #DC3912'>failed</td>180 <td style='font-weight: bold; color: #DC3912'>failed</td>
177 <td>unspecified</td>181 <td>unspecified</td>
182 <td>1</td>
178 <td><div style="vertical-align: middle; width: 420px; overflow: auto;">183 <td><div style="vertical-align: middle; width: 420px; overflow: auto;">
179 <pre>FATAL ERROR184 <pre>FATAL ERROR
180</pre>185</pre>
@@ -185,12 +190,14 @@
185 <td>job 2</td>190 <td>job 2</td>
186 <td style='font-weight: bold; color: #6AA84F'>passed</td>191 <td style='font-weight: bold; color: #6AA84F'>passed</td>
187 <td>unspecified</td>192 <td>unspecified</td>
193 <td>1</td>
188 <td>blah blah</td>194 <td>blah blah</td>
189 </tr>195 </tr>
190 <tr>196 <tr>
191 <td>job 3</td>197 <td>job 3</td>
192 <td style='font-weight: bold; color: #FF9900'>skipped</td>198 <td style='font-weight: bold; color: #FF9900'>skipped</td>
193 <td>unspecified</td>199 <td>unspecified</td>
200 <td>1</td>
194 <td>No such device</td>201 <td>No such device</td>
195 </tr>202 </tr>
196 </tbody>203 </tbody>

Subscribers

People subscribed via source and target branches