VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testmanager/webui/wuireport.py@ 88675

Last change on this file since 88675 was 83403, checked in by vboxsync, 5 years ago

TestManager/webui: Better/generic testbox links.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 34.3 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: wuireport.py 83403 2020-03-25 12:09:58Z vboxsync $
3
4"""
5Test Manager WUI - Reports.
6"""
7
8__copyright__ = \
9"""
10Copyright (C) 2012-2020 Oracle Corporation
11
12This file is part of VirtualBox Open Source Edition (OSE), as
13available from http://www.virtualbox.org. This file is free software;
14you can redistribute it and/or modify it under the terms of the GNU
15General Public License (GPL) as published by the Free Software
16Foundation, in version 2 as it comes in the "COPYING" file of the
17VirtualBox OSE distribution. VirtualBox OSE is distributed in the
18hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
19
20The contents of this file may alternatively be used under the terms
21of the Common Development and Distribution License Version 1.0
22(CDDL) only, as it comes in the "COPYING.CDDL" file of the
23VirtualBox OSE distribution, in which case the provisions of the
24CDDL are applicable instead of those of the GPL.
25
26You may elect to license modified versions of this file under the
27terms and conditions of either the GPL or the CDDL or both.
28"""
29__version__ = "$Revision: 83403 $"
30
31
32# Validation Kit imports.
33from common import webutils;
34from testmanager.webui.wuicontentbase import WuiContentBase, WuiTmLink, WuiSvnLinkWithTooltip;
35from testmanager.webui.wuihlpgraph import WuiHlpGraphDataTable, WuiHlpBarGraph;
36from testmanager.webui.wuitestresult import WuiTestSetLink, WuiTestResultsForTestCaseLink, WuiTestResultsForTestBoxLink;
37from testmanager.webui.wuiadmintestcase import WuiTestCaseDetailsLink;
38from testmanager.webui.wuiadmintestbox import WuiTestBoxDetailsLinkShort;
39from testmanager.core.report import ReportModelBase, ReportFilter;
40from testmanager.core.testresults import TestResultFilter;
41
42
43class WuiReportSummaryLink(WuiTmLink):
44 """ Generic report summary link. """
45
46 def __init__(self, sSubject, aIdSubjects, sName = WuiContentBase.ksShortReportLink,
47 tsNow = None, cPeriods = None, cHoursPerPeriod = None, fBracketed = False, dExtraParams = None):
48 from testmanager.webui.wuimain import WuiMain;
49 dParams = {
50 WuiMain.ksParamAction: WuiMain.ksActionReportSummary,
51 WuiMain.ksParamReportSubject: sSubject,
52 WuiMain.ksParamReportSubjectIds: aIdSubjects,
53 };
54 if dExtraParams is not None:
55 dParams.update(dExtraParams);
56 if tsNow is not None:
57 dParams[WuiMain.ksParamEffectiveDate] = tsNow;
58 if cPeriods is not None:
59 dParams[WuiMain.ksParamReportPeriods] = cPeriods;
60 if cPeriods is not None:
61 dParams[WuiMain.ksParamReportPeriodInHours] = cHoursPerPeriod;
62 WuiTmLink.__init__(self, sName, WuiMain.ksScriptName, dParams, fBracketed = fBracketed);
63
64
65class WuiReportBase(WuiContentBase):
66 """
67 Base class for the reports.
68 """
69
70 def __init__(self, oModel, dParams, fSubReport = False, aiSortColumns = None, fnDPrint = None, oDisp = None):
71 WuiContentBase.__init__(self, fnDPrint = fnDPrint, oDisp = oDisp);
72 self._oModel = oModel;
73 self._dParams = dParams;
74 self._fSubReport = fSubReport;
75 self._sTitle = None;
76 self._aiSortColumns = aiSortColumns;
77
78 # Additional URL parameters for reports:
79 from testmanager.webui.wuimain import WuiMain;
80 self._dExtraParams = ReportFilter().strainParameters(dict() if oDisp is None else oDisp.getParameters(),
81 (WuiMain.ksParamReportPeriods,
82 WuiMain.ksParamReportPeriodInHours,
83 WuiMain.ksParamEffectiveDate,));
84 # Additional URL parameters for test results:
85 self._dExtraTestResultsParams = TestResultFilter().strainParameters(oDisp.getParameters(),
86 (WuiMain.ksParamEffectiveDate,));
87 self._dExtraTestResultsParams[WuiMain.ksParamEffectivePeriod] = self.getPeriodForTestResults();
88
89
90 def generateNavigator(self, sWhere):
91 """
92 Generates the navigator (manipulate _dParams).
93 Returns HTML.
94 """
95 assert sWhere in ('top', 'bottom',);
96
97 return '';
98
99 def generateReportBody(self):
100 """
101 This is overridden by the child class to generate the report.
102 Returns HTML.
103 """
104 return '<h3>Must override generateReportBody!</h3>';
105
106 def show(self):
107 """
108 Generate the report.
109 Returns (sTitle, HTML).
110 """
111
112 sTitle = self._sTitle if self._sTitle is not None else type(self).__name__;
113 sReport = self.generateReportBody();
114 if not self._fSubReport:
115 sReport = self.generateNavigator('top') + sReport + self.generateNavigator('bottom');
116 sTitle = self._oModel.sSubject + ' - ' + sTitle; ## @todo add subject to title in a proper way!
117
118 sReport += '\n\n<!-- HEYYOU: sSubject=%s aidSubjects=%s -->\n\n' % (self._oModel.sSubject, self._oModel.aidSubjects);
119 return (sTitle, sReport);
120
121 #
122 # Utility methods
123 #
124
125 def getPeriodForTestResults(self):
126 """
127 Takes the report period length and count and translates it into a
128 reasonable test result period (value).
129 """
130 from testmanager.webui.wuimain import WuiMain;
131 cHours = self._oModel.cPeriods * self._oModel.cHoursPerPeriod;
132 if cHours > 7*24:
133 cHours = cHours // 2;
134 for sPeriodValue, _, cPeriodHours in WuiMain.kaoResultPeriods:
135 sPeriod = sPeriodValue;
136 if cPeriodHours >= cHours:
137 return sPeriod;
138 return sPeriod;
139
140 @staticmethod
141 def fmtPct(cHits, cTotal):
142 """
143 Formats a percent number.
144 Returns a string.
145 """
146 uPct = cHits * 100 // cTotal;
147 if uPct >= 10 and (uPct > 103 or uPct <= 95):
148 return '%s%%' % (uPct,);
149 return '%.1f%%' % (cHits * 100.0 / cTotal,);
150
151 @staticmethod
152 def fmtPctWithHits(cHits, cTotal):
153 """
154 Formats a percent number with total in parentheses.
155 Returns a string.
156 """
157 return '%s (%s)' % (WuiReportBase.fmtPct(cHits, cTotal), cHits);
158
159 @staticmethod
160 def fmtPctWithHitsAndTotal(cHits, cTotal):
161 """
162 Formats a percent number with total in parentheses.
163 Returns a string.
164 """
165 return '%s (%s/%s)' % (WuiReportBase.fmtPct(cHits, cTotal), cHits, cTotal);
166
167
168
169class WuiReportSuccessRate(WuiReportBase):
170 """
171 Generates a report displaying the success rate over time.
172 """
173
174 def generateReportBody(self):
175 self._sTitle = 'Success rate';
176 fTailoredForGoogleCharts = True;
177
178 #
179 # Get the data and check if we have anything in the 'skipped' category.
180 #
181 adPeriods = self._oModel.getSuccessRates();
182
183 cTotalSkipped = 0;
184 for dStatuses in adPeriods:
185 cTotalSkipped += dStatuses[ReportModelBase.ksTestStatus_Skipped];
186
187 #
188 # Output some general stats before the graphs.
189 #
190 cTotalNow = adPeriods[0][ReportModelBase.ksTestStatus_Success];
191 cTotalNow += adPeriods[0][ReportModelBase.ksTestStatus_Skipped];
192 cSuccessNow = cTotalNow;
193 cTotalNow += adPeriods[0][ReportModelBase.ksTestStatus_Failure];
194
195 sReport = '<p>Current success rate: ';
196 if cTotalNow > 0:
197 cSkippedNow = adPeriods[0][ReportModelBase.ksTestStatus_Skipped];
198 if cSkippedNow > 0:
199 sReport += '%s (thereof %s skipped)</p>\n' \
200 % (self.fmtPct(cSuccessNow, cTotalNow), self.fmtPct(cSkippedNow, cTotalNow),);
201 else:
202 sReport += '%s (none skipped)</p>\n' % (self.fmtPct(cSuccessNow, cTotalNow),);
203 else:
204 sReport += 'N/A</p>\n'
205
206 #
207 # Create the data table.
208 #
209 if fTailoredForGoogleCharts:
210 if cTotalSkipped > 0:
211 oTable = WuiHlpGraphDataTable(None, [ 'Succeeded', 'Skipped', 'Failed' ]);
212 else:
213 oTable = WuiHlpGraphDataTable(None, [ 'Succeeded', 'Failed' ]);
214 else:
215 if cTotalSkipped > 0:
216 oTable = WuiHlpGraphDataTable('When', [ 'Succeeded', 'Skipped', 'Failed' ]);
217 else:
218 oTable = WuiHlpGraphDataTable('When', [ 'Succeeded', 'Failed' ]);
219
220 for i, dStatuses in enumerate(adPeriods):
221 cSuccesses = dStatuses[ReportModelBase.ksTestStatus_Success];
222 cFailures = dStatuses[ReportModelBase.ksTestStatus_Failure];
223 cSkipped = dStatuses[ReportModelBase.ksTestStatus_Skipped];
224
225 cSuccess = cSuccesses + cSkipped;
226 cTotal = cSuccess + cFailures;
227 sPeriod = self._oModel.getPeriodDesc(i);
228 if fTailoredForGoogleCharts:
229 if cTotalSkipped > 0:
230 oTable.addRow(sPeriod,
231 [ cSuccesses * 100 // cTotal if cTotal else 0,
232 cSkipped * 100 // cTotal if cTotal else 0,
233 cFailures * 100 // cTotal if cTotal else 0, ],
234 [ self.fmtPct(cSuccesses, cTotal) if cSuccesses else None,
235 self.fmtPct(cSkipped, cTotal) if cSkipped else None,
236 self.fmtPct(cFailures, cTotal) if cFailures else None, ]);
237 else:
238 oTable.addRow(sPeriod,
239 [ cSuccesses * 100 // cTotal if cTotal else 0,
240 cFailures * 100 // cTotal if cTotal else 0, ],
241 [ self.fmtPct(cSuccesses, cTotal) if cSuccesses else None,
242 self.fmtPct(cFailures, cTotal) if cFailures else None, ]);
243 elif cTotal > 0:
244 if cTotalSkipped > 0:
245 oTable.addRow(sPeriod,
246 [ cSuccesses * 100 // cTotal,
247 cSkipped * 100 // cTotal,
248 cFailures * 100 // cTotal, ],
249 [ self.fmtPctWithHits(cSuccesses, cTotal),
250 self.fmtPctWithHits(cSkipped, cTotal),
251 self.fmtPctWithHits(cFailures, cTotal), ]);
252 else:
253 oTable.addRow(sPeriod,
254 [ cSuccesses * 100 // cTotal,
255 cFailures * 100 // cTotal, ],
256 [ self.fmtPctWithHits(cSuccesses, cTotal),
257 self.fmtPctWithHits(cFailures, cTotal), ]);
258 elif cTotalSkipped > 0:
259 oTable.addRow(sPeriod, [ 0, 0, 0 ], [ '0%', '0%', '0%' ]);
260 else:
261 oTable.addRow(sPeriod, [ 0, 0 ], [ '0%', '0%' ]);
262
263 #
264 # Render the graph.
265 #
266 oGraph = WuiHlpBarGraph('success-rate', oTable, self._oDisp);
267 oGraph.setRangeMax(100);
268 sReport += oGraph.renderGraph();
269
270 #
271 # Graph with absolute counts.
272 #
273 if fTailoredForGoogleCharts:
274 if cTotalSkipped > 0:
275 oTable = WuiHlpGraphDataTable(None, [ 'Succeeded', 'Skipped', 'Failed' ]);
276 else:
277 oTable = WuiHlpGraphDataTable(None, [ 'Succeeded', 'Failed' ]);
278 for i, dStatuses in enumerate(adPeriods):
279 cSuccesses = dStatuses[ReportModelBase.ksTestStatus_Success];
280 cFailures = dStatuses[ReportModelBase.ksTestStatus_Failure];
281 cSkipped = dStatuses[ReportModelBase.ksTestStatus_Skipped];
282
283 if cTotalSkipped > 0:
284 oTable.addRow(None, #self._oModel.getPeriodDesc(i),
285 [ cSuccesses, cSkipped, cFailures, ],
286 [ str(cSuccesses) if cSuccesses > 0 else None,
287 str(cSkipped) if cSkipped > 0 else None,
288 str(cFailures) if cFailures > 0 else None, ]);
289 else:
290 oTable.addRow(None, #self._oModel.getPeriodDesc(i),
291 [ cSuccesses, cFailures, ],
292 [ str(cSuccesses) if cSuccesses > 0 else None,
293 str(cFailures) if cFailures > 0 else None, ]);
294 oGraph = WuiHlpBarGraph('success-numbers', oTable, self._oDisp);
295 oGraph.invertYDirection();
296 sReport += oGraph.renderGraph();
297
298 return sReport;
299
300
301class WuiReportFailuresBase(WuiReportBase):
302 """
303 Common parent of WuiReportFailureReasons and WuiReportTestCaseFailures.
304 """
305
306 def _splitSeriesIntoMultipleGraphs(self, aidSorted, cMaxSeriesPerGraph = 8):
307 """
308 Splits the ID array into one or more arrays, making sure we don't
309 have too many series per graph.
310 Returns array of ID arrays.
311 """
312 if len(aidSorted) <= cMaxSeriesPerGraph + 2:
313 return [aidSorted,];
314 cGraphs = len(aidSorted) // cMaxSeriesPerGraph + (len(aidSorted) % cMaxSeriesPerGraph != 0);
315 cPerGraph = len(aidSorted) // cGraphs + (len(aidSorted) % cGraphs != 0);
316
317 aaoRet = [];
318 cLeft = len(aidSorted);
319 iSrc = 0;
320 while cLeft > 0:
321 cThis = cPerGraph;
322 if cLeft <= cPerGraph + 2:
323 cThis = cLeft;
324 elif cLeft <= cPerGraph * 2 + 4:
325 cThis = cLeft // 2;
326 aaoRet.append(aidSorted[iSrc : iSrc + cThis]);
327 iSrc += cThis;
328 cLeft -= cThis;
329 return aaoRet;
330
331 def _formatEdgeOccurenceSubject(self, oTransient):
332 """
333 Worker for _formatEdgeOccurence that child classes overrides to format
334 their type of subject data in the best possible way.
335 """
336 _ = oTransient;
337 assert False;
338 return '';
339
340 def _formatEdgeOccurence(self, oTransient):
341 """
342 Helper for formatting the transients.
343 oTransient is of type ReportFailureReasonTransient or ReportTestCaseFailureTransient.
344 """
345 sHtml = u'<li>';
346 if oTransient.fEnter: sHtml += 'Since ';
347 else: sHtml += 'Until ';
348 sHtml += WuiSvnLinkWithTooltip(oTransient.iRevision, oTransient.sRepository, fBracketed = 'False').toHtml();
349 sHtml += u', %s: ' % (WuiTestSetLink(oTransient.idTestSet, self.formatTsShort(oTransient.tsDone),
350 fBracketed = False).toHtml(), )
351 sHtml += self._formatEdgeOccurenceSubject(oTransient);
352 sHtml += u'</li>\n';
353 return sHtml;
354
355 def _generateTransitionList(self, oSet):
356 """
357 Generates the enter and leave lists.
358 """
359 # Skip this if we're looking at builds.
360 if self._oModel.sSubject in [self._oModel.ksSubBuild,] and len(self._oModel.aidSubjects) in [1, 2]:
361 return u'';
362
363 sHtml = u'<h4>Movements:</h4>\n' \
364 u'<ul>\n';
365 if not oSet.aoEnterInfo and not oSet.aoLeaveInfo:
366 sHtml += u'<li>No changes</li>\n';
367 else:
368 for oTransient in oSet.aoEnterInfo:
369 sHtml += self._formatEdgeOccurence(oTransient);
370 for oTransient in oSet.aoLeaveInfo:
371 sHtml += self._formatEdgeOccurence(oTransient);
372 sHtml += u'</ul>\n';
373
374 return sHtml;
375
376
377 def _formatSeriesNameColumnHeadersForTable(self):
378 """ Formats the series name column for the HTML table. """
379 return '<th>Subject Name</th>';
380
381 def _formatSeriesNameForTable(self, oSet, idKey):
382 """ Formats the series name for the HTML table. """
383 _ = oSet;
384 return '<td>%d</td>' % (idKey,);
385
386 def _formatRowValueForTable(self, oRow, oPeriod, cColsPerSeries):
387 """ Formats a row value for the HTML table. """
388 _ = oPeriod;
389 if oRow is None:
390 return u'<td colspan="%d"> </td>' % (cColsPerSeries,);
391 if cColsPerSeries == 2:
392 return u'<td align="right">%u%%</td><td align="center">%u / %u</td>' \
393 % (oRow.cHits * 100 // oRow.cTotal, oRow.cHits, oRow.cTotal);
394 return u'<td align="center">%u</td>' % (oRow.cHits,);
395
396 def _formatSeriesTotalForTable(self, oSet, idKey, cColsPerSeries):
397 """ Formats the totals cell for a data series in the HTML table. """
398 dcTotalPerId = getattr(oSet, 'dcTotalPerId', None);
399 if cColsPerSeries == 2:
400 return u'<td align="right">%u%%</td><td align="center">%u/%u</td>' \
401 % (oSet.dcHitsPerId[idKey] * 100 // dcTotalPerId[idKey], oSet.dcHitsPerId[idKey], dcTotalPerId[idKey]);
402 return u'<td align="center">%u</td>' % (oSet.dcHitsPerId[idKey],);
403
404 def _generateTableForSet(self, oSet, aidSorted = None, iSortColumn = 0,
405 fWithTotals = True, cColsPerSeries = None):
406 """
407 Turns the set into a table.
408
409 Returns raw html.
410 """
411 sHtml = u'<table class="tmtbl-report-set" width="100%%">\n';
412 if cColsPerSeries is None:
413 cColsPerSeries = 2 if hasattr(oSet, 'dcTotalPerId') else 1;
414
415 # Header row.
416 sHtml += u' <tr><thead><th>#</th>';
417 sHtml += self._formatSeriesNameColumnHeadersForTable();
418 for iPeriod, oPeriod in enumerate(reversed(oSet.aoPeriods)):
419 sHtml += u'<th colspan="%d"><a href="javascript:ahrefActionSortByColumns(\'%s\',[%s]);">%s</a>%s</th>' \
420 % ( cColsPerSeries, self._oDisp.ksParamSortColumns, iPeriod, webutils.escapeElem(oPeriod.sDesc),
421 '&#x25bc;' if iPeriod == iSortColumn else '');
422 if fWithTotals:
423 sHtml += u'<th colspan="%d"><a href="javascript:ahrefActionSortByColumns(\'%s\',[%s]);">Total</a>%s</th>' \
424 % ( cColsPerSeries, self._oDisp.ksParamSortColumns, len(oSet.aoPeriods),
425 '&#x25bc;' if iSortColumn == len(oSet.aoPeriods) else '');
426 sHtml += u'</thead></td>\n';
427
428 # Each data series.
429 if aidSorted is None:
430 aidSorted = oSet.dSubjects.keys();
431 sHtml += u' <tbody>\n';
432 for iRow, idKey in enumerate(aidSorted):
433 sHtml += u' <tr class="%s">' % ('tmodd' if iRow & 1 else 'tmeven',);
434 sHtml += u'<td align="left">#%u</td>' % (iRow + 1,);
435 sHtml += self._formatSeriesNameForTable(oSet, idKey);
436 for oPeriod in reversed(oSet.aoPeriods):
437 oRow = oPeriod.dRowsById.get(idKey, None);
438 sHtml += self._formatRowValueForTable(oRow, oPeriod, cColsPerSeries);
439 if fWithTotals:
440 sHtml += self._formatSeriesTotalForTable(oSet, idKey, cColsPerSeries);
441 sHtml += u' </tr>\n';
442 sHtml += u' </tbody>\n';
443 sHtml += u'</table>\n';
444 return sHtml;
445
446
447class WuiReportFailuresWithTotalBase(WuiReportFailuresBase):
448 """
449 For ReportPeriodSetWithTotalBase.
450 """
451
452 def _formatSeriedNameForGraph(self, oSubject):
453 """
454 Format the subject name for the graph.
455 """
456 return str(oSubject);
457
458 def _getSortedIds(self, oSet):
459 """
460 Get default sorted subject IDs and which column.
461 """
462
463 # Figure the sorting column.
464 if self._aiSortColumns is not None \
465 and self._aiSortColumns \
466 and abs(self._aiSortColumns[0]) <= len(oSet.aoPeriods):
467 iSortColumn = abs(self._aiSortColumns[0]);
468 fByTotal = iSortColumn >= len(oSet.aoPeriods); # pylint: disable=unused-variable
469 elif oSet.cMaxTotal < 10:
470 iSortColumn = len(oSet.aoPeriods);
471 else:
472 iSortColumn = 0;
473
474 if iSortColumn >= len(oSet.aoPeriods):
475 # Sort the total.
476 aidSortedRaw = sorted(oSet.dSubjects,
477 key = lambda idKey: oSet.dcHitsPerId[idKey] * 10000 // oSet.dcTotalPerId[idKey],
478 reverse = True);
479 else:
480 # Sort by NOW column.
481 dTmp = {};
482 for idKey in oSet.dSubjects:
483 oRow = oSet.aoPeriods[-1 - iSortColumn].dRowsById.get(idKey, None);
484 if oRow is None: dTmp[idKey] = 0;
485 else: dTmp[idKey] = oRow.cHits * 10000 // max(1, oRow.cTotal);
486 aidSortedRaw = sorted(dTmp, key = lambda idKey: dTmp[idKey], reverse = True);
487 return (aidSortedRaw, iSortColumn);
488
489 def _generateGraph(self, oSet, sIdBase, aidSortedRaw):
490 """
491 Generates graph.
492 """
493 sHtml = u'';
494 fGenerateGraph = len(aidSortedRaw) <= 6 and len(aidSortedRaw) > 0; ## Make this configurable.
495 if fGenerateGraph:
496 # Figure the graph width for all of them.
497 uPctMax = max(oSet.uMaxPct, oSet.cMaxHits * 100 // oSet.cMaxTotal);
498 uPctMax = max(uPctMax + 2, 10);
499
500 for _, aidSorted in enumerate(self._splitSeriesIntoMultipleGraphs(aidSortedRaw, 8)):
501 asNames = [];
502 for idKey in aidSorted:
503 oSubject = oSet.dSubjects[idKey];
504 asNames.append(self._formatSeriedNameForGraph(oSubject));
505
506 oTable = WuiHlpGraphDataTable('Period', asNames);
507
508 for _, oPeriod in enumerate(reversed(oSet.aoPeriods)):
509 aiValues = [];
510 asValues = [];
511
512 for idKey in aidSorted:
513 oRow = oPeriod.dRowsById.get(idKey, None);
514 if oRow is not None:
515 aiValues.append(oRow.cHits * 100 // oRow.cTotal);
516 asValues.append(self.fmtPctWithHitsAndTotal(oRow.cHits, oRow.cTotal));
517 else:
518 aiValues.append(0);
519 asValues.append('0');
520
521 oTable.addRow(oPeriod.sDesc, aiValues, asValues);
522
523 if True: # pylint: disable=using-constant-test
524 aiValues = [];
525 asValues = [];
526 for idKey in aidSorted:
527 uPct = oSet.dcHitsPerId[idKey] * 100 // oSet.dcTotalPerId[idKey];
528 aiValues.append(uPct);
529 asValues.append(self.fmtPctWithHitsAndTotal(oSet.dcHitsPerId[idKey], oSet.dcTotalPerId[idKey]));
530 oTable.addRow('Totals', aiValues, asValues);
531
532 oGraph = WuiHlpBarGraph(sIdBase, oTable, self._oDisp);
533 oGraph.setRangeMax(uPctMax);
534 sHtml += '<br>\n';
535 sHtml += oGraph.renderGraph();
536 return sHtml;
537
538
539
540class WuiReportFailureReasons(WuiReportFailuresBase):
541 """
542 Generates a report displaying the failure reasons over time.
543 """
544
545 def _formatEdgeOccurenceSubject(self, oTransient):
546 return u'%s / %s' % ( webutils.escapeElem(oTransient.oReason.oCategory.sShort),
547 webutils.escapeElem(oTransient.oReason.sShort),);
548
549 def _formatSeriesNameColumnHeadersForTable(self):
550 return '<th>Failure Reason</th>';
551
552 def _formatSeriesNameForTable(self, oSet, idKey):
553 oReason = oSet.dSubjects[idKey];
554 sHtml = u'<td>';
555 sHtml += u'%s / %s' % ( webutils.escapeElem(oReason.oCategory.sShort), webutils.escapeElem(oReason.sShort),);
556 sHtml += u'</td>';
557 return sHtml;
558
559
560 def generateReportBody(self):
561 self._sTitle = 'Failure reasons';
562
563 #
564 # Get the data and sort the data series in descending order of badness.
565 #
566 oSet = self._oModel.getFailureReasons();
567 aidSortedRaw = sorted(oSet.dSubjects, key = lambda idReason: oSet.dcHitsPerId[idReason], reverse = True);
568
569 #
570 # Generate table and transition list. These are the most useful ones with the current graph machinery.
571 #
572 sHtml = self._generateTableForSet(oSet, aidSortedRaw, len(oSet.aoPeriods));
573 sHtml += self._generateTransitionList(oSet);
574
575 #
576 # Check if most of the stuff is without any assign reason, if so, skip
577 # that part of the graph so it doesn't offset the interesting bits.
578 #
579 fIncludeWithoutReason = True;
580 for oPeriod in reversed(oSet.aoPeriods):
581 if oPeriod.cWithoutReason > oSet.cMaxHits * 4:
582 fIncludeWithoutReason = False;
583 sHtml += '<p>Warning: Many failures without assigned reason!</p>\n';
584 break;
585
586 #
587 # Generate the graph.
588 #
589 fGenerateGraph = len(aidSortedRaw) <= 9 and len(aidSortedRaw) > 0; ## Make this configurable.
590 if fGenerateGraph:
591 aidSorted = aidSortedRaw;
592
593 asNames = [];
594 for idReason in aidSorted:
595 oReason = oSet.dSubjects[idReason];
596 asNames.append('%s / %s' % (oReason.oCategory.sShort, oReason.sShort,) )
597 if fIncludeWithoutReason:
598 asNames.append('No reason');
599
600 oTable = WuiHlpGraphDataTable('Period', asNames);
601
602 cMax = oSet.cMaxHits;
603 for _, oPeriod in enumerate(reversed(oSet.aoPeriods)):
604 aiValues = [];
605
606 for idReason in aidSorted:
607 oRow = oPeriod.dRowsById.get(idReason, None);
608 iValue = oRow.cHits if oRow is not None else 0;
609 aiValues.append(iValue);
610
611 if fIncludeWithoutReason:
612 aiValues.append(oPeriod.cWithoutReason);
613 if oPeriod.cWithoutReason > cMax:
614 cMax = oPeriod.cWithoutReason;
615
616 oTable.addRow(oPeriod.sDesc, aiValues);
617
618 oGraph = WuiHlpBarGraph('failure-reason', oTable, self._oDisp);
619 oGraph.setRangeMax(max(cMax + 1, 3));
620 sHtml += oGraph.renderGraph();
621 return sHtml;
622
623
624class WuiReportTestCaseFailures(WuiReportFailuresWithTotalBase):
625 """
626 Generates a report displaying the failure reasons over time.
627 """
628
629 def _formatEdgeOccurenceSubject(self, oTransient):
630 sHtml = u'%s ' % ( webutils.escapeElem(oTransient.oSubject.sName),);
631 sHtml += WuiTestCaseDetailsLink(oTransient.oSubject.idTestCase, fBracketed = False).toHtml();
632 return sHtml;
633
634 def _formatSeriesNameColumnHeadersForTable(self):
635 return '<th>Test Case</th>';
636
637 def _formatSeriesNameForTable(self, oSet, idKey):
638 oTestCase = oSet.dSubjects[idKey];
639 return u'<td>%s %s %s</td>' % \
640 ( WuiTestResultsForTestCaseLink(idKey, oTestCase.sName, self._dExtraTestResultsParams).toHtml(),
641 WuiTestCaseDetailsLink(oTestCase.idTestCase).toHtml(),
642 WuiReportSummaryLink(ReportModelBase.ksSubTestCase, oTestCase.idTestCase,
643 dExtraParams = self._dExtraParams).toHtml(),);
644
645 def _formatSeriedNameForGraph(self, oSubject):
646 return oSubject.sName;
647
648 def generateReportBody(self):
649 self._sTitle = 'Test Case Failures';
650 oSet = self._oModel.getTestCaseFailures();
651 (aidSortedRaw, iSortColumn) = self._getSortedIds(oSet);
652
653 sHtml = self._generateTableForSet(oSet, aidSortedRaw, iSortColumn);
654 sHtml += self._generateTransitionList(oSet);
655 sHtml += self._generateGraph(oSet, 'testcase-graph', aidSortedRaw);
656 return sHtml;
657
658
659class WuiReportTestCaseArgsFailures(WuiReportFailuresWithTotalBase):
660 """
661 Generates a report displaying the failure reasons over time.
662 """
663
664 def __init__(self, oModel, dParams, fSubReport = False, aiSortColumns = None, fnDPrint = None, oDisp = None):
665 WuiReportFailuresWithTotalBase.__init__(self, oModel, dParams, fSubReport = fSubReport,
666 aiSortColumns = aiSortColumns, fnDPrint = fnDPrint, oDisp = oDisp);
667 self.oTestCaseCrit = TestResultFilter().aCriteria[TestResultFilter.kiTestCases] # type: FilterCriterion
668
669 @staticmethod
670 def _formatName(oTestCaseArgs):
671 """ Internal helper for formatting the testcase name. """
672 if oTestCaseArgs.sSubName:
673 sName = u'%s / %s' % ( oTestCaseArgs.oTestCase.sName, oTestCaseArgs.sSubName, );
674 else:
675 sName = u'%s / #%u' % ( oTestCaseArgs.oTestCase.sName, oTestCaseArgs.idTestCaseArgs, );
676 return sName;
677
678 def _formatEdgeOccurenceSubject(self, oTransient):
679 sHtml = u'%s ' % ( webutils.escapeElem(self._formatName(oTransient.oSubject)),);
680 sHtml += WuiTestCaseDetailsLink(oTransient.oSubject.idTestCase, fBracketed = False).toHtml();
681 return sHtml;
682
683 def _formatSeriesNameColumnHeadersForTable(self):
684 return '<th>Test Case / Variation</th>';
685
686 def _formatSeriesNameForTable(self, oSet, idKey):
687 oTestCaseArgs = oSet.dSubjects[idKey];
688 sHtml = u'<td>';
689 dParams = dict(self._dExtraTestResultsParams);
690 dParams[self.oTestCaseCrit.sVarNm] = oTestCaseArgs.idTestCase;
691 dParams[self.oTestCaseCrit.oSub.sVarNm] = idKey;
692 sHtml += WuiTestResultsForTestCaseLink(oTestCaseArgs.idTestCase, self._formatName(oTestCaseArgs), dParams).toHtml();
693 sHtml += u' ';
694 sHtml += WuiTestCaseDetailsLink(oTestCaseArgs.idTestCase).toHtml();
695 #sHtml += u' ';
696 #sHtml += WuiReportSummaryLink(ReportModelBase.ksSubTestCaseArgs, oTestCaseArgs.idTestCaseArgs,
697 # sName = self._formatName(oTestCaseArgs), dExtraParams = self._dExtraParams).toHtml();
698 sHtml += u'</td>';
699 return sHtml;
700
701 def _formatSeriedNameForGraph(self, oSubject):
702 return self._formatName(oSubject);
703
704 def generateReportBody(self):
705 self._sTitle = 'Test Case Variation Failures';
706 oSet = self._oModel.getTestCaseVariationFailures();
707 (aidSortedRaw, iSortColumn) = self._getSortedIds(oSet);
708
709 sHtml = self._generateTableForSet(oSet, aidSortedRaw, iSortColumn);
710 sHtml += self._generateTransitionList(oSet);
711 sHtml += self._generateGraph(oSet, 'testcasearg-graph', aidSortedRaw);
712 return sHtml;
713
714
715
716class WuiReportTestBoxFailures(WuiReportFailuresWithTotalBase):
717 """
718 Generates a report displaying the failure reasons over time.
719 """
720
721 def _formatEdgeOccurenceSubject(self, oTransient):
722 sHtml = u'%s ' % ( webutils.escapeElem(oTransient.oSubject.sName),);
723 sHtml += WuiTestBoxDetailsLinkShort(oTransient.oSubject).toHtml();
724 return sHtml;
725
726 def _formatSeriesNameColumnHeadersForTable(self):
727 return '<th colspan="5">Test Box</th>';
728
729 def _formatSeriesNameForTable(self, oSet, idKey):
730 oTestBox = oSet.dSubjects[idKey];
731 sHtml = u'<td>';
732 sHtml += WuiTestResultsForTestBoxLink(idKey, oTestBox.sName, self._dExtraTestResultsParams).toHtml()
733 sHtml += u' ';
734 sHtml += WuiTestBoxDetailsLinkShort(oTestBox).toHtml();
735 sHtml += u' ';
736 sHtml += WuiReportSummaryLink(ReportModelBase.ksSubTestBox, oTestBox.idTestBox,
737 dExtraParams = self._dExtraParams).toHtml();
738 sHtml += u'</td>';
739 sOsAndVer = '%s %s' % (oTestBox.sOs, oTestBox.sOsVersion.strip(),);
740 if len(sOsAndVer) < 22:
741 sHtml += u'<td>%s</td>' % (webutils.escapeElem(sOsAndVer),);
742 else: # wonder if td.title works..
743 sHtml += u'<td title="%s" width="1%%" style="white-space:nowrap;">%s...</td>' \
744 % (webutils.escapeAttr(sOsAndVer), webutils.escapeElem(sOsAndVer[:20]));
745 sHtml += u'<td>%s</td>' % (webutils.escapeElem(oTestBox.getArchBitString()),);
746 sHtml += u'<td>%s</td>' % (webutils.escapeElem(oTestBox.getPrettyCpuVendor()),);
747 sHtml += u'<td>%s' % (oTestBox.getPrettyCpuVersion(),);
748 if oTestBox.fCpuNestedPaging: sHtml += u', np';
749 elif oTestBox.fCpuHwVirt: sHtml += u', hw';
750 else: sHtml += u', raw';
751 if oTestBox.fCpu64BitGuest: sHtml += u', 64';
752 sHtml += u'</td>';
753 return sHtml;
754
755 def _formatSeriedNameForGraph(self, oSubject):
756 return oSubject.sName;
757
758 def generateReportBody(self):
759 self._sTitle = 'Test Box Failures';
760 oSet = self._oModel.getTestBoxFailures();
761 (aidSortedRaw, iSortColumn) = self._getSortedIds(oSet);
762
763 sHtml = self._generateTableForSet(oSet, aidSortedRaw, iSortColumn);
764 sHtml += self._generateTransitionList(oSet);
765 sHtml += self._generateGraph(oSet, 'testbox-graph', aidSortedRaw);
766 return sHtml;
767
768
769class WuiReportSummary(WuiReportBase):
770 """
771 Summary report.
772 """
773
774 def generateReportBody(self):
775 self._sTitle = 'Summary';
776 sHtml = '<p>This will display several reports and listings useful to get an overview of %s (id=%s).</p>' \
777 % (self._oModel.sSubject, self._oModel.aidSubjects,);
778
779 aoReports = [];
780
781 aoReports.append(WuiReportSuccessRate( self._oModel, self._dParams, fSubReport = True,
782 aiSortColumns = self._aiSortColumns,
783 fnDPrint = self._fnDPrint, oDisp = self._oDisp));
784 aoReports.append(WuiReportTestCaseFailures(self._oModel, self._dParams, fSubReport = True,
785 aiSortColumns = self._aiSortColumns,
786 fnDPrint = self._fnDPrint, oDisp = self._oDisp));
787 if self._oModel.sSubject == ReportModelBase.ksSubTestCase:
788 aoReports.append(WuiReportTestCaseArgsFailures(self._oModel, self._dParams, fSubReport = True,
789 aiSortColumns = self._aiSortColumns,
790 fnDPrint = self._fnDPrint, oDisp = self._oDisp));
791 aoReports.append(WuiReportTestBoxFailures( self._oModel, self._dParams, fSubReport = True,
792 aiSortColumns = self._aiSortColumns,
793 fnDPrint = self._fnDPrint, oDisp = self._oDisp));
794 aoReports.append(WuiReportFailureReasons( self._oModel, self._dParams, fSubReport = True,
795 aiSortColumns = self._aiSortColumns,
796 fnDPrint = self._fnDPrint, oDisp = self._oDisp));
797
798 for oReport in aoReports:
799 (sTitle, sContent) = oReport.show();
800 sHtml += '<br>'; # drop this layout hack
801 sHtml += '<div>';
802 sHtml += '<h3>%s</h3>\n' % (webutils.escapeElem(sTitle),);
803 sHtml += sContent;
804 sHtml += '</div>';
805
806 return sHtml;
807
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette