VirtualBox

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

Last change on this file since 78697 was 76553, checked in by vboxsync, 6 years ago

scm --update-copyright-year

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