1 | # -*- coding: utf-8 -*-
|
---|
2 | # $Id: reporting.py 93115 2022-01-01 11:31:46Z vboxsync $
|
---|
3 |
|
---|
4 | """
|
---|
5 | Test Result Report Writer.
|
---|
6 |
|
---|
7 | This takes a processed test result tree and creates a HTML, re-structured text,
|
---|
8 | or normal text report from it.
|
---|
9 | """
|
---|
10 |
|
---|
11 | __copyright__ = \
|
---|
12 | """
|
---|
13 | Copyright (C) 2010-2022 Oracle Corporation
|
---|
14 |
|
---|
15 | This file is part of VirtualBox Open Source Edition (OSE), as
|
---|
16 | available from http://www.virtualbox.org. This file is free software;
|
---|
17 | you can redistribute it and/or modify it under the terms of the GNU
|
---|
18 | General Public License (GPL) as published by the Free Software
|
---|
19 | Foundation, in version 2 as it comes in the "COPYING" file of the
|
---|
20 | VirtualBox OSE distribution. VirtualBox OSE is distributed in the
|
---|
21 | hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
|
---|
22 |
|
---|
23 | The contents of this file may alternatively be used under the terms
|
---|
24 | of the Common Development and Distribution License Version 1.0
|
---|
25 | (CDDL) only, as it comes in the "COPYING.CDDL" file of the
|
---|
26 | VirtualBox OSE distribution, in which case the provisions of the
|
---|
27 | CDDL are applicable instead of those of the GPL.
|
---|
28 |
|
---|
29 | You may elect to license modified versions of this file under the
|
---|
30 | terms and conditions of either the GPL or the CDDL or both.
|
---|
31 | """
|
---|
32 | __version__ = "$Revision: 93115 $"
|
---|
33 | __all__ = ['HtmlReport', 'RstReport', 'TextReport'];
|
---|
34 |
|
---|
35 |
|
---|
36 | def tryAddThousandSeparators(sPotentialInterger):
|
---|
37 | """ Apparently, python 3.0(/3.1) has(/will have) support for this..."""
|
---|
38 | # Try convert the string/value to a long.
|
---|
39 | try:
|
---|
40 | lVal = long(sPotentialInterger);
|
---|
41 | lVal = long(sPotentialInterger);
|
---|
42 | except:
|
---|
43 | return sPotentialInterger;
|
---|
44 |
|
---|
45 | # Convert it back to a string (paranoia) and build up the new string.
|
---|
46 | sOld = str(lVal);
|
---|
47 | chSign = '';
|
---|
48 | if sOld[0] == '-':
|
---|
49 | chSign = '-';
|
---|
50 | sOld = sOld[1:];
|
---|
51 | elif sPotentialInterger[0] == '+':
|
---|
52 | chSign = '+';
|
---|
53 | cchDigits = len(sOld);
|
---|
54 | iDigit = 0
|
---|
55 | sNewVal = '';
|
---|
56 | while iDigit < cchDigits:
|
---|
57 | if (iDigit % 3) == 0 and iDigit > 0:
|
---|
58 | sNewVal = ' ' + sNewVal;
|
---|
59 | sNewVal = sOld[cchDigits - iDigit - 1] + sNewVal;
|
---|
60 | iDigit += 1;
|
---|
61 | return chSign + sNewVal;
|
---|
62 |
|
---|
63 |
|
---|
64 | class Table(object):
|
---|
65 | """
|
---|
66 | A table as a header as well as data rows, thus this class.
|
---|
67 | """
|
---|
68 | def __init__(self, oTest, fSplitDiff):
|
---|
69 | self.aasRows = [];
|
---|
70 | self.asHeader = ['Test',];
|
---|
71 | self.asUnits = ['',];
|
---|
72 | for oValue in oTest.aoValues:
|
---|
73 | self.asHeader.append(oValue.sName);
|
---|
74 | self.asUnits.append(oValue.sUnit);
|
---|
75 | self.addRow(oTest, fSplitDiff);
|
---|
76 |
|
---|
77 | def addRow(self, oTest, fSplitDiff):
|
---|
78 | """Adds a row."""
|
---|
79 | asRow = [oTest.getFullName(),];
|
---|
80 | for oValue in oTest.aoValues:
|
---|
81 | asRow.append(oValue.sValue);
|
---|
82 | if not fSplitDiff:
|
---|
83 | self.aasRows.append(asRow);
|
---|
84 | else:
|
---|
85 | # Split cells into multiple rows on '|'. Omit the first column.
|
---|
86 | iRow = 0;
|
---|
87 | asThisRow = [asRow[0], ];
|
---|
88 | fMoreTodo = True;
|
---|
89 | while fMoreTodo:
|
---|
90 | for i in range(1, len(asRow)):
|
---|
91 | asSplit = asRow[i].split('|');
|
---|
92 | asThisRow.append(asSplit[0]);
|
---|
93 | asRow[i] = '|'.join(asSplit[1:])
|
---|
94 | self.aasRows.append(asThisRow);
|
---|
95 |
|
---|
96 | # Done?
|
---|
97 | fMoreTodo = False;
|
---|
98 | for i in range(1, len(asRow)):
|
---|
99 | if len(asRow[i]):
|
---|
100 | fMoreTodo = True;
|
---|
101 | asThisRow = ['', ];
|
---|
102 | iRow += 1;
|
---|
103 |
|
---|
104 | # Readability hack: Add an extra row if there are diffs.
|
---|
105 | if iRow > 1:
|
---|
106 | asRow[0] = '';
|
---|
107 | self.aasRows.append(asRow);
|
---|
108 |
|
---|
109 | return True;
|
---|
110 |
|
---|
111 | def hasTheSameHeadingAsTest(self, oTest):
|
---|
112 | """ Checks if the test values has the same heading."""
|
---|
113 | i = 1;
|
---|
114 | for oValue in oTest.aoValues:
|
---|
115 | if self.asHeader[i] != oValue.sName:
|
---|
116 | return False;
|
---|
117 | if self.asUnits[i] != oValue.sUnit:
|
---|
118 | return False;
|
---|
119 | i += 1;
|
---|
120 | return True;
|
---|
121 |
|
---|
122 | def hasTheSameHeadingAsTable(self, oTable):
|
---|
123 | """ Checks if the other table has the same heading."""
|
---|
124 | if len(oTable.asHeader) != len(self.asHeader):
|
---|
125 | return False;
|
---|
126 | for i in range(len(self.asHeader)):
|
---|
127 | if self.asHeader[i] != oTable.asHeader[i]:
|
---|
128 | return False;
|
---|
129 | if self.asUnits[i] != oTable.asUnits[i]:
|
---|
130 | return False;
|
---|
131 | return True;
|
---|
132 |
|
---|
133 | def appendTable(self, oTable):
|
---|
134 | """ Append the rows in oTable. oTable has the same heading as us. """
|
---|
135 | self.aasRows.extend(oTable.aasRows);
|
---|
136 | return True;
|
---|
137 |
|
---|
138 | # manipulation and stuff
|
---|
139 |
|
---|
140 | def optimizeUnit(self):
|
---|
141 | """ Turns bytes into KB, MB or GB. """
|
---|
142 | ## @todo
|
---|
143 | pass;
|
---|
144 |
|
---|
145 | def addThousandSeparators(self):
|
---|
146 | """ Adds thousand separators to make numbers more readable. """
|
---|
147 | for iRow in range(len(self.aasRows)):
|
---|
148 | for iColumn in range(1, len(self.aasRows[iRow])):
|
---|
149 | asValues = self.aasRows[iRow][iColumn].split('|');
|
---|
150 | for i in range(len(asValues)):
|
---|
151 | asValues[i] = tryAddThousandSeparators(asValues[i]);
|
---|
152 | self.aasRows[iRow][iColumn] = '|'.join(asValues);
|
---|
153 | return True;
|
---|
154 |
|
---|
155 | def getRowWidths(self):
|
---|
156 | """Figure out the column withs."""
|
---|
157 | # Header is first.
|
---|
158 | acchColumns = [];
|
---|
159 | for i in range(len(self.asHeader)):
|
---|
160 | cch = 1;
|
---|
161 | asWords = self.asHeader[i].split();
|
---|
162 | for s in asWords:
|
---|
163 | if len(s) > cch:
|
---|
164 | cch = len(s);
|
---|
165 | if i > 0 and len(self.asUnits[i]) > cch:
|
---|
166 | cch = len(self.asUnits[i]);
|
---|
167 | acchColumns.append(cch);
|
---|
168 |
|
---|
169 | # Check out all cells.
|
---|
170 | for asColumns in self.aasRows:
|
---|
171 | for i in range(len(asColumns)):
|
---|
172 | if len(asColumns[i]) > acchColumns[i]:
|
---|
173 | acchColumns[i] = len(asColumns[i]);
|
---|
174 | return acchColumns;
|
---|
175 |
|
---|
176 |
|
---|
177 | def tabelizeTestResults(oTest, fSplitDiff):
|
---|
178 | """
|
---|
179 | Break the test results down into a list of tables containing the values.
|
---|
180 |
|
---|
181 | TODO: Handle passed / failed stuff too. Not important for benchmarks.
|
---|
182 | """
|
---|
183 | # Pass 1
|
---|
184 | aoTables = [];
|
---|
185 | aoStack = [];
|
---|
186 | aoStack.append((oTest, 0));
|
---|
187 | while len(aoStack) > 0:
|
---|
188 | oCurTest, iChild = aoStack.pop();
|
---|
189 |
|
---|
190 | # depth first
|
---|
191 | if iChild < len(oCurTest.aoChildren):
|
---|
192 | aoStack.append((oCurTest, iChild + 1));
|
---|
193 | aoStack.append((oCurTest.aoChildren[iChild], 0));
|
---|
194 | continue;
|
---|
195 |
|
---|
196 | # values -> row
|
---|
197 | if len(oCurTest.aoValues) > 0:
|
---|
198 | if len(aoTables) > 0 and aoTables[len(aoTables) - 1].hasTheSameHeadingAsTest(oCurTest):
|
---|
199 | aoTables[len(aoTables) - 1].addRow(oCurTest, fSplitDiff);
|
---|
200 | else:
|
---|
201 | aoTables.append(Table(oCurTest, fSplitDiff));
|
---|
202 |
|
---|
203 | # Pass 2 - Combine tables with the same heading.
|
---|
204 | aoTables2 = [];
|
---|
205 | for oTable in aoTables:
|
---|
206 | for i in range(len(aoTables2)):
|
---|
207 | if aoTables2[i].hasTheSameHeadingAsTable(oTable):
|
---|
208 | aoTables2[i].appendTable(oTable);
|
---|
209 | oTable = None;
|
---|
210 | break;
|
---|
211 | if oTable is not None:
|
---|
212 | aoTables2.append(oTable);
|
---|
213 |
|
---|
214 | return aoTables2;
|
---|
215 |
|
---|
216 | def produceHtmlReport(oTest):
|
---|
217 | """
|
---|
218 | Produce an HTML report on stdout (via print).
|
---|
219 | """
|
---|
220 | print('not implemented: %s' % (oTest));
|
---|
221 | return False;
|
---|
222 |
|
---|
223 | def produceReStructuredTextReport(oTest):
|
---|
224 | """
|
---|
225 | Produce a ReStructured text report on stdout (via print).
|
---|
226 | """
|
---|
227 | print('not implemented: %s' % (oTest));
|
---|
228 | return False;
|
---|
229 |
|
---|
230 | def produceTextReport(oTest):
|
---|
231 | """
|
---|
232 | Produce a text report on stdout (via print).
|
---|
233 | """
|
---|
234 |
|
---|
235 | #
|
---|
236 | # Report header.
|
---|
237 | #
|
---|
238 | ## @todo later
|
---|
239 |
|
---|
240 | #
|
---|
241 | # Tabelize the results and display the tables.
|
---|
242 | #
|
---|
243 | aoTables = tabelizeTestResults(oTest, True)
|
---|
244 | for oTable in aoTables:
|
---|
245 | ## @todo do max/min on the columns where we can do [GMK]B(/s).
|
---|
246 | oTable.addThousandSeparators();
|
---|
247 | acchColumns = oTable.getRowWidths();
|
---|
248 |
|
---|
249 | # The header.
|
---|
250 | # This is a bit tedious and the solution isn't entirely elegant due
|
---|
251 | # to the pick-it-up-as-you-go-along python skills.
|
---|
252 | aasHeader = [];
|
---|
253 | aasHeader.append([]);
|
---|
254 | for i in range(len(oTable.asHeader)):
|
---|
255 | aasHeader[0].append('');
|
---|
256 |
|
---|
257 | for iColumn in range(len(oTable.asHeader)):
|
---|
258 | asWords = oTable.asHeader[iColumn].split();
|
---|
259 | iLine = 0;
|
---|
260 | for s in asWords:
|
---|
261 | if len(aasHeader[iLine][iColumn]) <= 0:
|
---|
262 | aasHeader[iLine][iColumn] = s;
|
---|
263 | elif len(s) + 1 + len(aasHeader[iLine][iColumn]) <= acchColumns[iColumn]:
|
---|
264 | aasHeader[iLine][iColumn] += ' ' + s;
|
---|
265 | else:
|
---|
266 | iLine += 1;
|
---|
267 | if iLine >= len(aasHeader): # There must be a better way to do this...
|
---|
268 | aasHeader.append([]);
|
---|
269 | for i in range(len(oTable.asHeader)):
|
---|
270 | aasHeader[iLine].append('');
|
---|
271 | aasHeader[iLine][iColumn] = s;
|
---|
272 |
|
---|
273 | for asLine in aasHeader:
|
---|
274 | sLine = '';
|
---|
275 | for i in range(len(asLine)):
|
---|
276 | if i > 0: sLine += ' ';
|
---|
277 | sLine += asLine[i].center(acchColumns[i]);
|
---|
278 | print(sLine);
|
---|
279 |
|
---|
280 | # Units.
|
---|
281 | sLine = '';
|
---|
282 | for i in range(len(oTable.asUnits)):
|
---|
283 | if i > 0: sLine += ' ';
|
---|
284 | sLine += oTable.asUnits[i].center(acchColumns[i]);
|
---|
285 | print(sLine);
|
---|
286 |
|
---|
287 | # Separator line.
|
---|
288 | sLine = '';
|
---|
289 | for i in range(len(oTable.asHeader)):
|
---|
290 | if i > 0: sLine += ' '
|
---|
291 | sLine += '=' * acchColumns[i];
|
---|
292 | print(sLine);
|
---|
293 |
|
---|
294 | # The rows.
|
---|
295 | for asColumns in oTable.aasRows:
|
---|
296 | sText = asColumns[0].ljust(acchColumns[0]);
|
---|
297 | for i in range(1, len(asColumns)):
|
---|
298 | sText += ' ' + asColumns[i].rjust(acchColumns[i]);
|
---|
299 | print(sText);
|
---|
300 |
|
---|
301 | return None;
|
---|
302 |
|
---|