VirtualBox

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

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

virtual test sheriff: started on the real job. can detect a few simple guru meditations.

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