VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testmanager/core/testresultfailures.py@ 63694

Last change on this file since 63694 was 62484, checked in by vboxsync, 8 years ago

(C) 2016

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 20.8 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: testresultfailures.py 62484 2016-07-22 18:35:33Z vboxsync $
3# pylint: disable=C0302
4
5## @todo Rename this file to testresult.py!
6
7"""
8Test Manager - Test result failures.
9"""
10
11__copyright__ = \
12"""
13Copyright (C) 2012-2016 Oracle Corporation
14
15This file is part of VirtualBox Open Source Edition (OSE), as
16available from http://www.virtualbox.org. This file is free software;
17you can redistribute it and/or modify it under the terms of the GNU
18General Public License (GPL) as published by the Free Software
19Foundation, in version 2 as it comes in the "COPYING" file of the
20VirtualBox OSE distribution. VirtualBox OSE is distributed in the
21hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
22
23The contents of this file may alternatively be used under the terms
24of the Common Development and Distribution License Version 1.0
25(CDDL) only, as it comes in the "COPYING.CDDL" file of the
26VirtualBox OSE distribution, in which case the provisions of the
27CDDL are applicable instead of those of the GPL.
28
29You may elect to license modified versions of this file under the
30terms and conditions of either the GPL or the CDDL or both.
31"""
32__version__ = "$Revision: 62484 $"
33# Standard python imports.
34import unittest;
35
36# Validation Kit imports.
37from testmanager.core.base import ModelDataBase, ModelLogicBase, ModelDataBaseTestCase, TMInvalidData, TMRowNotFound, \
38 TMRowAlreadyExists, ChangeLogEntry, AttributeChangeEntry;
39from testmanager.core.failurereason import FailureReasonData;
40from testmanager.core.useraccount import UserAccountLogic;
41
42
43
44class TestResultFailureData(ModelDataBase):
45 """
46 Test result failure reason data.
47 """
48
49 ksIdAttr = 'idTestResult';
50 kfIdAttrIsForForeign = True; # Modifies the 'add' validation.
51
52 ksParam_idTestResult = 'TestResultFailure_idTestResult';
53 ksParam_tsEffective = 'TestResultFailure_tsEffective';
54 ksParam_tsExpire = 'TestResultFailure_tsExpire';
55 ksParam_uidAuthor = 'TestResultFailure_uidAuthor';
56 ksParam_idTestSet = 'TestResultFailure_idTestSet';
57 ksParam_idFailureReason = 'TestResultFailure_idFailureReason';
58 ksParam_sComment = 'TestResultFailure_sComment';
59
60 kasAllowNullAttributes = ['tsEffective', 'tsExpire', 'uidAuthor', 'sComment', 'idTestSet' ];
61
62 kcDbColumns = 7;
63
64 def __init__(self):
65 ModelDataBase.__init__(self)
66 self.idTestResult = None;
67 self.tsEffective = None;
68 self.tsExpire = None;
69 self.uidAuthor = None;
70 self.idTestSet = None;
71 self.idFailureReason = None;
72 self.sComment = None;
73
74 def initFromDbRow(self, aoRow):
75 """
76 Reinitialize from a SELECT * FROM TestResultFailures.
77 Return self. Raises exception if no row.
78 """
79 if aoRow is None:
80 raise TMRowNotFound('Test result file record not found.')
81
82 self.idTestResult = aoRow[0];
83 self.tsEffective = aoRow[1];
84 self.tsExpire = aoRow[2];
85 self.uidAuthor = aoRow[3];
86 self.idTestSet = aoRow[4];
87 self.idFailureReason = aoRow[5];
88 self.sComment = aoRow[6];
89 return self;
90
91 def initFromDbWithId(self, oDb, idTestResult, tsNow = None, sPeriodBack = None):
92 """
93 Initialize the object from the database.
94 """
95 oDb.execute(self.formatSimpleNowAndPeriodQuery(oDb,
96 'SELECT *\n'
97 'FROM TestResultFailures\n'
98 'WHERE idTestResult = %s\n'
99 , ( idTestResult,), tsNow, sPeriodBack));
100 aoRow = oDb.fetchOne()
101 if aoRow is None:
102 raise TMRowNotFound('idTestResult=%s not found (tsNow=%s, sPeriodBack=%s)' % (idTestResult, tsNow, sPeriodBack));
103 assert len(aoRow) == self.kcDbColumns;
104 return self.initFromDbRow(aoRow);
105
106 def initFromValues(self, idTestResult, idFailureReason, uidAuthor,
107 tsExpire = None, tsEffective = None, idTestSet = None, sComment = None):
108 """
109 Initialize from values.
110 """
111 self.idTestResult = idTestResult;
112 self.tsEffective = tsEffective;
113 self.tsExpire = tsExpire;
114 self.uidAuthor = uidAuthor;
115 self.idTestSet = idTestSet;
116 self.idFailureReason = idFailureReason;
117 self.sComment = sComment;
118 return self;
119
120
121
122class TestResultFailureDataEx(TestResultFailureData):
123 """
124 Extends TestResultFailureData by resolving reasons and user.
125 """
126
127 def __init__(self):
128 TestResultFailureData.__init__(self);
129 self.oFailureReason = None;
130 self.oAuthor = None;
131
132 def initFromDbRowEx(self, aoRow, oFailureReasonLogic, oUserAccountLogic):
133 """
134 Reinitialize from a SELECT * FROM TestResultFailures.
135 Return self. Raises exception if no row.
136 """
137 self.initFromDbRow(aoRow);
138 self.oFailureReason = oFailureReasonLogic.cachedLookup(self.idFailureReason);
139 self.oAuthor = oUserAccountLogic.cachedLookup(self.uidAuthor);
140 return self;
141
142
143class TestResultListingData(ModelDataBase): # pylint: disable=R0902
144 """
145 Test case result data representation for table listing
146 """
147
148 def __init__(self):
149 """Initialize"""
150 ModelDataBase.__init__(self)
151
152 self.idTestSet = None
153
154 self.idBuildCategory = None;
155 self.sProduct = None
156 self.sRepository = None;
157 self.sBranch = None
158 self.sType = None
159 self.idBuild = None;
160 self.sVersion = None;
161 self.iRevision = None
162
163 self.sOs = None;
164 self.sOsVersion = None;
165 self.sArch = None;
166 self.sCpuVendor = None;
167 self.sCpuName = None;
168 self.cCpus = None;
169 self.fCpuHwVirt = None;
170 self.fCpuNestedPaging = None;
171 self.fCpu64BitGuest = None;
172 self.idTestBox = None
173 self.sTestBoxName = None
174
175 self.tsCreated = None
176 self.tsElapsed = None
177 self.enmStatus = None
178 self.cErrors = None;
179
180 self.idTestCase = None
181 self.sTestCaseName = None
182 self.sBaseCmd = None
183 self.sArgs = None
184 self.sSubName = None;
185
186 self.idBuildTestSuite = None;
187 self.iRevisionTestSuite = None;
188
189 self.oFailureReason = None;
190 self.oFailureReasonAssigner = None;
191 self.tsFailureReasonAssigned = None;
192 self.sFailureReasonComment = None;
193
194 def initFromDbRowEx(self, aoRow, oFailureReasonLogic, oUserAccountLogic):
195 """
196 Reinitialize from a database query.
197 Return self. Raises exception if no row.
198 """
199 if aoRow is None:
200 raise TMRowNotFound('Test result record not found.')
201
202 self.idTestSet = aoRow[0];
203
204 self.idBuildCategory = aoRow[1];
205 self.sProduct = aoRow[2];
206 self.sRepository = aoRow[3];
207 self.sBranch = aoRow[4];
208 self.sType = aoRow[5];
209 self.idBuild = aoRow[6];
210 self.sVersion = aoRow[7];
211 self.iRevision = aoRow[8];
212
213 self.sOs = aoRow[9];
214 self.sOsVersion = aoRow[10];
215 self.sArch = aoRow[11];
216 self.sCpuVendor = aoRow[12];
217 self.sCpuName = aoRow[13];
218 self.cCpus = aoRow[14];
219 self.fCpuHwVirt = aoRow[15];
220 self.fCpuNestedPaging = aoRow[16];
221 self.fCpu64BitGuest = aoRow[17];
222 self.idTestBox = aoRow[18];
223 self.sTestBoxName = aoRow[19];
224
225 self.tsCreated = aoRow[20];
226 self.tsElapsed = aoRow[21];
227 self.enmStatus = aoRow[22];
228 self.cErrors = aoRow[23];
229
230 self.idTestCase = aoRow[24];
231 self.sTestCaseName = aoRow[25];
232 self.sBaseCmd = aoRow[26];
233 self.sArgs = aoRow[27];
234 self.sSubName = aoRow[28];
235
236 self.idBuildTestSuite = aoRow[29];
237 self.iRevisionTestSuite = aoRow[30];
238
239 self.oFailureReason = None;
240 if aoRow[31] is not None:
241 self.oFailureReason = oFailureReasonLogic.cachedLookup(aoRow[31]);
242 self.oFailureReasonAssigner = None;
243 if aoRow[32] is not None:
244 self.oFailureReasonAssigner = oUserAccountLogic.cachedLookup(aoRow[32]);
245 self.tsFailureReasonAssigned = aoRow[33];
246 self.sFailureReasonComment = aoRow[34];
247
248 return self
249
250
251
252class TestResultFailureLogic(ModelLogicBase): # pylint: disable=R0903
253 """
254 Test result failure reason logic.
255 """
256
257 def __init__(self, oDb):
258 ModelLogicBase.__init__(self, oDb)
259
260 def fetchForChangeLog(self, idTestResult, iStart, cMaxRows, tsNow): # pylint: disable=R0914
261 """
262 Fetches change log entries for a failure reason.
263
264 Returns an array of ChangeLogEntry instance and an indicator whether
265 there are more entries.
266 Raises exception on error.
267 """
268
269 if tsNow is None:
270 tsNow = self._oDb.getCurrentTimestamp();
271
272 # 1. Get a list of the changes from both TestResultFailures and assoicated
273 # FailureReasons. The latter is useful since the failure reason
274 # description may evolve along side the invidiual failure analysis.
275 self._oDb.execute('( SELECT trf.tsEffective AS tsEffectiveChangeLog,\n'
276 ' trf.uidAuthor AS uidAuthorChangeLog,\n'
277 ' trf.*,\n'
278 ' fr.*\n'
279 ' FROM TestResultFailures trf,\n'
280 ' FailureReasons fr\n'
281 ' WHERE trf.idTestResult = %s\n'
282 ' AND trf.tsEffective <= %s\n'
283 ' AND trf.idFailureReason = fr.idFailureReason\n'
284 ' AND fr.tsEffective <= trf.tsEffective\n'
285 ' AND fr.tsExpire > trf.tsEffective\n'
286 ')\n'
287 'UNION\n'
288 '( SELECT fr.tsEffective AS tsEffectiveChangeLog,\n'
289 ' fr.uidAuthor AS uidAuthorChangeLog,\n'
290 ' trf.*,\n'
291 ' fr.*\n'
292 ' FROM TestResultFailures trf,\n'
293 ' FailureReasons fr\n'
294 ' WHERE trf.idTestResult = %s\n'
295 ' AND trf.tsEffective <= %s\n'
296 ' AND trf.idFailureReason = fr.idFailureReason\n'
297 ' AND fr.tsEffective > trf.tsEffective\n'
298 ' AND fr.tsEffective < trf.tsExpire\n'
299 ')\n'
300 'ORDER BY tsEffectiveChangeLog DESC\n'
301 'LIMIT %s OFFSET %s\n'
302 , ( idTestResult, tsNow, idTestResult, tsNow, cMaxRows + 1, iStart, ));
303
304 aaoRows = [];
305 for aoChange in self._oDb.fetchAll():
306 oTrf = TestResultFailureDataEx().initFromDbRow(aoChange[2:]);
307 oFr = FailureReasonData().initFromDbRow(aoChange[(2+TestResultFailureData.kcDbColumns):]);
308 oTrf.oFailureReason = oFr;
309 aaoRows.append([aoChange[0], aoChange[1], oTrf, oFr]);
310
311 # 2. Calculate the changes.
312 oFailureCategoryLogic = None;
313 aoEntries = [];
314 for i in xrange(0, len(aaoRows) - 1):
315 aoNew = aaoRows[i];
316 aoOld = aaoRows[i + 1];
317
318 aoChanges = [];
319 oNew = aoNew[2];
320 oOld = aoOld[2];
321 for sAttr in oNew.getDataAttributes():
322 if sAttr not in [ 'tsEffective', 'tsExpire', 'uidAuthor', 'oFailureReason', 'oAuthor' ]:
323 oOldAttr = getattr(oOld, sAttr);
324 oNewAttr = getattr(oNew, sAttr);
325 if oOldAttr != oNewAttr:
326 if sAttr == 'idFailureReason':
327 oNewAttr = '%s (%s)' % (oNewAttr, oNew.oFailureReason.sShort, );
328 oOldAttr = '%s (%s)' % (oOldAttr, oOld.oFailureReason.sShort, );
329 aoChanges.append(AttributeChangeEntry(sAttr, oNewAttr, oOldAttr, str(oNewAttr), str(oOldAttr)));
330 if oOld.idFailureReason == oNew.idFailureReason:
331 oNew = aoNew[3];
332 oOld = aoOld[3];
333 for sAttr in oNew.getDataAttributes():
334 if sAttr not in [ 'tsEffective', 'tsExpire', 'uidAuthor', ]:
335 oOldAttr = getattr(oOld, sAttr);
336 oNewAttr = getattr(oNew, sAttr);
337 if oOldAttr != oNewAttr:
338 if sAttr == 'idFailureCategory':
339 if oFailureCategoryLogic is None:
340 from testmanager.core.failurecategory import FailureCategoryLogic;
341 oFailureCategoryLogic = FailureCategoryLogic(self._oDb);
342 oCat = oFailureCategoryLogic.cachedLookup(oNewAttr);
343 if oCat is not None:
344 oNewAttr = '%s (%s)' % (oNewAttr, oCat.sShort, );
345 oCat = oFailureCategoryLogic.cachedLookup(oOldAttr);
346 if oCat is not None:
347 oOldAttr = '%s (%s)' % (oOldAttr, oCat.sShort, );
348 aoChanges.append(AttributeChangeEntry(sAttr, oNewAttr, oOldAttr, str(oNewAttr), str(oOldAttr)));
349
350
351 tsExpire = aaoRows[i - 1][0] if i > 0 else aoNew[2].tsExpire;
352 aoEntries.append(ChangeLogEntry(aoNew[1], None, aoNew[0], tsExpire, aoNew[2], aoOld[2], aoChanges));
353
354 # If we're at the end of the log, add the initial entry.
355 if len(aaoRows) <= cMaxRows and len(aaoRows) > 0:
356 aoNew = aaoRows[-1];
357 tsExpire = aaoRows[-1 - 1][0] if len(aaoRows) > 1 else aoNew[2].tsExpire;
358 aoEntries.append(ChangeLogEntry(aoNew[1], None, aoNew[0], tsExpire, aoNew[2], None, []));
359
360 return (UserAccountLogic(self._oDb).resolveChangeLogAuthors(aoEntries), len(aaoRows) > cMaxRows);
361
362
363 def getById(self, idTestResult):
364 """Get Test result failure reason data by idTestResult"""
365
366 self._oDb.execute('SELECT *\n'
367 'FROM TestResultFailures\n'
368 'WHERE tsExpire = \'infinity\'::timestamp\n'
369 ' AND idTestResult = %s;', (idTestResult,))
370 aRows = self._oDb.fetchAll()
371 if len(aRows) not in (0, 1):
372 raise self._oDb.integrityException(
373 'Found more than one failure reasons with the same credentials. Database structure is corrupted.')
374 try:
375 return TestResultFailureData().initFromDbRow(aRows[0])
376 except IndexError:
377 return None
378
379 def addEntry(self, oData, uidAuthor, fCommit = False):
380 """
381 Add a test result failure reason record.
382 """
383
384 #
385 # Validate inputs and read in the old(/current) data.
386 #
387 assert isinstance(oData, TestResultFailureData);
388 dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_AddForeignId);
389 if len(dErrors) > 0:
390 raise TMInvalidData('editEntry invalid input: %s' % (dErrors,));
391
392 # Check if it exist first (we're adding, not editing, collisions not allowed).
393 oOldData = self.getById(oData.idTestResult);
394 if oOldData is not None:
395 raise TMRowAlreadyExists('TestResult %d already have a failure reason associated with it:'
396 '%s\n'
397 'Perhaps someone else beat you to it? Or did you try resubmit?'
398 % (oData.idTestResult, oOldData));
399 oData = self._resolveSetTestIdIfMissing(oData);
400
401 #
402 # Add record.
403 #
404 self._readdEntry(uidAuthor, oData);
405 self._oDb.maybeCommit(fCommit);
406 return True;
407
408 def editEntry(self, oData, uidAuthor, fCommit = False):
409 """
410 Modifies a test result failure reason.
411 """
412
413 #
414 # Validate inputs and read in the old(/current) data.
415 #
416 assert isinstance(oData, TestResultFailureData);
417 dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Edit);
418 if len(dErrors) > 0:
419 raise TMInvalidData('editEntry invalid input: %s' % (dErrors,));
420
421 oOldData = self.getById(oData.idTestResult)
422 oData.idTestSet = oOldData.idTestSet;
423
424 #
425 # Update the data that needs updating.
426 #
427 if not oData.isEqualEx(oOldData, [ 'tsEffective', 'tsExpire', 'uidAuthor', ]):
428 self._historizeEntry(oData.idTestResult);
429 self._readdEntry(uidAuthor, oData);
430 self._oDb.maybeCommit(fCommit);
431 return True;
432
433
434 def removeEntry(self, uidAuthor, idTestResult, fCascade = False, fCommit = False):
435 """
436 Deletes a test result failure reason.
437 """
438 _ = fCascade; # Not applicable.
439
440 oData = self.getById(idTestResult)
441 (tsCur, tsCurMinusOne) = self._oDb.getCurrentTimestamps();
442 if oData.tsEffective != tsCur and oData.tsEffective != tsCurMinusOne:
443 self._historizeEntry(idTestResult, tsCurMinusOne);
444 self._readdEntry(uidAuthor, oData, tsCurMinusOne);
445 self._historizeEntry(idTestResult);
446 self._oDb.execute('UPDATE TestResultFailures\n'
447 'SET tsExpire = CURRENT_TIMESTAMP\n'
448 'WHERE idTestResult = %s\n'
449 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
450 , (idTestResult,));
451 self._oDb.maybeCommit(fCommit);
452 return True;
453
454 #
455 # Helpers.
456 #
457
458 def _readdEntry(self, uidAuthor, oData, tsEffective = None):
459 """
460 Re-adds the TestResultFailure entry. Used by addEntry, editEntry and removeEntry.
461 """
462 if tsEffective is None:
463 tsEffective = self._oDb.getCurrentTimestamp();
464 self._oDb.execute('INSERT INTO TestResultFailures (\n'
465 ' uidAuthor,\n'
466 ' tsEffective,\n'
467 ' idTestResult,\n'
468 ' idTestSet,\n'
469 ' idFailureReason,\n'
470 ' sComment)\n'
471 'VALUES (%s, %s, %s, %s, %s, %s)\n'
472 , ( uidAuthor,
473 tsEffective,
474 oData.idTestResult,
475 oData.idTestSet,
476 oData.idFailureReason,
477 oData.sComment,) );
478 return True;
479
480
481 def _historizeEntry(self, idTestResult, tsExpire = None):
482 """ Historizes the current entry. """
483 if tsExpire is None:
484 tsExpire = self._oDb.getCurrentTimestamp();
485 self._oDb.execute('UPDATE TestResultFailures\n'
486 'SET tsExpire = %s\n'
487 'WHERE idTestResult = %s\n'
488 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
489 , (tsExpire, idTestResult,));
490 return True;
491
492
493 def _resolveSetTestIdIfMissing(self, oData):
494 """ Resolve any missing idTestSet reference (it's a duplicate for speed efficiency). """
495 if oData.idTestSet is None and oData.idTestResult is not None:
496 self._oDb.execute('SELECT idTestSet FROM TestResults WHERE idTestResult = %s', (oData.idTestResult,));
497 oData.idTestSet = self._oDb.fetchOne()[0];
498 return oData;
499
500
501
502#
503# Unit testing.
504#
505
506# pylint: disable=C0111
507class TestResultFailureDataTestCase(ModelDataBaseTestCase):
508 def setUp(self):
509 self.aoSamples = [TestResultFailureData(),];
510
511if __name__ == '__main__':
512 unittest.main();
513 # not reached.
514
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