VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testmanager/core/testresults.py@ 80128

Last change on this file since 80128 was 80128, checked in by vboxsync, 5 years ago

vsheriff: Better detect VM process SIGSEGVs. [3rd try]

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 136.3 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: testresults.py 80128 2019-08-05 11:06:37Z vboxsync $
3# pylint: disable=too-many-lines
4
5## @todo Rename this file to testresult.py!
6
7"""
8Test Manager - Fetch test results.
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: 80128 $"
33
34
35# Standard python imports.
36import sys;
37import unittest;
38
39# Validation Kit imports.
40from common import constants;
41from testmanager import config;
42from testmanager.core.base import ModelDataBase, ModelLogicBase, ModelDataBaseTestCase, ModelFilterBase, \
43 FilterCriterion, FilterCriterionValueAndDescription, \
44 TMExceptionBase, TMTooManyRows, TMRowNotFound;
45from testmanager.core.testgroup import TestGroupData;
46from testmanager.core.build import BuildDataEx, BuildCategoryData;
47from testmanager.core.failurereason import FailureReasonLogic;
48from testmanager.core.testbox import TestBoxData, TestBoxLogic;
49from testmanager.core.testcase import TestCaseData;
50from testmanager.core.schedgroup import SchedGroupData, SchedGroupLogic;
51from testmanager.core.systemlog import SystemLogData, SystemLogLogic;
52from testmanager.core.testresultfailures import TestResultFailureDataEx;
53from testmanager.core.useraccount import UserAccountLogic;
54
55# Python 3 hacks:
56if sys.version_info[0] >= 3:
57 long = int; # pylint: disable=redefined-builtin,invalid-name
58
59
60class TestResultData(ModelDataBase):
61 """
62 Test case execution result data
63 """
64
65 ## @name TestStatus_T
66 # @{
67 ksTestStatus_Running = 'running';
68 ksTestStatus_Success = 'success';
69 ksTestStatus_Skipped = 'skipped';
70 ksTestStatus_BadTestBox = 'bad-testbox';
71 ksTestStatus_Aborted = 'aborted';
72 ksTestStatus_Failure = 'failure';
73 ksTestStatus_TimedOut = 'timed-out';
74 ksTestStatus_Rebooted = 'rebooted';
75 ## @}
76
77 ## List of relatively harmless (to testgroup/case) statuses.
78 kasHarmlessTestStatuses = [ ksTestStatus_Skipped, ksTestStatus_BadTestBox, ksTestStatus_Aborted, ];
79 ## List of bad statuses.
80 kasBadTestStatuses = [ ksTestStatus_Failure, ksTestStatus_TimedOut, ksTestStatus_Rebooted, ];
81
82
83 ksIdAttr = 'idTestResult';
84
85 ksParam_idTestResult = 'TestResultData_idTestResult';
86 ksParam_idTestResultParent = 'TestResultData_idTestResultParent';
87 ksParam_idTestSet = 'TestResultData_idTestSet';
88 ksParam_tsCreated = 'TestResultData_tsCreated';
89 ksParam_tsElapsed = 'TestResultData_tsElapsed';
90 ksParam_idStrName = 'TestResultData_idStrName';
91 ksParam_cErrors = 'TestResultData_cErrors';
92 ksParam_enmStatus = 'TestResultData_enmStatus';
93 ksParam_iNestingDepth = 'TestResultData_iNestingDepth';
94 kasValidValues_enmStatus = [
95 ksTestStatus_Running,
96 ksTestStatus_Success,
97 ksTestStatus_Skipped,
98 ksTestStatus_BadTestBox,
99 ksTestStatus_Aborted,
100 ksTestStatus_Failure,
101 ksTestStatus_TimedOut,
102 ksTestStatus_Rebooted
103 ];
104
105
106 def __init__(self):
107 ModelDataBase.__init__(self)
108 self.idTestResult = None
109 self.idTestResultParent = None
110 self.idTestSet = None
111 self.tsCreated = None
112 self.tsElapsed = None
113 self.idStrName = None
114 self.cErrors = 0;
115 self.enmStatus = None
116 self.iNestingDepth = None
117
118 def initFromDbRow(self, aoRow):
119 """
120 Reinitialize from a SELECT * FROM TestResults.
121 Return self. Raises exception if no row.
122 """
123 if aoRow is None:
124 raise TMRowNotFound('Test result record not found.')
125
126 self.idTestResult = aoRow[0]
127 self.idTestResultParent = aoRow[1]
128 self.idTestSet = aoRow[2]
129 self.tsCreated = aoRow[3]
130 self.tsElapsed = aoRow[4]
131 self.idStrName = aoRow[5]
132 self.cErrors = aoRow[6]
133 self.enmStatus = aoRow[7]
134 self.iNestingDepth = aoRow[8]
135 return self;
136
137 def initFromDbWithId(self, oDb, idTestResult, tsNow = None, sPeriodBack = None):
138 """
139 Initialize from the database, given the ID of a row.
140 """
141 _ = tsNow;
142 _ = sPeriodBack;
143 oDb.execute('SELECT *\n'
144 'FROM TestResults\n'
145 'WHERE idTestResult = %s\n'
146 , ( idTestResult,));
147 aoRow = oDb.fetchOne()
148 if aoRow is None:
149 raise TMRowNotFound('idTestResult=%s not found' % (idTestResult,));
150 return self.initFromDbRow(aoRow);
151
152 def isFailure(self):
153 """ Check if it's a real failure. """
154 return self.enmStatus in self.kasBadTestStatuses;
155
156
157class TestResultDataEx(TestResultData):
158 """
159 Extended test result data class.
160
161 This is intended for use as a node in a result tree. This is not intended
162 for serialization to parameters or vice versa. Use TestResultLogic to
163 construct the tree.
164 """
165
166 def __init__(self):
167 TestResultData.__init__(self)
168 self.sName = None; # idStrName resolved.
169 self.oParent = None; # idTestResultParent within the tree.
170
171 self.aoChildren = []; # TestResultDataEx;
172 self.aoValues = []; # TestResultValueDataEx;
173 self.aoMsgs = []; # TestResultMsgDataEx;
174 self.aoFiles = []; # TestResultFileDataEx;
175 self.oReason = None; # TestResultReasonDataEx;
176
177 def initFromDbRow(self, aoRow):
178 """
179 Initialize from a query like this:
180 SELECT TestResults.*, TestResultStrTab.sValue
181 FROM TestResults, TestResultStrTab
182 WHERE TestResultStrTab.idStr = TestResults.idStrName
183
184 Note! The caller is expected to fetch children, values, failure
185 details, and files.
186 """
187 self.sName = None;
188 self.oParent = None;
189 self.aoChildren = [];
190 self.aoValues = [];
191 self.aoMsgs = [];
192 self.aoFiles = [];
193 self.oReason = None;
194
195 TestResultData.initFromDbRow(self, aoRow);
196
197 self.sName = aoRow[9];
198 return self;
199
200 def deepCountErrorContributers(self):
201 """
202 Counts how many test result instances actually contributed to cErrors.
203 """
204
205 # Check each child (if any).
206 cChanges = 0;
207 cChildErrors = 0;
208 for oChild in self.aoChildren:
209 if oChild.cErrors > 0:
210 cChildErrors += oChild.cErrors;
211 cChanges += oChild.deepCountErrorContributers();
212
213 # Did we contribute as well?
214 if self.cErrors > cChildErrors:
215 cChanges += 1;
216 return cChanges;
217
218 def getListOfFailures(self):
219 """
220 Get a list of test results instances actually contributing to cErrors.
221
222 Returns a list of TestResultDataEx instances from this tree. (shared!)
223 """
224 # Check each child (if any).
225 aoRet = [];
226 cChildErrors = 0;
227 for oChild in self.aoChildren:
228 if oChild.cErrors > 0:
229 cChildErrors += oChild.cErrors;
230 aoRet.extend(oChild.getListOfFailures());
231
232 # Did we contribute as well?
233 if self.cErrors > cChildErrors:
234 aoRet.append(self);
235
236 return aoRet;
237
238 def getListOfLogFilesByKind(self, asKinds):
239 """
240 Get a list of test results instances actually contributing to cErrors.
241
242 Returns a list of TestResultFileDataEx instances from this tree. (shared!)
243 """
244 aoRet = [];
245
246 # Check the children first.
247 for oChild in self.aoChildren:
248 aoRet.extend(oChild.getListOfLogFilesByKind(asKinds));
249
250 # Check our own files next.
251 for oFile in self.aoFiles:
252 if oFile.sKind in asKinds:
253 aoRet.append(oFile);
254
255 return aoRet;
256
257 def getFullName(self):
258 """ Constructs the full name of this test result. """
259 if self.oParent is None:
260 return self.sName;
261 return self.oParent.getFullName() + ' / ' + self.sName;
262
263
264
265class TestResultValueData(ModelDataBase):
266 """
267 Test result value data.
268 """
269
270 ksIdAttr = 'idTestResultValue';
271
272 ksParam_idTestResultValue = 'TestResultValue_idTestResultValue';
273 ksParam_idTestResult = 'TestResultValue_idTestResult';
274 ksParam_idTestSet = 'TestResultValue_idTestSet';
275 ksParam_tsCreated = 'TestResultValue_tsCreated';
276 ksParam_idStrName = 'TestResultValue_idStrName';
277 ksParam_lValue = 'TestResultValue_lValue';
278 ksParam_iUnit = 'TestResultValue_iUnit';
279
280 kasAllowNullAttributes = [ 'idTestSet', ];
281
282 def __init__(self):
283 ModelDataBase.__init__(self)
284 self.idTestResultValue = None;
285 self.idTestResult = None;
286 self.idTestSet = None;
287 self.tsCreated = None;
288 self.idStrName = None;
289 self.lValue = None;
290 self.iUnit = 0;
291
292 def initFromDbRow(self, aoRow):
293 """
294 Reinitialize from a SELECT * FROM TestResultValues.
295 Return self. Raises exception if no row.
296 """
297 if aoRow is None:
298 raise TMRowNotFound('Test result value record not found.')
299
300 self.idTestResultValue = aoRow[0];
301 self.idTestResult = aoRow[1];
302 self.idTestSet = aoRow[2];
303 self.tsCreated = aoRow[3];
304 self.idStrName = aoRow[4];
305 self.lValue = aoRow[5];
306 self.iUnit = aoRow[6];
307 return self;
308
309
310class TestResultValueDataEx(TestResultValueData):
311 """
312 Extends TestResultValue by resolving the value name and unit string.
313 """
314
315 def __init__(self):
316 TestResultValueData.__init__(self)
317 self.sName = None;
318 self.sUnit = '';
319
320 def initFromDbRow(self, aoRow):
321 """
322 Reinitialize from a query like this:
323 SELECT TestResultValues.*, TestResultStrTab.sValue
324 FROM TestResultValues, TestResultStrTab
325 WHERE TestResultStrTab.idStr = TestResultValues.idStrName
326
327 Return self. Raises exception if no row.
328 """
329 TestResultValueData.initFromDbRow(self, aoRow);
330 self.sName = aoRow[7];
331 if self.iUnit < len(constants.valueunit.g_asNames):
332 self.sUnit = constants.valueunit.g_asNames[self.iUnit];
333 else:
334 self.sUnit = '<%d>' % (self.iUnit,);
335 return self;
336
337class TestResultMsgData(ModelDataBase):
338 """
339 Test result message data.
340 """
341
342 ksIdAttr = 'idTestResultMsg';
343
344 ksParam_idTestResultMsg = 'TestResultValue_idTestResultMsg';
345 ksParam_idTestResult = 'TestResultValue_idTestResult';
346 ksParam_idTestSet = 'TestResultValue_idTestSet';
347 ksParam_tsCreated = 'TestResultValue_tsCreated';
348 ksParam_idStrMsg = 'TestResultValue_idStrMsg';
349 ksParam_enmLevel = 'TestResultValue_enmLevel';
350
351 kasAllowNullAttributes = [ 'idTestSet', ];
352
353 kcDbColumns = 6
354
355 def __init__(self):
356 ModelDataBase.__init__(self)
357 self.idTestResultMsg = None;
358 self.idTestResult = None;
359 self.idTestSet = None;
360 self.tsCreated = None;
361 self.idStrMsg = None;
362 self.enmLevel = None;
363
364 def initFromDbRow(self, aoRow):
365 """
366 Reinitialize from a SELECT * FROM TestResultMsgs.
367 Return self. Raises exception if no row.
368 """
369 if aoRow is None:
370 raise TMRowNotFound('Test result value record not found.')
371
372 self.idTestResultMsg = aoRow[0];
373 self.idTestResult = aoRow[1];
374 self.idTestSet = aoRow[2];
375 self.tsCreated = aoRow[3];
376 self.idStrMsg = aoRow[4];
377 self.enmLevel = aoRow[5];
378 return self;
379
380class TestResultMsgDataEx(TestResultMsgData):
381 """
382 Extends TestResultMsg by resolving the message string.
383 """
384
385 def __init__(self):
386 TestResultMsgData.__init__(self)
387 self.sMsg = None;
388
389 def initFromDbRow(self, aoRow):
390 """
391 Reinitialize from a query like this:
392 SELECT TestResultMsg.*, TestResultStrTab.sValue
393 FROM TestResultMsg, TestResultStrTab
394 WHERE TestResultStrTab.idStr = TestResultMsgs.idStrName
395
396 Return self. Raises exception if no row.
397 """
398 TestResultMsgData.initFromDbRow(self, aoRow);
399 self.sMsg = aoRow[self.kcDbColumns];
400 return self;
401
402
403class TestResultFileData(ModelDataBase):
404 """
405 Test result message data.
406 """
407
408 ksIdAttr = 'idTestResultFile';
409
410 ksParam_idTestResultFile = 'TestResultFile_idTestResultFile';
411 ksParam_idTestResult = 'TestResultFile_idTestResult';
412 ksParam_tsCreated = 'TestResultFile_tsCreated';
413 ksParam_idStrFile = 'TestResultFile_idStrFile';
414 ksParam_idStrDescription = 'TestResultFile_idStrDescription';
415 ksParam_idStrKind = 'TestResultFile_idStrKind';
416 ksParam_idStrMime = 'TestResultFile_idStrMime';
417
418 ## @name Kind of files.
419 ## @{
420 ksKind_LogReleaseVm = 'log/release/vm';
421 ksKind_LogDebugVm = 'log/debug/vm';
422 ksKind_LogReleaseSvc = 'log/release/svc';
423 ksKind_LogDebugSvc = 'log/debug/svc';
424 ksKind_LogReleaseClient = 'log/release/client';
425 ksKind_LogDebugClient = 'log/debug/client';
426 ksKind_LogInstaller = 'log/installer';
427 ksKind_LogUninstaller = 'log/uninstaller';
428 ksKind_LogGuestKernel = 'log/guest/kernel';
429 ksKind_ProcessReportVm = 'process/report/vm';
430 ksKind_CrashReportVm = 'crash/report/vm';
431 ksKind_CrashDumpVm = 'crash/dump/vm';
432 ksKind_CrashReportSvc = 'crash/report/svc';
433 ksKind_CrashDumpSvc = 'crash/dump/svc';
434 ksKind_CrashReportClient = 'crash/report/client';
435 ksKind_CrashDumpClient = 'crash/dump/client';
436 ksKind_InfoCollection = 'info/collection';
437 ksKind_InfoVgaText = 'info/vgatext';
438 ksKind_MiscOther = 'misc/other';
439 ksKind_ScreenshotFailure = 'screenshot/failure';
440 ksKind_ScreenshotSuccesss = 'screenshot/success';
441 #kskind_ScreenCaptureFailure = 'screencapture/failure';
442 ## @}
443
444 kasKinds = [
445 ksKind_LogReleaseVm,
446 ksKind_LogDebugVm,
447 ksKind_LogReleaseSvc,
448 ksKind_LogDebugSvc,
449 ksKind_LogReleaseClient,
450 ksKind_LogDebugClient,
451 ksKind_LogInstaller,
452 ksKind_LogUninstaller,
453 ksKind_LogGuestKernel,
454 ksKind_ProcessReportVm,
455 ksKind_CrashReportVm,
456 ksKind_CrashDumpVm,
457 ksKind_CrashReportSvc,
458 ksKind_CrashDumpSvc,
459 ksKind_CrashReportClient,
460 ksKind_CrashDumpClient,
461 ksKind_InfoCollection,
462 ksKind_InfoVgaText,
463 ksKind_MiscOther,
464 ksKind_ScreenshotFailure,
465 ksKind_ScreenshotSuccesss,
466 #kskind_ScreenCaptureFailure,
467 ];
468
469 kasAllowNullAttributes = [ 'idTestSet', ];
470
471 kcDbColumns = 8
472
473 def __init__(self):
474 ModelDataBase.__init__(self)
475 self.idTestResultFile = None;
476 self.idTestResult = None;
477 self.idTestSet = None;
478 self.tsCreated = None;
479 self.idStrFile = None;
480 self.idStrDescription = None;
481 self.idStrKind = None;
482 self.idStrMime = None;
483
484 def initFromDbRow(self, aoRow):
485 """
486 Reinitialize from a SELECT * FROM TestResultFiles.
487 Return self. Raises exception if no row.
488 """
489 if aoRow is None:
490 raise TMRowNotFound('Test result file record not found.')
491
492 self.idTestResultFile = aoRow[0];
493 self.idTestResult = aoRow[1];
494 self.idTestSet = aoRow[2];
495 self.tsCreated = aoRow[3];
496 self.idStrFile = aoRow[4];
497 self.idStrDescription = aoRow[5];
498 self.idStrKind = aoRow[6];
499 self.idStrMime = aoRow[7];
500 return self;
501
502class TestResultFileDataEx(TestResultFileData):
503 """
504 Extends TestResultFile by resolving the strings.
505 """
506
507 def __init__(self):
508 TestResultFileData.__init__(self)
509 self.sFile = None;
510 self.sDescription = None;
511 self.sKind = None;
512 self.sMime = None;
513
514 def initFromDbRow(self, aoRow):
515 """
516 Reinitialize from a query like this:
517 SELECT TestResultFiles.*,
518 StrTabFile.sValue AS sFile,
519 StrTabDesc.sValue AS sDescription
520 StrTabKind.sValue AS sKind,
521 StrTabMime.sValue AS sMime,
522 FROM ...
523
524 Return self. Raises exception if no row.
525 """
526 TestResultFileData.initFromDbRow(self, aoRow);
527 self.sFile = aoRow[self.kcDbColumns];
528 self.sDescription = aoRow[self.kcDbColumns + 1];
529 self.sKind = aoRow[self.kcDbColumns + 2];
530 self.sMime = aoRow[self.kcDbColumns + 3];
531 return self;
532
533 def initFakeMainLog(self, oTestSet):
534 """
535 Reinitializes to represent the main.log object (not in DB).
536
537 Returns self.
538 """
539 self.idTestResultFile = 0;
540 self.idTestResult = oTestSet.idTestResult;
541 self.tsCreated = oTestSet.tsCreated;
542 self.idStrFile = None;
543 self.idStrDescription = None;
544 self.idStrKind = None;
545 self.idStrMime = None;
546
547 self.sFile = 'main.log';
548 self.sDescription = '';
549 self.sKind = 'log/main';
550 self.sMime = 'text/plain';
551 return self;
552
553 def isProbablyUtf8Encoded(self):
554 """
555 Checks if the file is likely to be UTF-8 encoded.
556 """
557 if self.sMime in [ 'text/plain', 'text/html' ]:
558 return True;
559 return False;
560
561 def getMimeWithEncoding(self):
562 """
563 Gets the MIME type with encoding if likely to be UTF-8.
564 """
565 if self.isProbablyUtf8Encoded():
566 return '%s; charset=utf-8' % (self.sMime,);
567 return self.sMime;
568
569
570
571class TestResultListingData(ModelDataBase): # pylint: disable=too-many-instance-attributes
572 """
573 Test case result data representation for table listing
574 """
575
576 class FailureReasonListingData(object):
577 """ Failure reason listing data """
578 def __init__(self):
579 self.oFailureReason = None;
580 self.oFailureReasonAssigner = None;
581 self.tsFailureReasonAssigned = None;
582 self.sFailureReasonComment = None;
583
584 def __init__(self):
585 """Initialize"""
586 ModelDataBase.__init__(self)
587
588 self.idTestSet = None
589
590 self.idBuildCategory = None;
591 self.sProduct = None
592 self.sRepository = None;
593 self.sBranch = None
594 self.sType = None
595 self.idBuild = None;
596 self.sVersion = None;
597 self.iRevision = None
598
599 self.sOs = None;
600 self.sOsVersion = None;
601 self.sArch = None;
602 self.sCpuVendor = None;
603 self.sCpuName = None;
604 self.cCpus = None;
605 self.fCpuHwVirt = None;
606 self.fCpuNestedPaging = None;
607 self.fCpu64BitGuest = None;
608 self.idTestBox = None
609 self.sTestBoxName = None
610
611 self.tsCreated = None
612 self.tsElapsed = None
613 self.enmStatus = None
614 self.cErrors = None;
615
616 self.idTestCase = None
617 self.sTestCaseName = None
618 self.sBaseCmd = None
619 self.sArgs = None
620 self.sSubName = None;
621
622 self.idBuildTestSuite = None;
623 self.iRevisionTestSuite = None;
624
625 self.aoFailureReasons = [];
626
627 def initFromDbRowEx(self, aoRow, oFailureReasonLogic, oUserAccountLogic):
628 """
629 Reinitialize from a database query.
630 Return self. Raises exception if no row.
631 """
632 if aoRow is None:
633 raise TMRowNotFound('Test result record not found.')
634
635 self.idTestSet = aoRow[0];
636
637 self.idBuildCategory = aoRow[1];
638 self.sProduct = aoRow[2];
639 self.sRepository = aoRow[3];
640 self.sBranch = aoRow[4];
641 self.sType = aoRow[5];
642 self.idBuild = aoRow[6];
643 self.sVersion = aoRow[7];
644 self.iRevision = aoRow[8];
645
646 self.sOs = aoRow[9];
647 self.sOsVersion = aoRow[10];
648 self.sArch = aoRow[11];
649 self.sCpuVendor = aoRow[12];
650 self.sCpuName = aoRow[13];
651 self.cCpus = aoRow[14];
652 self.fCpuHwVirt = aoRow[15];
653 self.fCpuNestedPaging = aoRow[16];
654 self.fCpu64BitGuest = aoRow[17];
655 self.idTestBox = aoRow[18];
656 self.sTestBoxName = aoRow[19];
657
658 self.tsCreated = aoRow[20];
659 self.tsElapsed = aoRow[21];
660 self.enmStatus = aoRow[22];
661 self.cErrors = aoRow[23];
662
663 self.idTestCase = aoRow[24];
664 self.sTestCaseName = aoRow[25];
665 self.sBaseCmd = aoRow[26];
666 self.sArgs = aoRow[27];
667 self.sSubName = aoRow[28];
668
669 self.idBuildTestSuite = aoRow[29];
670 self.iRevisionTestSuite = aoRow[30];
671
672 self.aoFailureReasons = [];
673 for i, _ in enumerate(aoRow[31]):
674 if aoRow[31][i] is not None \
675 or aoRow[32][i] is not None \
676 or aoRow[33][i] is not None \
677 or aoRow[34][i] is not None:
678 oReason = self.FailureReasonListingData();
679 if aoRow[31][i] is not None:
680 oReason.oFailureReason = oFailureReasonLogic.cachedLookup(aoRow[31][i]);
681 if aoRow[32][i] is not None:
682 oReason.oFailureReasonAssigner = oUserAccountLogic.cachedLookup(aoRow[32][i]);
683 oReason.tsFailureReasonAssigned = aoRow[33][i];
684 oReason.sFailureReasonComment = aoRow[34][i];
685 self.aoFailureReasons.append(oReason);
686
687 return self
688
689
690class TestResultHangingOffence(TMExceptionBase):
691 """Hanging offence committed by test case."""
692 pass; # pylint: disable=unnecessary-pass
693
694
695class TestResultFilter(ModelFilterBase):
696 """
697 Test result filter.
698 """
699
700 kiTestStatus = 0;
701 kiErrorCounts = 1;
702 kiBranches = 2;
703 kiBuildTypes = 3;
704 kiRevisions = 4;
705 kiFailReasons = 5;
706 kiTestCases = 6;
707 kiTestCaseMisc = 7;
708 kiTestBoxes = 8
709 kiOses = 9;
710 kiCpuArches = 10;
711 kiCpuVendors = 11;
712 kiCpuCounts = 12;
713 kiMemory = 13;
714 kiTestboxMisc = 14;
715 kiPythonVersions = 15;
716 kiSchedGroups = 16;
717
718 ## Misc test case / variation name filters.
719 ## Presented in table order. The first sub element is the presistent ID.
720 kaTcMisc = (
721 ( 1, 'x86', ),
722 ( 2, 'amd64', ),
723 ( 3, 'uni', ),
724 ( 4, 'smp', ),
725 ( 5, 'raw', ),
726 ( 6, 'hw', ),
727 ( 7, 'np', ),
728 ( 8, 'Install', ),
729 ( 20, 'UInstall', ), # NB. out of order.
730 ( 9, 'Benchmark', ),
731 ( 18, 'smoke', ), # NB. out of order.
732 ( 19, 'unit', ), # NB. out of order.
733 ( 10, 'USB', ),
734 ( 11, 'Debian', ),
735 ( 12, 'Fedora', ),
736 ( 13, 'Oracle', ),
737 ( 14, 'RHEL', ),
738 ( 15, 'SUSE', ),
739 ( 16, 'Ubuntu', ),
740 ( 17, 'Win', ),
741 );
742
743 kiTbMisc_NestedPaging = 0;
744 kiTbMisc_NoNestedPaging = 1;
745 kiTbMisc_RawMode = 2;
746 kiTbMisc_NoRawMode = 3;
747 kiTbMisc_64BitGuest = 4;
748 kiTbMisc_No64BitGuest = 5;
749 kiTbMisc_HwVirt = 6;
750 kiTbMisc_NoHwVirt = 7;
751 kiTbMisc_IoMmu = 8;
752 kiTbMisc_NoIoMmu = 9;
753
754 def __init__(self):
755 ModelFilterBase.__init__(self);
756
757 # Test statuses
758 oCrit = FilterCriterion('Test statuses', sVarNm = 'ts', sType = FilterCriterion.ksType_String,
759 sTable = 'TestSets', sColumn = 'enmStatus');
760 self.aCriteria.append(oCrit);
761 assert self.aCriteria[self.kiTestStatus] is oCrit;
762
763 # Error counts
764 oCrit = FilterCriterion('Error counts', sVarNm = 'ec', sTable = 'TestResults', sColumn = 'cErrors');
765 self.aCriteria.append(oCrit);
766 assert self.aCriteria[self.kiErrorCounts] is oCrit;
767
768 # Branches
769 oCrit = FilterCriterion('Branches', sVarNm = 'br', sType = FilterCriterion.ksType_String,
770 sTable = 'BuildCategories', sColumn = 'sBranch');
771 self.aCriteria.append(oCrit);
772 assert self.aCriteria[self.kiBranches] is oCrit;
773
774 # Build types
775 oCrit = FilterCriterion('Build types', sVarNm = 'bt', sType = FilterCriterion.ksType_String,
776 sTable = 'BuildCategories', sColumn = 'sType');
777 self.aCriteria.append(oCrit);
778 assert self.aCriteria[self.kiBuildTypes] is oCrit;
779
780 # Revisions
781 oCrit = FilterCriterion('Revisions', sVarNm = 'rv', sTable = 'Builds', sColumn = 'iRevision');
782 self.aCriteria.append(oCrit);
783 assert self.aCriteria[self.kiRevisions] is oCrit;
784
785 # Failure reasons
786 oCrit = FilterCriterion('Failure reasons', sVarNm = 'fr', sType = FilterCriterion.ksType_UIntNil,
787 sTable = 'TestResultFailures', sColumn = 'idFailureReason');
788 self.aCriteria.append(oCrit);
789 assert self.aCriteria[self.kiFailReasons] is oCrit;
790
791 # Test cases and variations.
792 oCrit = FilterCriterion('Test case / var', sVarNm = 'tc', sTable = 'TestSets', sColumn = 'idTestCase',
793 oSub = FilterCriterion('Test variations', sVarNm = 'tv',
794 sTable = 'TestSets', sColumn = 'idTestCaseArgs'));
795 self.aCriteria.append(oCrit);
796 assert self.aCriteria[self.kiTestCases] is oCrit;
797
798 # Special test case and varation name sub string matching.
799 oCrit = FilterCriterion('Test case name', sVarNm = 'cm', sKind = FilterCriterion.ksKind_Special,
800 asTables = ('TestCases', 'TestCaseArgs'));
801 oCrit.aoPossible = [
802 FilterCriterionValueAndDescription(aoCur[0], 'Include %s' % (aoCur[1],)) for aoCur in self.kaTcMisc
803 ];
804 oCrit.aoPossible.extend([
805 FilterCriterionValueAndDescription(aoCur[0] + 32, 'Exclude %s' % (aoCur[1],)) for aoCur in self.kaTcMisc
806 ]);
807 self.aCriteria.append(oCrit);
808 assert self.aCriteria[self.kiTestCaseMisc] is oCrit;
809
810 # Testboxes
811 oCrit = FilterCriterion('Testboxes', sVarNm = 'tb', sTable = 'TestSets', sColumn = 'idTestBox');
812 self.aCriteria.append(oCrit);
813 assert self.aCriteria[self.kiTestBoxes] is oCrit;
814
815 # Testbox OS and OS version.
816 oCrit = FilterCriterion('OS / version', sVarNm = 'os', sTable = 'TestBoxesWithStrings', sColumn = 'idStrOs',
817 oSub = FilterCriterion('OS Versions', sVarNm = 'ov',
818 sTable = 'TestBoxesWithStrings', sColumn = 'idStrOsVersion'));
819 self.aCriteria.append(oCrit);
820 assert self.aCriteria[self.kiOses] is oCrit;
821
822 # Testbox CPU architectures.
823 oCrit = FilterCriterion('CPU arches', sVarNm = 'ca', sTable = 'TestBoxesWithStrings', sColumn = 'idStrCpuArch');
824 self.aCriteria.append(oCrit);
825 assert self.aCriteria[self.kiCpuArches] is oCrit;
826
827 # Testbox CPU vendors and revisions.
828 oCrit = FilterCriterion('CPU vendor / rev', sVarNm = 'cv', sTable = 'TestBoxesWithStrings', sColumn = 'idStrCpuVendor',
829 oSub = FilterCriterion('CPU revisions', sVarNm = 'cr',
830 sTable = 'TestBoxesWithStrings', sColumn = 'lCpuRevision'));
831 self.aCriteria.append(oCrit);
832 assert self.aCriteria[self.kiCpuVendors] is oCrit;
833
834 # Testbox CPU (thread) count
835 oCrit = FilterCriterion('CPU counts', sVarNm = 'cc', sTable = 'TestBoxesWithStrings', sColumn = 'cCpus');
836 self.aCriteria.append(oCrit);
837 assert self.aCriteria[self.kiCpuCounts] is oCrit;
838
839 # Testbox memory sizes.
840 oCrit = FilterCriterion('Memory', sVarNm = 'mb', sTable = 'TestBoxesWithStrings', sColumn = 'cMbMemory');
841 self.aCriteria.append(oCrit);
842 assert self.aCriteria[self.kiMemory] is oCrit;
843
844 # Testbox features.
845 oCrit = FilterCriterion('Testbox features', sVarNm = 'tm', sKind = FilterCriterion.ksKind_Special,
846 sTable = 'TestBoxesWithStrings');
847 oCrit.aoPossible = [
848 FilterCriterionValueAndDescription(self.kiTbMisc_NestedPaging, "req nested paging"),
849 FilterCriterionValueAndDescription(self.kiTbMisc_NoNestedPaging, "w/o nested paging"),
850 #FilterCriterionValueAndDescription(self.kiTbMisc_RawMode, "req raw-mode"), - not implemented yet.
851 #FilterCriterionValueAndDescription(self.kiTbMisc_NoRawMode, "w/o raw-mode"), - not implemented yet.
852 FilterCriterionValueAndDescription(self.kiTbMisc_64BitGuest, "req 64-bit guests"),
853 FilterCriterionValueAndDescription(self.kiTbMisc_No64BitGuest, "w/o 64-bit guests"),
854 FilterCriterionValueAndDescription(self.kiTbMisc_HwVirt, "req VT-x / AMD-V"),
855 FilterCriterionValueAndDescription(self.kiTbMisc_NoHwVirt, "w/o VT-x / AMD-V"),
856 #FilterCriterionValueAndDescription(self.kiTbMisc_IoMmu, "req I/O MMU"), - not implemented yet.
857 #FilterCriterionValueAndDescription(self.kiTbMisc_NoIoMmu, "w/o I/O MMU"), - not implemented yet.
858 ];
859 self.aCriteria.append(oCrit);
860 assert self.aCriteria[self.kiTestboxMisc] is oCrit;
861
862 # Testbox python versions.
863 oCrit = FilterCriterion('Python', sVarNm = 'py', sTable = 'TestBoxesWithStrings', sColumn = 'iPythonHexVersion');
864 self.aCriteria.append(oCrit);
865 assert self.aCriteria[self.kiPythonVersions] is oCrit;
866
867 # Scheduling groups.
868 oCrit = FilterCriterion('Sched groups', sVarNm = 'sg', sTable = 'TestSets', sColumn = 'idSchedGroup');
869 self.aCriteria.append(oCrit);
870 assert self.aCriteria[self.kiSchedGroups] is oCrit;
871
872
873 kdTbMiscConditions = {
874 kiTbMisc_NestedPaging: 'TestBoxesWithStrings.fCpuNestedPaging IS TRUE',
875 kiTbMisc_NoNestedPaging: 'TestBoxesWithStrings.fCpuNestedPaging IS FALSE',
876 kiTbMisc_RawMode: 'TestBoxesWithStrings.fRawMode IS TRUE',
877 kiTbMisc_NoRawMode: 'TestBoxesWithStrings.fRawMode IS NOT TRUE',
878 kiTbMisc_64BitGuest: 'TestBoxesWithStrings.fCpu64BitGuest IS TRUE',
879 kiTbMisc_No64BitGuest: 'TestBoxesWithStrings.fCpu64BitGuest IS FALSE',
880 kiTbMisc_HwVirt: 'TestBoxesWithStrings.fCpuHwVirt IS TRUE',
881 kiTbMisc_NoHwVirt: 'TestBoxesWithStrings.fCpuHwVirt IS FALSE',
882 kiTbMisc_IoMmu: 'TestBoxesWithStrings.fChipsetIoMmu IS TRUE',
883 kiTbMisc_NoIoMmu: 'TestBoxesWithStrings.fChipsetIoMmu IS FALSE',
884 };
885
886 def _getWhereWorker(self, iCrit, oCrit, sExtraIndent, iOmit):
887 """ Formats one - main or sub. """
888 sQuery = '';
889 if oCrit.sState == FilterCriterion.ksState_Selected and iCrit != iOmit:
890 if iCrit == self.kiTestCaseMisc:
891 for iValue, sLike in self.kaTcMisc:
892 if iValue in oCrit.aoSelected: sNot = '';
893 elif iValue + 32 in oCrit.aoSelected: sNot = 'NOT ';
894 else: continue;
895 sQuery += '%s AND %s (' % (sExtraIndent, sNot,);
896 if len(sLike) <= 3: # do word matching for small substrings (hw, np, smp, uni, ++).
897 sQuery += 'TestCases.sName ~ \'.*\\y%s\\y.*\' ' \
898 'OR COALESCE(TestCaseArgs.sSubName, \'\') ~ \'.*\\y%s\\y.*\')\n' \
899 % ( sLike, sLike,);
900 else:
901 sQuery += 'TestCases.sName LIKE \'%%%s%%\' ' \
902 'OR COALESCE(TestCaseArgs.sSubName, \'\') LIKE \'%%%s%%\')\n' \
903 % ( sLike, sLike,);
904 elif iCrit == self.kiTestboxMisc:
905 dConditions = self.kdTbMiscConditions;
906 for iValue in oCrit.aoSelected:
907 if iValue in dConditions:
908 sQuery += '%s AND %s\n' % (sExtraIndent, dConditions[iValue],);
909 else:
910 assert len(oCrit.asTables) == 1;
911 sQuery += '%s AND (' % (sExtraIndent,);
912
913 if oCrit.sType != FilterCriterion.ksType_UIntNil or max(oCrit.aoSelected) != -1:
914 if iCrit == self.kiMemory:
915 sQuery += '(%s.%s / 1024)' % (oCrit.asTables[0], oCrit.sColumn,);
916 else:
917 sQuery += '%s.%s' % (oCrit.asTables[0], oCrit.sColumn,);
918 if not oCrit.fInverted:
919 sQuery += ' IN (';
920 else:
921 sQuery += ' NOT IN (';
922 if oCrit.sType == FilterCriterion.ksType_String:
923 sQuery += ', '.join('\'%s\'' % (sValue,) for sValue in oCrit.aoSelected) + ')';
924 else:
925 sQuery += ', '.join(str(iValue) for iValue in oCrit.aoSelected if iValue != -1) + ')';
926
927 if oCrit.sType == FilterCriterion.ksType_UIntNil \
928 and -1 in oCrit.aoSelected:
929 if sQuery[-1] != '(': sQuery += ' OR ';
930 sQuery += '%s.%s IS NULL' % (oCrit.asTables[0], oCrit.sColumn,);
931
932 if iCrit == self.kiFailReasons:
933 if oCrit.fInverted:
934 sQuery += '%s OR TestResultFailures.idFailureReason IS NULL\n' % (sExtraIndent,);
935 else:
936 sQuery += '%s AND TestSets.enmStatus >= \'failure\'::TestStatus_T\n' % (sExtraIndent,);
937 sQuery += ')\n';
938 if oCrit.oSub is not None:
939 sQuery += self._getWhereWorker(iCrit | (((iCrit >> 8) + 1) << 8), oCrit.oSub, sExtraIndent, iOmit);
940 return sQuery;
941
942 def getWhereConditions(self, sExtraIndent = '', iOmit = -1):
943 """
944 Construct the WHERE conditions for the filter, optionally omitting one
945 criterion.
946 """
947 sQuery = '';
948 for iCrit, oCrit in enumerate(self.aCriteria):
949 sQuery += self._getWhereWorker(iCrit, oCrit, sExtraIndent, iOmit);
950 return sQuery;
951
952 def getTableJoins(self, sExtraIndent = '', iOmit = -1, dOmitTables = None):
953 """
954 Construct the WHERE conditions for the filter, optionally omitting one
955 criterion.
956 """
957 afDone = { 'TestSets': True, };
958 if dOmitTables is not None:
959 afDone.update(dOmitTables);
960
961 sQuery = '';
962 for iCrit, oCrit in enumerate(self.aCriteria):
963 if oCrit.sState == FilterCriterion.ksState_Selected \
964 and iCrit != iOmit:
965 for sTable in oCrit.asTables:
966 if sTable not in afDone:
967 afDone[sTable] = True;
968 if sTable == 'Builds':
969 sQuery += '%sINNER JOIN Builds\n' \
970 '%s ON Builds.idBuild = TestSets.idBuild\n' \
971 '%s AND Builds.tsExpire > TestSets.tsCreated\n' \
972 '%s AND Builds.tsEffective <= TestSets.tsCreated\n' \
973 % ( sExtraIndent, sExtraIndent, sExtraIndent, sExtraIndent, );
974 elif sTable == 'BuildCategories':
975 sQuery += '%sINNER JOIN BuildCategories\n' \
976 '%s ON BuildCategories.idBuildCategory = TestSets.idBuildCategory\n' \
977 % ( sExtraIndent, sExtraIndent, );
978 elif sTable == 'TestBoxesWithStrings':
979 sQuery += '%sLEFT OUTER JOIN TestBoxesWithStrings\n' \
980 '%s ON TestBoxesWithStrings.idGenTestBox = TestSets.idGenTestBox\n' \
981 % ( sExtraIndent, sExtraIndent, );
982 elif sTable == 'TestCases':
983 sQuery += '%sINNER JOIN TestCases\n' \
984 '%s ON TestCases.idGenTestCase = TestSets.idGenTestCase\n' \
985 % ( sExtraIndent, sExtraIndent, );
986 elif sTable == 'TestCaseArgs':
987 sQuery += '%sINNER JOIN TestCaseArgs\n' \
988 '%s ON TestCaseArgs.idGenTestCaseArgs = TestSets.idGenTestCaseArgs\n' \
989 % ( sExtraIndent, sExtraIndent, );
990 elif sTable == 'TestResults':
991 sQuery += '%sINNER JOIN TestResults\n' \
992 '%s ON TestResults.idTestResult = TestSets.idTestResult\n' \
993 % ( sExtraIndent, sExtraIndent, );
994 elif sTable == 'TestResultFailures':
995 sQuery += '%sLEFT OUTER JOIN TestResultFailures\n' \
996 '%s ON TestResultFailures.idTestSet = TestSets.idTestSet\n' \
997 '%s AND TestResultFailures.tsExpire = \'infinity\'::TIMESTAMP\n' \
998 % ( sExtraIndent, sExtraIndent, sExtraIndent, );
999 else:
1000 assert False, sTable;
1001 return sQuery;
1002
1003 def isJoiningWithTable(self, sTable):
1004 """ Checks whether getTableJoins already joins with TestResultFailures. """
1005 for oCrit in self.aCriteria:
1006 if oCrit.sState == FilterCriterion.ksState_Selected and sTable in oCrit.asTables:
1007 return True;
1008 return False
1009
1010
1011
1012class TestResultLogic(ModelLogicBase): # pylint: disable=too-few-public-methods
1013 """
1014 Results grouped by scheduling group.
1015 """
1016
1017 #
1018 # Result grinding for displaying in the WUI.
1019 #
1020
1021 ksResultsGroupingTypeNone = 'ResultsGroupingTypeNone';
1022 ksResultsGroupingTypeTestGroup = 'ResultsGroupingTypeTestGroup';
1023 ksResultsGroupingTypeBuildCat = 'ResultsGroupingTypeBuildCat';
1024 ksResultsGroupingTypeBuildRev = 'ResultsGroupingTypeBuildRev';
1025 ksResultsGroupingTypeTestBox = 'ResultsGroupingTypeTestBox';
1026 ksResultsGroupingTypeTestCase = 'ResultsGroupingTypeTestCase';
1027 ksResultsGroupingTypeOS = 'ResultsGroupingTypeOS';
1028 ksResultsGroupingTypeArch = 'ResultsGroupingTypeArch';
1029 ksResultsGroupingTypeSchedGroup = 'ResultsGroupingTypeSchedGroup';
1030
1031 ## @name Result sorting options.
1032 ## @{
1033 ksResultsSortByRunningAndStart = 'ResultsSortByRunningAndStart'; ##< Default
1034 ksResultsSortByBuildRevision = 'ResultsSortByBuildRevision';
1035 ksResultsSortByTestBoxName = 'ResultsSortByTestBoxName';
1036 ksResultsSortByTestBoxOs = 'ResultsSortByTestBoxOs';
1037 ksResultsSortByTestBoxOsVersion = 'ResultsSortByTestBoxOsVersion';
1038 ksResultsSortByTestBoxOsArch = 'ResultsSortByTestBoxOsArch';
1039 ksResultsSortByTestBoxArch = 'ResultsSortByTestBoxArch';
1040 ksResultsSortByTestBoxCpuVendor = 'ResultsSortByTestBoxCpuVendor';
1041 ksResultsSortByTestBoxCpuName = 'ResultsSortByTestBoxCpuName';
1042 ksResultsSortByTestBoxCpuRev = 'ResultsSortByTestBoxCpuRev';
1043 ksResultsSortByTestBoxCpuFeatures = 'ResultsSortByTestBoxCpuFeatures';
1044 ksResultsSortByTestCaseName = 'ResultsSortByTestCaseName';
1045 ksResultsSortByFailureReason = 'ResultsSortByFailureReason';
1046 kasResultsSortBy = {
1047 ksResultsSortByRunningAndStart,
1048 ksResultsSortByBuildRevision,
1049 ksResultsSortByTestBoxName,
1050 ksResultsSortByTestBoxOs,
1051 ksResultsSortByTestBoxOsVersion,
1052 ksResultsSortByTestBoxOsArch,
1053 ksResultsSortByTestBoxArch,
1054 ksResultsSortByTestBoxCpuVendor,
1055 ksResultsSortByTestBoxCpuName,
1056 ksResultsSortByTestBoxCpuRev,
1057 ksResultsSortByTestBoxCpuFeatures,
1058 ksResultsSortByTestCaseName,
1059 ksResultsSortByFailureReason,
1060 };
1061 ## Used by the WUI for generating the drop down.
1062 kaasResultsSortByTitles = (
1063 ( ksResultsSortByRunningAndStart, 'Running & Start TS' ),
1064 ( ksResultsSortByBuildRevision, 'Build Revision' ),
1065 ( ksResultsSortByTestBoxName, 'TestBox Name' ),
1066 ( ksResultsSortByTestBoxOs, 'O/S' ),
1067 ( ksResultsSortByTestBoxOsVersion, 'O/S Version' ),
1068 ( ksResultsSortByTestBoxOsArch, 'O/S & Architecture' ),
1069 ( ksResultsSortByTestBoxArch, 'Architecture' ),
1070 ( ksResultsSortByTestBoxCpuVendor, 'CPU Vendor' ),
1071 ( ksResultsSortByTestBoxCpuName, 'CPU Vendor & Name' ),
1072 ( ksResultsSortByTestBoxCpuRev, 'CPU Vendor & Revision' ),
1073 ( ksResultsSortByTestBoxCpuFeatures, 'CPU Features' ),
1074 ( ksResultsSortByTestCaseName, 'Test Case Name' ),
1075 ( ksResultsSortByFailureReason, 'Failure Reason' ),
1076 );
1077 ## @}
1078
1079 ## Default sort by map.
1080 kdResultSortByMap = {
1081 ksResultsSortByRunningAndStart: ( (), None, None, '', '' ),
1082 ksResultsSortByBuildRevision: (
1083 # Sorting tables.
1084 ('Builds',),
1085 # Sorting table join(s).
1086 ' AND TestSets.idBuild = Builds.idBuild'
1087 ' AND Builds.tsExpire >= TestSets.tsCreated'
1088 ' AND Builds.tsEffective <= TestSets.tsCreated',
1089 # Start of ORDER BY statement.
1090 ' Builds.iRevision DESC',
1091 # Extra columns to fetch for the above ORDER BY to work in a SELECT DISTINCT statement.
1092 '',
1093 # Columns for the GROUP BY
1094 ''),
1095 ksResultsSortByTestBoxName: (
1096 ('TestBoxes',),
1097 ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox',
1098 ' TestBoxes.sName DESC',
1099 '', '' ),
1100 ksResultsSortByTestBoxOsArch: (
1101 ('TestBoxesWithStrings',),
1102 ' AND TestSets.idGenTestBox = TestBoxesWithStrings.idGenTestBox',
1103 ' TestBoxesWithStrings.sOs, TestBoxesWithStrings.sCpuArch',
1104 '', '' ),
1105 ksResultsSortByTestBoxOs: (
1106 ('TestBoxesWithStrings',),
1107 ' AND TestSets.idGenTestBox = TestBoxesWithStrings.idGenTestBox',
1108 ' TestBoxesWithStrings.sOs',
1109 '', '' ),
1110 ksResultsSortByTestBoxOsVersion: (
1111 ('TestBoxesWithStrings',),
1112 ' AND TestSets.idGenTestBox = TestBoxesWithStrings.idGenTestBox',
1113 ' TestBoxesWithStrings.sOs, TestBoxesWithStrings.sOsVersion DESC',
1114 '', '' ),
1115 ksResultsSortByTestBoxArch: (
1116 ('TestBoxesWithStrings',),
1117 ' AND TestSets.idGenTestBox = TestBoxesWithStrings.idGenTestBox',
1118 ' TestBoxesWithStrings.sCpuArch',
1119 '', '' ),
1120 ksResultsSortByTestBoxCpuVendor: (
1121 ('TestBoxesWithStrings',),
1122 ' AND TestSets.idGenTestBox = TestBoxesWithStrings.idGenTestBox',
1123 ' TestBoxesWithStrings.sCpuVendor',
1124 '', '' ),
1125 ksResultsSortByTestBoxCpuName: (
1126 ('TestBoxesWithStrings',),
1127 ' AND TestSets.idGenTestBox = TestBoxesWithStrings.idGenTestBox',
1128 ' TestBoxesWithStrings.sCpuVendor, TestBoxesWithStrings.sCpuName',
1129 '', '' ),
1130 ksResultsSortByTestBoxCpuRev: (
1131 ('TestBoxesWithStrings',),
1132 ' AND TestSets.idGenTestBox = TestBoxesWithStrings.idGenTestBox',
1133 ' TestBoxesWithStrings.sCpuVendor, TestBoxesWithStrings.lCpuRevision DESC',
1134 ', TestBoxesWithStrings.lCpuRevision',
1135 ', TestBoxesWithStrings.lCpuRevision' ),
1136 ksResultsSortByTestBoxCpuFeatures: (
1137 ('TestBoxes',),
1138 ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox',
1139 ' TestBoxes.fCpuHwVirt DESC, TestBoxes.fCpuNestedPaging DESC, TestBoxes.fCpu64BitGuest DESC, TestBoxes.cCpus DESC',
1140 '',
1141 '' ),
1142 ksResultsSortByTestCaseName: (
1143 ('TestCases',),
1144 ' AND TestSets.idGenTestCase = TestCases.idGenTestCase',
1145 ' TestCases.sName',
1146 '', '' ),
1147 ksResultsSortByFailureReason: (
1148 (), '',
1149 'asSortByFailureReason ASC',
1150 ', array_agg(FailureReasons.sShort ORDER BY TestResultFailures.idTestResult) AS asSortByFailureReason',
1151 '' ),
1152 };
1153
1154 kdResultGroupingMap = {
1155 ksResultsGroupingTypeNone: (
1156 # Grouping tables;
1157 (),
1158 # Grouping field;
1159 None,
1160 # Grouping where addition.
1161 None,
1162 # Sort by overrides.
1163 {},
1164 ),
1165 ksResultsGroupingTypeTestGroup: ('', 'TestSets.idTestGroup', None, {},),
1166 ksResultsGroupingTypeTestBox: ('', 'TestSets.idTestBox', None, {},),
1167 ksResultsGroupingTypeTestCase: ('', 'TestSets.idTestCase', None, {},),
1168 ksResultsGroupingTypeOS: (
1169 ('TestBoxes',),
1170 'TestBoxes.idStrOs',
1171 ' AND TestBoxes.idGenTestBox = TestSets.idGenTestBox',
1172 {},
1173 ),
1174 ksResultsGroupingTypeArch: (
1175 ('TestBoxes',),
1176 'TestBoxes.idStrCpuArch',
1177 ' AND TestBoxes.idGenTestBox = TestSets.idGenTestBox',
1178 {},
1179 ),
1180 ksResultsGroupingTypeBuildCat: ('', 'TestSets.idBuildCategory', None, {},),
1181 ksResultsGroupingTypeBuildRev: (
1182 ('Builds',),
1183 'Builds.iRevision',
1184 ' AND Builds.idBuild = TestSets.idBuild'
1185 ' AND Builds.tsExpire > TestSets.tsCreated'
1186 ' AND Builds.tsEffective <= TestSets.tsCreated',
1187 { ksResultsSortByBuildRevision: ( (), None, ' Builds.iRevision DESC' ), }
1188 ),
1189 ksResultsGroupingTypeSchedGroup: ( '', 'TestSets.idSchedGroup', None, {},),
1190 };
1191
1192
1193 def __init__(self, oDb):
1194 ModelLogicBase.__init__(self, oDb)
1195 self.oFailureReasonLogic = None;
1196 self.oUserAccountLogic = None;
1197
1198 def _getTimePeriodQueryPart(self, tsNow, sInterval, sExtraIndent = ''):
1199 """
1200 Get part of SQL query responsible for SELECT data within
1201 specified period of time.
1202 """
1203 assert sInterval is not None; # too many rows.
1204
1205 cMonthsMourningPeriod = 2; # Stop reminding everyone about testboxes after 2 months. (May also speed up the query.)
1206 if tsNow is None:
1207 sRet = '(TestSets.tsDone IS NULL OR TestSets.tsDone >= (CURRENT_TIMESTAMP - \'%s\'::interval))\n' \
1208 '%s AND TestSets.tsCreated >= (CURRENT_TIMESTAMP - \'%s\'::interval - \'%u months\'::interval)\n' \
1209 % ( sInterval,
1210 sExtraIndent, sInterval, cMonthsMourningPeriod);
1211 else:
1212 sTsNow = '\'%s\'::TIMESTAMP' % (tsNow,); # It's actually a string already. duh.
1213 sRet = 'TestSets.tsCreated <= %s\n' \
1214 '%s AND TestSets.tsCreated >= (%s - \'%s\'::interval - \'%u months\'::interval)\n' \
1215 '%s AND (TestSets.tsDone IS NULL OR TestSets.tsDone >= (%s - \'%s\'::interval))\n' \
1216 % ( sTsNow,
1217 sExtraIndent, sTsNow, sInterval, cMonthsMourningPeriod,
1218 sExtraIndent, sTsNow, sInterval );
1219 return sRet
1220
1221 def fetchResultsForListing(self, iStart, cMaxRows, tsNow, sInterval, oFilter, enmResultSortBy, # pylint: disable=too-many-arguments
1222 enmResultsGroupingType, iResultsGroupingValue, fOnlyFailures, fOnlyNeedingReason):
1223 """
1224 Fetches TestResults table content.
1225
1226 If @param enmResultsGroupingType and @param iResultsGroupingValue
1227 are not None, then resulting (returned) list contains only records
1228 that match specified @param enmResultsGroupingType.
1229
1230 If @param enmResultsGroupingType is None, then
1231 @param iResultsGroupingValue is ignored.
1232
1233 Returns an array (list) of TestResultData items, empty list if none.
1234 Raises exception on error.
1235 """
1236
1237 _ = oFilter;
1238
1239 #
1240 # Get SQL query parameters
1241 #
1242 if enmResultsGroupingType is None or enmResultsGroupingType not in self.kdResultGroupingMap:
1243 raise TMExceptionBase('Unknown grouping type');
1244 if enmResultSortBy is None or enmResultSortBy not in self.kasResultsSortBy:
1245 raise TMExceptionBase('Unknown sorting');
1246 asGroupingTables, sGroupingField, sGroupingCondition, dSortOverrides = self.kdResultGroupingMap[enmResultsGroupingType];
1247 if enmResultSortBy in dSortOverrides:
1248 asSortTables, sSortWhere, sSortOrderBy, sSortColumns, sSortGroupBy = dSortOverrides[enmResultSortBy];
1249 else:
1250 asSortTables, sSortWhere, sSortOrderBy, sSortColumns, sSortGroupBy = self.kdResultSortByMap[enmResultSortBy];
1251
1252 #
1253 # Construct the query.
1254 #
1255 sQuery = 'SELECT DISTINCT TestSets.idTestSet,\n' \
1256 ' BuildCategories.idBuildCategory,\n' \
1257 ' BuildCategories.sProduct,\n' \
1258 ' BuildCategories.sRepository,\n' \
1259 ' BuildCategories.sBranch,\n' \
1260 ' BuildCategories.sType,\n' \
1261 ' Builds.idBuild,\n' \
1262 ' Builds.sVersion,\n' \
1263 ' Builds.iRevision,\n' \
1264 ' TestBoxesWithStrings.sOs,\n' \
1265 ' TestBoxesWithStrings.sOsVersion,\n' \
1266 ' TestBoxesWithStrings.sCpuArch,\n' \
1267 ' TestBoxesWithStrings.sCpuVendor,\n' \
1268 ' TestBoxesWithStrings.sCpuName,\n' \
1269 ' TestBoxesWithStrings.cCpus,\n' \
1270 ' TestBoxesWithStrings.fCpuHwVirt,\n' \
1271 ' TestBoxesWithStrings.fCpuNestedPaging,\n' \
1272 ' TestBoxesWithStrings.fCpu64BitGuest,\n' \
1273 ' TestBoxesWithStrings.idTestBox,\n' \
1274 ' TestBoxesWithStrings.sName,\n' \
1275 ' TestResults.tsCreated,\n' \
1276 ' COALESCE(TestResults.tsElapsed, CURRENT_TIMESTAMP - TestResults.tsCreated) AS tsElapsedTestResult,\n' \
1277 ' TestSets.enmStatus,\n' \
1278 ' TestResults.cErrors,\n' \
1279 ' TestCases.idTestCase,\n' \
1280 ' TestCases.sName,\n' \
1281 ' TestCases.sBaseCmd,\n' \
1282 ' TestCaseArgs.sArgs,\n' \
1283 ' TestCaseArgs.sSubName,\n' \
1284 ' TestSuiteBits.idBuild AS idBuildTestSuite,\n' \
1285 ' TestSuiteBits.iRevision AS iRevisionTestSuite,\n' \
1286 ' array_agg(TestResultFailures.idFailureReason ORDER BY TestResultFailures.idTestResult),\n' \
1287 ' array_agg(TestResultFailures.uidAuthor ORDER BY TestResultFailures.idTestResult),\n' \
1288 ' array_agg(TestResultFailures.tsEffective ORDER BY TestResultFailures.idTestResult),\n' \
1289 ' array_agg(TestResultFailures.sComment ORDER BY TestResultFailures.idTestResult),\n' \
1290 ' (TestSets.tsDone IS NULL) SortRunningFirst' + sSortColumns + '\n' \
1291 'FROM ( SELECT TestSets.idTestSet AS idTestSet,\n' \
1292 ' TestSets.tsDone AS tsDone,\n' \
1293 ' TestSets.tsCreated AS tsCreated,\n' \
1294 ' TestSets.enmStatus AS enmStatus,\n' \
1295 ' TestSets.idBuild AS idBuild,\n' \
1296 ' TestSets.idBuildTestSuite AS idBuildTestSuite,\n' \
1297 ' TestSets.idGenTestBox AS idGenTestBox,\n' \
1298 ' TestSets.idGenTestCase AS idGenTestCase,\n' \
1299 ' TestSets.idGenTestCaseArgs AS idGenTestCaseArgs\n' \
1300 ' FROM TestSets\n';
1301 sQuery += oFilter.getTableJoins(' ');
1302 if fOnlyNeedingReason and not oFilter.isJoiningWithTable('TestResultFailures'):
1303 sQuery += '\n' \
1304 ' LEFT OUTER JOIN TestResultFailures\n' \
1305 ' ON TestSets.idTestSet = TestResultFailures.idTestSet\n' \
1306 ' AND TestResultFailures.tsExpire = \'infinity\'::TIMESTAMP';
1307 for asTables in [asGroupingTables, asSortTables]:
1308 for sTable in asTables:
1309 if not oFilter.isJoiningWithTable(sTable):
1310 sQuery = sQuery[:-1] + ',\n ' + sTable + '\n';
1311
1312 sQuery += ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sInterval, ' ') + \
1313 oFilter.getWhereConditions(' ');
1314 if fOnlyFailures or fOnlyNeedingReason:
1315 sQuery += ' AND TestSets.enmStatus != \'success\'::TestStatus_T\n' \
1316 ' AND TestSets.enmStatus != \'running\'::TestStatus_T\n';
1317 if fOnlyNeedingReason:
1318 sQuery += ' AND TestResultFailures.idTestSet IS NULL\n';
1319 if sGroupingField is not None:
1320 sQuery += ' AND %s = %d\n' % (sGroupingField, iResultsGroupingValue,);
1321 if sGroupingCondition is not None:
1322 sQuery += sGroupingCondition.replace(' AND ', ' AND ');
1323 if sSortWhere is not None:
1324 sQuery += sSortWhere.replace(' AND ', ' AND ');
1325 sQuery += ' ORDER BY ';
1326 if sSortOrderBy is not None and sSortOrderBy.find('FailureReason') < 0:
1327 sQuery += sSortOrderBy + ',\n ';
1328 sQuery += '(TestSets.tsDone IS NULL) DESC, TestSets.idTestSet DESC\n' \
1329 ' LIMIT %s OFFSET %s\n' % (cMaxRows, iStart,);
1330
1331 # Note! INNER JOIN TestBoxesWithStrings performs miserable compared to LEFT OUTER JOIN. Doesn't matter for the result
1332 # because TestSets.idGenTestBox is a foreign key and unique in TestBoxes. So, let's do what ever is faster.
1333 sQuery += ' ) AS TestSets\n' \
1334 ' LEFT OUTER JOIN TestBoxesWithStrings\n' \
1335 ' ON TestSets.idGenTestBox = TestBoxesWithStrings.idGenTestBox' \
1336 ' LEFT OUTER JOIN Builds AS TestSuiteBits\n' \
1337 ' ON TestSets.idBuildTestSuite = TestSuiteBits.idBuild\n' \
1338 ' LEFT OUTER JOIN TestResultFailures\n' \
1339 ' ON TestSets.idTestSet = TestResultFailures.idTestSet\n' \
1340 ' AND TestResultFailures.tsExpire = \'infinity\'::TIMESTAMP';
1341 if sSortOrderBy is not None and sSortOrderBy.find('FailureReason') >= 0:
1342 sQuery += '\n' \
1343 ' LEFT OUTER JOIN FailureReasons\n' \
1344 ' ON TestResultFailures.idFailureReason = FailureReasons.idFailureReason\n' \
1345 ' AND FailureReasons.tsExpire = \'infinity\'::TIMESTAMP';
1346 sQuery += ',\n' \
1347 ' BuildCategories,\n' \
1348 ' Builds,\n' \
1349 ' TestResults,\n' \
1350 ' TestCases,\n' \
1351 ' TestCaseArgs\n';
1352 sQuery += 'WHERE TestSets.idTestSet = TestResults.idTestSet\n' \
1353 ' AND TestResults.idTestResultParent is NULL\n' \
1354 ' AND TestSets.idBuild = Builds.idBuild\n' \
1355 ' AND Builds.tsExpire > TestSets.tsCreated\n' \
1356 ' AND Builds.tsEffective <= TestSets.tsCreated\n' \
1357 ' AND Builds.idBuildCategory = BuildCategories.idBuildCategory\n' \
1358 ' AND TestSets.idGenTestCase = TestCases.idGenTestCase\n' \
1359 ' AND TestSets.idGenTestCaseArgs = TestCaseArgs.idGenTestCaseArgs\n';
1360 sQuery += 'GROUP BY TestSets.idTestSet,\n' \
1361 ' BuildCategories.idBuildCategory,\n' \
1362 ' BuildCategories.sProduct,\n' \
1363 ' BuildCategories.sRepository,\n' \
1364 ' BuildCategories.sBranch,\n' \
1365 ' BuildCategories.sType,\n' \
1366 ' Builds.idBuild,\n' \
1367 ' Builds.sVersion,\n' \
1368 ' Builds.iRevision,\n' \
1369 ' TestBoxesWithStrings.sOs,\n' \
1370 ' TestBoxesWithStrings.sOsVersion,\n' \
1371 ' TestBoxesWithStrings.sCpuArch,\n' \
1372 ' TestBoxesWithStrings.sCpuVendor,\n' \
1373 ' TestBoxesWithStrings.sCpuName,\n' \
1374 ' TestBoxesWithStrings.cCpus,\n' \
1375 ' TestBoxesWithStrings.fCpuHwVirt,\n' \
1376 ' TestBoxesWithStrings.fCpuNestedPaging,\n' \
1377 ' TestBoxesWithStrings.fCpu64BitGuest,\n' \
1378 ' TestBoxesWithStrings.idTestBox,\n' \
1379 ' TestBoxesWithStrings.sName,\n' \
1380 ' TestResults.tsCreated,\n' \
1381 ' tsElapsedTestResult,\n' \
1382 ' TestSets.enmStatus,\n' \
1383 ' TestResults.cErrors,\n' \
1384 ' TestCases.idTestCase,\n' \
1385 ' TestCases.sName,\n' \
1386 ' TestCases.sBaseCmd,\n' \
1387 ' TestCaseArgs.sArgs,\n' \
1388 ' TestCaseArgs.sSubName,\n' \
1389 ' TestSuiteBits.idBuild,\n' \
1390 ' TestSuiteBits.iRevision,\n' \
1391 ' SortRunningFirst' + sSortGroupBy + '\n';
1392 sQuery += 'ORDER BY ';
1393 if sSortOrderBy is not None:
1394 sQuery += sSortOrderBy.replace('TestBoxes.', 'TestBoxesWithStrings.') + ',\n ';
1395 sQuery += '(TestSets.tsDone IS NULL) DESC, TestSets.idTestSet DESC\n';
1396
1397 #
1398 # Execute the query and return the wrapped results.
1399 #
1400 self._oDb.execute(sQuery);
1401
1402 if self.oFailureReasonLogic is None:
1403 self.oFailureReasonLogic = FailureReasonLogic(self._oDb);
1404 if self.oUserAccountLogic is None:
1405 self.oUserAccountLogic = UserAccountLogic(self._oDb);
1406
1407 aoRows = [];
1408 for aoRow in self._oDb.fetchAll():
1409 aoRows.append(TestResultListingData().initFromDbRowEx(aoRow, self.oFailureReasonLogic, self.oUserAccountLogic));
1410
1411 return aoRows
1412
1413
1414 def fetchTimestampsForLogViewer(self, idTestSet):
1415 """
1416 Returns an ordered list with all the test result timestamps, both start
1417 and end.
1418
1419 The log viewer create anchors in the log text so we can jump directly to
1420 the log lines relevant for a test event.
1421 """
1422 self._oDb.execute('(\n'
1423 'SELECT tsCreated\n'
1424 'FROM TestResults\n'
1425 'WHERE idTestSet = %s\n'
1426 ') UNION (\n'
1427 'SELECT tsCreated + tsElapsed\n'
1428 'FROM TestResults\n'
1429 'WHERE idTestSet = %s\n'
1430 ' AND tsElapsed IS NOT NULL\n'
1431 ') UNION (\n'
1432 'SELECT TestResultFiles.tsCreated\n'
1433 'FROM TestResultFiles\n'
1434 'WHERE idTestSet = %s\n'
1435 ') UNION (\n'
1436 'SELECT tsCreated\n'
1437 'FROM TestResultValues\n'
1438 'WHERE idTestSet = %s\n'
1439 ') UNION (\n'
1440 'SELECT TestResultMsgs.tsCreated\n'
1441 'FROM TestResultMsgs\n'
1442 'WHERE idTestSet = %s\n'
1443 ') ORDER by 1'
1444 , ( idTestSet, idTestSet, idTestSet, idTestSet, idTestSet, ));
1445 return [aoRow[0] for aoRow in self._oDb.fetchAll()];
1446
1447
1448 def getEntriesCount(self, tsNow, sInterval, oFilter, enmResultsGroupingType, iResultsGroupingValue,
1449 fOnlyFailures, fOnlyNeedingReason):
1450 """
1451 Get number of table records.
1452
1453 If @param enmResultsGroupingType and @param iResultsGroupingValue
1454 are not None, then we count only only those records
1455 that match specified @param enmResultsGroupingType.
1456
1457 If @param enmResultsGroupingType is None, then
1458 @param iResultsGroupingValue is ignored.
1459 """
1460 _ = oFilter;
1461
1462 #
1463 # Get SQL query parameters
1464 #
1465 if enmResultsGroupingType is None:
1466 raise TMExceptionBase('Unknown grouping type')
1467
1468 if enmResultsGroupingType not in self.kdResultGroupingMap:
1469 raise TMExceptionBase('Unknown grouping type')
1470 asGroupingTables, sGroupingField, sGroupingCondition, _ = self.kdResultGroupingMap[enmResultsGroupingType];
1471
1472 #
1473 # Construct the query.
1474 #
1475 sQuery = 'SELECT COUNT(TestSets.idTestSet)\n' \
1476 'FROM TestSets\n';
1477 sQuery += oFilter.getTableJoins();
1478 if fOnlyNeedingReason and not oFilter.isJoiningWithTable('TestResultFailures'):
1479 sQuery += ' LEFT OUTER JOIN TestResultFailures\n' \
1480 ' ON TestSets.idTestSet = TestResultFailures.idTestSet\n' \
1481 ' AND TestResultFailures.tsExpire = \'infinity\'::TIMESTAMP\n';
1482 for sTable in asGroupingTables:
1483 if not oFilter.isJoiningWithTable(sTable):
1484 sQuery = sQuery[:-1] + ',\n ' + sTable + '\n';
1485 sQuery += 'WHERE ' + self._getTimePeriodQueryPart(tsNow, sInterval) + \
1486 oFilter.getWhereConditions();
1487 if fOnlyFailures or fOnlyNeedingReason:
1488 sQuery += ' AND TestSets.enmStatus != \'success\'::TestStatus_T\n' \
1489 ' AND TestSets.enmStatus != \'running\'::TestStatus_T\n';
1490 if fOnlyNeedingReason:
1491 sQuery += ' AND TestResultFailures.idTestSet IS NULL\n';
1492 if sGroupingField is not None:
1493 sQuery += ' AND %s = %d\n' % (sGroupingField, iResultsGroupingValue,);
1494 if sGroupingCondition is not None:
1495 sQuery += sGroupingCondition.replace(' AND ', ' AND ');
1496
1497 #
1498 # Execute the query and return the result.
1499 #
1500 self._oDb.execute(sQuery)
1501 return self._oDb.fetchOne()[0]
1502
1503 def getTestGroups(self, tsNow, sPeriod):
1504 """
1505 Get list of uniq TestGroupData objects which
1506 found in all test results.
1507 """
1508
1509 self._oDb.execute('SELECT DISTINCT TestGroups.*\n'
1510 'FROM TestGroups, TestSets\n'
1511 'WHERE TestSets.idTestGroup = TestGroups.idTestGroup\n'
1512 ' AND TestGroups.tsExpire > TestSets.tsCreated\n'
1513 ' AND TestGroups.tsEffective <= TestSets.tsCreated'
1514 ' AND ' + self._getTimePeriodQueryPart(tsNow, sPeriod))
1515 aaoRows = self._oDb.fetchAll()
1516 aoRet = []
1517 for aoRow in aaoRows:
1518 aoRet.append(TestGroupData().initFromDbRow(aoRow))
1519 return aoRet
1520
1521 def getBuilds(self, tsNow, sPeriod):
1522 """
1523 Get list of uniq BuildDataEx objects which
1524 found in all test results.
1525 """
1526
1527 self._oDb.execute('SELECT DISTINCT Builds.*, BuildCategories.*\n'
1528 'FROM Builds, BuildCategories, TestSets\n'
1529 'WHERE TestSets.idBuild = Builds.idBuild\n'
1530 ' AND Builds.idBuildCategory = BuildCategories.idBuildCategory\n'
1531 ' AND Builds.tsExpire > TestSets.tsCreated\n'
1532 ' AND Builds.tsEffective <= TestSets.tsCreated'
1533 ' AND ' + self._getTimePeriodQueryPart(tsNow, sPeriod))
1534 aaoRows = self._oDb.fetchAll()
1535 aoRet = []
1536 for aoRow in aaoRows:
1537 aoRet.append(BuildDataEx().initFromDbRow(aoRow))
1538 return aoRet
1539
1540 def getTestBoxes(self, tsNow, sPeriod):
1541 """
1542 Get list of uniq TestBoxData objects which
1543 found in all test results.
1544 """
1545 # Note! INNER JOIN TestBoxesWithStrings performs miserable compared to LEFT OUTER JOIN. Doesn't matter for the result
1546 # because TestSets.idGenTestBox is a foreign key and unique in TestBoxes. So, let's do what ever is faster.
1547 self._oDb.execute('SELECT TestBoxesWithStrings.*\n'
1548 'FROM ( SELECT idTestBox AS idTestBox,\n'
1549 ' MAX(idGenTestBox) AS idGenTestBox\n'
1550 ' FROM TestSets\n'
1551 ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
1552 ' GROUP BY idTestBox\n'
1553 ' ) AS TestBoxIDs\n'
1554 ' LEFT OUTER JOIN TestBoxesWithStrings\n'
1555 ' ON TestBoxesWithStrings.idGenTestBox = TestBoxIDs.idGenTestBox\n'
1556 'ORDER BY TestBoxesWithStrings.sName\n' );
1557 aoRet = []
1558 for aoRow in self._oDb.fetchAll():
1559 aoRet.append(TestBoxData().initFromDbRow(aoRow));
1560 return aoRet
1561
1562 def getTestCases(self, tsNow, sPeriod):
1563 """
1564 Get a list of unique TestCaseData objects which is appears in the test
1565 specified result period.
1566 """
1567
1568 # Using LEFT OUTER JOIN instead of INNER JOIN in case it performs better, doesn't matter for the result.
1569 self._oDb.execute('SELECT TestCases.*\n'
1570 'FROM ( SELECT idTestCase AS idTestCase,\n'
1571 ' MAX(idGenTestCase) AS idGenTestCase\n'
1572 ' FROM TestSets\n'
1573 ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
1574 ' GROUP BY idTestCase\n'
1575 ' ) AS TestCasesIDs\n'
1576 ' LEFT OUTER JOIN TestCases ON TestCases.idGenTestCase = TestCasesIDs.idGenTestCase\n'
1577 'ORDER BY TestCases.sName\n' );
1578
1579 aoRet = [];
1580 for aoRow in self._oDb.fetchAll():
1581 aoRet.append(TestCaseData().initFromDbRow(aoRow));
1582 return aoRet
1583
1584 def getOSes(self, tsNow, sPeriod):
1585 """
1586 Get a list of [idStrOs, sOs] tuples of the OSes that appears in the specified result period.
1587 """
1588
1589 # Note! INNER JOIN TestBoxesWithStrings performs miserable compared to LEFT OUTER JOIN. Doesn't matter for the result
1590 # because TestSets.idGenTestBox is a foreign key and unique in TestBoxes. So, let's do what ever is faster.
1591 self._oDb.execute('SELECT DISTINCT TestBoxesWithStrings.idStrOs, TestBoxesWithStrings.sOs\n'
1592 'FROM ( SELECT idTestBox AS idTestBox,\n'
1593 ' MAX(idGenTestBox) AS idGenTestBox\n'
1594 ' FROM TestSets\n'
1595 ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
1596 ' GROUP BY idTestBox\n'
1597 ' ) AS TestBoxIDs\n'
1598 ' LEFT OUTER JOIN TestBoxesWithStrings\n'
1599 ' ON TestBoxesWithStrings.idGenTestBox = TestBoxIDs.idGenTestBox\n'
1600 'ORDER BY TestBoxesWithStrings.sOs\n' );
1601 return self._oDb.fetchAll();
1602
1603 def getArchitectures(self, tsNow, sPeriod):
1604 """
1605 Get a list of [idStrCpuArch, sCpuArch] tuples of the architecutres
1606 that appears in the specified result period.
1607 """
1608
1609 # Note! INNER JOIN TestBoxesWithStrings performs miserable compared to LEFT OUTER JOIN. Doesn't matter for the result
1610 # because TestSets.idGenTestBox is a foreign key and unique in TestBoxes. So, let's do what ever is faster.
1611 self._oDb.execute('SELECT DISTINCT TestBoxesWithStrings.idStrCpuArch, TestBoxesWithStrings.sCpuArch\n'
1612 'FROM ( SELECT idTestBox AS idTestBox,\n'
1613 ' MAX(idGenTestBox) AS idGenTestBox\n'
1614 ' FROM TestSets\n'
1615 ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
1616 ' GROUP BY idTestBox\n'
1617 ' ) AS TestBoxIDs\n'
1618 ' LEFT OUTER JOIN TestBoxesWithStrings\n'
1619 ' ON TestBoxesWithStrings.idGenTestBox = TestBoxIDs.idGenTestBox\n'
1620 'ORDER BY TestBoxesWithStrings.sCpuArch\n' );
1621 return self._oDb.fetchAll();
1622
1623 def getBuildCategories(self, tsNow, sPeriod):
1624 """
1625 Get a list of BuildCategoryData that appears in the specified result period.
1626 """
1627
1628 self._oDb.execute('SELECT DISTINCT BuildCategories.*\n'
1629 'FROM ( SELECT DISTINCT idBuildCategory AS idBuildCategory\n'
1630 ' FROM TestSets\n'
1631 ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
1632 ' ) AS BuildCategoryIDs\n'
1633 ' LEFT OUTER JOIN BuildCategories\n'
1634 ' ON BuildCategories.idBuildCategory = BuildCategoryIDs.idBuildCategory\n'
1635 'ORDER BY BuildCategories.sProduct, BuildCategories.sBranch, BuildCategories.sType\n');
1636 aoRet = [];
1637 for aoRow in self._oDb.fetchAll():
1638 aoRet.append(BuildCategoryData().initFromDbRow(aoRow));
1639 return aoRet;
1640
1641 def getSchedGroups(self, tsNow, sPeriod):
1642 """
1643 Get list of uniq SchedGroupData objects which
1644 found in all test results.
1645 """
1646
1647 self._oDb.execute('SELECT SchedGroups.*\n'
1648 'FROM ( SELECT idSchedGroup,\n'
1649 ' MAX(TestSets.tsCreated) AS tsNow\n'
1650 ' FROM TestSets\n'
1651 ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
1652 ' GROUP BY idSchedGroup\n'
1653 ' ) AS SchedGroupIDs\n'
1654 ' INNER JOIN SchedGroups\n'
1655 ' ON SchedGroups.idSchedGroup = SchedGroupIDs.idSchedGroup\n'
1656 ' AND SchedGroups.tsExpire > SchedGroupIDs.tsNow\n'
1657 ' AND SchedGroups.tsEffective <= SchedGroupIDs.tsNow\n'
1658 'ORDER BY SchedGroups.sName\n' );
1659 aoRet = []
1660 for aoRow in self._oDb.fetchAll():
1661 aoRet.append(SchedGroupData().initFromDbRow(aoRow));
1662 return aoRet
1663
1664 def getById(self, idTestResult):
1665 """
1666 Get build record by its id
1667 """
1668 self._oDb.execute('SELECT *\n'
1669 'FROM TestResults\n'
1670 'WHERE idTestResult = %s\n',
1671 (idTestResult,))
1672
1673 aRows = self._oDb.fetchAll()
1674 if len(aRows) not in (0, 1):
1675 raise TMTooManyRows('Found more than one test result with the same credentials. Database structure is corrupted.')
1676 try:
1677 return TestResultData().initFromDbRow(aRows[0])
1678 except IndexError:
1679 return None
1680
1681 def fetchPossibleFilterOptions(self, oFilter, tsNow, sPeriod, oReportModel = None):
1682 """
1683 Fetches the available filter criteria, given the current filtering.
1684
1685 Returns oFilter.
1686 """
1687 assert isinstance(oFilter, TestResultFilter);
1688
1689 # Hack to avoid lot's of conditionals or duplicate this code.
1690 if oReportModel is None:
1691 class DummyReportModel(object):
1692 """ Dummy """
1693 def getExtraSubjectTables(self):
1694 """ Dummy """
1695 return [];
1696 def getExtraSubjectWhereExpr(self):
1697 """ Dummy """
1698 return '';
1699 oReportModel = DummyReportModel();
1700
1701 def workerDoFetch(oMissingLogicType, sNameAttr = 'sName', fIdIsName = False, idxHover = -1,
1702 idNull = -1, sNullDesc = '<NULL>'):
1703 """ Does the tedious result fetching and handling of missing bits. """
1704 dLeft = { oValue: 1 for oValue in oCrit.aoSelected };
1705 oCrit.aoPossible = [];
1706 for aoRow in self._oDb.fetchAll():
1707 oCrit.aoPossible.append(FilterCriterionValueAndDescription(aoRow[0] if aoRow[0] is not None else idNull,
1708 aoRow[1] if aoRow[1] is not None else sNullDesc,
1709 aoRow[2],
1710 aoRow[idxHover] if idxHover >= 0 else None));
1711 if aoRow[0] in dLeft:
1712 del dLeft[aoRow[0]];
1713 if dLeft:
1714 if fIdIsName:
1715 for idMissing in dLeft:
1716 oCrit.aoPossible.append(FilterCriterionValueAndDescription(idMissing, str(idMissing),
1717 fIrrelevant = True));
1718 else:
1719 oMissingLogic = oMissingLogicType(self._oDb);
1720 for idMissing in dLeft:
1721 oMissing = oMissingLogic.cachedLookup(idMissing);
1722 if oMissing is not None:
1723 oCrit.aoPossible.append(FilterCriterionValueAndDescription(idMissing,
1724 getattr(oMissing, sNameAttr),
1725 fIrrelevant = True));
1726
1727 def workerDoFetchNested():
1728 """ Does the tedious result fetching and handling of missing bits. """
1729 oCrit.aoPossible = [];
1730 oCrit.oSub.aoPossible = [];
1731 dLeft = { oValue: 1 for oValue in oCrit.aoSelected };
1732 dSubLeft = { oValue: 1 for oValue in oCrit.oSub.aoSelected };
1733 oMain = None;
1734 for aoRow in self._oDb.fetchAll():
1735 if oMain is None or oMain.oValue != aoRow[0]:
1736 oMain = FilterCriterionValueAndDescription(aoRow[0], aoRow[1], 0);
1737 oCrit.aoPossible.append(oMain);
1738 if aoRow[0] in dLeft:
1739 del dLeft[aoRow[0]];
1740 oCurSub = FilterCriterionValueAndDescription(aoRow[2], aoRow[3], aoRow[4]);
1741 oCrit.oSub.aoPossible.append(oCurSub);
1742 if aoRow[2] in dSubLeft:
1743 del dSubLeft[aoRow[2]];
1744
1745 oMain.aoSubs.append(oCurSub);
1746 oMain.cTimes += aoRow[4];
1747
1748 if dLeft:
1749 pass; ## @todo
1750
1751 # Statuses.
1752 oCrit = oFilter.aCriteria[TestResultFilter.kiTestStatus];
1753 self._oDb.execute('SELECT TestSets.enmStatus, TestSets.enmStatus, COUNT(TestSets.idTestSet)\n'
1754 'FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiTestStatus) +
1755 ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
1756 'WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod) +
1757 oFilter.getWhereConditions(iOmit = TestResultFilter.kiTestStatus) +
1758 oReportModel.getExtraSubjectWhereExpr() +
1759 'GROUP BY TestSets.enmStatus\n'
1760 'ORDER BY TestSets.enmStatus\n');
1761 workerDoFetch(None, fIdIsName = True);
1762
1763 # Scheduling groups (see getSchedGroups).
1764 oCrit = oFilter.aCriteria[TestResultFilter.kiSchedGroups];
1765 self._oDb.execute('SELECT SchedGroups.idSchedGroup, SchedGroups.sName, SchedGroupIDs.cTimes\n'
1766 'FROM ( SELECT TestSets.idSchedGroup,\n'
1767 ' MAX(TestSets.tsCreated) AS tsNow,\n'
1768 ' COUNT(TestSets.idTestSet) AS cTimes\n'
1769 ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiSchedGroups) +
1770 ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
1771 ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
1772 oFilter.getWhereConditions(iOmit = TestResultFilter.kiSchedGroups) +
1773 oReportModel.getExtraSubjectWhereExpr() +
1774 ' GROUP BY TestSets.idSchedGroup\n'
1775 ' ) AS SchedGroupIDs\n'
1776 ' INNER JOIN SchedGroups\n'
1777 ' ON SchedGroups.idSchedGroup = SchedGroupIDs.idSchedGroup\n'
1778 ' AND SchedGroups.tsExpire > SchedGroupIDs.tsNow\n'
1779 ' AND SchedGroups.tsEffective <= SchedGroupIDs.tsNow\n'
1780 'ORDER BY SchedGroups.sName\n' );
1781 workerDoFetch(SchedGroupLogic);
1782
1783 # Testboxes (see getTestBoxes).
1784 oCrit = oFilter.aCriteria[TestResultFilter.kiTestBoxes];
1785 self._oDb.execute('SELECT TestBoxesWithStrings.idTestBox,\n'
1786 ' TestBoxesWithStrings.sName,\n'
1787 ' TestBoxIDs.cTimes\n'
1788 'FROM ( SELECT TestSets.idTestBox AS idTestBox,\n'
1789 ' MAX(TestSets.idGenTestBox) AS idGenTestBox,\n'
1790 ' COUNT(TestSets.idTestSet) AS cTimes\n'
1791 ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiTestBoxes) +
1792 ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
1793 ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
1794 oFilter.getWhereConditions(iOmit = TestResultFilter.kiTestBoxes) +
1795 oReportModel.getExtraSubjectWhereExpr() +
1796 ' GROUP BY TestSets.idTestBox\n'
1797 ' ) AS TestBoxIDs\n'
1798 ' LEFT OUTER JOIN TestBoxesWithStrings\n'
1799 ' ON TestBoxesWithStrings.idGenTestBox = TestBoxIDs.idGenTestBox\n'
1800 'ORDER BY TestBoxesWithStrings.sName\n' );
1801 workerDoFetch(TestBoxLogic);
1802
1803 # Testbox OSes and versions.
1804 oCrit = oFilter.aCriteria[TestResultFilter.kiOses];
1805 self._oDb.execute('SELECT TestBoxesWithStrings.idStrOs,\n'
1806 ' TestBoxesWithStrings.sOs,\n'
1807 ' TestBoxesWithStrings.idStrOsVersion,\n'
1808 ' TestBoxesWithStrings.sOsVersion,\n'
1809 ' SUM(TestBoxGenIDs.cTimes)\n'
1810 'FROM ( SELECT TestSets.idGenTestBox,\n'
1811 ' COUNT(TestSets.idTestSet) AS cTimes\n'
1812 ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiOses) +
1813 ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
1814 ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
1815 oFilter.getWhereConditions(iOmit = TestResultFilter.kiOses) +
1816 oReportModel.getExtraSubjectWhereExpr() +
1817 ' GROUP BY TestSets.idGenTestBox\n'
1818 ' ) AS TestBoxGenIDs\n'
1819 ' LEFT OUTER JOIN TestBoxesWithStrings\n'
1820 ' ON TestBoxesWithStrings.idGenTestBox = TestBoxGenIDs.idGenTestBox\n'
1821 'GROUP BY TestBoxesWithStrings.idStrOs,\n'
1822 ' TestBoxesWithStrings.sOs,\n'
1823 ' TestBoxesWithStrings.idStrOsVersion,\n'
1824 ' TestBoxesWithStrings.sOsVersion\n'
1825 'ORDER BY TestBoxesWithStrings.sOs,\n'
1826 ' TestBoxesWithStrings.sOs = \'win\' AND TestBoxesWithStrings.sOsVersion = \'10\' DESC,\n'
1827 ' TestBoxesWithStrings.sOsVersion DESC\n'
1828 );
1829 workerDoFetchNested();
1830
1831 # Testbox CPU(/OS) architectures.
1832 oCrit = oFilter.aCriteria[TestResultFilter.kiCpuArches];
1833 self._oDb.execute('SELECT TestBoxesWithStrings.idStrCpuArch,\n'
1834 ' TestBoxesWithStrings.sCpuArch,\n'
1835 ' SUM(TestBoxGenIDs.cTimes)\n'
1836 'FROM ( SELECT TestSets.idGenTestBox,\n'
1837 ' COUNT(TestSets.idTestSet) AS cTimes\n'
1838 ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiCpuArches) +
1839 ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
1840 ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
1841 oFilter.getWhereConditions(iOmit = TestResultFilter.kiCpuArches) +
1842 oReportModel.getExtraSubjectWhereExpr() +
1843 ' GROUP BY TestSets.idGenTestBox\n'
1844 ' ) AS TestBoxGenIDs\n'
1845 ' LEFT OUTER JOIN TestBoxesWithStrings\n'
1846 ' ON TestBoxesWithStrings.idGenTestBox = TestBoxGenIDs.idGenTestBox\n'
1847 'GROUP BY TestBoxesWithStrings.idStrCpuArch, TestBoxesWithStrings.sCpuArch\n'
1848 'ORDER BY TestBoxesWithStrings.sCpuArch\n' );
1849 workerDoFetch(None, fIdIsName = True);
1850
1851 # Testbox CPU revisions.
1852 oCrit = oFilter.aCriteria[TestResultFilter.kiCpuVendors];
1853 self._oDb.execute('SELECT TestBoxesWithStrings.idStrCpuVendor,\n'
1854 ' TestBoxesWithStrings.sCpuVendor,\n'
1855 ' TestBoxesWithStrings.lCpuRevision,\n'
1856 ' TestBoxesWithStrings.sCpuVendor,\n'
1857 ' SUM(TestBoxGenIDs.cTimes)\n'
1858 'FROM ( SELECT TestSets.idGenTestBox,\n'
1859 ' COUNT(TestSets.idTestSet) AS cTimes\n'
1860 ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiCpuVendors) +
1861 ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
1862 ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
1863 oFilter.getWhereConditions(iOmit = TestResultFilter.kiCpuVendors) +
1864 oReportModel.getExtraSubjectWhereExpr() +
1865 ' GROUP BY TestSets.idGenTestBox'
1866 ' ) AS TestBoxGenIDs\n'
1867 ' LEFT OUTER JOIN TestBoxesWithStrings\n'
1868 ' ON TestBoxesWithStrings.idGenTestBox = TestBoxGenIDs.idGenTestBox\n'
1869 'GROUP BY TestBoxesWithStrings.idStrCpuVendor,\n'
1870 ' TestBoxesWithStrings.sCpuVendor,\n'
1871 ' TestBoxesWithStrings.lCpuRevision,\n'
1872 ' TestBoxesWithStrings.sCpuVendor\n'
1873 'ORDER BY TestBoxesWithStrings.sCpuVendor DESC,\n'
1874 ' TestBoxesWithStrings.sCpuVendor = \'GenuineIntel\'\n'
1875 ' AND (TestBoxesWithStrings.lCpuRevision >> 24) = 15,\n' # P4 at the bottom is a start...
1876 ' TestBoxesWithStrings.lCpuRevision DESC\n'
1877 );
1878 workerDoFetchNested();
1879 for oCur in oCrit.oSub.aoPossible:
1880 oCur.sDesc = TestBoxData.getPrettyCpuVersionEx(oCur.oValue, oCur.sDesc).replace('_', ' ');
1881
1882 # Testbox CPU core/thread counts.
1883 oCrit = oFilter.aCriteria[TestResultFilter.kiCpuCounts];
1884 self._oDb.execute('SELECT TestBoxesWithStrings.cCpus,\n'
1885 ' CAST(TestBoxesWithStrings.cCpus AS TEXT),\n'
1886 ' SUM(TestBoxGenIDs.cTimes)\n'
1887 'FROM ( SELECT TestSets.idGenTestBox,\n'
1888 ' COUNT(TestSets.idTestSet) AS cTimes\n'
1889 ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiCpuCounts) +
1890 ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
1891 ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
1892 oFilter.getWhereConditions(iOmit = TestResultFilter.kiCpuCounts) +
1893 oReportModel.getExtraSubjectWhereExpr() +
1894 ' GROUP BY TestSets.idGenTestBox'
1895 ' ) AS TestBoxGenIDs\n'
1896 ' LEFT OUTER JOIN TestBoxesWithStrings\n'
1897 ' ON TestBoxesWithStrings.idGenTestBox = TestBoxGenIDs.idGenTestBox\n'
1898 'GROUP BY TestBoxesWithStrings.cCpus\n'
1899 'ORDER BY TestBoxesWithStrings.cCpus\n' );
1900 workerDoFetch(None, fIdIsName = True);
1901
1902 # Testbox memory.
1903 oCrit = oFilter.aCriteria[TestResultFilter.kiMemory];
1904 self._oDb.execute('SELECT TestBoxesWithStrings.cMbMemory / 1024,\n'
1905 ' NULL,\n'
1906 ' SUM(TestBoxGenIDs.cTimes)\n'
1907 'FROM ( SELECT TestSets.idGenTestBox,\n'
1908 ' COUNT(TestSets.idTestSet) AS cTimes\n'
1909 ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiMemory) +
1910 ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
1911 ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
1912 oFilter.getWhereConditions(iOmit = TestResultFilter.kiMemory) +
1913 oReportModel.getExtraSubjectWhereExpr() +
1914 ' GROUP BY TestSets.idGenTestBox'
1915 ' ) AS TestBoxGenIDs\n'
1916 ' LEFT OUTER JOIN TestBoxesWithStrings\n'
1917 ' ON TestBoxesWithStrings.idGenTestBox = TestBoxGenIDs.idGenTestBox\n'
1918 'GROUP BY TestBoxesWithStrings.cMbMemory / 1024\n'
1919 'ORDER BY 1\n' );
1920 workerDoFetch(None, fIdIsName = True);
1921 for oCur in oCrit.aoPossible:
1922 oCur.sDesc = '%u GB' % (oCur.oValue,);
1923
1924 # Testbox python versions .
1925 oCrit = oFilter.aCriteria[TestResultFilter.kiPythonVersions];
1926 self._oDb.execute('SELECT TestBoxesWithStrings.iPythonHexVersion,\n'
1927 ' NULL,\n'
1928 ' SUM(TestBoxGenIDs.cTimes)\n'
1929 'FROM ( SELECT TestSets.idGenTestBox AS idGenTestBox,\n'
1930 ' COUNT(TestSets.idTestSet) AS cTimes\n'
1931 ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiPythonVersions) +
1932 ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
1933 ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
1934 oFilter.getWhereConditions(iOmit = TestResultFilter.kiPythonVersions) +
1935 oReportModel.getExtraSubjectWhereExpr() +
1936 ' GROUP BY TestSets.idGenTestBox\n'
1937 ' ) AS TestBoxGenIDs\n'
1938 ' LEFT OUTER JOIN TestBoxesWithStrings\n'
1939 ' ON TestBoxesWithStrings.idGenTestBox = TestBoxGenIDs.idGenTestBox\n'
1940 'GROUP BY TestBoxesWithStrings.iPythonHexVersion\n'
1941 'ORDER BY TestBoxesWithStrings.iPythonHexVersion\n' );
1942 workerDoFetch(None, fIdIsName = True);
1943 for oCur in oCrit.aoPossible:
1944 oCur.sDesc = TestBoxData.formatPythonVersionEx(oCur.oValue); # pylint: disable=redefined-variable-type
1945
1946 # Testcase with variation.
1947 oCrit = oFilter.aCriteria[TestResultFilter.kiTestCases];
1948 self._oDb.execute('SELECT TestCaseArgsIDs.idTestCase,\n'
1949 ' TestCases.sName,\n'
1950 ' TestCaseArgsIDs.idTestCaseArgs,\n'
1951 ' CASE WHEN TestCaseArgs.sSubName IS NULL OR TestCaseArgs.sSubName = \'\' THEN\n'
1952 ' CONCAT(\'/ #\', TestCaseArgs.idTestCaseArgs)\n'
1953 ' ELSE\n'
1954 ' TestCaseArgs.sSubName\n'
1955 ' END,'
1956 ' TestCaseArgsIDs.cTimes\n'
1957 'FROM ( SELECT TestSets.idTestCase AS idTestCase,\n'
1958 ' TestSets.idTestCaseArgs AS idTestCaseArgs,\n'
1959 ' MAX(TestSets.idGenTestCase) AS idGenTestCase,\n'
1960 ' MAX(TestSets.idGenTestCaseArgs) AS idGenTestCaseArgs,\n'
1961 ' COUNT(TestSets.idTestSet) AS cTimes\n'
1962 ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiTestCases) +
1963 ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
1964 ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
1965 oFilter.getWhereConditions(iOmit = TestResultFilter.kiTestCases) +
1966 oReportModel.getExtraSubjectWhereExpr() +
1967 ' GROUP BY TestSets.idTestCase, TestSets.idTestCaseArgs\n'
1968 ' ) AS TestCaseArgsIDs\n'
1969 ' LEFT OUTER JOIN TestCases ON TestCases.idGenTestCase = TestCaseArgsIDs.idGenTestCase\n'
1970 ' LEFT OUTER JOIN TestCaseArgs\n'
1971 ' ON TestCaseArgs.idGenTestCaseArgs = TestCaseArgsIDs.idGenTestCaseArgs\n'
1972 'ORDER BY TestCases.sName, 4\n' );
1973 workerDoFetchNested();
1974
1975 # Build revisions.
1976 oCrit = oFilter.aCriteria[TestResultFilter.kiRevisions];
1977 self._oDb.execute('SELECT Builds.iRevision, CONCAT(\'r\', Builds.iRevision), SUM(BuildIDs.cTimes)\n'
1978 'FROM ( SELECT TestSets.idBuild AS idBuild,\n'
1979 ' MAX(TestSets.tsCreated) AS tsNow,\n'
1980 ' COUNT(TestSets.idBuild) AS cTimes\n'
1981 ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiRevisions) +
1982 ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
1983 ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
1984 oFilter.getWhereConditions(iOmit = TestResultFilter.kiRevisions) +
1985 oReportModel.getExtraSubjectWhereExpr() +
1986 ' GROUP BY TestSets.idBuild\n'
1987 ' ) AS BuildIDs\n'
1988 ' INNER JOIN Builds\n'
1989 ' ON Builds.idBuild = BuildIDs.idBuild\n'
1990 ' AND Builds.tsExpire > BuildIDs.tsNow\n'
1991 ' AND Builds.tsEffective <= BuildIDs.tsNow\n'
1992 'GROUP BY Builds.iRevision\n'
1993 'ORDER BY Builds.iRevision DESC\n' );
1994 workerDoFetch(None, fIdIsName = True);
1995
1996 # Build branches.
1997 oCrit = oFilter.aCriteria[TestResultFilter.kiBranches];
1998 self._oDb.execute('SELECT BuildCategories.sBranch, BuildCategories.sBranch, SUM(BuildCategoryIDs.cTimes)\n'
1999 'FROM ( SELECT TestSets.idBuildCategory,\n'
2000 ' COUNT(TestSets.idTestSet) AS cTimes\n'
2001 ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiBranches) +
2002 ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
2003 ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
2004 oFilter.getWhereConditions(iOmit = TestResultFilter.kiBranches) +
2005 oReportModel.getExtraSubjectWhereExpr() +
2006 ' GROUP BY TestSets.idBuildCategory\n'
2007 ' ) AS BuildCategoryIDs\n'
2008 ' INNER JOIN BuildCategories\n'
2009 ' ON BuildCategories.idBuildCategory = BuildCategoryIDs.idBuildCategory\n'
2010 'GROUP BY BuildCategories.sBranch\n'
2011 'ORDER BY BuildCategories.sBranch DESC\n' );
2012 workerDoFetch(None, fIdIsName = True);
2013
2014 # Build types.
2015 oCrit = oFilter.aCriteria[TestResultFilter.kiBuildTypes];
2016 self._oDb.execute('SELECT BuildCategories.sType, BuildCategories.sType, SUM(BuildCategoryIDs.cTimes)\n'
2017 'FROM ( SELECT TestSets.idBuildCategory,\n'
2018 ' COUNT(TestSets.idTestSet) AS cTimes\n'
2019 ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiBuildTypes) +
2020 ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
2021 ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
2022 oFilter.getWhereConditions(iOmit = TestResultFilter.kiBuildTypes) +
2023 oReportModel.getExtraSubjectWhereExpr() +
2024 ' GROUP BY TestSets.idBuildCategory\n'
2025 ' ) AS BuildCategoryIDs\n'
2026 ' INNER JOIN BuildCategories\n'
2027 ' ON BuildCategories.idBuildCategory = BuildCategoryIDs.idBuildCategory\n'
2028 'GROUP BY BuildCategories.sType\n'
2029 'ORDER BY BuildCategories.sType DESC\n' );
2030 workerDoFetch(None, fIdIsName = True);
2031
2032 # Failure reasons.
2033 oCrit = oFilter.aCriteria[TestResultFilter.kiFailReasons];
2034 self._oDb.execute('SELECT FailureReasons.idFailureReason, FailureReasons.sShort, FailureReasonIDs.cTimes\n'
2035 'FROM ( SELECT TestResultFailures.idFailureReason,\n'
2036 ' COUNT(TestSets.idTestSet) as cTimes\n'
2037 ' FROM TestSets\n'
2038 ' LEFT OUTER JOIN TestResultFailures\n'
2039 ' ON TestResultFailures.idTestSet = TestSets.idTestSet\n'
2040 ' AND TestResultFailures.tsExpire = \'infinity\'::TIMESTAMP\n' +
2041 oFilter.getTableJoins(iOmit = TestResultFilter.kiFailReasons) +
2042 ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
2043 ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
2044 ' AND TestSets.enmStatus >= \'failure\'::TestStatus_T\n' +
2045 oFilter.getWhereConditions(iOmit = TestResultFilter.kiFailReasons) +
2046 oReportModel.getExtraSubjectWhereExpr() +
2047 ' GROUP BY TestResultFailures.idFailureReason\n'
2048 ' ) AS FailureReasonIDs\n'
2049 ' LEFT OUTER JOIN FailureReasons\n'
2050 ' ON FailureReasons.idFailureReason = FailureReasonIDs.idFailureReason\n'
2051 ' AND FailureReasons.tsExpire = \'infinity\'::TIMESTAMP\n'
2052 'ORDER BY FailureReasons.idFailureReason IS NULL DESC,\n'
2053 ' FailureReasons.sShort\n' );
2054 workerDoFetch(FailureReasonLogic, 'sShort', sNullDesc = 'Not given');
2055
2056 # Error counts.
2057 oCrit = oFilter.aCriteria[TestResultFilter.kiErrorCounts];
2058 self._oDb.execute('SELECT TestResults.cErrors, CAST(TestResults.cErrors AS TEXT), COUNT(TestResults.idTestResult)\n'
2059 'FROM ( SELECT TestSets.idTestResult AS idTestResult\n'
2060 ' FROM TestSets\n' +
2061 oFilter.getTableJoins(iOmit = TestResultFilter.kiFailReasons) +
2062 ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) +
2063 ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') +
2064 oFilter.getWhereConditions(iOmit = TestResultFilter.kiFailReasons) +
2065 oReportModel.getExtraSubjectWhereExpr() +
2066 ' ) AS TestSetIDs\n'
2067 ' INNER JOIN TestResults\n'
2068 ' ON TestResults.idTestResult = TestSetIDs.idTestResult\n'
2069 'GROUP BY TestResults.cErrors\n'
2070 'ORDER BY TestResults.cErrors\n');
2071
2072 workerDoFetch(None, fIdIsName = True);
2073
2074 return oFilter;
2075
2076
2077 #
2078 # Details view and interface.
2079 #
2080
2081 def fetchResultTree(self, idTestSet, cMaxDepth = None):
2082 """
2083 Fetches the result tree for the given test set.
2084
2085 Returns a tree of TestResultDataEx nodes.
2086 Raises exception on invalid input and database issues.
2087 """
2088 # Depth first, i.e. just like the XML added them.
2089 ## @todo this still isn't performing extremely well, consider optimizations.
2090 sQuery = self._oDb.formatBindArgs(
2091 'SELECT TestResults.*,\n'
2092 ' TestResultStrTab.sValue,\n'
2093 ' EXISTS ( SELECT idTestResultValue\n'
2094 ' FROM TestResultValues\n'
2095 ' WHERE TestResultValues.idTestResult = TestResults.idTestResult ) AS fHasValues,\n'
2096 ' EXISTS ( SELECT idTestResultMsg\n'
2097 ' FROM TestResultMsgs\n'
2098 ' WHERE TestResultMsgs.idTestResult = TestResults.idTestResult ) AS fHasMsgs,\n'
2099 ' EXISTS ( SELECT idTestResultFile\n'
2100 ' FROM TestResultFiles\n'
2101 ' WHERE TestResultFiles.idTestResult = TestResults.idTestResult ) AS fHasFiles,\n'
2102 ' EXISTS ( SELECT idTestResult\n'
2103 ' FROM TestResultFailures\n'
2104 ' WHERE TestResultFailures.idTestResult = TestResults.idTestResult ) AS fHasReasons\n'
2105 'FROM TestResults, TestResultStrTab\n'
2106 'WHERE TestResults.idTestSet = %s\n'
2107 ' AND TestResults.idStrName = TestResultStrTab.idStr\n'
2108 , ( idTestSet, ));
2109 if cMaxDepth is not None:
2110 sQuery += self._oDb.formatBindArgs(' AND TestResults.iNestingDepth <= %s\n', (cMaxDepth,));
2111 sQuery += 'ORDER BY idTestResult ASC\n'
2112
2113 self._oDb.execute(sQuery);
2114 cRows = self._oDb.getRowCount();
2115 if cRows > 65536:
2116 raise TMTooManyRows('Too many rows returned for idTestSet=%d: %d' % (idTestSet, cRows,));
2117
2118 aaoRows = self._oDb.fetchAll();
2119 if not aaoRows:
2120 raise TMRowNotFound('No test results for idTestSet=%d.' % (idTestSet,));
2121
2122 # Set up the root node first.
2123 aoRow = aaoRows[0];
2124 oRoot = TestResultDataEx().initFromDbRow(aoRow);
2125 if oRoot.idTestResultParent is not None:
2126 raise self._oDb.integrityException('The root TestResult (#%s) has a parent (#%s)!'
2127 % (oRoot.idTestResult, oRoot.idTestResultParent));
2128 self._fetchResultTreeNodeExtras(oRoot, aoRow[-4], aoRow[-3], aoRow[-2], aoRow[-1]);
2129
2130 # The chilren (if any).
2131 dLookup = { oRoot.idTestResult: oRoot };
2132 oParent = oRoot;
2133 for iRow in range(1, len(aaoRows)):
2134 aoRow = aaoRows[iRow];
2135 oCur = TestResultDataEx().initFromDbRow(aoRow);
2136 self._fetchResultTreeNodeExtras(oCur, aoRow[-4], aoRow[-3], aoRow[-2], aoRow[-1]);
2137
2138 # Figure out and vet the parent.
2139 if oParent.idTestResult != oCur.idTestResultParent:
2140 oParent = dLookup.get(oCur.idTestResultParent, None);
2141 if oParent is None:
2142 raise self._oDb.integrityException('TestResult #%d is orphaned from its parent #%s.'
2143 % (oCur.idTestResult, oCur.idTestResultParent,));
2144 if oParent.iNestingDepth + 1 != oCur.iNestingDepth:
2145 raise self._oDb.integrityException('TestResult #%d has incorrect nesting depth (%d instead of %d)'
2146 % (oCur.idTestResult, oCur.iNestingDepth, oParent.iNestingDepth + 1,));
2147
2148 # Link it up.
2149 oCur.oParent = oParent;
2150 oParent.aoChildren.append(oCur);
2151 dLookup[oCur.idTestResult] = oCur;
2152
2153 return (oRoot, dLookup);
2154
2155 def _fetchResultTreeNodeExtras(self, oCurNode, fHasValues, fHasMsgs, fHasFiles, fHasReasons):
2156 """
2157 fetchResultTree worker that fetches values, message and files for the
2158 specified node.
2159 """
2160 assert(oCurNode.aoValues == []);
2161 assert(oCurNode.aoMsgs == []);
2162 assert(oCurNode.aoFiles == []);
2163 assert(oCurNode.oReason is None);
2164
2165 if fHasValues:
2166 self._oDb.execute('SELECT TestResultValues.*,\n'
2167 ' TestResultStrTab.sValue\n'
2168 'FROM TestResultValues, TestResultStrTab\n'
2169 'WHERE TestResultValues.idTestResult = %s\n'
2170 ' AND TestResultValues.idStrName = TestResultStrTab.idStr\n'
2171 'ORDER BY idTestResultValue ASC\n'
2172 , ( oCurNode.idTestResult, ));
2173 for aoRow in self._oDb.fetchAll():
2174 oCurNode.aoValues.append(TestResultValueDataEx().initFromDbRow(aoRow));
2175
2176 if fHasMsgs:
2177 self._oDb.execute('SELECT TestResultMsgs.*,\n'
2178 ' TestResultStrTab.sValue\n'
2179 'FROM TestResultMsgs, TestResultStrTab\n'
2180 'WHERE TestResultMsgs.idTestResult = %s\n'
2181 ' AND TestResultMsgs.idStrMsg = TestResultStrTab.idStr\n'
2182 'ORDER BY idTestResultMsg ASC\n'
2183 , ( oCurNode.idTestResult, ));
2184 for aoRow in self._oDb.fetchAll():
2185 oCurNode.aoMsgs.append(TestResultMsgDataEx().initFromDbRow(aoRow));
2186
2187 if fHasFiles:
2188 self._oDb.execute('SELECT TestResultFiles.*,\n'
2189 ' StrTabFile.sValue AS sFile,\n'
2190 ' StrTabDesc.sValue AS sDescription,\n'
2191 ' StrTabKind.sValue AS sKind,\n'
2192 ' StrTabMime.sValue AS sMime\n'
2193 'FROM TestResultFiles,\n'
2194 ' TestResultStrTab AS StrTabFile,\n'
2195 ' TestResultStrTab AS StrTabDesc,\n'
2196 ' TestResultStrTab AS StrTabKind,\n'
2197 ' TestResultStrTab AS StrTabMime\n'
2198 'WHERE TestResultFiles.idTestResult = %s\n'
2199 ' AND TestResultFiles.idStrFile = StrTabFile.idStr\n'
2200 ' AND TestResultFiles.idStrDescription = StrTabDesc.idStr\n'
2201 ' AND TestResultFiles.idStrKind = StrTabKind.idStr\n'
2202 ' AND TestResultFiles.idStrMime = StrTabMime.idStr\n'
2203 'ORDER BY idTestResultFile ASC\n'
2204 , ( oCurNode.idTestResult, ));
2205 for aoRow in self._oDb.fetchAll():
2206 oCurNode.aoFiles.append(TestResultFileDataEx().initFromDbRow(aoRow));
2207
2208 if fHasReasons or True:
2209 if self.oFailureReasonLogic is None:
2210 self.oFailureReasonLogic = FailureReasonLogic(self._oDb);
2211 if self.oUserAccountLogic is None:
2212 self.oUserAccountLogic = UserAccountLogic(self._oDb);
2213 self._oDb.execute('SELECT *\n'
2214 'FROM TestResultFailures\n'
2215 'WHERE idTestResult = %s\n'
2216 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
2217 , ( oCurNode.idTestResult, ));
2218 if self._oDb.getRowCount() > 0:
2219 oCurNode.oReason = TestResultFailureDataEx().initFromDbRowEx(self._oDb.fetchOne(), self.oFailureReasonLogic,
2220 self.oUserAccountLogic);
2221
2222 return True;
2223
2224
2225
2226 #
2227 # TestBoxController interface(s).
2228 #
2229
2230 def _inhumeTestResults(self, aoStack, idTestSet, sError):
2231 """
2232 The test produces too much output, kill and bury it.
2233
2234 Note! We leave the test set open, only the test result records are
2235 completed. Thus, _getResultStack will return an empty stack and
2236 cause XML processing to fail immediately, while we can still
2237 record when it actually completed in the test set the normal way.
2238 """
2239 self._oDb.dprint('** _inhumeTestResults: idTestSet=%d\n%s' % (idTestSet, self._stringifyStack(aoStack),));
2240
2241 #
2242 # First add a message.
2243 #
2244 self._newFailureDetails(aoStack[0].idTestResult, idTestSet, sError, None);
2245
2246 #
2247 # The complete all open test results.
2248 #
2249 for oTestResult in aoStack:
2250 oTestResult.cErrors += 1;
2251 self._completeTestResults(oTestResult, None, TestResultData.ksTestStatus_Failure, oTestResult.cErrors);
2252
2253 # A bit of paranoia.
2254 self._oDb.execute('UPDATE TestResults\n'
2255 'SET cErrors = cErrors + 1,\n'
2256 ' enmStatus = \'failure\'::TestStatus_T,\n'
2257 ' tsElapsed = CURRENT_TIMESTAMP - tsCreated\n'
2258 'WHERE idTestSet = %s\n'
2259 ' AND enmStatus = \'running\'::TestStatus_T\n'
2260 , ( idTestSet, ));
2261 self._oDb.commit();
2262
2263 return None;
2264
2265 def strTabString(self, sString, fCommit = False):
2266 """
2267 Gets the string table id for the given string, adding it if new.
2268
2269 Note! A copy of this code is also in TestSetLogic.
2270 """
2271 ## @todo move this and make a stored procedure for it.
2272 self._oDb.execute('SELECT idStr\n'
2273 'FROM TestResultStrTab\n'
2274 'WHERE sValue = %s'
2275 , (sString,));
2276 if self._oDb.getRowCount() == 0:
2277 self._oDb.execute('INSERT INTO TestResultStrTab (sValue)\n'
2278 'VALUES (%s)\n'
2279 'RETURNING idStr\n'
2280 , (sString,));
2281 if fCommit:
2282 self._oDb.commit();
2283 return self._oDb.fetchOne()[0];
2284
2285 @staticmethod
2286 def _stringifyStack(aoStack):
2287 """Returns a string rep of the stack."""
2288 sRet = '';
2289 for i, _ in enumerate(aoStack):
2290 sRet += 'aoStack[%d]=%s\n' % (i, aoStack[i]);
2291 return sRet;
2292
2293 def _getResultStack(self, idTestSet):
2294 """
2295 Gets the current stack of result sets.
2296 """
2297 self._oDb.execute('SELECT *\n'
2298 'FROM TestResults\n'
2299 'WHERE idTestSet = %s\n'
2300 ' AND enmStatus = \'running\'::TestStatus_T\n'
2301 'ORDER BY idTestResult DESC'
2302 , ( idTestSet, ));
2303 aoStack = [];
2304 for aoRow in self._oDb.fetchAll():
2305 aoStack.append(TestResultData().initFromDbRow(aoRow));
2306
2307 for i, _ in enumerate(aoStack):
2308 assert aoStack[i].iNestingDepth == len(aoStack) - i - 1, self._stringifyStack(aoStack);
2309
2310 return aoStack;
2311
2312 def _newTestResult(self, idTestResultParent, idTestSet, iNestingDepth, tsCreated, sName, dCounts, fCommit = False):
2313 """
2314 Creates a new test result.
2315 Returns the TestResultData object for the new record.
2316 May raise exception on database error.
2317 """
2318 assert idTestResultParent is not None;
2319 assert idTestResultParent > 1;
2320
2321 #
2322 # This isn't necessarily very efficient, but it's necessary to prevent
2323 # a wild test or testbox from filling up the database.
2324 #
2325 sCountName = 'cTestResults';
2326 if sCountName not in dCounts:
2327 self._oDb.execute('SELECT COUNT(idTestResult)\n'
2328 'FROM TestResults\n'
2329 'WHERE idTestSet = %s\n'
2330 , ( idTestSet,));
2331 dCounts[sCountName] = self._oDb.fetchOne()[0];
2332 dCounts[sCountName] += 1;
2333 if dCounts[sCountName] > config.g_kcMaxTestResultsPerTS:
2334 raise TestResultHangingOffence('Too many sub-tests in total!');
2335
2336 sCountName = 'cTestResultsIn%d' % (idTestResultParent,);
2337 if sCountName not in dCounts:
2338 self._oDb.execute('SELECT COUNT(idTestResult)\n'
2339 'FROM TestResults\n'
2340 'WHERE idTestResultParent = %s\n'
2341 , ( idTestResultParent,));
2342 dCounts[sCountName] = self._oDb.fetchOne()[0];
2343 dCounts[sCountName] += 1;
2344 if dCounts[sCountName] > config.g_kcMaxTestResultsPerTR:
2345 raise TestResultHangingOffence('Too many immediate sub-tests!');
2346
2347 # This is also a hanging offence.
2348 if iNestingDepth > config.g_kcMaxTestResultDepth:
2349 raise TestResultHangingOffence('To deep sub-test nesting!');
2350
2351 # Ditto.
2352 if len(sName) > config.g_kcchMaxTestResultName:
2353 raise TestResultHangingOffence('Test name is too long: %d chars - "%s"' % (len(sName), sName));
2354
2355 #
2356 # Within bounds, do the job.
2357 #
2358 idStrName = self.strTabString(sName, fCommit);
2359 self._oDb.execute('INSERT INTO TestResults (\n'
2360 ' idTestResultParent,\n'
2361 ' idTestSet,\n'
2362 ' tsCreated,\n'
2363 ' idStrName,\n'
2364 ' iNestingDepth )\n'
2365 'VALUES (%s, %s, TIMESTAMP WITH TIME ZONE %s, %s, %s)\n'
2366 'RETURNING *\n'
2367 , ( idTestResultParent, idTestSet, tsCreated, idStrName, iNestingDepth) )
2368 oData = TestResultData().initFromDbRow(self._oDb.fetchOne());
2369
2370 self._oDb.maybeCommit(fCommit);
2371 return oData;
2372
2373 def _newTestValue(self, idTestResult, idTestSet, sName, lValue, sUnit, dCounts, tsCreated = None, fCommit = False):
2374 """
2375 Creates a test value.
2376 May raise exception on database error.
2377 """
2378
2379 #
2380 # Bounds checking.
2381 #
2382 sCountName = 'cTestValues';
2383 if sCountName not in dCounts:
2384 self._oDb.execute('SELECT COUNT(idTestResultValue)\n'
2385 'FROM TestResultValues, TestResults\n'
2386 'WHERE TestResultValues.idTestResult = TestResults.idTestResult\n'
2387 ' AND TestResults.idTestSet = %s\n'
2388 , ( idTestSet,));
2389 dCounts[sCountName] = self._oDb.fetchOne()[0];
2390 dCounts[sCountName] += 1;
2391 if dCounts[sCountName] > config.g_kcMaxTestValuesPerTS:
2392 raise TestResultHangingOffence('Too many values in total!');
2393
2394 sCountName = 'cTestValuesIn%d' % (idTestResult,);
2395 if sCountName not in dCounts:
2396 self._oDb.execute('SELECT COUNT(idTestResultValue)\n'
2397 'FROM TestResultValues\n'
2398 'WHERE idTestResult = %s\n'
2399 , ( idTestResult,));
2400 dCounts[sCountName] = self._oDb.fetchOne()[0];
2401 dCounts[sCountName] += 1;
2402 if dCounts[sCountName] > config.g_kcMaxTestValuesPerTR:
2403 raise TestResultHangingOffence('Too many immediate values for one test result!');
2404
2405 if len(sName) > config.g_kcchMaxTestValueName:
2406 raise TestResultHangingOffence('Value name is too long: %d chars - "%s"' % (len(sName), sName));
2407
2408 #
2409 # Do the job.
2410 #
2411 iUnit = constants.valueunit.g_kdNameToConst.get(sUnit, constants.valueunit.NONE);
2412
2413 idStrName = self.strTabString(sName, fCommit);
2414 if tsCreated is None:
2415 self._oDb.execute('INSERT INTO TestResultValues (\n'
2416 ' idTestResult,\n'
2417 ' idTestSet,\n'
2418 ' idStrName,\n'
2419 ' lValue,\n'
2420 ' iUnit)\n'
2421 'VALUES ( %s, %s, %s, %s, %s )\n'
2422 , ( idTestResult, idTestSet, idStrName, lValue, iUnit,) );
2423 else:
2424 self._oDb.execute('INSERT INTO TestResultValues (\n'
2425 ' idTestResult,\n'
2426 ' idTestSet,\n'
2427 ' tsCreated,\n'
2428 ' idStrName,\n'
2429 ' lValue,\n'
2430 ' iUnit)\n'
2431 'VALUES ( %s, %s, TIMESTAMP WITH TIME ZONE %s, %s, %s, %s )\n'
2432 , ( idTestResult, idTestSet, tsCreated, idStrName, lValue, iUnit,) );
2433 self._oDb.maybeCommit(fCommit);
2434 return True;
2435
2436 def _newFailureDetails(self, idTestResult, idTestSet, sText, dCounts, tsCreated = None, fCommit = False):
2437 """
2438 Creates a record detailing cause of failure.
2439 May raise exception on database error.
2440 """
2441
2442 #
2443 # Overflow protection.
2444 #
2445 if dCounts is not None:
2446 sCountName = 'cTestMsgsIn%d' % (idTestResult,);
2447 if sCountName not in dCounts:
2448 self._oDb.execute('SELECT COUNT(idTestResultMsg)\n'
2449 'FROM TestResultMsgs\n'
2450 'WHERE idTestResult = %s\n'
2451 , ( idTestResult,));
2452 dCounts[sCountName] = self._oDb.fetchOne()[0];
2453 dCounts[sCountName] += 1;
2454 if dCounts[sCountName] > config.g_kcMaxTestMsgsPerTR:
2455 raise TestResultHangingOffence('Too many messages under for one test result!');
2456
2457 if len(sText) > config.g_kcchMaxTestMsg:
2458 raise TestResultHangingOffence('Failure details message is too long: %d chars - "%s"' % (len(sText), sText));
2459
2460 #
2461 # Do the job.
2462 #
2463 idStrMsg = self.strTabString(sText, fCommit);
2464 if tsCreated is None:
2465 self._oDb.execute('INSERT INTO TestResultMsgs (\n'
2466 ' idTestResult,\n'
2467 ' idTestSet,\n'
2468 ' idStrMsg,\n'
2469 ' enmLevel)\n'
2470 'VALUES ( %s, %s, %s, %s)\n'
2471 , ( idTestResult, idTestSet, idStrMsg, 'failure',) );
2472 else:
2473 self._oDb.execute('INSERT INTO TestResultMsgs (\n'
2474 ' idTestResult,\n'
2475 ' idTestSet,\n'
2476 ' tsCreated,\n'
2477 ' idStrMsg,\n'
2478 ' enmLevel)\n'
2479 'VALUES ( %s, %s, TIMESTAMP WITH TIME ZONE %s, %s, %s)\n'
2480 , ( idTestResult, idTestSet, tsCreated, idStrMsg, 'failure',) );
2481
2482 self._oDb.maybeCommit(fCommit);
2483 return True;
2484
2485
2486 def _completeTestResults(self, oTestResult, tsDone, enmStatus, cErrors = 0, fCommit = False):
2487 """
2488 Completes a test result. Updates the oTestResult object.
2489 May raise exception on database error.
2490 """
2491 self._oDb.dprint('** _completeTestResults: cErrors=%s tsDone=%s enmStatus=%s oTestResults=\n%s'
2492 % (cErrors, tsDone, enmStatus, oTestResult,));
2493
2494 #
2495 # Sanity check: No open sub tests (aoStack should make sure about this!).
2496 #
2497 self._oDb.execute('SELECT COUNT(idTestResult)\n'
2498 'FROM TestResults\n'
2499 'WHERE idTestResultParent = %s\n'
2500 ' AND enmStatus = %s\n'
2501 , ( oTestResult.idTestResult, TestResultData.ksTestStatus_Running,));
2502 cOpenSubTest = self._oDb.fetchOne()[0];
2503 assert cOpenSubTest == 0, 'cOpenSubTest=%d - %s' % (cOpenSubTest, oTestResult,);
2504 assert oTestResult.enmStatus == TestResultData.ksTestStatus_Running;
2505
2506 #
2507 # Make sure the reporter isn't lying about successes or error counts.
2508 #
2509 self._oDb.execute('SELECT COALESCE(SUM(cErrors), 0)\n'
2510 'FROM TestResults\n'
2511 'WHERE idTestResultParent = %s\n'
2512 , ( oTestResult.idTestResult, ));
2513 cMinErrors = self._oDb.fetchOne()[0] + oTestResult.cErrors;
2514 if cErrors < cMinErrors:
2515 cErrors = cMinErrors;
2516 if cErrors > 0 and enmStatus == TestResultData.ksTestStatus_Success:
2517 enmStatus = TestResultData.ksTestStatus_Failure
2518
2519 #
2520 # Do the update.
2521 #
2522 if tsDone is None:
2523 self._oDb.execute('UPDATE TestResults\n'
2524 'SET cErrors = %s,\n'
2525 ' enmStatus = %s,\n'
2526 ' tsElapsed = CURRENT_TIMESTAMP - tsCreated\n'
2527 'WHERE idTestResult = %s\n'
2528 'RETURNING tsElapsed'
2529 , ( cErrors, enmStatus, oTestResult.idTestResult,) );
2530 else:
2531 self._oDb.execute('UPDATE TestResults\n'
2532 'SET cErrors = %s,\n'
2533 ' enmStatus = %s,\n'
2534 ' tsElapsed = TIMESTAMP WITH TIME ZONE %s - tsCreated\n'
2535 'WHERE idTestResult = %s\n'
2536 'RETURNING tsElapsed'
2537 , ( cErrors, enmStatus, tsDone, oTestResult.idTestResult,) );
2538
2539 oTestResult.tsElapsed = self._oDb.fetchOne()[0];
2540 oTestResult.enmStatus = enmStatus;
2541 oTestResult.cErrors = cErrors;
2542
2543 self._oDb.maybeCommit(fCommit);
2544 return None;
2545
2546 def _doPopHint(self, aoStack, cStackEntries, dCounts, idTestSet):
2547 """ Executes a PopHint. """
2548 assert cStackEntries >= 0;
2549 while len(aoStack) > cStackEntries:
2550 if aoStack[0].enmStatus == TestResultData.ksTestStatus_Running:
2551 self._newFailureDetails(aoStack[0].idTestResult, idTestSet, 'XML error: Missing </Test>', dCounts);
2552 self._completeTestResults(aoStack[0], tsDone = None, cErrors = 1,
2553 enmStatus = TestResultData.ksTestStatus_Failure, fCommit = True);
2554 aoStack.pop(0);
2555 return True;
2556
2557
2558 @staticmethod
2559 def _validateElement(sName, dAttribs, fClosed):
2560 """
2561 Validates an element and its attributes.
2562 """
2563
2564 #
2565 # Validate attributes by name.
2566 #
2567
2568 # Validate integer attributes.
2569 for sAttr in [ 'errors', 'testdepth' ]:
2570 if sAttr in dAttribs:
2571 try:
2572 _ = int(dAttribs[sAttr]);
2573 except:
2574 return 'Element %s has an invalid %s attribute value: %s.' % (sName, sAttr, dAttribs[sAttr],);
2575
2576 # Validate long attributes.
2577 for sAttr in [ 'value', ]:
2578 if sAttr in dAttribs:
2579 try:
2580 _ = long(dAttribs[sAttr]); # pylint: disable=redefined-variable-type
2581 except:
2582 return 'Element %s has an invalid %s attribute value: %s.' % (sName, sAttr, dAttribs[sAttr],);
2583
2584 # Validate string attributes.
2585 for sAttr in [ 'name', 'text' ]: # 'unit' can be zero length.
2586 if sAttr in dAttribs and not dAttribs[sAttr]:
2587 return 'Element %s has an empty %s attribute value.' % (sName, sAttr,);
2588
2589 # Validate the timestamp attribute.
2590 if 'timestamp' in dAttribs:
2591 (dAttribs['timestamp'], sError) = ModelDataBase.validateTs(dAttribs['timestamp'], fAllowNull = False);
2592 if sError is not None:
2593 return 'Element %s has an invalid timestamp ("%s"): %s' % (sName, dAttribs['timestamp'], sError,);
2594
2595
2596 #
2597 # Check that attributes that are required are present.
2598 # We ignore extra attributes.
2599 #
2600 dElementAttribs = \
2601 {
2602 'Test': [ 'timestamp', 'name', ],
2603 'Value': [ 'timestamp', 'name', 'unit', 'value', ],
2604 'FailureDetails': [ 'timestamp', 'text', ],
2605 'Passed': [ 'timestamp', ],
2606 'Skipped': [ 'timestamp', ],
2607 'Failed': [ 'timestamp', 'errors', ],
2608 'TimedOut': [ 'timestamp', 'errors', ],
2609 'End': [ 'timestamp', ],
2610 'PushHint': [ 'testdepth', ],
2611 'PopHint': [ 'testdepth', ],
2612 };
2613 if sName not in dElementAttribs:
2614 return 'Unknown element "%s".' % (sName,);
2615 for sAttr in dElementAttribs[sName]:
2616 if sAttr not in dAttribs:
2617 return 'Element %s requires attribute "%s".' % (sName, sAttr);
2618
2619 #
2620 # Only the Test element can (and must) remain open.
2621 #
2622 if sName == 'Test' and fClosed:
2623 return '<Test/> is not allowed.';
2624 if sName != 'Test' and not fClosed:
2625 return 'All elements except <Test> must be closed.';
2626
2627 return None;
2628
2629 @staticmethod
2630 def _parseElement(sElement):
2631 """
2632 Parses an element.
2633
2634 """
2635 #
2636 # Element level bits.
2637 #
2638 sName = sElement.split()[0];
2639 sElement = sElement[len(sName):];
2640
2641 fClosed = sElement[-1] == '/';
2642 if fClosed:
2643 sElement = sElement[:-1];
2644
2645 #
2646 # Attributes.
2647 #
2648 sError = None;
2649 dAttribs = {};
2650 sElement = sElement.strip();
2651 while sElement:
2652 # Extract attribute name.
2653 off = sElement.find('=');
2654 if off < 0 or not sElement[:off].isalnum():
2655 sError = 'Attributes shall have alpha numberical names and have values.';
2656 break;
2657 sAttr = sElement[:off];
2658
2659 # Extract attribute value.
2660 if off + 2 >= len(sElement) or sElement[off + 1] != '"':
2661 sError = 'Attribute (%s) value is missing or not in double quotes.' % (sAttr,);
2662 break;
2663 off += 2;
2664 offEndQuote = sElement.find('"', off);
2665 if offEndQuote < 0:
2666 sError = 'Attribute (%s) value is missing end quotation mark.' % (sAttr,);
2667 break;
2668 sValue = sElement[off:offEndQuote];
2669
2670 # Check for duplicates.
2671 if sAttr in dAttribs:
2672 sError = 'Attribute "%s" appears more than once.' % (sAttr,);
2673 break;
2674
2675 # Unescape the value.
2676 sValue = sValue.replace('&lt;', '<');
2677 sValue = sValue.replace('&gt;', '>');
2678 sValue = sValue.replace('&apos;', '\'');
2679 sValue = sValue.replace('&quot;', '"');
2680 sValue = sValue.replace('&#xA;', '\n');
2681 sValue = sValue.replace('&#xD;', '\r');
2682 sValue = sValue.replace('&amp;', '&'); # last
2683
2684 # Done.
2685 dAttribs[sAttr] = sValue;
2686
2687 # advance
2688 sElement = sElement[offEndQuote + 1:];
2689 sElement = sElement.lstrip();
2690
2691 #
2692 # Validate the element before we return.
2693 #
2694 if sError is None:
2695 sError = TestResultLogic._validateElement(sName, dAttribs, fClosed);
2696
2697 return (sName, dAttribs, sError)
2698
2699 def _handleElement(self, sName, dAttribs, idTestSet, aoStack, aaiHints, dCounts):
2700 """
2701 Worker for processXmlStream that handles one element.
2702
2703 Returns None on success, error string on bad XML or similar.
2704 Raises exception on hanging offence and on database error.
2705 """
2706 if sName == 'Test':
2707 iNestingDepth = aoStack[0].iNestingDepth + 1 if aoStack else 0;
2708 aoStack.insert(0, self._newTestResult(idTestResultParent = aoStack[0].idTestResult, idTestSet = idTestSet,
2709 tsCreated = dAttribs['timestamp'], sName = dAttribs['name'],
2710 iNestingDepth = iNestingDepth, dCounts = dCounts, fCommit = True) );
2711
2712 elif sName == 'Value':
2713 self._newTestValue(idTestResult = aoStack[0].idTestResult, idTestSet = idTestSet, tsCreated = dAttribs['timestamp'],
2714 sName = dAttribs['name'], sUnit = dAttribs['unit'], lValue = long(dAttribs['value']),
2715 dCounts = dCounts, fCommit = True);
2716
2717 elif sName == 'FailureDetails':
2718 self._newFailureDetails(idTestResult = aoStack[0].idTestResult, idTestSet = idTestSet,
2719 tsCreated = dAttribs['timestamp'], sText = dAttribs['text'], dCounts = dCounts,
2720 fCommit = True);
2721
2722 elif sName == 'Passed':
2723 self._completeTestResults(aoStack[0], tsDone = dAttribs['timestamp'],
2724 enmStatus = TestResultData.ksTestStatus_Success, fCommit = True);
2725
2726 elif sName == 'Skipped':
2727 self._completeTestResults(aoStack[0], tsDone = dAttribs['timestamp'],
2728 enmStatus = TestResultData.ksTestStatus_Skipped, fCommit = True);
2729
2730 elif sName == 'Failed':
2731 self._completeTestResults(aoStack[0], tsDone = dAttribs['timestamp'], cErrors = int(dAttribs['errors']),
2732 enmStatus = TestResultData.ksTestStatus_Failure, fCommit = True);
2733
2734 elif sName == 'TimedOut':
2735 self._completeTestResults(aoStack[0], tsDone = dAttribs['timestamp'], cErrors = int(dAttribs['errors']),
2736 enmStatus = TestResultData.ksTestStatus_TimedOut, fCommit = True);
2737
2738 elif sName == 'End':
2739 self._completeTestResults(aoStack[0], tsDone = dAttribs['timestamp'],
2740 cErrors = int(dAttribs.get('errors', '1')),
2741 enmStatus = TestResultData.ksTestStatus_Success, fCommit = True);
2742
2743 elif sName == 'PushHint':
2744 if len(aaiHints) > 1:
2745 return 'PushHint cannot be nested.'
2746
2747 aaiHints.insert(0, [len(aoStack), int(dAttribs['testdepth'])]);
2748
2749 elif sName == 'PopHint':
2750 if not aaiHints:
2751 return 'No hint to pop.'
2752
2753 iDesiredTestDepth = int(dAttribs['testdepth']);
2754 cStackEntries, iTestDepth = aaiHints.pop(0);
2755 self._doPopHint(aoStack, cStackEntries, dCounts, idTestSet); # Fake the necessary '<End/></Test>' tags.
2756 if iDesiredTestDepth != iTestDepth:
2757 return 'PopHint tag has different testdepth: %d, on stack %d.' % (iDesiredTestDepth, iTestDepth);
2758 else:
2759 return 'Unexpected element "%s".' % (sName,);
2760 return None;
2761
2762
2763 def processXmlStream(self, sXml, idTestSet):
2764 """
2765 Processes the "XML" stream section given in sXml.
2766
2767 The sXml isn't a complete XML document, even should we save up all sXml
2768 for a given set, they may not form a complete and well formed XML
2769 document since the test may be aborted, abend or simply be buggy. We
2770 therefore do our own parsing and treat the XML tags as commands more
2771 than anything else.
2772
2773 Returns (sError, fUnforgivable), where sError is None on success.
2774 May raise database exception.
2775 """
2776 aoStack = self._getResultStack(idTestSet); # [0] == top; [-1] == bottom.
2777 if not aoStack:
2778 return ('No open results', True);
2779 self._oDb.dprint('** processXmlStream len(aoStack)=%s' % (len(aoStack),));
2780 #self._oDb.dprint('processXmlStream: %s' % (self._stringifyStack(aoStack),));
2781 #self._oDb.dprint('processXmlStream: sXml=%s' % (sXml,));
2782
2783 dCounts = {};
2784 aaiHints = [];
2785 sError = None;
2786
2787 fExpectCloseTest = False;
2788 sXml = sXml.strip();
2789 while sXml:
2790 if sXml.startswith('</Test>'): # Only closing tag.
2791 offNext = len('</Test>');
2792 if len(aoStack) <= 1:
2793 sError = 'Trying to close the top test results.'
2794 break;
2795 # ASSUMES that we've just seen an <End/>, <Passed/>, <Failed/>,
2796 # <TimedOut/> or <Skipped/> tag earlier in this call!
2797 if aoStack[0].enmStatus == TestResultData.ksTestStatus_Running or not fExpectCloseTest:
2798 sError = 'Missing <End/>, <Passed/>, <Failed/>, <TimedOut/> or <Skipped/> tag.';
2799 break;
2800 aoStack.pop(0);
2801 fExpectCloseTest = False;
2802
2803 elif fExpectCloseTest:
2804 sError = 'Expected </Test>.'
2805 break;
2806
2807 elif sXml.startswith('<?xml '): # Ignore (included files).
2808 offNext = sXml.find('?>');
2809 if offNext < 0:
2810 sError = 'Unterminated <?xml ?> element.';
2811 break;
2812 offNext += 2;
2813
2814 elif sXml[0] == '<':
2815 # Parse and check the tag.
2816 if not sXml[1].isalpha():
2817 sError = 'Malformed element.';
2818 break;
2819 offNext = sXml.find('>')
2820 if offNext < 0:
2821 sError = 'Unterminated element.';
2822 break;
2823 (sName, dAttribs, sError) = self._parseElement(sXml[1:offNext]);
2824 offNext += 1;
2825 if sError is not None:
2826 break;
2827
2828 # Handle it.
2829 try:
2830 sError = self._handleElement(sName, dAttribs, idTestSet, aoStack, aaiHints, dCounts);
2831 except TestResultHangingOffence as oXcpt:
2832 self._inhumeTestResults(aoStack, idTestSet, str(oXcpt));
2833 return (str(oXcpt), True);
2834
2835
2836 fExpectCloseTest = sName in [ 'End', 'Passed', 'Failed', 'TimedOut', 'Skipped', ];
2837 else:
2838 sError = 'Unexpected content.';
2839 break;
2840
2841 # Advance.
2842 sXml = sXml[offNext:];
2843 sXml = sXml.lstrip();
2844
2845 #
2846 # Post processing checks.
2847 #
2848 if sError is None and fExpectCloseTest:
2849 sError = 'Expected </Test> before the end of the XML section.'
2850 elif sError is None and aaiHints:
2851 sError = 'Expected </PopHint> before the end of the XML section.'
2852 if aaiHints:
2853 self._doPopHint(aoStack, aaiHints[-1][0], dCounts, idTestSet);
2854
2855 #
2856 # Log the error.
2857 #
2858 if sError is not None:
2859 SystemLogLogic(self._oDb).addEntry(SystemLogData.ksEvent_XmlResultMalformed,
2860 'idTestSet=%s idTestResult=%s XML="%s" %s'
2861 % ( idTestSet,
2862 aoStack[0].idTestResult if aoStack else -1,
2863 sXml[:min(len(sXml), 30)],
2864 sError, ),
2865 cHoursRepeat = 6, fCommit = True);
2866 return (sError, False);
2867
2868
2869
2870
2871
2872#
2873# Unit testing.
2874#
2875
2876# pylint: disable=missing-docstring
2877class TestResultDataTestCase(ModelDataBaseTestCase):
2878 def setUp(self):
2879 self.aoSamples = [TestResultData(),];
2880
2881class TestResultValueDataTestCase(ModelDataBaseTestCase):
2882 def setUp(self):
2883 self.aoSamples = [TestResultValueData(),];
2884
2885if __name__ == '__main__':
2886 unittest.main();
2887 # not reached.
2888
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