VirtualBox

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

Last change on this file since 61278 was 61278, checked in by vboxsync, 9 years ago

split TestResultFailures related bits out of testresults.py. removed some lint.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 20.5 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: testresultfailures.py 61278 2016-05-29 16:52:40Z 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-2015 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: 61278 $"
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
107class TestResultFailureDataEx(TestResultFailureData):
108 """
109 Extends TestResultFailureData by resolving reasons and user.
110 """
111
112 def __init__(self):
113 TestResultFailureData.__init__(self);
114 self.oFailureReason = None;
115 self.oAuthor = None;
116
117 def initFromDbRowEx(self, aoRow, oFailureReasonLogic, oUserAccountLogic):
118 """
119 Reinitialize from a query like this:
120 SELECT TestResultFiles.*,
121 StrTabFile.sValue AS sFile,
122 StrTabDesc.sValue AS sDescription
123 StrTabKind.sValue AS sKind,
124 StrTabMime.sValue AS sMime,
125 FROM ...
126
127 Return self. Raises exception if no row.
128 """
129 self.initFromDbRow(aoRow);
130 self.oFailureReason = oFailureReasonLogic.cachedLookup(self.idFailureReason);
131 self.oAuthor = oUserAccountLogic.cachedLookup(self.uidAuthor);
132 return self;
133
134
135class TestResultListingData(ModelDataBase): # pylint: disable=R0902
136 """
137 Test case result data representation for table listing
138 """
139
140 def __init__(self):
141 """Initialize"""
142 ModelDataBase.__init__(self)
143
144 self.idTestSet = None
145
146 self.idBuildCategory = None;
147 self.sProduct = None
148 self.sRepository = None;
149 self.sBranch = None
150 self.sType = None
151 self.idBuild = None;
152 self.sVersion = None;
153 self.iRevision = None
154
155 self.sOs = None;
156 self.sOsVersion = None;
157 self.sArch = None;
158 self.sCpuVendor = None;
159 self.sCpuName = None;
160 self.cCpus = None;
161 self.fCpuHwVirt = None;
162 self.fCpuNestedPaging = None;
163 self.fCpu64BitGuest = None;
164 self.idTestBox = None
165 self.sTestBoxName = None
166
167 self.tsCreated = None
168 self.tsElapsed = None
169 self.enmStatus = None
170 self.cErrors = None;
171
172 self.idTestCase = None
173 self.sTestCaseName = None
174 self.sBaseCmd = None
175 self.sArgs = None
176 self.sSubName = None;
177
178 self.idBuildTestSuite = None;
179 self.iRevisionTestSuite = None;
180
181 self.oFailureReason = None;
182 self.oFailureReasonAssigner = None;
183 self.tsFailureReasonAssigned = None;
184 self.sFailureReasonComment = None;
185
186 def initFromDbRowEx(self, aoRow, oFailureReasonLogic, oUserAccountLogic):
187 """
188 Reinitialize from a database query.
189 Return self. Raises exception if no row.
190 """
191 if aoRow is None:
192 raise TMRowNotFound('Test result record not found.')
193
194 self.idTestSet = aoRow[0];
195
196 self.idBuildCategory = aoRow[1];
197 self.sProduct = aoRow[2];
198 self.sRepository = aoRow[3];
199 self.sBranch = aoRow[4];
200 self.sType = aoRow[5];
201 self.idBuild = aoRow[6];
202 self.sVersion = aoRow[7];
203 self.iRevision = aoRow[8];
204
205 self.sOs = aoRow[9];
206 self.sOsVersion = aoRow[10];
207 self.sArch = aoRow[11];
208 self.sCpuVendor = aoRow[12];
209 self.sCpuName = aoRow[13];
210 self.cCpus = aoRow[14];
211 self.fCpuHwVirt = aoRow[15];
212 self.fCpuNestedPaging = aoRow[16];
213 self.fCpu64BitGuest = aoRow[17];
214 self.idTestBox = aoRow[18];
215 self.sTestBoxName = aoRow[19];
216
217 self.tsCreated = aoRow[20];
218 self.tsElapsed = aoRow[21];
219 self.enmStatus = aoRow[22];
220 self.cErrors = aoRow[23];
221
222 self.idTestCase = aoRow[24];
223 self.sTestCaseName = aoRow[25];
224 self.sBaseCmd = aoRow[26];
225 self.sArgs = aoRow[27];
226 self.sSubName = aoRow[28];
227
228 self.idBuildTestSuite = aoRow[29];
229 self.iRevisionTestSuite = aoRow[30];
230
231 self.oFailureReason = None;
232 if aoRow[31] is not None:
233 self.oFailureReason = oFailureReasonLogic.cachedLookup(aoRow[31]);
234 self.oFailureReasonAssigner = None;
235 if aoRow[32] is not None:
236 self.oFailureReasonAssigner = oUserAccountLogic.cachedLookup(aoRow[32]);
237 self.tsFailureReasonAssigned = aoRow[33];
238 self.sFailureReasonComment = aoRow[34];
239
240 return self
241
242
243
244class TestResultFailureLogic(ModelLogicBase): # pylint: disable=R0903
245 """
246 Test result failure reason logic.
247 """
248
249 def __init__(self, oDb):
250 ModelLogicBase.__init__(self, oDb)
251
252 def fetchForChangeLog(self, idTestResult, iStart, cMaxRows, tsNow): # pylint: disable=R0914
253 """
254 Fetches change log entries for a failure reason.
255
256 Returns an array of ChangeLogEntry instance and an indicator whether
257 there are more entries.
258 Raises exception on error.
259 """
260
261 if tsNow is None:
262 tsNow = self._oDb.getCurrentTimestamp();
263
264 # 1. Get a list of the changes from both TestResultFailures and assoicated
265 # FailureReasons. The latter is useful since the failure reason
266 # description may evolve along side the invidiual failure analysis.
267 self._oDb.execute('( SELECT trf.tsEffective AS tsEffectiveChangeLog,\n'
268 ' trf.uidAuthor AS uidAuthorChangeLog,\n'
269 ' trf.*,\n'
270 ' fr.*\n'
271 ' FROM TestResultFailures trf,\n'
272 ' FailureReasons fr\n'
273 ' WHERE trf.idTestResult = %s\n'
274 ' AND trf.tsEffective <= %s\n'
275 ' AND trf.idFailureReason = fr.idFailureReason\n'
276 ' AND fr.tsEffective <= trf.tsEffective\n'
277 ' AND fr.tsExpire > trf.tsEffective\n'
278 ')\n'
279 'UNION\n'
280 '( SELECT fr.tsEffective AS tsEffectiveChangeLog,\n'
281 ' fr.uidAuthor AS uidAuthorChangeLog,\n'
282 ' trf.*,\n'
283 ' fr.*\n'
284 ' FROM TestResultFailures trf,\n'
285 ' FailureReasons fr\n'
286 ' WHERE trf.idTestResult = %s\n'
287 ' AND trf.tsEffective <= %s\n'
288 ' AND trf.idFailureReason = fr.idFailureReason\n'
289 ' AND fr.tsEffective > trf.tsEffective\n'
290 ' AND fr.tsEffective < trf.tsExpire\n'
291 ')\n'
292 'ORDER BY tsEffectiveChangeLog DESC\n'
293 'LIMIT %s OFFSET %s\n'
294 , ( idTestResult, tsNow, idTestResult, tsNow, cMaxRows + 1, iStart, ));
295
296 aaoRows = [];
297 for aoChange in self._oDb.fetchAll():
298 oTrf = TestResultFailureDataEx().initFromDbRow(aoChange[2:]);
299 oFr = FailureReasonData().initFromDbRow(aoChange[(2+TestResultFailureData.kcDbColumns):]);
300 oTrf.oFailureReason = oFr;
301 aaoRows.append([aoChange[0], aoChange[1], oTrf, oFr]);
302
303 # 2. Calculate the changes.
304 oFailureCategoryLogic = None;
305 aoEntries = [];
306 for i in xrange(0, len(aaoRows) - 1):
307 aoNew = aaoRows[i];
308 aoOld = aaoRows[i + 1];
309
310 aoChanges = [];
311 oNew = aoNew[2];
312 oOld = aoOld[2];
313 for sAttr in oNew.getDataAttributes():
314 if sAttr not in [ 'tsEffective', 'tsExpire', 'uidAuthor', 'oFailureReason', 'oAuthor' ]:
315 oOldAttr = getattr(oOld, sAttr);
316 oNewAttr = getattr(oNew, sAttr);
317 if oOldAttr != oNewAttr:
318 if sAttr == 'idFailureReason':
319 oNewAttr = '%s (%s)' % (oNewAttr, oNew.oFailureReason.sShort, );
320 oOldAttr = '%s (%s)' % (oOldAttr, oOld.oFailureReason.sShort, );
321 aoChanges.append(AttributeChangeEntry(sAttr, oNewAttr, oOldAttr, str(oNewAttr), str(oOldAttr)));
322 if oOld.idFailureReason == oNew.idFailureReason:
323 oNew = aoNew[3];
324 oOld = aoOld[3];
325 for sAttr in oNew.getDataAttributes():
326 if sAttr not in [ 'tsEffective', 'tsExpire', 'uidAuthor', ]:
327 oOldAttr = getattr(oOld, sAttr);
328 oNewAttr = getattr(oNew, sAttr);
329 if oOldAttr != oNewAttr:
330 if sAttr == 'idFailureCategory':
331 if oFailureCategoryLogic is None:
332 from testmanager.core.failurecategory import FailureCategoryLogic;
333 oFailureCategoryLogic = FailureCategoryLogic(self._oDb);
334 oCat = oFailureCategoryLogic.cachedLookup(oNewAttr);
335 if oCat is not None:
336 oNewAttr = '%s (%s)' % (oNewAttr, oCat.sShort, );
337 oCat = oFailureCategoryLogic.cachedLookup(oOldAttr);
338 if oCat is not None:
339 oOldAttr = '%s (%s)' % (oOldAttr, oCat.sShort, );
340 aoChanges.append(AttributeChangeEntry(sAttr, oNewAttr, oOldAttr, str(oNewAttr), str(oOldAttr)));
341
342
343 tsExpire = aaoRows[i - 1][0] if i > 0 else aoNew[2].tsExpire;
344 aoEntries.append(ChangeLogEntry(aoNew[1], None, aoNew[0], tsExpire, aoNew[2], aoOld[2], aoChanges));
345
346 # If we're at the end of the log, add the initial entry.
347 if len(aaoRows) <= cMaxRows and len(aaoRows) > 0:
348 aoNew = aaoRows[-1];
349 tsExpire = aaoRows[-1 - 1][0] if len(aaoRows) > 1 else aoNew[2].tsExpire;
350 aoEntries.append(ChangeLogEntry(aoNew[1], None, aoNew[0], tsExpire, aoNew[2], None, []));
351
352 return (UserAccountLogic(self._oDb).resolveChangeLogAuthors(aoEntries), len(aaoRows) > cMaxRows);
353
354
355 def getById(self, idTestResult):
356 """Get Test result failure reason data by idTestResult"""
357
358 self._oDb.execute('SELECT *\n'
359 'FROM TestResultFailures\n'
360 'WHERE tsExpire = \'infinity\'::timestamp\n'
361 ' AND idTestResult = %s;', (idTestResult,))
362 aRows = self._oDb.fetchAll()
363 if len(aRows) not in (0, 1):
364 raise self._oDb.integrityException(
365 'Found more than one failure reasons with the same credentials. Database structure is corrupted.')
366 try:
367 return TestResultFailureData().initFromDbRow(aRows[0])
368 except IndexError:
369 return None
370
371 def addEntry(self, oData, uidAuthor, fCommit = False):
372 """
373 Add a test result failure reason record.
374 """
375
376 #
377 # Validate inputs and read in the old(/current) data.
378 #
379 assert isinstance(oData, TestResultFailureData);
380 dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_AddForeignId);
381 if len(dErrors) > 0:
382 raise TMInvalidData('editEntry invalid input: %s' % (dErrors,));
383
384 # Check if it exist first (we're adding, not editing, collisions not allowed).
385 oOldData = self.getById(oData.idTestResult);
386 if oOldData is not None:
387 raise TMRowAlreadyExists('TestResult %d already have a failure reason associated with it:'
388 '%s\n'
389 'Perhaps someone else beat you to it? Or did you try resubmit?'
390 % (oData.idTestResult, oOldData));
391 oData = self._resolveSetTestIdIfMissing(oData);
392
393 #
394 # Add record.
395 #
396 self._readdEntry(uidAuthor, oData);
397 self._oDb.maybeCommit(fCommit);
398 return True;
399
400 def editEntry(self, oData, uidAuthor, fCommit = False):
401 """
402 Modifies a test result failure reason.
403 """
404
405 #
406 # Validate inputs and read in the old(/current) data.
407 #
408 assert isinstance(oData, TestResultFailureData);
409 dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Edit);
410 if len(dErrors) > 0:
411 raise TMInvalidData('editEntry invalid input: %s' % (dErrors,));
412
413 oOldData = self.getById(oData.idTestResult)
414 oData.idTestSet = oOldData.idTestSet;
415
416 #
417 # Update the data that needs updating.
418 #
419 if not oData.isEqualEx(oOldData, [ 'tsEffective', 'tsExpire', 'uidAuthor', ]):
420 self._historizeEntry(oData.idTestResult);
421 self._readdEntry(uidAuthor, oData);
422 self._oDb.maybeCommit(fCommit);
423 return True;
424
425
426 def removeEntry(self, uidAuthor, idTestResult, fCascade = False, fCommit = False):
427 """
428 Deletes a test result failure reason.
429 """
430 _ = fCascade; # Not applicable.
431
432 oData = self.getById(idTestResult)
433 (tsCur, tsCurMinusOne) = self._oDb.getCurrentTimestamps();
434 if oData.tsEffective != tsCur and oData.tsEffective != tsCurMinusOne:
435 self._historizeEntry(idTestResult, tsCurMinusOne);
436 self._readdEntry(uidAuthor, oData, tsCurMinusOne);
437 self._historizeEntry(idTestResult);
438 self._oDb.execute('UPDATE TestResultFailures\n'
439 'SET tsExpire = CURRENT_TIMESTAMP\n'
440 'WHERE idTestResult = %s\n'
441 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
442 , (idTestResult,));
443 self._oDb.maybeCommit(fCommit);
444 return True;
445
446 #
447 # Helpers.
448 #
449
450 def _readdEntry(self, uidAuthor, oData, tsEffective = None):
451 """
452 Re-adds the TestResultFailure entry. Used by addEntry, editEntry and removeEntry.
453 """
454 if tsEffective is None:
455 tsEffective = self._oDb.getCurrentTimestamp();
456 self._oDb.execute('INSERT INTO TestResultFailures (\n'
457 ' uidAuthor,\n'
458 ' tsEffective,\n'
459 ' idTestResult,\n'
460 ' idTestSet,\n'
461 ' idFailureReason,\n'
462 ' sComment)\n'
463 'VALUES (%s, %s, %s, %s, %s, %s)\n'
464 , ( uidAuthor,
465 tsEffective,
466 oData.idTestResult,
467 oData.idTestSet,
468 oData.idFailureReason,
469 oData.sComment,) );
470 return True;
471
472
473 def _historizeEntry(self, idTestResult, tsExpire = None):
474 """ Historizes the current entry. """
475 if tsExpire is None:
476 tsExpire = self._oDb.getCurrentTimestamp();
477 self._oDb.execute('UPDATE TestResultFailures\n'
478 'SET tsExpire = %s\n'
479 'WHERE idTestResult = %s\n'
480 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
481 , (tsExpire, idTestResult,));
482 return True;
483
484
485 def _resolveSetTestIdIfMissing(self, oData):
486 """ Resolve any missing idTestSet reference (it's a duplicate for speed efficiency). """
487 if oData.idTestSet is None and oData.idTestResult is not None:
488 self._oDb.execute('SELECT idTestSet FROM TestResults WHERE idTestResult = %s', (oData.idTestResult,));
489 oData.idTestSet = self._oDb.fetchOne()[0];
490 return oData;
491
492
493
494#
495# Unit testing.
496#
497
498# pylint: disable=C0111
499class TestResultFailureDataTestCase(ModelDataBaseTestCase):
500 def setUp(self):
501 self.aoSamples = [TestResultFailureData(),];
502
503if __name__ == '__main__':
504 unittest.main();
505 # not reached.
506
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