1 | # -*- coding: utf-8 -*-
|
---|
2 | # $Id: wuireport.py 61292 2016-05-30 13:03:50Z vboxsync $
|
---|
3 |
|
---|
4 | """
|
---|
5 | Test Manager WUI - Reports.
|
---|
6 | """
|
---|
7 |
|
---|
8 | __copyright__ = \
|
---|
9 | """
|
---|
10 | Copyright (C) 2012-2015 Oracle Corporation
|
---|
11 |
|
---|
12 | This file is part of VirtualBox Open Source Edition (OSE), as
|
---|
13 | available from http://www.virtualbox.org. This file is free software;
|
---|
14 | you can redistribute it and/or modify it under the terms of the GNU
|
---|
15 | General Public License (GPL) as published by the Free Software
|
---|
16 | Foundation, in version 2 as it comes in the "COPYING" file of the
|
---|
17 | VirtualBox OSE distribution. VirtualBox OSE is distributed in the
|
---|
18 | hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
|
---|
19 |
|
---|
20 | The contents of this file may alternatively be used under the terms
|
---|
21 | of the Common Development and Distribution License Version 1.0
|
---|
22 | (CDDL) only, as it comes in the "COPYING.CDDL" file of the
|
---|
23 | VirtualBox OSE distribution, in which case the provisions of the
|
---|
24 | CDDL are applicable instead of those of the GPL.
|
---|
25 |
|
---|
26 | You may elect to license modified versions of this file under the
|
---|
27 | terms and conditions of either the GPL or the CDDL or both.
|
---|
28 | """
|
---|
29 | __version__ = "$Revision: 61292 $"
|
---|
30 |
|
---|
31 |
|
---|
32 | # Validation Kit imports.
|
---|
33 | from common import webutils;
|
---|
34 | from testmanager.webui.wuicontentbase import WuiContentBase, WuiTmLink, WuiSvnLinkWithTooltip;
|
---|
35 | from testmanager.webui.wuihlpgraph import WuiHlpGraphDataTable, WuiHlpBarGraph;
|
---|
36 | from testmanager.webui.wuitestresult import WuiTestSetLink;
|
---|
37 | from testmanager.webui.wuiadmintestcase import WuiTestCaseDetailsLink;
|
---|
38 | from testmanager.webui.wuiadmintestbox import WuiTestBoxDetailsLink;
|
---|
39 | from testmanager.core.report import ReportModelBase;
|
---|
40 |
|
---|
41 |
|
---|
42 | class WuiReportSummaryLink(WuiTmLink):
|
---|
43 | """ Generic report summary link. """
|
---|
44 |
|
---|
45 | def __init__(self, sSubject, aIdSubjects, sName = WuiContentBase.ksShortReportLink,
|
---|
46 | tsNow = None, cPeriods = None, cHoursPerPeriod = None, fBracketed = False, dExtraParams = None):
|
---|
47 | from testmanager.webui.wuimain import WuiMain;
|
---|
48 | dParams = {
|
---|
49 | WuiMain.ksParamAction: WuiMain.ksActionReportSummary,
|
---|
50 | WuiMain.ksParamReportSubject: sSubject,
|
---|
51 | WuiMain.ksParamReportSubjectIds: aIdSubjects,
|
---|
52 | };
|
---|
53 | if dExtraParams is not None:
|
---|
54 | dParams.update(dExtraParams);
|
---|
55 | if tsNow is not None:
|
---|
56 | dParams[WuiMain.ksParamEffectiveDate] = tsNow;
|
---|
57 | if cPeriods is not None:
|
---|
58 | dParams[WuiMain.ksParamReportPeriods] = cPeriods;
|
---|
59 | if cPeriods is not None:
|
---|
60 | dParams[WuiMain.ksParamReportPeriodInHours] = cHoursPerPeriod;
|
---|
61 | WuiTmLink.__init__(self, sName, WuiMain.ksScriptName, dParams, fBracketed = fBracketed);
|
---|
62 |
|
---|
63 |
|
---|
64 | class WuiReportBase(WuiContentBase):
|
---|
65 | """
|
---|
66 | Base class for the reports.
|
---|
67 | """
|
---|
68 |
|
---|
69 | def __init__(self, oModel, dParams, fSubReport = False, fnDPrint = None, oDisp = None):
|
---|
70 | WuiContentBase.__init__(self, fnDPrint = fnDPrint, oDisp = oDisp);
|
---|
71 | self._oModel = oModel;
|
---|
72 | self._dParams = dParams;
|
---|
73 | self._fSubReport = fSubReport;
|
---|
74 | self._sTitle = None;
|
---|
75 |
|
---|
76 | # Additional URL parameters for reports
|
---|
77 | self._dExtraParams = {};
|
---|
78 | dCurParams = None if oDisp is None else oDisp.getParameters();
|
---|
79 | if dCurParams is not None:
|
---|
80 | from testmanager.webui.wuimain import WuiMain;
|
---|
81 | for sExtraParam in [ WuiMain.ksParamReportPeriods, WuiMain.ksParamReportPeriodInHours,
|
---|
82 | WuiMain.ksParamEffectiveDate, ]:
|
---|
83 | if sExtraParam in dCurParams:
|
---|
84 | self._dExtraParams[sExtraParam] = dCurParams[sExtraParam];
|
---|
85 |
|
---|
86 |
|
---|
87 | def generateNavigator(self, sWhere):
|
---|
88 | """
|
---|
89 | Generates the navigator (manipulate _dParams).
|
---|
90 | Returns HTML.
|
---|
91 | """
|
---|
92 | assert sWhere == 'top' or sWhere == 'bottom';
|
---|
93 |
|
---|
94 | return '';
|
---|
95 |
|
---|
96 | def generateReportBody(self):
|
---|
97 | """
|
---|
98 | This is overridden by the child class to generate the report.
|
---|
99 | Returns HTML.
|
---|
100 | """
|
---|
101 | return '<h3>Must override generateReportBody!</h3>';
|
---|
102 |
|
---|
103 | def show(self):
|
---|
104 | """
|
---|
105 | Generate the report.
|
---|
106 | Returns (sTitle, HTML).
|
---|
107 | """
|
---|
108 |
|
---|
109 | sTitle = self._sTitle if self._sTitle is not None else type(self).__name__;
|
---|
110 | sReport = self.generateReportBody();
|
---|
111 | if not self._fSubReport:
|
---|
112 | sReport = self.generateNavigator('top') + sReport + self.generateNavigator('bottom');
|
---|
113 | sTitle = self._oModel.sSubject + ' - ' + sTitle; ## @todo add subject to title in a proper way!
|
---|
114 |
|
---|
115 | sReport += '\n\n<!-- HEYYOU: sSubject=%s aidSubjects=%s -->\n\n' % (self._oModel.sSubject, self._oModel.aidSubjects);
|
---|
116 | return (sTitle, sReport);
|
---|
117 |
|
---|
118 |
|
---|
119 | class WuiReportSuccessRate(WuiReportBase):
|
---|
120 | """
|
---|
121 | Generates a report displaying the success rate over time.
|
---|
122 | """
|
---|
123 |
|
---|
124 | def generateReportBody(self):
|
---|
125 | self._sTitle = 'Success rate';
|
---|
126 |
|
---|
127 | adPeriods = self._oModel.getSuccessRates();
|
---|
128 |
|
---|
129 | sReport = '';
|
---|
130 |
|
---|
131 | oTable = WuiHlpGraphDataTable('Period', [ 'Succeeded', 'Skipped', 'Failed' ]);
|
---|
132 |
|
---|
133 | #for i in range(len(adPeriods) - 1, -1, -1):
|
---|
134 | for i, dStatuses in enumerate(adPeriods):
|
---|
135 | cSuccess = dStatuses[ReportModelBase.ksTestStatus_Success] + dStatuses[ReportModelBase.ksTestStatus_Skipped];
|
---|
136 | cTotal = cSuccess + dStatuses[ReportModelBase.ksTestStatus_Failure];
|
---|
137 | sPeriod = self._oModel.getPeriodDesc(i);
|
---|
138 | if cTotal > 0:
|
---|
139 | iPctSuccess = dStatuses[ReportModelBase.ksTestStatus_Success] * 100 / cTotal;
|
---|
140 | iPctSkipped = dStatuses[ReportModelBase.ksTestStatus_Skipped] * 100 / cTotal;
|
---|
141 | iPctFailure = dStatuses[ReportModelBase.ksTestStatus_Failure] * 100 / cTotal;
|
---|
142 | oTable.addRow(sPeriod, [ iPctSuccess, iPctSkipped, iPctFailure ],
|
---|
143 | [ '%s%% (%d)' % (iPctSuccess, dStatuses[ReportModelBase.ksTestStatus_Success]),
|
---|
144 | '%s%% (%d)' % (iPctSkipped, dStatuses[ReportModelBase.ksTestStatus_Skipped]),
|
---|
145 | '%s%% (%d)' % (iPctFailure, dStatuses[ReportModelBase.ksTestStatus_Failure]), ]);
|
---|
146 | else:
|
---|
147 | oTable.addRow(sPeriod, [ 0, 0, 0 ], [ '0%', '0%', '0%' ]);
|
---|
148 |
|
---|
149 | cTotalNow = adPeriods[0][ReportModelBase.ksTestStatus_Success];
|
---|
150 | cTotalNow += adPeriods[0][ReportModelBase.ksTestStatus_Skipped];
|
---|
151 | cSuccessNow = cTotalNow;
|
---|
152 | cTotalNow += adPeriods[0][ReportModelBase.ksTestStatus_Failure];
|
---|
153 | sReport += '<p>Current success rate: ';
|
---|
154 | if cTotalNow > 0:
|
---|
155 | sReport += '%s%% (thereof %s%% skipped)</p>\n' \
|
---|
156 | % ( cSuccessNow * 100 / cTotalNow, adPeriods[0][ReportModelBase.ksTestStatus_Skipped] * 100 / cTotalNow);
|
---|
157 | else:
|
---|
158 | sReport += 'N/A</p>\n'
|
---|
159 |
|
---|
160 | oGraph = WuiHlpBarGraph('success-rate', oTable, self._oDisp);
|
---|
161 | oGraph.setRangeMax(100);
|
---|
162 | sReport += oGraph.renderGraph();
|
---|
163 |
|
---|
164 | return sReport;
|
---|
165 |
|
---|
166 |
|
---|
167 | class WuiReportFailuresBase(WuiReportBase):
|
---|
168 | """
|
---|
169 | Common parent of WuiReportFailureReasons and WuiReportTestCaseFailures.
|
---|
170 | """
|
---|
171 |
|
---|
172 | def _splitSeriesIntoMultipleGraphs(self, aidSorted, cMaxSeriesPerGraph = 8):
|
---|
173 | """
|
---|
174 | Splits the ID array into one or more arrays, making sure we don't
|
---|
175 | have too many series per graph.
|
---|
176 | Returns array of ID arrays.
|
---|
177 | """
|
---|
178 | if len(aidSorted) <= cMaxSeriesPerGraph + 2:
|
---|
179 | return [aidSorted,];
|
---|
180 | cGraphs = len(aidSorted) / cMaxSeriesPerGraph + (len(aidSorted) % cMaxSeriesPerGraph != 0);
|
---|
181 | cPerGraph = len(aidSorted) / cGraphs + (len(aidSorted) % cGraphs != 0);
|
---|
182 |
|
---|
183 | aaoRet = [];
|
---|
184 | cLeft = len(aidSorted);
|
---|
185 | iSrc = 0;
|
---|
186 | while cLeft > 0:
|
---|
187 | cThis = cPerGraph;
|
---|
188 | if cLeft <= cPerGraph + 2:
|
---|
189 | cThis = cLeft;
|
---|
190 | elif cLeft <= cPerGraph * 2 + 4:
|
---|
191 | cThis = cLeft / 2;
|
---|
192 | aaoRet.append(aidSorted[iSrc : iSrc + cThis]);
|
---|
193 | iSrc += cThis;
|
---|
194 | cLeft -= cThis;
|
---|
195 | return aaoRet;
|
---|
196 |
|
---|
197 | def _formatEdgeOccurenceSubject(self, oTransient):
|
---|
198 | """
|
---|
199 | Worker for _formatEdgeOccurence that child classes overrides to format
|
---|
200 | their type of subject data in the best possible way.
|
---|
201 | """
|
---|
202 | _ = oTransient;
|
---|
203 | assert False;
|
---|
204 | return '';
|
---|
205 |
|
---|
206 | def _formatEdgeOccurence(self, oTransient):
|
---|
207 | """
|
---|
208 | Helper for formatting the transients.
|
---|
209 | oTransient is of type ReportFailureReasonTransient or ReportTestCaseFailureTransient.
|
---|
210 | """
|
---|
211 | sHtml = u'<li>';
|
---|
212 | if oTransient.fEnter: sHtml += 'Since ';
|
---|
213 | else: sHtml += 'Until ';
|
---|
214 | sHtml += WuiSvnLinkWithTooltip(oTransient.iRevision, oTransient.sRepository, fBracketed = 'False').toHtml();
|
---|
215 | sHtml += u', %s: ' % (WuiTestSetLink(oTransient.idTestSet, self.formatTsShort(oTransient.tsDone),
|
---|
216 | fBracketed = False).toHtml(), )
|
---|
217 | sHtml += self._formatEdgeOccurenceSubject(oTransient);
|
---|
218 | sHtml += u'</li>\n';
|
---|
219 | return sHtml;
|
---|
220 |
|
---|
221 | def _generateTransitionList(self, oSet):
|
---|
222 | """
|
---|
223 | Generates the enter and leave lists.
|
---|
224 | """
|
---|
225 | # Skip this if we're looking at builds.
|
---|
226 | if self._oModel.sSubject in [self._oModel.ksSubBuild,] and len(self._oModel.aidSubjects) in [1, 2]:
|
---|
227 | return u'';
|
---|
228 |
|
---|
229 | sHtml = u'<h4>Movements:</h4>\n' \
|
---|
230 | u'<ul>\n';
|
---|
231 | if len(oSet.aoEnterInfo) == 0 and len(oSet.aoLeaveInfo) == 0:
|
---|
232 | sHtml += u'<li>No changes</li>\n';
|
---|
233 | else:
|
---|
234 | for oTransient in oSet.aoEnterInfo:
|
---|
235 | sHtml += self._formatEdgeOccurence(oTransient);
|
---|
236 | for oTransient in oSet.aoLeaveInfo:
|
---|
237 | sHtml += self._formatEdgeOccurence(oTransient);
|
---|
238 | sHtml += u'</ul>\n';
|
---|
239 |
|
---|
240 | return sHtml;
|
---|
241 |
|
---|
242 |
|
---|
243 | def _formatSeriesNameForTable(self, oSet, idKey):
|
---|
244 | """ Formats the series name for the HTML table. """
|
---|
245 | _ = oSet;
|
---|
246 | return '<td>%d</td>' % (idKey,);
|
---|
247 |
|
---|
248 | def _formatRowValueForTable(self, oRow, oPeriod, cColsPerSeries):
|
---|
249 | """ Formats a row value for the HTML table. """
|
---|
250 | _ = oPeriod;
|
---|
251 | if oRow is None:
|
---|
252 | return u'<td colspan="%d"> </td>' % (cColsPerSeries,);
|
---|
253 | if cColsPerSeries == 2:
|
---|
254 | return u'<td align="right">%u%%</td><td align="center">%u / %u</td>' \
|
---|
255 | % (oRow.cHits * 100 / oRow.cTotal, oRow.cHits, oRow.cTotal);
|
---|
256 | return u'<td align="center">%u</td>' % (oRow.cHits,);
|
---|
257 |
|
---|
258 | def _formatSeriesTotalForTable(self, oSet, idKey, cColsPerSeries):
|
---|
259 | """ Formats the totals cell for a data series in the HTML table. """
|
---|
260 | dcTotalPerId = getattr(oSet, 'dcTotalPerId', None);
|
---|
261 | if cColsPerSeries == 2:
|
---|
262 | return u'<td align="right">%u%%</td><td align="center">%u/%u</td>' \
|
---|
263 | % (oSet.dcHitsPerId[idKey] * 100 / dcTotalPerId[idKey], oSet.dcHitsPerId[idKey], dcTotalPerId[idKey]);
|
---|
264 | return u'<td align="center">%u</td>' % (oSet.dcHitsPerId[idKey],);
|
---|
265 |
|
---|
266 | def _generateTableForSet(self, oSet, sColumnName, aidSorted = None, iSortColumn = 0,
|
---|
267 | fWithTotals = True, cColsPerSeries = None):
|
---|
268 | """
|
---|
269 | Turns the set into a table.
|
---|
270 |
|
---|
271 | Returns raw html.
|
---|
272 | """
|
---|
273 | sHtml = u'<table class="tmtbl-report-set" width="100%%">\n';
|
---|
274 | if cColsPerSeries is None:
|
---|
275 | cColsPerSeries = 2 if hasattr(oSet, 'dcTotalPerId') else 1;
|
---|
276 |
|
---|
277 | # Header row.
|
---|
278 | sHtml += u' <tr><thead><th></th><th>%s</th>' % (webutils.escapeElem(sColumnName),)
|
---|
279 | for iPeriod, oPeriod in enumerate(reversed(oSet.aoPeriods)):
|
---|
280 | sHtml += u'<th colspan="%d">%s%s</th>' % ( cColsPerSeries, webutils.escapeElem(oPeriod.sDesc),
|
---|
281 | '▼' if iPeriod == iSortColumn else '');
|
---|
282 | if fWithTotals:
|
---|
283 | sHtml += u'<th colspan="%d">Total%s</th>' % (cColsPerSeries,
|
---|
284 | '▼' if iSortColumn == len(oSet.aoPeriods) else '');
|
---|
285 | sHtml += u'</thead></td>\n';
|
---|
286 |
|
---|
287 | # Each data series.
|
---|
288 | if aidSorted is None:
|
---|
289 | aidSorted = oSet.dSubjects.keys();
|
---|
290 | sHtml += u' <tbody>\n';
|
---|
291 | for iRow, idKey in enumerate(aidSorted):
|
---|
292 | sHtml += u' <tr class="%s">' % ('tmodd' if iRow & 1 else 'tmeven',);
|
---|
293 | sHtml += u'<td align="left">#%u</td>' % (iRow + 1,);
|
---|
294 | sHtml += self._formatSeriesNameForTable(oSet, idKey);
|
---|
295 | for oPeriod in reversed(oSet.aoPeriods):
|
---|
296 | oRow = oPeriod.dRowsById.get(idKey, None);
|
---|
297 | sHtml += self._formatRowValueForTable(oRow, oPeriod, cColsPerSeries);
|
---|
298 | if fWithTotals:
|
---|
299 | sHtml += self._formatSeriesTotalForTable(oSet, idKey, cColsPerSeries);
|
---|
300 | sHtml += u' </tr>\n';
|
---|
301 | sHtml += u' </tbody>\n';
|
---|
302 | sHtml += u'</table>\n';
|
---|
303 | return sHtml;
|
---|
304 |
|
---|
305 |
|
---|
306 | class WuiReportFailuresWithTotalBase(WuiReportFailuresBase):
|
---|
307 | """
|
---|
308 | For ReportPeriodSetWithTotalBase.
|
---|
309 | """
|
---|
310 |
|
---|
311 | def _formatSeriedNameForGraph(self, oSubject):
|
---|
312 | """
|
---|
313 | Format the subject name for the graph.
|
---|
314 | """
|
---|
315 | return str(oSubject);
|
---|
316 |
|
---|
317 | def _getSortedIds(self, oSet, fByTotal = None):
|
---|
318 | """
|
---|
319 | Get default sorted subject IDs and which column.
|
---|
320 | """
|
---|
321 |
|
---|
322 | if fByTotal is None:
|
---|
323 | fByTotal = oSet.cMaxTotal < 10;
|
---|
324 |
|
---|
325 | if fByTotal is True:
|
---|
326 | # Sort the total.
|
---|
327 | aidSortedRaw = sorted(oSet.dSubjects,
|
---|
328 | key = lambda idKey: oSet.dcHitsPerId[idKey] * 10000 / oSet.dcTotalPerId[idKey],
|
---|
329 | reverse = True);
|
---|
330 | iColumn = len(oSet.aoPeriods);
|
---|
331 | else:
|
---|
332 | # Sort by NOW column.
|
---|
333 | dTmp = {};
|
---|
334 | for idKey in oSet.dSubjects:
|
---|
335 | oRow = oSet.aoPeriods[-1].dRowsById.get(idKey, None);
|
---|
336 | if oRow is None: dTmp[idKey] = 0;
|
---|
337 | else: dTmp[idKey] = oRow.cHits * 10000 / max(1, oRow.cTotal);
|
---|
338 | aidSortedRaw = sorted(dTmp, key = lambda idKey: dTmp[idKey], reverse = True);
|
---|
339 | iColumn = 0;
|
---|
340 | return (aidSortedRaw, iColumn);
|
---|
341 |
|
---|
342 | def _generateGraph(self, oSet, sIdBase, aidSortedRaw):
|
---|
343 | """
|
---|
344 | Generates graph.
|
---|
345 | """
|
---|
346 | sHtml = u'';
|
---|
347 | fGenerateGraph = len(aidSortedRaw) <= 6 and len(aidSortedRaw) > 0; ## Make this configurable.
|
---|
348 | if fGenerateGraph:
|
---|
349 | # Figure the graph width for all of them.
|
---|
350 | uPctMax = max(oSet.uMaxPct, oSet.cMaxHits * 100 / oSet.cMaxTotal);
|
---|
351 | uPctMax = max(uPctMax + 2, 10);
|
---|
352 |
|
---|
353 | for _, aidSorted in enumerate(self._splitSeriesIntoMultipleGraphs(aidSortedRaw, 8)):
|
---|
354 | asNames = [];
|
---|
355 | for idKey in aidSorted:
|
---|
356 | oSubject = oSet.dSubjects[idKey];
|
---|
357 | asNames.append(self._formatSeriedNameForGraph(oSubject));
|
---|
358 |
|
---|
359 | oTable = WuiHlpGraphDataTable('Period', asNames);
|
---|
360 |
|
---|
361 | for _, oPeriod in enumerate(reversed(oSet.aoPeriods)):
|
---|
362 | aiValues = [];
|
---|
363 | asValues = [];
|
---|
364 |
|
---|
365 | for idKey in aidSorted:
|
---|
366 | oRow = oPeriod.dRowsById.get(idKey, None);
|
---|
367 | if oRow is not None:
|
---|
368 | uPct = oRow.cHits * 100 / oRow.cTotal;
|
---|
369 | aiValues.append(uPct);
|
---|
370 | asValues.append('%u%% (%u/%u)' % (uPct, oRow.cHits, oRow.cTotal));
|
---|
371 | else:
|
---|
372 | aiValues.append(0);
|
---|
373 | asValues.append('0');
|
---|
374 |
|
---|
375 | oTable.addRow(oPeriod.sDesc, aiValues, asValues);
|
---|
376 |
|
---|
377 | if True: # pylint: disable=W0125
|
---|
378 | aiValues = [];
|
---|
379 | asValues = [];
|
---|
380 | for idKey in aidSorted:
|
---|
381 | uPct = oSet.dcHitsPerId[idKey] * 100 / oSet.dcTotalPerId[idKey];
|
---|
382 | aiValues.append(uPct);
|
---|
383 | asValues.append('%u%% (%u/%u)' % (uPct, oSet.dcHitsPerId[idKey], oSet.dcTotalPerId[idKey]));
|
---|
384 | oTable.addRow('Totals', aiValues, asValues);
|
---|
385 |
|
---|
386 | oGraph = WuiHlpBarGraph(sIdBase, oTable, self._oDisp);
|
---|
387 | oGraph.setRangeMax(uPctMax);
|
---|
388 | sHtml += '<br>\n';
|
---|
389 | sHtml += oGraph.renderGraph();
|
---|
390 | return sHtml;
|
---|
391 |
|
---|
392 |
|
---|
393 |
|
---|
394 | class WuiReportFailureReasons(WuiReportFailuresBase):
|
---|
395 | """
|
---|
396 | Generates a report displaying the failure reasons over time.
|
---|
397 | """
|
---|
398 |
|
---|
399 | def _formatEdgeOccurenceSubject(self, oTransient):
|
---|
400 | return u'%s / %s' % ( webutils.escapeElem(oTransient.oReason.oCategory.sShort),
|
---|
401 | webutils.escapeElem(oTransient.oReason.sShort),);
|
---|
402 |
|
---|
403 | def _formatSeriesNameForTable(self, oSet, idKey):
|
---|
404 | oReason = oSet.dSubjects[idKey];
|
---|
405 | sHtml = u'<td>';
|
---|
406 | sHtml += u'%s / %s' % ( webutils.escapeElem(oReason.oCategory.sShort), webutils.escapeElem(oReason.sShort),);
|
---|
407 | sHtml += u'</td>';
|
---|
408 | return sHtml;
|
---|
409 |
|
---|
410 |
|
---|
411 | def generateReportBody(self):
|
---|
412 | self._sTitle = 'Failure reasons';
|
---|
413 |
|
---|
414 | #
|
---|
415 | # Get the data and sort the data series in descending order of badness.
|
---|
416 | #
|
---|
417 | oSet = self._oModel.getFailureReasons();
|
---|
418 | aidSortedRaw = sorted(oSet.dSubjects, key = lambda idReason: oSet.dcHitsPerId[idReason], reverse = True);
|
---|
419 |
|
---|
420 | #
|
---|
421 | # Generate table and transition list. These are the most useful ones with the current graph machinery.
|
---|
422 | #
|
---|
423 | sHtml = self._generateTableForSet(oSet, 'Test Cases', aidSortedRaw, len(oSet.aoPeriods));
|
---|
424 | sHtml += self._generateTransitionList(oSet);
|
---|
425 |
|
---|
426 | #
|
---|
427 | # Check if most of the stuff is without any assign reason, if so, skip
|
---|
428 | # that part of the graph so it doesn't offset the interesting bits.
|
---|
429 | #
|
---|
430 | fIncludeWithoutReason = True;
|
---|
431 | for oPeriod in reversed(oSet.aoPeriods):
|
---|
432 | if oPeriod.cWithoutReason > oSet.cMaxHits * 4:
|
---|
433 | fIncludeWithoutReason = False;
|
---|
434 | sHtml += '<p>Warning: Many failures without assigned reason!</p>\n';
|
---|
435 | break;
|
---|
436 |
|
---|
437 | #
|
---|
438 | # Generate the graph.
|
---|
439 | #
|
---|
440 | fGenerateGraph = len(aidSortedRaw) <= 9 and len(aidSortedRaw) > 0; ## Make this configurable.
|
---|
441 | if fGenerateGraph:
|
---|
442 | aidSorted = aidSortedRaw;
|
---|
443 |
|
---|
444 | asNames = [];
|
---|
445 | for idReason in aidSorted:
|
---|
446 | oReason = oSet.dSubjects[idReason];
|
---|
447 | asNames.append('%s / %s' % (oReason.oCategory.sShort, oReason.sShort,) )
|
---|
448 | if fIncludeWithoutReason:
|
---|
449 | asNames.append('No reason');
|
---|
450 |
|
---|
451 | oTable = WuiHlpGraphDataTable('Period', asNames);
|
---|
452 |
|
---|
453 | cMax = oSet.cMaxHits;
|
---|
454 | for _, oPeriod in enumerate(reversed(oSet.aoPeriods)):
|
---|
455 | aiValues = [];
|
---|
456 |
|
---|
457 | for idReason in aidSorted:
|
---|
458 | oRow = oPeriod.dRowsById.get(idReason, None);
|
---|
459 | iValue = oRow.cHits if oRow is not None else 0;
|
---|
460 | aiValues.append(iValue);
|
---|
461 |
|
---|
462 | if fIncludeWithoutReason:
|
---|
463 | aiValues.append(oPeriod.cWithoutReason);
|
---|
464 | if oPeriod.cWithoutReason > cMax:
|
---|
465 | cMax = oPeriod.cWithoutReason;
|
---|
466 |
|
---|
467 | oTable.addRow(oPeriod.sDesc, aiValues);
|
---|
468 |
|
---|
469 | oGraph = WuiHlpBarGraph('failure-reason', oTable, self._oDisp);
|
---|
470 | oGraph.setRangeMax(max(cMax + 1, 3));
|
---|
471 | sHtml += oGraph.renderGraph();
|
---|
472 | return sHtml;
|
---|
473 |
|
---|
474 |
|
---|
475 | class WuiReportTestCaseFailures(WuiReportFailuresWithTotalBase):
|
---|
476 | """
|
---|
477 | Generates a report displaying the failure reasons over time.
|
---|
478 | """
|
---|
479 |
|
---|
480 | def _formatEdgeOccurenceSubject(self, oTransient):
|
---|
481 | sHtml = u'%s ' % ( webutils.escapeElem(oTransient.oSubject.sName),);
|
---|
482 | sHtml += WuiTestCaseDetailsLink(oTransient.oSubject.idTestCase, fBracketed = False).toHtml();
|
---|
483 | return sHtml;
|
---|
484 |
|
---|
485 | def _formatSeriesNameForTable(self, oSet, idKey):
|
---|
486 | oTestCase = oSet.dSubjects[idKey];
|
---|
487 | sHtml = u'<td>';
|
---|
488 | sHtml += WuiReportSummaryLink(ReportModelBase.ksSubTestCase, oTestCase.idTestCase, sName = oTestCase.sName,
|
---|
489 | dExtraParams = self._dExtraParams).toHtml();
|
---|
490 | sHtml += u' ';
|
---|
491 | sHtml += WuiTestCaseDetailsLink(oTestCase.idTestCase).toHtml();
|
---|
492 | sHtml += u'</td>';
|
---|
493 | return sHtml;
|
---|
494 |
|
---|
495 | def _formatSeriedNameForGraph(self, oSubject):
|
---|
496 | return oSubject.sName;
|
---|
497 |
|
---|
498 | def generateReportBody(self):
|
---|
499 | self._sTitle = 'Test Case Failures';
|
---|
500 | oSet = self._oModel.getTestCaseFailures();
|
---|
501 | (aidSortedRaw, iSortColumn) = self._getSortedIds(oSet);
|
---|
502 |
|
---|
503 | sHtml = self._generateTableForSet(oSet, 'Test Cases', aidSortedRaw, iSortColumn);
|
---|
504 | sHtml += self._generateTransitionList(oSet);
|
---|
505 | sHtml += self._generateGraph(oSet, 'testcase-graph', aidSortedRaw);
|
---|
506 | return sHtml;
|
---|
507 |
|
---|
508 |
|
---|
509 | class WuiReportTestCaseArgsFailures(WuiReportFailuresWithTotalBase):
|
---|
510 | """
|
---|
511 | Generates a report displaying the failure reasons over time.
|
---|
512 | """
|
---|
513 |
|
---|
514 | @staticmethod
|
---|
515 | def _formatName(oTestCaseArgs):
|
---|
516 | if oTestCaseArgs.sSubName is not None and len(oTestCaseArgs.sSubName) > 0:
|
---|
517 | sName = u'%s / %s' % ( oTestCaseArgs.oTestCase.sName, oTestCaseArgs.sSubName, );
|
---|
518 | else:
|
---|
519 | sName = u'%s / #%u' % ( oTestCaseArgs.oTestCase.sName, oTestCaseArgs.idTestCaseArgs, );
|
---|
520 | return sName;
|
---|
521 |
|
---|
522 | def _formatEdgeOccurenceSubject(self, oTransient):
|
---|
523 | sHtml = u'%s ' % ( webutils.escapeElem(self._formatName(oTransient.oSubject)),);
|
---|
524 | sHtml += WuiTestCaseDetailsLink(oTransient.oSubject.idTestCase, fBracketed = False).toHtml();
|
---|
525 | return sHtml;
|
---|
526 |
|
---|
527 | def _formatSeriesNameForTable(self, oSet, idKey):
|
---|
528 | oTestCaseArgs = oSet.dSubjects[idKey];
|
---|
529 | sHtml = u'<td>';
|
---|
530 | sHtml += WuiReportSummaryLink(ReportModelBase.ksSubTestCaseArgs, oTestCaseArgs.idTestCaseArgs,
|
---|
531 | sName = self._formatName(oTestCaseArgs), dExtraParams = self._dExtraParams).toHtml();
|
---|
532 | sHtml += u' ';
|
---|
533 | sHtml += WuiTestCaseDetailsLink(oTestCaseArgs.idTestCase).toHtml();
|
---|
534 | sHtml += u'</td>';
|
---|
535 | return sHtml;
|
---|
536 |
|
---|
537 | def _formatSeriedNameForGraph(self, oSubject):
|
---|
538 | return self._formatName(oSubject);
|
---|
539 |
|
---|
540 | def generateReportBody(self):
|
---|
541 | self._sTitle = 'Test Case Variation Failures';
|
---|
542 | oSet = self._oModel.getTestCaseVariationFailures();
|
---|
543 | (aidSortedRaw, iSortColumn) = self._getSortedIds(oSet);
|
---|
544 |
|
---|
545 | sHtml = self._generateTableForSet(oSet, 'Test Case Variations', aidSortedRaw, iSortColumn);
|
---|
546 | sHtml += self._generateTransitionList(oSet);
|
---|
547 | sHtml += self._generateGraph(oSet, 'testcasearg-graph', aidSortedRaw);
|
---|
548 | return sHtml;
|
---|
549 |
|
---|
550 |
|
---|
551 |
|
---|
552 | class WuiReportTestBoxFailures(WuiReportFailuresWithTotalBase):
|
---|
553 | """
|
---|
554 | Generates a report displaying the failure reasons over time.
|
---|
555 | """
|
---|
556 |
|
---|
557 | def _formatEdgeOccurenceSubject(self, oTransient):
|
---|
558 | sHtml = u'%s ' % ( webutils.escapeElem(oTransient.oSubject.sName),);
|
---|
559 | sHtml += WuiTestBoxDetailsLink(oTransient.oSubject.idTestBox, fBracketed = False).toHtml();
|
---|
560 | return sHtml;
|
---|
561 |
|
---|
562 | def _formatSeriesNameForTable(self, oSet, idKey):
|
---|
563 | oTestBox = oSet.dSubjects[idKey];
|
---|
564 | sHtml = u'<td>';
|
---|
565 | sHtml += WuiReportSummaryLink(ReportModelBase.ksSubTestBox, oTestBox.idTestBox, sName = oTestBox.sName,
|
---|
566 | dExtraParams = self._dExtraParams).toHtml();
|
---|
567 | sHtml += u' ';
|
---|
568 | sHtml += WuiTestBoxDetailsLink(oTestBox.idTestBox).toHtml();
|
---|
569 | sHtml += u'</td>';
|
---|
570 | return sHtml;
|
---|
571 |
|
---|
572 | def _formatSeriedNameForGraph(self, oSubject):
|
---|
573 | return oSubject.sName;
|
---|
574 |
|
---|
575 | def generateReportBody(self):
|
---|
576 | self._sTitle = 'Test Box Failures';
|
---|
577 | oSet = self._oModel.getTestBoxFailures();
|
---|
578 | (aidSortedRaw, iSortColumn) = self._getSortedIds(oSet);
|
---|
579 |
|
---|
580 | sHtml = self._generateTableForSet(oSet, 'Test Boxes', aidSortedRaw, iSortColumn);
|
---|
581 | sHtml += self._generateTransitionList(oSet);
|
---|
582 | sHtml += self._generateGraph(oSet, 'testbox-graph', aidSortedRaw);
|
---|
583 | return sHtml;
|
---|
584 |
|
---|
585 |
|
---|
586 | class WuiReportSummary(WuiReportBase):
|
---|
587 | """
|
---|
588 | Summary report.
|
---|
589 | """
|
---|
590 |
|
---|
591 | def generateReportBody(self):
|
---|
592 | self._sTitle = 'Summary';
|
---|
593 | sHtml = '<p>This will display several reports and listings useful to get an overview of %s (id=%s).</p>' \
|
---|
594 | % (self._oModel.sSubject, self._oModel.aidSubjects,);
|
---|
595 |
|
---|
596 | aoReports = [];
|
---|
597 |
|
---|
598 | aoReports.append(WuiReportSuccessRate( self._oModel, self._dParams, fSubReport = True,
|
---|
599 | fnDPrint = self._fnDPrint, oDisp = self._oDisp));
|
---|
600 | aoReports.append(WuiReportTestCaseFailures(self._oModel, self._dParams, fSubReport = True,
|
---|
601 | fnDPrint = self._fnDPrint, oDisp = self._oDisp));
|
---|
602 | if self._oModel.sSubject == ReportModelBase.ksSubTestCase:
|
---|
603 | aoReports.append(WuiReportTestCaseArgsFailures(self._oModel, self._dParams, fSubReport = True,
|
---|
604 | fnDPrint = self._fnDPrint, oDisp = self._oDisp));
|
---|
605 | aoReports.append(WuiReportTestBoxFailures( self._oModel, self._dParams, fSubReport = True,
|
---|
606 | fnDPrint = self._fnDPrint, oDisp = self._oDisp));
|
---|
607 | aoReports.append(WuiReportFailureReasons( self._oModel, self._dParams, fSubReport = True,
|
---|
608 | fnDPrint = self._fnDPrint, oDisp = self._oDisp));
|
---|
609 |
|
---|
610 | for oReport in aoReports:
|
---|
611 | (sTitle, sContent) = oReport.show();
|
---|
612 | sHtml += '<br>'; # drop this layout hack
|
---|
613 | sHtml += '<div>';
|
---|
614 | sHtml += '<h3>%s</h3>\n' % (webutils.escapeElem(sTitle),);
|
---|
615 | sHtml += sContent;
|
---|
616 | sHtml += '</div>';
|
---|
617 |
|
---|
618 | return sHtml;
|
---|
619 |
|
---|